RPC frontend for libvirt #1
33
client.py
Normal file → Executable file
33
client.py
Normal file → Executable file
|
@ -1,46 +1,49 @@
|
|||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
class RPCClient():
|
||||
|
||||
class RPCClient:
|
||||
"""
|
||||
RPCClient Class
|
||||
"""
|
||||
|
||||
def create_vm(self, **kwargs):
|
||||
|
||||
url= "http://127.0.0.1:8080/create?"
|
||||
url = "http://127.0.0.1:8080/create?"
|
||||
if kwargs:
|
||||
q = ""
|
||||
for k in kwargs.keys():
|
||||
q += "&{}={}".format(k,kwargs[k])
|
||||
q += "&{}={}".format(k, kwargs[k])
|
||||
|
||||
url+=q
|
||||
url += q
|
||||
|
||||
print(url)
|
||||
resp= requests.get(url)
|
||||
resp = requests.get(url)
|
||||
|
||||
return resp.json()
|
||||
|
||||
def start_vm(self, vm):
|
||||
""" get a vm and try starting it
|
||||
"""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
|
||||
# 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)
|
||||
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"]))
|
||||
|
|
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
|
||||
|
||||
)
|
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;
|
||||
}
|
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>
|
804
templates/manage.html
Normal file
804
templates/manage.html
Normal file
|
@ -0,0 +1,804 @@
|
|||
<!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">
|
||||
<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>
|
||||
<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>
|
||||
$(document).ready(function(){
|
||||
//window.onload= function(){
|
||||
// Activate tooltip
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
// 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{
|
||||
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{
|
||||
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{
|
||||
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>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>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>
|
190
virtpool.py
Normal file → Executable file
190
virtpool.py
Normal file → Executable file
|
@ -7,32 +7,48 @@ import sys
|
|||
import cherrypy
|
||||
import libvirt
|
||||
import json
|
||||
import threading
|
||||
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)
|
||||
loader=FileSystemLoader(os.path.join(PATH, "templates")),
|
||||
trim_blocks=False,
|
||||
)
|
||||
|
||||
# TEMPLATE_ENVIRONMENT.globals['STATIC_PREFIX'] = '/templates/'
|
||||
|
||||
#TEMPLATE_ENVIRONMENT.globals['STATIC_PREFIX'] = '/templates/'
|
||||
|
||||
def render_template(template_filename, context):
|
||||
return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)
|
||||
|
||||
|
||||
class Client():
|
||||
class Client:
|
||||
"""Main manager class"""
|
||||
uris = [
|
||||
|
||||
""" 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):
|
||||
|
@ -58,14 +74,12 @@ class Client():
|
|||
for did in domain_ids:
|
||||
dom = c.lookupByID(did)
|
||||
state, maxmem, mem2, cpus, cput = dom.info()
|
||||
usedmem += maxmem/1024
|
||||
usedmem += maxmem / 1024
|
||||
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
|
||||
|
@ -77,21 +91,21 @@ class Client():
|
|||
domain_type="kvm",
|
||||
memory_unit="MiB",
|
||||
memory=1024,
|
||||
current_memory= 1024,
|
||||
current_memory=1024,
|
||||
vcpu=2,
|
||||
arch="x86_64",
|
||||
machine="pc-q35-4.2",
|
||||
offset="utc",
|
||||
emulator="/usr/bin/qemu-system-x86_64"
|
||||
emulator="/usr/bin/qemu-system-x86_64",
|
||||
):
|
||||
|
||||
""" create a VM with given specs
|
||||
"""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
|
||||
# TODO: Check host resources(cpus, mem, etc.) before creating a VM
|
||||
|
||||
param= {
|
||||
param = {
|
||||
"name": name,
|
||||
"uuid": str(uuid.uuid4()),
|
||||
"iso_source": iso_source,
|
||||
|
@ -100,33 +114,30 @@ class Client():
|
|||
"memory_unit": memory_unit,
|
||||
"memory": memory,
|
||||
"current_memory": current_memory,
|
||||
"vcpu":vcpu,
|
||||
"vcpu": vcpu,
|
||||
"arch": arch,
|
||||
"machine": machine,
|
||||
"offset": offset,
|
||||
"emulator": emulator,
|
||||
}
|
||||
|
||||
context= {
|
||||
'args': param
|
||||
}
|
||||
context = {"args": param}
|
||||
|
||||
xml= render_template("config.tmpl.xml", context)
|
||||
#print(xml)
|
||||
xml = render_template("config.tmpl.xml", context)
|
||||
# print(xml)
|
||||
|
||||
host= self.connections[self.uris[0]]
|
||||
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})
|
||||
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})
|
||||
return json.dumps({"err": str(e), "status": 0})
|
||||
|
||||
@cherrypy.expose
|
||||
def gethost(self):
|
||||
|
@ -134,35 +145,35 @@ class Client():
|
|||
It'll show relevant info about all available hosts
|
||||
"""
|
||||
|
||||
host_data= []
|
||||
host_data = []
|
||||
|
||||
for i, each in enumerate(self.uris):
|
||||
tmp= {}
|
||||
c= self.connections[each]
|
||||
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"]= {}
|
||||
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
|
||||
usedmem += maxmem / 1024
|
||||
usedcpus += cpus2
|
||||
|
||||
tmp["cpu"]["total"]= cpus
|
||||
tmp["cpu"]["free"]= cpus - usedcpus
|
||||
tmp["mem"]["total"]= mem
|
||||
tmp["mem"]["free"]= int(mem - usedmem)
|
||||
tmp["cpu"]["total"] = cpus
|
||||
tmp["cpu"]["free"] = cpus - usedcpus
|
||||
tmp["mem"]["total"] = mem
|
||||
tmp["mem"]["free"] = int(mem - usedmem)
|
||||
|
||||
host_data.append(tmp)
|
||||
|
||||
|
@ -174,51 +185,106 @@ class Client():
|
|||
It'll show host-wise available guests and detailed info about them
|
||||
"""
|
||||
|
||||
guest_data= []
|
||||
cnt=0
|
||||
guest_data = []
|
||||
cnt = 0
|
||||
for i, each in enumerate(self.uris):
|
||||
c= self.connections[each]
|
||||
c = self.connections[each]
|
||||
for did in c.listDomainsID():
|
||||
dom= c.lookupByID(did)
|
||||
tmp= {
|
||||
"sn": cnt+1,
|
||||
dom = c.lookupByID(did)
|
||||
tmp = {
|
||||
"sn": cnt + 1,
|
||||
"guestname": dom.name(),
|
||||
"hostname": c.getHostname(),
|
||||
"id_": dom.ID(),
|
||||
"mem": int(dom.info()[1]/1024),
|
||||
"mem": int(dom.info()[1] / 1024),
|
||||
"cpu": dom.info()[3],
|
||||
"status": "Active" if dom.isActive() else "Inactive",
|
||||
"uuid": dom.UUIDString()
|
||||
"uuid": dom.UUIDString(),
|
||||
}
|
||||
|
||||
guest_data.append(tmp)
|
||||
cnt+=1
|
||||
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": ""}
|
||||
|
||||
def manage_host(self,host_id):
|
||||
pass
|
||||
|
||||
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"
|
||||
}
|
||||
"/": {
|
||||
"tools.staticdir.on": True,
|
||||
"tools.staticdir.dir": PATH + "/templates",
|
||||
}
|
||||
},
|
||||
)
|
||||
if hasattr(ENGINE, "signal_handler"):
|
||||
ENGINE.signal_handler.subscribe()
|
||||
|
|
Loading…
Reference in New Issue
Block a user