rot finished
This commit is contained in:
305
web/src/components/WebSwitch.svelte
Normal file
305
web/src/components/WebSwitch.svelte
Normal file
@@ -0,0 +1,305 @@
|
||||
<script>
|
||||
import { api } from '../lib/api.js';
|
||||
|
||||
export let status;
|
||||
|
||||
$: relays = status?.relays || [];
|
||||
$: connected = status?.connected || false;
|
||||
|
||||
const relayNames = {
|
||||
1: 'Power Supply',
|
||||
2: 'PGXL',
|
||||
3: 'TGXL',
|
||||
4: 'Flex Radio Start',
|
||||
5: 'Reserve'
|
||||
};
|
||||
|
||||
let loading = {};
|
||||
|
||||
async function toggleRelay(relayNum) {
|
||||
const relay = relays.find(r => r.number === relayNum);
|
||||
const currentState = relay?.state || false;
|
||||
|
||||
loading[relayNum] = true;
|
||||
try {
|
||||
if (currentState) {
|
||||
await api.webswitch.relayOff(relayNum);
|
||||
} else {
|
||||
await api.webswitch.relayOn(relayNum);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to toggle relay:', err);
|
||||
alert('Failed to control relay');
|
||||
} finally {
|
||||
loading[relayNum] = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function allOn() {
|
||||
try {
|
||||
await api.webswitch.allOn();
|
||||
} catch (err) {
|
||||
console.error('Failed to turn all on:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function allOff() {
|
||||
try {
|
||||
await api.webswitch.allOff();
|
||||
} catch (err) {
|
||||
console.error('Failed to turn all off:', err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>WebSwitch</h2>
|
||||
<span class="status-dot" class:disconnected={!connected}></span>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="relays">
|
||||
{#each [1, 2, 3, 4, 5] as relayNum}
|
||||
{@const relay = relays.find(r => r.number === relayNum)}
|
||||
{@const isOn = relay?.state || false}
|
||||
<div class="relay-card" class:relay-on={isOn}>
|
||||
<div class="relay-info">
|
||||
<div class="relay-details">
|
||||
<div class="relay-name">{relayNames[relayNum]}</div>
|
||||
<div class="relay-status">{isOn ? 'ON' : 'OFF'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="relay-toggle"
|
||||
class:active={isOn}
|
||||
class:loading={loading[relayNum]}
|
||||
disabled={loading[relayNum]}
|
||||
on:click={() => toggleRelay(relayNum)}
|
||||
>
|
||||
<div class="toggle-track">
|
||||
<div class="toggle-thumb"></div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="control-btn all-on" on:click={allOn}>
|
||||
<span class="btn-icon">⚡</span>
|
||||
ALL ON
|
||||
</button>
|
||||
<button class="control-btn all-off" on:click={allOff}>
|
||||
<span class="btn-icon">⏻</span>
|
||||
ALL OFF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
background: linear-gradient(135deg, #1a2332 0%, #0f1923 100%);
|
||||
border: 1px solid #2d3748;
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: rgba(79, 195, 247, 0.05);
|
||||
border-bottom: 1px solid #2d3748;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-cyan);
|
||||
margin: 0;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #4caf50;
|
||||
box-shadow: 0 0 8px #4caf50;
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: #f44336;
|
||||
box-shadow: 0 0 8px #f44336;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Relays */
|
||||
.relays {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.relay-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.relay-card.relay-on {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
border-color: rgba(76, 175, 80, 0.3);
|
||||
box-shadow: 0 0 15px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
.relay-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.relay-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.relay-name {
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.relay-status {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.relay-card.relay-on .relay-status {
|
||||
color: #4caf50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Toggle Switch */
|
||||
.relay-toggle {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-track {
|
||||
width: 52px;
|
||||
height: 28px;
|
||||
background: var(--bg-primary);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.relay-toggle:hover .toggle-track {
|
||||
border-color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.relay-toggle.active .toggle-track {
|
||||
background: linear-gradient(135deg, #4caf50, #66bb6a);
|
||||
border-color: #4caf50;
|
||||
box-shadow: 0 0 15px rgba(76, 175, 80, 0.5);
|
||||
}
|
||||
|
||||
.toggle-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.relay-toggle.active .toggle-thumb {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
.relay-toggle:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.all-on {
|
||||
background: linear-gradient(135deg, #4caf50, #66bb6a);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.all-on:hover {
|
||||
box-shadow: 0 6px 16px rgba(76, 175, 80, 0.5);
|
||||
}
|
||||
|
||||
.all-off {
|
||||
background: linear-gradient(135deg, #f44336, #d32f2f);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
|
||||
}
|
||||
|
||||
.all-off:hover {
|
||||
box-shadow: 0 6px 16px rgba(244, 67, 54, 0.5);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user