forked from Bitmessage/virtpool
Compare commits
No commits in common. "development" and "master" have entirely different histories.
developmen
...
master
52
client.py
52
client.py
|
@ -1,52 +0,0 @@
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
class RPCClient:
|
|
||||||
"""
|
|
||||||
RPCClient Class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def create_vm(self, **kwargs):
|
|
||||||
|
|
||||||
url = "http://127.0.0.1:8080/create?"
|
|
||||||
if kwargs:
|
|
||||||
q = ""
|
|
||||||
for k in kwargs.keys():
|
|
||||||
q += "&{}={}".format(k, kwargs[k])
|
|
||||||
|
|
||||||
url += q
|
|
||||||
|
|
||||||
print(url)
|
|
||||||
resp = requests.get(url)
|
|
||||||
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
def start_vm(self, vm):
|
|
||||||
"""get a vm and try starting it
|
|
||||||
return the ID or appropriate message
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def stop_vm(self, id):
|
|
||||||
# stops vm of a given id
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import random
|
|
||||||
|
|
||||||
client = RPCClient()
|
|
||||||
print("Creating VM...")
|
|
||||||
name = "CentOS-8-Demo-{}-{}".format(
|
|
||||||
random.sample("abcdefghijklmn", 1)[0], random.sample(range(10), 1)[0]
|
|
||||||
)
|
|
||||||
iso_src = "/home/coolguy/Downloads/CentOS-8.2.2004-x86_64-boot.iso"
|
|
||||||
img_src = "/var/lib/libvirt/images/vol.img"
|
|
||||||
|
|
||||||
vm_info = client.create_vm(name=name, iso_source=iso_src, img_source=img_src)
|
|
||||||
if vm_info["status"]:
|
|
||||||
print("\n\nGuest '{}' has been created and booted".format(name))
|
|
||||||
print("UUID for {}: {}".format(name, vm_info["uuid"]))
|
|
||||||
print("ID for {} : {}".format(name, vm_info["id"]))
|
|
||||||
else:
|
|
||||||
print(vm_info["err"])
|
|
|
@ -1,7 +0,0 @@
|
||||||
[host]
|
|
||||||
# enter comma separated hosts
|
|
||||||
available_hosts = 'qemu:///system', "qemu:///session"
|
|
||||||
|
|
||||||
[server]
|
|
||||||
server_host = 127.0.0.1
|
|
||||||
server_port = 8080
|
|
|
@ -1,9 +0,0 @@
|
||||||
-- This table will store consumer/worker info/configurations
|
|
||||||
|
|
||||||
-- some info about workers
|
|
||||||
CREATE TABLE IF NOT EXISTS worker(
|
|
||||||
worker_id INT UNIQUE NOT NULL,
|
|
||||||
status VARCHAR NOT NULL,
|
|
||||||
--OTHER_DATA
|
|
||||||
|
|
||||||
)
|
|
199
database.py
199
database.py
|
@ -1,199 +0,0 @@
|
||||||
import sqlite3
|
|
||||||
from sqlite3 import Error
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
class Database:
|
|
||||||
def __init__(self):
|
|
||||||
if not os.path.exists("database/"):
|
|
||||||
os.makedirs("database/")
|
|
||||||
|
|
||||||
# .db file
|
|
||||||
self.db = ""
|
|
||||||
|
|
||||||
# Insert into database
|
|
||||||
def insert_into_database(self, tableName, conn, data):
|
|
||||||
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
c = conn.execute("select * from {}".format(tableName))
|
|
||||||
fields = tuple([des[0] for des in c.description][:])
|
|
||||||
|
|
||||||
if "id" in fields:
|
|
||||||
fields = tuple(list(fields)[1:])
|
|
||||||
cur = conn.cursor()
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO {}
|
|
||||||
{} VALUES {}
|
|
||||||
""".format(
|
|
||||||
tableName, fields, data
|
|
||||||
)
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
return True
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Update Database
|
|
||||||
def update_database(self, tableName, conn, fields, field_vals, ref, index):
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
cur = conn.cursor()
|
|
||||||
if not isinstance(fields, tuple) and not isinstance(fields, list):
|
|
||||||
fields = list([fields])
|
|
||||||
field_vals = list([field_vals])
|
|
||||||
|
|
||||||
for field, field_val in zip(fields, field_vals):
|
|
||||||
# print(field,field_val)
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
UPDATE {}
|
|
||||||
SET {}= ? WHERE {}= ?
|
|
||||||
""".format(
|
|
||||||
tableName, field, ref
|
|
||||||
),
|
|
||||||
(field_val, index),
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# print("Error in updating data: {}".format(e))
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Delete from Database
|
|
||||||
def delete_from_database(self, conn, tableName, condition):
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
cur = conn.cursor()
|
|
||||||
# just to track if deletion was successful
|
|
||||||
count = len(
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
SELECT * FROM {} WHERE {}
|
|
||||||
""".format(
|
|
||||||
tableName, condition
|
|
||||||
)
|
|
||||||
).fetchall()
|
|
||||||
)
|
|
||||||
if not count:
|
|
||||||
return False
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
DELETE FROM {} WHERE {}
|
|
||||||
""".format(
|
|
||||||
tableName, condition
|
|
||||||
)
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
return True
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
# print("Error in deleting data: {}".format(e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Search in the database
|
|
||||||
def search_from_database(self, tableName, conn, prop, value, order_by="id"):
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
cur = conn.cursor()
|
|
||||||
# print("cur: {}".format(cur))
|
|
||||||
filtered_list = cur.execute(
|
|
||||||
"""
|
|
||||||
SELECT * FROM {} WHERE {} LIKE ? ORDER BY {};
|
|
||||||
""".format(
|
|
||||||
tableName, prop, order_by
|
|
||||||
),
|
|
||||||
(str(value) + "%",),
|
|
||||||
).fetchall()
|
|
||||||
return filtered_list
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
# print("Error in searching: {}".format(e))
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def search_from_database_many(self, tableName, conn, condition):
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
cur = conn.cursor()
|
|
||||||
filtered_list = cur.execute(
|
|
||||||
"""
|
|
||||||
SELECT * FROM {} WHERE {}
|
|
||||||
""".format(
|
|
||||||
tableName, condition
|
|
||||||
)
|
|
||||||
).fetchall()
|
|
||||||
return filtered_list
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
# print("Error in deleting data: {}".format(e))
|
|
||||||
return None
|
|
||||||
|
|
||||||
# connect database
|
|
||||||
def connect_database(self, db_file):
|
|
||||||
try:
|
|
||||||
conn = sqlite3.connect("database/" + db_file)
|
|
||||||
return conn
|
|
||||||
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
# print("Error in database: {}".format(e))
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
# create table
|
|
||||||
def create_table(self, conn, table):
|
|
||||||
|
|
||||||
if conn is not None:
|
|
||||||
try:
|
|
||||||
cur = conn.cursor()
|
|
||||||
cur.execute(table)
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
def delete_table(self, conn, db_file, table_name):
|
|
||||||
|
|
||||||
if conn is not None:
|
|
||||||
cur = conn.cursor()
|
|
||||||
try:
|
|
||||||
cur.execute("DROP TABLE {}".format(table_name))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def extract_all_data(self, conn, db_file, tableName, order_by="id"):
|
|
||||||
|
|
||||||
if conn is not None:
|
|
||||||
cur = conn.execute(
|
|
||||||
"SELECT * FROM {} ORDER BY {}".format(tableName, order_by)
|
|
||||||
)
|
|
||||||
data = cur.fetchall()
|
|
||||||
conn.close()
|
|
||||||
return data
|
|
||||||
return None
|
|
||||||
|
|
||||||
def delete_all_data(self, conn, db_file, tableName):
|
|
||||||
|
|
||||||
if conn is not None:
|
|
||||||
# conn.commit()
|
|
||||||
cur = conn.cursor()
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
DELETE FROM {};
|
|
||||||
""".format(
|
|
||||||
tableName
|
|
||||||
)
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
pass
|
|
51
pc.py
51
pc.py
|
@ -1,51 +0,0 @@
|
||||||
from threading import Thread, Condition
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
|
|
||||||
queue = []
|
|
||||||
BUFF_SIZE = 10
|
|
||||||
condition = Condition()
|
|
||||||
|
|
||||||
|
|
||||||
class ProducerThread(Thread):
|
|
||||||
def run(self):
|
|
||||||
nums = range(5)
|
|
||||||
global queue
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
condition.acquire()
|
|
||||||
if len(queue) == BUFF_SIZE:
|
|
||||||
# Queue full, producer is waiting
|
|
||||||
condition.wait()
|
|
||||||
# Space in queue, Consumer notified the producer
|
|
||||||
|
|
||||||
num = random.choice(nums)
|
|
||||||
queue.append(num)
|
|
||||||
print("Produced ", num)
|
|
||||||
condition.notify()
|
|
||||||
condition.release()
|
|
||||||
time.sleep(random.random() * 2)
|
|
||||||
|
|
||||||
|
|
||||||
class ConsumerThread(Thread):
|
|
||||||
def run(self):
|
|
||||||
global queue
|
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
condition.acquire()
|
|
||||||
if not queue:
|
|
||||||
# Nothing in queue, consumer is waiting
|
|
||||||
condition.wait()
|
|
||||||
# Producer added something to queue and notified the consumer
|
|
||||||
|
|
||||||
num = queue.pop(0)
|
|
||||||
print("Consumed ", num)
|
|
||||||
condition.notify()
|
|
||||||
condition.release()
|
|
||||||
time.sleep(random.random() * 2)
|
|
||||||
|
|
||||||
|
|
||||||
ProducerThread().start()
|
|
||||||
ConsumerThread().start()
|
|
10
queue.sql
10
queue.sql
|
@ -1,10 +0,0 @@
|
||||||
-- This table will store processes --
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS {}(
|
|
||||||
worker_id INT UNIQUE NOT NULL,
|
|
||||||
start DATETIME NOT NULL, -- timestamps
|
|
||||||
end DATETIME NOT NULL,
|
|
||||||
status VARCHAR NOT NULL,
|
|
||||||
--OTHER_VM_DATA
|
|
||||||
|
|
||||||
)
|
|
5
tags.sql
5
tags.sql
|
@ -1,5 +0,0 @@
|
||||||
CREATE TABLE IF NOT EXISTS tags(
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
tag VARCHAR,
|
|
||||||
|
|
||||||
)
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: #74ebd5;
|
|
||||||
background: -webkit-linear-gradient(to right, #74ebd5, #ACB6E5);
|
|
||||||
background: linear-gradient(to right, #74ebd5, #ACB6E5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableFixHead{
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableFixHead thead th{
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
55
templates/assets/css/bootstrap-tagsinput.css
vendored
55
templates/assets/css/bootstrap-tagsinput.css
vendored
|
@ -1,55 +0,0 @@
|
||||||
.bootstrap-tagsinput {
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
|
||||||
display: inline-block;
|
|
||||||
padding: 4px 6px;
|
|
||||||
color: #555;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-radius: 4px;
|
|
||||||
max-width: 100%;
|
|
||||||
line-height: 22px;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput input {
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
outline: none;
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0 6px;
|
|
||||||
margin: 0;
|
|
||||||
width: auto;
|
|
||||||
max-width: inherit;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput.form-control input::-moz-placeholder {
|
|
||||||
color: #777;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput.form-control input:-ms-input-placeholder {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput input:focus {
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag {
|
|
||||||
margin-right: 2px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"] {
|
|
||||||
margin-left: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"]:after {
|
|
||||||
content: "x";
|
|
||||||
padding: 0px 2px;
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"]:hover {
|
|
||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
|
|
||||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
|
||||||
}
|
|
646
templates/assets/js/bootstrap-tagsinput.js
vendored
646
templates/assets/js/bootstrap-tagsinput.js
vendored
|
@ -1,646 +0,0 @@
|
||||||
(function ($) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var defaultOptions = {
|
|
||||||
tagClass: function(item) {
|
|
||||||
return 'label label-info';
|
|
||||||
},
|
|
||||||
itemValue: function(item) {
|
|
||||||
return item ? item.toString() : item;
|
|
||||||
},
|
|
||||||
itemText: function(item) {
|
|
||||||
return this.itemValue(item);
|
|
||||||
},
|
|
||||||
itemTitle: function(item) {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
freeInput: true,
|
|
||||||
addOnBlur: true,
|
|
||||||
maxTags: undefined,
|
|
||||||
maxChars: undefined,
|
|
||||||
confirmKeys: [13, 44],
|
|
||||||
delimiter: ',',
|
|
||||||
delimiterRegex: null,
|
|
||||||
cancelConfirmKeysOnEmpty: true,
|
|
||||||
onTagExists: function(item, $tag) {
|
|
||||||
$tag.hide().fadeIn();
|
|
||||||
},
|
|
||||||
trimValue: false,
|
|
||||||
allowDuplicates: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor function
|
|
||||||
*/
|
|
||||||
function TagsInput(element, options) {
|
|
||||||
this.itemsArray = [];
|
|
||||||
|
|
||||||
this.$element = $(element);
|
|
||||||
this.$element.hide();
|
|
||||||
|
|
||||||
this.isSelect = (element.tagName === 'SELECT');
|
|
||||||
this.multiple = (this.isSelect && element.hasAttribute('multiple'));
|
|
||||||
this.objectItems = options && options.itemValue;
|
|
||||||
this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
|
|
||||||
this.inputSize = Math.max(1, this.placeholderText.length);
|
|
||||||
|
|
||||||
this.$container = $('<div class="bootstrap-tagsinput"></div>');
|
|
||||||
this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
|
|
||||||
|
|
||||||
this.$element.before(this.$container);
|
|
||||||
|
|
||||||
this.build(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
TagsInput.prototype = {
|
|
||||||
constructor: TagsInput,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the given item as a new tag. Pass true to dontPushVal to prevent
|
|
||||||
* updating the elements val()
|
|
||||||
*/
|
|
||||||
add: function(item, dontPushVal, options) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ignore falsey values, except false
|
|
||||||
if (item !== false && !item)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Trim value
|
|
||||||
if (typeof item === "string" && self.options.trimValue) {
|
|
||||||
item = $.trim(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw an error when trying to add an object while the itemValue option was not set
|
|
||||||
if (typeof item === "object" && !self.objectItems)
|
|
||||||
throw("Can't add objects when itemValue option is not set");
|
|
||||||
|
|
||||||
// Ignore strings only containg whitespace
|
|
||||||
if (item.toString().match(/^\s*$/))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If SELECT but not multiple, remove current tag
|
|
||||||
if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
|
|
||||||
self.remove(self.itemsArray[0]);
|
|
||||||
|
|
||||||
if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
|
|
||||||
var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter;
|
|
||||||
var items = item.split(delimiter);
|
|
||||||
if (items.length > 1) {
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
this.add(items[i], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dontPushVal)
|
|
||||||
self.pushVal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemValue = self.options.itemValue(item),
|
|
||||||
itemText = self.options.itemText(item),
|
|
||||||
tagClass = self.options.tagClass(item),
|
|
||||||
itemTitle = self.options.itemTitle(item);
|
|
||||||
|
|
||||||
// Ignore items allready added
|
|
||||||
var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
|
|
||||||
if (existing && !self.options.allowDuplicates) {
|
|
||||||
// Invoke onTagExists
|
|
||||||
if (self.options.onTagExists) {
|
|
||||||
var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
|
|
||||||
self.options.onTagExists(item, $existingTag);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if length greater than limit
|
|
||||||
if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// raise beforeItemAdd arg
|
|
||||||
var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
|
|
||||||
self.$element.trigger(beforeItemAddEvent);
|
|
||||||
if (beforeItemAddEvent.cancel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// register item in internal array and map
|
|
||||||
self.itemsArray.push(item);
|
|
||||||
|
|
||||||
// add a tag element
|
|
||||||
|
|
||||||
var $tag = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
|
|
||||||
$tag.data('item', item);
|
|
||||||
self.findInputWrapper().before($tag);
|
|
||||||
$tag.after(' ');
|
|
||||||
|
|
||||||
// add <option /> if item represents a value not present in one of the <select />'s options
|
|
||||||
if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
|
|
||||||
var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
|
|
||||||
$option.data('item', item);
|
|
||||||
$option.attr('value', itemValue);
|
|
||||||
self.$element.append($option);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dontPushVal)
|
|
||||||
self.pushVal();
|
|
||||||
|
|
||||||
// Add class when reached maxTags
|
|
||||||
if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
|
|
||||||
self.$container.addClass('bootstrap-tagsinput-max');
|
|
||||||
|
|
||||||
self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the given item. Pass true to dontPushVal to prevent updating the
|
|
||||||
* elements val()
|
|
||||||
*/
|
|
||||||
remove: function(item, dontPushVal, options) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (self.objectItems) {
|
|
||||||
if (typeof item === "object")
|
|
||||||
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
|
|
||||||
else
|
|
||||||
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
|
|
||||||
|
|
||||||
item = item[item.length-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
|
|
||||||
self.$element.trigger(beforeItemRemoveEvent);
|
|
||||||
if (beforeItemRemoveEvent.cancel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
$('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
|
|
||||||
$('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
|
|
||||||
if($.inArray(item, self.itemsArray) !== -1)
|
|
||||||
self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dontPushVal)
|
|
||||||
self.pushVal();
|
|
||||||
|
|
||||||
// Remove class when reached maxTags
|
|
||||||
if (self.options.maxTags > self.itemsArray.length)
|
|
||||||
self.$container.removeClass('bootstrap-tagsinput-max');
|
|
||||||
|
|
||||||
self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all items
|
|
||||||
*/
|
|
||||||
removeAll: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
$('.tag', self.$container).remove();
|
|
||||||
$('option', self.$element).remove();
|
|
||||||
|
|
||||||
while(self.itemsArray.length > 0)
|
|
||||||
self.itemsArray.pop();
|
|
||||||
|
|
||||||
self.pushVal();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes the tags so they match the text/value of their corresponding
|
|
||||||
* item.
|
|
||||||
*/
|
|
||||||
refresh: function() {
|
|
||||||
var self = this;
|
|
||||||
$('.tag', self.$container).each(function() {
|
|
||||||
var $tag = $(this),
|
|
||||||
item = $tag.data('item'),
|
|
||||||
itemValue = self.options.itemValue(item),
|
|
||||||
itemText = self.options.itemText(item),
|
|
||||||
tagClass = self.options.tagClass(item);
|
|
||||||
|
|
||||||
// Update tag's class and inner text
|
|
||||||
$tag.attr('class', null);
|
|
||||||
$tag.addClass('tag ' + htmlEncode(tagClass));
|
|
||||||
$tag.contents().filter(function() {
|
|
||||||
return this.nodeType == 3;
|
|
||||||
})[0].nodeValue = htmlEncode(itemText);
|
|
||||||
|
|
||||||
if (self.isSelect) {
|
|
||||||
var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
|
|
||||||
option.attr('value', itemValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the items added as tags
|
|
||||||
*/
|
|
||||||
items: function() {
|
|
||||||
return this.itemsArray;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assembly value by retrieving the value of each item, and set it on the
|
|
||||||
* element.
|
|
||||||
*/
|
|
||||||
pushVal: function() {
|
|
||||||
var self = this,
|
|
||||||
val = $.map(self.items(), function(item) {
|
|
||||||
return self.options.itemValue(item).toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.$element.val(val, true).trigger('change');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the tags input behaviour on the element
|
|
||||||
*/
|
|
||||||
build: function(options) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.options = $.extend({}, defaultOptions, options);
|
|
||||||
// When itemValue is set, freeInput should always be false
|
|
||||||
if (self.objectItems)
|
|
||||||
self.options.freeInput = false;
|
|
||||||
|
|
||||||
makeOptionItemFunction(self.options, 'itemValue');
|
|
||||||
makeOptionItemFunction(self.options, 'itemText');
|
|
||||||
makeOptionFunction(self.options, 'tagClass');
|
|
||||||
|
|
||||||
// Typeahead Bootstrap version 2.3.2
|
|
||||||
if (self.options.typeahead) {
|
|
||||||
var typeahead = self.options.typeahead || {};
|
|
||||||
|
|
||||||
makeOptionFunction(typeahead, 'source');
|
|
||||||
|
|
||||||
self.$input.typeahead($.extend({}, typeahead, {
|
|
||||||
source: function (query, process) {
|
|
||||||
function processItems(items) {
|
|
||||||
var texts = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
var text = self.options.itemText(items[i]);
|
|
||||||
map[text] = items[i];
|
|
||||||
texts.push(text);
|
|
||||||
}
|
|
||||||
process(texts);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.map = {};
|
|
||||||
var map = this.map,
|
|
||||||
data = typeahead.source(query);
|
|
||||||
|
|
||||||
if ($.isFunction(data.success)) {
|
|
||||||
// support for Angular callbacks
|
|
||||||
data.success(processItems);
|
|
||||||
} else if ($.isFunction(data.then)) {
|
|
||||||
// support for Angular promises
|
|
||||||
data.then(processItems);
|
|
||||||
} else {
|
|
||||||
// support for functions and jquery promises
|
|
||||||
$.when(data)
|
|
||||||
.then(processItems);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updater: function (text) {
|
|
||||||
self.add(this.map[text]);
|
|
||||||
return this.map[text];
|
|
||||||
},
|
|
||||||
matcher: function (text) {
|
|
||||||
return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
|
|
||||||
},
|
|
||||||
sorter: function (texts) {
|
|
||||||
return texts.sort();
|
|
||||||
},
|
|
||||||
highlighter: function (text) {
|
|
||||||
var regex = new RegExp( '(' + this.query + ')', 'gi' );
|
|
||||||
return text.replace( regex, "<strong>$1</strong>" );
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeahead.js
|
|
||||||
if (self.options.typeaheadjs) {
|
|
||||||
var typeaheadConfig = null;
|
|
||||||
var typeaheadDatasets = {};
|
|
||||||
|
|
||||||
// Determine if main configurations were passed or simply a dataset
|
|
||||||
var typeaheadjs = self.options.typeaheadjs;
|
|
||||||
if ($.isArray(typeaheadjs)) {
|
|
||||||
typeaheadConfig = typeaheadjs[0];
|
|
||||||
typeaheadDatasets = typeaheadjs[1];
|
|
||||||
} else {
|
|
||||||
typeaheadDatasets = typeaheadjs;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
|
|
||||||
if (typeaheadDatasets.valueKey)
|
|
||||||
self.add(datum[typeaheadDatasets.valueKey]);
|
|
||||||
else
|
|
||||||
self.add(datum);
|
|
||||||
self.$input.typeahead('val', '');
|
|
||||||
}, self));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$container.on('click', $.proxy(function(event) {
|
|
||||||
if (! self.$element.attr('disabled')) {
|
|
||||||
self.$input.removeAttr('disabled');
|
|
||||||
}
|
|
||||||
self.$input.focus();
|
|
||||||
}, self));
|
|
||||||
|
|
||||||
if (self.options.addOnBlur && self.options.freeInput) {
|
|
||||||
self.$input.on('focusout', $.proxy(function(event) {
|
|
||||||
// HACK: only process on focusout when no typeahead opened, to
|
|
||||||
// avoid adding the typeahead text as tag
|
|
||||||
if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
|
|
||||||
self.add(self.$input.val());
|
|
||||||
self.$input.val('');
|
|
||||||
}
|
|
||||||
}, self));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
self.$container.on('keydown', 'input', $.proxy(function(event) {
|
|
||||||
var $input = $(event.target),
|
|
||||||
$inputWrapper = self.findInputWrapper();
|
|
||||||
|
|
||||||
if (self.$element.attr('disabled')) {
|
|
||||||
self.$input.attr('disabled', 'disabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.which) {
|
|
||||||
// BACKSPACE
|
|
||||||
case 8:
|
|
||||||
if (doGetCaretPosition($input[0]) === 0) {
|
|
||||||
var prev = $inputWrapper.prev();
|
|
||||||
if (prev.length) {
|
|
||||||
self.remove(prev.data('item'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// DELETE
|
|
||||||
case 46:
|
|
||||||
if (doGetCaretPosition($input[0]) === 0) {
|
|
||||||
var next = $inputWrapper.next();
|
|
||||||
if (next.length) {
|
|
||||||
self.remove(next.data('item'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LEFT ARROW
|
|
||||||
case 37:
|
|
||||||
// Try to move the input before the previous tag
|
|
||||||
var $prevTag = $inputWrapper.prev();
|
|
||||||
if ($input.val().length === 0 && $prevTag[0]) {
|
|
||||||
$prevTag.before($inputWrapper);
|
|
||||||
$input.focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// RIGHT ARROW
|
|
||||||
case 39:
|
|
||||||
// Try to move the input after the next tag
|
|
||||||
var $nextTag = $inputWrapper.next();
|
|
||||||
if ($input.val().length === 0 && $nextTag[0]) {
|
|
||||||
$nextTag.after($inputWrapper);
|
|
||||||
$input.focus();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset internal input's size
|
|
||||||
var textLength = $input.val().length,
|
|
||||||
wordSpace = Math.ceil(textLength / 5),
|
|
||||||
size = textLength + wordSpace + 1;
|
|
||||||
$input.attr('size', Math.max(this.inputSize, $input.val().length));
|
|
||||||
}, self));
|
|
||||||
|
|
||||||
self.$container.on('keypress', 'input', $.proxy(function(event) {
|
|
||||||
var $input = $(event.target);
|
|
||||||
|
|
||||||
if (self.$element.attr('disabled')) {
|
|
||||||
self.$input.attr('disabled', 'disabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = $input.val(),
|
|
||||||
maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
|
|
||||||
if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
|
|
||||||
// Only attempt to add a tag if there is data in the field
|
|
||||||
if (text.length !== 0) {
|
|
||||||
self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
|
|
||||||
$input.val('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the field is empty, let the event triggered fire as usual
|
|
||||||
if (self.options.cancelConfirmKeysOnEmpty === false) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset internal input's size
|
|
||||||
var textLength = $input.val().length,
|
|
||||||
wordSpace = Math.ceil(textLength / 5),
|
|
||||||
size = textLength + wordSpace + 1;
|
|
||||||
$input.attr('size', Math.max(this.inputSize, $input.val().length));
|
|
||||||
}, self));
|
|
||||||
|
|
||||||
// Remove icon clicked
|
|
||||||
self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
|
|
||||||
if (self.$element.attr('disabled')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.remove($(event.target).closest('.tag').data('item'));
|
|
||||||
}, self));
|
|
||||||
|
|
||||||
// Only add existing value as tags when using strings as tags
|
|
||||||
if (self.options.itemValue === defaultOptions.itemValue) {
|
|
||||||
if (self.$element[0].tagName === 'INPUT') {
|
|
||||||
self.add(self.$element.val());
|
|
||||||
} else {
|
|
||||||
$('option', self.$element).each(function() {
|
|
||||||
self.add($(this).attr('value'), true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all tagsinput behaviour and unregsiter all event handlers
|
|
||||||
*/
|
|
||||||
destroy: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Unbind events
|
|
||||||
self.$container.off('keypress', 'input');
|
|
||||||
self.$container.off('click', '[role=remove]');
|
|
||||||
|
|
||||||
self.$container.remove();
|
|
||||||
self.$element.removeData('tagsinput');
|
|
||||||
self.$element.show();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets focus on the tagsinput
|
|
||||||
*/
|
|
||||||
focus: function() {
|
|
||||||
this.$input.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the internal input element
|
|
||||||
*/
|
|
||||||
input: function() {
|
|
||||||
return this.$input;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the element which is wrapped around the internal input. This
|
|
||||||
* is normally the $container, but typeahead.js moves the $input element.
|
|
||||||
*/
|
|
||||||
findInputWrapper: function() {
|
|
||||||
var elt = this.$input[0],
|
|
||||||
container = this.$container[0];
|
|
||||||
while(elt && elt.parentNode !== container)
|
|
||||||
elt = elt.parentNode;
|
|
||||||
|
|
||||||
return $(elt);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register JQuery plugin
|
|
||||||
*/
|
|
||||||
$.fn.tagsinput = function(arg1, arg2, arg3) {
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
this.each(function() {
|
|
||||||
var tagsinput = $(this).data('tagsinput');
|
|
||||||
// Initialize a new tags input
|
|
||||||
if (!tagsinput) {
|
|
||||||
tagsinput = new TagsInput(this, arg1);
|
|
||||||
$(this).data('tagsinput', tagsinput);
|
|
||||||
results.push(tagsinput);
|
|
||||||
|
|
||||||
if (this.tagName === 'SELECT') {
|
|
||||||
$('option', $(this)).attr('selected', 'selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init tags from $(this).val()
|
|
||||||
$(this).val($(this).val());
|
|
||||||
} else if (!arg1 && !arg2) {
|
|
||||||
// tagsinput already exists
|
|
||||||
// no function, trying to init
|
|
||||||
results.push(tagsinput);
|
|
||||||
} else if(tagsinput[arg1] !== undefined) {
|
|
||||||
// Invoke function on existing tags input
|
|
||||||
if(tagsinput[arg1].length === 3 && arg3 !== undefined){
|
|
||||||
var retVal = tagsinput[arg1](arg2, null, arg3);
|
|
||||||
}else{
|
|
||||||
var retVal = tagsinput[arg1](arg2);
|
|
||||||
}
|
|
||||||
if (retVal !== undefined)
|
|
||||||
results.push(retVal);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if ( typeof arg1 == 'string') {
|
|
||||||
// Return the results from the invoked function calls
|
|
||||||
return results.length > 1 ? results : results[0];
|
|
||||||
} else {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.tagsinput.Constructor = TagsInput;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Most options support both a string or number as well as a function as
|
|
||||||
* option value. This function makes sure that the option with the given
|
|
||||||
* key in the given options is wrapped in a function
|
|
||||||
*/
|
|
||||||
function makeOptionItemFunction(options, key) {
|
|
||||||
if (typeof options[key] !== 'function') {
|
|
||||||
var propertyName = options[key];
|
|
||||||
options[key] = function(item) { return item[propertyName]; };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function makeOptionFunction(options, key) {
|
|
||||||
if (typeof options[key] !== 'function') {
|
|
||||||
var value = options[key];
|
|
||||||
options[key] = function() { return value; };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* HtmlEncodes the given value
|
|
||||||
*/
|
|
||||||
var htmlEncodeContainer = $('<div />');
|
|
||||||
function htmlEncode(value) {
|
|
||||||
if (value) {
|
|
||||||
return htmlEncodeContainer.text(value).html();
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the position of the caret in the given input field
|
|
||||||
* http://flightschool.acylt.com/devnotes/caret-position-woes/
|
|
||||||
*/
|
|
||||||
function doGetCaretPosition(oField) {
|
|
||||||
var iCaretPos = 0;
|
|
||||||
if (document.selection) {
|
|
||||||
oField.focus ();
|
|
||||||
var oSel = document.selection.createRange();
|
|
||||||
oSel.moveStart ('character', -oField.value.length);
|
|
||||||
iCaretPos = oSel.text.length;
|
|
||||||
} else if (oField.selectionStart || oField.selectionStart == '0') {
|
|
||||||
iCaretPos = oField.selectionStart;
|
|
||||||
}
|
|
||||||
return (iCaretPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns boolean indicates whether user has pressed an expected key combination.
|
|
||||||
* @param object keyPressEvent: JavaScript event object, refer
|
|
||||||
* http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
|
|
||||||
* @param object lookupList: expected key combinations, as in:
|
|
||||||
* [13, {which: 188, shiftKey: true}]
|
|
||||||
*/
|
|
||||||
function keyCombinationInList(keyPressEvent, lookupList) {
|
|
||||||
var found = false;
|
|
||||||
$.each(lookupList, function (index, keyCombination) {
|
|
||||||
if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
|
|
||||||
found = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyPressEvent.which === keyCombination.which) {
|
|
||||||
var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
|
|
||||||
shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
|
|
||||||
ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
|
|
||||||
if (alt && shift && ctrl) {
|
|
||||||
found = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize tagsinput behaviour on inputs and selects which have
|
|
||||||
* data-role=tagsinput
|
|
||||||
*/
|
|
||||||
$(function() {
|
|
||||||
$("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
|
|
||||||
});
|
|
||||||
})(window.jQuery);
|
|
2
templates/assets/js/jquery.min.js
vendored
2
templates/assets/js/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,37 +0,0 @@
|
||||||
<domain type="{{ args.type }}">
|
|
||||||
<name>{{ args.name }}</name>
|
|
||||||
<uuid>{{ args.uuid }}</uuid>
|
|
||||||
<memory unit="{{ args.memory_unit }}">{{ args.memory }}</memory>
|
|
||||||
<currentMemory unit="{{ args.memory_unit }}">{{ args.current_memory }}</currentMemory>
|
|
||||||
<vcpu placement="static">{{ args.vcpu }}</vcpu>
|
|
||||||
<os>
|
|
||||||
<type arch='{{ args.arch }}' machine='{{ args.machine }}'>hvm</type>
|
|
||||||
<!--boot dev='hd'/-->
|
|
||||||
<boot dev='cdrom'/>
|
|
||||||
</os>
|
|
||||||
<clock offset='{{ args.offset }}'/>
|
|
||||||
<on_poweroff>destroy</on_poweroff>
|
|
||||||
<on_reboot>restart</on_reboot>
|
|
||||||
<on_crash>destroy</on_crash>
|
|
||||||
<devices>
|
|
||||||
<emulator>{{ args.emulator }}</emulator>
|
|
||||||
<disk type="file" device="disk">
|
|
||||||
<driver name="qemu" type="raw"/>
|
|
||||||
<source file="{{ args.img_source }}"/>
|
|
||||||
<target dev="vda" bus="virtio"/>
|
|
||||||
</disk>
|
|
||||||
<disk type='file' device='cdrom'>
|
|
||||||
<source file='{{ args.iso_source }}'/>
|
|
||||||
<target dev='sda' bus='sata'/>
|
|
||||||
</disk>
|
|
||||||
<interface type="network">
|
|
||||||
<mac address="52:54:00:b6:8e:76"/>
|
|
||||||
<source network="default"/>
|
|
||||||
<model type="virtio"/>
|
|
||||||
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
|
|
||||||
</interface>
|
|
||||||
<!--graphics will be removed after testing as we probably won't need this-->
|
|
||||||
<graphics type='vnc' port='-1' listen='127.0.0.1'/>
|
|
||||||
</devices>
|
|
||||||
|
|
||||||
</domain>
|
|
|
@ -1,81 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
|
||||||
<title>Guests Info</title>
|
|
||||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="assets/css/Bootstrap-4---Table-Fixed-Header.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container py-5">
|
|
||||||
<div class="col ">
|
|
||||||
<h3 class="text-center">Guest Detail</h4>
|
|
||||||
<hr>
|
|
||||||
<div class="tableFixHead">
|
|
||||||
<table class="table">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th scope="col">S.No.</th>
|
|
||||||
<th scope="col">Guest Name</th>
|
|
||||||
<th scope="col">Host Name</th>
|
|
||||||
<th scope="col">ID</th>
|
|
||||||
<th scope="col">Allocated<br>Memory(MiB)</th>
|
|
||||||
<th scope="col">Allocated<br>CPUs</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">UUID</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body"></tbody>
|
|
||||||
|
|
||||||
{% if guest_data %}
|
|
||||||
{% for data in guest_data %}
|
|
||||||
<tr id="${{ data.guestname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.guestname }}</td>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.id_ }}</td>
|
|
||||||
<td>{{ data.mem }}</td>
|
|
||||||
<td>{{ data.cpu }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Active" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uuid }}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${{ data.guestname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.guestname }}</td>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.id_ }}</td>
|
|
||||||
<td>{{ data.mem }}</td>
|
|
||||||
<td>{{ data.cpu }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Active" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uuid }}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${{ data.guestname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.guestname }}</td>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.id_ }}</td>
|
|
||||||
<td>{{ data.mem }}</td>
|
|
||||||
<td>{{ data.cpu }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Active" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uuid }}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
|
||||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,182 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
|
||||||
<title>Hosts Info</title>
|
|
||||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}assets/css/Bootstrap-4---Table-Fixed-Header.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container py-5">
|
|
||||||
<div class="col ">
|
|
||||||
<h3 class="text-center">Host Detail</h4>
|
|
||||||
<hr>
|
|
||||||
<div class="tableFixHead">
|
|
||||||
<table class="table">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th scope="col">S.No.</th>
|
|
||||||
<th scope="col">Host Name</th>
|
|
||||||
<th scope="col">CPUs<br>(Total / Free)</th>
|
|
||||||
<th scope="col">Memory(MiB)<br>(Total / Free)</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">URI</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body"></tbody>
|
|
||||||
|
|
||||||
<!--tr id="${data.hostname}">
|
|
||||||
<th scope="row">1</th>
|
|
||||||
<td>CentOS-8-Demo-a-1</td>
|
|
||||||
<td>4 / 3</td>
|
|
||||||
<td>3800 / 2800</td>
|
|
||||||
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
|
||||||
<td>qemu:///system</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">2</th>
|
|
||||||
<td>CentOS-8-Demo-b-2</td>
|
|
||||||
<td>4 / 2</td>
|
|
||||||
<td>3800 / 3800</td>
|
|
||||||
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
|
||||||
<td>qemu+ssh:///system/something/user</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">3</th>
|
|
||||||
<td>CentOS-8-Demo-g-0</td>
|
|
||||||
<td>4 / 2</td>
|
|
||||||
<td>3800 / 2726</td>
|
|
||||||
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
|
||||||
<td>qemu:///system</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">4</th>
|
|
||||||
<td>Win2k19-Demo-gz-30</td>
|
|
||||||
<td>4 / 2</td>
|
|
||||||
<td>3800 / 2726</td>
|
|
||||||
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
|
||||||
<td>qemu+ssh://pool@check2.homedevops/system</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">5</th>
|
|
||||||
<td>Linux-20-04-rc-302321</td>
|
|
||||||
<td>8 / 5</td>
|
|
||||||
<td>8800 / 6726</td>
|
|
||||||
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
|
||||||
<td>qemu:///system</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">6</th>
|
|
||||||
<td>Win2k19-Demo-gz-30</td>
|
|
||||||
<td>4 / 2</td>
|
|
||||||
<td>3800 / 2726</td>
|
|
||||||
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
|
||||||
<td>qemu+ssh://pool@reserve.homedevops/system</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">7</th>
|
|
||||||
<td>Win2k19-Demo-gz-30</td>
|
|
||||||
<td>4 / 2</td>
|
|
||||||
<td>3800 / 2726</td>
|
|
||||||
<td id="host-stat" class="text-success"><b>Alive</b></td>
|
|
||||||
<td>qemu:///system</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr id="${data.hostname}">
|
|
||||||
<th scope="row">8</th>
|
|
||||||
<td>Win2k19-Demo-gz-30</td>
|
|
||||||
<td>4 / 2</td>
|
|
||||||
<td>3800 / 2726</td>
|
|
||||||
<td id="host-stat" class="text-secondary"><b>Dead</b></td>
|
|
||||||
<td>qemu+ssh://pool@check3.homedevops/system</td>
|
|
||||||
</tr-->
|
|
||||||
|
|
||||||
{% if host_data %}
|
|
||||||
{% for data in host_data %}
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr id="${{ data.hostname }}">
|
|
||||||
<th scope="row">{{ data.sn }}</th>
|
|
||||||
<td>{{ data.hostname }}</td>
|
|
||||||
<td>{{ data.cpu.total }} / {{ data.cpu.free }}</td>
|
|
||||||
<td>{{ data.mem.total }} / {{ data.mem.free }}</td>
|
|
||||||
<td id="host-stat" class="{{ "text-success" if data.status=="Alive" else "text-secondary" }}"><b>{{ data.status }}</b></td>
|
|
||||||
<td>{{ data.uri }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="assets/js/jquery.min.js"></script>
|
|
||||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,830 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Manage Hosts</title>
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto|Varela+Round">
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}assets/css/bootstrap-tagsinput.css">
|
|
||||||
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
|
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
|
||||||
<script src="{{ STATIC_PREFIX }}assets/js/bootstrap-tagsinput.js"></script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
color: #566787;
|
|
||||||
background: #74ebd5;
|
|
||||||
background: -webkit-linear-gradient(to right, #74ebd5, #ACB6E5);
|
|
||||||
background: linear-gradient(to right, #74ebd5, #ACB6E5);
|
|
||||||
font-family: 'Varela Round', sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-responsive {
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
.table-wrapper {
|
|
||||||
min-width: 1000px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px 25px;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-shadow: 0 1px 1px rgba(0,0,0,.05);
|
|
||||||
}
|
|
||||||
.table-title {
|
|
||||||
padding-bottom: 15px;
|
|
||||||
background: #435d7d;
|
|
||||||
color: #fff;
|
|
||||||
padding: 16px 30px;
|
|
||||||
margin: -20px -25px 10px;
|
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
}
|
|
||||||
.table-title h2 {
|
|
||||||
margin: 5px 0 0;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.table-title .btn-group {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.table-title .btn {
|
|
||||||
color: #fff;
|
|
||||||
float: right;
|
|
||||||
font-size: 13px;
|
|
||||||
border: none;
|
|
||||||
min-width: 50px;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: none;
|
|
||||||
outline: none !important;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.table-title .btn i {
|
|
||||||
float: left;
|
|
||||||
font-size: 21px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
.table-title .btn span {
|
|
||||||
float: left;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
table.table tr th, table.table tr td {
|
|
||||||
border-color: #e9e9e9;
|
|
||||||
padding: 12px 15px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
table.table tr th:first-child {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
table.table tr th:last-child {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
table.table-striped tbody tr:nth-of-type(odd) {
|
|
||||||
background-color: #fcfcfc;
|
|
||||||
}
|
|
||||||
table.table-striped.table-hover tbody tr:hover {
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
table.table th i {
|
|
||||||
font-size: 13px;
|
|
||||||
margin: 0 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
table.table td:last-child i {
|
|
||||||
opacity: 0.9;
|
|
||||||
font-size: 22px;
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
table.table td a {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #566787;
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
table.table td a:hover {
|
|
||||||
color: #2196F3;
|
|
||||||
}
|
|
||||||
table.table td a.edit {
|
|
||||||
color: #FFC107;
|
|
||||||
}
|
|
||||||
table.table td a.delete {
|
|
||||||
color: #F44336;
|
|
||||||
}
|
|
||||||
table.table td i {
|
|
||||||
font-size: 19px;
|
|
||||||
}
|
|
||||||
table.table .avatar {
|
|
||||||
border-radius: 50%;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.pagination {
|
|
||||||
float: right;
|
|
||||||
margin: 0 0 5px;
|
|
||||||
}
|
|
||||||
.pagination li a {
|
|
||||||
border: none;
|
|
||||||
font-size: 13px;
|
|
||||||
min-width: 30px;
|
|
||||||
min-height: 30px;
|
|
||||||
color: #999;
|
|
||||||
margin: 0 2px;
|
|
||||||
line-height: 30px;
|
|
||||||
border-radius: 2px !important;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0 6px;
|
|
||||||
}
|
|
||||||
.pagination li a:hover {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.pagination li.active a, .pagination li.active a.page-link {
|
|
||||||
background: #03A9F4;
|
|
||||||
}
|
|
||||||
.pagination li.active a:hover {
|
|
||||||
background: #0397d6;
|
|
||||||
}
|
|
||||||
.pagination li.disabled i {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
.pagination li i {
|
|
||||||
font-size: 16px;
|
|
||||||
padding-top: 6px
|
|
||||||
}
|
|
||||||
.hint-text {
|
|
||||||
float: left;
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
/* Custom checkbox */
|
|
||||||
.custom-checkbox {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.custom-checkbox input[type="checkbox"] {
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
margin: 5px 0 0 3px;
|
|
||||||
z-index: 9;
|
|
||||||
}
|
|
||||||
.custom-checkbox label:before{
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
.custom-checkbox label:before {
|
|
||||||
content: '';
|
|
||||||
margin-right: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: text-top;
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #bbb;
|
|
||||||
border-radius: 2px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.custom-checkbox input[type="checkbox"]:checked + label:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 6px;
|
|
||||||
top: 3px;
|
|
||||||
width: 6px;
|
|
||||||
height: 11px;
|
|
||||||
border: solid #000;
|
|
||||||
border-width: 0 3px 3px 0;
|
|
||||||
transform: inherit;
|
|
||||||
z-index: 3;
|
|
||||||
transform: rotateZ(45deg);
|
|
||||||
}
|
|
||||||
.custom-checkbox input[type="checkbox"]:checked + label:before {
|
|
||||||
border-color: #03A9F4;
|
|
||||||
background: #03A9F4;
|
|
||||||
}
|
|
||||||
.custom-checkbox input[type="checkbox"]:checked + label:after {
|
|
||||||
border-color: #fff;
|
|
||||||
}
|
|
||||||
.custom-checkbox input[type="checkbox"]:disabled + label:before {
|
|
||||||
color: #b8b8b8;
|
|
||||||
cursor: auto;
|
|
||||||
box-shadow: none;
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
/* Modal styles */
|
|
||||||
.modal .modal-dialog {
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
.modal .modal-header, .modal .modal-body, .modal .modal-footer {
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
.modal .modal-content {
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
.modal .modal-footer {
|
|
||||||
background: #ecf0f1;
|
|
||||||
border-radius: 0 0 3px 3px;
|
|
||||||
}
|
|
||||||
.modal .modal-title {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.modal .form-control {
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: none;
|
|
||||||
border-color: #dddddd;
|
|
||||||
}
|
|
||||||
.modal textarea.form-control {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
.modal .btn {
|
|
||||||
border-radius: 2px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
.modal form label {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
#overlay{
|
|
||||||
position: absolute;
|
|
||||||
top:0;
|
|
||||||
left:0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: black;
|
|
||||||
opacity: .5;
|
|
||||||
display: none;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
|
|
||||||
border: 10px solid #f3f3f3;
|
|
||||||
border-radius: 50%;
|
|
||||||
border-top: 10px solid #4e729e;
|
|
||||||
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
-webkit-animation: spin .4s linear infinite; /* Safari */
|
|
||||||
animation: spin .4s linear infinite;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Safari */
|
|
||||||
@-webkit-keyframes spin {
|
|
||||||
0% { -webkit-transform: rotate(0deg); }
|
|
||||||
100% { -webkit-transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
//import Tagify from '@yaireo/tagify';
|
|
||||||
$(document).ready(function(){
|
|
||||||
|
|
||||||
//window.onload= function(){
|
|
||||||
// Activate tooltip
|
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
|
||||||
|
|
||||||
//$('input[name=tags]').data('tagify').addTags('aaa, bbb, ccc');
|
|
||||||
//new Tagify(input)
|
|
||||||
|
|
||||||
// Select/Deselect checkboxes
|
|
||||||
var checkbox = $('table tbody input[type="checkbox"]');
|
|
||||||
$("#selectAll").click(function(){
|
|
||||||
if(this.checked){
|
|
||||||
checkbox.each(function(){
|
|
||||||
this.checked = true;
|
|
||||||
});
|
|
||||||
} else{
|
|
||||||
checkbox.each(function(){
|
|
||||||
this.checked = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
checkbox.click(function(){
|
|
||||||
if(!this.checked){
|
|
||||||
$("#selectAll").prop("checked", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var actions = '<td>\
|
|
||||||
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>\
|
|
||||||
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>\
|
|
||||||
</td>'
|
|
||||||
var row = '<tr>';
|
|
||||||
|
|
||||||
$("#add-hostname").change((obj)=>{
|
|
||||||
console.log(obj.target.value)
|
|
||||||
|
|
||||||
$.post({
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify({"hostname": obj.target.value}),
|
|
||||||
url: "/query_host",
|
|
||||||
beforeSend: ()=>{$("#overlay").css("display", "flex");},
|
|
||||||
complete: ()=>{$("#overlay").css("display", "none");},
|
|
||||||
error:function () {
|
|
||||||
alert('An error occured while querying host. Please try again later.');
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
|
|
||||||
if(response.done){
|
|
||||||
$("table").append(row)
|
|
||||||
|
|
||||||
input= $("#add-modal").find("input");
|
|
||||||
input.each(function(index){
|
|
||||||
var elem = this
|
|
||||||
if(elem.type=="checkbox"){
|
|
||||||
if(response.gpu){
|
|
||||||
$(elem)[0].checked = true
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$(elem)[0].checked= false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
|
|
||||||
elem.value= response.data[index]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#add")[0].disabled = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
alert(`Error: ${response.msg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$("input:checkbox").on("click", false);
|
|
||||||
|
|
||||||
$("#add-form").submit((e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
$("#add")[0].disabled = true;
|
|
||||||
|
|
||||||
if($("#add-form")[0].checkValidity()){
|
|
||||||
|
|
||||||
var requestData={
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
|
|
||||||
par = $("#add").parent().parent();
|
|
||||||
bdy= par.find(".modal-body");
|
|
||||||
input= bdy.find("input");
|
|
||||||
input.each(function(){
|
|
||||||
var elem = this
|
|
||||||
if(elem.type=="checkbox"){
|
|
||||||
if(elem.checked){
|
|
||||||
requestData.gpu= "Yes"
|
|
||||||
row += '<td class="text-success">Yes</td>'
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
requestData.gpu= "No"
|
|
||||||
row += '<td class="text-secondary">No</td>'
|
|
||||||
}
|
|
||||||
elem.checked= false;
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(elem.placeholder=="Tags"){
|
|
||||||
requestData.data.push(elem.value)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
requestData.data.push(elem.value)
|
|
||||||
row += `<td>${elem.value}</td>`
|
|
||||||
elem.value=""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
requestData.power= $(".sel-add").find(":selected").text()
|
|
||||||
row += `<td>${$(".sel-add").find(":selected").text()}</td>`
|
|
||||||
row += actions + '</tr>';
|
|
||||||
|
|
||||||
//make the AJAX call over POST
|
|
||||||
|
|
||||||
$.post({
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(requestData),
|
|
||||||
url: "/add_host",
|
|
||||||
error:function () {
|
|
||||||
alert('An error occured while adding host. Please try again later.');
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
|
|
||||||
if(response.done){
|
|
||||||
$("table").append(row)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
alert(`Error: ${response.msg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
row= '<tr>'
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$('.sel-add option:contains("Always On")').prop('selected', true)
|
|
||||||
|
|
||||||
$("#addHostModal").modal('toggle');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var curr_idx= -1
|
|
||||||
$("table").on('click', 'a.edit', function(){
|
|
||||||
//console.log(idx);
|
|
||||||
td= $(this).parents("tr").find("td:not(:last-child)")
|
|
||||||
curr_idx= $(this).parents("tr").index()
|
|
||||||
data= []
|
|
||||||
td.each(function(){
|
|
||||||
data.push($(this).text())
|
|
||||||
});
|
|
||||||
|
|
||||||
bdy= $("#edit-form").find(".modal-body")
|
|
||||||
input= bdy.find("input");
|
|
||||||
input.each(function(index){
|
|
||||||
var elem = this
|
|
||||||
if(elem.type=="checkbox"){
|
|
||||||
if(data[5]=="Yes"){
|
|
||||||
$(elem).prop('checked', true)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$(elem).prop('checked', false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(elem.placeholder=="Tags"){
|
|
||||||
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
elem.value= data[index]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$(`.sel-edit option:contains(${data[6]})`).prop('selected', true)
|
|
||||||
$("#editHostModal").modal('toggle');
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#edit-form").submit((e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if($("#edit-form")[0].checkValidity()){
|
|
||||||
var requestData={
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
|
|
||||||
par = $("#save").parent().parent();
|
|
||||||
bdy= par.find(".modal-body");
|
|
||||||
input= bdy.find("input");
|
|
||||||
input.each(function(){
|
|
||||||
var elem = this //.children[1];
|
|
||||||
if(elem.type=="checkbox"){
|
|
||||||
if(elem.checked){
|
|
||||||
requestData.gpu= "Yes"
|
|
||||||
row += '<td class="text-success">Yes</td>'
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
requestData.gpu= "No"
|
|
||||||
row += '<td class="text-secondary">No</td>'
|
|
||||||
}
|
|
||||||
elem.checked= false;
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(elem.placeholder=="Tags"){
|
|
||||||
requestData.data.push(elem.value)
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
requestData.data.push(elem.value)
|
|
||||||
row += `<td>${elem.value}</td>`
|
|
||||||
elem.value=""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
requestData.power= $(".sel-edit").find(":selected").text()
|
|
||||||
row += `<td>${$(".sel-edit").find(":selected").text()}</td>`
|
|
||||||
row += actions + '</tr>';
|
|
||||||
|
|
||||||
$.post({
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(requestData),
|
|
||||||
url: "/edit_host",
|
|
||||||
error:function () {
|
|
||||||
alert('An error occured while updating host. Please try again later.');
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
|
|
||||||
if(response.done){
|
|
||||||
|
|
||||||
$("table tbody tr").eq(curr_idx).remove()
|
|
||||||
if(curr_idx==0){
|
|
||||||
$("table tbody tr:first").before(row)
|
|
||||||
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$("table tbody tr").eq(curr_idx-1).after(row)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
alert(`Error: ${response.msg}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
row= '<tr>'
|
|
||||||
$('.sel-edit option:contains("Always On")').prop('selected', true)
|
|
||||||
curr_idx= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$("#editHostModal").modal('toggle');
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var del_idx= -1
|
|
||||||
$("table").on('click', 'a.delete', function(){
|
|
||||||
hname= $(this).parents("tr").find("td:first").text()
|
|
||||||
del_idx = $(this).parents("tr").index()
|
|
||||||
bdy= $("#edit-form").find(".modal-body")
|
|
||||||
p= bdy.find(".info")
|
|
||||||
$("#info-text").text(`Are you sure you want to delete ${hname}?`)
|
|
||||||
|
|
||||||
$("#deleteHostModal").modal('toggle')
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#delete-form").submit((e)=>{
|
|
||||||
e.preventDefault();
|
|
||||||
requestData = {
|
|
||||||
hostname: $("table tbody tr").eq(del_idx).find("td:first").text()
|
|
||||||
}
|
|
||||||
$.post({
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(requestData),
|
|
||||||
url: "/delete_host",
|
|
||||||
error:function () {
|
|
||||||
alert('An error occured while deleting host. Please try again later.');
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
|
|
||||||
$("table tbody tr").eq(del_idx).remove()
|
|
||||||
del_idx= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$("#deleteHostModal").modal('toggle')
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<div class="table-wrapper">
|
|
||||||
<div class="table-title">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-6">
|
|
||||||
<h2>Manage <b>Hosts</b></h2>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6">
|
|
||||||
<a href="#addHostModal" class="btn btn-success" data-toggle="modal"><i class="material-icons"></i> <span>Add New Host</span></a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="table table-striped table-hover" id="main-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<!--th>
|
|
||||||
<span class="custom-checkbox">
|
|
||||||
<input type="checkbox" id="selectAll">
|
|
||||||
<label for="selectAll"></label>
|
|
||||||
</span>
|
|
||||||
</th-->
|
|
||||||
<th>HostName</th>
|
|
||||||
<th>CPUs</th>
|
|
||||||
<th>Memory</th>
|
|
||||||
<th>Ip Address</th>
|
|
||||||
<th>Mac Address</th>
|
|
||||||
<th>GPU</th>
|
|
||||||
<th>Power Option</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Host1</td>
|
|
||||||
<td>4</td>
|
|
||||||
<td>7826</td>
|
|
||||||
<td>34.10.20.0</td>
|
|
||||||
<td>9C-35-5B-5F-4C-D7</td>
|
|
||||||
<td class="text-secondary">No</td>
|
|
||||||
<td>Always On</td>
|
|
||||||
<td>
|
|
||||||
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>
|
|
||||||
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Host2</td>
|
|
||||||
<td>8</td>
|
|
||||||
<td>7826</td>
|
|
||||||
<td>34.10.20.0</td>
|
|
||||||
<td>9C-35-5B-5F-4C-D7</td>
|
|
||||||
<td class="text-success">Yes</td>
|
|
||||||
<td>Wake on LAN</td>
|
|
||||||
<td>
|
|
||||||
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>
|
|
||||||
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Host3</td>
|
|
||||||
<td>4</td>
|
|
||||||
<td>4096</td>
|
|
||||||
<td>34.10.20.0</td>
|
|
||||||
<td>9C-35-5B-5F-4C-D7</td>
|
|
||||||
<td class="text-secondary">No</td>
|
|
||||||
<td>Tasmota</td>
|
|
||||||
<td>
|
|
||||||
<a href="#" class="edit" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Edit"></i></a>
|
|
||||||
<a href="#" class="delete" data-toggle="modal"><i class="material-icons" data-toggle="tooltip" title="Delete"></i></a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Add Modal -->
|
|
||||||
<div id="addHostModal" class="modal fade">
|
|
||||||
<div id="overlay">
|
|
||||||
<div class="loader"></div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form id="add-form">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">Add Host</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" id="add-modal">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>HostName</label>
|
|
||||||
<input id="add-hostname" type="text" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>CPUs</label>
|
|
||||||
<input type="number" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Memory</label>
|
|
||||||
<input type="number" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Ip Address</label>
|
|
||||||
<input type="text" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Mac Address</label>
|
|
||||||
<input type="text" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>GPU Available </label>
|
|
||||||
<input type="checkbox" id="selectAll">
|
|
||||||
<!--span class="custom-checkbox">
|
|
||||||
<input type="checkbox" id="selectAll">
|
|
||||||
<label for="selectAll"></label>
|
|
||||||
</span-->
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tags</label>
|
|
||||||
<input type="text" value="" data-role="tagsinput" placeholder="Tags" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Power Option</label>
|
|
||||||
<select class="browser-default custom-select sel-add" type="select">
|
|
||||||
<option selected>Always On</option>
|
|
||||||
<option >Wake on LAN</option>
|
|
||||||
<option >Tasmota</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<input type="button" class="btn btn-default" data-dismiss="modal" value="Cancel">
|
|
||||||
<input type="submit" class="btn btn-success" value="Add" id="add" disabled>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Edit Modal HTML -->
|
|
||||||
<div id="editHostModal" class="modal fade">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form id="edit-form">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">Edit Host</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>HostName</label>
|
|
||||||
<input type="text" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>CPUs</label>
|
|
||||||
<input type="number" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Memory</label>
|
|
||||||
<input type="number" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Ip Address</label>
|
|
||||||
<input type="text" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Mac Address</label>
|
|
||||||
<input type="text" class="form-control" disabled>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>GPU Available </label>
|
|
||||||
<input type="checkbox" id="selectAll" disabled>
|
|
||||||
<!--span class="custom-checkbox">
|
|
||||||
<input type="checkbox" id="selectAll">
|
|
||||||
<label for="selectAll"></label>
|
|
||||||
</span-->
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Tags</label>
|
|
||||||
<input type="text" value="hello, world" data-role="tagsinput" placeholder="Tags" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Power Option</label>
|
|
||||||
<select class="browser-default custom-select sel-edit" type="select">
|
|
||||||
<option selected>Always On</option>
|
|
||||||
<option >Wake on LAN</option>
|
|
||||||
<option >Tasmota</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<input type="button" class="btn btn-default" data-dismiss="modal" value="Cancel">
|
|
||||||
<input type="submit" class="btn btn-success" value="Save" id="save">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Delete Modal HTML -->
|
|
||||||
<div id="deleteHostModal" class="modal fade">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form id="delete-form">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">Delete Host</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p id="info-text"></p>
|
|
||||||
<p class="text-warning"><small>This action cannot be undone.</small></p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<input type="button" class="btn btn-default" data-dismiss="modal" value="Cancel">
|
|
||||||
<input type="submit" class="btn btn-danger" value="Delete" id=delete>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
254
virtpool.py
Executable file → Normal file
254
virtpool.py
Executable file → Normal file
|
@ -6,50 +6,15 @@ import sys
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import libvirt
|
import libvirt
|
||||||
import json
|
|
||||||
from multiprocessing.pool import ThreadPool
|
|
||||||
|
|
||||||
pool = ThreadPool(processes=1)
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
|
||||||
|
|
||||||
PATH = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
import configparser
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read("config.ini")
|
|
||||||
|
|
||||||
TEMPLATE_ENVIRONMENT = Environment(
|
|
||||||
autoescape=False,
|
|
||||||
loader=FileSystemLoader(os.path.join(PATH, "templates")),
|
|
||||||
trim_blocks=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TEMPLATE_ENVIRONMENT.globals['STATIC_PREFIX'] = '/templates/'
|
|
||||||
|
|
||||||
|
|
||||||
def render_template(template_filename, context):
|
class Client():
|
||||||
return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)
|
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
"""Main manager class"""
|
"""Main manager class"""
|
||||||
|
uris = {
|
||||||
""" uris = [
|
'qemu+ssh://pool@reserve.homedevops/system',
|
||||||
#'qemu+ssh://pool@reserve.homedevops/system',
|
'qemu+ssh://root@check2.homedevops/system',
|
||||||
#'qemu+ssh://root@check2.homedevops/system',
|
'qemu+ssh://root@check3.homedevops/system'
|
||||||
#'qemu+ssh://root@check3.homedevops/system'
|
}
|
||||||
'qemu:///system'
|
|
||||||
] """
|
|
||||||
|
|
||||||
host_str = config["host"]["available_hosts"]
|
|
||||||
uris = [
|
|
||||||
each.strip()
|
|
||||||
for each in host_str.replace("'", "").replace('"', "").split(",")
|
|
||||||
if each.strip()
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.status = {}
|
self.status = {}
|
||||||
|
@ -78,214 +43,25 @@ class Client:
|
||||||
usedcpus += cpus
|
usedcpus += cpus
|
||||||
|
|
||||||
out += "{}: {}/{} CPUs free,{}MiB/{}MiB memory free<br/>\n".format(
|
out += "{}: {}/{} CPUs free,{}MiB/{}MiB memory free<br/>\n".format(
|
||||||
n, cpus - usedcpus, cpus, str((mem - usedmem)), str(mem)
|
n,
|
||||||
)
|
cpus - usedcpus, cpus,
|
||||||
|
str((mem - usedmem)),
|
||||||
|
str(mem))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def create(
|
|
||||||
self,
|
|
||||||
name,
|
|
||||||
iso_source,
|
|
||||||
img_source,
|
|
||||||
domain_type="kvm",
|
|
||||||
memory_unit="MiB",
|
|
||||||
memory=1024,
|
|
||||||
current_memory=1024,
|
|
||||||
vcpu=2,
|
|
||||||
arch="x86_64",
|
|
||||||
machine="pc-q35-4.2",
|
|
||||||
offset="utc",
|
|
||||||
emulator="/usr/bin/qemu-system-x86_64",
|
|
||||||
):
|
|
||||||
|
|
||||||
"""create a VM with given specs
|
|
||||||
boot it and return the ID for future ref
|
|
||||||
"""
|
|
||||||
|
|
||||||
# TODO: Check host resources(cpus, mem, etc.) before creating a VM
|
|
||||||
|
|
||||||
param = {
|
|
||||||
"name": name,
|
|
||||||
"uuid": str(uuid.uuid4()),
|
|
||||||
"iso_source": iso_source,
|
|
||||||
"img_source": img_source,
|
|
||||||
"type": domain_type,
|
|
||||||
"memory_unit": memory_unit,
|
|
||||||
"memory": memory,
|
|
||||||
"current_memory": current_memory,
|
|
||||||
"vcpu": vcpu,
|
|
||||||
"arch": arch,
|
|
||||||
"machine": machine,
|
|
||||||
"offset": offset,
|
|
||||||
"emulator": emulator,
|
|
||||||
}
|
|
||||||
|
|
||||||
context = {"args": param}
|
|
||||||
|
|
||||||
xml = render_template("config.tmpl.xml", context)
|
|
||||||
# print(xml)
|
|
||||||
|
|
||||||
host = self.connections[self.uris[0]]
|
|
||||||
if host:
|
|
||||||
async_result = pool.apply_async(self.__create_vm, (host, xml))
|
|
||||||
return async_result.get()
|
|
||||||
return "No host available"
|
|
||||||
|
|
||||||
def __create_vm(self, host, xml):
|
|
||||||
try:
|
|
||||||
vm = host.createXML(xml)
|
|
||||||
return json.dumps({"uuid": vm.UUIDString(), "id": vm.ID(), "status": 1})
|
|
||||||
except Exception as e:
|
|
||||||
return json.dumps({"err": str(e), "status": 0})
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def gethost(self):
|
|
||||||
"""
|
|
||||||
It'll show relevant info about all available hosts
|
|
||||||
"""
|
|
||||||
|
|
||||||
host_data = []
|
|
||||||
|
|
||||||
for i, each in enumerate(self.uris):
|
|
||||||
tmp = {}
|
|
||||||
c = self.connections[each]
|
|
||||||
nodeinfo = c.getInfo()
|
|
||||||
mem = nodeinfo[1]
|
|
||||||
cpus = nodeinfo[2]
|
|
||||||
usedmem = 0
|
|
||||||
usedcpus = 0
|
|
||||||
|
|
||||||
tmp["sn"] = i + 1
|
|
||||||
tmp["hostname"] = c.getHostname()
|
|
||||||
tmp["status"] = "Alive" if c.isAlive() else "Dead"
|
|
||||||
tmp["uri"] = c.getURI()
|
|
||||||
tmp["cpu"] = {}
|
|
||||||
tmp["mem"] = {}
|
|
||||||
|
|
||||||
domain_ids = c.listDomainsID()
|
|
||||||
for did in domain_ids:
|
|
||||||
dom = c.lookupByID(did)
|
|
||||||
state, maxmem, mem2, cpus2, cput = dom.info()
|
|
||||||
usedmem += maxmem / 1024
|
|
||||||
usedcpus += cpus2
|
|
||||||
|
|
||||||
tmp["cpu"]["total"] = cpus
|
|
||||||
tmp["cpu"]["free"] = cpus - usedcpus
|
|
||||||
tmp["mem"]["total"] = mem
|
|
||||||
tmp["mem"]["free"] = int(mem - usedmem)
|
|
||||||
|
|
||||||
host_data.append(tmp)
|
|
||||||
|
|
||||||
return render_template("host.html", {"host_data": host_data})
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def getguest(self):
|
|
||||||
"""
|
|
||||||
It'll show host-wise available guests and detailed info about them
|
|
||||||
"""
|
|
||||||
|
|
||||||
guest_data = []
|
|
||||||
cnt = 0
|
|
||||||
for i, each in enumerate(self.uris):
|
|
||||||
c = self.connections[each]
|
|
||||||
for did in c.listDomainsID():
|
|
||||||
dom = c.lookupByID(did)
|
|
||||||
tmp = {
|
|
||||||
"sn": cnt + 1,
|
|
||||||
"guestname": dom.name(),
|
|
||||||
"hostname": c.getHostname(),
|
|
||||||
"id_": dom.ID(),
|
|
||||||
"mem": int(dom.info()[1] / 1024),
|
|
||||||
"cpu": dom.info()[3],
|
|
||||||
"status": "Active" if dom.isActive() else "Inactive",
|
|
||||||
"uuid": dom.UUIDString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
guest_data.append(tmp)
|
|
||||||
cnt += 1
|
|
||||||
|
|
||||||
return render_template("guest.html", {"guest_data": guest_data})
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def manage(self):
|
|
||||||
# get available hosts from the database(if it's stored in one)
|
|
||||||
# and return the updated template
|
|
||||||
|
|
||||||
return render_template("manage.html", {})
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
@cherrypy.tools.json_in()
|
|
||||||
@cherrypy.tools.json_out()
|
|
||||||
def query_host(self):
|
|
||||||
# make ajax call to query host
|
|
||||||
|
|
||||||
json_obj = cherrypy.request.json
|
|
||||||
print(json_obj)
|
|
||||||
import time
|
|
||||||
|
|
||||||
time.sleep(1) # simulate delay
|
|
||||||
return {
|
|
||||||
"data": [json_obj["hostname"], 4, 4096, "10.56.0.1", "9C-35-5B-5F-4C-D7"],
|
|
||||||
"gpu": True,
|
|
||||||
"done": True,
|
|
||||||
"msg": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
@cherrypy.tools.json_in()
|
|
||||||
@cherrypy.tools.json_out()
|
|
||||||
def add_host(self):
|
|
||||||
# make ajax call to add host, maybe save it in a database
|
|
||||||
|
|
||||||
json_obj = cherrypy.request.json
|
|
||||||
print(json_obj)
|
|
||||||
return {"done": True, "msg": ""}
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
@cherrypy.tools.json_in()
|
|
||||||
@cherrypy.tools.json_out()
|
|
||||||
def edit_host(self):
|
|
||||||
# make ajax call to edit host. Update it in database
|
|
||||||
|
|
||||||
json_obj = cherrypy.request.json
|
|
||||||
|
|
||||||
print(json_obj)
|
|
||||||
return {"done": True, "msg": ""}
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
@cherrypy.tools.json_in()
|
|
||||||
@cherrypy.tools.json_out()
|
|
||||||
def delete_host(self):
|
|
||||||
# make ajax call to delete host from database
|
|
||||||
|
|
||||||
json_obj = cherrypy.request.json
|
|
||||||
|
|
||||||
print(json_obj)
|
|
||||||
return {"done": True, "msg": ""}
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = Client()
|
ROOT = Client()
|
||||||
CONF = os.path.join(os.path.dirname(__file__), "site.conf")
|
CONF = os.path.join(os.path.dirname(__file__), 'site.conf')
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
# cherrypy.config.update(CONF)
|
# cherrypy.config.update(CONF)
|
||||||
cherrypy.server.socket_host = config["server"].get("server_host", "127.0.0.1")
|
cherrypy.server.socket_host = '127.0.0.1'
|
||||||
cherrypy.server.socket_port = config["server"].getint("server_port", 8080)
|
cherrypy.server.socket_port = 8080
|
||||||
ENGINE = cherrypy.engine
|
ENGINE = cherrypy.engine
|
||||||
# cherrypy.process.plugins.Daemonizer(engine).subscribe()
|
# cherrypy.process.plugins.Daemonizer(engine).subscribe()
|
||||||
# cherrypy.process.plugins.DropPrivileges(
|
# cherrypy.process.plugins.DropPrivileges(
|
||||||
# ENGINE, uid='cherrypy', gid='cherrypy').subscribe()
|
# ENGINE, uid='cherrypy', gid='cherrypy').subscribe()
|
||||||
cherrypy.tree.mount(
|
cherrypy.tree.mount(Client())
|
||||||
Client(),
|
|
||||||
"/",
|
|
||||||
config={
|
|
||||||
"/": {
|
|
||||||
"tools.staticdir.on": True,
|
|
||||||
"tools.staticdir.dir": PATH + "/templates",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if hasattr(ENGINE, "signal_handler"):
|
if hasattr(ENGINE, "signal_handler"):
|
||||||
ENGINE.signal_handler.subscribe()
|
ENGINE.signal_handler.subscribe()
|
||||||
if hasattr(ENGINE, "console_control_handler"):
|
if hasattr(ENGINE, "console_control_handler"):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user