Initial Commit

master
Harald Wolff 2019-05-14 08:47:44 +02:00
commit 29523c6f07
9 changed files with 1281 additions and 0 deletions

84
frame.html 100644
View File

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Sky.JS</title>
<link href="style.css" rel="stylesheet" />
<link href="sky.tables.css" rel="stylesheet" />
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="sky.base.js"></script>
<script type="text/javascript" src="sky.tables.js"></script>
<script type="text/javascript" src="sky.controls.js"></script>
<script type="text/javascript" src="sky.form.js"></script>
<script type="text/javascript" src="sky.dhcp.js"></script>
</head>
<body>
<script type="text/javascript">
var counter = 0;
</script>
<div id="body">
<div id="header">
<div class="skylogo">
<div style="background-color: #009ee3; color: white;">Sky</div><div style="background-color: white;">JS</div></div>
</div>
<div id="page">
<div id="content">
<h1>Table Demo</h1>
<table id="tableDemo" style="width: 95%;"></table>
</div>
</div>
<div id="footer">
<button onclick="SKY.refresh('test');">STOP</button>
<button onclick="counter = 0; SKY.refresh('test', 1000, function(){ $('#refreshCounter').text( counter++ ); });">1s</button>
<button onclick="counter = 0; SKY.refresh('test', 250, function(){ $('#refreshCounter').text( counter++ ); });">250ms</button>
<span id="refreshCounter"></span>
</div>
</div>
<script type="text/javascript">
var demoObjects = [
{
Name: "First Demo Item",
Age: 14,
Comment: "none",
IP: "1.2.3.4"
},
{
Name: "Second Demo Item",
Age: 45,
Comment: "none",
IP: "5.6.7.8"
},
{
Name: "Third Demo Item",
Age: 87,
Comment: "some",
IP: "3.4.5.6"
},
];
$("#tableDemo").skyTable( {
rows: demoObjects,
columns: [
{ key: "Name", label: "Name", type: "string" },
{ key: "Age", label: "Alter", type: "int" },
{ key: "Comment", label: "Bemerkungen", type: "string" },
{ key: "IP", label: "IP", type: "IPv4" },
],
});
</script>
</body>
</html>

2
jquery.min.js vendored 100644

File diff suppressed because one or more lines are too long

280
sky.base.js 100644
View File

@ -0,0 +1,280 @@
var SKY = null;
(function(){
var options = {
url: "/",
types: {
}
}
var refreshTimer = null;
var refreshInterval = null;
var refreshList = [];
var refreshCounter = 0;
function SkyAPI(){
this.JQuery = {};
this.options = function(o){
options = Object.assign(options, o);
}
this.refresh = function( name, interval, f){
if (!interval)
{
for (var n=0;n<refreshList.length;n++)
if (refreshList[n].name == name){
refreshList.splice(n,1);
break;
}
} else {
var ritem = null;
for (var n=0;n<refreshList.length;n++)
if (refreshList[n].name == name){
ritem = refreshList[n];
break;
}
if (!ritem)
{
ritem = {
name: name,
interval: interval,
refresh: f,
counter: interval
}
refreshList.push( ritem );
} else {
ritem.interval = interval;
ritem.counter = interval;
ritem.refresh = f;
}
}
return this;
}
this.interval = function(i){
if (arguments.length == 0)
return refreshInterval;
if (refreshTimer){
clearInterval( refreshTimer );
refreshTimer = null;
}
if (i)
{
refreshInterval = i;
refreshTimer = setInterval(
function(){
refreshList.forEach( function(r){
r.counter -= refreshInterval;
if (r.counter <= 0)
{
r.counter += r.interval;
r.refresh();
}
});
},
refreshInterval
);
}
return this;
}
this.interval( 100 );
return this;
}
SKY = new SkyAPI();
}());
function SKYAPI(baseurl){
this.baseurl = baseurl;
this.refresh = []
this.setBaseURL = function(url){ this.baseurl = url; }
this.addRefresh = function( rh, seconds = null ){ this.refresh.push( { interval: seconds ? seconds : 5, refresh: rh } ); }
this.get = function(page, json, handler = null){ return this.__request("GET", page, json, handler); }
this.post = function(page, json, handler = null){ return this.__request("POST", page, json, handler); }
this.put = function(page, json, handler = null){ return this.__request("PUT", page, json, handler); }
this.__request = function(method, page, json, handler = null){
if (page[0] == '/')
page = page.substr(1);
var async = (handler) ? true : false;
var x = new XMLHttpRequest();
if (async)
{
x.onload = function(){
var responseText = x.responseText;
if (json && !content)
handler( JSON.parse( responseText ) );
else
handler( responseText );
}
}
x.open(method, this.baseurl + page, async );
if (json)
x.send(JSON.stringify(json));
else
x.send();
if (!async)
return x.responseText;
return null;
}
this.getJson = function(page, handler){
if (handler)
{
var j = function(t){
handler(JSON.parse(t));
};
return this.get( page, null, j );
}
var r = this.get( page );
return JSON.parse(r);
}
this.call = function(endpoint,method,parameters = [], receiver = null){
var x = new XMLHttpRequest();
x.open("POST", this.baseurl + endpoint, (receiver != null));
x.setRequestHeader("content-type","application/json");
if (receiver)
{
x.onload = function(){ var r = JSON.parse(this.responseText).Result; receiver(r); }
x.onerror = function(){ receiver(false); }
}
var methodCall = {
"MethodName": method,
"Parameters": parameters
}
x.send(JSON.stringify(methodCall));
if (!receiver)
{
var result = JSON.parse(x.responseText);
if (result.Exception != null)
throw result.Exception;
return result.Result;
}
return x;
}
this.loadPage = function (page) {
if (page[0] == '/')
page = page.substr(1);
var x = new XMLHttpRequest();
x.open("GET", this.baseurl + page);
x.setRequestHeader("x-template-unframed","unframed");
x.onload = function()
{
$("#content").empty();
$("#content").append(this.responseText);
history.pushState(null, page, skyapi().baseurl + page);
}
this.refresh = []
x.send();
return false;
}
this.fireOnLoad = function(element){
if (element.onload != null)
{
element.onload();
}
for (var n=0;n<element.children.length;n++)
this.fireOnLoad(element.children[n]);
}
this.__refresh_index = 0;
this.UIRefresh = function(){
this.__refresh_index++;
for (var n=0;n<this.refresh.length;n++)
{
var r = this.refresh[n];
if ((this.__refresh_index % r.interval)==0)
r.refresh();
}
}
setInterval( function(){ skyapi().UIRefresh(); }, 1000 );
}
var __skyapi = new SKYAPI("/");
function skyapi()
{
return __skyapi;
}
function ScaleSI(value)
{
if (value > 1000000000)
return ((value / 1000000000) | 0) + "G";
if (value > 1000000)
return ((value / 1000000) | 0) + "M";
if (value > 1000)
return ((value / 1000) | 0) + "k";
return value;
}
function BytesToString(bytestring, sep = ":")
{
return Array.from( bytestring, function(b){
return ('0' + b.charCodeAt(0).toString(16)).slice(-2);
}).join(sep);
}
function DecodeMAC(b64mac)
{
var smac = atob(b64mac);
return BytesToString(smac);
}
function PopulateForm( o, options = {} )
{
var opt = Object.assign( {
top: null,
transform: function(v){ return v; },
properties: {},
}, options );
for (const key in o)
{
var po = Object.assign( {
transform: opt.transform,
}, opt.properties[key]);
var i = $("#" + key, opt.top);
if (i.length)
i.val( po.transform(o[key]) );
}
}

97
sky.controls.js 100644
View File

@ -0,0 +1,97 @@

(function($){
$.extend($, { sky: {} } );
$.extend($.sky, { controls: {} });
$.widget( "sky.IPPool", {
options: {
url: "/DHCP/collections/IPPool",
},
_create: function(){
this.refresh();
},
refresh: function(){
var element = this.element;
$.ajax( this.options.url, {
error: function(x,status,error){
alert(`sky.IPPool: Error fetching list of IPPools from Server ${status} ${error}`);
},
success: function(data,status,x){
data.forEach( function(e){
$("<option></option>")
.attr("value", e.Name)
.text( e.Name )
.appendTo( element );
} );
},
});
},
_destroy: function(){
},
});
$.widget( "sky.IPv4", {
options: {
mask: '099.099.099.099',
placeholder: '000.000.000.000',
},
_create: function(){
this.element.mask( this.options.mask, {
placeholder: this.options.placeholder,
} );
},
value: function(v){
if (arguments.length == 0){
return this.element.val();
}
this.element.val(v);
},
_destroy: function(){
},
});
$.fn.___IPPool = function( opts ){
var select = this.filter("select")[0];
if (!select)
throw "IPPool needs an <select> element to be instantiated on";
return $(select);
}
me = function(opts){
return this.filter("select").map( function(){
var ctrl = this;
var pools = skyapi().getJson("/DHCP/collections/IPPool");
ctrl.val = function(v){ alert(v); }
return ctrl;
});
}
$.sky.controls.IPPool = function(type,key){
return $("<select></select>").attr( "id", key ).IPPool();
};
$.sky.controls.IPv4 = function(type,key){
return $("<input></input>").attr( "id", key ).IPv4();
};
}( jQuery ));

77
sky.dhcp.js 100755
View File

@ -0,0 +1,77 @@

function editIPPool(ippool, editable)
{
skyapi().getJson( "/DHCP/collections/IPPool/" + ippool, function(pool){
$.skyForm( pool, {
title: "DHCP IP Pool",
fields: [
{ key: "Name", label: "Bezeichnung", },
{ key: "FirstIP", label: "Erste IP", type: "IPv4", },
{ key: "LastIP", label: "Letzte IP", type: "IPv4", },
{ key: "DefaultLeaseTime", label: "Standard Gültigkeit", }
],
accept: function(pool){
alert("OK: " + pool);
},
} );
} );
}
function editDHCPServerInterface(intf, editable){
skyapi().getJson( "/DHCP/collections/DHCPServerInterface/" + intf, function(intf){
$.skyForm( intf, {
title: "DHCP Server Interface",
fields: [
{ key: "Name", label: "Bezeichnung", },
{ key: "InterfaceAddress", label: "Interface IP", },
{ key: "IPPool", label: "IP Pool", type: "IPPool", toEditor: function(p){ return p.Name; } },
]
} );
} );
}
function __editDHCPServerInterface(intf, editable)
{
var content = $(`<div>
<fieldset>
<label>Bezeichnung</label>
<input type="text" id="Name">
<label>Interface IP</label>
<input type="text" id="InterfaceAddress">
<label>IP Pool</label>
<select id="Pool"></select>
</fieldset>
</div>`);
var pools = skyapi().getJson("/DHCP/collections/IPPool");
pools.forEach( function(e){
$("<option></option>")
.attr("id", e.Name)
.text( e.Name )
.appendTo( $("#Pool", content) );
} );
content.dialog({
modal: true,
closeOnEscape: true,
draggable: false,
title: "DHCP Server Interface",
minWidth: 600,
autoOpen: false,
buttons: [
{
text: "abbrechen",
click: function(){ $(this).dialog( "close" ); },
},
{
text: "OK",
click: function(){ $(this).dialog( "close" ); },
},
]
});
}

124
sky.form.js 100644
View File

@ -0,0 +1,124 @@

(function($){
function log(o){
console.log(o);
return o;
}
var defaultOptions = {
fields: [ // Object Fields to show in Dialog
/* {
key: "", // Key of the property
label: "", // Label Text
type: "text", // Editor to use
toEditor: function(v){ return v; } // converter function to convert field value to editor value
fromEditor: function(c){ return v; }// converter function to convert editor value to field value
},
*/
],
id: null, // Name of field to use as unique identifier for ajax requests to collection,
// alternatively: function(o){ return id; } Method to retrieve ID from object
readonly: false, // default readonly flag
ajax: {
url: null, // ajax collection URLSchnuff17
},
accept: function(o){
},
cancel: function(o){
},
};
var defaultDialogOptions = {
modal: true,
closeOnEscape: true,
draggable: false,
title: "skyForm",
minWidth: 600,
autoOpen: false,
buttons: [
{
text: "abbrechen",
click: null,
},
{
text: "OK",
click: null,
},
]
};
function populateForm( o, options = {} )
{
var opt = Object.assign( {
top: null,
transform: function(v){ return v; },
properties: {},
}, options );
for (const key in o)
{
var po = Object.assign( {
transform: opt.transform,
}, opt.properties[key]);
var i = $("#" + key, opt.top);
if (i.length)
i.val( po.transform(o[key]) );
}
}
function createControl(type, key)
{
if ($.sky.controls[type])
{
return $.sky.controls[type](type, key);
}
switch (type)
{
default:
return $(`<input id="${key}">`);
}
}
$.skyForm = function(o, options = {} ){
var opts = Object.assign(defaultOptions, options);
var fieldSet = $("<fieldset></fieldset>");
var fields = (opts.fields.length) ? opts.fields : log(Object.keys(o)).map( function(k){ return { key: k, label: k }; } );
fields.forEach( function(field){
fieldSet.append($(`<label for="${field.key}">${field.label}</label>`))
fieldSet.append(
createControl( field.type, field.key ).val( o[field.key] )
);
});
var dlgOptions = Object.assign( {}, defaultDialogOptions );
dlgOptions.title = opts.title;
dlgOptions.buttons[0].click = function(){
if (options.cancel)
options.cancel(o);
$(this).dialog("close");
};
dlgOptions.buttons[1].click = function(){
if (options.accept)
options.accept(o);
$(this).dialog("close");
};
fieldSet.dialog( dlgOptions );
fieldSet.dialog( "open" );
}
}( jQuery ));

33
sky.tables.css 100644
View File

@ -0,0 +1,33 @@
.skytable {
padding: 8px;
}
table.skytable {
padding: 2px;
border-collapse: collapse;
}
td.skytable:first-child {
padding-left: 4px;
}
td.skytable:last-child {
padding-right: 4px;
}
tr.skytable-head {
background-color: silver;
}
td.skytable-head {
}
tr.skytable-row:hover {
background-color: #e7f7ff;
}
tr.skytable-row.selected {
background-color: #c7def5;
}

147
sky.tables.js 100644
View File

@ -0,0 +1,147 @@
(function(SKY,$){
var defaultOptions = {
url: null,
rows: [],
columns: [],
pagification: {
rows: 25,
}
};
$.fn.skyTable = function(opt){
if (this.length != 1)
throw "SkyTable must be constructed on exactly one <table>";
var self = this.get(0);
if (self.tagName != "TABLE")
self = this.parents("table").get(0);
if (!self)
throw "can't identify skyTable Element";
var skyTable = $(self).data( "sky.skyTable");
if (skyTable){
if (arguments.length)
throw "can't reinitialize SkyTable without prior destroy()";
return skyTable;
}
var options = Object.assign( {}, defaultOptions, opt );
function SkyTable(t){
var me = this;
t = $(t);
t .data( "sky.skyTable", me)
.addClass("skytable");
me.html = {
table: t,
head: $("<thead></thead>").appendTo(t),
body: $("<tbody></tbody>").appendTo(t),
footer: $("<tfooter></tfooter>").appendTo(t),
rowCache: [],
usedRows: [],
createRow: function(){
var row = $("<tr></tr>")
.addClass("skytable skytable-row")
.on("click", function(){
$(this).toggleClass("selected");
});
for (var n=0;n<options.columns.length;n++){
$("<td></td>")
.addClass("skytable skytable-cell")
.appendTo(row);
}
return row;
},
attachDataRow: function(dri){
if (me.html.rowCache.length == 0)
me.html.rowCache.push(me.html.createRow());
var drow = me.data.rows[dri];
var htmlRow = $(me.html.rowCache.pop());
htmlRow.data( "data-row", drow );
htmlRow.data( "data-row-index", dri );
var cells = $("td", htmlRow);
for (var n=0;n<options.columns.length;n++){
$(cells[n])
.text(drow[options.columns[n].key]);
}
htmlRow.appendTo(me.html.body);
},
initializeHeader: function(){
me.html.columnHeader = $("<tr></tr>")
.addClass("skytable skytable-head")
.appendTo(me.html.head);
for (var n=0;n<options.columns.length;n++){
$("<td></td>")
.text(options.columns[n].label)
.addClass("skytable skytable-head")
.appendTo(me.html.columnHeader);
}
},
empty: function(){
me.html.usedRows.forEach( function(htmlRow){
$(htmlRow).detach();
rowCache.push(htmlRow);
});
},
};
me.data = {
rows: options.rows,
firstrow: 0,
getLastRow: function(){
var lastRow = me.data.firstrow + options.pagification.rows;
if (lastRow > me.data.rows.length)
lastRow = me.data.rows.length;
return lastRow;
},
};
me.reset = function(){
me.html.empty();
}
me.rows = function(rows){
if (arguments.length == 0)
return me.currentRows;
me.empty();
}
me.refresh = function(){
me.html.empty();
var lastRow = me.data.getLastRow();
for (var n = me.data.firstrow;n < lastRow; n++){
me.html.attachDataRow( n );
}
}
me.html.initializeHeader();
me.refresh();
}
return new SkyTable(self);
};
}(SKY,jQuery));

437
style.css 100644
View File

@ -0,0 +1,437 @@
body {
padding: 0px;
margin: 0px;
display: block;
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
font-family: "Liberation Sans","DejaVu Sans","Sans";
}
div#body {
display: flex;
height: 100%;
width: 100%;
flex-direction: column;
}
/*!
div {
margin: 0px;
padding: 0px;
flex-grow: 0;
}
h1,h2,h3,h4,h5,h6 {
margin: 0px;
padding:2px;
}
table {
width: 98%;
}
h1 > div {
display: inline;
}
h1 {
font-size: 18px;
border-bottom: 1px solid black;
}
h2 {
font-size: 14px;
}
a {
text-decoration: none;
color: inherit;
}
*/
/*!
button {
border: 1px solid black;
border-radius: 4px;
padding: 6px;
width: 140px;
font-weight: 800;
}
*/
#header {
top: 0px;
left: 0px;
right: 0px;
border-bottom: 1px solid black;
background-color: #c7def5;
padding: 4px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
#header > div {
margin: 4px;
}
#footer {
height: 20px;
padding: 6px;
padding-left: 24px;
background-color: #c7def5;
border-top: 1px solid black;
flex-grow: 0;
flex-shrink: 0;
}
#page {
display: flex;
top: 56px;
bottom: 32px;
left: 0px;
right: 0px;
flex-grow: 1;
flex-direction: row;
}
#nav {
display: flex;
left: 0px;
top: 0px;
bottom: 0px;
padding: 6px;
flex-grow: 0;
}
#content {
padding: 8px;
padding-top: 16px;
overflow: auto;
flex-grow: 1;
}
.scroll {
display: inline-block;
overflow: scroll;
}
.fill {
flex-grow: 1;
flex-shrink: 1;
}
.right {
float: right;
right: 0px;
}
.flex {
display: flex;
position: relative;
}
.flex.row {
flex-direction: row;
}
.flex.column {
flex-direction: column;
}
.flex > div {
margin: 4px;
display: inline-block;
}
.skylogo {
font-family: Helvetica, Arial;
display: inline-block;
border: 1px solid black;
font-size: 18px;
font-weight: 800;
flex-grow: 0;
flex-shrink: 0;
}
.skylogo > div {
display: inline-block;
padding: 4px;
}
.indicator {
margin-left: 4px;
margin-right: 4px;
padding-top: 5px;
padding-left: 5px;
display: inline-block;
font-size: 12px;
width: 140px;
height: 18px;
border-radius: 8px;
color: #C0C0C0;
background-color: #009ee3;
}
.indicator > div {
border-radius: 50%;
background-color: #404040;
display: inline-block;
width: 11px;
height: 11px;
margin-bottom: -1px;
margin-right: 4px;
}
.indicator[state="STOPPING"] > div {
background-color: #C0C000;
}
.indicator[state="STARTED"] > div {
background-color: #00D000;
}
.indicator[state="FAILED"] > div {
background-color: #D00000;
}
.indicator[state="INITIALIZED"] > div {
background-color: #00D0D0;
}
.group {
min-width: 240px;
}
.group.right {
width: 24%;
border-left: 1px solid black;
padding-left: 8px;
top: 0px;
bottom: 0px;
}
#navigation h1 {
font-size: 14px;
border: none;
}
#navigation > div {
display: block;
margin-right: 16px;
}
#navigation > div > h1 {
margin-top: 8px;
margin-bottom: 4px;
}
#navigation > div > div {
margin-left: 8px;
}
#content > div {
margin: 6px;
}
fieldset {
display: inline-grid;
grid-template-columns: auto auto;
}
fieldset > * {
align-self: center;
margin-left: 6px;
margin-right: 6px;
margin-top: 4px;
margin-bottom: 2px;
}
.popup {
border: 2px solid rgba(0,0,0,0);
position: relative;
display: inline-block;
padding: 4px;
z-index: 1;
font-size: 16px;
transition: background-color 250ms;
}
.popup:hover {
border: 1px solid black;
border-bottom: 3px solid white;
border-radius: 6px 6px 0px 0px;
background-color: #dbe7f3;
transition: background-color 250ms;
}
.popup > div {
visibility: hidden;
opacity: 0;
transition: opacity 250ms;
display: block;
position: absolute;
z-index: -1;
left: -1px;
top: 26px;
min-width: 140px;
max-width: 240px;
background-color: white;
border: 1px solid black;
border-radius: 0px 6px 6px 6px;
padding: 8px;
}
.popup:hover > div {
visibility: visible;
opacity: 1;
transition: opacity 250ms;
}
.popup > div > div {
position: relative;
display: block;
padding: 4px;
white-space: nowrap;
}
.popup > div > div:hover {
background-color: #dbe7f3;
}
.ui-icon {
background-image: url(images/ui-icons_6495ED_256x240.png);
}
.issue-box {
display: block;
border-radius: 6px 6px 0px 0px;
border: 3px solid silver;
}
.issue-box > h2 {
font-size: 18px;
}
.issue-box > div {
background-color: white;
color: black;
font-size: 14px;
padding: 4px;
}
.issue-box #History {
max-height: 80px;
overflow-x: hidden;
overflow-y: auto;
}
.issue-WARN {
border-color: #C0C000;
background-color: #C0C000;
color: black;
}
.issue-CRITICAL {
border-color: red;
background-color: red;
color: white;
}
#CheckState {
font-size: 20px;
}
#CheckState.WARN, span#nWARN {
color: #C0C000;
}
#CheckState.CRITICAL, span#nCRITICAL{
color: RED;
margin-right: 12px;
}
#PerfValues > span, #History > span {
display: inline-block;
width: 100%;
}
#History > span {
color: green;
margin-right: 24px;
}
#PerfValues > .WARN, #History > .WARN {
color: #C0C000;
}
#PerfValues > .CRITICAL, #History > .CRITICAL {
color: white;
background-color: red;
}
span#nWARN, span#nCRITICAL {
font-size: 24px;
}
.performance-value > div {
display: inline-block;
margin-right: 8px;
white-space: nowrap;
}
.performance-value > div:last {
margin-right: 0px;
}
.performance-value {
border-bottom: 1px solid black;
}
.issue-box .performance-value > #chartBox {
position: relative;
width: 100%;
height: 100%;
}
.issue-box .performance-value {
position: relative;
height: 100px;
}
.issue-box:hover .performance-value {
height: 250px;
}
#content a:not([class])::before {
content: '\25B8';
color: blue;
padding-right: 2px;
}