308 lines
7.1 KiB
Vue
308 lines
7.1 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from "vue";
|
|
|
|
const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.254");
|
|
const serverUrl = ref(
|
|
localStorage.getItem("serverUrl") || "http://localhost:3000",
|
|
);
|
|
const errorMsg = ref("");
|
|
const isSuccess = ref(false);
|
|
const pingStatus = ref<{
|
|
[key: string]: "idle" | "pinging" | "success" | "error";
|
|
}>({
|
|
printer: "idle",
|
|
server: "idle",
|
|
});
|
|
|
|
const validateIpOrUrl = (value: string): boolean => {
|
|
if (!value) return false;
|
|
|
|
// Basic IP regex
|
|
const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
|
// Basic URL regex
|
|
const urlRegex = /^(http|https):\/\/[^ "]+$/;
|
|
|
|
return ipRegex.test(value) || urlRegex.test(value);
|
|
};
|
|
|
|
const handleSave = (): void => {
|
|
errorMsg.value = "";
|
|
isSuccess.value = false;
|
|
|
|
localStorage.setItem("printerUrl", printerUrl.value);
|
|
localStorage.setItem("serverUrl", serverUrl.value);
|
|
isSuccess.value = true;
|
|
|
|
setTimeout((): void => {
|
|
isSuccess.value = false;
|
|
}, 2000);
|
|
};
|
|
|
|
const performPing = async (type: "printer" | "server"): Promise<void> => {
|
|
errorMsg.value = "";
|
|
isSuccess.value = false;
|
|
|
|
const url = type === "printer" ? printerUrl.value : serverUrl.value;
|
|
|
|
pingStatus.value[type] = "pinging";
|
|
try {
|
|
const ok = await window.api.nfc.ping(url);
|
|
pingStatus.value[type] = ok ? "success" : "error";
|
|
if (!ok) {
|
|
errorMsg.value = `${type === "printer" ? "Printer" : "Server"} is unreachable`;
|
|
}
|
|
} catch (err) {
|
|
console.error("Ping error:", err);
|
|
pingStatus.value[type] = "error";
|
|
errorMsg.value = "An error occurred during ping";
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (pingStatus.value[type] !== "pinging") {
|
|
pingStatus.value[type] = "idle";
|
|
}
|
|
}, 3000);
|
|
};
|
|
|
|
const handlePingPrinter = (): void => {
|
|
performPing("printer");
|
|
};
|
|
|
|
const handlePingServer = (): void => {
|
|
performPing("server");
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="config-panel">
|
|
<div class="panel-header">
|
|
<h3>SYSTEM_CONFIGURATION</h3>
|
|
<div class="header-line"></div>
|
|
</div>
|
|
|
|
<div class="config-grid">
|
|
<!-- Printer Config -->
|
|
<div class="config-item">
|
|
<div class="item-meta">
|
|
<span class="item-index">01</span>
|
|
<label for="printer-url">PRINTER_ADDRESS</label>
|
|
</div>
|
|
<div class="input-with-action">
|
|
<input
|
|
id="printer-url"
|
|
v-model="printerUrl"
|
|
type="text"
|
|
placeholder="IP_OR_URL"
|
|
/>
|
|
<button
|
|
class="action-btn"
|
|
:disabled="pingStatus.printer === 'pinging'"
|
|
@click="handlePingPrinter"
|
|
>
|
|
{{ pingStatus.printer === "pinging" ? "..." : "PING" }}
|
|
</button>
|
|
</div>
|
|
<div
|
|
v-if="pingStatus.printer === 'success'"
|
|
class="field-status success"
|
|
>
|
|
ONLINE
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Server Config -->
|
|
<div class="config-item">
|
|
<div class="item-meta">
|
|
<span class="item-index">02</span>
|
|
<label for="server-url">GATEWAY_SERVER</label>
|
|
</div>
|
|
<div class="input-with-action">
|
|
<input
|
|
id="server-url"
|
|
v-model="serverUrl"
|
|
type="text"
|
|
placeholder="IP_OR_URL"
|
|
/>
|
|
<button
|
|
class="action-btn"
|
|
:disabled="pingStatus.server === 'pinging'"
|
|
@click="handlePingServer"
|
|
>
|
|
{{ pingStatus.server === "pinging" ? "..." : "PING" }}
|
|
</button>
|
|
</div>
|
|
<div
|
|
v-if="pingStatus.server === 'success'"
|
|
class="field-status success"
|
|
>
|
|
REACHABLE
|
|
</div>
|
|
<div
|
|
v-if="pingStatus.server === 'error'"
|
|
class="field-status error"
|
|
>
|
|
OFFLINE
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="errorMsg" class="form-error">
|
|
<span>[ERR]</span> {{ errorMsg }}
|
|
</div>
|
|
|
|
<div class="panel-actions">
|
|
<button class="save-btn" @click="handleSave">
|
|
WRITE_TO_MEMORY
|
|
</button>
|
|
<span v-if="isSuccess" class="write-success">WRITE_OK</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.config-panel {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 40px;
|
|
background-color: var(--te-bg);
|
|
}
|
|
|
|
.panel-header {
|
|
margin-bottom: 40px;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
color: var(--te-gray);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.header-line {
|
|
height: 1px;
|
|
background-color: var(--te-gray-light);
|
|
}
|
|
|
|
.config-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 40px;
|
|
}
|
|
|
|
.config-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.item-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.item-index {
|
|
font-family: var(--font-mono);
|
|
font-size: 9px;
|
|
color: var(--te-orange);
|
|
}
|
|
|
|
label {
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.input-with-action {
|
|
display: flex;
|
|
border: 1px solid var(--te-fg);
|
|
}
|
|
|
|
input {
|
|
flex: 1;
|
|
border: none;
|
|
background: transparent;
|
|
padding: 8px 12px;
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--te-fg);
|
|
width: 100%;
|
|
}
|
|
|
|
input:focus {
|
|
outline: none;
|
|
background-color: var(--te-gray-light);
|
|
}
|
|
|
|
.action-btn {
|
|
border-left: 1px solid var(--te-fg);
|
|
padding: 0 12px;
|
|
font-size: 9px;
|
|
font-weight: 700;
|
|
color: var(--te-fg);
|
|
}
|
|
|
|
.action-btn:hover:not(:disabled) {
|
|
background-color: var(--te-fg);
|
|
color: var(--te-bg);
|
|
}
|
|
|
|
.field-status {
|
|
font-family: var(--font-mono);
|
|
font-size: 8px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.field-status.success {
|
|
color: #00aa00;
|
|
}
|
|
|
|
.field-status.error {
|
|
color: #ff0000;
|
|
}
|
|
|
|
.form-error {
|
|
margin-top: 40px;
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
color: #ff0000;
|
|
}
|
|
|
|
.panel-actions {
|
|
margin-top: auto;
|
|
padding-top: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
.save-btn {
|
|
appearance: none;
|
|
background-color: var(--te-orange);
|
|
color: #fff;
|
|
padding: 10px 24px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.save-btn:active {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.write-success {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
color: #00aa00;
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
.header-line {
|
|
background-color: #222;
|
|
}
|
|
input:focus {
|
|
background-color: #1a1a1a;
|
|
}
|
|
}
|
|
</style>
|