├── .document ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── app ├── controllers │ └── c2 │ │ ├── base_controller.rb │ │ ├── informant │ │ ├── app_controller.rb │ │ ├── buckets_controller.rb │ │ ├── entries_controller.rb │ │ └── locus_controller.rb │ │ └── reporter │ │ └── app_controller.rb ├── models │ └── c2 │ │ ├── informant │ │ ├── bucket.rb │ │ ├── form_element.rb │ │ └── locus.rb │ │ └── reporter │ │ ├── bucket.rb │ │ └── report.rb ├── stylesheets │ └── c2.scss └── views │ ├── c2 │ ├── informant │ │ └── app │ │ │ ├── _entry_index.html.haml │ │ │ ├── _entry_index_aside.html.haml │ │ │ ├── _sidebar.html.haml │ │ │ └── show.html.haml │ └── reporter │ │ └── app │ │ ├── _sidebar.html.haml │ │ └── show.html.haml │ └── layouts │ └── c2.html.haml ├── c2.gemspec ├── config ├── initializers │ └── c2_inflections.rb ├── mongoid.yml └── routes.rb ├── lib ├── c2.rb ├── c2 │ ├── controller_additions.rb │ ├── engine.rb │ ├── exceptions.rb │ └── tasks.rake └── generators │ └── c2 │ └── install_generator.rb ├── public ├── images │ └── c2 │ │ ├── alert-overlay.png │ │ ├── back-light.png │ │ ├── bg-body.png │ │ ├── bg-header.png │ │ ├── categories-small.png │ │ ├── down-arrow.png │ │ ├── entries-small.png │ │ ├── external-link.png │ │ ├── eye.png │ │ ├── loading.gif │ │ ├── new-category.png │ │ ├── new-entry.png │ │ ├── new-section.png │ │ ├── ops.png │ │ └── trashcan.png ├── javascripts │ └── c2 │ │ ├── common.js │ │ ├── informant │ │ ├── application.js │ │ ├── collections │ │ │ ├── buckets.js │ │ │ ├── entries.js │ │ │ └── locus.js │ │ ├── controllers │ │ │ └── app.js │ │ ├── init.js │ │ ├── models │ │ │ ├── bucket.js │ │ │ ├── entry.js │ │ │ └── locus.js │ │ └── views │ │ │ ├── entries │ │ │ ├── edit.js │ │ │ └── index.js │ │ │ ├── notice.js │ │ │ └── sidebar.js │ │ ├── lib │ │ ├── Sexy.min.js │ │ ├── backbone-min.js │ │ ├── grid.js │ │ ├── jquery.Sexy.min.js │ │ ├── jquery.activity-indicator-1.0.0.min.js │ │ ├── jquery.ba-dotimeout.min.js │ │ ├── jquery.dform-0.1.2.min.js │ │ ├── jquery.min.js │ │ ├── jquery.timeago.js │ │ ├── pure_min.js │ │ └── underscore.min.js │ │ ├── reporter_lib.js │ │ ├── reporter_templates.js │ │ ├── reporter_vertebrae.js │ │ └── vertebrae.js └── stylesheets │ └── c2.css └── spec ├── dummy ├── .gitignore ├── .powrc ├── README ├── Rakefile ├── app │ ├── controllers │ │ └── application_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── models │ │ └── user.rb │ └── views │ │ └── layouts │ │ └── application.html.erb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ ├── mongoid.yml │ └── routes.rb ├── db │ └── seeds.rb ├── doc │ └── README_FOR_APP ├── lib │ └── tasks │ │ └── .gitkeep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ ├── images │ │ └── rails.png │ ├── index.html │ ├── javascripts │ │ ├── application.js │ │ ├── controls.js │ │ ├── dragdrop.js │ │ ├── effects.js │ │ ├── prototype.js │ │ └── rails.js │ ├── robots.txt │ └── stylesheets │ │ └── .gitkeep ├── script │ └── rails ├── test │ ├── performance │ │ └── browsing_test.rb │ └── test_helper.rb └── vendor │ └── plugins │ └── .gitkeep ├── fabricators ├── locus_fabricator.rb └── user_fabricator.rb ├── models └── locus_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | rdoc 5 | pkg 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '>=3.0.0' 4 | gem 'mongoid', '~> 2.1' 5 | gem 'bson_ext', '~> 1.3' 6 | 7 | gem 'haml' 8 | 9 | group :development do 10 | gem 'jeweler' 11 | gem 'rspec' 12 | end 13 | 14 | group :test do 15 | gem 'rspec-rails' 16 | gem 'fabrication' 17 | end -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | abstract (1.0.0) 5 | actionmailer (3.0.5) 6 | actionpack (= 3.0.5) 7 | mail (~> 2.2.15) 8 | actionpack (3.0.5) 9 | activemodel (= 3.0.5) 10 | activesupport (= 3.0.5) 11 | builder (~> 2.1.2) 12 | erubis (~> 2.6.6) 13 | i18n (~> 0.4) 14 | rack (~> 1.2.1) 15 | rack-mount (~> 0.6.13) 16 | rack-test (~> 0.5.7) 17 | tzinfo (~> 0.3.23) 18 | activemodel (3.0.5) 19 | activesupport (= 3.0.5) 20 | builder (~> 2.1.2) 21 | i18n (~> 0.4) 22 | activerecord (3.0.5) 23 | activemodel (= 3.0.5) 24 | activesupport (= 3.0.5) 25 | arel (~> 2.0.2) 26 | tzinfo (~> 0.3.23) 27 | activeresource (3.0.5) 28 | activemodel (= 3.0.5) 29 | activesupport (= 3.0.5) 30 | activesupport (3.0.5) 31 | arel (2.0.9) 32 | bson (1.4.0) 33 | bson_ext (1.4.0) 34 | builder (2.1.2) 35 | diff-lcs (1.1.2) 36 | erubis (2.6.6) 37 | abstract (>= 1.0.0) 38 | fabrication (0.9.5) 39 | git (1.2.5) 40 | haml (3.0.25) 41 | i18n (0.6.0) 42 | jeweler (1.5.2) 43 | bundler (~> 1.0.0) 44 | git (>= 1.2.5) 45 | rake 46 | mail (2.2.15) 47 | activesupport (>= 2.3.6) 48 | i18n (>= 0.4.0) 49 | mime-types (~> 1.16) 50 | treetop (~> 1.4.8) 51 | mime-types (1.16) 52 | mongo (1.4.0) 53 | bson (= 1.4.0) 54 | mongoid (2.2.3) 55 | activemodel (~> 3.0) 56 | mongo (~> 1.3) 57 | tzinfo (~> 0.3.22) 58 | polyglot (0.3.1) 59 | rack (1.2.2) 60 | rack-mount (0.6.14) 61 | rack (>= 1.0.0) 62 | rack-test (0.5.7) 63 | rack (>= 1.0) 64 | rails (3.0.5) 65 | actionmailer (= 3.0.5) 66 | actionpack (= 3.0.5) 67 | activerecord (= 3.0.5) 68 | activeresource (= 3.0.5) 69 | activesupport (= 3.0.5) 70 | bundler (~> 1.0) 71 | railties (= 3.0.5) 72 | railties (3.0.5) 73 | actionpack (= 3.0.5) 74 | activesupport (= 3.0.5) 75 | rake (>= 0.8.7) 76 | thor (~> 0.14.4) 77 | rake (0.8.7) 78 | rspec (2.0.1) 79 | rspec-core (~> 2.0.1) 80 | rspec-expectations (~> 2.0.1) 81 | rspec-mocks (~> 2.0.1) 82 | rspec-core (2.0.1) 83 | rspec-expectations (2.0.1) 84 | diff-lcs (>= 1.1.2) 85 | rspec-mocks (2.0.1) 86 | rspec-core (~> 2.0.1) 87 | rspec-expectations (~> 2.0.1) 88 | rspec-rails (2.0.1) 89 | rspec (~> 2.0.0) 90 | thor (0.14.6) 91 | treetop (1.4.9) 92 | polyglot (>= 0.3.1) 93 | tzinfo (0.3.30) 94 | 95 | PLATFORMS 96 | ruby 97 | 98 | DEPENDENCIES 99 | bson_ext (~> 1.3) 100 | fabrication 101 | haml 102 | jeweler 103 | mongoid (~> 2.1) 104 | rails (>= 3.0.0) 105 | rspec 106 | rspec-rails 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Cracker Snack Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C2 - Command & Control 2 | 3 | C2 consist of several modules that each provide different ways to take control of your app. The first module is Informant. Informant turns your models into a json store with a pretty web interface based on the amazing webpop.com CMS/Host. 4 | 5 | This project is very new and under active development. Watch this project to keep up to date on new features and documentation. 6 | 7 | ## Install 8 | 9 | Add the c2 gem to your gemfile. 10 | 11 | gem 'c2' 12 | 13 | Run bundler. 14 | 15 | bundle install 16 | 17 | Tell c2 to inspect models in your app and add them to the C2 - Informant App. 18 | 19 | rake c2:discover 20 | 21 | To serve static files from your public directory instead of the gem run the c2:install generator. 22 | 23 | rails g c2:install 24 | 25 | Start taking control of your app by visiting http://yourapp/c2/informant 26 | 27 | ## Note on Patches/Pull Requests 28 | 29 | * Fork the project. 30 | * Make your feature addition or bug fix. 31 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 32 | * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 33 | * Send me a pull request. Bonus points for topic branches. 34 | 35 | ## Copyright 36 | 37 | Copyright (c) 2009 hexorx. See LICENSE for details. 38 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | begin 5 | require 'jeweler' 6 | Jeweler::Tasks.new do |gem| 7 | gem.name = "c2" 8 | gem.summary = %Q{ Take control of your Rails 3 and Mongoid based site with the Comand & Control Admin Portal + CMS. } 9 | gem.description = %Q{ C2 aspires to be a simple, drop in, Admin Portal + CMS for apps based on Rails 3 + Mongoid. } 10 | gem.files = Dir["{lib}/**/*", "{app}/**/*", "{config}/**/*", "{public}/**/*"] 11 | gem.email = "hexorx@gmail.com" 12 | gem.homepage = "http://crackersnack.com/Command-And-Control" 13 | gem.authors = ["hexorx"] 14 | end 15 | Jeweler::GemcutterTasks.new 16 | rescue LoadError 17 | puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" 18 | end 19 | 20 | require 'rspec/core/rake_task' 21 | 22 | RSpec::Core::RakeTask.new(:spec) 23 | 24 | task :default => :spec -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.13 -------------------------------------------------------------------------------- /app/controllers/c2/base_controller.rb: -------------------------------------------------------------------------------- 1 | class C2::BaseController < ApplicationController 2 | unloadable 3 | layout 'c2' 4 | 5 | before_filter :authorize_c2_agent 6 | end -------------------------------------------------------------------------------- /app/controllers/c2/informant/app_controller.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::AppController < C2::BaseController 2 | def show 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/c2/informant/buckets_controller.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::BucketsController < C2::BaseController 2 | respond_to(:json) 3 | 4 | def index 5 | respond_with(@locus = C2::Informant::Locus.find(params[:locus_id]).buckets) 6 | end 7 | 8 | def update 9 | respond_with(@locus = C2::Information::Locus.find(params[:locus_id]).buckets) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/c2/informant/entries_controller.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::EntriesController < C2::BaseController 2 | respond_to(:json) 3 | before_filter :load_source 4 | 5 | def index 6 | @entries = @source.entries.offset(0).limit(12) 7 | render :json => @source.entries_as_json(@entries) 8 | end 9 | 10 | def show 11 | @entry = @source.entries.find(params[:id]) 12 | render :json => @source.entry_as_json(@entry) 13 | end 14 | 15 | def create 16 | @entry = @source.klass.new(@source.sanitized(params)) 17 | @entry.save 18 | render :json => @source.entry_as_json(@entry) 19 | end 20 | 21 | def update 22 | @entry = @source.entries.find(params[:id]) 23 | @entry.update_attributes(@source.sanitized(params)) 24 | render :json => @source.entry_as_json(@entry) 25 | end 26 | 27 | protected 28 | 29 | def load_source 30 | @locus = C2::Informant::Locus.find(params[:locus_id]) 31 | @bucket = @locus.buckets.find(params[:bucket_id]) if params[:bucket_id] 32 | @source = (@bucket || @locus) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/controllers/c2/informant/locus_controller.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::LocusController < C2::BaseController 2 | respond_to(:json) 3 | 4 | def index 5 | respond_with(@locus = C2::Informant::Locus.all.order(:class_name => :asc)) 6 | end 7 | 8 | def update 9 | respond_with(@locus = C2::Information::Locus.find(params[:id])) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/c2/reporter/app_controller.rb: -------------------------------------------------------------------------------- 1 | class C2::Reporter::AppController < C2::BaseController 2 | def show 3 | @reports = C2::Reporter.reports 4 | @reports.each(&:refresh) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/c2/informant/bucket.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::Bucket 2 | include Mongoid::Document 3 | 4 | field :label 5 | field :description 6 | 7 | field :method_name 8 | 9 | embedded_in :locus, :class_name => 'C2::Informant::Locus' 10 | 11 | delegate :count, :to => :entries 12 | delegate :elements, :entry_label, :sanitized, :to => :locus 13 | 14 | validate :method_name, :presences => true, :unique => true 15 | 16 | def label 17 | self[:label] || self.method_name.titleize 18 | end 19 | 20 | def entries 21 | return _parent.klass.all unless method_name && _parent.klass.respond_to?(method_name) 22 | _parent.klass.try(method_name) 23 | end 24 | 25 | def entries_page(page=1,per=10) 26 | # entries.offset((page - 1) * per).limit(per).map do |entry| 27 | entries.map do |entry| 28 | data = elements.enabled.inject({}) do |memo, element| 29 | memo[element.name] = entry.send(element.name) if entry.respond_to?(element.name) 30 | memo 31 | end 32 | data['_id'] = entry['_id'] 33 | data['created_at'] = entry['created_at'] 34 | data['updated_at'] = entry['updated_at'] 35 | data[entry_label] = entry[entry_label] 36 | data 37 | end 38 | end 39 | 40 | def as_json(options={}) 41 | cleaned = super((options || {}).merge({ 42 | :methods => [:label, :count, :entries_page] 43 | })).map {|k,v| [k.to_s, (v.is_a?(Numeric) ? v.to_s : v)]} 44 | Hash[cleaned] 45 | end 46 | end -------------------------------------------------------------------------------- /app/models/c2/informant/form_element.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::FormElement 2 | include Mongoid::Document 3 | 4 | field :tag 5 | field :name 6 | field :caption 7 | field :tip 8 | 9 | field :enabled, :type => Boolean, :default => true 10 | 11 | embedded_in :locus, :class_name => 'C2::Informant::Locus' 12 | 13 | scope :enabled, :where => { :enabled => true } 14 | 15 | delegate :klass, :to => :'_parent' 16 | 17 | def as_json(options={}) 18 | { 19 | 'type' => 'div', 20 | 'id' => "entry_#{name}_field", 21 | 'class' => "field #{name}_field #{tag}", 22 | 'elements' => [ 23 | { 24 | 'type' => tag, 25 | 'name' => name, 26 | 'id' => "entry_#{name}", 27 | 'class' => "#{name}_input #{tag}", 28 | 'caption' => (caption || name.titleize) 29 | }, 30 | { 31 | 'type' => 'div', 32 | 'class' => 'tip', 33 | 'elements' => [ 34 | { 35 | 'type' => 'p', 36 | 'html' => tip 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | end 43 | end -------------------------------------------------------------------------------- /app/models/c2/informant/locus.rb: -------------------------------------------------------------------------------- 1 | class C2::Informant::Locus 2 | include Mongoid::Document 3 | 4 | field :label 5 | field :description 6 | 7 | field :class_name 8 | 9 | field :entry_label 10 | 11 | embeds_many :buckets, :class_name => 'C2::Informant::Bucket' 12 | embeds_many :elements, :class_name => 'C2::Informant::FormElement' 13 | 14 | delegate :count, :to => :klass 15 | 16 | validate :class_name, :presences => true, :unique => true 17 | 18 | def label 19 | return self[:label_cache] || '' unless self.class_name 20 | self[:label_cache] ||= self.class_name.pluralize.titleize 21 | end 22 | 23 | def singular_label 24 | label.to_s.singularize 25 | end 26 | 27 | def entry_label 28 | return self[:entry_label_cache] || '' unless self.class_name 29 | self[:entry_label_cache] ||= ([:c2_label, :entry_label, :to_label, :label, :title, :name, :email, :subject].map(&:to_s) & klass.instance_methods).first 30 | end 31 | 32 | def entry_label=(value) 33 | self[:entry_label_cache] = value 34 | end 35 | 36 | def klass 37 | @klass ||= self.class_name.classify.constantize 38 | end 39 | 40 | def entries 41 | klass.all 42 | end 43 | 44 | def entries_page(page=1,per=10) 45 | entries_as_json(entries) 46 | end 47 | 48 | def entry_as_json(entry) 49 | data = self.elements.inject({}) do |memo, element| 50 | memo[element.name] = entry.send(element.name) if entry.respond_to?(element.name) 51 | memo 52 | end 53 | data['_id'] = entry['_id'] 54 | data[entry_label] = entry.send(entry_label) if entry_label && entry.respond_to?(entry_label) 55 | data['created_at'] = entry['created_at'] 56 | data['updated_at'] = entry['updated_at'] 57 | data['errors'] = entry.errors 58 | data 59 | end 60 | 61 | def entries_as_json(entries) 62 | entries.map { |entry| self.entry_as_json(entry) } 63 | end 64 | 65 | def sanitized(params) 66 | self.elements.enabled.map(&:name).inject({}) do |memo, field| 67 | memo[field] = params[field] 68 | memo 69 | end 70 | end 71 | 72 | def hash_path 73 | '#/locus/' + self.id.to_s 74 | end 75 | 76 | def entry_form_builder 77 | { 78 | 'action' => '#', 79 | 'elements' => [ 80 | { 81 | 'type' => 'div', 82 | 'id' => 'entry-fields', 83 | 'class' => 'fields', 84 | 'elements' => elements.enabled.as_json(:except => ['_id', 'tag']) 85 | }, 86 | { 87 | 'type' => 'div', 88 | 'class' => 'actions', 89 | 'elements' => [ 90 | {'type' => 'submit', 'class' => 'button', 'value' => 'Save'}, 91 | { 92 | 'type' => 'a', 93 | 'class' => 'cancel flip-trigger', 94 | 'href' => "#", 95 | 'html' => 'Cancel' 96 | } 97 | ] 98 | } 99 | ] 100 | } 101 | end 102 | 103 | def as_json(options={}) 104 | cleaned = super((options || {}).merge({ 105 | :methods => [:count, :buckets, :label, :singular_label, :entry_label, :entries_page, :entry_form_builder] 106 | })).map {|k,v| [k.to_s, (v.is_a?(Numeric) ? v.to_s : v)]} 107 | Hash[cleaned] 108 | end 109 | end -------------------------------------------------------------------------------- /app/models/c2/reporter/bucket.rb: -------------------------------------------------------------------------------- 1 | class C2::Reporter::Bucket 2 | attr_accessor :refreshed_at 3 | 4 | attr_accessor :series 5 | attr_accessor :series_cache 6 | 7 | def initialize(klass_name, scope_name, *series) 8 | @klass_name = klass_name 9 | @scope_name = scope_name 10 | 11 | series = [:count] if series.empty? 12 | @series = series.inject({}) do |m,v| 13 | name, *mutators = *[*v] 14 | m[name.to_sym] = { 15 | :id => name.to_sym, 16 | :name => name.to_s.titleize, 17 | :mutators => mutators, 18 | :data => [] 19 | } 20 | m 21 | end 22 | end 23 | 24 | def klass 25 | @klass_cache ||= @klass_name.to_s.classify.constantize 26 | end 27 | 28 | def scope 29 | self.klass.send(@scope_name) 30 | end 31 | 32 | def id 33 | @scope_name.to_sym 34 | end 35 | 36 | def label 37 | @scope_name.to_s.titleize 38 | end 39 | 40 | def count 41 | self.scope.count 42 | end 43 | 44 | def refresh 45 | @series_cache = {} 46 | started_at = self.scope.where(:created_at.ne => nil).order(:created_at).only(:created_at).first().created_at || Time.now 47 | self.scope.each do |item| 48 | date = (item.created_at || started_at).to_date 49 | @series.each do |key,value| 50 | data = item.respond_to?(key) ? item.send(key) : 1 51 | @series_cache[key] ||= Hash.new(0) 52 | @series_cache[key][date] += value[:mutators].inject(data) { |memo,mutator| memo.send(mutator) } 53 | end 54 | end 55 | @series.map do |key,value| 56 | value[:start] = started_at.to_date 57 | value[:pointStart] = started_at.to_i * 1000 58 | value[:end] = Date.today 59 | value[:data] = (value[:start]..value[:end]).map { |d| @series_cache[key][d] } 60 | end 61 | @refreshed_at = Time.now.utc 62 | end 63 | 64 | def as_json(options={}) 65 | { 66 | :id => self.id, 67 | :label => self.label, 68 | :count => self.count, 69 | :series => self.series.map {|k,v| v } 70 | } 71 | end 72 | 73 | end -------------------------------------------------------------------------------- /app/models/c2/reporter/report.rb: -------------------------------------------------------------------------------- 1 | module C2::Reporter 2 | mattr_accessor :reports 3 | @@reports = [] 4 | 5 | class Report 6 | attr_accessor :refreshed_at 7 | attr_accessor :buckets 8 | attr_accessor :options 9 | attr_accessor :meta 10 | 11 | def initialize(klass_name, options={}, &block) 12 | @options = options.with_indifferent_access 13 | 14 | @buckets = [] 15 | @klass_name = klass_name 16 | @meta = @options.delete(:meta) 17 | 18 | instance_eval(&block) 19 | 20 | ::C2::Reporter.reports.push(self) 21 | end 22 | 23 | def klass 24 | @klass_cache ||= @klass_name.to_s.classify.constantize 25 | end 26 | 27 | def id 28 | @klass_name.to_sym 29 | end 30 | 31 | def label 32 | @klass_name.to_s.pluralize.titleize 33 | end 34 | 35 | def refresh 36 | @buckets.each(&:refresh) 37 | @refreshed_at = Time.now.utc 38 | end 39 | 40 | def as_json(options={}) 41 | { 42 | :id => self.id, 43 | :label => self.label, 44 | :buckets => self.buckets 45 | } 46 | end 47 | 48 | private 49 | 50 | def bucket(scope_name, *series) 51 | @buckets.push(C2::Reporter::Bucket.new(@klass_name, scope_name, *series)) 52 | end 53 | 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/stylesheets/c2.scss: -------------------------------------------------------------------------------- 1 | @import "compass/utilities"; 2 | @import "compass/css3"; 3 | 4 | /* Fonts */ 5 | @font-face { 6 | font-family: 'Pictos'; 7 | src: url('/fonts/c2/pictos-web.eot'); 8 | src: local('☺'), url('/fonts/c2/pictos-web.woff') format('woff'), 9 | url('/fonts/c2/pictos-web.ttf') format('truetype'), 10 | url('/fonts/c2/pictos-web.svg#webfontIyfZbseF') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | 15 | /* Text Styles */ 16 | h1, h2, h3, h4 { 17 | @include text-shadow(rgba(255, 255, 255, 0.5)); 18 | color: #777; 19 | } 20 | 21 | h3 { 22 | font-size: 13px; 23 | line-height: 18px; 24 | } 25 | 26 | p { margin: 0px 0px 1.5em } 27 | 28 | ul { list-style: none; margin: 0; padding: 0 } 29 | a { text-decoration: none; outline: 0; } 30 | a, .dropdown-trigger, .tool, .count { 31 | @include transition(all, .3s, ease); 32 | } 33 | 34 | a.external-link::after { 35 | content: "(open in new window)"; 36 | display: inline-block; 37 | text-indent: -99999px; 38 | height: 16px; width: 16px; 39 | background: url(/images/c2/external-link.png) no-repeat center center; 40 | margin-left: 2px; 41 | opacity: .6; 42 | } 43 | 44 | dl { display: block; } 45 | 46 | .meta { 47 | color: #777; 48 | font-size: 11px; 49 | margin: 0; padding: 0; 50 | dt, dd { 51 | display: inline; 52 | margin: 0; 53 | } 54 | } 55 | 56 | abbr[title], dfn[title] { 57 | border-bottom: 1px dotted; 58 | cursor: help; 59 | } 60 | 61 | input[type="checkbox"], input.checkbox, 62 | input[type="radio"], input.radio { 63 | position: relative; 64 | top: 0.25em; 65 | } 66 | 67 | input[type="checkbox"] { vertical-align: bottom } 68 | 69 | /* Defaults */ 70 | html { 71 | background: #101010 url(/images/c2/bg-header.png) repeat-x; 72 | height: 100%; 73 | } 74 | 75 | body { 76 | @include box-sizing(border-box); 77 | height: 100%; 78 | margin: 0; padding: 76px 0 0 0; 79 | min-width: 960px; 80 | color: #555; 81 | font-size: 12px; 82 | font-family: 'Helvetica Neue', sans-serif; 83 | line-height: 1.5; 84 | z-index: 0; 85 | } 86 | 87 | /* Details */ 88 | #templates { display: none; } 89 | 90 | #wrapper { 91 | display: table; 92 | background: url(/images/c2/bg-body.png); 93 | height: 100%; width: 100%; 94 | z-index: 0; 95 | 96 | #header-wrapper { 97 | position: absolute; 98 | top: 0; left: 0; 99 | height: 76px; min-width: 960px; width: 100%; 100 | border-bottom: 1px solid rgba(255, 255, 255, 0.5); 101 | #header { 102 | padding: 14px 0 0 20px; 103 | a { 104 | text-shadow: rgba(0, 0, 0, 0.4), 1px 1px 2px; 105 | cursor: pointer; 106 | color: white; opacity: .5; 107 | font-weight: bold; 108 | } 109 | h1, h2 { 110 | white-space: nowrap; 111 | margin: 0; padding: 0 8px 0 0; 112 | line-height: 1; 113 | display: inline; 114 | } 115 | h1 { 116 | font-size: 30px; 117 | font-weight: normal; 118 | a { font-weight: normal; text-shadow: none; opacity: 1; } 119 | } 120 | h2 { 121 | font-size: 14px; 122 | text-transform: uppercase; 123 | a { 124 | color: #8EB1D0; opacity: 1; 125 | font-weight: normal; 126 | text-shadow: rgba(0, 0, 0, 0.4) 1px 1px 2px; 127 | &.preview::after { 128 | content: "(preview)"; 129 | display: inline-block; 130 | text-indent: -99999px; 131 | background: url(/images/c2/eye.png) no-repeat left center; 132 | margin-left: 5px; 133 | height: 15px; width: 25px; 134 | opacity: .6; 135 | } 136 | &.preview:hover { 137 | color: white; 138 | &::after { 139 | opacity: 1; 140 | } 141 | } 142 | } 143 | } 144 | .url { 145 | line-height: 1.4; 146 | font-weight: normal; 147 | font-size: 11px; 148 | 149 | a { color: #999; &::after { opacity: 0 } } 150 | } 151 | .links { 152 | position: absolute; 153 | top: 0; right: 10px; 154 | font-size: 11px; 155 | list-style: none; 156 | margin: 0; padding: 0; 157 | li { float: right; vertical-align: top; } 158 | & > li { 159 | & > a, & > .dropdown-trigger { 160 | cursor: pointer; 161 | display: block; 162 | line-height: 26px; 163 | text-align: left; 164 | padding-left: 10px; 165 | text-shadow: rgba(0, 0, 0, 0.4) 1px 1px 2px; 166 | color: white; 167 | opacity: .5; 168 | } 169 | & > a::after { 170 | content: "|"; 171 | display: inline-block; 172 | width: 1px; 173 | margin-left: 10px; 174 | color: #555; 175 | text-shadow: none; 176 | } 177 | } 178 | .dropdown-trigger::after { 179 | content: "↓"; 180 | display: inline-block; 181 | text-indent: -99999px; 182 | height: 16px; width: 16px; 183 | margin-top: -6px; margin-left: 2px; 184 | vertical-align: middle; 185 | background: url('/images/c2/down-arrow.png'); 186 | } 187 | .dropdown { 188 | @include border-bottom-radius(5px); 189 | @include box-shadow(rgba(0, 0, 0, 0.5), 0, 3px, 5px, 0); 190 | background: #191919; 191 | position: absolute; 192 | display: none; 193 | right: 0; 194 | min-width: 150px; 195 | padding: 0; 196 | opacity: 1; 197 | z-index: 9999; 198 | li { 199 | display: block; 200 | width: 100%; 201 | clear: both; 202 | text-align: left; 203 | white-space: nowrap; 204 | background-color: rgba(255, 255, 255, 0.1); 205 | border-bottom: 1px solid rgba(0, 0, 0, 0.2); 206 | border-top: 1px solid rgba(255, 255, 255, 0.05); 207 | a, .link button { 208 | @include border-radius(0); 209 | display: block; 210 | line-height: 1.2; 211 | padding: 9px 20px; 212 | } 213 | } 214 | li:last-child { &, a, .link button { @include border-bottom-radius(5px); } } 215 | } 216 | 217 | a.back { 218 | color: white; 219 | opacity: 1; 220 | &:hover { color: #8EB1D0; } 221 | &::before { 222 | content: "<"; 223 | background: url(/images/c2/back-light.png) no-repeat center center; 224 | display: inline-block; 225 | text-indent: -99999px; 226 | height: 26px; width: 16px; 227 | margin-right: 4px; 228 | } 229 | } 230 | .active > .dropdown { display: block; } 231 | .active .dropdown-trigger, .dropdown-trigger:hover { background: rgba(255, 255, 255, 0.05); opacity: 1; } 232 | .dropdown li { a:hover, a.hover, a:active, a.active { @include linear-gradient(color-stops(#555, #333)); } } 233 | } 234 | nav { 235 | color: #777; 236 | position: absolute; 237 | bottom: 15px; right: 10px; 238 | margin: 0px; 239 | li { 240 | display: inline-block; 241 | vertical-align: middle; 242 | &.current a { 243 | @include linear-gradient(color-stops(rgba(0, 0, 0, 0.398438), rgba(0, 0, 0, 0))); 244 | opacity: 1; 245 | color: white; 246 | } 247 | } 248 | a { 249 | @include border-top-radius(8px); 250 | font-size: 18px; 251 | opacity: 0.65; 252 | padding: 5px 15px 10px; 253 | } 254 | } 255 | a { &.active, &.current, &:hover, &:active, &:hover::after { opacity: 1; } } 256 | } 257 | } 258 | 259 | #main-wrapper { 260 | display: table-row; 261 | height: 100%; 262 | margin: 0; padding: 0; 263 | vertical-align: baseline; 264 | #sidebar-wrapper, #main-content, #aside-wrapper { 265 | display: table-cell; 266 | height: 100%; 267 | margin: 0; padding: 0; 268 | vertical-align: top; 269 | } 270 | #sidebar-wrapper, #aside-wrapper { 271 | min-width: 225px; width: 22%; 272 | } 273 | 274 | aside, nav { 275 | display: block; 276 | color: #777; 277 | border: 0; outline: 0; 278 | margin: 0; padding: 0; 279 | } 280 | 281 | #sidebar-wrapper { 282 | @include box-shadow(rgba(0, 0, 0, 0.0976562), -2px, 2px, 10px, inset); 283 | background: #EFEFEF; 284 | #sidebar { 285 | dislpay: block; 286 | min-height: 300px; 287 | margin: 0; padding: 0; 288 | vertical-align: baseline; 289 | .sitemap { 290 | a { 291 | color: #777; 292 | margin: 0px 10px 0px 20px; 293 | font-style: normal; 294 | font-variant: normal; 295 | vertical-align: baseline; 296 | &:hover, &.active { color: black; } 297 | } 298 | h3 { 299 | text-shadow: rgba(255, 255, 255, 0.496094) 0px 1px 0px; 300 | text-transform: uppercase; 301 | border-top: 1px solid transparent; 302 | margin: 0px; 303 | font-weight: normal; 304 | a { 305 | display: block; 306 | padding-bottom: 6px; 307 | padding-top: 9px; 308 | } 309 | } 310 | li { display: list-item; } 311 | .header { 312 | display: block; 313 | position: relative; 314 | border-bottom: 1px dotted rgba(0, 0, 0, 0.136719); 315 | margin-bottom: 4.5px; 316 | } 317 | .tree { 318 | dislpay: block; padding-bottom: 18px; 319 | ul { margin: 0; } 320 | .label { position: relative; } 321 | .item { 322 | .entries, .bucket { 323 | .label {padding-left: 15px} 324 | a.entries, a.bucket { padding-left: 20px } 325 | a.entries { background: url(/images/c2/entries-small.png) no-repeat 0% 50%; } 326 | a.bucket { background: url(/images/c2/categories-small.png) no-repeat 0% 50%; } 327 | a .count { 328 | @include border-radius(6px); 329 | float: right; 330 | margin-top: 1px; padding: 0px 6px ; 331 | background: #999; color: white; 332 | font-size: 11px; 333 | font-style: normal; 334 | line-height: 16px; 335 | text-shadow: none; 336 | } 337 | a:hover .count { background: #555; } 338 | .current a .count { background: white; color: #525252 } 339 | } 340 | } 341 | a { 342 | -webkit-font-smoothing: antialiased; 343 | display: block; 344 | font-weight: 600; 345 | padding-bottom: 2.25px; 346 | padding-top: 2.25px; 347 | text-shadow: rgba(255, 255, 255, 0.496094) 1px 1px 0px; 348 | } 349 | } 350 | a.section { 351 | color: #333; 352 | font-size: 14px; 353 | margin-top: 4.5px; 354 | } 355 | .current, .current a { 356 | background: #525252; color: white; 357 | text-shadow: none; 358 | } 359 | } 360 | } 361 | } 362 | 363 | #main-content { 364 | padding: 0px; 365 | min-width: 510px; 366 | height: 100%; 367 | #app-wrapper, #app { position: relative; height: 100% } 368 | #app { 369 | position: relative; 370 | vertical-align: baseline; 371 | z-index: 0; 372 | .sheet { 373 | @include transition(all, .5s, ease-in-out); 374 | @include border-radius(5px); 375 | position: absolute; 376 | overflow: auto; 377 | top: -5px; bottom: 0; left: 0; right: 0; 378 | background: white; 379 | border: 0px solid white; 380 | border-width: 0 5px 5px 5px; 381 | padding: 18px 20px; 382 | 383 | &#app-front { z-index: 900; } 384 | &#app-back { z-index: 800; } 385 | } 386 | &.flip { 387 | #app-front { z-index: 900; } 388 | #app-back { z-index: 1000; } 389 | } 390 | .toolbar { 391 | position: absolute; 392 | top: 0; right: 0; 393 | height: 27px; 394 | text-align: right; 395 | line-height: 27px; 396 | padding-right: 5px; 397 | a { 398 | color: #555; 399 | height: 100%; 400 | } 401 | } 402 | .header { 403 | position: relative; 404 | display: block; 405 | margin: 0; padding: 0; 406 | h1 { 407 | margin: 0; 408 | color: #A5A5A5; 409 | font-size: 23px; 410 | font-weight: 100; 411 | line-height: 27px; 412 | text-transform: none; 413 | strong { 414 | color: #555; 415 | font-weight: normal; 416 | } 417 | } 418 | a.option { 419 | display: block; float: right; 420 | } 421 | } 422 | .header::after, .actions::before { 423 | content: '.'; 424 | display: block; 425 | height: 1px; 426 | margin: 6px 0px 18px; 427 | text-indent: -99999px; 428 | background: rgba(0, 0, 0, 0.0976562); 429 | border: 1px solid rgba(255, 255, 255, 0.648438); border-left: 0px; border-right: 0px; 430 | } 431 | .field { 432 | max-width: 100%; 433 | margin-bottom: 18px; 434 | label { 435 | color: #777; 436 | cursor: pointer; 437 | display: block; 438 | font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; 439 | font-size: 12px; 440 | font-weight: bold; 441 | line-height: 18px; 442 | margin: 0px 0px 3px; 443 | text-shadow: none; 444 | text-transform: none; 445 | } 446 | input, textarea { 447 | border: 1px solid #BBB; 448 | color: #222; 449 | font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; 450 | font-size: 16px; 451 | font-weight: bold; 452 | } 453 | input, textarea, .tip { 454 | @include box-sizing(border-box); 455 | width: 100%; 456 | } 457 | input { 458 | line-height: 1; 459 | margin: 0px 0px 3px; 460 | padding: 6px; 461 | } 462 | textarea { 463 | height: 5em; 464 | line-height: 22.5px; 465 | margin: 0px; 466 | min-height: 60px; 467 | padding: 3px 6px; 468 | } 469 | &.checkbox input, &.radio input { margin-right: 5px; } 470 | 471 | &.checkbox input, &.radio input, 472 | &.select input, &.readonly input { 473 | display: inline; 474 | top: 0px; 475 | width: auto; 476 | } 477 | 478 | &.checkbox label, &.radio label, 479 | &.select label, &.readonly label { 480 | color: #555; 481 | display: inline; 482 | width: auto; 483 | } 484 | 485 | .tip { 486 | color: #999; 487 | font-size: 11px; 488 | margin: 0px 0px 18px; 489 | } 490 | p { margin-bottom: 0px } 491 | } 492 | } 493 | .block-list { 494 | margin-top: -9px; 495 | .item { 496 | position: relative; 497 | border-bottom: 1px dotted rgba(0, 0, 0, 0.136719); 498 | margin: 0; padding: 9px 0px; 499 | h3 { 500 | margin: 0; 501 | font-size: 15px; 502 | font-weight: normal; 503 | a { color: #6186B7; } 504 | } 505 | .meta { margin-top: 9px; } 506 | } 507 | 508 | } 509 | } 510 | 511 | #aside-wrapper { 512 | @include box-shadow(rgba(0, 0, 0, 0.0976562), 2px, 2px, 10px, inset); 513 | background: #E8E8E8; 514 | #aside { 515 | padding: 20px; 516 | .call-to-action { 517 | .button { 518 | display: block; 519 | text-align: center; 520 | strong { 521 | @include border-radius; 522 | @include box-sizing(border-box); 523 | border: none; outline: none; 524 | background: #484848 url(/images/c2/alert-overlay.png) repeat-x; 525 | text-shadow: rgba(0, 0, 0, 0.699219) 0px 1px 2px; 526 | color: white; cursor: pointer; 527 | display: inline-block; 528 | font-size: 15px; 529 | font-weight: bold; 530 | line-height: 1; 531 | opacity: 0.95; 532 | padding: 10px 20px 12px; 533 | position: relative; 534 | text-align: center; 535 | text-decoration: none; 536 | vertical-align: middle; 537 | } 538 | } 539 | .button::before { 540 | content: '+'; 541 | display: block; 542 | height: 100px; width: 100%; 543 | margin-bottom: 8px; 544 | text-indent: -99999px; 545 | } 546 | &.new-entry .button::before{ background: url(/images/c2/new-entry.png) no-repeat 50% 50%; } 547 | } 548 | } 549 | } 550 | #footer-wrapper { 551 | position: absolute; 552 | bottom: 0; left: 0; 553 | height: 10px; min-width: 960px; width: 100%; 554 | border-top: 1px solid rgba(255, 255, 255, 0.5); 555 | background: #101010 url(/images/c2/bg-header.png) repeat-x; 556 | } 557 | 558 | .actions { 559 | .button, button, .cancel { 560 | @include text-shadow(rgba(0, 0, 0, 0.699219), 0px, 1px, 2px); 561 | @include box-sizing(border-box); 562 | @include border-radius(8px); 563 | 564 | position: relative; display: inline-block; 565 | border: none; outline: none; cursor: pointer; 566 | height: 36px; min-width: 95px; 567 | 568 | font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; 569 | font-size: 15px; 570 | font-weight: bold; 571 | line-height: 1; 572 | text-align: center; 573 | text-decoration: none; 574 | vertical-align: middle; 575 | } 576 | .button, button { 577 | @include transition(all, .3s, ease); 578 | @include linear-gradient(color-stops(#75A7D0, #6186B7 50%, #4470A1)); 579 | background-color: #4470A1; color: white; 580 | margin-right: 10px; padding: 9px 20px; 581 | &:hover, &.hover, &:active, &.active { 582 | background-color: #335F90; 583 | background-image: none; 584 | } 585 | } 586 | .cancel { 587 | background-color: #999; color: white; 588 | margin-right: 10px; padding: 9px 20px; 589 | opacity: 0.7; 590 | &:hover, &.hover, &:active, &.active { opacity: 1; } 591 | } 592 | } 593 | } 594 | } 595 | 596 | .field_with_errors { 597 | label { position: relative ; } 598 | .error { 599 | color: #D63104; 600 | display: inline-block; 601 | margin: 0; padding: 0px 55px 0px 0px; 602 | font-size: 12px; 603 | font-weight: bold; 604 | z-index: 9999; 605 | &::before { 606 | content: '→'; 607 | color: #999; 608 | margin: 0px 6px; 609 | } 610 | &::after { 611 | content: 'Opps!;'; 612 | background: url(/images/c2/ops.png) no-repeat 50% 50%; 613 | display: inline-block; position: absolute; 614 | height: 53px; width: 42px; 615 | top: -6px; right: 10px; 616 | text-indent: -99999px; 617 | z-index: 1; 618 | } 619 | } 620 | } 621 | 622 | #notices { 623 | display: block; position: absolute; 624 | top: 0; left: 0; 625 | margin: 0; padding: 0; 626 | border: none; 627 | width: 100%; min-width: 960px; 628 | .success, .notice, .warning, .error { 629 | @include border-radius; 630 | display: block; 631 | width: 490px; 632 | margin: 10px auto; padding: 10px; 633 | color: #333; 634 | text-align: center; 635 | line-height: 20px; 636 | font-size: 16px; 637 | font-weight: bold; 638 | } 639 | .success, .notice { 640 | background: #BBFFB6; 641 | border: 1px solid #1FDF00; 642 | } 643 | .warning { 644 | background: #FFC; 645 | border: 1px solid #FF8; 646 | } 647 | .error { 648 | background: #F77; 649 | border: 1px solid #F55; 650 | } 651 | } 652 | 653 | /* Pictos */ 654 | .pictos-icon { 655 | display: none; 656 | font-family: 'Pictos'; 657 | font-size: 20px; 658 | } 659 | body.pictos-icons { 660 | .pictos-text { display: none; } 661 | .pictos-icon { display: block; } 662 | } 663 | 664 | /* Experimental */ 665 | body.lab { 666 | #app { 667 | .sheet { 668 | &#app-front { 669 | -webkit-transform: rotateY(0deg); 670 | -webkit-transform-style: preserve-3d; 671 | -webkit-backface-visibility: hidden; 672 | } 673 | &#app-back { 674 | -webkit-transform: rotateY(180deg); 675 | -webkit-transform-style: preserve-3d; 676 | -webkit-backface-visibility: hidden; 677 | } 678 | } 679 | &.flip { 680 | #app-front { -webkit-transform: rotateY(180deg); } 681 | #app-back { -webkit-transform: rotateY(0deg); } 682 | } 683 | } 684 | } -------------------------------------------------------------------------------- /app/views/c2/informant/app/_entry_index.html.haml: -------------------------------------------------------------------------------- 1 | %script{ :'data-template' => 'entryIndex', :type => 'text/js-template' } 2 | .header 3 | %h1 4 | %span.type <%= entries.parent.index_label() %> 5 | %strong <%= entries.locus().escape('label') %> 6 | .toolbar 7 | .body 8 | #items 9 | %ul.block-list.entries 10 | <% entries.each(function(entry) { %> 11 | %li.entry.item 12 | %h3 13 | :plain 14 | <%= entry.label() %> 15 | <% if (entry.get('updated_at')) { %> 16 | %dl.meta 17 | %dt.date Updated 18 | %dd.date 19 | :plain 20 | <%= entry.get('updated_at') %> 21 | <% } %> 22 | <% }); %> 23 | -------------------------------------------------------------------------------- /app/views/c2/informant/app/_entry_index_aside.html.haml: -------------------------------------------------------------------------------- 1 | %script{ :'data-template' => 'entryIndexAside', :type => 'text/js-template' } 2 | %aside.call-to-action.new-entry 3 | .body 4 | :plain 5 | 6 | Add a new entry 7 | -------------------------------------------------------------------------------- /app/views/c2/informant/app/_sidebar.html.haml: -------------------------------------------------------------------------------- 1 | %script{ :'data-template' => 'sidebar', :type => 'text/js-template' } 2 | %nav#sitemap.sitemap 3 | .header 4 | %h3= link_to('Locus of Control', '#/') 5 | .body.tree 6 | <% collection.each(function(locus) { %> 7 | %ul 8 | %li.item.section 9 | .label{ :'data-depth' => 1 } 10 | %a.section{ :href => '#' } 11 | %span <%= locus.escape('label') %> 12 | %ul 13 | %li.item.entries 14 | .label{ :'data-depth' => 2 } 15 | :plain 16 | 17 | <%= locus.escape('count') %> 18 | Entries 19 | 20 | 21 | <% locus.buckets.each(function(bucket) { %> 22 | %li.item.bucket 23 | .label{ :'data-depth' => 2 } 24 | :plain 25 | 26 | <%= bucket.escape('count') %> 27 | <%= bucket.escape('label') %> 28 | 29 | <% }); %> 30 | <% }); %> 31 | -------------------------------------------------------------------------------- /app/views/c2/informant/app/show.html.haml: -------------------------------------------------------------------------------- 1 | .content 2 | .header 3 | %h1 4 | %span.type Home 5 | %strong Overview 6 | .body 7 | 8 | = content_for :back do 9 | .content 10 | .header 11 | %h1 12 | %span.type Editing 13 | %strong Overview 14 | .body 15 | %form#entry 16 | 17 | = content_for :sidebar do 18 | %nav#sitemap.sitemap 19 | .header 20 | %h3.current= link_to('Loading ...', '#') 21 | 22 | 23 | = content_for :head do 24 | :javascript 25 | Sexy.script('/javascripts/c2/informant/init.js'); 26 | 27 | = content_for :templates do 28 | = render 'entry_index' 29 | = render 'entry_index_aside' 30 | = render 'sidebar' -------------------------------------------------------------------------------- /app/views/c2/reporter/app/_sidebar.html.haml: -------------------------------------------------------------------------------- 1 | %script{ :'data-template' => 'sidebar', :type => 'text/js-template' } 2 | %nav#sitemap.sitemap 3 | .header 4 | %h3= link_to('Locus of Control', '#/') 5 | .body.tree 6 | <% collection.each(function(locus) { %> 7 | %ul 8 | %li.item.section 9 | .label{ :'data-depth' => 1 } 10 | %a.section{ :href => '#' } 11 | %span <%= locus.escape('label') %> 12 | %ul 13 | %li.item.entries 14 | .label{ :'data-depth' => 2 } 15 | :plain 16 | 17 | <%= locus.escape('count') %> 18 | Entries 19 | 20 | 21 | <% locus.buckets.each(function(bucket) { %> 22 | %li.item.bucket 23 | .label{ :'data-depth' => 2 } 24 | :plain 25 | 26 | <%= bucket.escape('count') %> 27 | <%= bucket.escape('label') %> 28 | 29 | <% }); %> 30 | <% }); %> 31 | -------------------------------------------------------------------------------- /app/views/c2/reporter/app/show.html.haml: -------------------------------------------------------------------------------- 1 | .content 2 | .header 3 | %h1 4 | %span.type Reporter 5 | %strong Newsroom 6 | %a#summation.option{:href => '#'} Σ 7 | .body 8 | #chart{ :style => "width: 100%; height: 400px; margin-bottom: 40px;" } 9 | 10 | = content_for :back do 11 | .content 12 | .header 13 | %h1 14 | %span.type Editing 15 | %strong Overview 16 | .body 17 | %form#entry 18 | 19 | = content_for :sidebar do 20 | %nav#sitemap.sitemap 21 | .header 22 | %nav#sitemap.sitemap 23 | .header 24 | %h3.current= link_to('Locus of Control', '#/') 25 | .body.tree{ 'data-bind' => 'partial: loci#view[menu]'} 26 | 27 | = content_for :head do 28 | = javascript_include_tag 'c2/common' 29 | = javascript_include_tag 'c2/vertebrae' 30 | = javascript_include_tag 'c2/reporter_lib' 31 | = javascript_include_tag 'c2/reporter_templates' 32 | = javascript_include_tag 'c2/reporter_vertebrae' 33 | 34 | :javascript 35 | $(function(){ 36 | Reporter.init({ loci: #{ @reports.to_json } }); 37 | }); 38 | -------------------------------------------------------------------------------- /app/views/layouts/c2.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %head 3 | %title= params[:controller].split('/').map(&:titleize).join(' ') 4 | = csrf_meta_tag 5 | 6 | %link{ :rel => 'stylesheet', :href => '/stylesheets/c2.css', :type => 'text/css', :media => 'screen', :charset => 'utf-8' } 7 | %script{:src => '/javascripts/c2/lib/Sexy.min.js', :type => 'text/javascript'} 8 | 9 | = yield :head 10 | 11 | %body.pictos-icons 12 | #templates 13 | = yield :templates 14 | #wrapper 15 | #header-wrapper 16 | %header#header 17 | %h1= link_to('Command & Control', '#') 18 | %h2=# link_to('Version 0.0.1', '#', :class => 'preview') 19 | .url= link_to('Control Yourself!', '#', :class => 'external-link') 20 | %ul.links 21 | %li#user-name= link_to(current_c2_agent_title, '#') 22 | %li=# link_to('Help & Feedback', '#', :id => 'support-link') 23 | %li=# link_to('Flip', '#', :class => 'back flip-trigger') 24 | 25 | %nav 26 | %ul 27 | %li.informant.tab{:class => "#{params[:controller] == 'c2/informant/app' ? 'current' : '' }"}= link_to('Informant', c2_informant_app_path) 28 | %li.reports.tab{:class => "#{params[:controller] == 'c2/reporter/app' ? 'current' : '' }"}= link_to('Reporter', c2_reporter_app_path) 29 | 30 | #main-wrapper 31 | #sidebar-wrapper 32 | #sidebar 33 | = yield :sidebar 34 | #main-content 35 | #notices 36 | = yield :notices 37 | #app-wrapper 38 | #app 39 | #app-front.sheet 40 | = yield 41 | #app-back.sheet 42 | = yield :back 43 | #aside-wrapper 44 | #aside 45 | = yield :aside 46 | #footer-wrapper 47 | #footer 48 | = yield :footer 49 | 50 | 51 | -------------------------------------------------------------------------------- /c2.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "c2" 8 | s.version = "0.1.13" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["hexorx"] 12 | s.date = "2011-11-01" 13 | s.description = " C2 aspires to be a simple, drop in, Admin Portal + CMS for apps based on Rails 3 + Mongoid. " 14 | s.email = "hexorx@gmail.com" 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.md" 18 | ] 19 | s.files = [ 20 | "app/controllers/c2/base_controller.rb", 21 | "app/controllers/c2/informant/app_controller.rb", 22 | "app/controllers/c2/informant/buckets_controller.rb", 23 | "app/controllers/c2/informant/entries_controller.rb", 24 | "app/controllers/c2/informant/locus_controller.rb", 25 | "app/controllers/c2/reporter/app_controller.rb", 26 | "app/models/c2/informant/bucket.rb", 27 | "app/models/c2/informant/form_element.rb", 28 | "app/models/c2/informant/locus.rb", 29 | "app/models/c2/reporter/bucket.rb", 30 | "app/models/c2/reporter/report.rb", 31 | "app/stylesheets/c2.scss", 32 | "app/views/c2/informant/app/_entry_index.html.haml", 33 | "app/views/c2/informant/app/_entry_index_aside.html.haml", 34 | "app/views/c2/informant/app/_sidebar.html.haml", 35 | "app/views/c2/informant/app/show.html.haml", 36 | "app/views/c2/reporter/app/_sidebar.html.haml", 37 | "app/views/c2/reporter/app/show.html.haml", 38 | "app/views/layouts/c2.html.haml", 39 | "config/initializers/c2_inflections.rb", 40 | "config/mongoid.yml", 41 | "config/routes.rb", 42 | "lib/c2.rb", 43 | "lib/c2/controller_additions.rb", 44 | "lib/c2/engine.rb", 45 | "lib/c2/exceptions.rb", 46 | "lib/c2/tasks.rake", 47 | "lib/generators/c2/install_generator.rb", 48 | "public/images/c2/alert-overlay.png", 49 | "public/images/c2/back-light.png", 50 | "public/images/c2/bg-body.png", 51 | "public/images/c2/bg-header.png", 52 | "public/images/c2/categories-small.png", 53 | "public/images/c2/down-arrow.png", 54 | "public/images/c2/entries-small.png", 55 | "public/images/c2/external-link.png", 56 | "public/images/c2/eye.png", 57 | "public/images/c2/loading.gif", 58 | "public/images/c2/new-category.png", 59 | "public/images/c2/new-entry.png", 60 | "public/images/c2/new-section.png", 61 | "public/images/c2/ops.png", 62 | "public/images/c2/trashcan.png", 63 | "public/javascripts/c2/common.js", 64 | "public/javascripts/c2/informant/application.js", 65 | "public/javascripts/c2/informant/collections/buckets.js", 66 | "public/javascripts/c2/informant/collections/entries.js", 67 | "public/javascripts/c2/informant/collections/locus.js", 68 | "public/javascripts/c2/informant/controllers/app.js", 69 | "public/javascripts/c2/informant/init.js", 70 | "public/javascripts/c2/informant/models/bucket.js", 71 | "public/javascripts/c2/informant/models/entry.js", 72 | "public/javascripts/c2/informant/models/locus.js", 73 | "public/javascripts/c2/informant/views/entries/edit.js", 74 | "public/javascripts/c2/informant/views/entries/index.js", 75 | "public/javascripts/c2/informant/views/notice.js", 76 | "public/javascripts/c2/informant/views/sidebar.js", 77 | "public/javascripts/c2/lib/Sexy.min.js", 78 | "public/javascripts/c2/lib/backbone-min.js", 79 | "public/javascripts/c2/lib/grid.js", 80 | "public/javascripts/c2/lib/jquery.Sexy.min.js", 81 | "public/javascripts/c2/lib/jquery.activity-indicator-1.0.0.min.js", 82 | "public/javascripts/c2/lib/jquery.ba-dotimeout.min.js", 83 | "public/javascripts/c2/lib/jquery.dform-0.1.2.min.js", 84 | "public/javascripts/c2/lib/jquery.min.js", 85 | "public/javascripts/c2/lib/jquery.timeago.js", 86 | "public/javascripts/c2/lib/pure_min.js", 87 | "public/javascripts/c2/lib/underscore.min.js", 88 | "public/javascripts/c2/reporter_lib.js", 89 | "public/javascripts/c2/reporter_templates.js", 90 | "public/javascripts/c2/reporter_vertebrae.js", 91 | "public/javascripts/c2/vertebrae.js", 92 | "public/stylesheets/c2.css" 93 | ] 94 | s.homepage = "http://crackersnack.com/Command-And-Control" 95 | s.require_paths = ["lib"] 96 | s.rubygems_version = "1.8.10" 97 | s.summary = "Take control of your Rails 3 and Mongoid based site with the Comand & Control Admin Portal + CMS." 98 | s.test_files = [ 99 | "spec/dummy/app/controllers/application_controller.rb", 100 | "spec/dummy/app/helpers/application_helper.rb", 101 | "spec/dummy/app/models/user.rb", 102 | "spec/dummy/config/application.rb", 103 | "spec/dummy/config/boot.rb", 104 | "spec/dummy/config/environment.rb", 105 | "spec/dummy/config/environments/development.rb", 106 | "spec/dummy/config/environments/production.rb", 107 | "spec/dummy/config/environments/test.rb", 108 | "spec/dummy/config/initializers/backtrace_silencers.rb", 109 | "spec/dummy/config/initializers/inflections.rb", 110 | "spec/dummy/config/initializers/mime_types.rb", 111 | "spec/dummy/config/initializers/secret_token.rb", 112 | "spec/dummy/config/initializers/session_store.rb", 113 | "spec/dummy/config/routes.rb", 114 | "spec/dummy/db/seeds.rb", 115 | "spec/dummy/test/performance/browsing_test.rb", 116 | "spec/dummy/test/test_helper.rb", 117 | "spec/fabricators/locus_fabricator.rb", 118 | "spec/fabricators/user_fabricator.rb", 119 | "spec/models/locus_spec.rb", 120 | "spec/spec_helper.rb" 121 | ] 122 | 123 | if s.respond_to? :specification_version then 124 | s.specification_version = 3 125 | 126 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 127 | s.add_runtime_dependency(%q, [">= 3.0.0"]) 128 | s.add_runtime_dependency(%q, ["~> 2.1"]) 129 | s.add_runtime_dependency(%q, ["~> 1.3"]) 130 | s.add_runtime_dependency(%q, [">= 0"]) 131 | s.add_development_dependency(%q, [">= 0"]) 132 | s.add_development_dependency(%q, [">= 0"]) 133 | else 134 | s.add_dependency(%q, [">= 3.0.0"]) 135 | s.add_dependency(%q, ["~> 2.1"]) 136 | s.add_dependency(%q, ["~> 1.3"]) 137 | s.add_dependency(%q, [">= 0"]) 138 | s.add_dependency(%q, [">= 0"]) 139 | s.add_dependency(%q, [">= 0"]) 140 | end 141 | else 142 | s.add_dependency(%q, [">= 3.0.0"]) 143 | s.add_dependency(%q, ["~> 2.1"]) 144 | s.add_dependency(%q, ["~> 1.3"]) 145 | s.add_dependency(%q, [">= 0"]) 146 | s.add_dependency(%q, [">= 0"]) 147 | s.add_dependency(%q, [">= 0"]) 148 | end 149 | end 150 | 151 | -------------------------------------------------------------------------------- /config/initializers/c2_inflections.rb: -------------------------------------------------------------------------------- 1 | ActiveSupport::Inflector.inflections do |inflect| 2 | inflect.irregular 'locus', 'locus' 3 | end -------------------------------------------------------------------------------- /config/mongoid.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | host: localhost 3 | autocreate_indexes: false 4 | allow_dynamic_fields: true 5 | include_root_in_json: false 6 | parameterize_keys: true 7 | persist_in_safe_mode: false 8 | raise_not_found_error: true 9 | reconnect_time: 3 10 | 11 | development: 12 | <<: *defaults 13 | database: develop_control 14 | 15 | test: 16 | <<: *defaults 17 | database: test_control 18 | 19 | production: 20 | <<: *defaults 21 | database: command_control 22 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | namespace :c2 do 3 | namespace :informant do 4 | get '/', :to => 'app#show', :as => :app 5 | resources :locus, :only => [:index, :create, :update] do 6 | resources :entries 7 | resources :buckets do 8 | resources :entries 9 | end 10 | end 11 | end 12 | namespace :reporter do 13 | get '/', :to => 'app#show', :as => :app 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /lib/c2.rb: -------------------------------------------------------------------------------- 1 | if defined?(Rails) && Rails::VERSION::MAJOR == 3 2 | require 'c2/engine' 3 | require 'c2/exceptions' 4 | require 'c2/controller_additions' 5 | end 6 | -------------------------------------------------------------------------------- /lib/c2/controller_additions.rb: -------------------------------------------------------------------------------- 1 | module C2 2 | module ControllerAdditions 3 | def self.included(base) 4 | base.extend ClassMethods 5 | base.helper_method :current_c2_agent, :current_c2_agent_title, :c2_login_path, :c2_profile_path, :c2_logout_path 6 | end 7 | 8 | module ClassMethods 9 | def current_c2_agent 10 | @current_c2_agent ||= current_user 11 | end 12 | 13 | def current_c2_agent_title 14 | @current_c2_agent_title ||= current_c2_agent.send(([:c2_title, :title, :name, :email].map(&:to_s) & current_c2_agent.methods).first) 15 | end 16 | 17 | def c2_clearance? 18 | current_c2_agent && current_c2_agent.admin? 19 | end 20 | 21 | def authorize_c2_agent 22 | raise C2::AccessDenied unless c2_clearance? 23 | end 24 | 25 | def c2_login_path 26 | '/' 27 | end 28 | 29 | def c2_profile_path 30 | '/' 31 | end 32 | 33 | def c2_logout_path 34 | '/' 35 | end 36 | end 37 | end 38 | end 39 | 40 | if defined? ActionController 41 | ActionController::Base.class_eval do 42 | include C2::ControllerAdditions 43 | end 44 | end -------------------------------------------------------------------------------- /lib/c2/engine.rb: -------------------------------------------------------------------------------- 1 | module C2 2 | class Engine < Rails::Engine 3 | 4 | rake_tasks do 5 | load 'c2/tasks.rake' 6 | end 7 | 8 | initializer "static assets" do |app| 9 | app.middleware.use ::ActionDispatch::Static, "#{root}/public" 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /lib/c2/exceptions.rb: -------------------------------------------------------------------------------- 1 | module C2 2 | class AccessDenied < StandardError 3 | attr_reader :action, :subject 4 | attr_writer :default_message 5 | 6 | def initialize(message = nil, subject = nil) 7 | @message = message 8 | @subject = subject 9 | @default_message = "You are not authorized to access this page." 10 | end 11 | 12 | def to_s 13 | @message || @default_message 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /lib/c2/tasks.rake: -------------------------------------------------------------------------------- 1 | namespace :c2 do 2 | def get_mongoid_models 3 | documents = [] 4 | Dir.glob("app/models/**/*.rb").sort.each do |file| 5 | model_path = file[0..-4].split('/')[2..-1] 6 | begin 7 | klass = model_path.map(&:classify).join('::').constantize 8 | if klass.ancestors.include?(Mongoid::Document) && !klass.embedded 9 | documents << klass 10 | end 11 | rescue => e 12 | # Just for non-mongoid objects that dont have the embedded 13 | # attribute at the class level. 14 | end 15 | end 16 | documents 17 | end 18 | 19 | def valid_scopes_for(document) 20 | valid_scopes = document.scopes.select do |method, scope| 21 | valid = case conditions = scope.conditions 22 | when Hash 23 | true 24 | when Proc 25 | true if conditions.arity == 0 26 | else 27 | false 28 | end 29 | valid || false 30 | end 31 | Hash[*valid_scopes.flatten] 32 | end 33 | 34 | def categorized_fields_for(document) 35 | valid_fields = document.fields.map do |name, field| 36 | tag = case field.type.name 37 | when 'String', 'Object' 38 | if field.options[:metadata] 39 | nil 40 | elsif name.to_s.ends_with?('_password') 41 | [name.to_s, 'password'] 42 | else 43 | [name.to_s, 'text'] 44 | end 45 | when 'Boolean' 46 | [name.to_s, 'checkbox'] 47 | else 48 | nil 49 | end 50 | end 51 | Hash[*valid_fields.compact.flatten] 52 | end 53 | 54 | desc 'List all mongoid models' 55 | task :discover => :environment do 56 | locus_list = get_mongoid_models.map do |document| 57 | C2::Informant::Locus.find_or_create_by({ :class_name => document.name }) 58 | end 59 | locus_list.each do |document| 60 | puts document.label 61 | 62 | puts ' > Scopes' 63 | valid_scopes_for(document.klass).each do |method, scope| 64 | puts " - #{method}" 65 | document.buckets.find_or_create_by({ :method_name => method.to_s }) 66 | end 67 | 68 | puts ' > Fields' 69 | categorized_fields_for(document.klass).each do |name, tag| 70 | puts " - #{name} as #{tag}" 71 | unless document.elements.where({ :name => name }).first 72 | document.elements.create({ :name => name, :tag => tag }) 73 | end 74 | end 75 | end 76 | 77 | end 78 | 79 | end -------------------------------------------------------------------------------- /lib/generators/c2/install_generator.rb: -------------------------------------------------------------------------------- 1 | class C2::InstallGenerator < Rails::Generators::Base 2 | source_root File.expand_path('../../../../', __FILE__) 3 | desc "Configure your app to work with C2." 4 | 5 | def cache_js 6 | directory "public/javascripts/c2", "public/javascripts/c2" 7 | end 8 | 9 | def cache_css 10 | copy_file "public/stylesheets/c2.css", "public/stylesheets/c2.css" 11 | end 12 | 13 | def cache_images 14 | directory "public/images/c2", "public/images/c2" 15 | end 16 | end -------------------------------------------------------------------------------- /public/images/c2/alert-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/alert-overlay.png -------------------------------------------------------------------------------- /public/images/c2/back-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/back-light.png -------------------------------------------------------------------------------- /public/images/c2/bg-body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/bg-body.png -------------------------------------------------------------------------------- /public/images/c2/bg-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/bg-header.png -------------------------------------------------------------------------------- /public/images/c2/categories-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/categories-small.png -------------------------------------------------------------------------------- /public/images/c2/down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/down-arrow.png -------------------------------------------------------------------------------- /public/images/c2/entries-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/entries-small.png -------------------------------------------------------------------------------- /public/images/c2/external-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/external-link.png -------------------------------------------------------------------------------- /public/images/c2/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/eye.png -------------------------------------------------------------------------------- /public/images/c2/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/loading.gif -------------------------------------------------------------------------------- /public/images/c2/new-category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/new-category.png -------------------------------------------------------------------------------- /public/images/c2/new-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/new-entry.png -------------------------------------------------------------------------------- /public/images/c2/new-section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/new-section.png -------------------------------------------------------------------------------- /public/images/c2/ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/ops.png -------------------------------------------------------------------------------- /public/images/c2/trashcan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexorx/Command-And-Control/bc3eef46efddcaeadb551d5acd985c8c93e00977/public/images/c2/trashcan.png -------------------------------------------------------------------------------- /public/javascripts/c2/informant/application.js: -------------------------------------------------------------------------------- 1 | var Informant = { 2 | init: function() { 3 | Informant.loadTemplates(); 4 | Informant.App = new Informant.AppController(); 5 | Backbone.history.start(); 6 | }, 7 | JST: {}, 8 | loadTemplates: function() { 9 | $('[type="text/js-template"]').each(function() { 10 | Informant.JST[$(this).data('template')] = _.template($(this).html()); 11 | }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /public/javascripts/c2/informant/collections/buckets.js: -------------------------------------------------------------------------------- 1 | Informant.BucketList = Backbone.Collection.extend({ 2 | model: Informant.BucketModel, 3 | url: function() { 4 | return this.parent.url() + '/buckets'; 5 | }, 6 | initialize: function(buckets, options) { 7 | this.parent = options.parent; 8 | }, 9 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/collections/entries.js: -------------------------------------------------------------------------------- 1 | Informant.EntryList = Backbone.Collection.extend({ 2 | model: Informant.EntryModel, 3 | url: function() { 4 | return this.parent.url() + '/entries'; 5 | }, 6 | initialize: function(buckets, options) { 7 | this.parent = options.parent; 8 | }, 9 | locus: function() { 10 | return this.parent.locus(); 11 | }, 12 | path: function() { 13 | return this.parent.entries_path(); 14 | }, 15 | newPath: function() { 16 | return this.locus().entries_path() + '/new'; 17 | } 18 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/collections/locus.js: -------------------------------------------------------------------------------- 1 | Informant.LocusList = Backbone.Collection.extend({ 2 | model: Informant.LocusModel, 3 | initialize: function() { 4 | this.url = '/c2/informant/locus'; 5 | } 6 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/controllers/app.js: -------------------------------------------------------------------------------- 1 | Informant.AppController = Backbone.Controller.extend({ 2 | routes: { 3 | "/": "root", 4 | "/locus/:locusId/entries": "entryIndex", 5 | "/locus/:locusId/entries/new": "entryNew", 6 | "/locus/:locusId/entries/:entryId/edit": "entryEdit", 7 | "/locus/:locusId/buckets/:bucketId/entries": "entryIndex" 8 | }, 9 | 10 | initialize: function(options) { 11 | this._current_nav_link = window.location.hash; 12 | this._locus = new Informant.LocusList(); 13 | this._sidebar = new Informant.SidebarView({ collection: this._locus }); 14 | this._entryIndex = new Informant.EntryIndexView(); 15 | this._locus.fetch(); 16 | }, 17 | 18 | root: function() { 19 | }, 20 | 21 | entryIndex: function(locusId,bucketId) { 22 | var locus = this._locus.get(locusId); 23 | if ( bucketId ) { 24 | var bucket = locus.buckets.get(bucketId); 25 | } 26 | var entries = (bucket || locus).entries 27 | 28 | this._current_nav_link = entries.path(); 29 | this._entryIndex.render(entries); 30 | }, 31 | 32 | entryNew: function(locusId) { 33 | var locus = this._locus.get(locusId); 34 | var entry = locus.entries.add().last(); 35 | new Informant.EntryEditView({ model: entry }); 36 | }, 37 | 38 | entryEdit: function(locusId,entryId) { 39 | var locus = this._locus.get(locusId); 40 | var entry = locus.entries.get(entryId); 41 | new Informant.EntryEditView({ model: entry }); 42 | } 43 | 44 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/init.js: -------------------------------------------------------------------------------- 1 | var lib_dir = '/javascripts/c2/lib/'; 2 | var app_dir = '/javascripts/c2/informant/'; 3 | 4 | Sexy 5 | .js(lib_dir + 'jquery.min.js') 6 | .js(lib_dir + 'underscore.min.js') 7 | .js(lib_dir + 'backbone-min.js') 8 | .js(lib_dir + 'jquery.ba-dotimeout.min.js') 9 | .js(lib_dir + 'jquery.timeago.js') 10 | .js(lib_dir + 'jquery.activity-indicator-1.0.0.min.js') 11 | .js(lib_dir + 'jquery.dform-0.1.2.min.js') 12 | .js(app_dir + 'application.js') 13 | .js(app_dir + 'views/notice.js') 14 | .js(app_dir + 'views/sidebar.js') 15 | .js(app_dir + 'views/entries/index.js') 16 | .js(app_dir + 'views/entries/edit.js') 17 | .js(app_dir + 'controllers/app.js') 18 | .js(app_dir + 'models/locus.js') 19 | .js(app_dir + 'models/bucket.js') 20 | .js(app_dir + 'models/entry.js') 21 | .js(app_dir + 'collections/locus.js') 22 | .js(app_dir + 'collections/buckets.js') 23 | .js(app_dir + 'collections/entries.js', function() { 24 | $(function() { 25 | // CSRF Load 26 | $(document).ajaxSend(function(e, xhr, options) { 27 | var token = $("meta[name='csrf-token']").attr("content"); 28 | xhr.setRequestHeader("X-CSRF-Token", token); 29 | }); 30 | 31 | window.location.hash = '/'; 32 | 33 | // Get a Backbone 34 | Informant.init(); 35 | 36 | $('.flip-trigger').bind('click', function() { 37 | $('#app').toggleClass('flip'); 38 | }); 39 | 40 | // Triggers 41 | $('.dropdown-trigger').bind('click', function() { 42 | $(this).parent().toggleClass('active'); 43 | }); 44 | }); 45 | }); 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/javascripts/c2/informant/models/bucket.js: -------------------------------------------------------------------------------- 1 | Informant.BucketModel = Backbone.Model.extend({ 2 | initialize: function(options) { 3 | this.id = options._id; 4 | this.entries = new Informant.EntryList(options.entries_page, { parent: this, cached: false }); 5 | }, 6 | 7 | locus: function() { 8 | return this.collection.parent; 9 | }, 10 | 11 | entries_path: function() { 12 | return '#/locus/' + this.locus().id + '/buckets/' + this.id + '/entries'; 13 | }, 14 | 15 | index_label: function() { return this.escape('label'); } 16 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/models/entry.js: -------------------------------------------------------------------------------- 1 | Informant.EntryModel = Backbone.Model.extend({ 2 | initialize: function(options) { 3 | this.id = (options._id || options.id); 4 | // this.bind('save', Informant.App._locus.fetch()); 5 | }, 6 | 7 | locus: function() { 8 | return this.collection.locus(); 9 | }, 10 | 11 | label: function() { 12 | return this.get(this.locus().escape('entry_label')); 13 | }, 14 | 15 | form: function() { 16 | var that = this; 17 | var form = that.locus().get('entry_form_builder'); 18 | var fields = _(form.elements).map(function(element) { 19 | return (element.id == 'entry-fields') ? element : null ; 20 | }); 21 | 22 | fields = _(fields).compact()[0]; 23 | fields = _(fields.elements).map(function(element) { 24 | return _(element.elements).select(function(e) { return e['class'] != 'tip'; })[0]; 25 | }); 26 | _(fields).each(function(field, index, list) { 27 | if (field.type == 'checkbox') { field.checked = that.get(field.name) } 28 | else { field.value = that.get(field.name); } 29 | }); 30 | form.action = '#'; 31 | return form; 32 | }, 33 | 34 | hash_path: function() { 35 | return this.locus().hash_path() + '/entries/' + this.id; 36 | } 37 | 38 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/models/locus.js: -------------------------------------------------------------------------------- 1 | Informant.LocusModel = Backbone.Model.extend({ 2 | initialize: function(options) { 3 | this.id = options._id; 4 | this.buckets = new Informant.BucketList(options.buckets, { parent: this }); 5 | this.entries = new Informant.EntryList(options.entries_page, { parent: this, cached: false }); 6 | }, 7 | 8 | locus: function() { 9 | return this; 10 | }, 11 | 12 | index_label: function() { return 'All'; }, 13 | 14 | entries_path: function() { 15 | return this.hash_path() + '/entries'; 16 | }, 17 | 18 | hash_path: function() { 19 | return '#/locus/' + this.locus().id; 20 | } 21 | 22 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/views/entries/edit.js: -------------------------------------------------------------------------------- 1 | Informant.EntryEditView = Backbone.View.extend({ 2 | el: $('#app-back'), 3 | 4 | events: { 5 | 'submit #entry': 'save', 6 | 'click .flip-trigger': 'flip' 7 | }, 8 | 9 | initialize: function() { 10 | this.render(); 11 | }, 12 | 13 | render: function() { 14 | if(this.model.isNew()) { 15 | this.$('.content .header h1 .type').html('New'); 16 | this.$('.content .header h1 strong').html(this.model.locus().get('singular_label')); 17 | } else { 18 | this.$('.content .header h1 .type').html('Editing ' + this.model.locus().get('singular_label')); 19 | this.$('.content .header h1 strong').html(this.model.label()); 20 | } 21 | 22 | this.$('#entry').empty().removeClass().buildForm(this.model.form()); 23 | 24 | this.$('abbr.timeago').timeago(); 25 | $('#app').addClass('flip'); 26 | 27 | return this; 28 | }, 29 | 30 | save: function() { 31 | var self = this; 32 | var msg = this.model.isNew() ? 'Entry Successfully Created!' : "Entry Saved!"; 33 | var fields = _(this.$('#entry').serializeArray()).reduce( function(memo, field) { 34 | memo[field.name] = field.value; 35 | return memo; 36 | }, {}); 37 | 38 | this.$('#entry-fields input:checkbox').each(function() { 39 | fields[$(this).attr('name')] = $(this).is(':checked'); 40 | }); 41 | 42 | this.el.activity(); 43 | 44 | this.model.save(fields, { 45 | success: function(model, response) { 46 | if (_.isEmpty(response.errors)) { 47 | new Informant.NoticeView({ message: msg }); 48 | self.clearErrors(); 49 | Informant.App._locus.fetch(); 50 | } else { 51 | new Informant.ErrorView(); 52 | self.addErrors(response.errors); 53 | } 54 | 55 | self.el.activity(false); 56 | }, 57 | error: function(model, response) { 58 | new Informant.ErrorView(); 59 | self.el.activity(false); 60 | } 61 | }); 62 | 63 | return false; 64 | }, 65 | 66 | flip: function() { $('#app').toggleClass('flip'); }, 67 | 68 | addErrors: function(errors) { 69 | console.log(errors); 70 | this.clearErrors(); 71 | _(errors).each(function(msg, field) { 72 | container = $('#entry_' + field + '_field'); 73 | container.addClass('field_with_errors'); 74 | container.find('label').append('' + msg.join(' & ') + ''); 75 | }); 76 | }, 77 | 78 | clearErrors: function() { 79 | $('.field_with_errors span.error').remove(); 80 | $('.field_with_errors').removeClass('.field_with_errors'); 81 | } 82 | 83 | 84 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/views/entries/index.js: -------------------------------------------------------------------------------- 1 | Informant.EntryIndexView = Backbone.View.extend({ 2 | el: $('#app-front'), 3 | 4 | initialize: function(options) { 5 | }, 6 | 7 | render: function(entries) { 8 | var that = this; 9 | that.el.empty(); 10 | that.el.html(Informant.JST.entryIndex({entries: entries})); 11 | $('#aside').html(Informant.JST.entryIndexAside({entries: entries})); 12 | 13 | $('.flip-trigger').bind('click', function() { 14 | $('#app').toggleClass('flip'); 15 | }); 16 | 17 | $('abbr.timeago').timeago(); 18 | 19 | $('#app').removeClass('flip'); 20 | 21 | return this; 22 | } 23 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/views/notice.js: -------------------------------------------------------------------------------- 1 | Informant.NoticeView = Backbone.View.extend({ 2 | className: "success", 3 | displayLength: 5000, 4 | defaultMessage: '', 5 | 6 | initialize: function() { 7 | _.bindAll(this, 'render'); 8 | this.message = this.options.message || this.defaultMessage; 9 | this.render(); 10 | }, 11 | 12 | render: function() { 13 | var view = this; 14 | 15 | $(this.el).html(this.message); 16 | $(this.el).hide(); 17 | $('#notices').html(this.el); 18 | $(this.el).slideDown(); 19 | $.doTimeout(this.displayLength, function() { 20 | $(view.el).slideUp(); 21 | $.doTimeout(2000, function() { 22 | view.remove(); 23 | }); 24 | }); 25 | 26 | return this; 27 | } 28 | }); 29 | 30 | Informant.ErrorView = Informant.NoticeView.extend({ 31 | className: "error", 32 | defaultMessage: 'Uh oh! Something went wrong. Please try again.' 33 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/informant/views/sidebar.js: -------------------------------------------------------------------------------- 1 | Informant.SidebarView = Backbone.View.extend({ 2 | el: $('#sidebar'), 3 | initialize: function(options) { 4 | _.bindAll(this, 'render'); 5 | this.collection.bind('refresh', this.render); 6 | 7 | this.el.activity(); 8 | }, 9 | 10 | render: function() { 11 | this.el.activity(); 12 | this.el.empty(); 13 | this.el.html(Informant.JST.sidebar({collection: this.collection})); 14 | 15 | this.$('.current').removeClass('current'); 16 | this.$('a[href="' + Informant.App._current_nav_link + '"]').parent().addClass('current'); 17 | 18 | this.el.activity(false); 19 | 20 | $('.sitemap a').bind('click', function() { 21 | $('.sitemap .current').removeClass('current'); 22 | $(this).parent().addClass('current'); 23 | }); 24 | 25 | return this; 26 | } 27 | }); -------------------------------------------------------------------------------- /public/javascripts/c2/lib/Sexy.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sexy.js v0.8 3 | * http://sexyjs.com/ 4 | * 5 | * Copyright 2010, Dave Furfero 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://sexyjs.com/license 8 | * 9 | * jQuery JavaScript Library v1.4.2 10 | * http://jquery.com/ 11 | * 12 | * Copyright 2010, John Resig 13 | * Dual licensed under the MIT or GPL Version 2 licenses. 14 | * http://jquery.org/license 15 | */ 16 | (function(j,k,p){var m=location.protocol+"//"+location.hostname+(location.port!==""?":"+location.port:""),f="__",l=["html","json","jsonp","script","style","text","xml"],e,b;function c(i){if(!(this instanceof c)){return new c(i);}this.cfgs=[];this.setup(i);}c.prototype={setup:function(i){this.cfg=i||{};return this;},sajax:function(w){var u=this.cfgs,v=u.length,r=u[v-1],y=w.dataType,q=w.url.indexOf("http")===0&&w.url.indexOf(m)===-1,t=y==="script",A=y==="style",i=v>0?q&&(t||A)?true:w.defer:false,z=w.success||(t||A?o:g),x=w.error||p.noop,n=w.complete||p.noop;u.push(p.extend(true,w,this.cfg,w,{sendAfterSend:[],dataType:(!q&&t||A)?"text":y,success:function(C,B){if(!r||f in r){if(t&&!q){p.globalEval(C);}else{if(A&&!q){C=p.styleEval(C);}}w.status=q?"success":B;w[f]=z.call(w,C,r&&r[f],u[v+1],w.status);if(w.nextSuccess){w.nextSuccess();}else{if(w.sendAfterSuccess){w.sendAfterSuccess();}}}else{r.nextSuccess=p.proxy(function(){w.success(C,B);},w);}},error:function(D,B,C){x.call(w,D,B,C);},complete:function(C,B){n.call(w,C,B);}}));function s(){var B,C;if(A&&q){p.getCSS(w.url,w.success);}else{p.ajax(w);}if(w.sendAfterSend.length>0){for(B=0,C=w.sendAfterSend.length;B0){setTimeout(function(){if(D&&!F){H("timeout");}},G.timeout);}try{D.send(A==="POST"||A==="PUT"||A==="DELETE"?G.data:null);}catch(N){l.ajax.handleError(G,D,null,N);l.ajax.handleComplete(G,D,L,Q);}if(!G.async){H();}return D;},param:function(e,z){var y=[],B=function(C,D){D=l.isFunction(D)?D():D;y[y.length]=encodeURIComponent(C)+"="+encodeURIComponent(D);};if(z===i){z=l.ajaxSettings.traditional;}if(l.isArray(e)||e.jquery){l.each(e,function(){B(this.name,this.value);});}else{for(var A in e){a(A,e[A],z,B);}}return y.join("&").replace(c,"+");}});function a(y,A,e,z){if(l.isArray(A)){l.each(A,function(C,B){if(e||/\[\]$/.test(y)){z(y,B);}else{a(y+"["+(typeof B==="object"||l.isArray(B)?C:"")+"]",B,e,z);}});}else{if(!e&&A!=null&&typeof A==="object"){l.each(A,function(C,B){a(y+"["+C+"]",B,e,z);});}else{z(y,A);}}}l.extend(l.ajax,{active:0,lastModified:{},etag:{},handleError:function(z,B,y,A){if(z.error){z.error.call(z.context,B,y,A);}},handleSuccess:function(y,A,e,z){if(y.success){y.success.call(y.context,z,e,A);}},handleComplete:function(y,z,e){if(y.complete){y.complete.call(y.context,z,e);}},httpSuccess:function(z){try{return !z.status&&location.protocol==="file:"||(z.status>=200&&z.status<300)||z.status===304||z.status===1223||z.status===0;}catch(y){}return false;},httpNotModified:function(A,e){var z=A.getResponseHeader("Last-Modified"),y=A.getResponseHeader("Etag");if(z){l.ajax.lastModified[e]=z;}if(y){l.ajax.etag[e]=y;}return A.status===304||A.status===0;},httpData:function(C,A,z){var y=C.getResponseHeader("content-type")||"",e=A==="xml"||!A&&y.indexOf("xml")>=0,B=e?C.responseXML:C.responseText;if(e&&B.documentElement.nodeName==="parsererror"){l.error("parsererror");}if(z&&z.dataFilter){B=z.dataFilter(B,A);}if(typeof B==="string"){if(A==="json"||!A&&y.indexOf("json")>=0){B=l.parseJSON(B);}else{if(A==="script"||!A&&y.indexOf("javascript")>=0){l.globalEval(B);}}}return B;}});l.extend(l.ajax);return l;})(this,this.document))); -------------------------------------------------------------------------------- /public/javascripts/c2/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.3.3 2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Backbone may be freely distributed under the MIT license. 4 | // For all details and documentation: 5 | // http://documentcloud.github.com/backbone 6 | (function(){var e;e=typeof exports!=="undefined"?exports:this.Backbone={};e.VERSION="0.3.3";var f=this._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var h=this.jQuery||this.Zepto;e.emulateHTTP=false;e.emulateJSON=false;e.Events={bind:function(a,b){this._callbacks||(this._callbacks={});(this._callbacks[a]||(this._callbacks[a]=[])).push(b);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d=0,g=c.length;d/g,">").replace(/"/g, 9 | """)},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return false;if("id"in a)this.id=a.id;for(var g in a){var i=a[g];if(!f.isEqual(c[g],i)){c[g]=i;delete d[g];if(!b.silent){this._changed=true;this.trigger("change:"+g,this,i,b)}}}!b.silent&&this._changed&&this.change(b);return this},unset:function(a,b){b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&& 10 | !this._performValidation(c,b))return false;delete this.attributes[a];delete this._escapedAttributes[a];if(!b.silent){this._changed=true;this.trigger("change:"+a,this,void 0,b);this.change(b)}return this},clear:function(a){a||(a={});var b=this.attributes,c={};for(attr in b)c[attr]=void 0;if(!a.silent&&this.validate&&!this._performValidation(c,a))return false;this.attributes={};this._escapedAttributes={};if(!a.silent){this._changed=true;for(attr in b)this.trigger("change:"+attr,this,void 0,a);this.change(a)}return this}, 11 | fetch:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("read",this,function(d){if(!b.set(b.parse(d),a))return false;a.success&&a.success(b,d)},c);return this},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return false;var c=this,d=j(b.error,c,b),g=this.isNew()?"create":"update";(this.sync||e.sync)(g,this,function(i){if(!c.set(c.parse(i),b))return false;b.success&&b.success(c,i)},d);return this},destroy:function(a){a||(a={});var b=this,c=j(a.error,b,a);(this.sync||e.sync)("delete", 12 | this,function(d){b.collection&&b.collection.remove(b);a.success&&a.success(b,d)},c);return this},url:function(){var a=k(this.collection);if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+this.id},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return!this.id},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=false},hasChanged:function(a){if(a)return this._previousAttributes[a]!= 13 | this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=false,d;for(d in a)if(!f.isEqual(b[d],a[d])){c=c||{};c[d]=a[d]}return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c){b.error?b.error(this,c):this.trigger("error",this,c,b);return false}return true}}); 14 | e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;delete b.comparator}this._boundOnModelEvent=f.bind(this._onModelEvent,this);this._reset();a&&this.refresh(a,{silent:true});this.initialize(a,b)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c').hide().appendTo("body")[0].contentWindow; 22 | "onhashchange"in window&&!a?h(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);return this.loadUrl()},route:function(a,b){this.handlers.push({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();if(a==this.fragment&&this.iframe)a=this.getFragment(this.iframe.location);if(a==this.fragment||a==decodeURIComponent(this.fragment))return false;if(this.iframe)window.location.hash=this.iframe.location.hash=a;this.loadUrl()},loadUrl:function(){var a=this.fragment= 23 | this.getFragment();return f.any(this.handlers,function(b){if(b.route.test(a)){b.callback(a);return true}})},saveLocation:function(a){a=(a||"").replace(l,"");if(this.fragment!=a){window.location.hash=this.fragment=a;if(this.iframe&&a!=this.getFragment(this.iframe.location)){this.iframe.document.open().close();this.iframe.location.hash=a}}}});e.View=function(a){this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize(a)};var q=/^(\w+)\s*(.*)$/;f.extend(e.View.prototype,e.Events, 24 | {tagName:"div",$:function(a){return h(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){h(this.el).remove();return this},make:function(a,b,c){a=document.createElement(a);b&&h(a).attr(b);c&&h(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events)){h(this.el).unbind();for(var b in a){var c=a[b],d=b.match(q),g=d[1];d=d[2];c=f.bind(this[c],this);d===""?h(this.el).bind(g,c):h(this.el).delegate(d,g,c)}}},_configure:function(a){if(this.options)a=f.extend({}, 25 | this.options,a);if(a.model)this.model=a.model;if(a.collection)this.collection=a.collection;if(a.el)this.el=a.el;if(a.id)this.id=a.id;if(a.className)this.className=a.className;if(a.tagName)this.tagName=a.tagName;this.options=a},_ensureElement:function(){if(!this.el){var a={};if(this.id)a.id=this.id;if(this.className)a["class"]=this.className;this.el=this.make(this.tagName,a)}}});var m=function(a,b){var c=r(this,a,b);c.extend=m;return c};e.Model.extend=e.Collection.extend=e.Controller.extend=e.View.extend= 26 | m;var s={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};e.sync=function(a,b,c,d){var g=s[a];a=a==="create"||a==="update"?JSON.stringify(b.toJSON()):null;b={url:k(b),type:g,contentType:"application/json",data:a,dataType:"json",processData:false,success:c,error:d};if(e.emulateJSON){b.contentType="application/x-www-form-urlencoded";b.processData=true;b.data=a?{model:a}:{}}if(e.emulateHTTP)if(g==="PUT"||g==="DELETE"){if(e.emulateJSON)b.data._method=g;b.type="POST";b.beforeSend=function(i){i.setRequestHeader("X-HTTP-Method-Override", 27 | g)}}h.ajax(b)};var n=function(){},r=function(a,b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){return a.apply(this,arguments)};n.prototype=a.prototype;d.prototype=new n;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},k=function(a){if(!(a&&a.url))throw Error("A 'url' property or function must be specified");return f.isFunction(a.url)?a.url():a.url},j=function(a,b,c){return function(d){a?a(b,d):b.trigger("error",b,d,c)}}})(); 28 | -------------------------------------------------------------------------------- /public/javascripts/c2/lib/grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid theme for Highcharts JS 3 | * @author Torstein Hønsi 4 | */ 5 | 6 | Highcharts.theme = { 7 | colors: ['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572', '#FF9655', '#FFF263', '#6AF9C4'], 8 | chart: { 9 | backgroundColor: { 10 | linearGradient: [0, 0, 500, 500], 11 | stops: [ 12 | [0, 'rgb(255, 255, 255)'], 13 | [1, 'rgb(240, 240, 255)'] 14 | ] 15 | }, 16 | borderWidth: 2, 17 | plotBackgroundColor: 'rgba(255, 255, 255, .9)', 18 | plotShadow: true, 19 | plotBorderWidth: 1 20 | }, 21 | title: { 22 | style: { 23 | color: '#000', 24 | font: 'bold 16px "Trebuchet MS", Verdana, sans-serif' 25 | } 26 | }, 27 | subtitle: { 28 | style: { 29 | color: '#666666', 30 | font: 'bold 12px "Trebuchet MS", Verdana, sans-serif' 31 | } 32 | }, 33 | xAxis: { 34 | gridLineWidth: 1, 35 | lineColor: '#000', 36 | tickColor: '#000', 37 | labels: { 38 | style: { 39 | color: '#000', 40 | font: '11px Trebuchet MS, Verdana, sans-serif' 41 | } 42 | }, 43 | title: { 44 | style: { 45 | color: '#333', 46 | fontWeight: 'bold', 47 | fontSize: '12px', 48 | fontFamily: 'Trebuchet MS, Verdana, sans-serif' 49 | 50 | } 51 | } 52 | }, 53 | yAxis: { 54 | minorTickInterval: 'auto', 55 | lineColor: '#000', 56 | lineWidth: 1, 57 | tickWidth: 1, 58 | tickColor: '#000', 59 | labels: { 60 | style: { 61 | color: '#000', 62 | font: '11px Trebuchet MS, Verdana, sans-serif' 63 | } 64 | }, 65 | title: { 66 | style: { 67 | color: '#333', 68 | fontWeight: 'bold', 69 | fontSize: '12px', 70 | fontFamily: 'Trebuchet MS, Verdana, sans-serif' 71 | } 72 | } 73 | }, 74 | legend: { 75 | itemStyle: { 76 | font: '9pt Trebuchet MS, Verdana, sans-serif', 77 | color: 'black' 78 | 79 | }, 80 | itemHoverStyle: { 81 | color: '#039' 82 | }, 83 | itemHiddenStyle: { 84 | color: 'gray' 85 | } 86 | }, 87 | labels: { 88 | style: { 89 | color: '#99b' 90 | } 91 | } 92 | }; 93 | 94 | // Apply the theme 95 | var highchartsOptions = Highcharts.setOptions(Highcharts.theme); 96 | 97 | -------------------------------------------------------------------------------- /public/javascripts/c2/lib/jquery.Sexy.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sexy.js v0.8 3 | * http://sexyjs.com/ 4 | * 5 | * Copyright 2010, Dave Furfero 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://sexyjs.com/license 8 | */ 9 | (function(k,l,q){var h=k.location,o=h.protocol+"//"+h.hostname+(h.port!==""?":"+h.port:""),f="__",m=["html","json","jsonp","script","style","text","xml"],e,b;function c(i){if(!(this instanceof c)){return new c(i);}this.cfgs=[];this.setup(i);}c.prototype={setup:function(i){this.cfg=i||{};return this;},sajax:function(x){var v=this.cfgs,w=v.length,s=v[w-1],z=x.dataType,r=x.url.indexOf("http")===0&&x.url.indexOf(o)===-1,u=z==="script",B=z==="style",i=w>0?r&&(u||B)?true:x.defer:false,A=x.success||(u||B?p:g),y=x.error||q.noop,n=x.complete||q.noop;v.push(q.extend(true,x,this.cfg,x,{sendAfterSend:[],dataType:(!r&&u||B)?"text":z,success:function(D,C){if(!s||f in s){if(u&&!r){q.globalEval(D);}else{if(B&&!r){D=q.styleEval(D);}}x.status=r?"success":C;x[f]=A.call(x,D,s&&s[f],v[w+1],x.status);if(x.nextSuccess){x.nextSuccess();}else{if(x.sendAfterSuccess){x.sendAfterSuccess();}}}else{s.nextSuccess=q.proxy(function(){x.success(D,C);},x);}},error:function(E,C,D){y.call(x,E,C,D);},complete:function(D,C){n.call(x,D,C);}}));function t(){var C,D;if(B&&r){q.getCSS(x.url,x.success);}else{q.ajax(x);}if(x.sendAfterSend.length>0){for(C=0,D=x.sendAfterSend.length;C").addClass("busy");};var animate=function(){};function svg(tag,attr){var el=document.createElementNS("http://www.w3.org/2000/svg",tag||"svg");if(attr){$.each(attr,function(k,v){el.setAttributeNS(null,k,v);});}return $(el);}if(document.createElementNS&&document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect){render=function(target,d){var innerRadius=d.width*2+d.space;var r=(innerRadius+d.length+Math.ceil(d.width/2)+1);var el=svg().width(r*2).height(r*2);var g=svg("g",{"stroke-width":d.width,"stroke-linecap":"round",stroke:d.color}).appendTo(svg("g",{transform:"translate("+r+","+r+")"}).appendTo(el));for(var i=0;i").append(el).width(2*r).height(2*r);};if(document.createElement("div").style.WebkitAnimationName!==undefined){var animations={};animate=function(el,steps,duration){if(!animations[steps]){var name="spin"+steps;var rule="@-webkit-keyframes "+name+" {";for(var i=0;i").css("behavior","url(#default#VML)").appendTo("body");if(s.get(0).adj){var sheet=document.createStyleSheet();$.each(["group","shape","stroke"],function(){sheet.addRule(this,"behavior:url(#default#VML);");});render=function(target,d){var innerRadius=d.width*2+d.space;var r=(innerRadius+d.length+Math.ceil(d.width/2)+1);var s=r*2;var o=-Math.ceil(s/2);var el=$("",{coordsize:s+" "+s,coordorigin:o+" "+o}).css({top:o,left:o,width:s,height:s});for(var i=0;i",{path:"m "+innerRadius+",0 l "+(innerRadius+d.length)+",0"}).css({width:s,height:s,rotation:(360/d.segments*i)+"deg"}).append($("",{color:d.color,weight:d.width+"px",endcap:"round",opacity:$.fn.activity.getOpacity(d,i)})));}return $("",{coordsize:s+" "+s}).css({width:s,height:s,overflow:"hidden"}).append(el);};animate=function(el,steps,duration){var rotation=0;var g=el.get(0);el.data("interval",setInterval(function(){g.style.rotation=++rotation%steps*(360/steps);},duration*1000/steps));};}$(s).remove();}})(jQuery); -------------------------------------------------------------------------------- /public/javascripts/c2/lib/jquery.ba-dotimeout.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery doTimeout: Like setTimeout, but better! - v1.0 - 3/3/2010 3 | * http://benalman.com/projects/jquery-dotimeout-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($){var a={},c="doTimeout",d=Array.prototype.slice;$[c]=function(){return b.apply(window,[0].concat(d.call(arguments)))};$.fn[c]=function(){var f=d.call(arguments),e=b.apply(this,[c+f[0]].concat(f));return typeof f[0]==="number"||typeof f[1]==="number"?this:e};function b(l){var m=this,h,k={},g=l?$.fn:$,n=arguments,i=4,f=n[1],j=n[2],p=n[3];if(typeof f!=="string"){i--;f=l=0;j=n[1];p=n[2]}if(l){h=m.eq(0);h.data(l,k=h.data(l)||{})}else{if(f){k=a[f]||(a[f]={})}}k.id&&clearTimeout(k.id);delete k.id;function e(){if(l){h.removeData(l)}else{if(f){delete a[f]}}}function o(){k.id=setTimeout(function(){k.fn()},j)}if(p){k.fn=function(q){if(typeof p==="string"){p=g[p]}p.apply(m,d.call(n,i))===true&&!q?o():e()};o()}else{if(k.fn){j===undefined?e():k.fn(j===false);return true}else{e()}}}})(jQuery); -------------------------------------------------------------------------------- /public/javascripts/c2/lib/jquery.dform-0.1.2.min.js: -------------------------------------------------------------------------------- 1 | (function(a){function h(b,d,f){if(typeof d=="string"){a.isArray(b[d])||(b[d]=[]);b[d].push(f)}else typeof d=="object"&&a.each(d,function(g,i){h(b,g,i)})}var c={},e={};a.fn.extend({runSubscription:function(b,d,f){var g=this;a.dform.hasSubscription(b)&&a.each(c[b],function(i,j){j.call(g,d,f)});return this},runAll:function(b){var d=b.type,f=this;this.runSubscription("[pre]",b,d);a.each(b,function(g,i){a(f).runSubscription(g,i,d)});this.runSubscription("[post]",b,d);return this},formElement:function(b){var d= 2 | a.dform.createElement(b);this.append(a(d));a(d).runAll(b);return this},buildForm:function(b){if(typeof b=="string"){var d=a(this);a.get(b,function(g){a(d).buildForm(g)},a.dform.options.ajaxFormat)}else if(b.type)this.formElement(b);else{var f=this.is("form")?this:this.append("
").children("form:last");b=a.extend({type:"form"},b);a(f).dformAttr(b);a(f).runAll(b)}},dformAttr:function(b,d){var f=a.keyset(c);a.isArray(d)&&a.merge(f,d);this.attr(a.withoutKeys(b,f));return this}});a.extend(a,{keyset:function(b){var d= 3 | [];a.each(b,function(f){d.push(f)});return d},withKeys:function(b,d){var f={};a.each(d,function(g,i){if(b[i])f[i]=b[i]});return f},withoutKeys:function(b,d){var f={};a.each(b,function(g,i){if(a.inArray(g,d)==-1)f[g]=i});return f}});a.dform={options:{prefix:"ui-dform-",ajaxFormat:"json",defaultType:function(b){return a("<"+b.type+">").dformAttr(b)}},removeType:function(b){delete e[b]},typeNames:function(){return a.keyset(e)},addType:function(b,d){h(e,b,d)},addTypeIf:function(b,d,f){b&&a.dform.addType(d, 4 | f)},subscriberNames:function(){return a.keyset(c)},subscribe:function(b,d){h(c,b,d)},subscribeIf:function(b,d,f){b&&a.dform.subscribe(d,f)},removeSubscription:function(b){delete c[b]},hasSubscription:function(b){return c[b]?true:false},createElement:function(b){var d=b.type;if(!d)throw"No element type given! Must always exist.";var f=null;if(e[d]){var g=a.withoutKeys(b,"type");a.each(e[d],function(i,j){f=j.call(f,g)})}else f=a.dform.options.defaultType(b);return a(f)}}})(jQuery); 5 | (function(a){a.fn.placeholder=function(h){var c=this;a(this).data("placeholder",h);a(this).val(h);a(this).focus(function(){a(this).val()==a(this).data("placeholder")&&a(this).val("")});a(this).blur(function(){a(this).val()==""&&a(this).val(a(this).data("placeholder"))});a(this).parents("form").submit(function(){a(c).val()==a(c).data("placeholder")&&a(c).val("")});return this}})(jQuery); 6 | (function(a){function h(c,e){return function(b){var d=a(c).dformAttr(b,e);if(a(d).is("input")||a(d).is("button"))a(d).attr("type",b.type);return d}}a.dform.addType({text:h(""),password:h(""),submit:h(""),reset:h(""),hidden:h(""),radio:h(""),checkbox:h(""),checkboxes:h("
",["name"]),radiobuttons:h("
",["name"]),container:h("
"),file:h("")});a.dform.subscribe({"class":function(c){this.addClass(c)},html:function(c){this.html(c)},elements:function(c){var e= 7 | a(this);a.each(c,function(b,d){if(typeof b=="string")d.name=name;a(e).formElement(d)})},value:function(c){this.val(c)},options:function(c,e){var b=a(this);if(e=="select")a.each(c,function(d,f){var g;if(typeof f=="string")g=a("