mirror of
https://gitlab.com/JKANetwork/powerfulcomputermanager.git
synced 2026-02-16 02:01:31 +01:00
First version with web gui
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,7 +8,4 @@ BD/database*
|
||||
__pycache__/
|
||||
**/__pycache__/
|
||||
.vscode/
|
||||
cooks/*.yaml
|
||||
control/
|
||||
site/
|
||||
admin/
|
||||
cooks/*.yaml
|
||||
275
admin/admin.py
Normal file
275
admin/admin.py
Normal file
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/python3
|
||||
from flask import Flask, url_for, render_template, request, Response,redirect,make_response
|
||||
import os
|
||||
import jinja2
|
||||
import requests
|
||||
import hashlib #SHA256
|
||||
import json
|
||||
import base64
|
||||
import configparser
|
||||
from datetime import datetime
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
BUILD=1
|
||||
APIC_VER=2
|
||||
|
||||
# Load config to comunicate with API or use default
|
||||
try:
|
||||
config = configparser.ConfigParser()
|
||||
config.read(os.path.relpath('..\\client\\configpcm.ini'))
|
||||
c_server = config['General']['server']
|
||||
except:
|
||||
c_server = "https://127.0.0.1:3333" #Default server api address
|
||||
|
||||
app = Flask(__name__,static_url_path="/assets", static_folder='assets', template_folder="templates")
|
||||
|
||||
|
||||
def returnvalueapi(suburl,field="Name"):
|
||||
global c_server
|
||||
passha256=request.cookies.get('admin_logued')
|
||||
if 'Password=' in suburl:
|
||||
twopart = ""
|
||||
elif '?' in suburl:
|
||||
twopart = '&Password='+str(passha256)
|
||||
else:
|
||||
twopart = '?Password='+str(passha256)
|
||||
|
||||
r=(requests.post(c_server+suburl+twopart, verify=False).text).replace("\'", "\"").replace('\\"',"'").replace(': None',': "None"')
|
||||
try:
|
||||
#print(suburl+twopart+"->"+r)
|
||||
jsonobj = json.loads(r)
|
||||
return jsonobj[field]
|
||||
except:
|
||||
print('Error: '+str(r))
|
||||
return str({'TEXT':r,'RESULT':'ERROR'})
|
||||
|
||||
def returnTable(suburl,field=["Name"]):
|
||||
global c_server
|
||||
passha256=request.cookies.get('admin_logued')
|
||||
if isinstance(field,str):
|
||||
if field != 'ASIS': # Do not touch if it says that we have to return table AS IS
|
||||
field=[field] # Convert to list
|
||||
if 'Password=' in suburl:
|
||||
twopart = ""
|
||||
elif '?' in suburl:
|
||||
twopart = '&Password='+str(passha256)
|
||||
else:
|
||||
twopart= '?Password='+str(passha256)
|
||||
try:
|
||||
r=(requests.post(c_server+suburl+twopart, verify=False).text).replace("\'", "\"").replace('\\"',"'").replace(': None',': "None"')
|
||||
jsonobj = json.loads(r)
|
||||
if field == 'ASIS':
|
||||
return jsonobj
|
||||
else:
|
||||
listitems = []
|
||||
for ite in jsonobj: # Run in array from json
|
||||
to = []
|
||||
for i in field:
|
||||
to.append(str(ite[i]))
|
||||
listitems.append(to)
|
||||
return listitems
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
@app.route("/admin",methods=['GET'])
|
||||
@app.route("/admin/",methods=['GET'])
|
||||
def pAdminIndex(): #Admin Index page
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
table = returnTable("/get/computers",['ID_C','Name','RAM','CPUName','SOVersion','SOCaption','HDD','LastConnection','RAMFree'])
|
||||
|
||||
for lista in table:
|
||||
lista.append(returnTable("/get/groups?ComputerID="+lista[0],["Name"])) #lista[9]¿
|
||||
if lista[6] is not None:
|
||||
try:
|
||||
lista[6]=json.loads(base64.b64decode(lista[6]).decode('UTF-8'))
|
||||
except:
|
||||
lista[6]=json.loads(str("{}"))
|
||||
else:
|
||||
lista[6]=json.loads(str("{}"))
|
||||
#print (lista[6])
|
||||
try:
|
||||
lista[7] = datetime.fromtimestamp(int(lista[7])).strftime('%Y-%m-%d %H:%M:%S') # LastConnection
|
||||
except:
|
||||
lista[7] = "Never"
|
||||
return render_template('/adminindex.tmpl', title="Admin Dashboard",tablecomputers=table)
|
||||
|
||||
@app.route("/admin/addcomputer",methods=['GET'])
|
||||
def pAdminAddcomputer():
|
||||
computeradd=request.args.get('computeradd')
|
||||
if computeradd is not None:
|
||||
r = returnvalueapi("/add/computer?ComputerName="+computeradd,field="RESULT")
|
||||
if r == "OK":
|
||||
return redirect('/admin')
|
||||
else:
|
||||
return "ERROR"
|
||||
else:
|
||||
return redirect('/admin')
|
||||
|
||||
@app.route("/admin/delcomputer",methods=['GET'])
|
||||
def pAdminDelcomputer():
|
||||
computerdel=request.args.get('computerdel')
|
||||
r = returnvalueapi("/del/computer?ComputerID="+computerdel,field="RESULT")
|
||||
if r == "OK":
|
||||
return redirect('/admin')
|
||||
else:
|
||||
return "ERROR"
|
||||
|
||||
@app.route("/admin/computer",methods=['GET'])
|
||||
def pAdminComputer(): #Admin see one group
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
|
||||
computerid=request.args.get('ID_C')
|
||||
if computerid is not None and computerid.isnumeric():
|
||||
computername = returnvalueapi('/get/computers?ComputerID='+str(computerid),"Name")
|
||||
groups = returnTable("/get/groups?ComputerName="+str(computername),['ID_G','Name'])
|
||||
#####IT DOESNT HAVE TEMPLATE CREATED
|
||||
return render_template('/admincomputer.tmpl', title="Computer "+computername,computerid=computerid,computername=computername,groups=groups)
|
||||
return redirect('/admin')
|
||||
|
||||
|
||||
@app.route("/admin/groups",methods=['GET'])
|
||||
def pAdminGroups(): #Admin Index groups
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
table = returnTable("/get/groups",['ID_G','Name'])
|
||||
|
||||
for lista in table:
|
||||
lista.append(returnTable("/get/computersgrp?GroupID="+lista[0],['ID_C','Name'])) #lista[3]¿
|
||||
lista.append(returnTable("/get/cookgrp?GroupID="+lista[0],['CookName'])) #lista[4]¿
|
||||
return render_template('/admingroups.tmpl', title="Groups",tablegroups=table)
|
||||
|
||||
|
||||
@app.route("/admin/group",methods=['GET'])
|
||||
def pAdminGroup(): #Admin see one group
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
|
||||
groupid=request.args.get('ID_G')
|
||||
if groupid is not None and groupid.isnumeric():
|
||||
groupname=returnvalueapi('/get/groups?GroupID='+str(groupid),"Name")
|
||||
computers = returnTable("/get/computers",['ID_C','Name'])
|
||||
datagroup = (returnTable("/get/computersgrp?GroupID="+groupid,['ID_C','Name'])) #lista[0]¿
|
||||
tem = (returnTable("/get/cookgrp?GroupID="+groupid,['CookName'])) #lista[1]¿
|
||||
cooksdata = []
|
||||
for cook in tem:
|
||||
to = []
|
||||
to.append(cook[0])
|
||||
to.append(returnvalueapi('/get/lastrevisioncook?CookName='+cook[0],"Revision"))
|
||||
to.append(returnTable("/get/statuscook?CookName="+cook[0],'ASIS'))
|
||||
cooksdata.append(to)
|
||||
|
||||
tem = (returnTable("/get/cookall",'ASIS')) #Lista de todas las recetas
|
||||
allcooks = []
|
||||
for cook in tem['CookName']:
|
||||
to = []
|
||||
to.append(cook)
|
||||
to.append(returnvalueapi('/get/lastrevisioncook?CookName='+cook,"Revision"))
|
||||
allcooks.append(to)
|
||||
|
||||
return render_template('/admingroup.tmpl', title="Group "+groupname,groupname=groupname,groupid=groupid,datagroup=datagroup,cooksdata=cooksdata,computers=computers,allcooks=allcooks)
|
||||
return redirect('/admin/groups')
|
||||
|
||||
@app.route("/admin/group/addcook",methods=['GET'])
|
||||
def pAdminGroupAddcook(): #Add cook form enter
|
||||
cooktoadd=request.args.get('cooknameadd')
|
||||
groupid=request.args.get('groupid')
|
||||
if cooktoadd != None and groupid != None:
|
||||
r = returnvalueapi("/add/cookgrp?CookName="+cooktoadd+"&GroupID="+groupid,field="RESULT")
|
||||
if r == "OK":
|
||||
return redirect('/admin/group?ID_G='+groupid)
|
||||
|
||||
|
||||
@app.route("/admin/group/addcomputer",methods=['GET'])
|
||||
def pAdminGroupAddcomputer(): #Add computer form enter
|
||||
computertoadd=request.args.get('computeridadd')
|
||||
groupid=request.args.get('groupid')
|
||||
if computertoadd != None and groupid != None:
|
||||
r = returnvalueapi("/add/grpcomputer?ComputerID="+computertoadd+"&GroupID="+groupid,field="RESULT")
|
||||
if r == "OK":
|
||||
return redirect('/admin/group?ID_G='+groupid)
|
||||
|
||||
|
||||
@app.route("/admin/group/delcomputer",methods=['GET'])
|
||||
def pAdminGroupDelcomputer(): #Add computer form enter
|
||||
computertodel=request.args.get('computernamedel')
|
||||
groupid=request.args.get('groupid')
|
||||
if computertodel != None and groupid != None:
|
||||
r = returnvalueapi("/del/grpcomputer?ComputerName="+computertodel+"&GroupID="+groupid,field="RESULT")
|
||||
if r == "OK":
|
||||
return redirect('/admin/group?ID_G='+groupid)
|
||||
else:
|
||||
return redirect('/admin/group?ID_G='+groupid)
|
||||
|
||||
|
||||
@app.route("/admin/cook",methods=['GET'])
|
||||
def pAdminCook(): #Admin Index groups
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
|
||||
cookname=request.args.get('CookName')
|
||||
if cookname != None:
|
||||
groupscook=returnTable('/get/grpcook?CookName='+str(cookname),['Name'])
|
||||
allgroups=returnTable('/get/groups',['ID_G','Name'])
|
||||
return render_template('/admincook.tmpl', title="Cook "+cookname,cookname=cookname,allgroups=allgroups,groupscook=groupscook)
|
||||
|
||||
@app.route("/admin/cook/addgroup",methods=['GET'])
|
||||
def pAdminCookAddgroup(): # Add cook to group
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
cookname=request.args.get('cooknameadd')
|
||||
groupid=request.args.get('groupidadd')
|
||||
if cookname != None and groupid != None:
|
||||
returnvalueapi('/add/cookgrp?CookName='+cookname+'&GroupID='+groupid,'RESULT')
|
||||
return redirect('/admin/cook?CookName='+cookname)
|
||||
else:
|
||||
return "Error of arguments"
|
||||
|
||||
@app.route("/admin/cook/delgroup",methods=['GET'])
|
||||
def pAdminCookDelgroup(): # Add cook to group
|
||||
if returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE') != '0':
|
||||
return render_template('/login.tmpl', title="Login")
|
||||
cookname=request.args.get('cooknamedel')
|
||||
groupname=request.args.get('groupnamedel')
|
||||
if cookname != None and groupname != None:
|
||||
ret = returnvalueapi('/del/cookgrp?CookName='+cookname+'&GroupName='+groupname,'RESULT')
|
||||
if ret != '0':
|
||||
return "Error "+str(ret)
|
||||
return redirect('/admin/cook?CookName='+cookname)
|
||||
else:
|
||||
return "Error of arguments"
|
||||
|
||||
|
||||
|
||||
@app.route("/",methods=['GET'])
|
||||
def pIndex(): #Index page
|
||||
howispasswd=returnvalueapi('/check/password?Password='+str(request.cookies.get('admin_logued')),'EXITCODE')
|
||||
if howispasswd == '0': # Logued
|
||||
return redirect(url_for('pAdminIndex'))
|
||||
if howispasswd == '1' or howispasswd == '2': # Has to login
|
||||
return render_template('/login.tmpl')
|
||||
if howispasswd == '3': # No password yet
|
||||
return 'Yet to do it..'
|
||||
|
||||
@app.route("/login",methods=['GET','POST'])
|
||||
def pLogin(): #Login
|
||||
if request.values.get('password'):
|
||||
trpass = hashlib.sha256(request.values.get('password').encode()).hexdigest()
|
||||
#print(returnvalueapi('/check/password?Password='+trpass,'EXITCODE'))
|
||||
if returnvalueapi('/check/password?Password='+trpass,'EXITCODE') == '0':
|
||||
res = make_response(redirect(url_for('pAdminIndex')))
|
||||
res.set_cookie("admin_logued",value=trpass)
|
||||
return res
|
||||
else:
|
||||
return render_template('/login.tmpl')
|
||||
else:
|
||||
return render_template('/login.tmpl')
|
||||
|
||||
print("Build: "+str(BUILD))
|
||||
print("API Client Compatible Version: "+str(APIC_VER))
|
||||
|
||||
app.run(debug=True,port=3434,ssl_context='adhoc',host='0.0.0.0',threaded=True) #Default is port 3434
|
||||
7
admin/assets/css/bootstrap.min.css
vendored
Normal file
7
admin/assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
93
admin/assets/css/dashboard.css
Normal file
93
admin/assets/css/dashboard.css
Normal file
@@ -0,0 +1,93 @@
|
||||
body {
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.feather {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100; /* Behind the navbar */
|
||||
padding: 0;
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.sidebar-sticky {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 48px; /* Height of navbar */
|
||||
height: calc(100vh - 48px);
|
||||
padding-top: .5rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sidebar .nav-link .feather {
|
||||
margin-right: 4px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover .feather,
|
||||
.sidebar .nav-link.active .feather {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.form-control-dark {
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
border-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
.form-control-dark:focus {
|
||||
border-color: transparent;
|
||||
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
|
||||
}
|
||||
|
||||
/*
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
.border-top { border-top: 1px solid #e5e5e5; }
|
||||
.border-bottom { border-bottom: 1px solid #e5e5e5; }
|
||||
BIN
admin/assets/images/blue-mocha-grunge.jpg
Normal file
BIN
admin/assets/images/blue-mocha-grunge.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
admin/assets/images/cream-dust.png
Normal file
BIN
admin/assets/images/cream-dust.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 661 B |
BIN
admin/assets/images/glyphicons-halflings-white.png
Normal file
BIN
admin/assets/images/glyphicons-halflings-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
BIN
admin/assets/images/glyphicons-halflings.png
Normal file
BIN
admin/assets/images/glyphicons-halflings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
10
admin/assets/js/Chart.min.js
vendored
Normal file
10
admin/assets/js/Chart.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
admin/assets/js/bootstrap.min.js
vendored
Normal file
7
admin/assets/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
admin/assets/js/feather-4.24.1.min.js
vendored
Normal file
13
admin/assets/js/feather-4.24.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
admin/assets/js/jquery-3.2.1.slim.min.js
vendored
Normal file
4
admin/assets/js/jquery-3.2.1.slim.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
admin/assets/js/popper.min.js
vendored
Normal file
5
admin/assets/js/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
32
admin/templates/admincomputer.tmpl
Normal file
32
admin/templates/admincomputer.tmpl
Normal file
@@ -0,0 +1,32 @@
|
||||
{# -*- coding: utf-8 -*- #} {# NOT DID ALREADY!!! #}
|
||||
{% extends 'baseadmin.tmpl' %}
|
||||
{% block content %}
|
||||
<h2>Computer {{computername}}</h2>
|
||||
<br>
|
||||
<h2>Groups of {{computername}}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Group Name</th>
|
||||
<th>Cooks</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in tablegroups %} {#['ID_G','Name','Cooks[CookName]']#}
|
||||
<tr>
|
||||
<td><a href="/admin/group?ID_G={{item.0}}">{{item.1}}</a></td>
|
||||
<td style='width:50%;'>
|
||||
{% for x in item.2 %}
|
||||
{{x.0}},
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td><a class="btn btn-outline-danger btn-sm" data-wgroup="{{item.1}}" data-toggle="modal" data-target="#delGroup">Delete {{computername}} from group {{item.1}}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{% endblock %}
|
||||
92
admin/templates/admincook.tmpl
Normal file
92
admin/templates/admincook.tmpl
Normal file
@@ -0,0 +1,92 @@
|
||||
{# -*- coding: utf-8 -*- #}
|
||||
{% extends 'baseadmin.tmpl' %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Modal Delete Group From Cook -->
|
||||
<div class="modal fade" id="modalDelete" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/cook/delgroup">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Delete group from cook {{cookname}}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<input type="text" readonly="readonly" class="form-control" hidden="hidden" name="cooknamedel" id="cooknamedel" value="{{cookname}}"/>
|
||||
<p>Are you sure?<br>
|
||||
<label for="groupnamedel" class="col-form-label">Group:</label>
|
||||
</div>
|
||||
<input type="text" readonly="readonly" class="form-control" name="groupnamedel" id="groupnamedel">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-danger">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Add Group To Cook -->
|
||||
<div class="modal fade" id="modalAdd" tabindex="-1" role="dialog" aria-labelledby="modalAddLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/cook/addgroup">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalAddLabel">Add cook to Group {{groupname}}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<p>Add group to cook {{cookname}}<br>
|
||||
<input type="text" readonly="readonly" class="form-control" hidden="hidden" name="cooknameadd" id="cooknameadd" value="{{cookname}}"/>
|
||||
<label for="groupidadd" class="col-form-label">Group:</label>
|
||||
<select class="form-control" name="groupidadd" id="groupidadd">
|
||||
{% for item in allgroups %}
|
||||
<option value="{{item.0}}">{{item.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Groups of cook {{cookname}}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Group Name</th>
|
||||
<th style="align:right">Delete Group from this Cook</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in groupscook %} {#['Name']#}
|
||||
<tr>
|
||||
<td style='width:64px'>{{item.0}}</td>
|
||||
<td><a href="/admin/group?ID_G={{item.0}}">{{item.0}}</a></td>
|
||||
<td><a class="btn btn-outline-danger btn-sm" data-wgroup="{{item.0}}" data-toggle="modal" data-target="#modalDelete">Remove group {{item.0}} from cook {{cookname}}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td>#</td>
|
||||
<td colspan="2"><a class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#modalAdd">Add group to {{cookname}}</a></td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#modalDelete').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget) // Button that triggered the modal
|
||||
var recipient = button.data('wgroup') // Extract info from data-* attributes
|
||||
var modal = $(this)
|
||||
modal.find('#groupnamedel').val(recipient)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
150
admin/templates/admingroup.tmpl
Normal file
150
admin/templates/admingroup.tmpl
Normal file
@@ -0,0 +1,150 @@
|
||||
{# -*- coding: utf-8 -*- #}
|
||||
{% extends 'baseadmin.tmpl' %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Modal Delete Computer From Group -->
|
||||
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/group/delcomputer">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Delete computer from Group {{groupname}}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<input type="text" readonly="readonly" class="form-control" hidden="hidden" name="groupid" id="groupid" value="{{groupid}}"/>
|
||||
<p>Are you sure?<br>
|
||||
<label for="computername" class="col-form-label">Computer:</label>
|
||||
</div>
|
||||
<input type="text" readonly="readonly" class="form-control" name="computernamedel" id="computernamedel">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Add Computer To Group -->
|
||||
<div class="modal fade" id="modalAdd" tabindex="-1" role="dialog" aria-labelledby="modalAddLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/group/addcomputer">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalAddLabel">Add computer to Group {{groupname}}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<p>Add computer to group {{groupname}}<br>
|
||||
<input type="text" readonly="readonly" class="form-control" hidden="hidden" name="groupid" value="{{groupid}}"/>
|
||||
<label for="computername2" class="col-form-label">Computer:</label>
|
||||
<select class="form-control" name="computeridadd" id="computeridadd">
|
||||
{% for item in computers %}
|
||||
<option value="{{item.0}}">{{item.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Add Cook To Group -->
|
||||
<div class="modal fade" id="modalAddCook" tabindex="-1" role="dialog" aria-labelledby="modalAddCookLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/group/addcook">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalAddCookLabel">Add Cook to Group {{groupname}}</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<p>Add cook to group {{groupname}}<br>
|
||||
<input type="text" readonly="readonly" class="form-control" hidden="hidden" name="groupid" value="{{groupid}}"/>
|
||||
<label for="cookname" class="col-form-label">Cook name:</label>
|
||||
<select class="form-control" name="cooknameadd" id="cooknameadd">
|
||||
{% for item in allcooks %}
|
||||
<option value="{{item.0}}">{{item.0}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Computers of group {{groupname}}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Computer Name</th>
|
||||
<th style="align:right">Delete computer from Group</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in datagroup %} {#['ID_C','Name']#}
|
||||
<tr>
|
||||
<td style='width:64px'>{{item.0}}</td>
|
||||
<td><a href="/admin/computer?ID_C={{item.0}}">{{item.1}}</a></td>
|
||||
<td><a class="btn btn-outline-danger btn-sm" {#href="/admin/groupcomputerdel?ID_C={{item.0}}&ID_G={{groupid}}"#} data-wcomputer="{{item.1}}" data-toggle="modal" data-target="#exampleModal">Delete {{item.1}} from group {{groupname}}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td>#</td>
|
||||
<td colspan="2"><a class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#modalAdd">Add computer to {{groupname}}</a></td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h2>Cooks of group {{groupname}}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cook Name</th>
|
||||
<th style="width:64px">Cook Last Revision</th>
|
||||
<th>Applied to</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="3"><a class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#modalAddCook">Add new cook to group {{groupname}}</a></td>
|
||||
</tr>
|
||||
{% for item in cooksdata %} {#['CookName','LastRevision','AppliedTo']#}
|
||||
<tr>
|
||||
<td><a href="/admin/cook?CookName={{item.0}}">{{item.0}}</a></td>
|
||||
<td>{{item.1}}</td>
|
||||
<td>
|
||||
{% for subitem in item.2|sort(attribute='Revision')|sort(attribute='Name') %} {# item.2 = AppliedTo #}
|
||||
{{subitem.Name}}{% if subitem.Revision|int < item.1|int %}<span style="color:red">({{subitem.Revision}})</span>{% elif subitem.Error != 0 %}<span style="color:orange;font-weigth:bold;font-style:italic" title="{{subitem.ErrorDesc}}">(Error r.{{subitem.Revision}})</span>{% endif %},
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('#exampleModal').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget) // Button that triggered the modal
|
||||
var recipient = button.data('wcomputer') // Extract info from data-* attributes
|
||||
var modal = $(this)
|
||||
//modal.find('.modal-title').text('New message to ' + recipient)
|
||||
modal.find('#computernamedel').val(recipient)
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
35
admin/templates/admingroups.tmpl
Normal file
35
admin/templates/admingroups.tmpl
Normal file
@@ -0,0 +1,35 @@
|
||||
{# -*- coding: utf-8 -*- #}
|
||||
{% extends 'baseadmin.tmpl' %}
|
||||
{% block content %}
|
||||
<h2>Groups</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Group Name</th>
|
||||
<th>Cooks</th>
|
||||
<th>Computers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in tablegroups %} {#['ID_G','Name','Computers[ID_C,Name]','Cooks[CookName]']#}
|
||||
<tr>
|
||||
<td>{{item.0}}</td>
|
||||
<td><a href="/admin/group?ID_G={{item.0}}">{{item.1}}</a></td>
|
||||
<td style='width:50%;'>
|
||||
{% for x in item.3 %}
|
||||
{{x.0}},
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% for x in item.2 %}
|
||||
{{x.1}},
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
102
admin/templates/adminindex.tmpl
Normal file
102
admin/templates/adminindex.tmpl
Normal file
@@ -0,0 +1,102 @@
|
||||
{# -*- coding: utf-8 -*- #}
|
||||
{% extends 'baseadmin.tmpl' %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Modal Add Computer To Database -->
|
||||
<div class="modal fade" id="modalAdd" tabindex="-1" role="dialog" aria-labelledby="modalAddLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/addcomputer">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalAddLabel">Add computer to Database</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<p>Add computer to database<br>
|
||||
<label for="computeradd" class="col-form-label">Computer:</label>
|
||||
<input name="computeradd" id="computeradd" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Delete Computer To Database -->
|
||||
<div class="modal fade" id="modalDel" tabindex="-1" role="dialog" aria-labelledby="modalDelLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="get" action="/admin/delcomputer">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalDelLabel">Delete computer to Database. NOT REVERSIBLE</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<p>Delete computer from database. NOT REVERSIBLE<br>
|
||||
<label for="computerdel" class="col-form-label">Computer:</label>
|
||||
<select class="form-control" name="computerdel" id="computerdel">
|
||||
{% for item in tablecomputers %} {#['ID_C','Name',.....]#}
|
||||
<option value="{{item.0}}">{{item.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-danger">DELETE</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Computers</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Computer</th>
|
||||
<th>Operating System</th>
|
||||
<th>Groups</th>
|
||||
<th>CPU</th>
|
||||
<th>RAM (RAMUsed/RAM) Mb</th>
|
||||
<th>HDD</th>
|
||||
<th>Last ping</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="8"><a class="btn btn-outline-success btn-sm" data-toggle="modal" data-target="#modalAdd">Add computer to database</a></td>
|
||||
</tr>
|
||||
{% for item in tablecomputers %} {#['ID_C','Name','RAM','CPUName','SOVersion','SOCaption','HDD','LastConnection','RamFree','GroupsNames']#}
|
||||
<tr>
|
||||
<td>{{item.0}}</td>
|
||||
<td>{{item.1}}</td>
|
||||
<td>{{item.5}} {{item.4}}</td>
|
||||
<td width='200px'>
|
||||
{% for x in item.9 %}
|
||||
{{x.0}},
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{item.3}}</td>
|
||||
<td>{{item.2|int - item.8|int}}/{{item.2}}</td>
|
||||
<td>
|
||||
{% for x in item.6 %}
|
||||
{{x.Volume}}({{x.FreeSpace}}/{{x.Capacity}} GB)<br>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{item.7}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td colspan="8"><a class="btn btn-outline-danger btn-sm" data-toggle="modal" data-target="#modalDel">Delete computer to database</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
84
admin/templates/baseadmin.tmpl
Normal file
84
admin/templates/baseadmin.tmpl
Normal file
@@ -0,0 +1,84 @@
|
||||
{# -*- coding: utf-8 -*- #}
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<!--<link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">-->
|
||||
|
||||
<title>{{title}} - Powerful Computer Manager</title>
|
||||
|
||||
<link rel="canonical" href="index.html">
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="{{ url_for('static', filename='css/dashboard.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- Jquery JS -->
|
||||
<script src="{{ url_for('static', filename='js/jquery-3.2.1.slim.min.js') }}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
|
||||
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">Company name</a>
|
||||
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
|
||||
<ul class="navbar-nav px-3">
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" href="#">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
|
||||
<div class="sidebar-sticky pt-4">
|
||||
<ul class="nav flex-column pt-4">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path == "/admin/" or request.path == "/admin" %}active{% endif %}" href="/admin">
|
||||
<span data-feather="home"></span>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path == "/admin/groups" or request.path == "/admin/group" %}active{% endif %}" href="/admin/groups">
|
||||
<span data-feather="file"></span>
|
||||
Groups
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">{{title}}</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
</div>
|
||||
</div>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="{{ url_for('static', filename='js/popper.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
|
||||
|
||||
<!-- Icons -->
|
||||
<script src="{{ url_for('static', filename='js/feather-4.24.1.min.js') }}"></script>
|
||||
<script>
|
||||
feather.replace()
|
||||
</script>
|
||||
|
||||
<!-- Graphs -->
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
17
admin/templates/login.tmpl
Normal file
17
admin/templates/login.tmpl
Normal file
@@ -0,0 +1,17 @@
|
||||
{# -*- coding: utf-8 -*- #}
|
||||
{% extends 'baseadmin.tmpl' %}
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
<form action="/login" method="POST">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<tr>
|
||||
<td>Password<input type="password" name="password" id="password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><button type="submit">Login</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
304
admin/templates/test.html
Normal file
304
admin/templates/test.html
Normal file
@@ -0,0 +1,304 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">
|
||||
|
||||
<title>Dashboard Template for Bootstrap</title>
|
||||
|
||||
<link rel="canonical" href="index.html">
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="../../admin/assets/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="../../admin/assets/css/dashboard.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
|
||||
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="#">Company name</a>
|
||||
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
|
||||
<ul class="navbar-nav px-3">
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" href="#">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
|
||||
<div class="sidebar-sticky">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">
|
||||
<span data-feather="home"></span>
|
||||
Dashboard <span class="sr-only">(current)</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file"></span>
|
||||
Orders
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="shopping-cart"></span>
|
||||
Products
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="users"></span>
|
||||
Customers
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="bar-chart-2"></span>
|
||||
Reports
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="layers"></span>
|
||||
Integrations
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>Saved reports</span>
|
||||
<a class="d-flex align-items-center text-muted" href="#">
|
||||
<span data-feather="plus-circle"></span>
|
||||
</a>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Current month
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Last quarter
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Social engagement
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Year-end sale
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group mr-2">
|
||||
<button class="btn btn-sm btn-outline-secondary">Share</button>
|
||||
<button class="btn btn-sm btn-outline-secondary">Export</button>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
|
||||
<span data-feather="calendar"></span>
|
||||
This week
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<canvas class="my-4" id="myChart" width="900" height="380"></canvas>
|
||||
|
||||
<h2>Section title</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1,001</td>
|
||||
<td>Lorem</td>
|
||||
<td>ipsum</td>
|
||||
<td>dolor</td>
|
||||
<td>sit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,002</td>
|
||||
<td>amet</td>
|
||||
<td>consectetur</td>
|
||||
<td>adipiscing</td>
|
||||
<td>elit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>Integer</td>
|
||||
<td>nec</td>
|
||||
<td>odio</td>
|
||||
<td>Praesent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>libero</td>
|
||||
<td>Sed</td>
|
||||
<td>cursus</td>
|
||||
<td>ante</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,004</td>
|
||||
<td>dapibus</td>
|
||||
<td>diam</td>
|
||||
<td>Sed</td>
|
||||
<td>nisi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,005</td>
|
||||
<td>Nulla</td>
|
||||
<td>quis</td>
|
||||
<td>sem</td>
|
||||
<td>at</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,006</td>
|
||||
<td>nibh</td>
|
||||
<td>elementum</td>
|
||||
<td>imperdiet</td>
|
||||
<td>Duis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,007</td>
|
||||
<td>sagittis</td>
|
||||
<td>ipsum</td>
|
||||
<td>Praesent</td>
|
||||
<td>mauris</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,008</td>
|
||||
<td>Fusce</td>
|
||||
<td>nec</td>
|
||||
<td>tellus</td>
|
||||
<td>sed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,009</td>
|
||||
<td>augue</td>
|
||||
<td>semper</td>
|
||||
<td>porta</td>
|
||||
<td>Mauris</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,010</td>
|
||||
<td>massa</td>
|
||||
<td>Vestibulum</td>
|
||||
<td>lacinia</td>
|
||||
<td>arcu</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,011</td>
|
||||
<td>eget</td>
|
||||
<td>nulla</td>
|
||||
<td>Class</td>
|
||||
<td>aptent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,012</td>
|
||||
<td>taciti</td>
|
||||
<td>sociosqu</td>
|
||||
<td>ad</td>
|
||||
<td>litora</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,013</td>
|
||||
<td>torquent</td>
|
||||
<td>per</td>
|
||||
<td>conubia</td>
|
||||
<td>nostra</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,014</td>
|
||||
<td>per</td>
|
||||
<td>inceptos</td>
|
||||
<td>himenaeos</td>
|
||||
<td>Curabitur</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,015</td>
|
||||
<td>sodales</td>
|
||||
<td>ligula</td>
|
||||
<td>in</td>
|
||||
<td>libero</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="../../admin/assets/js/jquery-3.2.1.slim.min.js"></script>
|
||||
<script src="../../admin/assets/js/popper.min.js"></script>
|
||||
<script src="../../admin/assets/js/bootstrap.min.js"></script>
|
||||
|
||||
<!-- Icons -->
|
||||
<script src="../../admin/assets/js/feather-4.24.1.min.js"></script>
|
||||
<script>
|
||||
feather.replace()
|
||||
</script>
|
||||
|
||||
<!-- Graphs -->
|
||||
<script src="../../admin/assets/js/Chart.min.js"></script>
|
||||
<script>
|
||||
var ctx = document.getElementById("myChart");
|
||||
var myChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
datasets: [{
|
||||
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
|
||||
lineTension: 0,
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: '#007bff',
|
||||
borderWidth: 4,
|
||||
pointBackgroundColor: '#007bff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
beginAtZero: false
|
||||
}
|
||||
}]
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
66
api.py
66
api.py
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/python3
|
||||
BUILD = 26
|
||||
BUILD = 28
|
||||
API_VER = 2
|
||||
from flask import Flask, request
|
||||
import random
|
||||
@@ -72,7 +72,7 @@ def getComputerExists():
|
||||
|
||||
|
||||
##
|
||||
# getGroups: Know all groups or groups of a computer
|
||||
# getGroups: Know all groups, or groups of a computer
|
||||
# /get/groups?
|
||||
# @param ComputerName/ComputerID -> Computer to see groups (Not neccesary)
|
||||
# @param GroupID -> For know Name of a GroupID
|
||||
@@ -138,14 +138,27 @@ def getComputersGrp():
|
||||
return str(data)
|
||||
|
||||
##
|
||||
# getCookAll: Get the list of cooks added at least to one group
|
||||
# getCookAll: Get the list of all cooks (Or all added at least to one group)
|
||||
# /get/cookall?
|
||||
# NoParams
|
||||
# @param OnlyAdded: Show only added cooks
|
||||
##
|
||||
@app.route("/get/cookall",methods=['POST'])
|
||||
def getCookAll():
|
||||
logit(request.base_url)
|
||||
data = sql.select("SELECT DISTINCT CookName FROM COOKS_IDG ORDER BY CookName ASC") #All cooks
|
||||
|
||||
OnlyAdded =None
|
||||
for key, value in request.args.to_dict().items():
|
||||
if key == "OnlyAdded":
|
||||
OnlyAdded= 1
|
||||
if OnlyAdded is None:
|
||||
x = []
|
||||
for file in os.listdir("cooks"):
|
||||
if file.endswith(".yaml"):
|
||||
x.append(str(file.replace('.yaml','')).lower())
|
||||
data = {}
|
||||
data['CookName'] = x
|
||||
else:
|
||||
data = sql.select("SELECT DISTINCT CookName FROM COOKS_IDG ORDER BY CookName ASC") #All cooks
|
||||
return str(data)
|
||||
|
||||
|
||||
@@ -247,11 +260,12 @@ def delCookGrp(): #Delete cook from a group
|
||||
if key == "GroupID":
|
||||
GroupID = value
|
||||
|
||||
if GroupID is not None and CookName is not None:
|
||||
result = sql.insert("DELETE FROM COOKS_IDG WHERE ID_G='"+GroupID+"' AND CookName='"+CookName+"'")
|
||||
return str(result)
|
||||
else:
|
||||
return str({'TEXT': 'Error, no Group, or CookName does\'t exists','RESULT':'ERROR'})
|
||||
if GroupID is not None and CookName is not None:
|
||||
result = sql.insert("DELETE FROM COOKS_IDG WHERE ID_G='"+GroupID+"' AND CookName='"+CookName+"'")
|
||||
cleanDatabase()
|
||||
return str({'TEXT': '','RESULT':'0'}) #Return text str(result) is problematic (json inside json)
|
||||
else:
|
||||
return str({'TEXT': 'Error, no Group, or CookName doesnt exists','RESULT':'ERROR'})
|
||||
|
||||
##
|
||||
# delEmptyPcsGroup: Delete all computers from from a group
|
||||
@@ -273,7 +287,7 @@ def delEmptyPcsGroup():
|
||||
result = sql.insert("DELETE FROM COMPUTER_GROUP WHERE ID_G='"+GroupID+"'")
|
||||
return str(result)
|
||||
else:
|
||||
return str({'TEXT': 'Error, this group doesn\'t exists','RESULT':'ERROR'})
|
||||
return str({'TEXT': 'Error, this group doesnt exists','RESULT':'ERROR'})
|
||||
|
||||
|
||||
##
|
||||
@@ -460,7 +474,6 @@ def delCook():
|
||||
##
|
||||
@app.route("/del/groupscook",methods=['POST'])
|
||||
def delGroupsCook():
|
||||
|
||||
logit(request.base_url)
|
||||
CookName = None, None # Initialize
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -482,7 +495,6 @@ def delGroupsCook():
|
||||
##
|
||||
@app.route("/del/cleancook",methods=['POST'])
|
||||
def delCleanCook():
|
||||
|
||||
logit(request.base_url)
|
||||
CookName = None, None # Initialize
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -505,7 +517,6 @@ def delCleanCook():
|
||||
##
|
||||
@app.route("/upd/group",methods=['POST'])
|
||||
def updGroup():
|
||||
|
||||
logit(request.base_url)
|
||||
GroupID, GroupNewName= None, None
|
||||
for key,value in request.args.to_dict().items():
|
||||
@@ -534,7 +545,6 @@ def updGroup():
|
||||
##
|
||||
@app.route("/add/grpcomputer",methods=['POST'])
|
||||
def addGrpComputer():
|
||||
|
||||
logit(request.base_url)
|
||||
ComputerID, GroupID= None, None
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -561,7 +571,6 @@ def addGrpComputer():
|
||||
##
|
||||
@app.route("/del/grpcomputer",methods=['POST'])
|
||||
def delGrpComputer():
|
||||
|
||||
logit(request.base_url)
|
||||
ComputerID, GroupID= None, None
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -589,7 +598,6 @@ def delGrpComputer():
|
||||
##
|
||||
@app.route("/del/computer",methods=['POST'])
|
||||
def delComputer():
|
||||
|
||||
logit(request.base_url)
|
||||
ComputerID= None
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -669,7 +677,6 @@ def updComputer():
|
||||
##
|
||||
@app.route("/upd/cookname",methods=['POST'])
|
||||
def updCookName():
|
||||
|
||||
logit(request.base_url)
|
||||
CookName, CookNewName,Password= None, None,None #Initialize
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -705,7 +712,6 @@ def updCookName():
|
||||
##
|
||||
@app.route("/load/cook",methods=['POST'])
|
||||
def loadCook():
|
||||
|
||||
logit(request.base_url)
|
||||
CookName, ComputerID, UUID= None, None, None #Initialize
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -732,6 +738,15 @@ def loadCook():
|
||||
data = myfile.read()
|
||||
return str(yaml.safe_load(data))
|
||||
|
||||
##
|
||||
# setCookStatus: Set status of a executed Cook
|
||||
# /set/cookstatus?
|
||||
# @param ComputerID/ComputerName -> ID or Name of computer
|
||||
# @param CookName -> Name of the cook
|
||||
# @param Revision -> Revision of cook applied
|
||||
# @param Error -> Output, 0 if not error
|
||||
# @param ErrorDesc -> Optional, if error ocurred, description
|
||||
##
|
||||
@app.route("/set/cookstatus",methods=['POST'])
|
||||
def setCookStatus():
|
||||
logit(request.base_url)
|
||||
@@ -779,7 +794,6 @@ def setCookStatus():
|
||||
##
|
||||
@app.route("/check/password",methods=['POST'])
|
||||
def checkPassword(): #Check password (ERROR if password not valid or no password and 1 if valid)
|
||||
|
||||
logit(request.base_url)
|
||||
Password= None
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -807,7 +821,6 @@ def checkPassword(): #Check password (ERROR if password not valid or no passwor
|
||||
##
|
||||
@app.route("/upd/password",methods=['POST'])
|
||||
def updPassword(): #Update password (ERROR if password not valid or no password and 1 if changed)
|
||||
|
||||
logit(request.base_url)
|
||||
OldPassword,NewPassword= None, None
|
||||
for key, value in request.args.to_dict().items():
|
||||
@@ -851,7 +864,14 @@ def getOptionValue():
|
||||
return str({'TEXT': 'Invalid password','RESULT':'ERROR'})
|
||||
else:
|
||||
return sql.select("SELECT VALUE FROM OPTIONS WHERE Option='"+str(Option)+"'")[0]
|
||||
|
||||
|
||||
|
||||
def cleanDatabase():
|
||||
sql.insert("DELETE FROM COOKS_STATUS WHERE CookName NOT IN (SELECT CookName FROM COOKS_IDG)")
|
||||
sq = sql.select("SELECT ID_C FROM COMPUTERS")
|
||||
for val in sq:
|
||||
sql.insert("DELETE FROM COOKS_STATUS WHERE ID_C='"+str(val['ID_C'])+"' AND CookName NOT IN (SELECT DISTINCT CookName FROM COOKS_IDG WHERE ID_G IN (SELECT ID_G FROM COMPUTER_GROUP WHERE ID_C='"+str(val['ID_C'])+"'))")
|
||||
return 0
|
||||
|
||||
|
||||
@app.route('/get/apiver',methods=['POST'])
|
||||
@@ -861,5 +881,7 @@ def getApiVer():
|
||||
print("Build: "+str(BUILD))
|
||||
print("API Version: "+str(API_VER))
|
||||
|
||||
print("Cleaning database before start..")
|
||||
cleanDatabase()
|
||||
|
||||
app.run(debug=False,port=3333,ssl_context='adhoc',host='0.0.0.0',threaded=True)
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 2020/02/20
|
||||
- Fixing more bugs
|
||||
- First public beta of web gui, it doesn't have some parts but it's working
|
||||
|
||||
## 2019/11/21
|
||||
- Fixing bugs of last change and cleaning some code
|
||||
- Preparing web gui (It's hide yet)
|
||||
|
||||
@@ -15,8 +15,8 @@ It's not error proof, then, I don't have any responsability if something crashes
|
||||
|
||||
- Complete translation English and Spanish
|
||||
- Stabilize all
|
||||
- I created a simple GUI in EasyGUI to be easy used, but if I have time, maybe will do a webUI more dynamic and visually better (Without needing to install php/apache/nginx, using python)
|
||||
- Support Linux clients
|
||||
- Make better WebUI to replace all easygui (Password create/change and some subpages)
|
||||
|
||||
# Requirements
|
||||
|
||||
@@ -29,17 +29,19 @@ For server you can use Linux, Mac or Windows, and in theory any environment that
|
||||
- Yaml
|
||||
- Python3 deps can be installed it using pip install command (pip install pyopenssl flask pyyaml)
|
||||
|
||||
- For controlling the program, you have to start control.pyw using Python3. It doesn't need to be started in server, but it has to be in that folder (Or with configpcm.ini file).
|
||||
- It uses easygui of python3 (pip install easygui)
|
||||
- For controlling the program, you have to enter in a browser and go to https://SERVERIP:3434 (By default)
|
||||
- At first time yet, for putting password, you have to start control.pyw using Python3 (Needs easygui, pip install easygui). It doesn't need to be started in server, but it has to be in that folder (Or a folder with configpcm.ini file).
|
||||
|
||||
## Clients
|
||||
- Windows:
|
||||
- Windows 7 SP1 absolute minimal, but its only officially tested in 8.1 and 10, Windows 7 is being old now.
|
||||
- It needs Powershell 5.0+ (Or Powershell Core 6.1+), lower versions doesnt guaranteed to work, not tested. In Windows 7 and 8.1, you have to update Powershell. (With Powershell 3.0/4.0, some commands like Get-Package doesn't work)
|
||||
You can update from here: https://docs.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell?view=powershell-6#upgrading-existing-windows-powershell (Download link: https://www.microsoft.com/en-us/download/details.aspx?id=54616)
|
||||
- Windows 7 SP1 absolute minimal, but its only officially tested in 8.1 and 10, Windows 7 is obsolete by Microsoft.
|
||||
- It needs Powershell 5.0+ (Or Powershell Core 6.2+), lower versions doesn't have some of the commands and are not guaranteed to work, not tested. In Windows 7 and 8.1, you have to update Powershell. Windows 10 have Powershell 5 by default
|
||||
You can update from here:
|
||||
-Powershell Core: https://github.com/PowerShell/PowerShell/releases
|
||||
-Powershell 5.1: https://www.microsoft.com/en-us/download/details.aspx?id=54616 )
|
||||
|
||||
- Linux:
|
||||
- Not supported yet, but when Windows be stable, I will try to do it in some way
|
||||
- Not supported yet, but when Windows be stable, I will try to do using bash scripts
|
||||
|
||||
- Clients may have to be queried by DNS requests. If you don't have a domain (Samba or Windows AD), you may have to have a DNS server with local machines
|
||||
|
||||
@@ -48,7 +50,7 @@ For server you can use Linux, Mac or Windows, and in theory any environment that
|
||||
## Server
|
||||
- Copy this folder to a folder in your server
|
||||
- Install dependencies
|
||||
- Start the server: Start api.py using loadserver.bat (Windows) o loadserver.sh (Linux) file. You can do in systemd way, or a cron, or in a terminal of windows server,...
|
||||
- Start the server: Start api.py using loadserver.bat (Windows) o loadserver.sh (Linux) file. You can do in systemd way, or a cron, or in a terminal of windows server,... It also will start the webserver gui app for controlling it (https://localhost:3434).
|
||||
|
||||
## Client. Example using GPO and Task Schedule
|
||||
- Go to "client" folder and copy "configpcm_example.ini" to "configpcm.ini" and change their values to yours. Change lang value for having client gui translated to your lang. Here you will see:
|
||||
@@ -66,4 +68,7 @@ For server you can use Linux, Mac or Windows, and in theory any environment that
|
||||
- Args: -executionpolicy bypass -windowstyle hidden -noninteractive -nologo -file "\\SERVER\SysVol\DOMAINNAME\scripts\client.ps1" -startup 1
|
||||
|
||||
## Control app
|
||||
- Start control.pyw to setup password and start adding computers, groups and cooks and configure options. You can use docs/example_cooks for examples. If anyone wants, I will setup a git repo for cooks.
|
||||
- Start control.pyw to setup password
|
||||
- Go to a browser and load https://SERVER_IP:3434/ , login and manage all. control.pyw is the old way, it has to work, but its deprecated
|
||||
|
||||
- For creating cooks, you can see the documentation (It takes 3 minutes to make something workable), and can use docs/example_cooks for examples. If anyone wants, I will setup a git repo for cooks.
|
||||
@@ -1,3 +1,5 @@
|
||||
REM Starting Server. Will only work if python and dependencies are installed and in PATH
|
||||
cd /D "%~dp0"
|
||||
@start /b python3 api.py
|
||||
cd admin
|
||||
@start /b python3 admin.py
|
||||
@@ -1,3 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$(realpath "$0")")";
|
||||
python3 api.py &
|
||||
cd admin
|
||||
python3 admin.py
|
||||
Reference in New Issue
Block a user