From 6555ce3b3851ecc74fc0669fbd7ff643f0af1b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxi=20Quo=C3=9F?= Date: Wed, 18 Jan 2023 02:51:25 +0100 Subject: [PATCH] created basic website functions and look --- .gitignore | 5 ++ README.md | 53 ++++++++++++- frontend/.gitignore | 10 --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 13 +-- frontend/src/app.html | 4 +- frontend/src/components/DeviceCard.svelte | 92 ++++++++++++++-------- frontend/src/components/Navbar.svelte | 27 +++++++ frontend/src/components/Transition.svelte | 10 +++ frontend/src/routes/+layout.js | 1 + frontend/src/routes/+layout.svelte | 37 ++++++--- frontend/src/routes/+page.svelte | 28 ++++--- frontend/src/routes/settings/+page.svelte | 19 +++-- frontend/src/scss/main.scss | 74 +++++++++++++++++ frontend/src/stores/pocketbase.js | 4 + frontend/static/favicon.png | Bin 1571 -> 8668 bytes frontend/svelte.config.js | 13 +-- 17 files changed, 301 insertions(+), 91 deletions(-) delete mode 100644 frontend/.gitignore create mode 100644 frontend/src/components/Transition.svelte create mode 100644 frontend/src/routes/+layout.js create mode 100644 frontend/src/stores/pocketbase.js diff --git a/.gitignore b/.gitignore index 70bdf955..ce33fd59 100644 --- a/.gitignore +++ b/.gitignore @@ -192,5 +192,10 @@ package # End of https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,svelte,node +frontend/build +frontend/.svelte-kit +frontend/vite.config.js.timestamp-* +frontend/vite.config.ts.timestamp-* backend/pb_data +backend/pb_public/* diff --git a/README.md b/README.md index b2402c19..c3bc7e4d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,51 @@ -# to do +# UpSnap v3 -- add db import from v2 -- rewrite sveltekit frontend +> This is the dev branch for v3. It's very much in progress but will have a release candidate once all boxes below are checked. + +## Help developing + +Fork this branch and clone it. + +1. Start backend + +```sh +cd backend +go mod tidy +go run main.go serve +``` + +Log in to [http://127.0.0.1:8090/\_/](http://127.0.0.1:8090/_/), create an admin user and add some devices. + +2. Start frontend + +```sh +cd frontend +pnpm i +pnpm run dev +``` + +Open up [localhost:5173/](localhost:5173/) + +## To do + +- frontend + + - [ ] add rest of settings page + - [ ] form for adding new devices + - [ ] add per device settings + - [ ] theme toggle button + - [ ] add device ports to cards + +- backend + + - [ ] make sure ping works + - [ ] make sure arp works + - [ ] make sure wake works + - [ ] remove nmap? + - [ ] [#34 Add support for WOL passwords](https://github.com/seriousm4x/UpSnap/issues/34) + - [x] [~~#33 Seemingly High Ram usage~~](https://github.com/seriousm4x/UpSnap/issues/33) + - [x] [~~#32 API available?~~](https://github.com/seriousm4x/UpSnap/issues/32) + +- [x] ~~add db import from v2~~ +- [ ] move docker images to github packages and setup new workflow file +- [ ] create dockerfile and docker-compose.yml diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 6635cf55..00000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example -vite.config.js.timestamp-* -vite.config.ts.timestamp-* diff --git a/frontend/package.json b/frontend/package.json index 8fd56221..2c796ae7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,7 @@ "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { - "@sveltejs/adapter-auto": "^1.0.1", + "@sveltejs/adapter-static": "^1.0.1", "@sveltejs/kit": "^1.1.1", "eslint": "^8.32.0", "eslint-config-prettier": "^8.6.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index c54f515b..0a29213c 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@fortawesome/free-solid-svg-icons': ^6.2.1 '@popperjs/core': ^2.11.6 - '@sveltejs/adapter-auto': ^1.0.1 + '@sveltejs/adapter-static': ^1.0.1 '@sveltejs/kit': ^1.1.1 bootstrap: 5.3.0-alpha1 date-fns: ^2.29.3 @@ -29,7 +29,7 @@ dependencies: svelte-fa: 3.0.3 devDependencies: - '@sveltejs/adapter-auto': 1.0.1_@sveltejs+kit@1.1.1 + '@sveltejs/adapter-static': 1.0.4_@sveltejs+kit@1.1.1 '@sveltejs/kit': 1.1.1_svelte@3.55.1+vite@4.0.4 eslint: 8.32.0 eslint-config-prettier: 8.6.0_eslint@8.32.0 @@ -324,13 +324,12 @@ packages: resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} dev: false - /@sveltejs/adapter-auto/1.0.1_@sveltejs+kit@1.1.1: - resolution: {integrity: sha512-IGek24xN1wJb+gz0hyGSxbIvM7q2K+BXquyycteCWbH9MPosjw8+LuIB2HIH20thE/647UEQBNqCM9R73lAUXA==} + /@sveltejs/adapter-static/1.0.4_@sveltejs+kit@1.1.1: + resolution: {integrity: sha512-L6kGg19MBHW3kv3GWAPl9tJo3ohNM+dcj9Yq1U1XVu0J0MKrusyCWQ62XOfxFgS6T8Fq0KYJ5NSSAPGppvX4xA==} peerDependencies: '@sveltejs/kit': ^1.0.0 dependencies: '@sveltejs/kit': 1.1.1_svelte@3.55.1+vite@4.0.4 - import-meta-resolve: 2.2.1 dev: true /@sveltejs/kit/1.1.1_svelte@3.55.1+vite@4.0.4: @@ -894,10 +893,6 @@ packages: resolve-from: 4.0.0 dev: true - /import-meta-resolve/2.2.1: - resolution: {integrity: sha512-C6lLL7EJPY44kBvA80gq4uMsVFw5x3oSKfuMl1cuZ2RkI5+UJqQXgn+6hlUew0y4ig7Ypt4CObAAIzU53Nfpuw==} - dev: true - /imurmurhash/0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} diff --git a/frontend/src/app.html b/frontend/src/app.html index effe0d0d..f4a15b7a 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -1,12 +1,12 @@ - + %sveltekit.head% - +
%sveltekit.body%
diff --git a/frontend/src/components/DeviceCard.svelte b/frontend/src/components/DeviceCard.svelte index fa86257b..c901391e 100644 --- a/frontend/src/components/DeviceCard.svelte +++ b/frontend/src/components/DeviceCard.svelte @@ -1,31 +1,61 @@ - - -
-
-
-

- -

-

{device.name} ({device.status})

-

{device.ip}

-
- -
-
+ + +
+
+
+
+
+ wake() + : device.status == 'online' + ? () => shutdown() + : ''} + on:keydown={device.status == 'offline' + ? () => wake() + : device.status == 'online' + ? () => shutdown() + : ''} + > + + +
+
+ +
+
+

{device.name}

+

{device.ip}

+

+ {formatDistance(parseISO(device.updated), now, { + includeSeconds: true, + addSuffix: true + })} +

+
+
+
diff --git a/frontend/src/components/Navbar.svelte b/frontend/src/components/Navbar.svelte index e69de29b..d6b53602 100644 --- a/frontend/src/components/Navbar.svelte +++ b/frontend/src/components/Navbar.svelte @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/components/Transition.svelte b/frontend/src/components/Transition.svelte new file mode 100644 index 00000000..60e55d95 --- /dev/null +++ b/frontend/src/components/Transition.svelte @@ -0,0 +1,10 @@ + + +{#key url} +
+ +
+{/key} diff --git a/frontend/src/routes/+layout.js b/frontend/src/routes/+layout.js new file mode 100644 index 00000000..189f71e2 --- /dev/null +++ b/frontend/src/routes/+layout.js @@ -0,0 +1 @@ +export const prerender = true; diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 9e7bcc36..860ba01b 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -1,22 +1,39 @@ - + - + + + diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index b2fc7ecb..9c5a85be 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,38 +1,40 @@ -
+
{#each Object.entries(devices) as [_, device]} diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index c06b53d5..af942a99 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -1,12 +1,14 @@
-

Settings

-
+

Settings

+
+

Restore

+
+ If you were running UpSnap v2.3.2 (or higher), you can restore your devices here. This will + wipe the existing database. +
restoreV2Backup(e)} /> -
+
diff --git a/frontend/src/scss/main.scss b/frontend/src/scss/main.scss index 680103a2..d04916e0 100644 --- a/frontend/src/scss/main.scss +++ b/frontend/src/scss/main.scss @@ -32,9 +32,83 @@ $body-bg-dark: $gray-1000; @import '../../node_modules/bootstrap/scss/grid'; @import '../../node_modules/bootstrap/scss/helpers'; @import '../../node_modules/bootstrap/scss/card'; +@import '../../node_modules/bootstrap/scss/nav'; +@import '../../node_modules/bootstrap/scss/navbar'; +@import '../../node_modules/bootstrap/scss/forms'; @import '../../node_modules/bootstrap/scss/utilities/api'; +section { + padding: 1rem; + margin: 1rem 0; + border-radius: 1rem; +} + +.nav-link { + padding: 0.3rem 0.6rem !important; + border-radius: 0.4rem; + + &.active { + background-color: $gray-300; + } +} + .text-success { color: $teal-500 !important; } + +.text-warning { + color: $yellow !important; +} + +.text-danger { + color: #ef476f !important; +} + +.callout { + padding: 1rem; + border-radius: 0.3rem; + margin: 1rem 0; + + &.callout-info { + background-color: #1177b21f; + border-left: 0.5rem solid #1177b21f; + } + + &.callout-danger { + background-color: #ef476f1f; + border-left: 0.5rem solid #ef476f1f; + } +} + +html[data-bs-theme='light'] { + html, + body { + background: #f0f1f2; + } + + section, + .card { + background: #ffffff; + box-shadow: 0 1rem 1rem rgba(238, 238, 238, 0.7); + } +} + +html[data-bs-theme='dark'] { + html, + body { + background: #171718; + } + + section, + .card { + background: #252525; + box-shadow: 0 1rem 1rem rgba(32, 32, 32, 0.4); + } + + .nav-link { + &.active { + background-color: $gray-800; + } + } +} diff --git a/frontend/src/stores/pocketbase.js b/frontend/src/stores/pocketbase.js new file mode 100644 index 00000000..38bc88ac --- /dev/null +++ b/frontend/src/stores/pocketbase.js @@ -0,0 +1,4 @@ +import { writable } from 'svelte/store'; +import PocketBase from 'pocketbase'; + +export let pocketbase = writable(new PocketBase('http://127.0.0.1:8090')); diff --git a/frontend/static/favicon.png b/frontend/static/favicon.png index 825b9e65af7c104cfb07089bb28659393b4f2097..9c658ae5acd7edecd5b850c242ed8daca38c3a2b 100644 GIT binary patch literal 8668 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