mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-27 18:49:49 +00:00
Compare commits
6 Commits
copilot/fi
...
upgrade/sv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d128700c1 | ||
|
|
6743750d8f | ||
|
|
640e957065 | ||
|
|
d4f11c0412 | ||
|
|
01acc6d6e8 | ||
|
|
e89bb53941 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Set up node
|
- name: Set up node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '19.x'
|
node-version: '22.x'
|
||||||
- name: Build with node
|
- name: Build with node
|
||||||
run: |
|
run: |
|
||||||
cd lib/SvelteUi/app
|
cd lib/SvelteUi/app
|
||||||
|
|||||||
2
.github/workflows/release-deploy-env.yml
vendored
2
.github/workflows/release-deploy-env.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
|||||||
- name: Set up node
|
- name: Set up node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '19.x'
|
node-version: '22.x'
|
||||||
- name: Build with node
|
- name: Build with node
|
||||||
run: |
|
run: |
|
||||||
cd lib/SvelteUi/app
|
cd lib/SvelteUi/app
|
||||||
|
|||||||
59
lib/SvelteUi/app/README.md
Normal file
59
lib/SvelteUi/app/README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# SvelteUi App
|
||||||
|
|
||||||
|
Web interface for AMS Reader firmware built with Svelte 5 and Vite 6.
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js 20.x or 22.x LTS (required for Vite 6)
|
||||||
|
- npm
|
||||||
|
|
||||||
|
### Local Development Configuration
|
||||||
|
|
||||||
|
To develop against your AMS reader device, you need to configure the proxy target:
|
||||||
|
|
||||||
|
1. Copy the example config file:
|
||||||
|
```bash
|
||||||
|
cp vite.config.local.example.js vite.config.local.js
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `vite.config.local.js` and update the IP address to match your device:
|
||||||
|
```javascript
|
||||||
|
export default {
|
||||||
|
proxyTarget: "http://192.168.1.100" // Your device's IP
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The `vite.config.local.js` file is gitignored, so your personal settings won't be committed.
|
||||||
|
|
||||||
|
### Running Development Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The dev server will proxy API requests to your configured device IP.
|
||||||
|
|
||||||
|
### Building for Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
The build output will be in the `dist/` directory.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `src/` - Application source code
|
||||||
|
- `routes/` - Page components using svelte-spa-router
|
||||||
|
- `lib/` - Shared components and utilities
|
||||||
|
- `public/` - Static assets (favicon, etc.)
|
||||||
|
- `dist/` - Build output (not committed to git)
|
||||||
|
|
||||||
|
## Key Technologies
|
||||||
|
|
||||||
|
- **Svelte 5.17.0** - UI framework
|
||||||
|
- **Vite 6.0.7** - Build tool
|
||||||
|
- **svelte-spa-router 4.0.1** - Hash-based routing
|
||||||
|
- **Tailwind CSS** - Styling
|
||||||
2
lib/SvelteUi/app/dist/index.css
vendored
2
lib/SvelteUi/app/dist/index.css
vendored
File diff suppressed because one or more lines are too long
3
lib/SvelteUi/app/dist/index.html
vendored
3
lib/SvelteUi/app/dist/index.html
vendored
@@ -8,10 +8,9 @@
|
|||||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||||
<title>AMS reader</title>
|
<title>AMS reader</title>
|
||||||
<script type="module" crossorigin src="/index.js"></script>
|
<script type="module" crossorigin src="/index.js"></script>
|
||||||
<link rel="stylesheet" href="/index.css">
|
<link rel="stylesheet" crossorigin href="/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 dark:bg-gray-900">
|
<body class="bg-gray-100 dark:bg-gray-900">
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
12
lib/SvelteUi/app/dist/index.js
vendored
12
lib/SvelteUi/app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
2891
lib/SvelteUi/app/package-lock.json
generated
2891
lib/SvelteUi/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,28 +9,22 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"overrides": {
|
|
||||||
"svelte-navigator": {
|
|
||||||
"svelte": ">=4.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.2",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"http-proxy-middleware": "^2.0.9",
|
"http-proxy-middleware": "^2.0.9",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^5.17.0",
|
||||||
"svelte-navigator": "^3.2.2",
|
"svelte-spa-router": "^4.0.1",
|
||||||
"svelte-preprocess": "^5.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"svelte-qrcode": "^1.0.0",
|
"svelte-qrcode": "^1.0.0",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"vite": "^4.5.14"
|
"vite": "^6.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano": "^5.1.15",
|
"cssnano": "^5.1.15",
|
||||||
"esbuild": ">=0.25.0",
|
"esbuild": ">=0.25.0"
|
||||||
"ipaddr.js": "^2.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
lib/SvelteUi/app/public/favicon.svg
Normal file
19
lib/SvelteUi/app/public/favicon.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||||
|
<title>Amsleser</title>
|
||||||
|
<g transform="translate(-29.5,-83)">
|
||||||
|
<circle r="4.8016944" cy="123.56455" cx="55.064552"
|
||||||
|
style="fill:none;stroke:#045c7c;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path d="m 41.298717,103.9049 a 24,24 0 0 1 27.531669,0"
|
||||||
|
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path d="m 35.562952,95.713384 a 34,34 0 0 1 39.003199,-2e-6"
|
||||||
|
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<path d="m 47.034482,112.09642 a 14,14 0 0 1 16.06014,0"
|
||||||
|
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<circle r="3" cy="105.99158" cx="38.181862"
|
||||||
|
style="fill:none;stroke:#045c7c;stroke-width:2.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<circle r="3" cy="97.959579" cx="77.491386"
|
||||||
|
style="fill:none;stroke:#045c7c;stroke-width:2.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,49 +1,27 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Router, Route, navigate } from "svelte-navigator";
|
import Router from "svelte-spa-router";
|
||||||
import { getTariff, tariffStore, sysinfoStore, dataStore, importPricesStore, exportPricesStore, dayPlotStore, monthPlotStore, temperaturesStore, getSysinfo } from './lib/DataStores.js';
|
import { push } from "svelte-spa-router";
|
||||||
|
import { getTariff, sysinfoStore, dataStore, getSysinfo } from './lib/DataStores.js';
|
||||||
import { translationsStore, getTranslations } from "./lib/TranslationService.js";
|
import { translationsStore, getTranslations } from "./lib/TranslationService.js";
|
||||||
import Favicon from './assets/favicon.svg'; // Need this for the build
|
|
||||||
import Header from './lib/Header.svelte';
|
import Header from './lib/Header.svelte';
|
||||||
import Dashboard from './lib/Dashboard.svelte';
|
import DashboardRoute from './routes/DashboardRoute.svelte';
|
||||||
import ConfigurationPanel from './lib/ConfigurationPanel.svelte';
|
import ConfigurationRoute from './routes/ConfigurationRoute.svelte';
|
||||||
import StatusPage from './lib/StatusPage.svelte';
|
import StatusRoute from './routes/StatusRoute.svelte';
|
||||||
import VendorPanel from './lib/VendorPanel.svelte';
|
import PriceConfigRoute from './routes/PriceConfigRoute.svelte';
|
||||||
import SetupPanel from './lib/SetupPanel.svelte';
|
import MqttCaRoute from './routes/MqttCaRoute.svelte';
|
||||||
|
import MqttCertRoute from './routes/MqttCertRoute.svelte';
|
||||||
|
import MqttKeyRoute from './routes/MqttKeyRoute.svelte';
|
||||||
|
import ConsentRoute from './routes/ConsentRoute.svelte';
|
||||||
|
import SetupRoute from './routes/SetupRoute.svelte';
|
||||||
|
import VendorRoute from './routes/VendorRoute.svelte';
|
||||||
|
import EditDayRoute from './routes/EditDayRoute.svelte';
|
||||||
|
import EditMonthRoute from './routes/EditMonthRoute.svelte';
|
||||||
import Mask from './lib/Mask.svelte';
|
import Mask from './lib/Mask.svelte';
|
||||||
import FileUploadComponent from "./lib/FileUploadComponent.svelte";
|
|
||||||
import ConsentComponent from "./lib/ConsentComponent.svelte";
|
|
||||||
import PriceConfig from "./lib/PriceConfig.svelte";
|
|
||||||
import DataEdit from "./lib/DataEdit.svelte";
|
|
||||||
import { updateRealtime } from "./lib/RealtimeStore.js";
|
import { updateRealtime } from "./lib/RealtimeStore.js";
|
||||||
|
|
||||||
let basepath = document.getElementsByTagName('base')[0].getAttribute("href");
|
let basepath = document.getElementsByTagName('base')[0].getAttribute("href");
|
||||||
if(!basepath) basepath = "/";
|
if(!basepath) basepath = "/";
|
||||||
|
|
||||||
let importPrices;
|
|
||||||
importPricesStore.subscribe(update => {
|
|
||||||
importPrices = update;
|
|
||||||
});
|
|
||||||
|
|
||||||
let exportPrices;
|
|
||||||
exportPricesStore.subscribe(update => {
|
|
||||||
exportPrices = update;
|
|
||||||
});
|
|
||||||
|
|
||||||
let dayPlot;
|
|
||||||
dayPlotStore.subscribe(update => {
|
|
||||||
dayPlot = update;
|
|
||||||
});
|
|
||||||
|
|
||||||
let monthPlot;
|
|
||||||
monthPlotStore.subscribe(update => {
|
|
||||||
monthPlot = update;
|
|
||||||
});
|
|
||||||
|
|
||||||
let temperatures;
|
|
||||||
temperaturesStore.subscribe(update => {
|
|
||||||
temperatures = update;
|
|
||||||
});
|
|
||||||
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
translationsStore.subscribe(update => {
|
translationsStore.subscribe(update => {
|
||||||
translations = update;
|
translations = update;
|
||||||
@@ -56,11 +34,11 @@
|
|||||||
sysinfoStore.subscribe(update => {
|
sysinfoStore.subscribe(update => {
|
||||||
sysinfo = update;
|
sysinfo = update;
|
||||||
if(sysinfo.vndcfg === false) {
|
if(sysinfo.vndcfg === false) {
|
||||||
navigate(basepath + "vendor");
|
push("/vendor");
|
||||||
} else if(sysinfo.usrcfg === false) {
|
} else if(sysinfo.usrcfg === false) {
|
||||||
navigate(basepath + "setup");
|
push("/setup");
|
||||||
} else if(sysinfo.fwconsent === 0) {
|
} else if(sysinfo.fwconsent === 0) {
|
||||||
navigate(basepath + "consent");
|
push("/consent");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sysinfo.ui.k === 1) {
|
if(sysinfo.ui.k === 1) {
|
||||||
@@ -94,53 +72,26 @@
|
|||||||
updateRealtime(update);
|
updateRealtime(update);
|
||||||
});
|
});
|
||||||
|
|
||||||
let tariffData = {};
|
|
||||||
tariffStore.subscribe(update => {
|
|
||||||
tariffData = update;
|
|
||||||
});
|
|
||||||
getTariff();
|
getTariff();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container mx-auto m-3">
|
<div class="container mx-auto m-3">
|
||||||
<Router basepath={basepath}>
|
<Header data={data} basepath={basepath}/>
|
||||||
<Header data={data} basepath={basepath}/>
|
|
||||||
<Route path="/">
|
<Router routes={{
|
||||||
<Dashboard data={data} sysinfo={sysinfo} importPrices={importPrices} exportPrices={exportPrices} dayPlot={dayPlot} monthPlot={monthPlot} temperatures={temperatures} translations={translations} tariffData={tariffData}/>
|
'/': DashboardRoute,
|
||||||
</Route>
|
'/configuration': ConfigurationRoute,
|
||||||
<Route path="/configuration">
|
'/priceconfig': PriceConfigRoute,
|
||||||
<ConfigurationPanel sysinfo={sysinfo} basepath={basepath} data={data}/>
|
'/status': StatusRoute,
|
||||||
</Route>
|
'/mqtt-ca': MqttCaRoute,
|
||||||
<Route path="/priceconfig">
|
'/mqtt-cert': MqttCertRoute,
|
||||||
<PriceConfig basepath={basepath}/>
|
'/mqtt-key': MqttKeyRoute,
|
||||||
</Route>
|
'/consent': ConsentRoute,
|
||||||
<Route path="/status">
|
'/setup': SetupRoute,
|
||||||
<StatusPage sysinfo={sysinfo} data={data}/>
|
'/vendor': VendorRoute,
|
||||||
</Route>
|
'/edit-day': EditDayRoute,
|
||||||
<Route path="/mqtt-ca">
|
'/edit-month': EditMonthRoute,
|
||||||
<FileUploadComponent title="CA" action="/mqtt-ca"/>
|
}} />
|
||||||
</Route>
|
|
||||||
<Route path="/mqtt-cert">
|
|
||||||
<FileUploadComponent title="certificate" action="/mqtt-cert"/>
|
|
||||||
</Route>
|
|
||||||
<Route path="/mqtt-key">
|
|
||||||
<FileUploadComponent title="private key" action="/mqtt-key"/>
|
|
||||||
</Route>
|
|
||||||
<Route path="/consent">
|
|
||||||
<ConsentComponent sysinfo={sysinfo} basepath={basepath}/>
|
|
||||||
</Route>
|
|
||||||
<Route path="/setup">
|
|
||||||
<SetupPanel sysinfo={sysinfo}/>
|
|
||||||
</Route>
|
|
||||||
<Route path="/vendor">
|
|
||||||
<VendorPanel sysinfo={sysinfo} basepath={basepath}/>
|
|
||||||
</Route>
|
|
||||||
<Route path="/edit-day">
|
|
||||||
<DataEdit prefix="UTC Hour" data={dayPlot} url="/dayplot" basepath={basepath}/>
|
|
||||||
</Route>
|
|
||||||
<Route path="/edit-month">
|
|
||||||
<DataEdit prefix="Day" data={monthPlot} url="/monthplot" basepath={basepath}/>
|
|
||||||
</Route>
|
|
||||||
</Router>
|
|
||||||
|
|
||||||
{#if sysinfo.booting}
|
{#if sysinfo.booting}
|
||||||
{#if sysinfo.trying}
|
{#if sysinfo.trying}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Link } from "svelte-navigator";
|
|
||||||
import { tooltip } from './tooltip';
|
import { tooltip } from './tooltip';
|
||||||
|
|
||||||
export let config;
|
export let config;
|
||||||
@@ -47,7 +46,7 @@
|
|||||||
{#if config.link}
|
{#if config.link}
|
||||||
<div class="text-xs text-right">
|
<div class="text-xs text-right">
|
||||||
{#if config.link.route}
|
{#if config.link.route}
|
||||||
<Link to={config.link.url}>{config.link.text}</Link>
|
<a href={"#" + config.link.url}>{config.link.text}</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a href={config.link.url} target={config.link.target}>{config.link.text}</a>
|
<a href={config.link.url} target={config.link.target}>{config.link.text}</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { translationsStore } from './TranslationService';
|
import { translationsStore } from './TranslationService';
|
||||||
import { navigate } from 'svelte-navigator';
|
import { push } from 'svelte-spa-router';
|
||||||
import Mask from './Mask.svelte'
|
import Mask from './Mask.svelte'
|
||||||
|
|
||||||
export let prefix;
|
export let prefix;
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
let res = (await response.json())
|
let res = (await response.json())
|
||||||
|
|
||||||
saving = false;
|
saving = false;
|
||||||
navigate(basepath);
|
push(basepath);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
{#each importElements as el}
|
{#each importElements as el}
|
||||||
<label class="flex w-60 m-1">
|
<label class="flex w-60 m-1">
|
||||||
<span class="in-pre">{el.name}</span>
|
<span class="in-pre">{el.name}</span>
|
||||||
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.01" class="in-txt w-full text-right"/>
|
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.001" class="in-txt w-full text-right"/>
|
||||||
<span class="in-post">kWh</span>
|
<span class="in-post">kWh</span>
|
||||||
</label>
|
</label>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
{#each exportElements as el}
|
{#each exportElements as el}
|
||||||
<label class="flex w-60 m-1">
|
<label class="flex w-60 m-1">
|
||||||
<span class="in-pre">{el.name}</span>
|
<span class="in-pre">{el.name}</span>
|
||||||
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.01" class="in-txt w-full text-right"/>
|
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.001" class="in-txt w-full text-right"/>
|
||||||
<span class="in-post">kWh</span>
|
<span class="in-post">kWh</span>
|
||||||
</label>
|
</label>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Link } from "svelte-navigator";
|
|
||||||
import { sysinfoStore } from './DataStores.js';
|
import { sysinfoStore } from './DataStores.js';
|
||||||
import { upgrade, upgradeWarningText } from './UpgradeHelper';
|
import { upgrade, upgradeWarningText } from './UpgradeHelper';
|
||||||
import { boardtype, isBusPowered, wiki, bcol } from './Helpers.js';
|
import { boardtype, isBusPowered, wiki, bcol } from './Helpers.js';
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
<nav class="hdr">
|
<nav class="hdr">
|
||||||
<div class="flex flex-wrap space-x-4 text-sm text-gray-300">
|
<div class="flex flex-wrap space-x-4 text-sm text-gray-300">
|
||||||
<div class="flex text-lg text-gray-100 p-2">
|
<div class="flex text-lg text-gray-100 p-2">
|
||||||
<Link to="/">AMS reader <span>{sysinfo.version}</span></Link>
|
<a href={basepath}>AMS reader <span>{sysinfo.version}</span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none my-auto p-2 flex space-x-4">
|
<div class="flex-none my-auto p-2 flex space-x-4">
|
||||||
<div class="flex-none my-auto"><Uptime epoch={data.u}/></div>
|
<div class="flex-none my-auto"><Uptime epoch={data.u}/></div>
|
||||||
@@ -79,10 +78,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if sysinfo.vndcfg && sysinfo.usrcfg}
|
{#if sysinfo.vndcfg && sysinfo.usrcfg}
|
||||||
<div class="flex-none px-1 mt-1" title={translations.header?.config ?? ""}>
|
<div class="flex-none px-1 mt-1" title={translations.header?.config ?? ""}>
|
||||||
<Link to="/configuration"><GearIcon/></Link>
|
<a href="#/configuration"><GearIcon/></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none px-1 mt-1" title={translations.header?.status ?? ""}>
|
<div class="flex-none px-1 mt-1" title={translations.header?.status ?? ""}>
|
||||||
<Link to="/status"><InfoIcon/></Link>
|
<a href="#/status"><InfoIcon/></a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex-none px-1 mt-1" title={translations.header?.doc ?? ""}>
|
<div class="flex-none px-1 mt-1" title={translations.header?.doc ?? ""}>
|
||||||
|
|||||||
@@ -41,22 +41,22 @@
|
|||||||
for(i = 0; i < tariffData.p.length; i++) {
|
for(i = 0; i < tariffData.p.length; i++) {
|
||||||
let peak = tariffData.p[i];
|
let peak = tariffData.p[i];
|
||||||
|
|
||||||
let title = "";
|
let peakTitle = "";
|
||||||
let daylabel = "-";
|
let daylabel = "-";
|
||||||
if(peak.d > 0) {
|
if(peak.d > 0) {
|
||||||
daylabel = zeropad(peak.d) + ".";
|
daylabel = zeropad(peak.d) + ".";
|
||||||
title = zeropad(peak.d) + "." + (translations.months ? translations.months?.[new Date().getMonth()] : zeropad(new Date().getMonth()+1));
|
peakTitle = zeropad(peak.d) + "." + (translations.months ? translations.months?.[new Date().getMonth()] : zeropad(new Date().getMonth()+1));
|
||||||
if(tariffData.p.length < 4) {
|
if(tariffData.p.length < 4) {
|
||||||
daylabel = title;
|
daylabel = peakTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!isNaN(peak.h))
|
if(!isNaN(peak.h))
|
||||||
title = title + ' ' + zeropad(peak.h) + ':00';
|
peakTitle = peakTitle + ' ' + zeropad(peak.h) + ':00';
|
||||||
title = title + ': ' + peak.v.toFixed(2) + ' kWh';
|
peakTitle = peakTitle + ': ' + peak.v.toFixed(2) + ' kWh';
|
||||||
points.push({
|
points.push({
|
||||||
label: peak.v.toFixed(2),
|
label: peak.v.toFixed(2),
|
||||||
value: peak.v,
|
value: peak.v,
|
||||||
title: title,
|
title: peakTitle,
|
||||||
color: dark ? '#5c2da5' : '#7c3aed'
|
color: dark ? '#5c2da5' : '#7c3aed'
|
||||||
});
|
});
|
||||||
xTicks.push({
|
xTicks.push({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import "./app.postcss";
|
import "./app.postcss";
|
||||||
|
import { mount } from "svelte";
|
||||||
import App from "./App.svelte";
|
import App from "./App.svelte";
|
||||||
|
|
||||||
const app = new App({
|
const app = mount(App, {
|
||||||
target: document.getElementById("app"),
|
target: document.getElementById("app"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getConfiguration, configurationStore } from './ConfigurationStore'
|
import { getConfiguration, configurationStore } from '../lib/ConfigurationStore'
|
||||||
import { sysinfoStore, networksStore } from './DataStores.js';
|
import { sysinfoStore, networksStore, dataStore } from '../lib/DataStores.js';
|
||||||
import fetchWithTimeout from './fetchWithTimeout';
|
import fetchWithTimeout from '../lib/fetchWithTimeout';
|
||||||
import { translationsStore } from './TranslationService';
|
import { translationsStore } from '../lib/TranslationService';
|
||||||
import { wiki, ipPattern, asciiPattern, asciiPatternExt, charAndNumPattern, hexPattern, numPattern, isBusPowered } from './Helpers.js';
|
import { wiki, ipPattern, asciiPattern, asciiPatternExt, charAndNumPattern, hexPattern, numPattern, isBusPowered } from '../lib/Helpers.js';
|
||||||
import UartSelectOptions from './UartSelectOptions.svelte';
|
import UartSelectOptions from '../lib/UartSelectOptions.svelte';
|
||||||
import Mask from './Mask.svelte'
|
import Mask from '../lib/Mask.svelte'
|
||||||
import Badge from './Badge.svelte';
|
import Badge from '../lib/Badge.svelte';
|
||||||
import CountrySelectOptions from './CountrySelectOptions.svelte';
|
import CountrySelectOptions from '../lib/CountrySelectOptions.svelte';
|
||||||
import { Link, navigate } from 'svelte-navigator';
|
import { push } from 'svelte-spa-router';
|
||||||
import SubnetOptions from './SubnetOptions.svelte';
|
import SubnetOptions from '../lib/SubnetOptions.svelte';
|
||||||
import QrCode from 'svelte-qrcode';
|
import QrCode from 'svelte-qrcode';
|
||||||
|
|
||||||
export let basepath = "/";
|
let basepath = "/";
|
||||||
export let sysinfo = {};
|
let sysinfo = {};
|
||||||
export let data;
|
let data;
|
||||||
|
|
||||||
|
sysinfoStore.subscribe(v => sysinfo = v);
|
||||||
|
dataStore.subscribe(v => data = v);
|
||||||
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
translationsStore.subscribe(update => {
|
translationsStore.subscribe(update => {
|
||||||
translations = update;
|
translations = update;
|
||||||
@@ -150,7 +153,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
saving = false;
|
saving = false;
|
||||||
navigate(basepath);
|
push(basepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reboot() {
|
async function reboot() {
|
||||||
@@ -336,7 +339,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<Link to="/priceconfig" class="text-blue-600 hover:text-blue-800">{translations.conf?.price?.conf ?? "Configure"}</Link>
|
<a href="#/priceconfig" class="text-blue-600 hover:text-blue-800">{translations.conf?.price?.conf ?? "Configure"}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<label><input type="checkbox" name="pe" value="true" bind:checked={configuration.p.e} class="rounded mb-1"/> {translations.conf?.price?.enabled ?? "Enabled"}</label>
|
<label><input type="checkbox" name="pe" value="true" bind:checked={configuration.p.e} class="rounded mb-1"/> {translations.conf?.price?.enabled ?? "Enabled"}</label>
|
||||||
@@ -603,28 +606,28 @@
|
|||||||
<div class="my-1 flex">
|
<div class="my-1 flex">
|
||||||
<span class="flex pr-2">
|
<span class="flex pr-2">
|
||||||
{#if configuration.q.s.c}
|
{#if configuration.q.s.c}
|
||||||
<span class="bd-on"><Link to="/mqtt-ca">{translations.conf?.mqtt?.ca_ok ?? "CA OK"}</Link></span>
|
<span class="bd-on"><a href="#/mqtt-ca">{translations.conf?.mqtt?.ca_ok ?? "CA OK"}</a></span>
|
||||||
<span class="bd-off" on:click={askDeleteCa} on:keypress={askDeleteCa}>🗑</span>
|
<span class="bd-off" on:click={askDeleteCa} on:keypress={askDeleteCa}>🗑</span>
|
||||||
{:else}
|
{:else}
|
||||||
<Link to="/mqtt-ca"><Badge color="blue" text={translations.conf?.mqtt?.btn_ca_upload ?? "Upload CA"} title={translations.conf?.mqtt?.title_ca ?? ""}/></Link>
|
<a href="#/mqtt-ca"><Badge color="blue" text={translations.conf?.mqtt?.btn_ca_upload ?? "Upload CA"} title={translations.conf?.mqtt?.title_ca ?? ""}/></a>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="flex pr-2">
|
<span class="flex pr-2">
|
||||||
{#if configuration.q.s.r}
|
{#if configuration.q.s.r}
|
||||||
<span class="bd-on"><Link to="/mqtt-cert">{translations.conf?.mqtt?.crt_ok ?? "Cert OK"}</Link></span>
|
<span class="bd-on"><a href="#/mqtt-cert">{translations.conf?.mqtt?.crt_ok ?? "Cert OK"}</a></span>
|
||||||
<span class="bd-off" on:click={askDeleteCert} on:keypress={askDeleteCert}>🗑</span>
|
<span class="bd-off" on:click={askDeleteCert} on:keypress={askDeleteCert}>🗑</span>
|
||||||
{:else}
|
{:else}
|
||||||
<Link to="/mqtt-cert"><Badge color="blue" text={translations.conf?.mqtt?.btn_crt_upload ?? "Upload cert"} title={translations.conf?.mqtt?.title_crt ?? ""}/></Link>
|
<a href="#/mqtt-cert"><Badge color="blue" text={translations.conf?.mqtt?.btn_crt_upload ?? "Upload cert"} title={translations.conf?.mqtt?.title_crt ?? ""}/></a>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="flex pr-2">
|
<span class="flex pr-2">
|
||||||
{#if configuration.q.s.k}
|
{#if configuration.q.s.k}
|
||||||
<span class="bd-on"><Link to="/mqtt-key">{translations.conf?.mqtt?.key_ok ?? "Key OK"}</Link></span>
|
<span class="bd-on"><a href="#/mqtt-key">{translations.conf?.mqtt?.key_ok ?? "Key OK"}</a></span>
|
||||||
<span class="bd-off" on:click={askDeleteKey} on:keypress={askDeleteKey}>🗑</span>
|
<span class="bd-off" on:click={askDeleteKey} on:keypress={askDeleteKey}>🗑</span>
|
||||||
{:else}
|
{:else}
|
||||||
<Link to="/mqtt-key"><Badge color="blue" text={translations.conf?.mqtt?.btn_key_upload ?? "Upload key"} title={translations.conf?.mqtt?.title_key ?? ""}/></Link>
|
<a href="#/mqtt-key"><Badge color="blue" text={translations.conf?.mqtt?.btn_key_upload ?? "Upload key"} title={translations.conf?.mqtt?.title_key ?? ""}/></a>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
<script>
|
<script>
|
||||||
import { sysinfoStore } from './DataStores.js';
|
import { sysinfoStore } from '../lib/DataStores.js';
|
||||||
import { translationsStore } from './TranslationService.js';
|
import { translationsStore } from '../lib/TranslationService.js';
|
||||||
import Mask from './Mask.svelte'
|
import Mask from '../lib/Mask.svelte'
|
||||||
import { navigate } from 'svelte-navigator';
|
import { push } from 'svelte-spa-router';
|
||||||
import { wiki } from './Helpers';
|
|
||||||
|
|
||||||
export let basepath = "/";
|
let basepath = "/";
|
||||||
export let sysinfo = {};
|
let sysinfo = {};
|
||||||
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
translationsStore.subscribe(update => {
|
translationsStore.subscribe(update => {
|
||||||
translations = update;
|
translations = update;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sysinfoStore.subscribe(v => sysinfo = v);
|
||||||
|
|
||||||
let loadingOrSaving = false;
|
let loadingOrSaving = false;
|
||||||
|
|
||||||
async function handleSubmit(e) {
|
async function handleSubmit(e) {
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
s.booting = res.reboot;
|
s.booting = res.reboot;
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
navigate(basepath);
|
push(basepath);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -1,26 +1,38 @@
|
|||||||
<script>
|
<script>
|
||||||
import { ampcol, exportcol, metertype, uiVisibility, formatUnit, fmtnum, formatCurrency } from './Helpers.js';
|
import { ampcol, exportcol, metertype, uiVisibility, formatUnit, fmtnum, formatCurrency } from '../lib/Helpers.js';
|
||||||
import PowerGauge from './PowerGauge.svelte';
|
import PowerGauge from '../lib/PowerGauge.svelte';
|
||||||
import VoltPlot from './VoltPlot.svelte';
|
import VoltPlot from '../lib/VoltPlot.svelte';
|
||||||
import ReactiveData from './ReactiveData.svelte';
|
import ReactiveData from '../lib/ReactiveData.svelte';
|
||||||
import AccountingData from './AccountingData.svelte';
|
import AccountingData from '../lib/AccountingData.svelte';
|
||||||
import PricePlot from './PricePlot.svelte';
|
import PricePlot from '../lib/PricePlot.svelte';
|
||||||
import DayPlot from './DayPlot.svelte';
|
import DayPlot from '../lib/DayPlot.svelte';
|
||||||
import MonthPlot from './MonthPlot.svelte';
|
import MonthPlot from '../lib/MonthPlot.svelte';
|
||||||
import TemperaturePlot from './TemperaturePlot.svelte';
|
import TemperaturePlot from '../lib/TemperaturePlot.svelte';
|
||||||
import TariffPeakChart from './TariffPeakChart.svelte';
|
import TariffPeakChart from '../lib/TariffPeakChart.svelte';
|
||||||
import RealtimePlot from './RealtimePlot.svelte';
|
import RealtimePlot from '../lib/RealtimePlot.svelte';
|
||||||
import PerPhasePlot from './PerPhasePlot.svelte';
|
import PerPhasePlot from '../lib/PerPhasePlot.svelte';
|
||||||
|
import { dataStore, sysinfoStore, importPricesStore, exportPricesStore, dayPlotStore, monthPlotStore, temperaturesStore, tariffStore } from '../lib/DataStores.js';
|
||||||
|
import { translationsStore } from '../lib/TranslationService.js';
|
||||||
|
|
||||||
export let data = {}
|
let data = {}
|
||||||
export let sysinfo = {}
|
let sysinfo = {}
|
||||||
export let importPrices = {}
|
let importPrices = {}
|
||||||
export let exportPrices = {}
|
let exportPrices = {}
|
||||||
export let dayPlot = {}
|
let dayPlot = {}
|
||||||
export let monthPlot = {}
|
let monthPlot = {}
|
||||||
export let temperatures = {};
|
let temperatures = {};
|
||||||
export let translations = {};
|
let translations = {};
|
||||||
export let tariffData = {};
|
let tariffData = {};
|
||||||
|
|
||||||
|
dataStore.subscribe(v => data = v);
|
||||||
|
sysinfoStore.subscribe(v => sysinfo = v);
|
||||||
|
importPricesStore.subscribe(v => importPrices = v);
|
||||||
|
exportPricesStore.subscribe(v => exportPrices = v);
|
||||||
|
dayPlotStore.subscribe(v => dayPlot = v);
|
||||||
|
monthPlotStore.subscribe(v => monthPlot = v);
|
||||||
|
temperaturesStore.subscribe(v => temperatures = v);
|
||||||
|
translationsStore.subscribe(v => translations = v);
|
||||||
|
tariffStore.subscribe(v => tariffData = v);
|
||||||
|
|
||||||
let it,et,threePhase, l1e, l2e, l3e;
|
let it,et,threePhase, l1e, l2e, l3e;
|
||||||
$: {
|
$: {
|
||||||
11
lib/SvelteUi/app/src/routes/EditDayRoute.svelte
Normal file
11
lib/SvelteUi/app/src/routes/EditDayRoute.svelte
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import DataEdit from '../lib/DataEdit.svelte';
|
||||||
|
import { dayPlotStore } from '../lib/DataStores.js';
|
||||||
|
|
||||||
|
let basepath = "/";
|
||||||
|
let dayPlot;
|
||||||
|
|
||||||
|
dayPlotStore.subscribe(v => dayPlot = v);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DataEdit prefix="UTC Hour" data={dayPlot} url="/dayplot" {basepath} />
|
||||||
11
lib/SvelteUi/app/src/routes/EditMonthRoute.svelte
Normal file
11
lib/SvelteUi/app/src/routes/EditMonthRoute.svelte
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import DataEdit from '../lib/DataEdit.svelte';
|
||||||
|
import { monthPlotStore } from '../lib/DataStores.js';
|
||||||
|
|
||||||
|
let basepath = "/";
|
||||||
|
let monthPlot;
|
||||||
|
|
||||||
|
monthPlotStore.subscribe(v => monthPlot = v);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DataEdit prefix="Day" data={monthPlot} url="/monthplot" {basepath} />
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import Mask from "./Mask.svelte";
|
import Mask from "../lib/Mask.svelte";
|
||||||
import { translationsStore } from "./TranslationService";
|
import { translationsStore } from "../lib/TranslationService";
|
||||||
|
|
||||||
export let action;
|
|
||||||
export let title;
|
|
||||||
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
translationsStore.subscribe(update => {
|
translationsStore.subscribe(update => {
|
||||||
@@ -15,12 +12,12 @@
|
|||||||
|
|
||||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||||
<div class="cnt">
|
<div class="cnt">
|
||||||
<strong>{translations.upload?.title ?? "Upload"} {title}</strong>
|
<strong>{translations.upload?.title ?? "Upload"} CA</strong>
|
||||||
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
||||||
<form action="{action}" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
<form action="/mqtt-ca" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||||
<input name="file" type="file">
|
<input name="file" type="file">
|
||||||
<div class="w-full text-right mt-4">
|
<div class="w-full text-right mt-4">
|
||||||
<button type="submit" class="btn-pri"><p class="mb-4">{translations.btn?.upload ?? "Upload"}</button>
|
<button type="submit" class="btn-pri">{translations.btn?.upload ?? "Upload"}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
25
lib/SvelteUi/app/src/routes/MqttCertRoute.svelte
Normal file
25
lib/SvelteUi/app/src/routes/MqttCertRoute.svelte
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import Mask from "../lib/Mask.svelte";
|
||||||
|
import { translationsStore } from "../lib/TranslationService";
|
||||||
|
|
||||||
|
let translations = {};
|
||||||
|
translationsStore.subscribe(update => {
|
||||||
|
translations = update;
|
||||||
|
});
|
||||||
|
|
||||||
|
let uploading = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||||
|
<div class="cnt">
|
||||||
|
<strong>{translations.upload?.title ?? "Upload"} certificate</strong>
|
||||||
|
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
||||||
|
<form action="/mqtt-cert" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||||
|
<input name="file" type="file">
|
||||||
|
<div class="w-full text-right mt-4">
|
||||||
|
<button type="submit" class="btn-pri">{translations.btn?.upload ?? "Upload"}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Mask active={uploading} message={translations.upload?.mask ?? "Uploading"}/>
|
||||||
25
lib/SvelteUi/app/src/routes/MqttKeyRoute.svelte
Normal file
25
lib/SvelteUi/app/src/routes/MqttKeyRoute.svelte
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script>
|
||||||
|
import Mask from "../lib/Mask.svelte";
|
||||||
|
import { translationsStore } from "../lib/TranslationService";
|
||||||
|
|
||||||
|
let translations = {};
|
||||||
|
translationsStore.subscribe(update => {
|
||||||
|
translations = update;
|
||||||
|
});
|
||||||
|
|
||||||
|
let uploading = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||||
|
<div class="cnt">
|
||||||
|
<strong>{translations.upload?.title ?? "Upload"} private key</strong>
|
||||||
|
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
||||||
|
<form action="/mqtt-key" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||||
|
<input name="file" type="file">
|
||||||
|
<div class="w-full text-right mt-4">
|
||||||
|
<button type="submit" class="btn-pri">{translations.btn?.upload ?? "Upload"}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Mask active={uploading} message={translations.upload?.mask ?? "Uploading"}/>
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { priceConfigStore, getPriceConfig } from './ConfigurationStore'
|
import { priceConfigStore, getPriceConfig } from '../lib/ConfigurationStore'
|
||||||
import { translationsStore } from './TranslationService';
|
import { translationsStore } from '../lib/TranslationService';
|
||||||
import { wiki, zeropad } from './Helpers.js';
|
import { wiki, zeropad } from '../lib/Helpers.js';
|
||||||
import Mask from './Mask.svelte'
|
import Mask from '../lib/Mask.svelte'
|
||||||
import { navigate } from 'svelte-navigator';
|
import { push } from 'svelte-spa-router';
|
||||||
|
|
||||||
export let basepath = "/";
|
|
||||||
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
translationsStore.subscribe(update => {
|
translationsStore.subscribe(update => {
|
||||||
@@ -53,7 +51,7 @@
|
|||||||
let res = (await response.json())
|
let res = (await response.json())
|
||||||
|
|
||||||
saving = false;
|
saving = false;
|
||||||
navigate(basepath + "configuration");
|
push("/configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
let toggleDay = function(arr, day) {
|
let toggleDay = function(arr, day) {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { sysinfoStore, networksStore } from './DataStores.js';
|
import { sysinfoStore, networksStore } from '../lib/DataStores.js';
|
||||||
import { translationsStore } from './TranslationService.js';
|
import { translationsStore } from '../lib/TranslationService.js';
|
||||||
import Mask from './Mask.svelte'
|
import Mask from '../lib/Mask.svelte'
|
||||||
import SubnetOptions from './SubnetOptions.svelte';
|
import SubnetOptions from '../lib/SubnetOptions.svelte';
|
||||||
import { scanForDevice, charAndNumPattern, asciiPatternExt, ipPattern } from './Helpers.js';
|
import { scanForDevice, charAndNumPattern, asciiPatternExt, ipPattern } from '../lib/Helpers.js';
|
||||||
|
|
||||||
let translations = {};
|
let translations = {};
|
||||||
translationsStore.subscribe(update => {
|
translationsStore.subscribe(update => {
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
networks = update;
|
networks = update;
|
||||||
});
|
});
|
||||||
|
|
||||||
export let sysinfo = {}
|
let sysinfo = {}
|
||||||
|
sysinfoStore.subscribe(v => sysinfo = v);
|
||||||
|
|
||||||
let staticIp = false;
|
let staticIp = false;
|
||||||
let connectionMode = 1;
|
let connectionMode = 1;
|
||||||
@@ -1,16 +1,70 @@
|
|||||||
<script>
|
<script>
|
||||||
import { metertype, boardtype, isBusPowered, getBaseChip, wiki } from './Helpers.js';
|
import { metertype, boardtype, isBusPowered, getBaseChip, wiki } from '../lib/Helpers.js';
|
||||||
import { getSysinfo, sysinfoStore } from './DataStores.js';
|
import { getSysinfo, sysinfoStore, dataStore } from '../lib/DataStores.js';
|
||||||
import { upgrade, upgradeWarningText } from './UpgradeHelper';
|
import { upgrade, upgradeWarningText } from '../lib/UpgradeHelper';
|
||||||
import { translationsStore } from './TranslationService.js';
|
import { translationsStore } from '../lib/TranslationService.js';
|
||||||
import { Link } from 'svelte-navigator';
|
import Clock from '../lib/Clock.svelte';
|
||||||
import Clock from './Clock.svelte';
|
import Mask from '../lib/Mask.svelte';
|
||||||
import Mask from './Mask.svelte';
|
import { scanForDevice } from '../lib/Helpers.js';
|
||||||
import { scanForDevice } from './Helpers.js';
|
|
||||||
import ipaddr from 'ipaddr.js';
|
|
||||||
|
|
||||||
export let data;
|
let data;
|
||||||
export let sysinfo;
|
let sysinfo;
|
||||||
|
|
||||||
|
dataStore.subscribe(v => data = v);
|
||||||
|
sysinfoStore.subscribe(v => sysinfo = v);
|
||||||
|
|
||||||
|
// Format IPv6 address to compact form (RFC 5952)
|
||||||
|
const formatIPv6 = (addr) => {
|
||||||
|
if (!addr) return addr;
|
||||||
|
|
||||||
|
// Split into groups
|
||||||
|
const groups = addr.toLowerCase().split(':');
|
||||||
|
|
||||||
|
// Remove leading zeros from each group
|
||||||
|
const normalized = groups.map(g => g.replace(/^0+/, '') || '0');
|
||||||
|
|
||||||
|
// Find longest sequence of consecutive zeros
|
||||||
|
let maxStart = -1, maxLen = 0;
|
||||||
|
let currStart = -1, currLen = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < normalized.length; i++) {
|
||||||
|
if (normalized[i] === '0') {
|
||||||
|
if (currStart === -1) currStart = i;
|
||||||
|
currLen++;
|
||||||
|
} else {
|
||||||
|
if (currLen > maxLen) {
|
||||||
|
maxStart = currStart;
|
||||||
|
maxLen = currLen;
|
||||||
|
}
|
||||||
|
currStart = -1;
|
||||||
|
currLen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check final sequence
|
||||||
|
if (currLen > maxLen) {
|
||||||
|
maxStart = currStart;
|
||||||
|
maxLen = currLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only compress if we have 2 or more consecutive zeros
|
||||||
|
if (maxLen > 1) {
|
||||||
|
const before = normalized.slice(0, maxStart);
|
||||||
|
const after = normalized.slice(maxStart + maxLen);
|
||||||
|
|
||||||
|
if (before.length === 0 && after.length === 0) {
|
||||||
|
return '::';
|
||||||
|
} else if (before.length === 0) {
|
||||||
|
return '::' + after.join(':');
|
||||||
|
} else if (after.length === 0) {
|
||||||
|
return before.join(':') + '::';
|
||||||
|
} else {
|
||||||
|
return before.join(':') + '::' + after.join(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.join(':');
|
||||||
|
};
|
||||||
|
|
||||||
let cfgItems = [{
|
let cfgItems = [{
|
||||||
name: 'WiFi',
|
name: 'WiFi',
|
||||||
@@ -73,11 +127,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let firmwareFileInput;
|
let firmwareFileInput;
|
||||||
let firmwareFiles = [];
|
let firmwareFiles = null;
|
||||||
let firmwareUploading = false;
|
let firmwareUploading = false;
|
||||||
|
|
||||||
let configFileInput;
|
let configFileInput;
|
||||||
let configFiles = [];
|
let configFiles = null;
|
||||||
let configUploading = false;
|
let configUploading = false;
|
||||||
|
|
||||||
getSysinfo();
|
getSysinfo();
|
||||||
@@ -119,7 +173,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if(configFiles.length == 1) {
|
if(configFiles && configFiles.length == 1) {
|
||||||
let file = configFiles[0];
|
let file = configFiles[0];
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
let parseConfigFile = ( e ) => {
|
let parseConfigFile = ( e ) => {
|
||||||
@@ -146,7 +200,7 @@
|
|||||||
{translations.status?.device?.chip ?? "Chip"}: {sysinfo.chip} {#if sysinfo.cpu}({sysinfo.cpu}MHz){/if}
|
{translations.status?.device?.chip ?? "Chip"}: {sysinfo.chip} {#if sysinfo.cpu}({sysinfo.cpu}MHz){/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
{translations.status?.device?.device ?? "Device"}: <Link to="/vendor">{boardtype(sysinfo.chip, sysinfo.board)}</Link>
|
{translations.status?.device?.device ?? "Device"}: <a href="#/vendor">{boardtype(sysinfo.chip, sysinfo.board)}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
{translations.status?.device?.mac ?? "MAC"}: {sysinfo.mac}
|
{translations.status?.device?.mac ?? "MAC"}: {sysinfo.mac}
|
||||||
@@ -169,9 +223,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if data?.a}
|
{#if data?.a}
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
<Link to="/consent">
|
<a href="#/consent">
|
||||||
<span class="btn-pri-sm">{translations.status?.device?.btn_consents ?? "Consents"}</span>
|
<span class="btn-pri-sm">{translations.status?.device?.btn_consents ?? "Consents"}</span>
|
||||||
</Link>
|
</a>
|
||||||
<button on:click={askReboot} class="btn-yellow-sm float-right">{translations.btn?.reboot ?? "Reboot"}</button>
|
<button on:click={askReboot} class="btn-yellow-sm float-right">{translations.btn?.reboot ?? "Reboot"}</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -208,11 +262,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if sysinfo.net.ipv6}
|
{#if sysinfo.net.ipv6}
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
IPv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.ipv6)}</span>
|
IPv6: <span style="font-size: 14px;">{formatIPv6(sysinfo.net.ipv6)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.dns1v6)}</span>{/if}
|
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{formatIPv6(sysinfo.net.dns1v6)}</span>{/if}
|
||||||
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.dns2v6)}</span>{/if}
|
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{formatIPv6(sysinfo.net.dns2v6)}</span>{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -267,7 +321,7 @@
|
|||||||
<div class="my-2 flex">
|
<div class="my-2 flex">
|
||||||
<form action="firmware" enctype="multipart/form-data" method="post" on:submit={() => firmwareUploading=true} autocomplete="off">
|
<form action="firmware" enctype="multipart/form-data" method="post" on:submit={() => firmwareUploading=true} autocomplete="off">
|
||||||
<input style="display:none" name="file" type="file" accept=".bin" bind:this={firmwareFileInput} bind:files={firmwareFiles}>
|
<input style="display:none" name="file" type="file" accept=".bin" bind:this={firmwareFileInput} bind:files={firmwareFiles}>
|
||||||
{#if firmwareFiles.length == 0}
|
{#if !firmwareFiles || firmwareFiles.length == 0}
|
||||||
<button type="button" on:click={()=>{firmwareFileInput.click();}} class="btn-pri-sm float-right">{translations.status?.firmware?.btn_select_file ?? "Select file"}</button>
|
<button type="button" on:click={()=>{firmwareFileInput.click();}} class="btn-pri-sm float-right">{translations.status?.firmware?.btn_select_file ?? "Select file"}</button>
|
||||||
{:else}
|
{:else}
|
||||||
{firmwareFiles[0].name}
|
{firmwareFiles[0].name}
|
||||||
@@ -287,13 +341,13 @@
|
|||||||
{/each}
|
{/each}
|
||||||
<label class="my-1 mx-3 col-span-2"><input type="checkbox" class="rounded" name="ic" value="true"/> {translations.status?.backup?.secrets ?? "Include secrets"}<br/><small>{translations.status?.backup?.secrets_desc ?? ""}</small></label>
|
<label class="my-1 mx-3 col-span-2"><input type="checkbox" class="rounded" name="ic" value="true"/> {translations.status?.backup?.secrets ?? "Include secrets"}<br/><small>{translations.status?.backup?.secrets_desc ?? ""}</small></label>
|
||||||
</div>
|
</div>
|
||||||
{#if configFiles.length == 0}
|
{#if !configFiles || configFiles.length == 0}
|
||||||
<button type="submit" class="btn-pri-sm float-right">{translations.status?.backup?.btn_download ?? "Download"}</button>
|
<button type="submit" class="btn-pri-sm float-right">{translations.status?.backup?.btn_download ?? "Download"}</button>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
<form on:submit|preventDefault={uploadConfigFile} autocomplete="off">
|
<form on:submit|preventDefault={uploadConfigFile} autocomplete="off">
|
||||||
<input style="display:none" name="file" type="file" accept=".cfg" bind:this={configFileInput} bind:files={configFiles}>
|
<input style="display:none" name="file" type="file" accept=".cfg" bind:this={configFileInput} bind:files={configFiles}>
|
||||||
{#if configFiles.length == 0}
|
{#if !configFiles || configFiles.length == 0}
|
||||||
<button type="button" on:click={()=>{configFileInput.click();}} class="btn-pri-sm">{translations.status?.backup?.btn_select_file ?? "Select file"}</button>
|
<button type="button" on:click={()=>{configFileInput.click();}} class="btn-pri-sm">{translations.status?.backup?.btn_select_file ?? "Select file"}</button>
|
||||||
{:else}
|
{:else}
|
||||||
{configFiles[0].name}
|
{configFiles[0].name}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { sysinfoStore } from './DataStores.js';
|
import { sysinfoStore } from '../lib/DataStores.js';
|
||||||
import BoardTypeSelectOptions from './BoardTypeSelectOptions.svelte';
|
import BoardTypeSelectOptions from '../lib/BoardTypeSelectOptions.svelte';
|
||||||
import UartSelectOptions from './UartSelectOptions.svelte';
|
import UartSelectOptions from '../lib/UartSelectOptions.svelte';
|
||||||
import Mask from './Mask.svelte'
|
import Mask from '../lib/Mask.svelte'
|
||||||
import { navigate } from 'svelte-navigator';
|
import { push } from 'svelte-spa-router';
|
||||||
|
|
||||||
export let basepath = "/";
|
let sysinfo = {};
|
||||||
export let sysinfo = {};
|
|
||||||
|
|
||||||
let loadingOrSaving = false;
|
let loadingOrSaving = false;
|
||||||
async function handleSubmit(e) {
|
async function handleSubmit(e) {
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
|
|
||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
navigate(basepath + (sysinfo.usrcfg ? "" : "setup"));
|
push(sysinfo.usrcfg ? "/" : "/setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
let cc = true;
|
let cc = true;
|
||||||
@@ -1,45 +1,62 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
|
// Try to import local config, fall back to default if not found
|
||||||
|
let localConfig = { proxyTarget: "http://192.168.4.1" };
|
||||||
|
try {
|
||||||
|
const imported = await import('./vite.config.local.js');
|
||||||
|
localConfig = imported.default;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('No vite.config.local.js found, using default proxy target:', localConfig.proxyTarget);
|
||||||
|
console.log('Copy vite.config.local.example.js to vite.config.local.js to customize');
|
||||||
|
}
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
assetsDir: '.',
|
assetsDir: '.',
|
||||||
|
minify: 'esbuild',
|
||||||
|
target: 'es2020',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
assetFileNames: '[name][extname]',
|
assetFileNames: '[name][extname]',
|
||||||
chunkFileNames: '[name].js',
|
chunkFileNames: '[name].js',
|
||||||
entryFileNames: '[name].js'
|
entryFileNames: '[name].js',
|
||||||
|
manualChunks: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [svelte()],
|
plugins: [svelte({
|
||||||
|
compilerOptions: {
|
||||||
|
dev: false
|
||||||
|
}
|
||||||
|
})],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
"/data.json": "http://192.168.21.122",
|
"/data.json": localConfig.proxyTarget,
|
||||||
"/energyprice.json": "http://192.168.21.122",
|
"/energyprice.json": localConfig.proxyTarget,
|
||||||
"/importprice.json": "http://192.168.21.122",
|
"/importprice.json": localConfig.proxyTarget,
|
||||||
"/exportprice.json": "http://192.168.21.122",
|
"/exportprice.json": localConfig.proxyTarget,
|
||||||
"/dayplot.json": "http://192.168.21.122",
|
"/dayplot.json": localConfig.proxyTarget,
|
||||||
"/monthplot.json": "http://192.168.21.122",
|
"/monthplot.json": localConfig.proxyTarget,
|
||||||
"/temperature.json": "http://192.168.21.122",
|
"/temperature.json": localConfig.proxyTarget,
|
||||||
"/sysinfo.json": "http://192.168.21.122",
|
"/sysinfo.json": localConfig.proxyTarget,
|
||||||
"/configuration.json": "http://192.168.21.122",
|
"/configuration.json": localConfig.proxyTarget,
|
||||||
"/tariff.json": "http://192.168.21.122",
|
"/tariff.json": localConfig.proxyTarget,
|
||||||
"/realtime.json": "http://192.168.21.122",
|
"/realtime.json": localConfig.proxyTarget,
|
||||||
"/priceconfig.json": "http://192.168.21.122",
|
"/priceconfig.json": localConfig.proxyTarget,
|
||||||
"/translations.json": "http://192.168.21.122",
|
"/translations.json": localConfig.proxyTarget,
|
||||||
"/cloudkey.json": "http://192.168.21.122",
|
"/cloudkey.json": localConfig.proxyTarget,
|
||||||
"/wifiscan.json": "http://192.168.21.122",
|
"/wifiscan.json": localConfig.proxyTarget,
|
||||||
"/save": "http://192.168.21.122",
|
"/save": localConfig.proxyTarget,
|
||||||
"/reboot": "http://192.168.21.122",
|
"/reboot": localConfig.proxyTarget,
|
||||||
"/configfile": "http://192.168.21.122",
|
"/configfile": localConfig.proxyTarget,
|
||||||
"/upgrade": "http://192.168.21.122",
|
"/upgrade": localConfig.proxyTarget,
|
||||||
"/mqtt-ca": "http://192.168.21.122",
|
"/mqtt-ca": localConfig.proxyTarget,
|
||||||
"/mqtt-cert": "http://192.168.21.122",
|
"/mqtt-cert": localConfig.proxyTarget,
|
||||||
"/mqtt-key": "http://192.168.21.122",
|
"/mqtt-key": localConfig.proxyTarget,
|
||||||
"/logo.svg": "http://192.168.21.122",
|
"/logo.svg": localConfig.proxyTarget,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
7
lib/SvelteUi/app/vite.config.local.example.js
Normal file
7
lib/SvelteUi/app/vite.config.local.example.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Copy this file to vite.config.local.js and update with your device's IP address
|
||||||
|
// vite.config.local.js is ignored by git so your settings won't be committed
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// The IP address of your AMS reader device for local development
|
||||||
|
proxyTarget: "http://192.168.4.1"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user