add services with statsu server state and remove stage lights
This commit is contained in:
@@ -62,14 +62,14 @@
|
||||
</q-table>
|
||||
<RenameDialog
|
||||
dialogLabel="Rename Datapoint"
|
||||
width="400px"
|
||||
:width="400"
|
||||
button-ok-label="Rename"
|
||||
ref="renameDialog"
|
||||
/>
|
||||
<UpdateDialog width="400px" button-ok-label="Write" ref="updateDialog" />
|
||||
<UpdateDriver width="400px" ref="updateDriverDialog" />
|
||||
<UpdateDialog :width="400" button-ok-label="Write" ref="updateDialog" />
|
||||
<UpdateDriver :width="400" ref="updateDriverDialog" />
|
||||
<UpdateDatatype
|
||||
width="400px"
|
||||
:width="400"
|
||||
button-ok-label="Update"
|
||||
ref="updateDatatype"
|
||||
dialog-label="Update Datatype"
|
||||
|
@@ -101,13 +101,13 @@
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<RenameDatapoint :dialogLabel="label" width="700px" button-ok-label="Rename" ref="renameDialog" />
|
||||
<AddDialog :dialogLabel="label" width="750px" button-ok-label="Add" ref="addDialog" />
|
||||
<RemoveDialog :dialogLabel="label" width="350px" button-ok-label="Remove" ref="removeDialog" />
|
||||
<CopyDialog :dialogLabel="label" width="300px" button-ok-label="Copy" ref="copyDialog" />
|
||||
<RenameDatapoint :dialogLabel="label" :width="700" button-ok-label="Rename" ref="renameDialog" />
|
||||
<AddDialog :dialogLabel="label" :width="750" button-ok-label="Add" ref="addDialog" />
|
||||
<RemoveDialog :dialogLabel="label" :width="350" button-ok-label="Remove" ref="removeDialog" />
|
||||
<CopyDialog :dialogLabel="label" :width="300" button-ok-label="Copy" ref="copyDialog" />
|
||||
<UpdateDatapoint
|
||||
:dialogLabel="label"
|
||||
width="300px"
|
||||
:width="300"
|
||||
button-ok-label="Update"
|
||||
ref="datatypeDialog"
|
||||
/>
|
||||
|
@@ -136,12 +136,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '650px',
|
||||
type: Number,
|
||||
default: 650,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -121,12 +121,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px',
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -130,12 +130,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '480px',
|
||||
type: Number,
|
||||
default: 480,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -86,8 +86,8 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -128,12 +128,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px',
|
||||
type: Number,
|
||||
default: 400,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -119,12 +119,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '340px',
|
||||
type: Number,
|
||||
default: 340,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -126,12 +126,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '500px',
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -106,12 +106,12 @@ const props = defineProps({
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px',
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '350px',
|
||||
type: Number,
|
||||
default: 350,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
class="dialog-header row items-center justify-between bg-grey-1"
|
||||
v-touch-pan.mouse.prevent.stop="handlePan"
|
||||
>
|
||||
<div v-if="headerTitle" class="text-left text-bold text-caption q-mx-sm">
|
||||
<div class="text-left text-bold text-caption q-mx-sm">
|
||||
{{ headerTitle }}
|
||||
</div>
|
||||
<div class="row justify-end q-mx-sm">
|
||||
@@ -45,8 +45,8 @@ defineExpose({ open, close });
|
||||
|
||||
const props = defineProps({
|
||||
headerTitle: { type: String, default: '' },
|
||||
width: { type: String, default: '400' },
|
||||
height: { type: String, default: '250' },
|
||||
width: { type: Number, default: 400 },
|
||||
height: { type: Number, default: 250 },
|
||||
});
|
||||
|
||||
// Fullscreen toggle
|
||||
@@ -59,8 +59,8 @@ function minMax() {
|
||||
|
||||
// Position and Size
|
||||
const position = ref({ x: 0, y: 0 });
|
||||
const width = ref(parseInt(props.width));
|
||||
const height = ref(parseInt(props.height));
|
||||
const width = ref(props.width);
|
||||
const height = ref(props.height);
|
||||
|
||||
// Dragging (only from header)
|
||||
const handlePan = (details: { delta: { x: number; y: number } }) => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<DialogFrame ref="Dialog" width="300px" height="380px" header-title="Login">
|
||||
<DialogFrame ref="Dialog" :width="300" :height="380" header-title="Login">
|
||||
<div class="text-black"></div>
|
||||
<q-form ref="refForm">
|
||||
<q-item-section class="q-gutter-md q-pa-md">
|
||||
|
64
src/vueLib/models/Services.ts
Normal file
64
src/vueLib/models/Services.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { appApi } from 'src/boot/axios';
|
||||
//import { ref } from 'vue';
|
||||
|
||||
export interface Service {
|
||||
name: string;
|
||||
workingDirectory: string;
|
||||
executablePath: string;
|
||||
description: string;
|
||||
arguments: string[];
|
||||
state: State;
|
||||
stateColor: string;
|
||||
stateTextColor: string;
|
||||
}
|
||||
export type Services = Service[];
|
||||
export type State = 'Stopped' | 'Stopping' | 'Starting' | 'Running' | 'Error';
|
||||
|
||||
export function useServices() {
|
||||
async function initServices() {
|
||||
return appApi
|
||||
.get('services/load')
|
||||
.then((resp) => {
|
||||
const normalized = resp.data.services.map((item: Service) => {
|
||||
if (!item.state) {
|
||||
item.state = 'Stopped';
|
||||
}
|
||||
|
||||
let stateColor = 'grey-4';
|
||||
let stateTextColor = 'black';
|
||||
|
||||
switch (item.state) {
|
||||
case 'Running':
|
||||
stateColor = 'green';
|
||||
stateTextColor = 'white';
|
||||
break;
|
||||
case 'Starting':
|
||||
case 'Stopping':
|
||||
stateColor = 'green';
|
||||
stateTextColor = 'white';
|
||||
break;
|
||||
case 'Stopped':
|
||||
default:
|
||||
stateColor = 'grey-4';
|
||||
stateTextColor = 'black';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
stateColor,
|
||||
stateTextColor,
|
||||
};
|
||||
});
|
||||
|
||||
return normalized;
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
initServices,
|
||||
};
|
||||
}
|
@@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<DialogFrame ref="refDialog" width="500px" header-title="Add new Service">
|
||||
<DialogFrame ref="refDialog" :width="250" header-title="Add new Service">
|
||||
<div class="row justify-center">
|
||||
<q-select class="col-4" :options="opts" v-model="option"></q-select>
|
||||
<div>
|
||||
<div class="q-ma-md text-primary text-bold">Choose new service</div>
|
||||
<q-select dense filled class="col-4" :options="opts" v-model="option"></q-select>
|
||||
<div class="row justify-end">
|
||||
<q-btn class="q-mt-md" no-caps color="primary">Add Service</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <q-table :rows="driverRows"> </q-table> -->
|
||||
</DialogFrame>
|
||||
</template>
|
||||
|
||||
@@ -18,17 +23,17 @@ const { NotifyResponse } = useNotify();
|
||||
const refDialog = ref();
|
||||
const driverRows = ref([]);
|
||||
const opts = ref();
|
||||
const option = ref('Choose new service');
|
||||
const option = ref('');
|
||||
|
||||
interface conf {
|
||||
name: string;
|
||||
}
|
||||
function open() {
|
||||
appApi
|
||||
.get('/allDrivers')
|
||||
.get('services/all')
|
||||
.then((resp) => {
|
||||
driverRows.value = resp.data;
|
||||
opts.value = resp.data.map((item: conf) => item.name);
|
||||
opts.value = resp.data.services.map((item: conf) => item.name);
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'));
|
||||
refDialog.value.open();
|
||||
|
92
src/vueLib/services/dialog/SubMenu.vue
Normal file
92
src/vueLib/services/dialog/SubMenu.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<q-menu ref="refMenu" context-menu>
|
||||
<q-list>
|
||||
<q-item :disable="!startEnabled" clickable @click="start">
|
||||
<q-item-section>
|
||||
<q-icon :color="startEnabled ? 'green' : 'green-2'" name="play_arrow" size="sm" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item :disable="!stopEnabled" clickable @click="stop">
|
||||
<q-item-section>
|
||||
<q-icon :color="stopEnabled ? 'red' : 'red-2'" name="stop" size="sm" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item :disable="!restartEnabled" clickable>
|
||||
<q-item-section>
|
||||
<q-icon
|
||||
:color="restartEnabled ? 'orange' : 'orange-3'"
|
||||
name="restart_alt"
|
||||
size="sm"
|
||||
@click="console.log('restart')"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { appApi } from 'src/boot/axios';
|
||||
import type { Service } from 'src/vueLib/models/Services';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useNotify } from 'src/vueLib/general/useNotify';
|
||||
|
||||
const { NotifyResponse } = useNotify();
|
||||
|
||||
const refMenu = ref();
|
||||
const startEnabled = computed(() => localService.value?.state === 'Stopped' || 'Error');
|
||||
const stopEnabled = computed(() => localService.value?.state === 'Running');
|
||||
const restartEnabled = computed(() => localService.value?.state === 'Running');
|
||||
const localService = ref<Service>();
|
||||
|
||||
const open = (event: MouseEvent, service: Service) => {
|
||||
localService.value = service;
|
||||
refMenu.value?.show(event);
|
||||
};
|
||||
|
||||
function start() {
|
||||
if (!localService.value) {
|
||||
return;
|
||||
}
|
||||
localService.value.state = 'Starting';
|
||||
localService.value.stateColor = 'yellow';
|
||||
localService.value.stateTextColor = 'black';
|
||||
appApi
|
||||
.get(`/services/${localService.value.name}/start`)
|
||||
.then(() => {
|
||||
if (!localService.value) {
|
||||
return;
|
||||
}
|
||||
localService.value.state = 'Running';
|
||||
localService.value.stateColor = 'green';
|
||||
localService.value.stateTextColor = 'white';
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
NotifyResponse(err, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (!localService.value) {
|
||||
return;
|
||||
}
|
||||
localService.value.state = 'Stopping';
|
||||
localService.value.stateColor = 'yellow';
|
||||
localService.value.stateTextColor = 'black';
|
||||
|
||||
appApi
|
||||
.get(`/services/${localService.value.name}/stop`)
|
||||
.then(() => {
|
||||
if (!localService.value) {
|
||||
return;
|
||||
}
|
||||
localService.value.state = 'Stopped';
|
||||
localService.value.stateColor = 'grey-4';
|
||||
localService.value.stateTextColor = 'black';
|
||||
})
|
||||
.catch((err) => NotifyResponse(err, 'error'));
|
||||
}
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
7
src/vueLib/services/statusServer/models.ts
Normal file
7
src/vueLib/services/statusServer/models.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type Payload = {
|
||||
action?: Action;
|
||||
topic: string;
|
||||
data: { state?: string; message?: string };
|
||||
};
|
||||
|
||||
export type Action = 'subscribe' | 'publish';
|
115
src/vueLib/services/statusServer/useStatusServer.ts
Normal file
115
src/vueLib/services/statusServer/useStatusServer.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { Response } from '../../models/Response';
|
||||
import type { QVueGlobals } from 'quasar';
|
||||
import { ref } from 'vue';
|
||||
import type { Payload } from './models';
|
||||
|
||||
let socket: WebSocket | null = null;
|
||||
|
||||
const isConnected = ref(false);
|
||||
|
||||
export function useStatusServer(
|
||||
host: string,
|
||||
port: number = 9500,
|
||||
path: string,
|
||||
id: string,
|
||||
callback: (payload: Payload) => void,
|
||||
$q?: QVueGlobals,
|
||||
) {
|
||||
const connect = () => {
|
||||
socket = new WebSocket(`ws://${host}:${port}/${path}?id=${id}`);
|
||||
|
||||
socket.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
isConnected.value = true;
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
isConnected.value = false;
|
||||
console.log('WebSocket disconnected');
|
||||
$q?.notify({
|
||||
message: 'WebSocket disconnected',
|
||||
color: 'orange',
|
||||
position: 'bottom-right',
|
||||
icon: 'warning',
|
||||
timeout: 10000,
|
||||
});
|
||||
};
|
||||
socket.onerror = (err) => {
|
||||
console.log(`WebSocket error: ${err.type}`);
|
||||
isConnected.value = false;
|
||||
$q?.notify({
|
||||
message: `WebSocket error: ${err.type}`,
|
||||
color: 'red',
|
||||
position: 'bottom-right',
|
||||
icon: 'error',
|
||||
timeout: 10000,
|
||||
});
|
||||
};
|
||||
socket.onmessage = (event) => {
|
||||
callback(JSON.parse(event.data));
|
||||
};
|
||||
};
|
||||
|
||||
function subscribe(topic: string): Promise<Response | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
waitForSocketConnection()
|
||||
.then(() => {
|
||||
socket?.send('{"action":"subscribe", "topic":"' + topic + '"}');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('WebSocket send failed:', err);
|
||||
resolve(undefined); // or reject(err) if strict failure is desired
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function publish(topic: string, data: unknown): Promise<Response | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
waitForSocketConnection()
|
||||
.then(() => {
|
||||
socket?.send(
|
||||
'{"action":"publish", "topic":"' + topic + '", "data":' + JSON.stringify(data) + '}',
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('WebSocket send failed:', err);
|
||||
resolve(undefined); // or reject(err) if strict failure is desired
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
if (socket) {
|
||||
socket.close();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
connect,
|
||||
close,
|
||||
subscribe,
|
||||
publish,
|
||||
};
|
||||
}
|
||||
|
||||
function waitForSocketConnection(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const maxWait = 5000; // timeout after 5 seconds
|
||||
const interval = 50;
|
||||
let waited = 0;
|
||||
|
||||
const check = () => {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
resolve();
|
||||
} else {
|
||||
waited += interval;
|
||||
if (waited >= maxWait) {
|
||||
reject(new Error('WebSocket connection timeout'));
|
||||
} else {
|
||||
setTimeout(check, interval);
|
||||
}
|
||||
}
|
||||
};
|
||||
check();
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user