parent
de1a2641f7
commit
f4da186fb7
@ -11,6 +11,7 @@ import {MyGPIO} from "./MyGPIO";
|
|||||||
import {ContainerHelper} from "./ContainerHelper";
|
import {ContainerHelper} from "./ContainerHelper";
|
||||||
import {Mixer} from "./Mixer";
|
import {Mixer} from "./Mixer";
|
||||||
import {ArduinoProxy} from "./ArduinoProxy";
|
import {ArduinoProxy} from "./ArduinoProxy";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
const log = debug("itender:server");
|
const log = debug("itender:server");
|
||||||
|
|
||||||
@ -18,6 +19,8 @@ const app = new App();
|
|||||||
const wsApp = new WebsocketApp();
|
const wsApp = new WebsocketApp();
|
||||||
|
|
||||||
|
|
||||||
|
global.appRoot = path.resolve(__dirname);
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
log("Starting...");
|
log("Starting...");
|
||||||
|
@ -18,6 +18,7 @@ import {ContainerHelper} from "../../ContainerHelper";
|
|||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import {promisify} from "util";
|
import {promisify} from "util";
|
||||||
import Drink from "../../database/Drink";
|
import Drink from "../../database/Drink";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
const exec = promisify(require('child_process').exec)
|
const exec = promisify(require('child_process').exec)
|
||||||
|
|
||||||
@ -59,8 +60,7 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
for (let c of data) {
|
for (let c of data) {
|
||||||
let container: IContainer | null = null;
|
let container: IContainer | null = null;
|
||||||
|
|
||||||
if( c.id )
|
if (c.id) {
|
||||||
{
|
|
||||||
container = await Container.findOne({_id: c.id});
|
container = await Container.findOne({_id: c.id});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +81,7 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let containers: IContainer[] = await Container.find();
|
let containers: IContainer[] = await Container.find();
|
||||||
for( let c of containers )
|
for (let c of containers) {
|
||||||
{
|
|
||||||
let find = data.find((e) => {
|
let find = data.find((e) => {
|
||||||
return c._id == e.id;
|
return c._id == e.id;
|
||||||
});
|
});
|
||||||
@ -215,8 +214,7 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
if (conf["arduino_proxy_enabled"]) {
|
if (conf["arduino_proxy_enabled"]) {
|
||||||
try {
|
try {
|
||||||
await ArduinoProxy.disconnect();
|
await ArduinoProxy.disconnect();
|
||||||
} catch( e )
|
} catch (e) {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -246,7 +244,7 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, content);
|
WebSocketHandler.answerRequest(msg.data["type"] as RequestType, content);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RequestType.TARE: {
|
case RequestType.TARE: {
|
||||||
@ -272,7 +270,6 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// { success: boolean, msg: string }
|
|
||||||
WebSocketHandler.answerRequest(type, {success: false, msg: e});
|
WebSocketHandler.answerRequest(type, {success: false, msg: e});
|
||||||
success = false;
|
success = false;
|
||||||
for (let t of timeouts)
|
for (let t of timeouts)
|
||||||
@ -305,16 +302,23 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, false);
|
return WebSocketHandler.answerRequest(msg.data["type"] as RequestType, false);
|
||||||
WebSocketHandler.answerRequest(msg.data["type"] as RequestType, true);
|
WebSocketHandler.answerRequest(msg.data["type"] as RequestType, true);
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result = await exec("/home/itender/itender/update.sh");
|
let result = await exec(path.join(global.appRoot, "/update.sh"));
|
||||||
if (result.stderr)
|
if (result.stderr)
|
||||||
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br>Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.<br>"));
|
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br>Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.<br>"));
|
||||||
} catch( e )
|
} catch (e ) {
|
||||||
{
|
console.error(e);
|
||||||
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br>Möglicherweise ist die Internetverbindung nicht ausreichend oder das Update enthält Fehler.<br>"));
|
let error = e as {code: number, killed: boolean, cmd: string};
|
||||||
}
|
|
||||||
|
|
||||||
|
let msg = "";
|
||||||
|
if(error.code == 127 )
|
||||||
|
msg = "Beim Ausführen ist ein unbekanntes Problem aufgetreten.";
|
||||||
|
else if ( error.code == 1 )
|
||||||
|
msg = "Die Internetverbindung ist nicht ausreichend, um iTender zu aktualisieren.";
|
||||||
|
|
||||||
|
await WebSocketHandler.send(new WebSocketPayload(WebSocketEvent.ERROR, "Der iTender konnte das Update nicht installieren.<br><br>" + msg ));
|
||||||
|
log("Could not execute update.sh" );
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,17 +329,14 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
net = nets["wlp0s20f3"];
|
net = nets["wlp0s20f3"];
|
||||||
let ipAddr: string = "";
|
let ipAddr: string = "";
|
||||||
if (net)
|
if (net)
|
||||||
for( let addr of net )
|
for (let addr of net) {
|
||||||
{
|
|
||||||
if (addr.family == "IPv4" && addr.address && addr.address !== "127.0.0.1")
|
if (addr.family == "IPv4" && addr.address && addr.address !== "127.0.0.1")
|
||||||
ipAddr = addr.address;
|
ipAddr = addr.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
let packageJson = require('../../../package.json');
|
let packageJson = require('../../../package.json');
|
||||||
|
|
||||||
let wifi = (await exec("iwgetid")).stdout
|
let wifi = (await exec("iwgetid")).stdout
|
||||||
|
|
||||||
|
|
||||||
let data = {
|
let data = {
|
||||||
"internet": iTender.internetConnection,
|
"internet": iTender.internetConnection,
|
||||||
"ip": ipAddr,
|
"ip": ipAddr,
|
||||||
@ -353,8 +354,7 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
case RequestType.CLEAR_DB: {
|
case RequestType.CLEAR_DB: {
|
||||||
await Drink.deleteMany({});
|
await Drink.deleteMany({});
|
||||||
await Ingredient.deleteMany({});
|
await Ingredient.deleteMany({});
|
||||||
for( let c of (await Container.find()) )
|
for (let c of (await Container.find())) {
|
||||||
{
|
|
||||||
c.content = undefined;
|
c.content = undefined;
|
||||||
c.save();
|
c.save();
|
||||||
}
|
}
|
||||||
@ -373,7 +373,6 @@ router.ws('/', async (ws, req, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
@ -8,9 +8,15 @@ export class Settings {
|
|||||||
// Settings Btns
|
// Settings Btns
|
||||||
const downloadDrinks = document.getElementById("settings_refreshDrinks") as HTMLButtonElement;
|
const downloadDrinks = document.getElementById("settings_refreshDrinks") as HTMLButtonElement;
|
||||||
downloadDrinks.onclick = () => this.onClickRefreshDrinks();
|
downloadDrinks.onclick = () => this.onClickRefreshDrinks();
|
||||||
|
|
||||||
|
const deleteDrinks = document.getElementById("settings_deleteDrinks") as HTMLButtonElement;
|
||||||
|
deleteDrinks.onclick = () => this.onClickDelete();
|
||||||
|
|
||||||
|
|
||||||
const getInfo = document.getElementById("settings_getInfo") as HTMLButtonElement;
|
const getInfo = document.getElementById("settings_getInfo") as HTMLButtonElement;
|
||||||
getInfo.onclick = () => this.onClickInfo();
|
getInfo.onclick = () => this.onClickInfo();
|
||||||
|
|
||||||
|
|
||||||
const reload = document.getElementById("settings_reload") as HTMLButtonElement;
|
const reload = document.getElementById("settings_reload") as HTMLButtonElement;
|
||||||
reload.onclick = () => window.location.reload();
|
reload.onclick = () => window.location.reload();
|
||||||
|
|
||||||
@ -74,8 +80,7 @@ export class Settings {
|
|||||||
WebWebSocketHandler.request(RequestType.UPDATE, null).then((payload) => {
|
WebWebSocketHandler.request(RequestType.UPDATE, null).then((payload) => {
|
||||||
let modal = new Modal("info", "System-Update");
|
let modal = new Modal("info", "System-Update");
|
||||||
let txt = document.createElement("p");
|
let txt = document.createElement("p");
|
||||||
if (payload.data as boolean) {
|
if (!!payload.data) {
|
||||||
|
|
||||||
txt.innerHTML = `Der iTender wird nun aktualisiert!<br><br>
|
txt.innerHTML = `Der iTender wird nun aktualisiert!<br><br>
|
||||||
Sobald das Update installiert ist, wird das System neu gestartet.<br>Die dadurch hergehende Verbindungswarnung kann ignoriert werden.<br>Der iTender stellt die Verbindung automatisch wieder her.<br><br><span style="color:red;font-weight: bold">Schalten Sie das System nicht aus und entfernen Sie nicht das Netzkabel!</span>`;
|
Sobald das Update installiert ist, wird das System neu gestartet.<br>Die dadurch hergehende Verbindungswarnung kann ignoriert werden.<br>Der iTender stellt die Verbindung automatisch wieder her.<br><br><span style="color:red;font-weight: bold">Schalten Sie das System nicht aus und entfernen Sie nicht das Netzkabel!</span>`;
|
||||||
modal.addContent(txt);
|
modal.addContent(txt);
|
||||||
@ -88,4 +93,22 @@ Sobald das Update installiert ist, wird das System neu gestartet.<br>Die dadurch
|
|||||||
modal.open();
|
modal.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static onClickDelete() {
|
||||||
|
WebWebSocketHandler.request(RequestType.CLEAR_DB, null).then((payload) => {
|
||||||
|
let modal = new Modal("delete", "Datenbank leeren");
|
||||||
|
let txt = document.createElement("p");
|
||||||
|
if (payload.data as boolean) {
|
||||||
|
|
||||||
|
txt.innerHTML = `Die Datenbank des iTender's wurde geleert.<br>Um nun Cocktails auszugeben, muss die Datenbank aktualisiert werden.<br><br>Wähle dazu bitte <strong>Getränke herunterladen</strong> in den Einstellungen.<br><br>`;
|
||||||
|
modal.addContent(txt);
|
||||||
|
} else {
|
||||||
|
txt.innerHTML = `Die Datenbank des iTenders konnte aus einem unbekannten Grund nicht geleert werden.<br>Bitte versuche es zu einem späteren Zeitpunkt erneut.<br><br>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
modal.addButton(ButtonType.PRIMARY, "Schließen", () => modal.close());
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
@ -12,7 +12,6 @@ import {Fill} from "./Fill";
|
|||||||
export class WebWebSocketHandler {
|
export class WebWebSocketHandler {
|
||||||
private static socket: WebSocket;
|
private static socket: WebSocket;
|
||||||
private static url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005";
|
private static url = (window.location.protocol == "http:" ? "ws://" : "wss://") + window.location.hostname + ":3005";
|
||||||
public static tareContainerUpdates: (payload: WebSocketPayload) => void;
|
|
||||||
private static eventRegister: { event: WebSocketEvent, fn: (payload: WebSocketPayload) => void }[] = [];
|
private static eventRegister: { event: WebSocketEvent, fn: (payload: WebSocketPayload) => void }[] = [];
|
||||||
|
|
||||||
|
|
||||||
@ -39,6 +38,70 @@ export class WebWebSocketHandler {
|
|||||||
WebWebSocketHandler.eventRegister.push({event: event, fn: fn});
|
WebWebSocketHandler.eventRegister.push({event: event, fn: fn});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Promise<WebSocketPayload>
|
||||||
|
* @param type
|
||||||
|
* @param content
|
||||||
|
*/
|
||||||
|
public static request(type: RequestType, content: any = null): Promise<WebSocketPayload> {
|
||||||
|
console.log("Request to " + type)
|
||||||
|
return new Promise(resolve => {
|
||||||
|
WebWebSocketHandler.registerForEvent(WebSocketEvent.RESPONSE, (payload) => {
|
||||||
|
if ((payload.data["type"] as RequestType) == type) {
|
||||||
|
resolve(payload.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.REQUEST, {
|
||||||
|
type: type,
|
||||||
|
data: content
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static send(payload: WebSocketPayload): Promise<void> {
|
||||||
|
console.log("[WS] Sending " + payload.event + " Event", payload);
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (this.socket && this.socket.readyState == 1) {
|
||||||
|
await this.socket.send(payload.toString());
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
/*let modal = new Modal("error", "Verbindungsfehler");
|
||||||
|
let txt = document.createElement("p");
|
||||||
|
txt.innerHTML = `Beim Austausch von Informationen ist ein Problem aufgetreten.<br>Falls dies öfter passieren sollte, ist der Support zu kontaktieren.`;
|
||||||
|
modal.addContent(txt);
|
||||||
|
await modal.open();*/
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static checkConnection(): Promise<boolean> {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("GET", '/status', true);
|
||||||
|
|
||||||
|
//Send the proper header information along with the request
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
xhr.onreadystatechange = () => { // Call a function when the state changes.
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||||
|
resolve(true);
|
||||||
|
} else if (xhr.readyState == XMLHttpRequest.DONE) {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await xhr.send();
|
||||||
|
} catch (e) {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private onMessage(msgEvent: MessageEvent) {
|
private onMessage(msgEvent: MessageEvent) {
|
||||||
let payload = WebSocketPayload.parseFromBase64Json(msgEvent.data);
|
let payload = WebSocketPayload.parseFromBase64Json(msgEvent.data);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
@ -105,13 +168,12 @@ export class WebWebSocketHandler {
|
|||||||
case iTenderStatus.DOWNLOADING: {
|
case iTenderStatus.DOWNLOADING: {
|
||||||
let modal = new Modal("download", "Aktualisieren");
|
let modal = new Modal("download", "Aktualisieren");
|
||||||
let txt = document.createElement("p");
|
let txt = document.createElement("p");
|
||||||
txt.innerHTML = `Einen Augenblick bitte<br>iTender aktualisiert die Getränke vom Server...`;
|
txt.innerHTML = `Einen Augenblick bitte<br>iTender synchronisiert die Datenbank mit der Cloud...`;
|
||||||
modal.addContent(txt);
|
modal.addContent(txt);
|
||||||
modal.loader = true;
|
modal.loader = true;
|
||||||
modal.open();
|
modal.open();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if( txt )
|
if (txt) {
|
||||||
{
|
|
||||||
txt.innerHTML = txt.innerHTML + "<br><br>Der Vorgang dauert länger als gewöhnlich.<br>Überprüfe deine Internetverbindung!"
|
txt.innerHTML = txt.innerHTML + "<br><br>Der Vorgang dauert länger als gewöhnlich.<br>Überprüfe deine Internetverbindung!"
|
||||||
}
|
}
|
||||||
}, 1000 * 15)
|
}, 1000 * 15)
|
||||||
@ -136,7 +198,6 @@ export class WebWebSocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private onOpen(event) {
|
private onOpen(event) {
|
||||||
console.log("[WS] Connected", event);
|
console.log("[WS] Connected", event);
|
||||||
|
|
||||||
@ -175,31 +236,6 @@ export class WebWebSocketHandler {
|
|||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private static checkConnection(): Promise<boolean> {
|
|
||||||
return new Promise(async resolve => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open("GET", '/status', true);
|
|
||||||
|
|
||||||
//Send the proper header information along with the request
|
|
||||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
|
|
||||||
xhr.onreadystatechange = () => { // Call a function when the state changes.
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
|
||||||
resolve(true);
|
|
||||||
} else if (xhr.readyState == XMLHttpRequest.DONE) {
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await xhr.send();
|
|
||||||
} catch (e) {
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private onError(event) {
|
private onError(event) {
|
||||||
console.error("[WS] Error", event);
|
console.error("[WS] Error", event);
|
||||||
/*let connectionElement = document.getElementById("right");
|
/*let connectionElement = document.getElementById("right");
|
||||||
@ -209,44 +245,5 @@ export class WebWebSocketHandler {
|
|||||||
//window.location.reload();
|
//window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Promise<WebSocketPayload>
|
|
||||||
* @param type
|
|
||||||
* @param content
|
|
||||||
*/
|
|
||||||
public static request(type: RequestType, content: any = null): Promise<WebSocketPayload> {
|
|
||||||
console.log("Request to " + type)
|
|
||||||
return new Promise(resolve => {
|
|
||||||
WebWebSocketHandler.registerForEvent(WebSocketEvent.RESPONSE, (payload) => {
|
|
||||||
if ((payload.data["type"] as RequestType) == type) {
|
|
||||||
resolve(payload.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
WebWebSocketHandler.send(new WebSocketPayload(WebSocketEvent.REQUEST, {
|
|
||||||
type: type,
|
|
||||||
data: content
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static send(payload: WebSocketPayload): Promise<void> {
|
|
||||||
console.log("[WS] Sending " + payload.event + " Event", payload);
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
if (this.socket && this.socket.readyState == 1) {
|
|
||||||
await this.socket.send(payload.toString());
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
/*let modal = new Modal("error", "Verbindungsfehler");
|
|
||||||
let txt = document.createElement("p");
|
|
||||||
txt.innerHTML = `Beim Austausch von Informationen ist ein Problem aufgetreten.<br>Falls dies öfter passieren sollte, ist der Support zu kontaktieren.`;
|
|
||||||
modal.addContent(txt);
|
|
||||||
await modal.open();*/
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -3,9 +3,9 @@ cd /home/itender/itender || exit -1
|
|||||||
git pull "https://tobiash:!IwedwrimmVeudiweN!@git.gaminggeneration.de/tobiash/itender.git" --quiet
|
git pull "https://tobiash:!IwedwrimmVeudiweN!@git.gaminggeneration.de/tobiash/itender.git" --quiet
|
||||||
yarn
|
yarn
|
||||||
yarn run compile
|
yarn run compile
|
||||||
sudo systemctl stop itender
|
|
||||||
cd ./arduino/itender/
|
cd ./arduino/itender/
|
||||||
arduino-cli compile --fqbn arduino:avr:mega itender.ino || true
|
arduino-cli compile --fqbn arduino:avr:mega itender.ino || true
|
||||||
|
sudo systemctl stop itender
|
||||||
arduino-cli upload --port /dev/ACM0 --fqbn arduino:avr:mega itender.ino || true
|
arduino-cli upload --port /dev/ACM0 --fqbn arduino:avr:mega itender.ino || true
|
||||||
sudo systemctl start itender
|
sudo systemctl start itender
|
||||||
exit 0
|
exit 0
|
@ -54,8 +54,8 @@ block menu
|
|||||||
|
|
||||||
block settings
|
block settings
|
||||||
// Settings
|
// Settings
|
||||||
button.btn.btn-primary#settings_refreshDrinks Getränke aktualisieren
|
button.btn.btn-primary#settings_refreshDrinks Datenbank aktualisieren
|
||||||
button.btn.btn-primary#settings_deleteDrinks(disabled="disabled") Getränke-DB löschen
|
button.btn.btn-primary#settings_deleteDrinks Datenbank leeren
|
||||||
button.btn.btn-primary#settings_reload Oberfläche neu starten
|
button.btn.btn-primary#settings_reload Oberfläche neu starten
|
||||||
button.btn.btn-primary#settings_getInfo Systeminformationen
|
button.btn.btn-primary#settings_getInfo Systeminformationen
|
||||||
button.btn.btn-primary#settings_update System aktualisieren
|
button.btn.btn-primary#settings_update System aktualisieren
|
||||||
|
Loading…
x
Reference in New Issue
Block a user