Files
wlan-cloud-loadsim/priv/www/demo.html
2020-12-18 00:08:55 -08:00

447 lines
14 KiB
HTML

<!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;
}
.actions {
display: grid;
grid-template-columns: minmax(96px, 144px);
grid-gap: 0.5rem;
}
.form-grid {
display: grid;
grid-template-columns: 40% 60%;
grid-gap: 0.5rem;
font-size: 0.875rem;
}
.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 d-flex p-3">
<div class="col p-3">
<h1 class="h3">^(OvO)^ OWLS</h1>
<div class="actions">
<button class="btn btn-sm btn-secondary"
v-on:click="loadCas()">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">
<h2 class="h5">API</h2>
<fieldset class="form-grid p-3">
<label class="form-label" for="apiBase">API base</label>
<div class="d-flex">
<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>
<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">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 class="col p-3">
<p>Nodes</p>
<ul>
<li v-for="(node, index) in nodes">{{ node }}</li>
</ul>
</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',
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');
// default to server url
if( window.location.protocol !== 'file:' )
this.apiBase = `${window.location.protocol}//${window.location.host}`;
this.nodes = new Set();
this.setup();
},
methods: {
setup: function() {
this.loadCas();
this.loadNodes();
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)
},
loadCas: function() {
const location = `${this.apiBase}${this.apiPrefix}/cas`;
fetch( location )
.then(response => response.json())
.then(data => {
if( data.Data && data.Data.CAs ) {
this.cas = data.Data.CAs;
}
});
},
loadNodes: function() {
},
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;
Object.keys(this.form).forEach((k) => {
this.form[k] = null;
});
},
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];
});
// rudimentary way to load nodes (/nodes empty)
if(data.nodes) {
data.nodes.forEach( n => {
this.nodes.add(n);
});
}
}
});
},
saveSimulation: function() {
let id = this.simulation ? this.simulation : this.form.name;
if( !id ) {
console.log('Missing simulation name');
return;
}
// [temp] take first node if possible...
if( !this.form.nodes ) {
this.form.nodes = [this.nodes[0]];
}
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;
}
}
});
},
// 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);
});
},
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 response.json();
},
// 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.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'
},
},
};
Vue.createApp(App).mount('#app')
</script>
</body>
</html>