diff --git a/app/admin/node_instructions.rb b/app/admin/node_instructions.rb new file mode 100644 index 000000000..0f2e0fab5 --- /dev/null +++ b/app/admin/node_instructions.rb @@ -0,0 +1,68 @@ +ActiveAdmin.register NodeInstruction do + + menu priority: 3 + + controller do + def scoped_collection + NodeInstruction.includes(:user) + end + end + + filter :user_uname, as: :string + filter :status, as: :select, collection: NodeInstruction::STATUSES + filter :updated_at + + index do + column :id + column :user + + column(:status, sortable: :status) do |ni| + status_tag(ni.status, status_color(ni)) + end + column :updated_at + + default_actions + end + + form do |f| + f.inputs do + f.input :user, as: :select, include_blank: false, collection: User.system.map { |u| [u.uname, u.id] } + f.input :status, as: :select, include_blank: false, collection: NodeInstruction::STATUSES + f.input :instruction, as: :text + end + f.actions + end + + show do + attributes_table do + row :id + row :user + row(:status, sortable: :status) do |ni| + status_tag(ni.status, status_color(ni)) + end + row :created_at + row :updated_at + row :instruction + row :output + end + end + + sidebar 'Actions', only: :show do + + %w(disable ready check restart fail).each do |state| + div do + link_to state.humanize, force_admin_node_instruction_path(resource, state: state), method: :patch + end if resource.send("can_#{state}?") + end + + end + + member_action :force, method: :patch do + if NodeInstruction::STATUSES.include?(params[:state]) + resource.send(params[:state]) + flash[:info] = 'Action added to queue successfully' + end + redirect_to admin_node_instruction_path(resource) + end + +end diff --git a/app/admin/shared/node_instruction.rb b/app/admin/shared/node_instruction.rb new file mode 100644 index 000000000..c911b8415 --- /dev/null +++ b/app/admin/shared/node_instruction.rb @@ -0,0 +1,12 @@ +def status_color(ni) + case + when ni.ready? + :green + when ni.disabled? + nil + when ni.failed? + :red + else + :orange + end +end \ No newline at end of file diff --git a/app/models/node_instruction.rb b/app/models/node_instruction.rb new file mode 100644 index 000000000..2fcfc2d7a --- /dev/null +++ b/app/models/node_instruction.rb @@ -0,0 +1,45 @@ +class NodeInstruction < ActiveRecord::Base + STATUSES = [ + DISABLED = 'disabled', + READY = 'ready', + CHECKING = 'checking', + RESTARTING = 'restarting', + FAILED = 'failed' + ] + + belongs_to :user + + attr_encrypted :instruction, key: APP_CONFIG['keys']['node_instruction_secret_key'] + + validates :user, presence: true + validates :instruction, presence: true, length: { maximum: 10000 } + validates :status, presence: true + validate -> { + errors.add(:status, 'Can be only single active instruction for each node') if !disabled? && NodeInstruction.where('id != ?', id.to_i).where(user_id: user_id, status: STATUSES - [DISABLED]).exists? + } + + attr_accessible :instruction, :user_id, :output, :status + + state_machine :status, initial: :ready do + event :ready do + transition %i(ready restarting disabled failed checking) => :ready + end + + event :disable do + transition ready: :disabled + end + + event :check do + transition ready: :checking + end + + event :restart do + transition checking: :restarting + end + + event :fail do + transition %i(restarting checking) => :failed + end + end + +end diff --git a/config/application.yml.sample b/config/application.yml.sample index 336c5e94c..58a80ea86 100644 --- a/config/application.yml.sample +++ b/config/application.yml.sample @@ -14,6 +14,7 @@ common: &common port: 6379 keys: key_pair_secret_key: 'key_pair_secret_key' + node_instruction_secret_key: 'node_instruction_secret_key' airbrake_api_key: 'airbrake_api_key' logentries_key: 'logentries_key' secret_token: 'secret_token' diff --git a/db/migrate/20140414195426_create_node_instructions.rb b/db/migrate/20140414195426_create_node_instructions.rb new file mode 100644 index 000000000..36a046b17 --- /dev/null +++ b/db/migrate/20140414195426_create_node_instructions.rb @@ -0,0 +1,12 @@ +class CreateNodeInstructions < ActiveRecord::Migration + def change + create_table :node_instructions do |t| + t.integer :user_id, null: false + t.text :encrypted_instruction, null: false + t.text :output + t.string :status + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 59c1f068d..e9d8dee51 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140407181059) do +ActiveRecord::Schema.define(version: 20140414195426) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -287,6 +287,56 @@ ActiveRecord::Schema.define(version: 20140407181059) do t.boolean "increase_release_tag", default: false, null: false end + create_table "users", force: true do |t| + t.string "name" + t.string "email", default: "", null: false + t.string "encrypted_password", limit: 128, default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.datetime "created_at" + t.datetime "updated_at" + t.text "ssh_key" + t.string "uname" + t.string "role" + t.string "language", default: "en" + t.integer "own_projects_count", default: 0, null: false + t.text "professional_experience" + t.string "site" + t.string "company" + t.string "location" + t.string "avatar_file_name" + t.string "avatar_content_type" + t.integer "avatar_file_size" + t.datetime "avatar_updated_at" + t.integer "failed_attempts", default: 0 + t.string "unlock_token" + t.datetime "locked_at" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "authentication_token" + t.integer "build_priority", default: 50 + t.boolean "sound_notifications", default: true + t.index ["authentication_token"], :name => "index_users_on_authentication_token" + t.index ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true + t.index ["email"], :name => "index_users_on_email", :unique => true + t.index ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + t.index ["uname"], :name => "index_users_on_uname", :unique => true + t.index ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true + end + + create_table "node_instructions", force: true do |t| + t.integer "user_id", null: false + t.text "encrypted_instruction", null: false + t.text "output" + t.string "status" + t.datetime "created_at" + t.datetime "updated_at" + t.index ["user_id"], :name => "fk__node_instructions_user_id" + t.foreign_key ["user_id"], "users", ["id"], :on_update => :no_action, :on_delete => :no_action, :name => "fk_node_instructions_user_id" + end + create_table "platform_arch_settings", force: true do |t| t.integer "platform_id", null: false t.integer "arch_id", null: false @@ -530,43 +580,4 @@ ActiveRecord::Schema.define(version: 20140407181059) do t.index ["subject_id", "subject_type"], :name => "index_tokens_on_subject_id_and_subject_type" end - create_table "users", force: true do |t| - t.string "name" - t.string "email", default: "", null: false - t.string "encrypted_password", limit: 128, default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.datetime "created_at" - t.datetime "updated_at" - t.text "ssh_key" - t.string "uname" - t.string "role" - t.string "language", default: "en" - t.integer "own_projects_count", default: 0, null: false - t.text "professional_experience" - t.string "site" - t.string "company" - t.string "location" - t.string "avatar_file_name" - t.string "avatar_content_type" - t.integer "avatar_file_size" - t.datetime "avatar_updated_at" - t.integer "failed_attempts", default: 0 - t.string "unlock_token" - t.datetime "locked_at" - t.string "confirmation_token" - t.datetime "confirmed_at" - t.datetime "confirmation_sent_at" - t.string "authentication_token" - t.integer "build_priority", default: 50 - t.boolean "sound_notifications", default: true - t.index ["authentication_token"], :name => "index_users_on_authentication_token" - t.index ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true - t.index ["email"], :name => "index_users_on_email", :unique => true - t.index ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true - t.index ["uname"], :name => "index_users_on_uname", :unique => true - t.index ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true - end - end