mirror of
https://github.com/Telecominfraproject/wlan-cloud-loadsim.git
synced 2026-01-10 14:01:40 +00:00
fixing stats
This commit is contained in:
11920
priv/www/_tmp-stats.js
11920
priv/www/_tmp-stats.js
File diff suppressed because it is too large
Load Diff
@@ -1,581 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" href="assets/favicon.ico" type="image/x-icon" />
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>OWLS</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
|
||||
|
||||
<style>
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.controls {
|
||||
background: #e9ecef;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(144px, 320px) 1fr 1.5fr;
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
.actions {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(96px, 50%);
|
||||
grid-gap: 0.5rem;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 40% 60%;
|
||||
grid-gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.table-sm {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.output {
|
||||
background: var(--bs-light);
|
||||
white-space: pre;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="controls p-3">
|
||||
|
||||
<div class="col">
|
||||
<h1 class="h3">^(OvO)^ OWLS</h1>
|
||||
|
||||
<label class="form-label" for="apiBase">API base</label>
|
||||
<div class="d-flex mb-3">
|
||||
<input type="text" name="apiBase" v-model="apiBase" class="form-control form-control-sm w-75" />
|
||||
<button class="btn btn-sm btn-secondary w-25" v-on:click="setup()">Set</button>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="loadImportCasForm()">Import CA</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="newSimulation()">Create Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('prepare')">Prepare Assets</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('push')">Push Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('start')">Start Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('stop')">Stop Simulation</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
|
||||
<div v-if="importCA">
|
||||
|
||||
<h2 class="h5">Import CA</h2>
|
||||
|
||||
<fieldset class="form-grid border border-secondary p-3">
|
||||
|
||||
<label class="form-label">Name</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="casForm.name"/></div>
|
||||
|
||||
<label class="form-label">Key File</label>
|
||||
<div>
|
||||
<input class="form-control form-control-sm" type="file" ref="keyFile" @change="loadText($event, 'key')">
|
||||
</div>
|
||||
|
||||
<label class="form-label">Cert File</label>
|
||||
<div>
|
||||
<input class="form-control form-control-sm" type="file" ref="certFile" @change="loadText($event, 'cert')">
|
||||
</div>
|
||||
|
||||
<label class="form-label">Password</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="casForm.password"/></div>
|
||||
|
||||
<div></div>
|
||||
<div>
|
||||
<button class="btn btn-secondary btn-sm" v-on:click="saveCA()">Import CA</button>
|
||||
<button class="btn btn-light btn-sm" v-on:click="loadImportCasForm()">Cancel</button>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
<h2 class="h5">Simulation</h2>
|
||||
|
||||
<fieldset class="form-grid p-3">
|
||||
|
||||
<label class="form-label">Current Simulation</label>
|
||||
<div><select class="form-select form-select-sm" v-model="simulation" v-on:change="selectSimulation()">
|
||||
<option value=""></option>
|
||||
<option v-for="(simulation, index) in simulations" :key="index" v-bind:value="simulation">{{ simulation }}</option>
|
||||
</select></div>
|
||||
|
||||
</fieldset>
|
||||
<fieldset class="form-grid border border-secondary p-3">
|
||||
|
||||
<label class="form-label">Name</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="form.name"/></div>
|
||||
|
||||
<label class="form-label">CA</label>
|
||||
<div><select class="form-select form-select-sm" v-model="form.caname"><option v-for="(ca, index) in cas" :key="index" v-bind:value="ca">{{ ca }}</option></select></div>
|
||||
|
||||
<label class="form-label">Number of Devices</label>
|
||||
<div><input type="number" class="form-control form-control-sm" v-model="form.num_devices" /></div>
|
||||
|
||||
<label class="form-label">Nodes</label>
|
||||
<div class="d-grid">
|
||||
<label class="" v-for="(node, index) in nodesNodes" :key="index">
|
||||
<input type="checkbox" v-model="form.nodes" :value="node" /> {{ node }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="form-label">Server Name</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="form.server"/></div>
|
||||
|
||||
<label class="form-label">Port</label>
|
||||
<div><input type="number" class="form-control form-control-sm" v-model="form.port" /></div>
|
||||
|
||||
<button class="btn btn-secondary btn-sm" v-on:click="saveSimulation()">Save</button>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
|
||||
<h2 class="h5">Nodes</h2>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node Name</th>
|
||||
<th>Total</th>
|
||||
<th>Allocated</th>
|
||||
<th>Biggest</th>
|
||||
<th>Procs</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(node, index) in nodes" :key="index">
|
||||
<td>{{ node.node }}</td>
|
||||
<td>{{ node.total }}</td>
|
||||
<td>{{ node.allocated }}</td>
|
||||
<td>{{ node.worst }}</td>
|
||||
<td>{{ node.processes }}</td>
|
||||
<td>{{ node.type }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-3">
|
||||
|
||||
<div id="charts"></div>
|
||||
|
||||
<h2 class="h3">Output</h2>
|
||||
<div class="output">
|
||||
<code v-for="(msg, index) in output" :key="index">{{ msg }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
start of scripts
|
||||
-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha256-t9UJPrESBeG2ojKTIcFLPGF7nHi2vEc7f5A2KpH/UBU=" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/vue@3.0.4/dist/vue.global.js"></script>
|
||||
<!-- temporary stats for stream testing -->
|
||||
<script src="_tmp-stats.js"></script>
|
||||
<script>
|
||||
const App = {
|
||||
data() {
|
||||
return {
|
||||
apiBase: 'http://localhost:9090',
|
||||
apiPrefix: '/api/v1',
|
||||
output: [],
|
||||
socket: null,
|
||||
socketOpen: false,
|
||||
wss: 'ws://localhost:9090/ws',
|
||||
msg: 'Hello World',
|
||||
importCA: false,
|
||||
casForm: {
|
||||
name: null,
|
||||
key: null,
|
||||
cert: null,
|
||||
password: null,
|
||||
},
|
||||
nodes: null,
|
||||
cas: [],
|
||||
simulations: [],
|
||||
simulationNew: false,
|
||||
simulation: null,
|
||||
form: {
|
||||
name: null,
|
||||
caname: null,
|
||||
num_devices: null,
|
||||
server: null,
|
||||
port: null,
|
||||
nodes: null,
|
||||
},
|
||||
// stream suff
|
||||
nodesDict: {},
|
||||
nodesData: [],
|
||||
charts: [],
|
||||
streamInterval: null,
|
||||
streamIntervalTime: 250,
|
||||
streamTimeSince: 0,
|
||||
streamOn: false,
|
||||
streamIndex: 0,
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
console.log('App loaded');
|
||||
|
||||
// apiBase
|
||||
// check localStorage
|
||||
if( localStorage.getItem('apiBase') ) {
|
||||
this.apiBase = localStorage.getItem('apiBase');
|
||||
} else if( window.location.protocol !== 'file:' ) {
|
||||
this.apiBase = `${window.location.protocol}//${window.location.host}`;
|
||||
}
|
||||
|
||||
// use a set for nodes
|
||||
this.nodes = new Set();
|
||||
|
||||
this.setup();
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
setup: function() {
|
||||
// save URL for future use
|
||||
localStorage.setItem('apiBase', this.apiBase);
|
||||
this.getCas();
|
||||
this.getNodes();
|
||||
this.getSimulations();
|
||||
},
|
||||
|
||||
setupWebsocket: function() {
|
||||
if( !this.wss ) {
|
||||
console.log('Missing websocket URL');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Starting websocket...');
|
||||
this.socket = new WebSocket(this.wss);
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
console.log(event);
|
||||
// try to pull data into a basic chart
|
||||
if( event.data && event.data.value ) {
|
||||
this.testLogKernel( event.data );
|
||||
} else {
|
||||
this.output.push(JSON.parse(event.data));
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.onopen = (event) => {
|
||||
console.log(event);
|
||||
this.output.push('Websocket opened');
|
||||
this.socketOpen = true;
|
||||
}
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
this.output.push('Websocket closed');
|
||||
this.socketOpen = false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
closeWebsocket: function() {
|
||||
if( this.socket )
|
||||
this.socket.close();
|
||||
},
|
||||
|
||||
// send a msg through the socket...
|
||||
send: function() {
|
||||
console.log(`Sending message '${this.msg}'...`);
|
||||
// should check if socket is good before
|
||||
this.socket.send(this.msg)
|
||||
},
|
||||
|
||||
loadImportCasForm: function() {
|
||||
this.importCA = !this.importCA;
|
||||
},
|
||||
|
||||
getCas: function() {
|
||||
this.cas = [];
|
||||
this.apiGet('/cas')
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
if( data.Data && data.Data.CAs ) {
|
||||
this.cas = data.Data.CAs;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getNodes: function() {
|
||||
this.nodes = [];
|
||||
this.apiGet('/nodes')
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
if(data.Data && data.Data.HardwareDefinitions) {
|
||||
// expecting array of objects
|
||||
this.nodes = data.Data.HardwareDefinitions[0];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getSimulations: function() {
|
||||
const location = `${this.apiBase}${this.apiPrefix}/simulations`;
|
||||
fetch( location )
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.simulations = data.Data.Simulations;
|
||||
this.simulation = this.simulations[0];
|
||||
this.selectSimulation();
|
||||
});
|
||||
},
|
||||
|
||||
// clear form for empty mode
|
||||
newSimulation() {
|
||||
this.simulation = '';
|
||||
this.simulationNew = true;
|
||||
this.resetProps( this.form );
|
||||
},
|
||||
|
||||
selectSimulation() {
|
||||
const sim = this.simulation;
|
||||
// if user selects empty, it's a "new" sim
|
||||
if( !sim ) {
|
||||
this.newSimulation();
|
||||
}
|
||||
const location = this.apiBase + this.apiPrefix + `/simulations/${sim}`;
|
||||
fetch( location )
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
if( data && data.name ) {
|
||||
this.simulationNew = false;
|
||||
let keys = Object.keys(this.form);
|
||||
keys.forEach( (k) => {
|
||||
console.log(data[k])
|
||||
if( data[k] !== undefined )
|
||||
this.form[k] = data[k];
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveSimulation: function() {
|
||||
let id = this.simulation ? this.simulation : this.form.name;
|
||||
if( !id ) {
|
||||
console.log('Missing simulation name');
|
||||
return;
|
||||
}
|
||||
this.apiPost( `/simulations/${id}`, this.form )
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
if( response.name ) {
|
||||
if(this.simulationNew) {
|
||||
this.simulationNew = false;
|
||||
this.simulations.append(response.name);
|
||||
this.simulation = response.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveCA: function() {
|
||||
console.log(JSON.stringify(this.casForm));
|
||||
// block if name exists
|
||||
if( this.cas.includes( this.casForm.name ) ) {
|
||||
console.log(`CA name ${this.casForm.name} already exists!`);
|
||||
return false;
|
||||
}
|
||||
// basic validation
|
||||
if( this.casForm.name && this.casForm.key && this.casForm.cert ) {
|
||||
this.apiPost( `/cas/${this.casForm.name}`, this.casForm )
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
this.resetCasForm();
|
||||
this.getCas();
|
||||
});
|
||||
} else {
|
||||
console.log(`Missing parameters`);
|
||||
}
|
||||
},
|
||||
|
||||
resetCasForm: function() {
|
||||
this.resetProps( this.casForm );
|
||||
this.$refs.keyFile.value = null;
|
||||
this.$refs.certFile.value = null;
|
||||
},
|
||||
|
||||
// simple helper to reset props of an object
|
||||
resetProps: function( obj, val ) {
|
||||
if( val === undefined ) val = null;
|
||||
Object.keys( obj ).forEach( k => {
|
||||
obj[k] = null;
|
||||
} );
|
||||
},
|
||||
|
||||
// prepare assets
|
||||
sendAction: function( action ) {
|
||||
// action requires sim to be created
|
||||
if( !this.simulation ) {
|
||||
console.log('Must select a simulation');
|
||||
return;
|
||||
}
|
||||
// allowed actions
|
||||
const actions = [ 'prepare', 'push', 'start', 'stop' ];
|
||||
if( !actions.includes(action) ) {
|
||||
console.log(`Invalid action ${action}`);
|
||||
return;
|
||||
}
|
||||
let payload = {
|
||||
"action": action,
|
||||
"simulation": this.simulation,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "stagger",
|
||||
"value": "10/2000"
|
||||
}
|
||||
]
|
||||
};
|
||||
this.apiPost(`/actions`, payload)
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
});
|
||||
},
|
||||
|
||||
// basic helper to read files
|
||||
// event, name is the key to store data after in casForm
|
||||
loadText( event, name ) {
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
console.log(event.target.result);
|
||||
this.casForm[name] = event.target.result;
|
||||
}
|
||||
reader.readAsText(file);
|
||||
},
|
||||
|
||||
// generic get
|
||||
apiGet: async function( path ) {
|
||||
const url = `${this.apiBase}${this.apiPrefix}${path}?format=detailed`;
|
||||
const response = await fetch( url );
|
||||
return {
|
||||
status: response.status,
|
||||
data: await response.json()
|
||||
}
|
||||
},
|
||||
|
||||
apiPost: async function( path, payload ) {
|
||||
const url = `${this.apiBase}${this.apiPrefix}${path}`;
|
||||
const response = await fetch( url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify( payload )
|
||||
} );
|
||||
return {
|
||||
status: response.status,
|
||||
data: await response.json()
|
||||
}
|
||||
},
|
||||
|
||||
fakeStream: function() {
|
||||
if( this.streamInterval ) {
|
||||
console.log('Stopping fake stream...');
|
||||
clearInterval( this.streamInterval );
|
||||
this.streamOn = false;
|
||||
} else {
|
||||
console.log('Starting fake stream...');
|
||||
this.streamInterval = setInterval(() => {
|
||||
if( this.streamIndex + 1 >= window.tmpStats.length ) {
|
||||
this.streamIndex = 0;
|
||||
}
|
||||
this.testLogKernel(window.tmpStats[ this.streamIndex ]);
|
||||
this.streamIndex++;
|
||||
this.streamTimeSince = this.streamIntervalTime * this.streamIndex;
|
||||
}, this.streamIntervalTime);
|
||||
this.streamOn = true;
|
||||
}
|
||||
},
|
||||
|
||||
testLogKernel: function( data ) {
|
||||
let idx = null;
|
||||
if( !data.node ) {
|
||||
return;
|
||||
}
|
||||
if( data.node in this.nodesDict ) {
|
||||
idx = this.nodesDict[data.node];
|
||||
} else {
|
||||
idx = this.nodes.length;
|
||||
this.nodesData.push([]);
|
||||
this.nodesDict[data.node] = idx;
|
||||
// canvas for chart
|
||||
const el = document.createElement('canvas');
|
||||
el.setAttribute('id', `chart-${idx}`);
|
||||
const charts = document.getElementById('charts');
|
||||
charts.appendChild(el);
|
||||
const chart = new Chart(el, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: data.node,
|
||||
data: this.nodesData[idx],
|
||||
}]
|
||||
},
|
||||
});
|
||||
this.charts.push( chart );
|
||||
}
|
||||
if( data.value && data.value.kernel_utilization ) {
|
||||
console.log(data.value.kernel_utilization);
|
||||
this.charts[idx].data.datasets[0].data.push(data.value.kernel_utilization);
|
||||
this.charts[idx].data.labels.push( this.streamTimeSince );
|
||||
this.charts[idx].update();
|
||||
} else {
|
||||
console.log('No kernel utilization');
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
computed: {
|
||||
fakeStreamLabel: function() {
|
||||
return this.streamOn ? 'Stop Fake Stream' : 'Start Fake Stream'
|
||||
},
|
||||
nodesNodes: function() {
|
||||
if( this.nodes ) {
|
||||
return this.nodes.filter( n => n.type === 'node' ).map( n => n.node );
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Vue.createApp(App).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<link rel="icon" href="assets/favicon.ico" type="image/x-icon" />
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -8,245 +8,593 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
|
||||
|
||||
<style>
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
|
||||
}
|
||||
.sidebar {
|
||||
background: #e9ecef;
|
||||
}
|
||||
.output {
|
||||
background: var(--bs-light);
|
||||
white-space: pre;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.controls {
|
||||
background: #e9ecef;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(144px, 320px) 1fr 1.5fr;
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
.actions {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(96px, 50%);
|
||||
grid-gap: 0.5rem;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 40% 60%;
|
||||
grid-gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.table-sm {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.output {
|
||||
background: var(--bs-light);
|
||||
white-space: pre;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="sidebar p-3">
|
||||
<h1 class="h3">OWLS test</h1>
|
||||
<p><em>*open console for more output</em></p>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="controls p-3">
|
||||
|
||||
<div class="border border-secondary border-1 p-3 mb-3">
|
||||
<div class="col">
|
||||
<h1 class="h3">^(OvO)^ OWLS</h1>
|
||||
|
||||
<h2 class="h5">API</h2>
|
||||
<label class="form-label" for="apiBase">API base</label>
|
||||
<input type="text" name="apiBase" v-model="apiBase" class="form-control" />
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/nodes')">GET /nodes</button>
|
||||
</div>
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/ouis')">GET /OUIs</button>
|
||||
</div>
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/vendors')">GET /vendors</button>
|
||||
</div>
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/cas')">GET /cas</button>
|
||||
</div>
|
||||
<label class="form-label" for="apiBase">API base</label>
|
||||
<div class="d-flex mb-3">
|
||||
<input type="text" name="apiBase" v-model="apiBase" class="form-control form-control-sm w-75" />
|
||||
<button class="btn btn-sm btn-secondary w-25" v-on:click="setup()">Set</button>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="loadImportCasForm()">Import CA</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="newSimulation()">Create Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('prepare')">Prepare Assets</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('push')">Push Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('start')">Start Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="sendAction('stop')">Stop Simulation</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="setupWebsocket()">Start WebSocket</button>
|
||||
<button class="btn btn-sm btn-secondary"
|
||||
v-on:click="closeWebsocket()">Stop WebSocket</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
|
||||
<div v-if="importCA">
|
||||
|
||||
<h2 class="h5">Import CA</h2>
|
||||
|
||||
<fieldset class="form-grid border border-secondary p-3">
|
||||
|
||||
<label class="form-label">Name</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="casForm.name"/></div>
|
||||
|
||||
<label class="form-label">Key File</label>
|
||||
<div>
|
||||
<input class="form-control form-control-sm" type="file" ref="keyFile" @change="loadText($event, 'key')">
|
||||
</div>
|
||||
|
||||
<label class="form-label">Cert File</label>
|
||||
<div>
|
||||
<input class="form-control form-control-sm" type="file" ref="certFile" @change="loadText($event, 'cert')">
|
||||
</div>
|
||||
|
||||
<label class="form-label">Password</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="casForm.password"/></div>
|
||||
|
||||
<div></div>
|
||||
<div>
|
||||
<button class="btn btn-secondary btn-sm" v-on:click="saveCA()">Import CA</button>
|
||||
<button class="btn btn-light btn-sm" v-on:click="loadImportCasForm()">Cancel</button>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
<h2 class="h5">Simulation</h2>
|
||||
|
||||
<fieldset class="form-grid p-3">
|
||||
|
||||
<label class="form-label">Current Simulation</label>
|
||||
<div><select class="form-select form-select-sm" v-model="simulation" v-on:change="selectSimulation()">
|
||||
<option value=""></option>
|
||||
<option v-for="(simulation, index) in simulations" :key="index" v-bind:value="simulation">{{ simulation }}</option>
|
||||
</select></div>
|
||||
|
||||
</fieldset>
|
||||
<fieldset class="form-grid border border-secondary p-3">
|
||||
|
||||
<label class="form-label">Name</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="form.name"/></div>
|
||||
|
||||
<label class="form-label">CA</label>
|
||||
<div><select class="form-select form-select-sm" v-model="form.caname"><option v-for="(ca, index) in cas" :key="index" v-bind:value="ca">{{ ca }}</option></select></div>
|
||||
|
||||
<label class="form-label">Number of Devices</label>
|
||||
<div><input type="number" class="form-control form-control-sm" v-model="form.num_devices" /></div>
|
||||
|
||||
<label class="form-label">Nodes</label>
|
||||
<div class="d-grid">
|
||||
<label class="" v-for="(node, index) in nodesNodes" :key="index">
|
||||
<input type="checkbox" v-model="form.nodes" :value="node" /> {{ node }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="form-label">Server Name</label>
|
||||
<div><input type="text" class="form-control form-control-sm" v-model="form.server"/></div>
|
||||
|
||||
<label class="form-label">Port</label>
|
||||
<div><input type="number" class="form-control form-control-sm" v-model="form.port" /></div>
|
||||
|
||||
<button class="btn btn-secondary btn-sm" v-on:click="saveSimulation()">Save</button>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="border border-secondary border-1 p-3 mb-3">
|
||||
|
||||
<h2 class="h5">Websocket</h2>
|
||||
<label class="form-label" for="wss">WebSocket URL</label>
|
||||
<input type="text" name="wss" v-model="wss" class="form-control" />
|
||||
<div class="mt-2">
|
||||
<button v-on:click="setupWebsocket()" class="btn btn-primary">Start</button>
|
||||
<button class="btn btn-light mw-2" v-on:click="closeWebsocket()" v-show="socketOpen">Close</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" v-if="socketOpen">
|
||||
<input type="text" class="form-control w-auto" name="msg" v-model="msg" />
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="send()">Send Message</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 bg-warning p-2">
|
||||
<button class="btn btn-sm btn-light" v-on:click="fakeStream()">{{ fakeStreamLabel }}</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
|
||||
<h2 class="h5">Nodes</h2>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node Name</th>
|
||||
<th>Total</th>
|
||||
<th>Allocated</th>
|
||||
<th>Biggest</th>
|
||||
<th>Procs</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(node, index) in nodes" :key="index">
|
||||
<td>{{ node.node }}</td>
|
||||
<td>{{ node.total }}</td>
|
||||
<td>{{ node.allocated }}</td>
|
||||
<td>{{ node.worst }}</td>
|
||||
<td>{{ node.processes }}</td>
|
||||
<td>{{ node.type }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-3">
|
||||
|
||||
<div id="charts"></div>
|
||||
|
||||
<h2 class="h3">Output</h2>
|
||||
<div class="output">
|
||||
<code v-for="(msg, index) in output" :key="index">{{ msg }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
start of scripts
|
||||
-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha256-t9UJPrESBeG2ojKTIcFLPGF7nHi2vEc7f5A2KpH/UBU=" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/vue@3.0.4/dist/vue.global.js"></script>
|
||||
<!-- temporary stats for stream testing -->
|
||||
<script src="_tmp-stats.js"></script>
|
||||
<script>
|
||||
const App = {
|
||||
<div class="p-3">
|
||||
<div id="charts"></div>
|
||||
<h2 class="h3">Output</h2>
|
||||
<div class="output">
|
||||
<code v-for="(msg, index) in output" :key="index">{{ msg }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
start of scripts
|
||||
-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha256-t9UJPrESBeG2ojKTIcFLPGF7nHi2vEc7f5A2KpH/UBU=" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/vue@3.0.4/dist/vue.global.js"></script>
|
||||
<!-- temporary stats for stream testing -->
|
||||
<script src="_tmp-stats.js"></script>
|
||||
<script>
|
||||
const App = {
|
||||
data() {
|
||||
return {
|
||||
apiBase: 'http://localhost:9090',
|
||||
output: [],
|
||||
socket: null,
|
||||
socketOpen: false,
|
||||
wss: 'ws://localhost:9090/ws',
|
||||
msg: 'Hello World',
|
||||
nodesDict: {},
|
||||
nodes: [],
|
||||
charts: [],
|
||||
streamInterval: null,
|
||||
streamIntervalTime: 250,
|
||||
streamTimeSince: 0,
|
||||
streamOn: false,
|
||||
streamIndex: 0,
|
||||
}
|
||||
return {
|
||||
apiBase: 'http://localhost:9090',
|
||||
apiPrefix: '/api/v1',
|
||||
output: [],
|
||||
socket: null,
|
||||
socketOpen: false,
|
||||
wss: 'ws://localhost:9090/ws',
|
||||
msg: 'Hello World',
|
||||
importCA: false,
|
||||
casForm: {
|
||||
name: null,
|
||||
key: null,
|
||||
cert: null,
|
||||
password: null,
|
||||
},
|
||||
nodes: null,
|
||||
cas: [],
|
||||
simulations: [],
|
||||
simulationNew: false,
|
||||
simulation: null,
|
||||
form: {
|
||||
name: null,
|
||||
caname: null,
|
||||
num_devices: null,
|
||||
server: null,
|
||||
port: null,
|
||||
nodes: null,
|
||||
},
|
||||
// stream suff
|
||||
nodesDict: {},
|
||||
nodesData: [],
|
||||
num_charts: 0,
|
||||
charts: [],
|
||||
streamInterval: null,
|
||||
streamIntervalTime: 250,
|
||||
streamTimeSince: 0,
|
||||
streamOn: false,
|
||||
streamIndex: 0,
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
console.log('App loaded')
|
||||
console.log('App loaded');
|
||||
|
||||
// apiBase
|
||||
// check localStorage
|
||||
if( localStorage.getItem('apiBase') ) {
|
||||
this.apiBase = localStorage.getItem('apiBase');
|
||||
} else if( window.location.protocol !== 'file:' ) {
|
||||
this.apiBase = `${window.location.protocol}//${window.location.host}`;
|
||||
}
|
||||
|
||||
// use a set for nodes
|
||||
this.nodes = new Set();
|
||||
|
||||
this.setup();
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
setupWebsocket: function() {
|
||||
if( !this.wss ) {
|
||||
console.log('Missing websocket URL');
|
||||
return false;
|
||||
}
|
||||
setup: function() {
|
||||
// save URL for future use
|
||||
localStorage.setItem('apiBase', this.apiBase);
|
||||
|
||||
console.log('Starting websocket...');
|
||||
this.socket = new WebSocket(this.wss);
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
console.log(event);
|
||||
this.output = [];
|
||||
// try to pull data into a basic chart
|
||||
if( event.data && event.data.value ) {
|
||||
this.testLogKernel( event.data );
|
||||
} else {
|
||||
this.output.push(JSON.parse(event.data));
|
||||
}
|
||||
}
|
||||
ApiURL = new URL(this.apiBase);
|
||||
this.wss = "ws://" + ApiURL.hostname + ":" + ApiURL.port + "/ws";
|
||||
this.getCas();
|
||||
this.getNodes();
|
||||
this.getSimulations();
|
||||
},
|
||||
|
||||
this.socket.onopen = (event) => {
|
||||
console.log(event);
|
||||
this.output.push('Websocket opened');
|
||||
this.socketOpen = true;
|
||||
}
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
this.output.push('Websocket closed');
|
||||
this.socketOpen = false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
closeWebsocket: function() {
|
||||
if( this.socket )
|
||||
this.socket.close();
|
||||
},
|
||||
|
||||
// send a msg through the socket...
|
||||
send: function() {
|
||||
console.log(`Sending message '${this.msg}'...`);
|
||||
// should check if socket is good before
|
||||
this.socket.send(this.msg)
|
||||
},
|
||||
|
||||
// generic get
|
||||
apiGet: function(path) {
|
||||
const location = this.apiBase + path + "?details=1";
|
||||
fetch( location )
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.output = [];
|
||||
this.output.push(data);
|
||||
});
|
||||
},
|
||||
|
||||
fakeStream: function() {
|
||||
if( this.streamInterval ) {
|
||||
console.log('Stopping fake stream...');
|
||||
clearInterval( this.streamInterval );
|
||||
this.streamOn = false;
|
||||
} else {
|
||||
console.log('Starting fake stream...');
|
||||
this.streamInterval = setInterval(() => {
|
||||
if( this.streamIndex + 1 >= window.tmpStats.length ) {
|
||||
this.streamIndex = 0;
|
||||
setupWebsocket: function() {
|
||||
if( !this.wss ) {
|
||||
console.log('Missing websocket URL');
|
||||
return false;
|
||||
}
|
||||
this.testLogKernel(window.tmpStats[ this.streamIndex ]);
|
||||
this.streamIndex++;
|
||||
this.streamTimeSince = this.streamIntervalTime * this.streamIndex;
|
||||
}, this.streamIntervalTime);
|
||||
this.streamOn = true;
|
||||
}
|
||||
},
|
||||
|
||||
testLogKernel: function( data ) {
|
||||
let idx = null;
|
||||
if( !data.node ) {
|
||||
return;
|
||||
}
|
||||
if( data.node in this.nodesDict ) {
|
||||
idx = this.nodesDict[data.node];
|
||||
} else {
|
||||
idx = this.nodes.length;
|
||||
this.nodes.push([]);
|
||||
this.nodesDict[data.node] = idx;
|
||||
// canvas for chart
|
||||
const el = document.createElement('canvas');
|
||||
el.setAttribute('id', `chart-${idx}`);
|
||||
const charts = document.getElementById('charts');
|
||||
charts.appendChild(el);
|
||||
const chart = new Chart(el, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: data.node,
|
||||
data: this.nodes[idx],
|
||||
}]
|
||||
},
|
||||
});
|
||||
this.charts.push( chart );
|
||||
}
|
||||
if( data.value && data.value.kernel_utilization ) {
|
||||
console.log(data.value.kernel_utilization);
|
||||
this.charts[idx].data.datasets[0].data.push(data.value.kernel_utilization);
|
||||
this.charts[idx].data.labels.push( this.streamTimeSince );
|
||||
this.charts[idx].update();
|
||||
} else {
|
||||
console.log('No kernel utilization');
|
||||
}
|
||||
},
|
||||
console.log('Starting websocket...');
|
||||
this.socket = new WebSocket(this.wss);
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
console.log(event);
|
||||
// try to pull data into a basic chart
|
||||
// this.output.push(event.data.length);
|
||||
// this.output.push(event.data);
|
||||
const Payload = JSON.parse(event.data);
|
||||
if (Payload.name === 'os_details') {
|
||||
// this.output.push('Valid report');
|
||||
this.testLogKernel(Payload);
|
||||
} else {
|
||||
// this.output=[];
|
||||
// this.output.push(Payload);
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.onopen = (event) => {
|
||||
console.log(event);
|
||||
this.output.push('Websocket opened');
|
||||
this.socketOpen = true;
|
||||
}
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
this.output.push('Websocket closed');
|
||||
this.socketOpen = false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
closeWebsocket: function() {
|
||||
if( this.socket )
|
||||
this.socket.close();
|
||||
},
|
||||
|
||||
// send a msg through the socket...
|
||||
send: function() {
|
||||
console.log(`Sending message '${this.msg}'...`);
|
||||
// should check if socket is good before
|
||||
this.socket.send(this.msg)
|
||||
},
|
||||
|
||||
loadImportCasForm: function() {
|
||||
this.importCA = !this.importCA;
|
||||
},
|
||||
|
||||
getCas: function() {
|
||||
this.cas = [];
|
||||
this.apiGet('/cas')
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
if( data.Data && data.Data.CAs ) {
|
||||
this.cas = data.Data.CAs;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getNodes: function() {
|
||||
this.nodes = [];
|
||||
this.apiGet('/nodes')
|
||||
.then((response) => {
|
||||
const data = response.data;
|
||||
if(data.Data && data.Data.HardwareDefinitions) {
|
||||
// expecting array of objects
|
||||
this.nodes = data.Data.HardwareDefinitions[0];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getSimulations: function() {
|
||||
const location = `${this.apiBase}${this.apiPrefix}/simulations`;
|
||||
fetch( location )
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.simulations = data.Data.Simulations;
|
||||
this.simulation = this.simulations[0];
|
||||
this.selectSimulation();
|
||||
});
|
||||
},
|
||||
|
||||
// clear form for empty mode
|
||||
newSimulation() {
|
||||
this.simulation = '';
|
||||
this.simulationNew = true;
|
||||
this.resetProps( this.form );
|
||||
},
|
||||
|
||||
selectSimulation() {
|
||||
const sim = this.simulation;
|
||||
// if user selects empty, it's a "new" sim
|
||||
if( !sim ) {
|
||||
this.newSimulation();
|
||||
}
|
||||
const location = this.apiBase + this.apiPrefix + `/simulations/${sim}`;
|
||||
fetch( location )
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
if( data && data.name ) {
|
||||
this.simulationNew = false;
|
||||
let keys = Object.keys(this.form);
|
||||
keys.forEach( (k) => {
|
||||
console.log(data[k])
|
||||
if( data[k] !== undefined )
|
||||
this.form[k] = data[k];
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveSimulation: function() {
|
||||
let id = this.simulation ? this.simulation : this.form.name;
|
||||
if( !id ) {
|
||||
console.log('Missing simulation name');
|
||||
return;
|
||||
}
|
||||
this.apiPost( `/simulations/${id}`, this.form )
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
if( response.name ) {
|
||||
if(this.simulationNew) {
|
||||
this.simulationNew = false;
|
||||
this.simulations.append(response.name);
|
||||
this.simulation = response.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveCA: function() {
|
||||
console.log(JSON.stringify(this.casForm));
|
||||
// block if name exists
|
||||
if( this.cas.includes( this.casForm.name ) ) {
|
||||
console.log(`CA name ${this.casForm.name} already exists!`);
|
||||
return false;
|
||||
}
|
||||
// basic validation
|
||||
if( this.casForm.name && this.casForm.key && this.casForm.cert ) {
|
||||
this.apiPost( `/cas/${this.casForm.name}`, this.casForm )
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
this.resetCasForm();
|
||||
this.getCas();
|
||||
});
|
||||
} else {
|
||||
console.log(`Missing parameters`);
|
||||
}
|
||||
},
|
||||
|
||||
resetCasForm: function() {
|
||||
this.resetProps( this.casForm );
|
||||
this.$refs.keyFile.value = null;
|
||||
this.$refs.certFile.value = null;
|
||||
},
|
||||
|
||||
// simple helper to reset props of an object
|
||||
resetProps: function( obj, val ) {
|
||||
if( val === undefined ) val = null;
|
||||
Object.keys( obj ).forEach( k => {
|
||||
obj[k] = null;
|
||||
} );
|
||||
},
|
||||
|
||||
// prepare assets
|
||||
sendAction: function( action ) {
|
||||
// action requires sim to be created
|
||||
if( !this.simulation ) {
|
||||
console.log('Must select a simulation');
|
||||
return;
|
||||
}
|
||||
// allowed actions
|
||||
const actions = [ 'prepare', 'push', 'start', 'stop' ];
|
||||
if( !actions.includes(action) ) {
|
||||
console.log(`Invalid action ${action}`);
|
||||
return;
|
||||
}
|
||||
let payload = {
|
||||
"action": action,
|
||||
"simulation": this.simulation,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "stagger",
|
||||
"value": "10/2000"
|
||||
}
|
||||
]
|
||||
};
|
||||
this.apiPost(`/actions`, payload)
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
});
|
||||
},
|
||||
|
||||
// basic helper to read files
|
||||
// event, name is the key to store data after in casForm
|
||||
loadText( event, name ) {
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
console.log(event.target.result);
|
||||
this.casForm[name] = event.target.result;
|
||||
}
|
||||
reader.readAsText(file);
|
||||
},
|
||||
|
||||
// generic get
|
||||
apiGet: async function( path ) {
|
||||
const url = `${this.apiBase}${this.apiPrefix}${path}?format=detailed`;
|
||||
const response = await fetch( url );
|
||||
return {
|
||||
status: response.status,
|
||||
data: await response.json()
|
||||
}
|
||||
},
|
||||
|
||||
apiPost: async function( path, payload ) {
|
||||
const url = `${this.apiBase}${this.apiPrefix}${path}`;
|
||||
const response = await fetch( url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify( payload )
|
||||
} );
|
||||
return {
|
||||
status: response.status,
|
||||
data: await response.json()
|
||||
}
|
||||
},
|
||||
|
||||
fakeStream: function() {
|
||||
if( this.streamInterval ) {
|
||||
console.log('Stopping fake stream...');
|
||||
clearInterval( this.streamInterval );
|
||||
this.streamOn = false;
|
||||
} else {
|
||||
console.log('Starting fake stream...');
|
||||
this.streamInterval = setInterval(() => {
|
||||
if( this.streamIndex + 1 >= window.tmpStats.length ) {
|
||||
this.streamIndex = 0;
|
||||
}
|
||||
this.testLogKernel(window.tmpStats[ this.streamIndex ]);
|
||||
this.streamIndex++;
|
||||
this.streamTimeSince = this.streamIntervalTime * this.streamIndex;
|
||||
}, this.streamIntervalTime);
|
||||
this.streamOn = true;
|
||||
}
|
||||
},
|
||||
|
||||
testLogKernel: function( data ) {
|
||||
let idx = null;
|
||||
if( !data.node ) {
|
||||
// this.output.push('Bad node');
|
||||
return;
|
||||
}
|
||||
if( data.node in this.nodesDict ) {
|
||||
// this.output.push("Node found");
|
||||
idx = this.nodesDict[data.node];
|
||||
} else {
|
||||
// this.output.push("New Node");
|
||||
idx = this.num_charts;
|
||||
this.nodesData.push([]);
|
||||
this.nodesDict[data.node] = idx;
|
||||
// canvas for chart
|
||||
const el = document.createElement('canvas');
|
||||
el.setAttribute('id', `chart-${idx}`);
|
||||
const charts = document.getElementById('charts');
|
||||
const newDiv = document.createElement('div');
|
||||
newDiv.id = `chart-col-div-${idx}`;
|
||||
newDiv.className = 'col';
|
||||
newDiv.appendChild(el);
|
||||
charts.appendChild(newDiv);
|
||||
const chart = new Chart(el, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: data.node,
|
||||
data: this.nodesData[idx],
|
||||
}]
|
||||
},
|
||||
});
|
||||
this.charts[idx] = chart;
|
||||
this.num_charts = this.num_charts + 1;
|
||||
}
|
||||
if( data.data ) {
|
||||
console.log(data.data.kernel_utilization);
|
||||
// this.output.push(data.node);
|
||||
// this.output.push(data.data.kernel_utilization);
|
||||
this.charts[idx].data.datasets[0].data.push(data.data.kernel_utilization);
|
||||
this.charts[idx].data.labels.push( this.streamTimeSince );
|
||||
this.charts[idx].update();
|
||||
} else {
|
||||
console.log('No kernel utilization');
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
computed: {
|
||||
fakeStreamLabel: function() {
|
||||
return this.streamOn ? 'Stop Fake Stream' : 'Start Fake Stream'
|
||||
}
|
||||
fakeStreamLabel: function() {
|
||||
return this.streamOn ? 'Stop Fake Stream' : 'Start Fake Stream'
|
||||
},
|
||||
nodesNodes: function() {
|
||||
if( this.nodes ) {
|
||||
return this.nodes.filter( n => n.type === 'node' ).map( n => n.node );
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Vue.createApp(App).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
Vue.createApp(App).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
252
priv/www/old-index.html
Normal file
252
priv/www/old-index.html
Normal file
@@ -0,0 +1,252 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" href="assets/favicon.ico" type="image/x-icon" />
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>OWLS</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
|
||||
|
||||
<style>
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
|
||||
}
|
||||
.sidebar {
|
||||
background: #e9ecef;
|
||||
}
|
||||
.output {
|
||||
background: var(--bs-light);
|
||||
white-space: pre;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="sidebar p-3">
|
||||
<h1 class="h3">OWLS test</h1>
|
||||
<p><em>*open console for more output</em></p>
|
||||
|
||||
<div class="border border-secondary border-1 p-3 mb-3">
|
||||
|
||||
<h2 class="h5">API</h2>
|
||||
<label class="form-label" for="apiBase">API base</label>
|
||||
<input type="text" name="apiBase" v-model="apiBase" class="form-control" />
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/nodes')">GET /nodes</button>
|
||||
</div>
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/ouis')">GET /OUIs</button>
|
||||
</div>
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/vendors')">GET /vendors</button>
|
||||
</div>
|
||||
<div class="mt-2 d-flex flex-wrap">
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="apiGet('/api/v1/cas')">GET /cas</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="border border-secondary border-1 p-3 mb-3">
|
||||
|
||||
<h2 class="h5">Websocket</h2>
|
||||
<label class="form-label" for="wss">WebSocket URL</label>
|
||||
<input type="text" name="wss" v-model="wss" class="form-control" />
|
||||
<div class="mt-2">
|
||||
<button v-on:click="setupWebsocket()" class="btn btn-primary">Start</button>
|
||||
<button class="btn btn-light mw-2" v-on:click="closeWebsocket()" v-show="socketOpen">Close</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" v-if="socketOpen">
|
||||
<input type="text" class="form-control w-auto" name="msg" v-model="msg" />
|
||||
<button class="btn btn-sm btn-secondary" v-on:click="send()">Send Message</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 bg-warning p-2">
|
||||
<button class="btn btn-sm btn-light" v-on:click="fakeStream()">{{ fakeStreamLabel }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-3">
|
||||
|
||||
<div id="charts"></div>
|
||||
|
||||
<h2 class="h3">Output</h2>
|
||||
<div class="output">
|
||||
<code v-for="(msg, index) in output" :key="index">{{ msg }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
start of scripts
|
||||
-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha256-t9UJPrESBeG2ojKTIcFLPGF7nHi2vEc7f5A2KpH/UBU=" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/vue@3.0.4/dist/vue.global.js"></script>
|
||||
<!-- temporary stats for stream testing -->
|
||||
<script src="_tmp-stats.js"></script>
|
||||
<script>
|
||||
const App = {
|
||||
data() {
|
||||
return {
|
||||
apiBase: 'http://localhost:9090',
|
||||
output: [],
|
||||
socket: null,
|
||||
socketOpen: false,
|
||||
wss: 'ws://localhost:9090/ws',
|
||||
msg: 'Hello World',
|
||||
nodesDict: {},
|
||||
nodes: [],
|
||||
charts: [],
|
||||
streamInterval: null,
|
||||
streamIntervalTime: 250,
|
||||
streamTimeSince: 0,
|
||||
streamOn: false,
|
||||
streamIndex: 0,
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
console.log('App loaded')
|
||||
},
|
||||
methods: {
|
||||
|
||||
setupWebsocket: function() {
|
||||
if( !this.wss ) {
|
||||
console.log('Missing websocket URL');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Starting websocket...');
|
||||
this.socket = new WebSocket(this.wss);
|
||||
|
||||
this.socket.onmessage = (event) => {
|
||||
console.log(event);
|
||||
this.output = [];
|
||||
// try to pull data into a basic chart
|
||||
if( event.data && event.data.value ) {
|
||||
this.testLogKernel( event.data );
|
||||
} else {
|
||||
this.output.push(JSON.parse(event.data));
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.onopen = (event) => {
|
||||
console.log(event);
|
||||
this.output.push('Websocket opened');
|
||||
this.socketOpen = true;
|
||||
}
|
||||
|
||||
this.socket.onclose = (event) => {
|
||||
this.output.push('Websocket closed');
|
||||
this.socketOpen = false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
closeWebsocket: function() {
|
||||
if( this.socket )
|
||||
this.socket.close();
|
||||
},
|
||||
|
||||
// send a msg through the socket...
|
||||
send: function() {
|
||||
console.log(`Sending message '${this.msg}'...`);
|
||||
// should check if socket is good before
|
||||
this.socket.send(this.msg)
|
||||
},
|
||||
|
||||
// generic get
|
||||
apiGet: function(path) {
|
||||
const location = this.apiBase + path + "?details=1";
|
||||
fetch( location )
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
this.output = [];
|
||||
this.output.push(data);
|
||||
});
|
||||
},
|
||||
|
||||
fakeStream: function() {
|
||||
if( this.streamInterval ) {
|
||||
console.log('Stopping fake stream...');
|
||||
clearInterval( this.streamInterval );
|
||||
this.streamOn = false;
|
||||
} else {
|
||||
console.log('Starting fake stream...');
|
||||
this.streamInterval = setInterval(() => {
|
||||
if( this.streamIndex + 1 >= window.tmpStats.length ) {
|
||||
this.streamIndex = 0;
|
||||
}
|
||||
this.testLogKernel(window.tmpStats[ this.streamIndex ]);
|
||||
this.streamIndex++;
|
||||
this.streamTimeSince = this.streamIntervalTime * this.streamIndex;
|
||||
}, this.streamIntervalTime);
|
||||
this.streamOn = true;
|
||||
}
|
||||
},
|
||||
|
||||
testLogKernel: function( data ) {
|
||||
let idx = null;
|
||||
if( !data.node ) {
|
||||
return;
|
||||
}
|
||||
if( data.node in this.nodesDict ) {
|
||||
idx = this.nodesDict[data.node];
|
||||
} else {
|
||||
idx = this.nodes.length;
|
||||
this.nodes.push([]);
|
||||
this.nodesDict[data.node] = idx;
|
||||
// canvas for chart
|
||||
const el = document.createElement('canvas');
|
||||
el.setAttribute('id', `chart-${idx}`);
|
||||
const charts = document.getElementById('charts');
|
||||
charts.appendChild(el);
|
||||
const chart = new Chart(el, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: data.node,
|
||||
data: this.nodes[idx],
|
||||
}]
|
||||
},
|
||||
});
|
||||
this.charts.push( chart );
|
||||
}
|
||||
if( data.value && data.value.kernel_utilization ) {
|
||||
console.log(data.value.kernel_utilization);
|
||||
this.charts[idx].data.datasets[0].data.push(data.value.kernel_utilization);
|
||||
this.charts[idx].data.labels.push( this.streamTimeSince );
|
||||
this.charts[idx].update();
|
||||
} else {
|
||||
console.log('No kernel utilization');
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
computed: {
|
||||
fakeStreamLabel: function() {
|
||||
return this.streamOn ? 'Stop Fake Stream' : 'Start Fake Stream'
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Vue.createApp(App).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user