mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-07-29 00:42:41 -04:00
created basic website functions and look
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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/*
|
||||
|
||||
53
README.md
53
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
|
||||
|
||||
10
frontend/.gitignore
vendored
10
frontend/.gitignore
vendored
@@ -1,10 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
@@ -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",
|
||||
|
||||
13
frontend/pnpm-lock.yaml
generated
13
frontend/pnpm-lock.yaml
generated
@@ -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'}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="h-100">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<body data-sveltekit-preload-data="hover" class="h-100">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,31 +1,61 @@
|
||||
<script>
|
||||
import { parseISO, formatDistance } from 'date-fns';
|
||||
import Fa from 'svelte-fa';
|
||||
import { faPowerOff } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export let device;
|
||||
export let now;
|
||||
</script>
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 g-4">
|
||||
<div class="card border-0 rounded-5 px-2 bg-body-secondary text-dark-emphasis">
|
||||
<div class="card-body">
|
||||
<p
|
||||
class="m-0 fs-4"
|
||||
class:text-danger={device.status == 'offline' ? true : false}
|
||||
class:text-success={device.status == 'online' ? true : false}
|
||||
class:text-warning={device.status == 'pending' ? true : false}
|
||||
>
|
||||
<Fa icon={faPowerOff} />
|
||||
</p>
|
||||
<p class="m-0 fw-bold fs-5">{device.name} ({device.status})</p>
|
||||
<p class="m-0 text-muted">{device.ip}</p>
|
||||
</div>
|
||||
<div class="card-footer mb-2 bg-transparent fst-italic">
|
||||
Last change: {formatDistance(parseISO(device.updated), now, {
|
||||
includeSeconds: true,
|
||||
addSuffix: true
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
import { parseISO, formatDistance } from 'date-fns';
|
||||
import Fa from 'svelte-fa';
|
||||
import { faPowerOff, faEllipsisVertical } from '@fortawesome/free-solid-svg-icons';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
export let device;
|
||||
export let now;
|
||||
|
||||
function shutdown() {
|
||||
fetch(`${dev ? 'http://127.0.0.1:8090' : ''}/api/upsnap/shutdown/${device.id}`);
|
||||
}
|
||||
|
||||
function wake() {
|
||||
fetch(`${dev ? 'http://127.0.0.1:8090' : ''}/api/upsnap/wake/${device.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-3 g-4">
|
||||
<div
|
||||
class="card border-0 rounded-5 px-2"
|
||||
class:offline={device.status == 'offline' ? true : false}
|
||||
class:online={device.status == 'online' ? true : false}
|
||||
class:pending={device.status == 'pending' ? true : false}
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="row fs-4">
|
||||
<div class="col-auto me-auto">
|
||||
<span
|
||||
class:text-danger={device.status == 'offline' ? true : false}
|
||||
class:text-success={device.status == 'online' ? true : false}
|
||||
class:text-warning={device.status == 'pending' ? true : false}
|
||||
on:click={device.status == 'offline'
|
||||
? () => wake()
|
||||
: device.status == 'online'
|
||||
? () => shutdown()
|
||||
: ''}
|
||||
on:keydown={device.status == 'offline'
|
||||
? () => wake()
|
||||
: device.status == 'online'
|
||||
? () => shutdown()
|
||||
: ''}
|
||||
>
|
||||
<Fa icon={faPowerOff} />
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<Fa icon={faEllipsisVertical} />
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 fw-bold fs-5">{device.name}</p>
|
||||
<p class="text-muted">{device.ip}</p>
|
||||
<p class="m-0 text-muted">
|
||||
{formatDistance(parseISO(device.updated), now, {
|
||||
includeSeconds: true,
|
||||
addSuffix: true
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<nav class="navbar navbar-expand">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="favicon.png" alt="UpSnap" width="30" height="30" />
|
||||
</a>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" class:active={$page.url.pathname === '/' ? true : false} href="/"
|
||||
>Home</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link active"
|
||||
class:active={$page.url.pathname === '/settings' ? true : false}
|
||||
href="/settings">Settings</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
10
frontend/src/components/Transition.svelte
Normal file
10
frontend/src/components/Transition.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script>
|
||||
import { fly } from 'svelte/transition';
|
||||
export let url;
|
||||
</script>
|
||||
|
||||
{#key url}
|
||||
<div in:fly={{ y: 50, duration: 300 }}>
|
||||
<slot />
|
||||
</div>
|
||||
{/key}
|
||||
1
frontend/src/routes/+layout.js
Normal file
1
frontend/src/routes/+layout.js
Normal file
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
@@ -1,22 +1,39 @@
|
||||
<script>
|
||||
import Navbar from '@components/Navbar.svelte';
|
||||
import Transition from '@components/Transition.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let preferesDark;
|
||||
|
||||
onMount(() => {
|
||||
preferesDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
preferesDark.addEventListener('change', (e) => {
|
||||
setTheme(e.matches ? 'dark' : 'light');
|
||||
});
|
||||
});
|
||||
|
||||
function setTheme(newTheme) {
|
||||
document.documentElement.setAttribute('data-bs-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<script>
|
||||
if (document) {
|
||||
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
if (document) {
|
||||
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||
}
|
||||
</script>
|
||||
</svelte:head>
|
||||
|
||||
<Navbar />
|
||||
|
||||
<slot />
|
||||
<Transition url={$page.url}>
|
||||
<slot />
|
||||
</Transition>
|
||||
|
||||
<style lang="scss" global>
|
||||
@import '../scss/main.scss';
|
||||
@import '../scss/main.scss';
|
||||
</style>
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
<script>
|
||||
import PocketBase from 'pocketbase';
|
||||
import { onMount } from 'svelte';
|
||||
import DeviceCard from '@components/DeviceCard.svelte';
|
||||
import { pocketbase } from '@stores/pocketbase';
|
||||
|
||||
let devices = {};
|
||||
|
||||
onMount(async () => {
|
||||
const pb = new PocketBase('http://127.0.0.1:8090');
|
||||
let pb;
|
||||
pocketbase.subscribe((conn) => {
|
||||
pb = conn;
|
||||
});
|
||||
|
||||
// get all devices in pocketbase
|
||||
const result = await pb.collection('devices').getFullList();
|
||||
result.forEach(device => {
|
||||
devices[device.id] = device
|
||||
result.forEach((device) => {
|
||||
devices[device.id] = device;
|
||||
});
|
||||
|
||||
// subscribe to database events
|
||||
pb.collection('devices').subscribe('*', function (e) {
|
||||
devices[e.record.id] = e.record
|
||||
devices[e.record.id] = e.record;
|
||||
});
|
||||
});
|
||||
|
||||
// update device date
|
||||
let now = Date.now();
|
||||
let clear;
|
||||
let clear;
|
||||
$: {
|
||||
clearInterval(clear)
|
||||
clear = setInterval(() => {
|
||||
now = Date.now()
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
clearInterval(clear);
|
||||
clear = setInterval(() => {
|
||||
now = Date.now();
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="container text-body-emphasis">
|
||||
<div class="row">
|
||||
{#each Object.entries(devices) as [_, device]}
|
||||
<DeviceCard {device} {now} />
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script>
|
||||
import PocketBase from 'pocketbase';
|
||||
import { onMount } from 'svelte';
|
||||
import { pocketbase } from '@stores/pocketbase';
|
||||
|
||||
let pb;
|
||||
let files;
|
||||
|
||||
onMount(() => {
|
||||
pb = new PocketBase('http://127.0.0.1:8090');
|
||||
pocketbase.subscribe((conn) => {
|
||||
pb = conn;
|
||||
});
|
||||
});
|
||||
|
||||
async function restoreV2Backup(e) {
|
||||
@@ -75,10 +77,15 @@
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<h1>Settings</h1>
|
||||
<div class="input-group flex-nowrap">
|
||||
<h1 class="h1">Settings</h1>
|
||||
<section>
|
||||
<h3>Restore</h3>
|
||||
<div class="callout callout-info">
|
||||
If you were running UpSnap v2.3.2 (or higher), you can restore your devices here. This will
|
||||
wipe the existing database.
|
||||
</div>
|
||||
<input
|
||||
class="form-control"
|
||||
class="form-control w-50"
|
||||
placeholder="Username"
|
||||
aria-label="Username"
|
||||
aria-describedby="addon-wrapping"
|
||||
@@ -87,5 +94,5 @@
|
||||
bind:files
|
||||
on:change={(e) => restoreV2Backup(e)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
frontend/src/stores/pocketbase.js
Normal file
4
frontend/src/stores/pocketbase.js
Normal file
@@ -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'));
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 8.5 KiB |
@@ -1,18 +1,19 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
import path from 'path';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess(),
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess(),
|
||||
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
'@components': path.resolve('./src/components')
|
||||
}
|
||||
alias: {
|
||||
'@components': path.resolve('./src/components'),
|
||||
'@stores': path.resolve('./src/stores')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user