Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Change all the things

  • Loading branch information...
commit 2727f53b48a332d66b0b46496cea1564dce0dca8 1 parent fe3f147
@leehambley leehambley authored
View
312 app/mvmc.rb
@@ -1,267 +1,76 @@
+require 'uri'
+
require 'rexml/document'
require 'builder'
require 'sinatra/base'
require 'sinatra/content_for'
require 'sinatra/i18n'
-VIRSH_URI = "qemu:///system"
+require './lib/virsh/storage_pool'
+require './lib/iso'
+require './lib/vm'
+
+DEFAULT_HYPERVISOR_URL = "qemu:///system"
+
+VIRSH_URI = "qemu+ssh://protonet@cebit.local/system?socket=/var/run/libvirt/libvirt-sock"
VIRSH_POOL_DIR = "/var/lib/libvirt/images"
VMS_DIR = File.expand_path(File.join(File.dirname(__FILE__), '../vms/'))
ISOS_DIR = File.expand_path(File.join(File.dirname(__FILE__), '../isos/'))
-ISO = Struct.new(:basename, :mtime, :size)
-
-$libvirt = Libvirt::open(VIRSH_URI)
-
-module Virsh
-
- class StoragePool
-
- attr_accessor :name, :uuid
-
- def num_of_volumes
- pool.num_of_volumes
- end
-
- def volumes
- pool.list_volumes.collect do |volume_name|
- pool.lookup_volume_by_name(volume_name)
- end
- end
-
- def create_volume(name, capacity)
- pool.create_volume_xml(create_volume_xml_string(name, capacity))
- end
+class Mvmc < Sinatra::Base
- class << self
- def find(uuid)
- pool = $libvirt.lookup_storage_pool_by_uuid(uuid)
- StoragePool.new.tap do |sp|
- sp.uuid = uuid
- end
- end
- def create_default
- $libvirt.define_storage_pool_xml(create_pool_xml).tap do |pool|
- pool.build
- pool.create
- pool.autostart = true
- end
- end
- def all
- $libvirt.list_storage_pools.collect do |pool_name|
- pool = $libvirt.lookup_storage_pool_by_name(pool_name)
- StoragePool.new.tap do |sp|
- sp.name = pool.name
- sp.uuid = pool.uuid
- end
- end
- end
+ set :root, File.join(File.dirname(__FILE__), '../')
+ set :locales, File.join(File.dirname(__FILE__), '../', 'config/locales/en.yml')
- private
+ helpers Sinatra::Cookies
+ helpers Sinatra::ContentFor
- def create_pool_xml
- xml = Builder::XmlMarkup.new
- xml.instruct! :xml, :version => '1.1'
- xml.pool type: :dir do |pool|
- pool.name "virsh-images"
- pool.target do |target|
- target.path VIRSH_POOL_DIR
- end
- end
- warn xml.target!
- xml.target!
- end
- end
+ register Sinatra::I18n
- def create_volume_xml_string(name, capacity)
- xml = Builder::XmlMarkup.new
- xml.instruct! :xml, :version => '1.1'
- xml.volume do |volume|
- volume.name "#{name}.img"
- volume.capacity capacity, unit: :G
- end
+ helpers do
+ def h(text)
+ Rack::Utils.escape_html(text)
end
-
- def pool
- $libvirt.lookup_storage_pool_by_uuid(uuid)
+ def hypervisor_url
+ URI.unescape(cookies[:hypervisor_url])
end
-
- end
-
-end
-
-class VM
-
- attr_accessor :id, :uuid, :name
-
- def running?
- domain.active?
- end
-
- def start
- domain.create
end
- def destroy
- domain.destroy
- end
-
- def undefine
- domain.undefine
- end
-
- def shutdown
- domain.shutdown
- end
-
- def state
- domain.state
- end
-
- def vnc_port
- doc = REXML::Document.new(domain.xml_desc)
- elements = doc.elements.to_a('/domain/devices/graphics')
- ports = elements.collect { |e| e.attribute(:port).value }
- ports.first
- end
-
- class << self
-
- def all
- defined + running
- end
-
- def defined
- $libvirt.list_defined_domains.collect do |domain_name|
- domain = $libvirt.lookup_domain_by_name(domain_name)
- VM.new.tap do |vm|
- vm.id = domain.id rescue nil
- vm.uuid = domain.uuid
- vm.name = domain_name
- end
- end
- end
-
- def running
- $libvirt.list_domains.collect do |domain_id|
- domain = $libvirt.lookup_domain_by_id(domain_id)
- VM.new.tap do |vm|
- vm.id = domain_id
- vm.uuid = domain.uuid
- vm.name = domain.name
- end
+ before do
+ #
+ # Try and connect to the Hypervisor
+ #
+ begin
+ hvuri = params[:hypervisor_url] || cookies[:hypervisor_url] || DEFAULT_HYPERVISOR_URL
+ $libvirt.close if $livbirt
+ $libvirt = Libvirt::open(hvuri)
+ rescue Libvirt::ConnectionError
+ warn "Couldn't access hypervisor at #{hvuri}"
+ ensure
+ if !$libvirt or $libvirt.closed?
+ redirect to('/hypervisor') unless request.path_info == '/hypervisor'
+ return
end
end
-
- def create(name, cdiso_paths, volume_paths)
- domain = $libvirt.define_domain_xml(create_xml(name, cdiso_paths, volume_paths))
- domain.tap do |d|
- domain.autostart = true
- domain.create
- end
+ begin
+ Virsh::StoragePool.create_defaults
+ rescue
+ warn "Couldn't create default storage volumes #{$!}"
end
-
- def create_xml(name, cdisos, volume_paths)
-
- xml = Builder::XmlMarkup.new
- xml.instruct! :xml, :version => '1.1'
-
- bus = -1
- devs = ('hda'...'hde').to_a
- vdas = ('vda'...'vde').to_a
-
- xml.domain type: :kvm do |domain|
-
- # Hardware clock in local time
- domain.clock sync: :localtime
-
- domain.name name
-
- domain.memory 262144
- domain.current_memory 262144
-
- domain.vcpu 2
-
- domain.os do |os|
- os.type "hvm", arch: :i686, machine: :pc
- os.boot dev: :cdrom
- end
-
- domain.devices do |devices|
-
- devices.emulator "/usr/bin/kvm"
-
- cdisos.each do |cdiso|
- devices.disk type: :file, device: :cdrom do |disk|
- disk.driver name: :qemu, type: :raw
- disk.source file: cdiso
- disk.target dev: devs.shift, bus: :ide
- disk.readonly
- disk.address type: :drive, controller: 0, bus: (bus += 1), unit: 0
- end
- end
-
- volume_paths.each do |volume_path|
- devices.disk do |disk|
- disk.driver name: :qemu, type: :raw, cache: :none
- disk.source file: volume_path
- disk.target dev: vdas.shift, bus: :virtio
- disk.address type: :pci,
- domain: '0x0000',
- bus: '0x0000',
- slot: '0x04',
- function: '0x00'
- end
- end
-
- devices.controller type: :ide, index: 0 do |controller|
- controller.address type: :pci,
- domain: '0x0000',
- bus: '0x0000',
- slot: '0x01',
- function: '0x01'
- end
-
- devices.interface type: :network do |interface|
- interface.source network: :default
- end
-
- devices.video do |video|
- video.model type: :vga, vram: 262144, heads: 1
- video.address type: :pci, domain: '0x0000', bus: '0x00', slot: '0x02', function: '0x0'
- end
-
- devices.graphics type: :vnc, autostart: :no, listen: '0.0.0.0' do |graphics|
- graphics.listen type: :address, address: '0.0.0.0'
- end
-
- end
-
- end
- xml.target!
- end
-
end
- private
-
- def domain
- $libvirt.lookup_domain_by_uuid(uuid)
+ post '/hypervisor' do
+ response.set_cookie(
+ :hypervisor_url,
+ value: params[:hypervisor_url],
+ expires: Time.now + 3600*24*7
+ )
+ haml :'hypervisor/show'
end
-end
-
-class Mvmc < Sinatra::Base
-
- set :root, File.join(File.dirname(__FILE__), '../')
- set :locales, File.join(File.dirname(__FILE__), '../', 'config/locales/en.yml')
-
- helpers Sinatra::ContentFor
- register Sinatra::I18n
-
- before do
- Virsh::StoragePool.create_default if Virsh::StoragePool.all.empty?
+ get '/hypervisor' do
+ haml :'hypervisor/show'
end
get '/' do
@@ -330,8 +139,22 @@ class Mvmc < Sinatra::Base
post '/isos' do
if params['file']
- File.open(File.join(ISOS_DIR, params['file'][:filename]), 'wb') do |f|
- f.write(params['file'][:tempfile].read)
+ Virsh::StoragePool.find_by_name('virsh-isos').tap do |pool|
+ pool.create_volume(
+ params[:file][:filename],
+ params[:file][:tempfile].size
+ ).tap do |volume|
+ stream = $libvirt.stream
+ volume.upload(
+ stream,
+ 0,
+ params[:file][:tempfile].size
+ )
+ stream.sendall(params[:file][:tempfile]) do |opaque, nbytes|
+ [0, opaque.read(nbytes)]
+ end
+ stream.finish
+ end
end
end
status 200
@@ -345,12 +168,9 @@ class Mvmc < Sinatra::Base
private
def isos
- Dir.glob(File.join(ISOS_DIR, '*.iso')).collect do |file|
- ISO.new(
- File.basename(file),
- File.mtime(file),
- File.size(file) / 1024 / 1024
- )
+ pool = $libvirt.lookup_storage_pool_by_name('virsh-isos')
+ pool.list_volumes.collect do |volume_name|
+ pool.lookup_volume_by_name(volume_name)
end
end
View
11 config/locales/en.yml
@@ -18,10 +18,10 @@ en:
new: "New"
table:
heading:
- basename: "Filename"
- uploaded_at: "Uploaded At"
- size: "Size"
+ path: "Path"
+ allocation: "Allocation"
form:
+ heading: "Upload New ISOs"
files: "Files"
select_files: "Select Files"
index:
@@ -39,7 +39,8 @@ en:
volumes:
heading:
path: "Path"
- allocated_available : "Allocated / Available"
+ allocation : "Allocation"
+ capacity: "Capacity"
vms:
table:
heading:
@@ -57,7 +58,7 @@ en:
bootiso: "Boot ISO"
volumes:
name: "Volume Name"
- capacity: "Capacity (G)"
+ capacity: "Capacity (Bytes)"
cdisos:
"0": "Boot ISO"
"1": "CDROM ISO"
View
1  lib/iso.rb
@@ -0,0 +1 @@
+ISO = Struct.new(:basename, :mtime, :size)
View
95 lib/virsh/storage_pool.rb
@@ -0,0 +1,95 @@
+module Virsh
+
+ class StoragePool
+
+ attr_accessor :name, :uuid
+
+ def num_of_volumes
+ pool.num_of_volumes
+ end
+
+ def volumes
+ pool.list_volumes.collect do |volume_name|
+ pool.lookup_volume_by_name(volume_name)
+ end
+ end
+
+ def create_volume(name, capacity)
+ pool.create_volume_xml(create_volume_xml_string(name, capacity))
+ end
+
+ class << self
+ def find(uuid)
+ pool = $libvirt.lookup_storage_pool_by_uuid(uuid)
+ StoragePool.new.tap do |sp|
+ sp.name = name
+ sp.uuid = uuid
+ end
+ end
+ def find_by_name(name)
+ find($libvirt.lookup_storage_pool_by_name(name).uuid)
+ end
+ def create_defaults
+ begin
+ $libvirt.lookup_storage_pool_by_name('virsh-images')
+ rescue
+ warn "Couldn't find pool virsh-images, will create"
+ $libvirt.define_storage_pool_xml(create_pool_xml('virsh-images')).tap do |pool|
+ pool.build
+ pool.create
+ pool.autostart = true
+ end
+ end
+ begin
+ $libvirt.lookup_storage_pool_by_name('virsh-isos')
+ rescue
+ warn "Couldn't find pool virsh-isos, will create"
+ $libvirt.define_storage_pool_xml(create_pool_xml('virsh-isos')).tap do |pool|
+ pool.build
+ pool.create
+ pool.autostart = true
+ end
+ end
+ end
+ def all
+ $libvirt.list_storage_pools.collect do |pool_name|
+ pool = $libvirt.lookup_storage_pool_by_name(pool_name)
+ StoragePool.new.tap do |sp|
+ sp.name = pool.name
+ sp.uuid = pool.uuid
+ end
+ end
+ end
+
+ private
+
+ def create_pool_xml(name)
+ xml = Builder::XmlMarkup.new
+ xml.instruct! :xml, :version => '1.1'
+ xml.pool type: :dir do |pool|
+ pool.name name
+ pool.target do |target|
+ target.path File.join(VIRSH_POOL_DIR, name)
+ end
+ end
+ warn xml.target!
+ xml.target!
+ end
+ end
+
+ def create_volume_xml_string(name, capacity)
+ xml = Builder::XmlMarkup.new
+ xml.instruct! :xml, :version => '1.1'
+ xml.volume do |volume|
+ volume.name "#{name}.img"
+ volume.capacity capacity
+ end
+ end
+
+ def pool
+ $libvirt.lookup_storage_pool_by_uuid(uuid)
+ end
+
+ end
+
+end
View
160 lib/vm.rb
@@ -0,0 +1,160 @@
+class VM
+
+ attr_accessor :id, :uuid, :name
+
+ def running?
+ domain.active?
+ end
+
+ def start
+ domain.create
+ end
+
+ def destroy
+ domain.destroy
+ end
+
+ def undefine
+ domain.undefine
+ end
+
+ def shutdown
+ domain.shutdown
+ end
+
+ def state
+ domain.state
+ end
+
+ def vnc_port
+ doc = REXML::Document.new(domain.xml_desc)
+ elements = doc.elements.to_a('/domain/devices/graphics')
+ ports = elements.collect { |e| e.attribute(:port).value }
+ ports.first
+ end
+
+ class << self
+
+ def all
+ defined + running
+ end
+
+ def defined
+ $libvirt.list_defined_domains.collect do |domain_name|
+ domain = $libvirt.lookup_domain_by_name(domain_name)
+ VM.new.tap do |vm|
+ vm.id = domain.id rescue nil
+ vm.uuid = domain.uuid
+ vm.name = domain_name
+ end
+ end
+ end
+
+ def running
+ $libvirt.list_domains.collect do |domain_id|
+ domain = $libvirt.lookup_domain_by_id(domain_id)
+ VM.new.tap do |vm|
+ vm.id = domain_id
+ vm.uuid = domain.uuid
+ vm.name = domain.name
+ end
+ end
+ end
+
+ def create(name, cdiso_paths, volume_paths)
+ domain = $libvirt.define_domain_xml(create_xml(name, cdiso_paths, volume_paths))
+ domain.tap do |d|
+ domain.autostart = true
+ domain.create
+ end
+ end
+
+ def create_xml(name, cdisos, volume_paths)
+
+ xml = Builder::XmlMarkup.new
+ xml.instruct! :xml, :version => '1.1'
+
+ bus = -1
+ devs = ('hda'...'hde').to_a
+ vdas = ('vda'...'vde').to_a
+
+ xml.domain type: :kvm do |domain|
+
+ # Hardware clock in local time
+ domain.clock sync: :localtime
+
+ domain.name name
+
+ domain.memory 262144
+ domain.current_memory 262144
+
+ domain.vcpu 2
+
+ domain.os do |os|
+ os.type "hvm", arch: :x86_64, machine: :pc
+ os.boot dev: :cdrom
+ end
+
+ domain.devices do |devices|
+
+ devices.emulator "/usr/bin/kvm"
+
+ cdisos.each do |cdiso|
+ devices.disk type: :file, device: :cdrom do |disk|
+ disk.driver name: :qemu, type: :raw
+ disk.source file: cdiso
+ disk.target dev: devs.shift, bus: :ide
+ disk.readonly
+ disk.address type: :drive, controller: 0, bus: (bus += 1), unit: 0
+ end
+ end
+
+ volume_paths.each do |volume_path|
+ devices.disk do |disk|
+ disk.driver name: :qemu, type: :raw, cache: :none
+ disk.source file: volume_path
+ disk.target dev: vdas.shift, bus: :virtio
+ disk.address type: :pci,
+ domain: '0x0000',
+ bus: '0x0000',
+ slot: '0x04',
+ function: '0x00'
+ end
+ end
+
+ devices.controller type: :ide, index: 0 do |controller|
+ controller.address type: :pci,
+ domain: '0x0000',
+ bus: '0x0000',
+ slot: '0x01',
+ function: '0x01'
+ end
+
+ devices.interface type: :network do |interface|
+ interface.source network: :default
+ end
+
+ devices.video do |video|
+ video.model type: :vga, vram: 262144, heads: 1
+ video.address type: :pci, domain: '0x0000', bus: '0x00', slot: '0x02', function: '0x0'
+ end
+
+ devices.graphics type: :vnc, autostart: :no, listen: '0.0.0.0' do |graphics|
+ graphics.listen type: :address, address: '0.0.0.0'
+ end
+
+ end
+
+ end
+ xml.target!
+ end
+
+ end
+
+ private
+
+ def domain
+ $libvirt.lookup_domain_by_uuid(uuid)
+ end
+
+end
View
150 public/css/bootstrap-combobox.css
@@ -0,0 +1,150 @@
+.combobox-container {
+ margin-bottom: 5px;
+ *zoom: 1;
+}
+.combobox-container:before,
+.combobox-container:after {
+ display: table;
+ content: "";
+}
+.combobox-container:after {
+ clear: both;
+}
+.combobox-container input,
+.combobox-container .uneditable-input {
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+.combobox-container input:focus,
+.combobox-container .uneditable-input:focus {
+ position: relative;
+ z-index: 2;
+}
+.combobox-container .uneditable-input {
+ border-left-color: #ccc;
+}
+.combobox-container .add-on {
+ float: left;
+ display: inline-block;
+ width: auto;
+ min-width: 16px;
+ height: inherit !important;
+ margin-right: -1px;
+ padding: 4px 5px;
+ font-weight: normal;
+ color: #999999;
+ text-align: center;
+ text-shadow: 0 1px 0 #ffffff;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+
+}
+.combobox-container .active {
+ background-color: #a9dba9;
+ border-color: #46a546;
+}
+.combobox-container input,
+.combobox-container .uneditable-input {
+ float: left;
+ -webkit-border-radius: 3px 0 0 3px;
+ -moz-border-radius: 3px 0 0 3px;
+ border-radius: 3px 0 0 3px;
+}
+.combobox-container .uneditable-input {
+ border-left-color: #eee;
+ border-right-color: #ccc;
+}
+.combobox-container .add-on {
+ margin-right: 0;
+ margin-left: -1px;
+ -webkit-border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ border-radius: 0 3px 3px 0;
+}
+.combobox-container input:first-child {
+ *margin-left: -160px;
+}
+.combobox-container input:first-child + .add-on {
+ *margin-left: -21px;
+}
+.combobox-container select {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ border: 0;
+ padding: 0;
+ margin: 0;
+ text-indent: -99999px;
+ *text-indent: 0;
+}
+.form-search .combobox-container,
+.form-inline .combobox-container {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: top;
+}
+.form-search .combobox-container .add-on,
+.form-inline .combobox-container .add-on {
+ vertical-align: middle;
+}
+.combobox-selected .combobox-clear {
+ display: inline-block;
+}
+.combobox-selected .caret {
+ display: none;
+}
+.combobox-clear {
+ display: none;
+ width: 14px;
+ height: 14px;
+ line-height: 14px;
+ vertical-align: top;
+ opacity: 0.3;
+ filter: alpha(opacity=30);
+}
+.dropdown:hover .combobox-clear,
+.open.dropdown .combobox-clear {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.btn .combobox-clear {
+ margin-top: 1px;
+ margin-left: 1px;
+}
+.btn:hover .combobox-clear,
+.open.btn-group .combobox-clear {
+ opacity: 1;
+ filter: alpha(opacity=100);
+}
+.typeahead-long {
+ max-height: 300px;
+ overflow-y: auto;
+}
+.control-group.error .combobox-container .add-on {
+ color: #B94A48;
+ border-color: #B94A48;
+}
+.control-group.error .combobox-container .caret {
+ border-top-color: #B94A48;
+}
+.control-group.warning .combobox-container .add-on {
+ color: #C09853;
+ border-color: #C09853;
+}
+.control-group.warning .combobox-container .caret {
+ border-top-color: #C09853;
+}
+.control-group.success .combobox-container .add-on {
+ color: #468847;
+ border-color: #468847;
+}
+.control-group.success .combobox-container .caret {
+ border-top-color: #468847;
+}
+.btn .combobox-clear [class^="icon-"] {
+ line-height: 1.4em;
+}
View
238 public/js/bootstrap-combobox.js
@@ -0,0 +1,238 @@
+/* =============================================================
+ * bootstrap-combobox.js v1.1.1
+ * =============================================================
+ * Copyright 2012 Daniel Farrell
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+!function( $ ) {
+
+ "use strict";
+
+ var Combobox = function ( element, options ) {
+ this.options = $.extend({}, $.fn.combobox.defaults, options)
+ this.$source = $(element)
+ this.$container = this.setup()
+ this.$element = this.$container.find('input[type=text]')
+ this.$target = this.$container.find('input[type=hidden]')
+ this.$button = this.$container.find('.dropdown-toggle')
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.shown = false
+ this.selected = false
+ this.refresh()
+ this.transferAttributes()
+ this.listen()
+ }
+
+ /* NOTE: COMBOBOX EXTENDS BOOTSTRAP-TYPEAHEAD.js
+ ========================================== */
+
+ Combobox.prototype = $.extend({}, $.fn.typeahead.Constructor.prototype, {
+
+ constructor: Combobox
+
+ , setup: function () {
+ var combobox = $(this.options.template)
+ this.$source.before(combobox)
+ this.$source.hide()
+ return combobox
+ }
+
+ , parse: function () {
+ var that = this
+ , map = {}
+ , source = []
+ , selected = false
+ this.$source.find('option').each(function() {
+ var option = $(this)
+ if (option.val() === '') {
+ that.options.placeholder = option.text()
+ return
+ }
+ map[option.text()] = option.val()
+ source.push(option.text())
+ if(option.attr('selected')) selected = option.html()
+ })
+ this.map = map
+ if (selected) {
+ this.$element.val(selected)
+ this.$container.addClass('combobox-selected')
+ this.selected = true
+ }
+ return source
+ }
+
+ , transferAttributes: function() {
+ this.options.placeholder = this.$source.attr('data-placeholder') || this.options.placeholder
+ this.$element.attr('placeholder', this.options.placeholder)
+ this.$target.prop('name', this.$source.prop('name'))
+ this.$target.val(this.$source.val())
+ this.$source.removeAttr('name') // Remove from source otherwise form will pass parameter twice.
+ this.$element.attr('required', this.$source.attr('required'))
+ this.$element.attr('rel', this.$source.attr('rel'))
+ this.$element.attr('title', this.$source.attr('title'))
+ this.$element.attr('class', this.$source.attr('class'))
+ }
+
+ , toggle: function () {
+ if (this.$container.hasClass('combobox-selected')) {
+ this.clearTarget()
+ this.triggerChange()
+ this.clearElement()
+ } else {
+ if (this.shown) {
+ this.hide()
+ } else {
+ this.clearElement()
+ this.lookup()
+ }
+ }
+ }
+
+ , clearElement: function () {
+ this.$element.val('').focus()
+ }
+
+ , clearTarget: function () {
+ this.$source.val('')
+ this.$target.val('')
+ this.$container.removeClass('combobox-selected')
+ this.selected = false
+ }
+
+ , triggerChange: function () {
+ this.$source.trigger('change')
+ }
+
+ , refresh: function () {
+ this.source = this.parse()
+ this.options.items = this.source.length
+ }
+
+ // modified typeahead function adding container and target handling
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element.val(this.updater(val)).trigger('change')
+ this.$source.val(this.map[val]).trigger('change')
+ this.$target.val(this.map[val]).trigger('change')
+ this.$container.addClass('combobox-selected')
+ this.selected = true
+ return this.hide()
+ }
+
+ // modified typeahead function removing the blank handling and source function handling
+ , lookup: function (event) {
+ this.query = this.$element.val()
+ return this.process(this.source)
+ }
+
+ // modified typeahead function adding button handling and remove mouseleave
+ , listen: function () {
+ this.$element
+ .on('focus', $.proxy(this.focus, this))
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if (this.eventSupported('keydown')) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
+
+ this.$button
+ .on('click', $.proxy(this.toggle, this))
+ }
+
+ // modified typeahead function to clear on type and prevent on moving around
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 39: // right arrow
+ case 38: // up arrow
+ case 37: // left arrow
+ case 36: // home
+ case 35: // end
+ case 16: // shift
+ case 17: // ctrl
+ case 18: // alt
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.clearTarget()
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ // modified typeahead function to force a match and add a delay on hide
+ , blur: function (e) {
+ var that = this
+ this.focused = false
+ var val = this.$element.val()
+ if (!this.selected && val !== '' ) {
+ this.$element.val('')
+ this.$source.val('').trigger('change')
+ this.$target.val('').trigger('change')
+ }
+ if (!this.mousedover && this.shown) setTimeout(function () { that.hide() }, 200)
+ }
+
+ // modified typeahead function to not hide
+ , mouseleave: function (e) {
+ this.mousedover = false
+ }
+ })
+
+ /* COMBOBOX PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.combobox = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('combobox')
+ , options = typeof option == 'object' && option
+ if(!data) $this.data('combobox', (data = new Combobox(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.combobox.defaults = {
+ template: '<div class="combobox-container"><input type="hidden" /><input type="text" autocomplete="off" /><span class="add-on btn dropdown-toggle" data-dropdown="dropdown"><span class="caret"/><span class="combobox-clear"><i class="icon-remove"/></span></span></div>'
+ , menu: '<ul class="typeahead typeahead-long dropdown-menu"></ul>'
+ , item: '<li><a href="#"></a></li>'
+ }
+
+ $.fn.combobox.Constructor = Combobox
+
+}( window.jQuery );
View
3  views/header.haml
@@ -9,3 +9,6 @@
%a{class: ( request.path_info =~ /\/isos/ ? 'active' : '' ), href: "/isos"}= t(:'header.nav.isos')
%li
%a{class: ( request.path_info =~ /\/pools/ ? 'active' : '' ), href: "/pools"}= t(:'header.nav.pools')
+ %form(class="navbar-form pull-right form-inline" action="/hypervisor" method="post")
+ %input{ type: "text", class: "span6", id: 'hypervisor_url', name: "hypervisor_url", value: hypervisor_url }
+ %button.btn(type="submit") Change
View
1  views/hypervisor/capabilities.haml
@@ -0,0 +1 @@
+%pre= $libvirt.capabilities
View
3  views/hypervisor/not_connected.haml
@@ -0,0 +1,3 @@
+.alert
+ = t(:'hypervisor.show.not_connected.alert')
+ %pre= hypervisor_url
View
6 views/hypervisor/show.haml
@@ -0,0 +1,6 @@
+.container-fluid
+ .row-fluid
+ - if $libvirt
+ = haml :'hypervisor/capabilities'
+ - else
+ = haml :'hypervisor/not_connected'
View
20 views/isos/index.haml
@@ -7,26 +7,16 @@
%table.table.table-striped
%thead
%tr
- %th= t(:'isos.table.heading.basename')
- %th= t(:'isos.table.heading.uploaded_at')
- %th= t(:'isos.table.heading.size')
+ %th= t(:'isos.table.heading.path')
+ %th= t(:'isos.table.heading.allocation')
%tbody
- @isos.each do |file|
%tr
%td
- %i.icon-download-alt
- %a{href: "isos/#{file.basename}"}= file.basename
- %td= file.mtime
+ %code= file.path
%td
- = file.size
+ = file.info.allocation / 1024 / 1024
MB
- %td
- %form(action="/vms" method="get")
- %input{type: "hidden", name: "bootiso", id: "bootiso", value: file.basename }
- %button(type="submit" class="btn btn-primary btn-mini")
- %i.icon-plus.icon-white
- = t(:'isos.new')
-
.span6
- %h1 Jumping The Shark
+ %h1= t(:'isos.form.heading')
.well= haml :'isos/form'
View
19 views/pools/index.haml
@@ -1,29 +1,26 @@
.container-fluid
.row-fluid
%table.table.table-striped
- %thead
- %tr
- %th= t(:'storage.pools.index.table.heading')
%tbody
- @pools.each do |pool|
%tr
%th(colspan="3")
- %code= pool.uuid
+ %h3= pool.name
- if pool.volumes.any?
%tr
%td= t(:'storage.pools.index.table.volumes.heading.path')
- %td= t(:'storage.pools.index.table.volumes.heading.allocated_available')
+ %td= t(:'storage.pools.index.table.volumes.heading.allocation')
+ %td= t(:'storage.pools.index.table.volumes.heading.capacity')
- pool.volumes.each do |volume|
%tr
%td
%code= volume.path
%td
- %code
- = volume.info.allocation / 1024 / 1024 / 1024
- GB
- /
- = volume.info.capacity / 1024 / 1024 / 1024
- GB
+ = volume.info.allocation / 1024 / 1024
+ MB
+ %td
+ = volume.info.capacity / 1024 / 1024
+ MB
- else
%tr
%td(colspan="2")= t(:'storage.pools.index.table.volumes.no_volumes')
View
4 views/vms/form.haml
@@ -14,7 +14,7 @@
%select{ id: "vm_cdisos_#{num}", name: "vm[cdisos][#{num}]" }
%option
- @isos.each do |file|
- %option{value: File.join(ISOS_DIR, file.basename)}= file.basename
+ %option{value: file.path}= File.basename(file.path)
%fieldset
%legend Storage
- num = 0
@@ -25,7 +25,7 @@
.control-group
%label.control-label{for: "vm_volumes_#{num}_capacity"}= t(:'vms.form.volumes.capacity')
.controls
- %input.input-mini{type: 'text', id: "vm_volumes_#{num}_capacity", name: "vm[volumes][#{num}][capacity]", value: "25"}
+ %input.input-mini{type: 'text', id: "vm_volumes_#{num}_capacity", name: "vm[volumes][#{num}][capacity]", value: "26214400"}
.form-actions
%input.btn.btn-primary{type: "submit", value: t(:'vms.form.submit')}
View
2  views/vms/index.haml
@@ -27,7 +27,7 @@
%i.icon-stop
= t(:'vms.table.button.stop')
- else
- %a.btn.btn-mini{href: "/vms/#{vm.uuid}/start"}
+ %a.btn.btn-mini.bnt-primary{href: "/vms/#{vm.uuid}/start"}
%i.icon-play
= t(:'vms.table.button.start')
%a.btn.btn-mini.btn-danger{href: "/vms/#{vm.uuid}/undefine"}
Please sign in to comment.
Something went wrong with that request. Please try again.