feat: Most settings implemented
This commit is contained in:
parent
f8c2f4f228
commit
98ad7d1432
41 changed files with 1976 additions and 421 deletions
259
src/lib/components/sections/FirmwareUpdateSection.svelte
Normal file
259
src/lib/components/sections/FirmwareUpdateSection.svelte
Normal file
|
@ -0,0 +1,259 @@
|
|||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { CardContainer, Alert } from '$lib/components';
|
||||
import { getFirmwareBinaryName, getWebUiBinaryName } from '$lib/utils';
|
||||
import { status, settings, firmwareRelease } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { uploadFirmwareFile, uploadWebUiFile, autoUpdate } from '$lib/clockControl';
|
||||
|
||||
// Format date to a more readable format
|
||||
function formatDate(dateString: string): string {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
// Upload status
|
||||
let firmwareUploadProgress = 0;
|
||||
let webuiUploadProgress = 0;
|
||||
let isUploading = false;
|
||||
let uploadError: string | null = null;
|
||||
let uploadSuccess = false;
|
||||
let countdownValue = 0;
|
||||
let countdownInterval: number | null = null;
|
||||
|
||||
// File selection status
|
||||
let firmwareFileSelected = false;
|
||||
let webuiFileSelected = false;
|
||||
|
||||
// Handle file selection change for firmware file
|
||||
function handleFirmwareFileChange(event: Event) {
|
||||
const fileInput = event.target as HTMLInputElement;
|
||||
firmwareFileSelected = !!(fileInput.files && fileInput.files.length > 0);
|
||||
}
|
||||
|
||||
// Handle file selection change for WebUI file
|
||||
function handleWebUIFileChange(event: Event) {
|
||||
const fileInput = event.target as HTMLInputElement;
|
||||
webuiFileSelected = !!(fileInput.files && fileInput.files.length > 0);
|
||||
}
|
||||
|
||||
// Start countdown to reload page after successful upload
|
||||
function startCountdownToReload(seconds: number) {
|
||||
countdownValue = seconds;
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
|
||||
countdownInterval = setInterval(() => {
|
||||
countdownValue--;
|
||||
if (countdownValue <= 0) {
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
window.location.reload();
|
||||
}
|
||||
}, 1000) as unknown as number;
|
||||
}
|
||||
|
||||
// Handle firmware file upload
|
||||
function handleFirmwareUpload() {
|
||||
const fileInput = document.getElementById('firmwareFile') as HTMLInputElement;
|
||||
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
||||
uploadError = 'Please select a firmware file';
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading = true;
|
||||
uploadError = null;
|
||||
uploadSuccess = false;
|
||||
firmwareUploadProgress = 0;
|
||||
|
||||
uploadFirmwareFile(fileInput.files[0], {
|
||||
onProgress: (progress) => {
|
||||
firmwareUploadProgress = progress;
|
||||
},
|
||||
onSuccess: () => {
|
||||
isUploading = false;
|
||||
uploadSuccess = true;
|
||||
startCountdownToReload(10);
|
||||
},
|
||||
onError: (error) => {
|
||||
isUploading = false;
|
||||
uploadError =
|
||||
typeof error === 'string' && error !== 'FAIL' ? error : 'Failed to upload firmware';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle webUI file upload
|
||||
function handleWebUIUpload() {
|
||||
const fileInput = document.getElementById('webuiFile') as HTMLInputElement;
|
||||
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
||||
uploadError = 'Please select a WebUI file';
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading = true;
|
||||
uploadError = null;
|
||||
uploadSuccess = false;
|
||||
webuiUploadProgress = 0;
|
||||
|
||||
uploadWebUiFile(fileInput.files[0], {
|
||||
onProgress: (progress) => {
|
||||
webuiUploadProgress = progress;
|
||||
},
|
||||
onSuccess: () => {
|
||||
isUploading = false;
|
||||
uploadSuccess = true;
|
||||
startCountdownToReload(10);
|
||||
},
|
||||
onError: (error) => {
|
||||
isUploading = false;
|
||||
uploadError = typeof error === 'string' ? error : 'Failed to upload WebUI';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onAutoUpdate = (e: Event) => {
|
||||
e.preventDefault();
|
||||
autoUpdate({
|
||||
onSuccess: () => {
|
||||
// toast.push({
|
||||
// type: 'success',
|
||||
// message
|
||||
// });
|
||||
startCountdownToReload(10);
|
||||
},
|
||||
onError: (error) => {
|
||||
// toast.push({
|
||||
// type: 'error',
|
||||
// message: error as string
|
||||
// });
|
||||
uploadError = typeof error === 'string' ? error : 'Failed to auto-update';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Fetch firmware data when component is mounted
|
||||
onMount(() => {
|
||||
firmwareRelease.fetchLatest();
|
||||
|
||||
// Cleanup on component destroy
|
||||
return () => {
|
||||
if (countdownInterval) clearInterval(countdownInterval);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<CardContainer title={m['section.control.firmwareUpdate']()} className="">
|
||||
<div>
|
||||
{#if $firmwareRelease.isLoading}
|
||||
<div class="my-4 flex justify-center">
|
||||
<span class="loading loading-spinner"></span>
|
||||
</div>
|
||||
{:else if $firmwareRelease.error}
|
||||
<Alert type="error" className="mb-4" message={$firmwareRelease.error} />
|
||||
{:else}
|
||||
<p class="mb-2 text-sm">
|
||||
{m['section.firmwareUpdater.latestVersion']()}
|
||||
{$firmwareRelease.tag_name} - {m['section.firmwareUpdater.releaseDate']()}
|
||||
{formatDate($firmwareRelease.published_at)} -
|
||||
<a
|
||||
href={$firmwareRelease.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary"
|
||||
>
|
||||
{m['section.firmwareUpdater.viewRelease']()}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
{#if !$status.isOTAUpdating && $settings.gitTag !== $firmwareRelease.tag_name}
|
||||
<Alert type="success" className="mb-4">
|
||||
{m['section.firmwareUpdater.swUpdateAvailable']()}
|
||||
{$settings.gitTag} → {$firmwareRelease.tag_name} -
|
||||
<a href="/" class="link link-primary" on:click={onAutoUpdate}
|
||||
>{m['section.firmwareUpdater.autoUpdate']()}</a
|
||||
>.
|
||||
</Alert>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if !$status.isOTAUpdating}
|
||||
<div class="form-control mb-4">
|
||||
<label class="label" for="firmwareFile">
|
||||
<span class="label-text">Firmware File ({getFirmwareBinaryName($settings.hwRev)})</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="file"
|
||||
class="file-input file-input-bordered w-full"
|
||||
id="firmwareFile"
|
||||
accept=".bin"
|
||||
disabled={isUploading}
|
||||
on:change={handleFirmwareFileChange}
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary w-1/4"
|
||||
on:click={handleFirmwareUpload}
|
||||
disabled={isUploading || !firmwareFileSelected}
|
||||
>
|
||||
{#if isUploading && firmwareUploadProgress > 0}
|
||||
{firmwareUploadProgress}%
|
||||
{:else}
|
||||
{m['section.control.firmwareUpdate']()}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if isUploading && firmwareUploadProgress > 0}
|
||||
<progress class="progress progress-primary mt-2" value={firmwareUploadProgress} max="100"
|
||||
></progress>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label" for="webuiFile">
|
||||
<span class="label-text">WebUI File ({getWebUiBinaryName($settings.hwRev)})</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="file"
|
||||
class="file-input file-input-bordered w-full"
|
||||
id="webuiFile"
|
||||
accept=".bin"
|
||||
disabled={isUploading}
|
||||
on:change={handleWebUIFileChange}
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary w-1/4"
|
||||
on:click={handleWebUIUpload}
|
||||
disabled={isUploading || !webuiFileSelected}
|
||||
>
|
||||
{#if isUploading && webuiUploadProgress > 0}
|
||||
{webuiUploadProgress}%
|
||||
{:else}
|
||||
Update WebUI
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if isUploading && webuiUploadProgress > 0}
|
||||
<progress class="progress progress-primary mt-2" value={webuiUploadProgress} max="100"
|
||||
></progress>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<Alert type="info" className="my-4">
|
||||
<span class="loading loading-spinner text-neutral"></span>
|
||||
{m['section.firmwareUpdater.autoUpdateInProgress']()}
|
||||
</Alert>
|
||||
{/if}
|
||||
{#if uploadSuccess}
|
||||
<Alert type="success" className="my-4">
|
||||
{m['section.firmwareUpdater.fileUploadSuccess']({ countdown: countdownValue })}
|
||||
</Alert>
|
||||
{:else if uploadError}
|
||||
<Alert type="error" className="my-4">
|
||||
{uploadError}
|
||||
</Alert>
|
||||
{/if}
|
||||
<Alert type="warning" className="mt-4">
|
||||
{m['section.firmwareUpdater.firmwareUpdateText']()}
|
||||
</Alert>
|
||||
</div>
|
||||
</CardContainer>
|
|
@ -1,233 +1,255 @@
|
|||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { CardContainer, Toggle, CollapsibleSection } from '$lib/components';
|
||||
import { settings } from '$lib/stores';
|
||||
|
||||
import {
|
||||
CardContainer,
|
||||
Toggle,
|
||||
CollapsibleSection,
|
||||
TimezoneSelector,
|
||||
ToggleWrapper,
|
||||
Radio,
|
||||
NumericInput,
|
||||
Select,
|
||||
RadioGroup,
|
||||
SettingsInputField
|
||||
} from '$lib/components';
|
||||
import { settings, status } from '$lib/stores';
|
||||
import { DataSourceType } from '$lib/types';
|
||||
import RangeSlider from '../form/RangeSlider.svelte';
|
||||
import { miningPoolMap } from '$lib/utils';
|
||||
let { ...restProps } = $props();
|
||||
|
||||
// Show/hide toggles
|
||||
let showAll = $state(false);
|
||||
let hideAll = $state(false);
|
||||
const textColorOptions: [string, boolean][] = [
|
||||
[m['colors.black']() + ' on ' + m['colors.white'](), false],
|
||||
[m['colors.white']() + ' on ' + m['colors.black'](), true]
|
||||
];
|
||||
|
||||
function toggleShowAll() {
|
||||
showAll = true;
|
||||
hideAll = false;
|
||||
}
|
||||
const fontPreferenceOptions: [string, string][] = $settings.availableFonts?.map((font) => {
|
||||
// Check if the translation key exists in messages
|
||||
// If it exists, use the translation, otherwise capitalize the font name
|
||||
const translationKey = `fonts.${font}` as keyof typeof m;
|
||||
const hasTranslation = translationKey in m && typeof m[translationKey] === 'function';
|
||||
|
||||
function toggleHideAll() {
|
||||
hideAll = true;
|
||||
showAll = false;
|
||||
}
|
||||
return [
|
||||
hasTranslation
|
||||
? (m[translationKey] as Function)()
|
||||
: font.charAt(0).toUpperCase() + font.slice(1),
|
||||
font
|
||||
];
|
||||
});
|
||||
|
||||
const handleColorChange = (e: Event) => {
|
||||
const select = e.target as HTMLSelectElement;
|
||||
$settings.invertedColor = select.value === 'true';
|
||||
};
|
||||
|
||||
// // Show/hide toggles
|
||||
// let showAll = $state(false);
|
||||
// let hideAll = $state(false);
|
||||
|
||||
// function toggleShowAll() {
|
||||
// showAll = true;
|
||||
// hideAll = false;
|
||||
// }
|
||||
|
||||
// function toggleHideAll() {
|
||||
// hideAll = true;
|
||||
// showAll = false;
|
||||
// }
|
||||
|
||||
const handleTimePerScreenChange = (e: Event) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
$settings.timePerScreen = Number(input.value);
|
||||
};
|
||||
|
||||
const handleReset = (e: Event) => {
|
||||
e.preventDefault();
|
||||
settings.reset();
|
||||
console.log('formReset');
|
||||
};
|
||||
|
||||
const handleSave = (e: Event) => {
|
||||
e.preventDefault();
|
||||
console.log('formSave');
|
||||
settings.update($settings);
|
||||
};
|
||||
|
||||
const dataSourceOptions = [
|
||||
{ label: m['section.settings.dataSource.btclock'](), value: DataSourceType.BTCLOCK_SOURCE },
|
||||
{
|
||||
label: m['section.settings.dataSource.thirdParty'](),
|
||||
value: DataSourceType.THIRD_PARTY_SOURCE
|
||||
},
|
||||
{ label: m['section.settings.dataSource.nostr'](), value: DataSourceType.NOSTR_SOURCE },
|
||||
{ label: m['section.settings.dataSource.custom'](), value: DataSourceType.CUSTOM_SOURCE }
|
||||
];
|
||||
|
||||
const miningPoolOptions = Array.from(miningPoolMap.entries()).map(([key, value]) => ({
|
||||
label: value,
|
||||
value: key
|
||||
}));
|
||||
</script>
|
||||
|
||||
<CardContainer title={m['section.settings.title']()} {...restProps}>
|
||||
<div class="mb-4 flex justify-end gap-2">
|
||||
<button class="btn btn-sm" onclick={toggleShowAll}>{m['section.settings.showAll']()}</button>
|
||||
<button class="btn btn-sm" onclick={toggleHideAll}>{m['section.settings.hideAll']()}</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<CollapsibleSection
|
||||
title={m['section.settings.section.screenSettings']()}
|
||||
open={showAll || !hideAll}
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.StealFocusOnNewBlock']()}
|
||||
bind:checked={$settings.stealFocus}
|
||||
/>
|
||||
<p class="text-xs">
|
||||
When a new block is mined, it will switch focus from the current screen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.useBigCharsMcap']()}
|
||||
bind:checked={$settings.mcapBigChar}
|
||||
/>
|
||||
<p class="text-xs">
|
||||
Use big characters for the market cap screen instead of using a suffix.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.useBlkCountdown']()}
|
||||
bind:checked={$settings.useBlkCountdown}
|
||||
/>
|
||||
<p class="text-xs">
|
||||
When enabled it count down blocks instead of years/monts/days/hours/minutes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.useSatsSymbol']()}
|
||||
bind:checked={$settings.useSatsSymbol}
|
||||
/>
|
||||
<p class="text-xs">Prefix satoshi amounts with the sats symbol.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.suffixPrice']()}
|
||||
bind:checked={$settings.suffixPrice}
|
||||
/>
|
||||
<p class="text-xs">Always use a suffix for the ticker screen.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.verticalDesc']()}
|
||||
bind:checked={$settings.verticalDesc}
|
||||
/>
|
||||
<p class="text-xs">Rotate the description of the screen 90 degrees.</p>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection title={m['section.settings.screens']()} open={showAll || !hideAll}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<CardContainer {...restProps}>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<CollapsibleSection title={m['section.settings.screens']()}>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
{#each $settings.screens as screen (screen.id)}
|
||||
<div class="form-control">
|
||||
<Toggle label={screen.name} checked={screen.enabled} />
|
||||
</div>
|
||||
<ToggleWrapper label={screen.name} bind:value={screen.enabled} />
|
||||
{/each}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection title={m['section.settings.currencies']()} open={showAll || !hideAll}>
|
||||
<div class="alert alert-warning">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/></svg
|
||||
>
|
||||
<span>restart required</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{#each $settings.actCurrencies as currency (currency)}
|
||||
<div class="form-control">
|
||||
<Toggle label={currency} checked={$settings.actCurrencies.includes(currency)} />
|
||||
</div>
|
||||
<CollapsibleSection title={m['section.settings.currencies']()}>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
{#each $settings.availableCurrencies as currency (currency)}
|
||||
<ToggleWrapper
|
||||
id={`currency-${currency}`}
|
||||
label={currency}
|
||||
bind:group={$settings.actCurrencies}
|
||||
value={currency}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection
|
||||
title={m['section.settings.section.displaysAndLed']()}
|
||||
open={showAll || !hideAll}
|
||||
>
|
||||
<CollapsibleSection title={m['section.settings.section.screenSettings']()}>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.StealFocusOnNewBlock']()}
|
||||
bind:value={$settings.stealFocus}
|
||||
description="When a new block is mined, it will switch focus from the current screen."
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.useBigCharsMcap']()}
|
||||
bind:value={$settings.mcapBigChar}
|
||||
description="Use big characters for the market cap screen instead of using a suffix."
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.useBlkCountdown']()}
|
||||
bind:value={$settings.useBlkCountdown}
|
||||
description="When enabled it count down blocks instead of years/monts/days/hours/minutes."
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.useSatsSymbol']()}
|
||||
bind:value={$settings.useSatsSymbol}
|
||||
description="Prefix satoshi amounts with the sats symbol."
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.suffixPrice']()}
|
||||
bind:value={$settings.suffixPrice}
|
||||
description="Always use a suffix for the ticker screen."
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.verticalDesc']()}
|
||||
bind:value={$settings.verticalDesc}
|
||||
description="Rotate the description of the screen 90 degrees."
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection title={m['section.settings.section.displaysAndLed']()}>
|
||||
<div class="grid gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.textColor']()}</span>
|
||||
</label>
|
||||
<select class="select select-bordered w-full">
|
||||
<option>White on Black</option>
|
||||
<option>Black on White</option>
|
||||
</select>
|
||||
</div>
|
||||
<Select
|
||||
label={m['section.settings.textColor']()}
|
||||
bind:value={$settings.invertedColor}
|
||||
options={textColorOptions.map(([label, value]) => ({ label, value: value }))}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label={m['section.settings.fontName']()}
|
||||
bind:value={$settings.fontName}
|
||||
options={fontPreferenceOptions.map(([label, value]) => ({ label, value }))}
|
||||
/>
|
||||
|
||||
<NumericInput
|
||||
label={m['section.settings.timePerScreen']()}
|
||||
value={$settings.timePerScreen ? $settings.timePerScreen : 1}
|
||||
min={1}
|
||||
required
|
||||
step={1}
|
||||
onchange={handleTimePerScreenChange}
|
||||
unit={m['time.minutes']()}
|
||||
/>
|
||||
|
||||
<NumericInput
|
||||
label={m['section.settings.fullRefreshEvery']()}
|
||||
bind:value={$settings.fullRefreshMin}
|
||||
min={1}
|
||||
required
|
||||
step={1}
|
||||
unit={m['time.minutes']()}
|
||||
/>
|
||||
|
||||
<NumericInput
|
||||
label={m['section.settings.timeBetweenPriceUpdates']()}
|
||||
bind:value={$settings.minSecPriceUpd}
|
||||
min={1}
|
||||
required
|
||||
step={1}
|
||||
unit={m['time.seconds']()}
|
||||
/>
|
||||
|
||||
<RangeSlider
|
||||
label={m['section.settings.ledBrightness']()}
|
||||
bind:value={$settings.ledBrightness}
|
||||
min={0}
|
||||
max={255}
|
||||
step={1}
|
||||
showValue={true}
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.ledPowerOnTest']()}
|
||||
bind:value={$settings.ledTestOnPower}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Font</span>
|
||||
</label>
|
||||
<select class="select select-bordered w-full">
|
||||
<option>Oswald</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.timePerScreen']()}</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="number" class="input input-bordered w-20" min="1" max="60" value="1" />
|
||||
<span>{m['time.minutes']()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control flex justify-between">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.fullRefreshEvery']()}</span>
|
||||
</label>
|
||||
<div class="input w-auto">
|
||||
<input type="number" class="" min="1" max="60" value="60" />
|
||||
<span class="label">{m['time.minutes']()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control flex justify-between">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.timeBetweenPriceUpdates']()}</span>
|
||||
</label>
|
||||
<div class="input w-auto">
|
||||
<input type="number" class="" min="1" max="60" value="30" />
|
||||
<span class="label">{m['time.seconds']()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.ledBrightness']()}</span>
|
||||
</label>
|
||||
<input type="range" min="0" max="100" class="range" value="50" />
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
label={m['section.settings.ledPowerOnTest']()}
|
||||
checked={$settings.ledTestOnPower}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.ledFlashOnBlock']()}
|
||||
checked={$settings.ledFlashOnUpd}
|
||||
bind:value={$settings.ledFlashOnUpd}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle label={m['section.settings.disableLeds']()} checked={$settings.disableLeds} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.disableLeds']()}
|
||||
bind:value={$settings.disableLeds}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
{#if $settings.hasFrontlight}
|
||||
<CollapsibleSection title="Frontlight Settings" open={showAll || !hideAll}>
|
||||
<CollapsibleSection title="Frontlight Settings">
|
||||
<div class="grid gap-4">
|
||||
<div class="form-control">
|
||||
<Toggle label="Disable Frontlight" checked={$settings.flDisable} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.flDisable']()}
|
||||
bind:value={$settings.flDisable}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle label="Always On" checked={$settings.flAlwaysOn} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.flAlwaysOn']()}
|
||||
bind:value={$settings.flAlwaysOn}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle label="Flash on Updates" checked={$settings.flFlashOnUpd} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.flFlashOnUpd']()}
|
||||
bind:value={$settings.flFlashOnUpd}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<Toggle label="Flash on Zaps" checked={$settings.flFlashOnZap} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.flFlashOnZap']()}
|
||||
bind:value={$settings.flFlashOnZap}
|
||||
/>
|
||||
|
||||
{#if $settings.hasLightLevel}
|
||||
<div class="form-control">
|
||||
<Toggle label="Turn Off in Dark" checked={$settings.flOffWhenDark} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.flOffWhenDark']()}
|
||||
bind:value={$settings.flOffWhenDark}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
|
@ -238,124 +260,228 @@
|
|||
min="0"
|
||||
max="255"
|
||||
class="range"
|
||||
value={$settings.luxLightToggle}
|
||||
bind:value={$settings.luxLightToggle}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Maximum Brightness</span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="4095"
|
||||
class="range"
|
||||
value={$settings.flMaxBrightness}
|
||||
/>
|
||||
</div>
|
||||
<RangeSlider
|
||||
label={m['section.settings.flMaxBrightness']()}
|
||||
bind:value={$settings.flMaxBrightness}
|
||||
min={0}
|
||||
max={4095}
|
||||
step={1}
|
||||
showValue={true}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Effect Delay (ms)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
class="input input-bordered w-20"
|
||||
min="10"
|
||||
max="1000"
|
||||
value={$settings.flEffectDelay}
|
||||
/>
|
||||
</div>
|
||||
<NumericInput
|
||||
label={m['section.settings.flEffectDelay']()}
|
||||
bind:value={$settings.flEffectDelay}
|
||||
min={10}
|
||||
max={1000}
|
||||
step={1}
|
||||
unit="ms"
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
{/if}
|
||||
|
||||
<CollapsibleSection
|
||||
title={m['section.settings.section.dataSource']()}
|
||||
open={showAll || !hideAll}
|
||||
>
|
||||
<CollapsibleSection title={m['section.settings.section.dataSource']()}>
|
||||
<div class="grid gap-4">
|
||||
<div class="form-control">
|
||||
<div class="form-control flex flex-col items-start justify-between md:flex-row">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.dataSource.label']()}</span>
|
||||
</label>
|
||||
<select class="select select-bordered w-full">
|
||||
<option value="btclock">{m['section.settings.dataSource.btclock']()}</option>
|
||||
<option value="thirdparty">{m['section.settings.dataSource.thirdParty']()}</option>
|
||||
<option value="nostr">{m['section.settings.dataSource.nostr']()}</option>
|
||||
<option value="custom">{m['section.settings.dataSource.custom']()}</option>
|
||||
</select>
|
||||
<div class="flex grid grid-cols-1 flex-row gap-2 md:grid-cols-2">
|
||||
{#each dataSourceOptions as option}
|
||||
<Radio
|
||||
label={option.label}
|
||||
value={option.value}
|
||||
name="dataSource"
|
||||
bind:group={$settings.dataSource}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<div class="form-control flex justify-between">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.mempoolnstance']()}</span>
|
||||
</label>
|
||||
<input type="text" class="input input-bordered w-full" value="mempool.space/coinlcp.io" />
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered input flex w-auto"
|
||||
bind:value={$settings.mempoolInstance}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<div class="form-control flex justify-between">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.ceEndpoint']()}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
class="input input-bordered input flex w-auto"
|
||||
placeholder="Custom Endpoint URL"
|
||||
bind:value={$settings.ceEndpoint}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection
|
||||
title={m['section.settings.section.extraFeatures']()}
|
||||
open={showAll || !hideAll}
|
||||
>
|
||||
<CollapsibleSection title={m['section.settings.section.extraFeatures']()}>
|
||||
<div class="grid gap-4">
|
||||
<div class="form-control">
|
||||
<Toggle label={m['section.settings.timeBasedDnd']()} checked={$settings.dnd.enabled} />
|
||||
</div>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.timeBasedDnd']()}
|
||||
bind:value={$settings.dnd.timeBasedEnabled}
|
||||
/>
|
||||
|
||||
<h4 class="text-lg font-bold">Bitaxe</h4>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.bitaxeEnabled']()}
|
||||
bind:value={$settings.bitaxeEnabled}
|
||||
/>
|
||||
|
||||
{#if $settings.bitaxeEnabled}
|
||||
<SettingsInputField
|
||||
label={m['section.settings.bitaxeHostname']()}
|
||||
bind:value={$settings.bitaxeHostname}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<h4 class="text-lg font-bold">Mining Pool</h4>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.miningPoolStats']()}
|
||||
bind:value={$settings.miningPoolStats}
|
||||
/>
|
||||
{#if $settings.miningPoolStats}
|
||||
<div class="form-control flex flex-col items-start justify-between md:flex-row">
|
||||
<label class="label" for="miningPoolName">
|
||||
<span class="label-text">{m['section.settings.miningPoolName']()}</span>
|
||||
</label>
|
||||
<div class="flex grid grid-cols-1 flex-row gap-2 md:grid-cols-2">
|
||||
{#each miningPoolOptions as option}
|
||||
<Radio
|
||||
label={option.label}
|
||||
value={option.value}
|
||||
name="miningPoolName"
|
||||
bind:group={$settings.miningPoolName}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $settings.miningPoolName === 'local_public_pool'}
|
||||
<SettingsInputField
|
||||
label={m['section.settings.localPoolEndpoint']()}
|
||||
bind:value={$settings.localPoolEndpoint}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<SettingsInputField
|
||||
label={m['section.settings.miningPoolUser']()}
|
||||
bind:value={$settings.miningPoolUser}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<h4 class="text-lg font-bold">Nostr</h4>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.nostrZapNotify']()}
|
||||
bind:value={$settings.nostrZapNotify}
|
||||
/>
|
||||
|
||||
{#if $settings.nostrZapNotify}
|
||||
<SettingsInputField
|
||||
label={m['section.settings.nostrRelay']()}
|
||||
bind:value={$settings.nostrRelay}
|
||||
/>
|
||||
|
||||
<SettingsInputField
|
||||
label={m['section.settings.nostrZapPubkey']()}
|
||||
bind:value={$settings.nostrZapPubkey}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection title={m['section.settings.section.system']()} open={showAll || !hideAll}>
|
||||
<CollapsibleSection title={m['section.settings.section.system']()}>
|
||||
<div class="grid gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.timezoneOffset']()}</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<select class="select select-bordered w-full">
|
||||
<option>Europe/Amsterdam</option>
|
||||
</select>
|
||||
<button class="btn">{m['auto-detect']()}</button>
|
||||
</div>
|
||||
<p class="mt-1 text-sm">{m['section.settings.tzOffsetHelpText']()}</p>
|
||||
</div>
|
||||
<TimezoneSelector
|
||||
selectedTimezone={$settings.tzString}
|
||||
change={(value: string) => ($settings.tzString = value)}
|
||||
helpText={''}
|
||||
/>
|
||||
|
||||
<div class="form-control">
|
||||
<div class="form-control flex justify-between">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.hostnamePrefix']()}</span>
|
||||
</label>
|
||||
<input type="text" class="input input-bordered w-full" value="btclock" />
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered input flex w-auto"
|
||||
bind:value={$settings.hostnamePrefix}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{m['section.settings.wpTimeout']()}</span>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="number" class="input input-bordered w-20" min="1" max="900" value="600" />
|
||||
<span>{m['time.seconds']()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<NumericInput
|
||||
label={m['section.settings.wpTimeout']()}
|
||||
bind:value={$settings.wpTimeout}
|
||||
min={1}
|
||||
required
|
||||
step={1}
|
||||
unit={m['time.seconds']()}
|
||||
/>
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.otaUpdates']()}
|
||||
bind:value={$settings.otaEnabled}
|
||||
/>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.enableMdns']()}
|
||||
bind:value={$settings.mdnsEnabled}
|
||||
/>
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.httpAuthEnabled']()}
|
||||
bind:value={$settings.httpAuthEnabled}
|
||||
/>
|
||||
|
||||
{#if $settings.httpAuthEnabled}
|
||||
<SettingsInputField
|
||||
label={m['section.settings.httpAuthUser']()}
|
||||
bind:value={$settings.httpAuthUser}
|
||||
/>
|
||||
|
||||
<SettingsInputField
|
||||
label={m['section.settings.httpAuthPass']()}
|
||||
bind:value={$settings.httpAuthPass}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<ToggleWrapper
|
||||
label={m['section.settings.enableDebugLog']()}
|
||||
bind:value={$settings.enableDebugLog}
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-between">
|
||||
<button class="btn btn-error">{m['button.reset']()}</button>
|
||||
<button class="btn btn-primary">{m['button.save']()}</button>
|
||||
</div>
|
||||
</CardContainer>
|
||||
|
||||
<div class="sticky right-0 bottom-0 left-0">
|
||||
<div class="navbar bg-base-100 flex justify-around">
|
||||
<button class="btn btn-error" type="button" onclick={handleReset}>{m['button.reset']()}</button>
|
||||
{#if $status.isOTAUpdating}
|
||||
<span class="text-center text-sm text-gray-500"
|
||||
>OTA Update in progress... Please wait until the update is complete.</span
|
||||
>
|
||||
{/if}
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
onclick={handleSave}
|
||||
disabled={$status.isOTAUpdating}>{m['button.save']()}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,80 +7,100 @@
|
|||
import { DataSourceType } from '$lib/types';
|
||||
import { toUptimestring } from '$lib/utils';
|
||||
|
||||
const screens = $settings.screens.map((screen) => ({
|
||||
id: screen.id,
|
||||
label: screen.name
|
||||
}));
|
||||
let screens: { id: number; label: string }[] = [];
|
||||
|
||||
settings.subscribe((settings) => {
|
||||
screens = settings.screens.map((screen) => ({
|
||||
id: screen.id,
|
||||
label: screen.name
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<CardContainer title={m['section.status.title']()}>
|
||||
<div class="mx-auto space-y-4">
|
||||
<div class="join">
|
||||
{#each screens as screen (screen.id)}
|
||||
<TabButton
|
||||
active={$status.currentScreen === screen.id}
|
||||
onClick={() => setActiveScreen(screen.id)}
|
||||
>
|
||||
{screen.label}
|
||||
</TabButton>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="join flex justify-center">
|
||||
{#each $settings.actCurrencies as currency (currency)}
|
||||
<CurrencyButton
|
||||
{currency}
|
||||
active={$status.currency === currency}
|
||||
onClick={() => setActiveCurrency(currency)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-center">
|
||||
<div class="w-3/4">
|
||||
<BTClock displays={$status.data} verticalDesc={$settings.verticalDesc} />
|
||||
{#if !$settings.isLoaded}
|
||||
<div class="flex justify-center">
|
||||
<div class="mx-auto flex w-3/4 flex-col items-center justify-center gap-4">
|
||||
<span class="loading loading-spinner loading-xl"></span>
|
||||
<p class="text-center text-lg font-bold">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mx-auto space-y-4">
|
||||
<div class="md:join">
|
||||
{#each screens as screen (screen.id)}
|
||||
<TabButton
|
||||
active={$status.currentScreen === screen.id}
|
||||
onClick={() => setActiveScreen(screen.id)}
|
||||
>
|
||||
{screen.label}
|
||||
</TabButton>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm text-gray-500">
|
||||
{m['section.status.screenCycle']()}: is {$status.timerRunning ? 'running' : 'stopped'}<br />
|
||||
{m['section.status.doNotDisturb']()}: {$status.dnd.enabled ? m['on']() : m['off']()}
|
||||
<small>
|
||||
{#if $status.dnd?.timeBasedEnabled}
|
||||
{m['section.status.timeBasedDnd']()} ( {$settings.dnd
|
||||
.startHour}:{$settings.dnd.startMinute.toString().padStart(2, '0')} - {$settings.dnd
|
||||
.endHour}:{$settings.dnd.endMinute.toString().padStart(2, '0')} )
|
||||
<div class="join flex justify-center">
|
||||
{#each $settings.actCurrencies as currency (currency)}
|
||||
<CurrencyButton
|
||||
{currency}
|
||||
active={$status.currency === currency}
|
||||
onClick={() => setActiveCurrency(currency)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-center">
|
||||
<div class="w-full md:w-3/4">
|
||||
<BTClock
|
||||
theme={$settings.invertColors ? 'light' : 'dark'}
|
||||
displays={$status.data}
|
||||
verticalDesc={$settings.verticalDesc}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm text-gray-500">
|
||||
{m['section.status.screenCycle']()}: is {$status.timerRunning ? 'running' : 'stopped'}<br />
|
||||
{m['section.status.doNotDisturb']()}: {$status.dnd.active ? m['on']() : m['off']()}
|
||||
<small>
|
||||
{#if $status.dnd?.timeBasedEnabled}
|
||||
{m['section.status.timeBasedDnd']()} ( {$settings.dnd
|
||||
.startHour}:{$settings.dnd.startMinute.toString().padStart(2, '0')} - {$settings.dnd
|
||||
.endHour}:{$settings.dnd.endMinute.toString().padStart(2, '0')} )
|
||||
{/if}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{#if $settings.dataSource === DataSourceType.NOSTR_SOURCE || $settings.nostrZapNotify}
|
||||
<Status
|
||||
text="Nostr Relay connection"
|
||||
status={$status.connectionStatus.nostr ? 'online' : 'offline'}
|
||||
/>
|
||||
{/if}
|
||||
</small>
|
||||
{#if $settings.dataSource === DataSourceType.THIRD_PARTY_SOURCE}
|
||||
<Status
|
||||
text={m['section.status.wsPriceConnection']()}
|
||||
status={$status.connectionStatus.price ? 'online' : 'offline'}
|
||||
/>
|
||||
<Status
|
||||
text={m['section.status.wsMempoolConnection']({ instance: $settings.mempoolInstance })}
|
||||
status={$status.connectionStatus.blocks ? 'online' : 'offline'}
|
||||
/>
|
||||
{:else}
|
||||
<Status
|
||||
text={m['section.status.wsDataConnection']()}
|
||||
status={$status.connectionStatus.V2 ? 'online' : 'offline'}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{#if $settings.dataSource === DataSourceType.NOSTR_SOURCE || $settings.nostrZapNotify}
|
||||
<Status text="Nostr Relay connection" status={$status.nostr ? 'online' : 'offline'} />
|
||||
{/if}
|
||||
{#if $settings.dataSource === DataSourceType.THIRD_PARTY_SOURCE}
|
||||
<Status
|
||||
text={m['section.status.wsPriceConnection']()}
|
||||
status={$status.connectionStatus.price ? 'online' : 'offline'}
|
||||
/>
|
||||
<Status
|
||||
text={m['section.status.wsMempoolConnection']({ instance: $settings.mempoolInstance })}
|
||||
status={$status.connectionStatus.blocks ? 'online' : 'offline'}
|
||||
/>
|
||||
{:else}
|
||||
<Status
|
||||
text={m['section.status.wsDataConnection']()}
|
||||
status={$status.connectionStatus.V2 ? 'online' : 'offline'}
|
||||
/>
|
||||
{/if}
|
||||
<div class="stats mt-4 flex justify-center shadow">
|
||||
<Stat
|
||||
title={m['section.status.memoryFree']()}
|
||||
value={`${Math.round($status.espFreeHeap / 1024)} / ${Math.round($status.espHeapSize / 1024)} KiB`}
|
||||
/>
|
||||
<Stat title={m['section.status.wifiSignalStrength']()} value={`${$status.rssi} dBm`} />
|
||||
<Stat title={m['section.status.uptime']()} value={`${toUptimestring($status.espUptime)}`} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats mt-4 flex justify-center shadow">
|
||||
<Stat
|
||||
title={m['section.status.memoryFree']()}
|
||||
value={`${Math.round($status.espFreeHeap / 1024)} / ${Math.round($status.espHeapSize / 1024)} KiB`}
|
||||
/>
|
||||
<Stat title={m['section.status.wifiSignalStrength']()} value={`${$status.rssi} dBm`} />
|
||||
<Stat title={m['section.status.uptime']()} value={`${toUptimestring($status.espUptime)}`} />
|
||||
</div>
|
||||
{/if}
|
||||
</CardContainer>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { CardContainer } from '$lib/components';
|
||||
import { CardContainer, Alert } from '$lib/components';
|
||||
import { settings } from '$lib/stores';
|
||||
import { restartClock, forceFullRefresh } from '$lib/clockControl';
|
||||
import { restartClock } from '$lib/clockControl';
|
||||
</script>
|
||||
|
||||
<CardContainer title="System Information">
|
||||
|
@ -22,7 +22,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{m['section.control.buildTime']()}</td>
|
||||
<td>{$settings.lastBuildTime}</td>
|
||||
<td>{new Date($settings.lastBuildTime * 1000).toLocaleString()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP</td>
|
||||
|
@ -36,6 +36,10 @@
|
|||
<td>{m['section.control.fwCommit']()}</td>
|
||||
<td class="text-xs">{$settings.gitRev}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WebUI commit</td>
|
||||
<td class="text-xs">{$settings.fsRev}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{m['section.control.hostname']()}</td>
|
||||
<td>{$settings.hostname}</td>
|
||||
|
@ -43,57 +47,11 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{#if $settings.fsRev !== $settings.gitRev}
|
||||
<Alert type="warning" message={m['section.system.firmwareOutdated']()} />
|
||||
{/if}
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button class="btn btn-error" onclick={restartClock}>{m['button.restart']()}</button>
|
||||
<button class="btn" onclick={forceFullRefresh}>{m['button.forceFullRefresh']()}</button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContainer>
|
||||
|
||||
<CardContainer title={m['section.control.firmwareUpdate']()} className="mt-4">
|
||||
<div>
|
||||
<p class="mb-2 text-sm">
|
||||
Latest Version: 3.3.5 - Release Date: 5/2/2025, 12:37:14 AM - <a
|
||||
href="#"
|
||||
class="link link-primary">{m['section.firmwareUpdater.viewRelease']()}</a
|
||||
>
|
||||
</p>
|
||||
<p class="text-success mb-4 text-sm">{m['section.firmwareUpdater.swUpToDate']()}</p>
|
||||
|
||||
<div class="form-control mb-4">
|
||||
<label class="label" for="firmwareFile">
|
||||
<span class="label-text">Firmware File (blib_s3_mini_213epd_firmware.bin)</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="file" class="file-input file-input-bordered w-full" id="firmwareFile" />
|
||||
<button class="btn btn-primary">{m['section.control.firmwareUpdate']()}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label" for="webuiFile">
|
||||
<span class="label-text">WebUI File (littlefs_4MB.bin)</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="file" class="file-input file-input-bordered w-full" id="webuiFile" />
|
||||
<button class="btn btn-primary">Update WebUI</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/></svg
|
||||
>
|
||||
<span>{m['section.firmwareUpdater.firmwareUpdateText']()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContainer>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue