🔐 Removes auth from frontend

This commit is contained in:
Rune Harlyk
2024-10-29 20:58:48 +01:00
parent 1c6b9f79c5
commit 84633e5707
22 changed files with 4225 additions and 4282 deletions
+2406 -1937
View File
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -1,4 +1,3 @@
import { user } from '$lib/stores/user';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { Err, Ok, type Result } from './utilities'; import { Err, Ok, type Result } from './utilities';
import { location } from './stores'; import { location } from './stores';
@@ -28,7 +27,6 @@ async function sendRequest<TResponse>(
params?: RequestInit params?: RequestInit
): Promise<Result<TResponse, Error>> { ): Promise<Result<TResponse, Error>> {
endpoint = resolveUrl(endpoint); endpoint = resolveUrl(endpoint);
const user_token = get(user).bearer_token;
const body = data !== null && typeof data !== 'undefined' ? JSON.stringify(data) : undefined; const body = data !== null && typeof data !== 'undefined' ? JSON.stringify(data) : undefined;
const request = { const request = {
@@ -37,7 +35,7 @@ async function sendRequest<TResponse>(
body, body,
headers: { headers: {
...params?.headers, ...params?.headers,
Authorization: user_token ? 'Bearer ' + user_token : 'Basic', Authorization: 'Basic',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}; };
@@ -58,7 +56,8 @@ async function sendRequest<TResponse>(
return Err.new(new ApiError(response), 'An error has occurred'); return Err.new(new ApiError(response), 'An error has occurred');
} }
const contentType = response.headers.get('Content-Type') ?? response.headers.get('Content-Type'); const contentType =
response.headers.get('Content-Type') ?? response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) { if (contentType && contentType.includes('application/json')) {
const data = await response.json(); const data = await response.json();
return Ok.new(data as TResponse); return Ok.new(data as TResponse);
+2 -2
View File
@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { user, location } from '$lib/stores'; import { location } from '$lib/stores';
let source = `${$location}/api/camera/stream?access_token=${$user.bearer_token}`; let source = `${$location}/api/camera/stream`;
onDestroy(() => (source = '#')); onDestroy(() => (source = '#'));
</script> </script>
-111
View File
@@ -1,111 +0,0 @@
<script lang="ts">
import logo from '$lib/assets/logo512.png';
import { PasswordInput } from '$lib/components/input';
import { user } from '$lib/stores/user';
import { notifications } from '$lib/components/toasts/notifications';
import { fade, fly } from 'svelte/transition';
import { api } from '$lib/api';
import type { JWT } from '$lib/types/models';
import { Login } from './icons';
type SignInData = {
password: string;
username: string;
};
let username = '';
let password = '';
let loginFailed = false;
let token = { access_token: '' };
async function signInUser(data: SignInData) {
const result = await api.post<JWT>('/api/signIn', data);
if (result.isErr()) {
username = '';
password = '';
notifications.error('Wrong Username or Password!', 5000);
loginFailed = true;
setTimeout(() => {
loginFailed = false;
}, 1500);
return;
}
token = result.inner;
user.init(token.access_token);
username = $user.username;
notifications.success('User ' + username + ' signed in', 5000);
}
</script>
<div class="hero from-primary/30 to-secondary/30 min-h-screen bg-gradient-to-br">
<div
class="card lg:card-side bg-base-100 face shadow-2xl {loginFailed
? 'failure border-error border-2'
: ''}"
in:fly={{ delay: 200, y: 100, duration: 500 }}
out:fade={{ duration: 200 }}
>
<figure class="bg-base-200"><img src={logo} alt="Logo" class="h-auto w-48 lg:w-64" /></figure>
<div class="card-body w-80">
<h2 class="card-title text-2xl">Login</h2>
<form class="form-control w-full max-w-xs">
<label class="label" for="user">
<span class="label-text text-md">Username</span>
</label>
<input
type="text"
class="input input-bordered w-full max-w-xs"
id="user"
bind:value={username}
/>
<label class="label" for="pwd">
<span class="label-text text-md">Password</span>
</label>
<PasswordInput id="pwd" bind:value={password} />
<div class="card-actions mt-4 justify-end">
<button
class="btn btn-primary inline-flex items-center"
on:click={() => signInUser({ username, password })}
>
<Login class="mr-2 h-5 w-5" /><span>Login</span>
</button>
</div>
</form>
</div>
</div>
</div>
<style>
.failure {
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}
@keyframes shake {
10%,
90% {
transform: translatex(-1px);
}
20%,
80% {
transform: translatex(2px);
}
30%,
50%,
70% {
transform: translatex(-4px);
}
40%,
60% {
transform: translatex(4px);
}
}
</style>
+1 -13
View File
@@ -1,15 +1,12 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { user } from '$lib/stores/user';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { useFeatureFlags } from '$lib/stores/featureFlags'; import { useFeatureFlags } from '$lib/stores/featureFlags';
import UserButton from '../menu/UserButton.svelte';
import GithubButton from '../menu/GithubButton.svelte'; import GithubButton from '../menu/GithubButton.svelte';
import LogoButton from '../menu/LogoButton.svelte'; import LogoButton from '../menu/LogoButton.svelte';
import MenuList from '../menu/MenuList.svelte'; import MenuList from '../menu/MenuList.svelte';
import { import {
Connection, Connection,
Users,
Settings, Settings,
MdiController, MdiController,
Devices, Devices,
@@ -122,12 +119,6 @@
} }
] ]
}, },
{
title: 'Users',
icon: Users,
href: '/user',
feature: $features.security && $user.admin
},
{ {
title: 'System', title: 'System',
icon: Settings, icon: Settings,
@@ -156,8 +147,7 @@
icon: Update, icon: Update,
href: '/system/update', href: '/system/update',
feature: feature:
($features.ota || $features.upload_firmware || $features.download_firmware) && $features.ota || $features.upload_firmware || $features.download_firmware
(!$features.security || $user.admin)
} }
] ]
} }
@@ -188,8 +178,6 @@
<MenuList {menuItems} on:select{updateMenu} class="flex-grow flex-nowrap overflow-y-auto" /> <MenuList {menuItems} on:select{updateMenu} class="flex-grow flex-nowrap overflow-y-auto" />
<UserButton />
<div class="divider my-0" /> <div class="divider my-0" />
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@@ -1,17 +0,0 @@
<script lang="ts">
import { user } from '$lib/stores';
import { useFeatureFlags } from "$lib/stores";
import { Avatar, Logout } from '../icons';
const features = useFeatureFlags();
</script>
{#if $features.security}
<div class="flex items-center">
<Avatar class="h-8 w-8" />
<span class="flex-grow px-4 text-xl font-bold">{$user.username}</span>
<button class="btn btn-ghost" on:click={user.invalidate}>
<Logout class="h-8 w-8 rotate-180" />
</button>
</div>
{/if}
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { openModal, closeAllModals } from 'svelte-modals'; import { openModal, closeAllModals } from 'svelte-modals';
import { user } from '$lib/stores/user';
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
@@ -68,7 +67,7 @@
} }
onMount(async () => { onMount(async () => {
if ($features.download_firmware && (!$features.security || $user.admin)) { if ($features.download_firmware) {
await getGithubAPI(); await getGithubAPI();
const interval = setInterval( const interval = setInterval(
async () => { async () => {
-1
View File
@@ -5,6 +5,5 @@ export * from './socket';
export * from './fullscreen'; export * from './fullscreen';
export * from './telemetry'; export * from './telemetry';
export * from './analytics'; export * from './analytics';
export * from './user';
export * from './featureFlags'; export * from './featureFlags';
export * from './location-store'; export * from './location-store';
-40
View File
@@ -1,40 +0,0 @@
import { goto } from '$app/navigation';
import { jwtDecode } from 'jwt-decode';
import { persistentStore } from '$lib/utilities';
export type UserProfile = {
username: string;
admin: boolean;
bearer_token: string;
};
type DecodedJWT = Omit<UserProfile, 'bearer_token'>;
const emptyUser: UserProfile = {
username: '',
admin: false,
bearer_token: ''
};
function createUserStore() {
const store = persistentStore<UserProfile>('user', emptyUser);
return {
subscribe: store.subscribe,
init: (access_token: string) => {
const decoded: DecodedJWT = jwtDecode(access_token);
const userProfile: UserProfile = {
bearer_token: access_token,
username: decoded.username,
admin: decoded.admin
};
store.set(userProfile);
},
invalidate: () => {
store.set(emptyUser);
goto('/');
}
};
}
export const user = createUserStore();
+1 -4
View File
@@ -17,8 +17,6 @@ export type GithubRelease = {
}>; }>;
}; };
export type JWT = { access_token: string };
export type angles = number[] | Int16Array; export type angles = number[] | Int16Array;
export type WifiStatus = { export type WifiStatus = {
@@ -91,7 +89,6 @@ export type NTPStatus = {
uptime: number; uptime: number;
}; };
export type Battery = { export type Battery = {
voltage: number; voltage: number;
current: number; current: number;
@@ -163,7 +160,7 @@ export interface I2CDevice {
address: number; address: number;
part_number: string; part_number: string;
name: string; name: string;
}; }
export type CameraSettings = { export type CameraSettings = {
framesize: number; framesize: number;
+5 -25
View File
@@ -8,12 +8,9 @@
import '../app.css'; import '../app.css';
import Menu from '../lib/components/menu/Menu.svelte'; import Menu from '../lib/components/menu/Menu.svelte';
import Statusbar from '../lib/components/statusbar/statusbar.svelte'; import Statusbar from '../lib/components/statusbar/statusbar.svelte';
import Login from '../lib/components/login.svelte';
import { import {
telemetry, telemetry,
analytics, analytics,
user,
type UserProfile,
ModesEnum, ModesEnum,
kinematicData, kinematicData,
mode, mode,
@@ -21,21 +18,16 @@
servoAngles, servoAngles,
servoAnglesOut, servoAnglesOut,
socket, socket,
location location,
useFeatureFlags
} from '$lib/stores'; } from '$lib/stores';
import type { Analytics, Battery, DownloadOTA } from '$lib/types/models'; import type { Analytics, Battery, DownloadOTA } from '$lib/types/models';
import { api } from '$lib/api';
import { useFeatureFlags } from '$lib/stores/featureFlags';
const features = useFeatureFlags(); const features = useFeatureFlags();
onMount(async () => { onMount(async () => {
if ($user.bearer_token !== '') {
await validateUser($user);
}
const ws_token = $features.security ? '?access_token=' + $user.bearer_token : '';
const ws = $location ? $location : window.location.host; const ws = $location ? $location : window.location.host;
socket.init(`ws://${ws}/ws/events${ws_token}`); socket.init(`ws://${ws}/ws/events`);
addEventListeners(); addEventListeners();
@@ -75,14 +67,6 @@
socket.off('otastatus', handleOAT); socket.off('otastatus', handleOAT);
}; };
async function validateUser(userdata: UserProfile) {
const result = await api.get('/api/verifyAuthorization');
if (result.isErr()) {
user.invalidate();
console.error('Error:', result.inner);
}
}
const handleOpen = () => { const handleOpen = () => {
notifications.success('Connection to device established', 5000); notifications.success('Connection to device established', 5000);
}; };
@@ -109,10 +93,7 @@
<title>{$page.data.title}</title> <title>{$page.data.title}</title>
</svelte:head> </svelte:head>
{#if $features?.security && $user.bearer_token === ''} <div class="drawer">
<Login />
{:else}
<div class="drawer">
<input id="main-menu" type="checkbox" class="drawer-toggle" bind:checked={menuOpen} /> <input id="main-menu" type="checkbox" class="drawer-toggle" bind:checked={menuOpen} />
<div class="drawer-content flex flex-col"> <div class="drawer-content flex flex-col">
<!-- Status bar content here --> <!-- Status bar content here -->
@@ -126,8 +107,7 @@
<label for="main-menu" class="drawer-overlay" /> <label for="main-menu" class="drawer-overlay" />
<Menu on:menuClicked={() => (menuOpen = false)} /> <Menu on:menuClicked={() => (menuOpen = false)} />
</div> </div>
</div> </div>
{/if}
<Modals> <Modals>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
+2 -3
View File
@@ -1,14 +1,13 @@
<script lang="ts"> <script lang="ts">
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
import { WiFi } from '$lib/components/icons'; import { WiFi } from '$lib/components/icons';
import { location, socket, useFeatureFlags, user } from '$lib/stores'; import { location, socket, useFeatureFlags } from '$lib/stores';
const features = useFeatureFlags(); const features = useFeatureFlags();
const update = () => { const update = () => {
const ws_token = $features.security ? '?access_token=' + $user.bearer_token : '';
const ws = $location ? $location : window.location.host; const ws = $location ? $location : window.location.host;
socket.init(`ws://${ws}/ws/events${ws_token}`); socket.init(`ws://${ws}/ws/events`);
}; };
</script> </script>
+20 -26
View File
@@ -5,7 +5,7 @@
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
import Collapsible from '$lib/components/Collapsible.svelte'; import Collapsible from '$lib/components/Collapsible.svelte';
import Spinner from '$lib/components/Spinner.svelte'; import Spinner from '$lib/components/Spinner.svelte';
import { user, useFeatureFlags } from '$lib/stores'; import { useFeatureFlags } from '$lib/stores';
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import { TIME_ZONES } from './timezones'; import { TIME_ZONES } from './timezones';
import type { NTPSettings, NTPStatus } from '$lib/types/models'; import type { NTPSettings, NTPStatus } from '$lib/types/models';
@@ -19,33 +19,27 @@
async function getNTPStatus() { async function getNTPStatus() {
const result = await api.get<NTPStatus>('/api/ntpStatus'); const result = await api.get<NTPStatus>('/api/ntpStatus');
if (result.isErr()){ if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
ntpStatus = result.inner ntpStatus = result.inner;
} }
async function getNTPSettings() { async function getNTPSettings() {
const result = await api.get<NTPSettings>('/api/ntpSettings'); const result = await api.get<NTPSettings>('/api/ntpSettings');
if (result.isErr()){ if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
ntpSettings = result.inner ntpSettings = result.inner;
} }
const interval = setInterval(async () => { const interval = setInterval(async () => getNTPStatus(), 5000);
getNTPStatus();
}, 5000);
onDestroy(() => clearInterval(interval)); onDestroy(() => clearInterval(interval));
onMount(() => { onMount(getNTPSettings);
if (!$features.security || $user.admin) {
getNTPSettings();
}
});
let formField: any; let formField: any;
@@ -55,12 +49,12 @@
async function postNTPSettings(data: NTPSettings) { async function postNTPSettings(data: NTPSettings) {
const result = await api.post<NTPSettings>('/api/ntpSettings', data); const result = await api.post<NTPSettings>('/api/ntpSettings', data);
if (result.isErr()){ if (result.isErr()) {
notifications.error('User not authorized.', 3000); notifications.error('User not authorized.', 3000);
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
ntpSettings = result.inner ntpSettings = result.inner;
} }
function handleSubmitNTP() { function handleSubmitNTP() {
@@ -130,13 +124,13 @@
> >
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div <div
class="mask mask-hexagon h-auto w-10 {ntpStatus.status === 1 class="mask mask-hexagon h-auto w-10 {ntpStatus.status === 1 ?
? 'bg-success' 'bg-success'
: 'bg-error'}" : 'bg-error'}"
> >
<NTP <NTP
class="h-auto w-full scale-75 {ntpStatus.status === 1 class="h-auto w-full scale-75 {ntpStatus.status === 1 ?
? 'text-success-content' 'text-success-content'
: 'text-error-content'}" : 'text-error-content'}"
/> />
</div> </div>
@@ -206,7 +200,6 @@
{/await} {/await}
</div> </div>
{#if !$features.security || $user.admin}
<Collapsible open={false} on:closed={getNTPSettings}> <Collapsible open={false} on:closed={getNTPSettings}>
<span slot="title">Change NTP Settings</span> <span slot="title">Change NTP Settings</span>
<form <form
@@ -230,8 +223,10 @@
type="text" type="text"
min="3" min="3"
max="64" max="64"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.server class="input input-bordered invalid:border-error w-full invalid:border-2 {(
? 'border-error border-2' formErrors.server
) ?
'border-error border-2'
: ''}" : ''}"
bind:value={ntpSettings.server} bind:value={ntpSettings.server}
id="server" id="server"
@@ -256,5 +251,4 @@
</div> </div>
</form> </form>
</Collapsible> </Collapsible>
{/if}
</SettingsCard> </SettingsCard>
@@ -1,8 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { openModal, closeModal } from 'svelte-modals'; import { openModal, closeModal } from 'svelte-modals';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
import Spinner from '$lib/components/Spinner.svelte'; import Spinner from '$lib/components/Spinner.svelte';
@@ -162,8 +160,8 @@
<div> <div>
<div class="font-bold">CPU Frequency</div> <div class="font-bold">CPU Frequency</div>
<div class="text-sm opacity-75"> <div class="text-sm opacity-75">
{systemInformation.cpu_freq_mhz} MHz {systemInformation.cpu_cores == 2 {systemInformation.cpu_freq_mhz} MHz {systemInformation.cpu_cores == 2 ?
? 'Dual Core' 'Dual Core'
: 'Single Core'} : 'Single Core'}
</div> </div>
</div> </div>
@@ -206,15 +204,19 @@
<div class="flex flex-wrap justify-start gap-1 text-sm opacity-75"> <div class="flex flex-wrap justify-start gap-1 text-sm opacity-75">
<span> <span>
{( {(
(systemInformation.sketch_size / systemInformation.free_sketch_space) * (systemInformation.sketch_size /
systemInformation.free_sketch_space) *
100 100
).toFixed(1)} % of ).toFixed(1)} % of
{(systemInformation.free_sketch_space / 1000000).toLocaleString('en-US')} MB used {(systemInformation.free_sketch_space / 1000000).toLocaleString(
'en-US'
)} MB used
</span> </span>
<span> <span>
({( ({(
(systemInformation.free_sketch_space - systemInformation.sketch_size) / (systemInformation.free_sketch_space -
systemInformation.sketch_size) /
1000000 1000000
).toLocaleString('en-US')} MB free) ).toLocaleString('en-US')} MB free)
</span> </span>
@@ -229,9 +231,10 @@
<div> <div>
<div class="font-bold">Flash Chip (Size / Speed)</div> <div class="font-bold">Flash Chip (Size / Speed)</div>
<div class="text-sm opacity-75"> <div class="text-sm opacity-75">
{(systemInformation.flash_chip_size / 1000000).toLocaleString('en-US')} MB / {( {(systemInformation.flash_chip_size / 1000000).toLocaleString('en-US')} MB
systemInformation.flash_chip_speed / 1000000 / {(systemInformation.flash_chip_speed / 1000000).toLocaleString(
).toLocaleString('en-US')} MHz 'en-US'
)} MHz
</div> </div>
</div> </div>
</div> </div>
@@ -244,7 +247,10 @@
<div class="font-bold">File System (Used / Total)</div> <div class="font-bold">File System (Used / Total)</div>
<div class="flex flex-wrap justify-start gap-1 text-sm opacity-75"> <div class="flex flex-wrap justify-start gap-1 text-sm opacity-75">
<span <span
>{((systemInformation.fs_used / systemInformation.fs_total) * 100).toFixed(1)} % of {( >{(
(systemInformation.fs_used / systemInformation.fs_total) *
100
).toFixed(1)} % of {(
systemInformation.fs_total / 1000000 systemInformation.fs_total / 1000000
).toLocaleString('en-US')} MB used</span ).toLocaleString('en-US')} MB used</span
> >
@@ -267,8 +273,8 @@
<div> <div>
<div class="font-bold">Core Temperature</div> <div class="font-bold">Core Temperature</div>
<div class="text-sm opacity-75"> <div class="text-sm opacity-75">
{systemInformation.core_temp == 53.33 {systemInformation.core_temp == 53.33 ?
? 'NaN' 'NaN'
: systemInformation.core_temp.toFixed(2) + ' °C'} : systemInformation.core_temp.toFixed(2) + ' °C'}
</div> </div>
</div> </div>
@@ -303,17 +309,15 @@
<div class="mt-4 flex flex-wrap justify-end gap-2"> <div class="mt-4 flex flex-wrap justify-end gap-2">
{#if $features.sleep} {#if $features.sleep}
<button class="btn btn-primary inline-flex items-center" on:click={confirmSleep} <button class="btn btn-primary inline-flex items-center" on:click={confirmSleep}>
><Sleep class="mr-2 h-5 w-5" /><span>Sleep</span></button <Sleep class="mr-2 h-5 w-5" /><span>Sleep</span>
> </button>
{/if}
{#if !$features.security || $user.admin}
<button class="btn btn-primary inline-flex items-center" on:click={confirmRestart}
><Power class="mr-2 h-5 w-5" /><span>Restart</span></button
>
<button class="btn btn-secondary inline-flex items-center" on:click={confirmReset}
><FactoryReset class="mr-2 h-5 w-5" /><span>Factory Reset</span></button
>
{/if} {/if}
<button class="btn btn-primary inline-flex items-center" on:click={confirmRestart}>
<Power class="mr-2 h-5 w-5" /><span>Restart</span>
</button>
<button class="btn btn-secondary inline-flex items-center" on:click={confirmReset}>
<FactoryReset class="mr-2 h-5 w-5" /><span>Factory Reset</span>
</button>
</div> </div>
</SettingsCard> </SettingsCard>
+2 -3
View File
@@ -1,18 +1,17 @@
<script lang="ts"> <script lang="ts">
import UploadFirmware from './UploadFirmware.svelte'; import UploadFirmware from './UploadFirmware.svelte';
import GithubFirmwareManager from './GithubFirmwareManager.svelte'; import GithubFirmwareManager from './GithubFirmwareManager.svelte';
import { user } from '$lib/stores/user';
import { useFeatureFlags } from '$lib/stores'; import { useFeatureFlags } from '$lib/stores';
const features = useFeatureFlags(); const features = useFeatureFlags();
</script> </script>
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8"> <div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
{#if $features.download_firmware && (!$features.security || $user.admin)} {#if $features.download_firmware}
<GithubFirmwareManager /> <GithubFirmwareManager />
{/if} {/if}
{#if $features.upload_firmware && (!$features.security || $user.admin)} {#if $features.upload_firmware}
<UploadFirmware /> <UploadFirmware />
{/if} {/if}
</div> </div>
-195
View File
@@ -1,195 +0,0 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { openModal, closeModal } from 'svelte-modals';
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { user } from '$lib/stores/user';
import { notifications } from '$lib/components/toasts/notifications';
import { PasswordInput } from '$lib/components/input';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import EditUser from './EditUser.svelte';
import Spinner from '$lib/components/Spinner.svelte';
import { api } from '$lib/api';
import {
Cancel,
Check,
Users,
AddUser,
Admin,
Edit,
Delete,
Warning
} from '$lib/components/icons';
type userSetting = {
username: string;
password: string;
admin: boolean;
};
type SecuritySettings = {
jwt_secret: string;
users: userSetting[];
};
let securitySettings: SecuritySettings;
async function getSecuritySettings() {
const result = await api.get<SecuritySettings>('/api/securitySettings');
if (result.isErr()) {
console.error('Error:', result.inner);
return;
}
securitySettings = result.inner;
}
async function postSecuritySettings(data: SecuritySettings) {
const result = await api.post<SecuritySettings>('/api/securitySettings', data);
if (result.isErr()) {
console.error('Error:', result.inner);
notifications.error('User not authorized.', 3000);
return;
}
securitySettings = result.inner;
if (await validateUser()) {
notifications.success('Security settings updated.', 3000);
}
}
async function validateUser() {
const result = await api.get('/api/verifyAuthorization');
if (result.isErr()) user.invalidate();
return result.isOk();
}
function confirmDelete(index: number) {
openModal(ConfirmDialog, {
title: 'Confirm Delete User',
message:
'Are you sure you want to delete the user "' +
securitySettings.users[index].username +
'"?',
labels: {
cancel: { label: 'Abort', icon: Cancel },
confirm: { label: 'Yes', icon: Check }
},
onConfirm: () => {
securitySettings.users.splice(index, 1);
securitySettings = securitySettings;
closeModal();
postSecuritySettings(securitySettings);
}
});
}
function handleEdit(index: number) {
openModal(EditUser, {
title: 'Edit User',
user: { ...securitySettings.users[index] }, // Shallow Copy
onSaveUser: (editedUser: userSetting) => {
securitySettings.users[index] = editedUser;
closeModal();
postSecuritySettings(securitySettings);
}
});
}
function handleNewUser() {
openModal(EditUser, {
title: 'Add User',
onSaveUser: (newUser: userSetting) => {
securitySettings.users = [...securitySettings.users, newUser];
closeModal();
postSecuritySettings(securitySettings);
}
});
//
}
</script>
{#if $user.admin}
<div
class="mx-0 my-1 flex flex-col space-y-4
sm:mx-8 sm:my-8"
>
<SettingsCard collapsible={false}>
<Users slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
<span slot="title">Manage Users</span>
{#await getSecuritySettings()}
<Spinner />
{:then nothing}
<div class="relative w-full overflow-visible">
<button
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
on:click={handleNewUser}
>
<AddUser class="h-6 w-6" /></button
>
<div class="overflow-x-auto" transition:slide|local={{ duration: 300, easing: cubicOut }}>
<table class="table w-full table-auto">
<thead>
<tr class="font-bold">
<th align="left">Username</th>
<th align="center">Admin</th>
<th align="right" class="pr-8">Edit</th>
</tr>
</thead>
<tbody>
{#each securitySettings.users as user, index}
<tr>
<td align="left">{user.username}</td>
<td align="center">
{#if user.admin}
<Admin class="text-secondary" />
{/if}
</td>
<td align="right">
<span class="my-auto inline-flex flex-row space-x-2">
<button
class="btn btn-ghost btn-circle btn-xs"
on:click={() => handleEdit(index)}
>
<Edit class="h-6 w-6" /></button
>
<button
class="btn btn-ghost btn-circle btn-xs"
on:click={() => confirmDelete(index)}
>
<Delete class="text-error h-6 w-6" />
</button>
</span>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
<div class="divider mb-0" />
<span class="pb-2 text-xl font-medium">Security Settings</span>
<div class="alert alert-warning shadow-lg">
<Warning class="h-6 w-6 flex-shrink-0" />
<span
>The JWT secret is used to sign authentication tokens. If you modify the JWT Secret, all
users will be signed out.</span
>
</div>
<label class="label" for="secret">
<span class="label-text text-md">JWT Secret</span>
</label>
<PasswordInput bind:value={securitySettings.jwt_secret} id="secret" />
<div class="mt-6 flex justify-end">
<button class="btn btn-primary" on:click={() => postSecuritySettings(securitySettings)}
>Apply Settings</button
>
</div>
{/await}
</SettingsCard>
</div>
{:else}
{goto('/')}
{/if}
-7
View File
@@ -1,7 +0,0 @@
import type { PageLoad } from './$types';
export const load = (async () => {
return {
title: 'Users'
};
}) satisfies PageLoad;
-103
View File
@@ -1,103 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { closeModal } from 'svelte-modals';
import { fly } from 'svelte/transition';
import { PasswordInput } from '$lib/components/input';
import { Cancel, Save } from '$lib/components/icons';
// provided by <Modals />
export let isOpen: boolean;
export let title: string;
export let onSaveUser: any; // Callback on Save
export let user = {
username: '',
password: '',
admin: false
};
let errorUsername = false;
let usernameEditable = false;
onMount(() => {
if (user.username == '') {
usernameEditable = true;
}
});
function handleSave() {
// Validate if username is within range
if (user.username.length < 3 || user.username.length > 32) {
errorUsername = true;
} else {
errorUsername = false;
// Callback on saving
onSaveUser(user);
}
}
</script>
{#if isOpen}
<div
role="dialog"
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
transition:fly={{ y: 50 }}
on:introstart
on:outroend
>
<div
class="rounded-box bg-base-100 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg md:w-[28rem]"
>
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
<div class="divider my-2" />
<form
class="form-control text-base-content mb-1 w-full"
on:submit|preventDefault={handleSave}
novalidate
>
<label class="label" for="username">
<span class="label-text text-md">Username</span>
</label>
<input
type="text"
min="3"
max="32"
class="input input-bordered invalid:border-error w-full invalid:border-2"
bind:value={user.username}
id="username"
disabled={!usernameEditable}
/>
<label for="username" class="label"
><span class="label-text-alt text-error {errorUsername ? '' : 'hidden'}"
>Username must be between 3 and 32 characters long</span
></label
>
<label class="label" for="pwd">
<span class="label-text text-md">Password</span>
</label>
<PasswordInput bind:value={user.password} id="pwd" />
<label class="label my-auto cursor-pointer justify-start gap-4">
<input type="checkbox" bind:checked={user.admin} class="checkbox checkbox-primary" />
<span class="">Is Admin?</span>
</label>
<div class="divider my-2" />
<div class="flex justify-end gap-2">
<button
class="btn btn-neutral text-neutral-content inline-flex items-center"
on:click={closeModal}
type="button"
>
<Cancel class="mr-2 h-5 w-5" /><span>Cancel</span></button
>
<button
class="btn btn-primary text-primary-content inline-flex items-center"
type="submit"
><Save class="mr-2 h-5 w-5" />
Save
</button>
</div>
</form>
</div>
</div>
{/if}
+56 -43
View File
@@ -4,8 +4,6 @@
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import { PasswordInput } from '$lib/components/input'; import { PasswordInput } from '$lib/components/input';
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import Spinner from '$lib/components/Spinner.svelte'; import Spinner from '$lib/components/Spinner.svelte';
import type { ApSettings, ApStatus } from '$lib/types/models'; import type { ApSettings, ApStatus } from '$lib/types/models';
@@ -22,21 +20,21 @@
async function getAPStatus() { async function getAPStatus() {
const result = await api.get<ApStatus>('/api/apStatus'); const result = await api.get<ApStatus>('/api/apStatus');
if (result.isErr()){ if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
apStatus = result.inner apStatus = result.inner;
return apStatus; return apStatus;
} }
async function getAPSettings() { async function getAPSettings() {
const result = await api.get<ApSettings>('/api/apSetting'); const result = await api.get<ApSettings>('/api/apSetting');
if (result.isErr()){ if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
apSettings = result.inner apSettings = result.inner;
return apSettings; return apSettings;
} }
@@ -46,11 +44,7 @@
onDestroy(() => clearInterval(interval)); onDestroy(() => clearInterval(interval));
onMount(() => { onMount(getAPSettings);
if (!$features.security || $user.admin) {
getAPSettings();
}
});
let provisionMode = [ let provisionMode = [
{ {
@@ -84,13 +78,13 @@
async function postAPSettings(data: ApSettings) { async function postAPSettings(data: ApSettings) {
const result = await api.post<ApSettings>('/api/apSettings', data); const result = await api.post<ApSettings>('/api/apSettings', data);
if (result.isErr()){ if (result.isErr()) {
notifications.error('User not authorized.', 3000); notifications.error('User not authorized.', 3000);
console.error('Error:', result.inner); console.error('Error:', result.inner);
return return;
} }
notifications.success('Access Point settings updated.', 3000); notifications.success('Access Point settings updated.', 3000);
apSettings = result.inner apSettings = result.inner;
} }
function handleSubmitAP() { function handleSubmitAP() {
@@ -170,9 +164,13 @@
> >
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div <div
class="mask mask-hexagon h-auto w-10 {apStatusDescription[apStatus.status].bg_color}" class="mask mask-hexagon h-auto w-10 {apStatusDescription[apStatus.status]
.bg_color}"
> >
<AP class="h-auto w-full scale-75 {apStatusDescription[apStatus.status].text_color}" /> <AP
class="h-auto w-full scale-75 {apStatusDescription[apStatus.status]
.text_color}"
/>
</div> </div>
<div> <div>
<div class="font-bold">Status</div> <div class="font-bold">Status</div>
@@ -221,7 +219,6 @@
{/await} {/await}
</div> </div>
{#if !$features.security || $user.admin}
<div class="bg-base-200 relative grid w-full max-w-2xl self-center overflow-hidden"> <div class="bg-base-200 relative grid w-full max-w-2xl self-center overflow-hidden">
<div <div
class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium" class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium"
@@ -263,8 +260,10 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.ssid class="input input-bordered invalid:border-error w-full invalid:border-2 {(
? 'border-error border-2' formErrors.ssid
) ?
'border-error border-2'
: ''}" : ''}"
bind:value={apSettings.ssid} bind:value={apSettings.ssid}
id="ssid" id="ssid"
@@ -273,7 +272,8 @@
required required
/> />
<label class="label" for="ssid"> <label class="label" for="ssid">
<span class="label-text-alt text-error {formErrors.ssid ? '' : 'hidden'}" <span
class="label-text-alt text-error {formErrors.ssid ? '' : 'hidden'}"
>SSID must be between 2 and 32 characters long</span >SSID must be between 2 and 32 characters long</span
> >
</label> </label>
@@ -293,16 +293,20 @@
type="number" type="number"
min="1" min="1"
max="13" max="13"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.channel class="input input-bordered invalid:border-error w-full invalid:border-2 {(
? 'border-error border-2' formErrors.channel
) ?
'border-error border-2'
: ''}" : ''}"
bind:value={apSettings.channel} bind:value={apSettings.channel}
id="channel" id="channel"
required required
/> />
<label class="label" for="channel"> <label class="label" for="channel">
<span class="label-text-alt text-error {formErrors.channel ? '' : 'hidden'}" <span
>Must be channel 1 to 13</span class="label-text-alt text-error {formErrors.channel ? '' : (
'hidden'
)}">Must be channel 1 to 13</span
> >
</label> </label>
</div> </div>
@@ -315,16 +319,20 @@
type="number" type="number"
min="1" min="1"
max="8" max="8"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.max_clients class="input input-bordered invalid:border-error w-full invalid:border-2 {(
? 'border-error border-2' formErrors.max_clients
) ?
'border-error border-2'
: ''}" : ''}"
bind:value={apSettings.max_clients} bind:value={apSettings.max_clients}
id="clients" id="clients"
required required
/> />
<label class="label" for="clients"> <label class="label" for="clients">
<span class="label-text-alt text-error {formErrors.max_clients ? '' : 'hidden'}" <span
>Maximum 8 clients allowed</span class="label-text-alt text-error {formErrors.max_clients ? '' : (
'hidden'
)}">Maximum 8 clients allowed</span
> >
</label> </label>
</div> </div>
@@ -335,8 +343,8 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered w-full {formErrors.local_ip class="input input-bordered w-full {formErrors.local_ip ?
? 'border-error border-2' 'border-error border-2'
: ''}" : ''}"
minlength="7" minlength="7"
maxlength="15" maxlength="15"
@@ -346,8 +354,10 @@
required required
/> />
<label class="label" for="localIP"> <label class="label" for="localIP">
<span class="label-text-alt text-error {formErrors.local_ip ? '' : 'hidden'}" <span
>Must be a valid IPv4 address</span class="label-text-alt text-error {formErrors.local_ip ? '' : (
'hidden'
)}">Must be a valid IPv4 address</span
> >
</label> </label>
</div> </div>
@@ -358,8 +368,8 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered w-full {formErrors.gateway_ip class="input input-bordered w-full {formErrors.gateway_ip ?
? 'border-error border-2' 'border-error border-2'
: ''}" : ''}"
minlength="7" minlength="7"
maxlength="15" maxlength="15"
@@ -369,8 +379,10 @@
required required
/> />
<label class="label" for="gateway"> <label class="label" for="gateway">
<span class="label-text-alt text-error {formErrors.gateway_ip ? '' : 'hidden'}" <span
>Must be a valid IPv4 address</span class="label-text-alt text-error {formErrors.gateway_ip ? '' : (
'hidden'
)}">Must be a valid IPv4 address</span
> >
</label> </label>
</div> </div>
@@ -380,8 +392,8 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered w-full {formErrors.subnet_mask class="input input-bordered w-full {formErrors.subnet_mask ?
? 'border-error border-2' 'border-error border-2'
: ''}" : ''}"
minlength="7" minlength="7"
maxlength="15" maxlength="15"
@@ -391,8 +403,10 @@
required required
/> />
<label class="label" for="subnet"> <label class="label" for="subnet">
<span class="label-text-alt text-error {formErrors.subnet_mask ? '' : 'hidden'}" <span
>Must be a valid IPv4 address</span class="label-text-alt text-error {formErrors.subnet_mask ? '' : (
'hidden'
)}">Must be a valid IPv4 address</span
> >
</label> </label>
</div> </div>
@@ -413,5 +427,4 @@
</div> </div>
{/await} {/await}
</div> </div>
{/if}
</SettingsCard> </SettingsCard>
+26 -45
View File
@@ -5,8 +5,6 @@
import { openModal, closeModal } from 'svelte-modals'; import { openModal, closeModal } from 'svelte-modals';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications'; import { notifications } from '$lib/components/toasts/notifications';
import DragDropList, { VerticalDropZone, reorder, type DropEvent } from 'svelte-dnd-list'; import DragDropList, { VerticalDropZone, reorder, type DropEvent } from 'svelte-dnd-list';
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte';
@@ -441,7 +439,6 @@
{/await} {/await}
</div> </div>
{#if !$features.security || $user.admin}
<div class="bg-base-200 relative grid w-full max-w-2xl self-center overflow-hidden"> <div class="bg-base-200 relative grid w-full max-w-2xl self-center overflow-hidden">
<div <div
class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium" class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium"
@@ -488,16 +485,13 @@
let:index let:index
> >
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div <div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"
>
<div class="mask mask-hexagon bg-primary h-auto w-10 shrink-0"> <div class="mask mask-hexagon bg-primary h-auto w-10 shrink-0">
<Router class="text-primary-content h-auto w-full scale-75" /> <Router class="text-primary-content h-auto w-full scale-75" />
</div> </div>
<div> <div>
<div class="font-bold">{dndNetworkList[index].ssid}</div> <div class="font-bold">{dndNetworkList[index].ssid}</div>
</div> </div>
{#if !$features.security || $user.admin}
<div class="flex-grow" /> <div class="flex-grow" />
<div class="space-x-0 px-0 mx-0"> <div class="space-x-0 px-0 mx-0">
<button <button
@@ -517,7 +511,6 @@
<Delete class="text-error h-6 w-6" /> <Delete class="text-error h-6 w-6" />
</button> </button>
</div> </div>
{/if}
</div> </div>
</DragDropList> </DragDropList>
</div> </div>
@@ -534,9 +527,7 @@
novalidate novalidate
bind:this={formField} bind:this={formField}
> >
<div <div class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2">
class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2"
>
<div> <div>
<label class="label" for="channel"> <label class="label" for="channel">
<span class="label-text text-md">Host Name</span> <span class="label-text text-md">Host Name</span>
@@ -599,9 +590,9 @@
/> />
<label class="label" for="ssid"> <label class="label" for="ssid">
<span <span
class="label-text-alt text-error {formErrors.ssid ? '' class="label-text-alt text-error {formErrors.ssid ? '' : (
: 'hidden'}" 'hidden'
>SSID must be between 3 and 32 characters long</span )}">SSID must be between 3 and 32 characters long</span
> >
</label> </label>
</div> </div>
@@ -633,9 +624,7 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered w-full {( class="input input-bordered w-full {formErrors.local_ip ?
formErrors.local_ip
) ?
'border-error border-2' 'border-error border-2'
: ''}" : ''}"
minlength="7" minlength="7"
@@ -647,9 +636,7 @@
/> />
<label class="label" for="localIP"> <label class="label" for="localIP">
<span <span
class="label-text-alt text-error {( class="label-text-alt text-error {formErrors.local_ip ?
formErrors.local_ip
) ?
'' ''
: 'hidden'}">Must be a valid IPv4 address</span : 'hidden'}">Must be a valid IPv4 address</span
> >
@@ -662,9 +649,7 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered w-full {( class="input input-bordered w-full {formErrors.gateway_ip ?
formErrors.gateway_ip
) ?
'border-error border-2' 'border-error border-2'
: ''}" : ''}"
minlength="7" minlength="7"
@@ -689,9 +674,7 @@
</label> </label>
<input <input
type="text" type="text"
class="input input-bordered w-full {( class="input input-bordered w-full {formErrors.subnet_mask ?
formErrors.subnet_mask
) ?
'border-error border-2' 'border-error border-2'
: ''}" : ''}"
minlength="7" minlength="7"
@@ -706,8 +689,10 @@
formErrors.subnet_mask formErrors.subnet_mask
) ? ) ?
'' ''
: 'hidden'}">Must be a valid IPv4 address</span : 'hidden'}"
> >
Must be a valid IPv4 address
</span>
</label> </label>
</div> </div>
<div> <div>
@@ -727,10 +712,11 @@
/> />
<label class="label" for="gateway"> <label class="label" for="gateway">
<span <span
class="label-text-alt text-error {formErrors.dns_1 ? class="label-text-alt text-error {formErrors.dns_1 ? ''
'' : 'hidden'}"
: 'hidden'}">Must be a valid IPv4 address</span
> >
Must be a valid IPv4 address
</span>
</label> </label>
</div> </div>
<div> <div>
@@ -750,10 +736,11 @@
/> />
<label class="label" for="subnet"> <label class="label" for="subnet">
<span <span
class="label-text-alt text-error {formErrors.dns_2 ? class="label-text-alt text-error {formErrors.dns_2 ? ''
'' : 'hidden'}"
: 'hidden'}">Must be a valid IPv4 address</span
> >
Must be a valid IPv4 address
</span>
</label> </label>
</div> </div>
</div> </div>
@@ -762,21 +749,15 @@
<div class="divider mb-2 mt-0" /> <div class="divider mb-2 mt-0" />
<div class="mx-4 flex flex-wrap justify-end gap-2"> <div class="mx-4 flex flex-wrap justify-end gap-2">
<button <button class="btn btn-primary" type="submit" disabled={!showNetworkEditor}>
class="btn btn-primary" {newNetwork ? 'Add Network' : 'Update Network'}
type="submit" </button>
disabled={!showNetworkEditor} <button class="btn btn-primary" type="button" on:click={validateHostName}>
>{newNetwork ? 'Add Network' : 'Update Network'}</button Apply Settings
> </button>
<button
class="btn btn-primary"
type="button"
on:click={validateHostName}>Apply Settings</button
>
</div> </div>
</form> </form>
</div> </div>
{/await} {/await}
</div> </div>
{/if}
</SettingsCard> </SettingsCard>
+1 -6
View File
@@ -5,7 +5,7 @@
The back end exposes a number of API endpoints which are referenced in the table below. The back end exposes a number of API endpoints which are referenced in the table below.
| Method | Endpoint | Authentication | POST JSON Body | Info | | Method | Endpoint | Authentication | POST JSON Body | Info |
| ------ | --------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | ------ | -------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| GET | /rest/features | `NONE_REQUIRED` | none | Tells the client which features of the UI should be use | | GET | /rest/features | `NONE_REQUIRED` | none | Tells the client which features of the UI should be use |
| GET | /rest/ntpStatus | `IS_AUTHENTICATED` | none | Current NTP connection status | | GET | /rest/ntpStatus | `IS_AUTHENTICATED` | none | Current NTP connection status |
| GET | /rest/ntpSettings | `IS_ADMIN` | none | Current NTP settings | | GET | /rest/ntpSettings | `IS_ADMIN` | none | Current NTP settings |
@@ -22,11 +22,6 @@ The back end exposes a number of API endpoints which are referenced in the table
| POST | /rest/restart | `IS_ADMIN` | none | Restart the ESP32 | | POST | /rest/restart | `IS_ADMIN` | none | Restart the ESP32 |
| POST | /rest/factoryReset | `IS_ADMIN` | none | Reset the ESP32 and all settings to their default values | | POST | /rest/factoryReset | `IS_ADMIN` | none | Reset the ESP32 and all settings to their default values |
| POST | /rest/uploadFirmware | `IS_ADMIN` | none | File upload of firmware.bin | | POST | /rest/uploadFirmware | `IS_ADMIN` | none | File upload of firmware.bin |
| POST | /rest/signIn | `NONE_REQUIRED` | `{"password": "admin","username": "admin"}` | Signs a user in and returns access token |
| GET | /rest/securitySettings | `IS_ADMIN` | none | retrieves all user information and roles |
| POST | /rest/securitySettings | `IS_ADMIN` | `{"jwt_secret": "734cb5bb-5597b722", "users": [{"username": "admin", "password": "admin", "admin": true}, {"username": "guest", "password": "guest", "admin": false, }]}` | retrieves all user information and roles |
| GET | /rest/verifyAuthorization | `NONE_REQUIRED` | none | Verifies the content of the auth bearer token |
| GET | /rest/generateToken?username={username} | `IS_ADMIN` | `{"token": "734cb5bb-5597b722"}` | Generates a new JWT token for the user from username |
| POST | /rest/sleep | `IS_AUTHENTICATED` | none | Puts the device in deep sleep mode | | POST | /rest/sleep | `IS_AUTHENTICATED` | none | Puts the device in deep sleep mode |
| POST | /rest/downloadUpdate | `IS_ADMIN` | `{"download_url": "https://github.com/theelims/ESP32-sveltekit/releases/download/v0.1.0/firmware_esp32s3.bin"}` | Download link for OTA. This requires a valid SSL certificate and will follow redirects. | | POST | /rest/downloadUpdate | `IS_ADMIN` | `{"download_url": "https://github.com/theelims/ESP32-sveltekit/releases/download/v0.1.0/firmware_esp32s3.bin"}` | Download link for OTA. This requires a valid SSL certificate and will follow redirects. |
+29 -29
View File
@@ -1,23 +1,22 @@
# Software description # Software description
The software make use of a range of different libraries to enhance the functionality. The software make use of a range of different libraries to enhance the functionality.
Up to date list can be seen in platformio.ini file. Up to date list can be seen in platformio.ini file.
The libraries includes: The libraries includes:
* Esp32SvelteKit - Esp32SvelteKit
* PsychicHttp - PsychicHttp
* ArduinoJson - ArduinoJson
* Adafruit SSD1306 - Adafruit SSD1306
* Adafruit GFX Library - Adafruit GFX Library
* Adafruit BusIO - Adafruit BusIO
* Adafruit PWM Servo Driver Library - Adafruit PWM Servo Driver Library
* Adafruit ADS1X15 - Adafruit ADS1X15
* Adafruit HMC5883 Unified - Adafruit HMC5883 Unified
* Adafruit Unified Sensor - Adafruit Unified Sensor
* I2Cdevlib-MPU6050 - I2Cdevlib-MPU6050
* NewPing - NewPing
* SPI - SPI
#### Structure #### Structure
@@ -36,7 +35,6 @@ To dis-/enable the major feature defines are used. Define them in either feature
| --- | --- | --- | --- | --- | ---
| FT_BATTERY | Whether or not to use battery | 0 | FT_BATTERY | Whether or not to use battery | 0
| FT_NTP | Whether or not to use time server | 1 | FT_NTP | Whether or not to use time server | 1
| FT_SECURITY | Whether or not to use login system | 0
| FT_SLEEP | Whether or not include sleep management | 0 | FT_SLEEP | Whether or not include sleep management | 0
| FT_UPLOAD_FIRMWARE | Whether or not to use OAT | 0 | FT_UPLOAD_FIRMWARE | Whether or not to use OAT | 0
| FT_DOWNLOAD_FIRMWARE | Whether or not to use github for firmware updates | 0 | FT_DOWNLOAD_FIRMWARE | Whether or not to use github for firmware updates | 0
@@ -50,7 +48,9 @@ To dis-/enable the major feature defines are used. Define them in either feature
### 📲 Controller ### 📲 Controller
The controller is a SvelteKit app, which main focus is to calibrate and control the robot. The controller is a SvelteKit app, which main focus is to calibrate and control the robot.
<!-- Write about the emulation, stream, controls and link to the space issues --> <!-- Write about the emulation, stream, controls and link to the space issues -->
It is made to be included and hosted by the robot. It is made to be included and hosted by the robot.
Therefore there is placed a lot of thought behind the functionality and dependencies. Therefore there is placed a lot of thought behind the functionality and dependencies.
@@ -58,22 +58,22 @@ Therefore there is placed a lot of thought behind the functionality and dependen
For the development dependencies I choose the following For the development dependencies I choose the following
| Dependencies | Description | Dependencies | Description |
| --- | --- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SvelteKit | SvelteKit is an application framework built on top of Svelte, enhancing it with features like routing, server-side rendering, and static site generation. It streamlines the development process by integrating server-side capabilities with Svelte's client-side benefits. Furthermore it make the development process fast and enjoyable. | SvelteKit | SvelteKit is an application framework built on top of Svelte, enhancing it with features like routing, server-side rendering, and static site generation. It streamlines the development process by integrating server-side capabilities with Svelte's client-side benefits. Furthermore it make the development process fast and enjoyable. |
| Vite | Vite is a frontend tool that is used for building fast and optimized web applications. Is serves code local during development and bundles assets for production | Vite | Vite is a frontend tool that is used for building fast and optimized web applications. Is serves code local during development and bundles assets for production |
| Typescript | TypeScript's integration of static typing enhances code reliability and maintainability. | Typescript | TypeScript's integration of static typing enhances code reliability and maintainability. |
| Tailwind CSS | Tailwind CSS accelerates web development with its utility-first approach, ensuring rapid styling and consistent design. | Tailwind CSS | Tailwind CSS accelerates web development with its utility-first approach, ensuring rapid styling and consistent design. |
#### Libraries #### Libraries
For the app functionality I choose the following: For the app functionality I choose the following:
| Dependencies | Description | Dependencies | Description |
| --- | --- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [Three](https://www.npmjs.com/package/three) | Easy to use, lightweight, cross-browser, general purpose 3D library. | [Three](https://www.npmjs.com/package/three) | Easy to use, lightweight, cross-browser, general purpose 3D library. |
| [Urdf-loader](https://www.npmjs.com/package/urdf-loader) | Utilities for loading URDF files into THREE.js and a Web Component that loads and renders the model. | [Urdf-loader](https://www.npmjs.com/package/urdf-loader) | Utilities for loading URDF files into THREE.js and a Web Component that loads and renders the model. |
| [Xacro-parser](https://www.npmjs.com/package/xacro-parser) | Javascript parser and loader for processing the ROS Xacro file format. | [Xacro-parser](https://www.npmjs.com/package/xacro-parser) | Javascript parser and loader for processing the ROS Xacro file format. |
| [NippleJS](https://www.npmjs.com/package/nipplejs) | A vanilla virtual joystick for touch capable interfaces. | [NippleJS](https://www.npmjs.com/package/nipplejs) | A vanilla virtual joystick for touch capable interfaces. |
| [Uzip](https://www.npmjs.com/package/uzip) | Simple, tiny and fast ZIP library. | [Uzip](https://www.npmjs.com/package/uzip) | Simple, tiny and fast ZIP library. |
| [ChartJS](https://www.npmjs.com/package/chart.js) | Simple and flexible charting library. | [ChartJS](https://www.npmjs.com/package/chart.js) | Simple and flexible charting library. |