Introduction of JS based pages

This commit is contained in:
Ziver Koc 2024-03-15 00:17:15 +01:00
parent c4cb9ff458
commit 7d64788154
24 changed files with 24446 additions and 30 deletions

View file

@ -0,0 +1,39 @@
export default {
props: [
"level", // ERROR, WARNING, SUCCESS, INFO
"message"
],
data() {
return { }
},
computed: {
levelClass() {
switch(this.level) {
case "ERROR": return "alert-danger";
case "WARNING": return "alert-warning";
case "SUCCESS": return "alert-success";
case "INFO":
default: return "alert-info";
}
},
levelIcon() {
switch(this.level) {
case "ERROR": return "glyphicon-minus-sign";
case "WARNING": return "glyphicon-warning-sign";
case "SUCCESS": return "glyphicon-ok-circle";
case "INFO":
default: return "glyphicon-info-sign";
}
}
},
template: `
<div id="" data-alert-id="" class="alert alert-dismissible fade in" :class="levelClass">
<button type="button" class="close" data-dismiss="alert">
<span>&times;</span>
</button>
<span class="glyphicon" :class="levelIcon"></span>
<strong class="alert-title">{{message}}</strong> &nbsp;
<span class="alert-description"></span>
</div>
`
}

View file

@ -0,0 +1,29 @@
import Alert from 'AlertComponent'
import { alertStore } from 'AlertStore'
export default {
data() {
return {
alertStore
}
},
components: {
Alert,
},
mounted() {
alertStore.enableAutoLoad(true);
},
template: `
<div v-if="alertStore.loading" class="modal fade in" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="false">
<div class="modal-dialog modal-sm">
<div class="modal-content modal-body">
<i class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></i>
Loading...
</div>
</div>
</div>
<div id="alert-container">
<Alert v-for="a in alertStore.alerts" v-bind="a" />
</div>`
}

View file

@ -0,0 +1,17 @@
import AlertList from 'AlertListComponent'
export default {
data() {
return {
}
},
components: {
AlertList,
},
template: `
<div class="container col-md-12">
<AlertList />
<router-view></router-view>
</div>`
}

View file

@ -0,0 +1,35 @@
import {eventStore} from 'EventStore'
export default {
props: {
'id': {default: 0},
},
data() {
let internalEvent = eventStore.getEvent(this.id);
return {
event: internalEvent,
checkboxChecked: internalEvent.data?.valueStr == 'ON',
}
},
template: `
<form method="POST">
<input type="hidden" name="action" value="modify">
<input type="hidden" name="action-id" :value="event.id">
<div class="btn-toolbar pull-right">
<template v-if="event.dataType === 'ColorEventData'">
<input type="hidden" name="type" value="color">
<input type="color" name="data" onchange="this.form.submit()" :value="event.data?.valueStr">
</template>
<template v-else-if="event.dataType === 'LevelEventData'">
<input type="hidden" name="type" value="level">
<input class="styled-slider slider-progress" type="range" name="data" min="0" max="100" step="10" onchange="this.form.submit()" :value="event.data?.value * 100">
</template>
<template v-else-if="event.dataType === 'OnOffEventData'">
<input type="hidden" name="type" value="on-off">
<input class="switch" type="checkbox" name="data" onchange="this.form.submit()" v-model="checkboxChecked">
</template>
</div>
</form>
`
}

View file

@ -0,0 +1,88 @@
import {eventStore} from 'EventStore'
export default {
data() {
var id = this.$route.params.id;
return {
id: id,
event: eventStore.getEvent(id),
}
},
template: `
<h1 class="page-header">Details for <a href="?id={{event.id}}">{{event.name}}</a></h1>
<div class="col-md-5">
<div class="panel panel-default drop-shadow">
<div class="panel-heading">Configuration</div>
<div class="panel-body">
<table class="table table-hover table-condensed">
<thead>
<tr>
<th class="text-right">Event ID:</th>
<th>{{event.id}</th>
</tr>
<tr>
<th class="text-right">Name:</th>
<th>{{event.name}}</th>
</tr>
</thead>
<tr>
<th class="text-right">Type:</th>
<td>{{event.configType}}</td>
</tr>
<tr>
<th class="text-right">Owner:</th>
<td>{{event.owner}} <p></td>
</tr>
<tr>
<th class="text-right">State:</th>
<td>
<form method="POST">
<input type="hidden" name="action" value="modify">
<input type="hidden" name="action-id" value="{{event.id}}">
<div class="btn-toolbar pull-left">
<input class="toggle-switch" type="checkbox" name="enabled"
data-on-color="danger">
</div>
</form>
</td>
</tr>
<tr v-for="(confName, confValue) in event.config">
<th class="text-right">{{name}}:</th>
<td>{{confValue}}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="col-md-7">
<div class="panel panel-default drop-shadow">
<div class="panel-heading">History data</div>
<div class="panel-body">
<table class="table table-hover table-condensed">
<thead>
<th class="col-md-6">Timestamp</th>
<th class="col-md-2">Raw Data</th>
</thead>
<tr>
<td><span class="timestamp">{{event.data.timestamp}}</span></td>
<td>{{event.data.value}}</td>
</tr>
</table>
</div>
</div>
</div>
<script>
$(function (){
$(".toggle-switch").on("switchChange.bootstrapSwitch", function (event, state) {
$(this).closest('form').submit();
});
});
</script>
`
}

View file

@ -0,0 +1,21 @@
import EventTable from 'EventTableComponent'
export default {
props: {
},
components: {
EventTable,
},
template: `
<h1 class="page-header">Event Overview</h1>
<div class="col-md-12">
<div class="panel panel-default drop-shadow">
<div class="panel-heading">Local Events</div>
<div class="panel-body">
<EventTable />
</div>
</div>
</div>
`
}

View file

@ -0,0 +1,34 @@
import {eventStore} from 'EventStore'
import EventTableRow from 'EventTableRowComponent'
export default {
data() {
return {
eventStore,
}
},
components: {
EventTableRow,
},
mounted() {
eventStore.enableAutoLoad(true);
},
unmounted() {
eventStore.enableAutoLoad(false);
},
template: `
<table id="event-device-table2" class="table table-hover table-condensed">
<thead>
<tr>
<th class="col-md-4">Name</th>
<th class="col-md-3">Type</th>
<th class="col-md-1">Data</th>
<th class="col-md-2">Last Update</th>
<th class="col-md-2 text-right">Actions</th>
</tr>
<EventTableRow v-for="e in eventStore.events" :key="e.id" :id="e.id" />
</thead>
</table>
`
}

View file

@ -0,0 +1,29 @@
import {eventStore} from 'EventStore'
import EventAction from 'EventActionComponent'
export default {
props: {
'id': {default: 0},
},
data() {
let event = eventStore.getEvent(this.id)
return {
event: event,
timestamp: getRelTimestamp(event.data.timestamp)
}
},
components: {
EventAction
},
template: `
<tr :data-device-id="event.id">
<td><a :href="'?id=' + event.id">{{ event.name }}</a></td>
<td>{{ event.configType }}</td>
<td>{{ event.data.valueStr }}</td>
<td class="timestamp">{{ timestamp }}</td>
<td>
<EventAction :id="event.id"/>
</td>
</tr>
`
}

View file

@ -0,0 +1,46 @@
import { reactive } from 'vue'
export const alertStore = reactive({
loading: false,
alerts: [],
pollTimer: null,
enableAutoLoad(enabled = true) {
if (enabled && this.pollTimer !== null) {
this.pollTimer = setInterval(function() {
load();
}, 3000);
} else {
clearInterval(this.timer);
this.pollTimer == null;
}
},
load() {
fetch('/api/alert?action=poll')
.then(response => response.json())
.then(data => {
data.forEach(alert => {
alert.source = "server";
alertStore.alerts.push(alert);
});
});
},
setLoading(l = true) {
this.loading = l;
},
addAlertInfo(message) {
this.alerts.push({source: "local", level: "info", message: message});
},
addAlertSuccess(message) {
this.alerts.push({source: "local", level: "success", message: message});
},
addAlertWarning(message) {
this.alerts.push({source: "local", level: "warning", message: message});
},
AddAlertError(message) {
this.alerts.push({source: "local", level: "danger", message: message});
},
});

View file

@ -0,0 +1,49 @@
import { reactive } from 'vue'
import { alertStore } from 'AlertStore'
export const eventStore = reactive({
events: [],
pollTimerId: null,
pollInterval: 10000, // 10 sec
enableAutoLoad(enabled = true) {
if (enabled) {
if (this.pollTimerId !== null)
return; // Timer already initialized
eventStore.load();
this.pollTimerId = setInterval(function() {
eventStore.load();
}, this.pollInterval);
} else {
clearInterval(this.pollTimerId);
this.pollTimerId == null;
}
},
load() {
alertStore.setLoading(true);
fetch('/api/event', {
method: 'GET',
headers: {
'Accept': 'application/json',
},
})
.then(response => response.json())
.then(json => {
if (json['error'] != null) {
alertStore.alertError(json['error']);
return;
}
eventStore.events = json;
alertStore.setLoading(false);
})
},
getEvent(id) {
let event = eventStore.events.find(event => id == event.id);
return event;
}
});

View file

@ -0,0 +1,44 @@
import { reactive } from 'vue'
import { alertStore } from 'AlertStore'
export const sensorStore = reactive({
sensors: {},
pollTimerId: null,
pollInterval: 10000, // 10 sec
enableAutoLoad(enabled = true) {
if (enabled) {
if (this.pollTimerId !== null)
return; // Timer already initialized
sensorStore.load();
this.pollTimerId = setInterval(function() {
sensorStore.load();
}, this.pollInterval);
} else {
clearInterval(this.pollTimerId);
this.pollTimerId == null;
}
},
load() {
alertStore.setLoading(true);
fetch('/api/sensor', {
method: 'GET',
headers: {
'Accept': 'application/json',
},
})
.then(response => response.json())
.then(json => {
if (json['error'] != null) {
alertStore.alertError(json['error']);
return;
}
sensorStore.sensors = json;
alertStore.setLoading(false);
})
},
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long