Compare commits
4 Commits
master
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
5d01c4278d | |||
91136a9e87 | |||
656071fa6e | |||
db5b2e96c1 |
52
client.py
Executable file
52
client.py
Executable file
|
@ -0,0 +1,52 @@
|
|||
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"])
|
7
config.ini
Executable file
7
config.ini
Executable file
|
@ -0,0 +1,7 @@
|
|||
[host]
|
||||
# enter comma separated hosts
|
||||
available_hosts = 'qemu:///system', "qemu:///session"
|
||||
|
||||
[server]
|
||||
server_host = 127.0.0.1
|
||||
server_port = 8080
|
9
consumer.sql
Executable file
9
consumer.sql
Executable file
|
@ -0,0 +1,9 @@
|
|||
-- 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
Executable file
199
database.py
Executable file
|
@ -0,0 +1,199 @@
|
|||
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
Executable file
51
pc.py
Executable file
|
@ -0,0 +1,51 @@
|
|||
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
Executable file
10
queue.sql
Executable file
|
@ -0,0 +1,10 @@
|
|||
-- 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
Normal file
5
tags.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS tags(
|
||||
id INTEGER PRIMARY KEY,
|
||||
tag VARCHAR,
|
||||
|
||||
)
|
6
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
6
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
7
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
17
templates/assets/css/Bootstrap-4---Table-Fixed-Header.css
Normal file
17
templates/assets/css/Bootstrap-4---Table-Fixed-Header.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
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
Normal file
55
templates/assets/css/bootstrap-tagsinput.css
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
.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
Normal file
646
templates/assets/js/bootstrap-tagsinput.js
vendored
Normal file
|
@ -0,0 +1,646 @@
|
|||
(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
Normal file
2
templates/assets/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
37
templates/config.tmpl.xml
Normal file
37
templates/config.tmpl.xml
Normal file
|
@ -0,0 +1,37 @@
|
|||
<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>
|
81
templates/guest.html
Normal file
81
templates/guest.html
Normal file
|
@ -0,0 +1,81 @@
|
|||
<!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>
|
182
templates/host.html
Normal file
182
templates/host.html
Normal file
|
@ -0,0 +1,182 @@
|
|||
<!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>
|
830
templates/manage.html
Normal file
830
templates/manage.html
Normal file
|
@ -0,0 +1,830 @@
|
|||
<!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
Normal file → Executable file
254
virtpool.py
Normal file → Executable file
|
@ -6,15 +6,50 @@ import sys
|
|||
|
||||
import cherrypy
|
||||
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/'
|
||||
|
||||
|
||||
class Client():
|
||||
def render_template(template_filename, context):
|
||||
return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)
|
||||
|
||||
|
||||
class Client:
|
||||
"""Main manager class"""
|
||||
uris = {
|
||||
'qemu+ssh://pool@reserve.homedevops/system',
|
||||
'qemu+ssh://root@check2.homedevops/system',
|
||||
'qemu+ssh://root@check3.homedevops/system'
|
||||
}
|
||||
|
||||
""" uris = [
|
||||
#'qemu+ssh://pool@reserve.homedevops/system',
|
||||
#'qemu+ssh://root@check2.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):
|
||||
self.status = {}
|
||||
|
@ -43,25 +78,214 @@ class Client():
|
|||
usedcpus += cpus
|
||||
|
||||
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
|
||||
|
||||
@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()
|
||||
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.server.socket_host = '127.0.0.1'
|
||||
cherrypy.server.socket_port = 8080
|
||||
cherrypy.server.socket_host = config["server"].get("server_host", "127.0.0.1")
|
||||
cherrypy.server.socket_port = config["server"].getint("server_port", 8080)
|
||||
ENGINE = cherrypy.engine
|
||||
# cherrypy.process.plugins.Daemonizer(engine).subscribe()
|
||||
# cherrypy.process.plugins.DropPrivileges(
|
||||
# ENGINE, uid='cherrypy', gid='cherrypy').subscribe()
|
||||
cherrypy.tree.mount(Client())
|
||||
cherrypy.tree.mount(
|
||||
Client(),
|
||||
"/",
|
||||
config={
|
||||
"/": {
|
||||
"tools.staticdir.on": True,
|
||||
"tools.staticdir.dir": PATH + "/templates",
|
||||
}
|
||||
},
|
||||
)
|
||||
if hasattr(ENGINE, "signal_handler"):
|
||||
ENGINE.signal_handler.subscribe()
|
||||
if hasattr(ENGINE, "console_control_handler"):
|
||||
|
|
Loading…
Reference in New Issue
Block a user