🪇 Implements major structure and service refactors
This commit is contained in:
@@ -24,6 +24,9 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { user } from '$lib/stores/user';
|
import { user } from '$lib/stores/user';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { useFeatureFlags } from '$lib/stores/featureFlags';
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
const appName = $page.data.app_name;
|
const appName = $page.data.app_name;
|
||||||
|
|
||||||
@@ -70,7 +73,7 @@
|
|||||||
title: 'Camera',
|
title: 'Camera',
|
||||||
icon: Camera,
|
icon: Camera,
|
||||||
href: '/peripherals/camera',
|
href: '/peripherals/camera',
|
||||||
feature: $page.data.features.camera,
|
feature: $features.camera,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Servo',
|
title: 'Servo',
|
||||||
@@ -82,20 +85,20 @@
|
|||||||
title: 'IMU',
|
title: 'IMU',
|
||||||
icon: Rotate3d,
|
icon: Rotate3d,
|
||||||
href: '/peripherals/imu',
|
href: '/peripherals/imu',
|
||||||
feature: $page.data.features.imu || $page.data.features.mag || $page.data.features.bmp,
|
feature: $features.imu || $features.mag || $features.bmp,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Connections',
|
title: 'Connections',
|
||||||
icon: Remote,
|
icon: Remote,
|
||||||
feature: $page.data.features.ntp,
|
feature: $features.ntp,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
title: 'NTP',
|
title: 'NTP',
|
||||||
icon: NTP,
|
icon: NTP,
|
||||||
href: '/connections/ntp',
|
href: '/connections/ntp',
|
||||||
feature: $page.data.features.ntp,
|
feature: $features.ntp,
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -125,7 +128,7 @@
|
|||||||
title: 'Users',
|
title: 'Users',
|
||||||
icon: Users,
|
icon: Users,
|
||||||
href: '/user',
|
href: '/user',
|
||||||
feature: $page.data.features.security && $user.admin,
|
feature: $features.security && $user.admin,
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -151,7 +154,7 @@
|
|||||||
title: 'System Metrics',
|
title: 'System Metrics',
|
||||||
icon: Metrics,
|
icon: Metrics,
|
||||||
href: '/system/metrics',
|
href: '/system/metrics',
|
||||||
feature: $page.data.features.analytics,
|
feature: $features.analytics,
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -159,10 +162,10 @@
|
|||||||
icon: Update,
|
icon: Update,
|
||||||
href: '/system/update',
|
href: '/system/update',
|
||||||
feature:
|
feature:
|
||||||
($page.data.features.ota ||
|
($features.ota ||
|
||||||
$page.data.features.upload_firmware ||
|
$features.upload_firmware ||
|
||||||
$page.data.features.download_firmware) &&
|
$features.download_firmware) &&
|
||||||
(!$page.data.features.security || $user.admin),
|
(!$features.security || $user.admin),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -245,7 +248,7 @@
|
|||||||
<div class="flex-col" />
|
<div class="flex-col" />
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
{#if $page.data.features.security}
|
{#if $features.security}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Avatar class="h-8 w-8" />
|
<Avatar class="h-8 w-8" />
|
||||||
<span class="flex-grow px-4 text-xl font-bold">{$user.username}</span>
|
<span class="flex-grow px-4 text-xl font-bold">{$user.username}</span>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { daisyColor } from "$lib/DaisyUiHelper";
|
import { daisyColor } from "$lib/utilities";
|
||||||
import { Chart, registerables } from "chart.js";
|
import { Chart, registerables } from "chart.js";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { cubicOut } from "svelte/easing";
|
import { cubicOut } from "svelte/easing";
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { api } from '$lib/api';
|
||||||
|
import { notifications } from '$lib/components/toasts/notifications';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export function useFeatureFlags() {
|
||||||
|
const featureFlags = writable<Record<string, boolean>>({});
|
||||||
|
onMount(async () => {
|
||||||
|
const result = await api.get<Record<string, boolean>>('/api/features');
|
||||||
|
if (result.isOk()) featureFlags.set(result.inner);
|
||||||
|
else {
|
||||||
|
notifications.error('Feature flag could not fetched', 2500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return featureFlags;
|
||||||
|
}
|
||||||
+20
-29
@@ -1,36 +1,27 @@
|
|||||||
import { type IMU } from '$lib/types/models';
|
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
import type { IMU } from '$lib/types/models';
|
||||||
let imu_data = {
|
|
||||||
x: <number[]>[],
|
|
||||||
y: <number[]>[],
|
|
||||||
z: <number[]>[],
|
|
||||||
imu_temp: <number[]>[],
|
|
||||||
altitude: <number[]>[],
|
|
||||||
pressure: <number[]>[],
|
|
||||||
bmp_temp: <number[]>[]
|
|
||||||
};
|
|
||||||
|
|
||||||
const maxIMUData = 100;
|
const maxIMUData = 100;
|
||||||
|
|
||||||
function createIMU() {
|
export const imu = (() => {
|
||||||
const { subscribe, update } = writable(imu_data);
|
const { subscribe, update } = writable({
|
||||||
|
x: [] as number[],
|
||||||
|
y: [] as number[],
|
||||||
|
z: [] as number[],
|
||||||
|
imu_temp: [] as number[],
|
||||||
|
altitude: [] as number[],
|
||||||
|
pressure: [] as number[],
|
||||||
|
bmp_temp: [] as number[]
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
const addData = (content: IMU) => {
|
||||||
subscribe,
|
update((data) => {
|
||||||
addData: (content: IMU) => {
|
(Object.keys(content) as (keyof IMU)[]).forEach((key) => {
|
||||||
update((imu_data) => ({
|
data[key] = [...data[key], content[key]].slice(-maxIMUData);
|
||||||
...imu_data,
|
});
|
||||||
x: [...imu_data.x, content.x].slice(-maxIMUData),
|
return data;
|
||||||
y: [...imu_data.y, content.y].slice(-maxIMUData),
|
});
|
||||||
z: [...imu_data.z, content.z].slice(-maxIMUData),
|
|
||||||
imu_temp: [...imu_data.imu_temp, content.imu_temp].slice(-maxIMUData),
|
|
||||||
altitude: [...imu_data.altitude, content.altitude].slice(-maxIMUData),
|
|
||||||
pressure: [...imu_data.pressure, content.pressure].slice(-maxIMUData),
|
|
||||||
bmp_temp: [...imu_data.bmp_temp, content.bmp_temp].slice(-maxIMUData)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const imu = createIMU();
|
return { subscribe, addData };
|
||||||
|
})();
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ export * from './fullscreen';
|
|||||||
export * from './telemetry';
|
export * from './telemetry';
|
||||||
export * from './analytics';
|
export * from './analytics';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
export * from './featureFlags';
|
||||||
|
|||||||
+12
-27
@@ -1,55 +1,40 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
|
import { persistentStore } from '$lib/utilities';
|
||||||
|
|
||||||
export type userProfile = {
|
export type UserProfile = {
|
||||||
username: string;
|
username: string;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
bearer_token: string;
|
bearer_token: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type decodedJWT = {
|
type DecodedJWT = Omit<UserProfile, 'bearer_token'>;
|
||||||
username: string;
|
|
||||||
admin: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
let empty = {
|
const emptyUser: UserProfile = {
|
||||||
username: '',
|
username: '',
|
||||||
admin: false,
|
admin: false,
|
||||||
bearer_token: ''
|
bearer_token: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
function createStore() {
|
function createUserStore() {
|
||||||
const { subscribe, set } = writable(empty);
|
const store = persistentStore<UserProfile>('user', emptyUser);
|
||||||
|
|
||||||
// retrieve store from sessionStorage / localStorage if available
|
|
||||||
const userdata = localStorage.getItem('user');
|
|
||||||
if (userdata) {
|
|
||||||
set(JSON.parse(userdata));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe: store.subscribe,
|
||||||
init: (access_token: string) => {
|
init: (access_token: string) => {
|
||||||
const decoded: decodedJWT = jwtDecode(access_token);
|
const decoded: DecodedJWT = jwtDecode(access_token);
|
||||||
const userdata = {
|
const userProfile: UserProfile = {
|
||||||
bearer_token: access_token,
|
bearer_token: access_token,
|
||||||
username: decoded.username,
|
username: decoded.username,
|
||||||
admin: decoded.admin
|
admin: decoded.admin
|
||||||
};
|
};
|
||||||
set(userdata);
|
store.set(userProfile);
|
||||||
// persist store in sessionStorage / localStorage
|
|
||||||
localStorage.setItem('user', JSON.stringify(userdata));
|
|
||||||
},
|
},
|
||||||
invalidate: () => {
|
invalidate: () => {
|
||||||
console.log('Log out user');
|
store.set(emptyUser);
|
||||||
set(empty);
|
|
||||||
// remove localStorage "user"
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
// redirect to login page
|
|
||||||
goto('/');
|
goto('/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const user = createStore();
|
export const user = createUserStore();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function daisyColor(name: string, opacity: number = 100) {
|
export const daisyColor = (name: string, opacity: number = 100) => {
|
||||||
const color = getComputedStyle(document.documentElement).getPropertyValue(name);
|
const color = getComputedStyle(document.documentElement).getPropertyValue(name);
|
||||||
return `oklch(${color} / ${opacity}%)`;
|
return `oklch(${color} / ${opacity}%)`;
|
||||||
}
|
};
|
||||||
@@ -5,3 +5,6 @@ export * from './math-utilities';
|
|||||||
export * from './buffer-utilities';
|
export * from './buffer-utilities';
|
||||||
export * from './model-utilities';
|
export * from './model-utilities';
|
||||||
export * from './location-utilities';
|
export * from './location-utilities';
|
||||||
|
export * from './position-utilities';
|
||||||
|
export * from './string-utilities';
|
||||||
|
export * from './color-utilities';
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { browser } from '$app/environment';
|
|||||||
|
|
||||||
export const isEmbeddedApp = import.meta.env.VITE_EMBEDDED_BUILD === 'true';
|
export const isEmbeddedApp = import.meta.env.VITE_EMBEDDED_BUILD === 'true';
|
||||||
|
|
||||||
export const persistentStore = (key: string, initialValue: any) => {
|
export const persistentStore = <T>(key: string, initialValue: T) => {
|
||||||
const savedValue = browser ? JSON.parse(localStorage.getItem(key) as string) : null;
|
const savedValue = browser ? localStorage.getItem(key) : null;
|
||||||
const data = savedValue !== null ? savedValue : initialValue;
|
const data: T = savedValue !== null ? JSON.parse(savedValue) : initialValue;
|
||||||
const store = writable(data);
|
const store = writable<T>(data);
|
||||||
|
|
||||||
store.subscribe((value) => {
|
store.subscribe((value) => {
|
||||||
browser && localStorage.setItem(key, JSON.stringify(value));
|
if (browser) localStorage.setItem(key, JSON.stringify(value));
|
||||||
});
|
});
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
import { notifications } from '$lib/components/toasts/notifications';
|
import { notifications } from '$lib/components/toasts/notifications';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import Menu from './menu.svelte';
|
import Menu from '../lib/components/menu.svelte';
|
||||||
import Statusbar from './statusbar.svelte';
|
import Statusbar from '../lib/components/statusbar/statusbar.svelte';
|
||||||
import Login from './login.svelte';
|
import Login from '../lib/components/login.svelte';
|
||||||
import {
|
import {
|
||||||
telemetry,
|
telemetry,
|
||||||
analytics,
|
analytics,
|
||||||
user,
|
user,
|
||||||
type userProfile,
|
type UserProfile,
|
||||||
ModesEnum,
|
ModesEnum,
|
||||||
kinematicData,
|
kinematicData,
|
||||||
mode,
|
mode,
|
||||||
@@ -24,12 +24,15 @@
|
|||||||
} 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 { api } from '$lib/api';
|
||||||
|
import { useFeatureFlags } from '$lib/stores/featureFlags';
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($user.bearer_token !== '') {
|
if ($user.bearer_token !== '') {
|
||||||
await validateUser($user);
|
await validateUser($user);
|
||||||
}
|
}
|
||||||
const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : '';
|
const ws_token = $features.security ? '?access_token=' + $user.bearer_token : '';
|
||||||
socket.init(`ws://${window.location.host}/ws/events${ws_token}`);
|
socket.init(`ws://${window.location.host}/ws/events${ws_token}`);
|
||||||
|
|
||||||
addEventListeners();
|
addEventListeners();
|
||||||
@@ -53,10 +56,10 @@
|
|||||||
socket.on('angles', (angles: number[]) => {
|
socket.on('angles', (angles: number[]) => {
|
||||||
if (angles.length) servoAngles.set(angles);
|
if (angles.length) servoAngles.set(angles);
|
||||||
});
|
});
|
||||||
if ($page.data.features.analytics) socket.on('analytics', handleAnalytics);
|
if ($features.analytics) socket.on('analytics', handleAnalytics);
|
||||||
if ($page.data.features.battery) socket.on('battery', handleBattery);
|
if ($features.battery) socket.on('battery', handleBattery);
|
||||||
if ($page.data.features.download_firmware) socket.on('otastatus', handleOAT);
|
if ($features.download_firmware) socket.on('otastatus', handleOAT);
|
||||||
if ($page.data.features.sonar) socket.on('sonar', (data) => console.log(data));
|
if ($features.sonar) socket.on('sonar', (data) => console.log(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeEventListeners = () => {
|
const removeEventListeners = () => {
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
socket.off('otastatus', handleOAT);
|
socket.off('otastatus', handleOAT);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function validateUser(userdata: userProfile) {
|
async function validateUser(userdata: UserProfile) {
|
||||||
const result = await api.get('/api/verifyAuthorization');
|
const result = await api.get('/api/verifyAuthorization');
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
user.invalidate();
|
user.invalidate();
|
||||||
@@ -81,7 +84,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
// notifications.error('Connection to device lost', 5000);
|
notifications.error('Connection to device lost', 5000);
|
||||||
telemetry.setRSSI(0);
|
telemetry.setRSSI(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,7 +105,7 @@
|
|||||||
<title>{$page.data.title}</title>
|
<title>{$page.data.title}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#if $page.data.features.security && $user.bearer_token === ''}
|
{#if $features.security && $user.bearer_token === ''}
|
||||||
<Login />
|
<Login />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="drawer">
|
<div class="drawer">
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ const registerFetchIntercept = async () => {
|
|||||||
|
|
||||||
export const load = async () => {
|
export const load = async () => {
|
||||||
await registerFetchIntercept();
|
await registerFetchIntercept();
|
||||||
const result = await fetch('/api/features');
|
|
||||||
const features = await result.json();
|
|
||||||
return {
|
return {
|
||||||
features,
|
|
||||||
title: 'Spot micro controller',
|
title: 'Spot micro controller',
|
||||||
github: 'runeharlyk/SpotMicroESP32-Leika',
|
github: 'runeharlyk/SpotMicroESP32-Leika',
|
||||||
app_name: 'Spot Micro Controller',
|
app_name: 'Spot Micro Controller',
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
import Stopwatch from '~icons/tabler/24-hours';
|
import Stopwatch from '~icons/tabler/24-hours';
|
||||||
import type { NTPSettings, NTPStatus } from '$lib/types/models';
|
import type { NTPSettings, NTPStatus } from '$lib/types/models';
|
||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
|
import { useFeatureFlags } from '$lib/stores/featureFlags';
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
let ntpSettings: NTPSettings;
|
let ntpSettings: NTPSettings;
|
||||||
let ntpStatus: NTPStatus;
|
let ntpStatus: NTPStatus;
|
||||||
@@ -45,7 +48,7 @@
|
|||||||
onDestroy(() => clearInterval(interval));
|
onDestroy(() => clearInterval(interval));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!$page.data.features.security || $user.admin) {
|
if (!$features.security || $user.admin) {
|
||||||
getNTPSettings();
|
getNTPSettings();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -209,7 +212,7 @@
|
|||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !$page.data.features.security || $user.admin}
|
{#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
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SettingsCard from "$lib/components/SettingsCard.svelte";
|
import SettingsCard from "$lib/components/SettingsCard.svelte";
|
||||||
import MdiConnection from '~icons/mdi/connection';
|
import MdiConnection from '~icons/mdi/connection';
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { socket } from "$lib/stores";
|
import { socket } from "$lib/stores";
|
||||||
import type { I2CDevice } from "$lib/types/models";
|
import type { I2CDevice } from "$lib/types/models";
|
||||||
|
|
||||||
@@ -18,10 +18,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
socket.on('i2cScan', handleScan);
|
socket.on('i2cScan', handleScan);
|
||||||
socket.sendEvent('i2cScan', "");
|
socket.sendEvent('i2cScan', "");
|
||||||
})
|
return () => socket.off('i2cScan', handleScan);
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
socket.off('i2cScan', handleScan);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleScan = (data: any) => {
|
const handleScan = (data: any) => {
|
||||||
|
|||||||
@@ -6,10 +6,12 @@
|
|||||||
import { cubicOut } from "svelte/easing";
|
import { cubicOut } from "svelte/easing";
|
||||||
import { slide } from "svelte/transition";
|
import { slide } from "svelte/transition";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { daisyColor } from "$lib/DaisyUiHelper";
|
import { daisyColor } from "$lib/utilities";
|
||||||
import { socket } from "$lib/stores";
|
import { socket } from "$lib/stores";
|
||||||
import type { IMU } from "$lib/types/models";
|
import type { IMU } from "$lib/types/models";
|
||||||
import { page } from "$app/stores";
|
import { useFeatureFlags } from "$lib/stores/featureFlags";
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
Chart.register(...registerables);
|
Chart.register(...registerables);
|
||||||
|
|
||||||
@@ -242,7 +244,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateData = () => {
|
const updateData = () => {
|
||||||
if ($page.data.features.imu) {
|
if ($features.imu) {
|
||||||
angleChart.data.labels = $imu.x;
|
angleChart.data.labels = $imu.x;
|
||||||
angleChart.data.datasets[0].data = $imu.x;
|
angleChart.data.datasets[0].data = $imu.x;
|
||||||
angleChart.data.datasets[1].data = $imu.y;
|
angleChart.data.datasets[1].data = $imu.y;
|
||||||
@@ -252,7 +254,7 @@
|
|||||||
angleChart.update('none');
|
angleChart.update('none');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($page.data.features.bmp) {
|
if ($features.bmp) {
|
||||||
tempChart.data.labels = $imu.bmp_temp;
|
tempChart.data.labels = $imu.bmp_temp;
|
||||||
tempChart.data.datasets[0].data = $imu.bmp_temp;
|
tempChart.data.datasets[0].data = $imu.bmp_temp;
|
||||||
tempChart.options.scales!.y!.min = Math.min(...$imu.bmp_temp) - 1;
|
tempChart.options.scales!.y!.min = Math.min(...$imu.bmp_temp) - 1;
|
||||||
@@ -272,7 +274,7 @@
|
|||||||
<SettingsCard collapsible={false}>
|
<SettingsCard collapsible={false}>
|
||||||
<Rotate3d slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
<Rotate3d slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||||
<span slot="title">IMU</span>
|
<span slot="title">IMU</span>
|
||||||
{#if $page.data.features.imu}
|
{#if $features.imu}
|
||||||
<div class="w-full overflow-x-auto">
|
<div class="w-full overflow-x-auto">
|
||||||
<div
|
<div
|
||||||
class="flex w-full flex-col space-y-1 h-60"
|
class="flex w-full flex-col space-y-1 h-60"
|
||||||
@@ -282,7 +284,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $page.data.features.bmp}
|
{#if $features.bmp}
|
||||||
<div class="w-full overflow-x-auto">
|
<div class="w-full overflow-x-auto">
|
||||||
<div
|
<div
|
||||||
class="flex w-full flex-col space-y-1 h-60"
|
class="flex w-full flex-col space-y-1 h-60"
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SystemMetrics from './SystemMetrics.svelte';
|
import SystemMetrics from './SystemMetrics.svelte';
|
||||||
import { page } from '$app/stores';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { useFeatureFlags } from '$lib/stores/featureFlags';
|
||||||
|
|
||||||
if (!$page.data.features.analytics) {
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
|
if (!$features.analytics) {
|
||||||
goto('/');
|
goto('/');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { cubicOut } from 'svelte/easing';
|
import { cubicOut } from 'svelte/easing';
|
||||||
import { Chart, registerables } from 'chart.js';
|
import { Chart, registerables } from 'chart.js';
|
||||||
import Metrics from '~icons/tabler/report-analytics';
|
import Metrics from '~icons/tabler/report-analytics';
|
||||||
import { daisyColor } from '$lib/DaisyUiHelper';
|
import { daisyColor } from '$lib/utilities';
|
||||||
import { analytics } from '$lib/stores/analytics';
|
import { analytics } from '$lib/stores/analytics';
|
||||||
|
|
||||||
Chart.register(...registerables);
|
Chart.register(...registerables);
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
import { convertSeconds } from '$lib/utilities';
|
import { convertSeconds } from '$lib/utilities';
|
||||||
|
|
||||||
|
import { useFeatureFlags } from '$lib/stores/featureFlags';
|
||||||
|
|
||||||
|
const features = useFeatureFlags()
|
||||||
|
|
||||||
let systemInformation: SystemInformation;
|
let systemInformation: SystemInformation;
|
||||||
|
|
||||||
async function getSystemStatus() {
|
async function getSystemStatus() {
|
||||||
@@ -295,12 +299,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-wrap justify-end gap-2">
|
<div class="mt-4 flex flex-wrap justify-end gap-2">
|
||||||
{#if $page.data.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}
|
||||||
{#if !$page.data.features.security || $user.admin}
|
{#if !$features.security || $user.admin}
|
||||||
<button class="btn btn-primary inline-flex items-center" on:click={confirmRestart}
|
<button class="btn btn-primary inline-flex items-center" on:click={confirmRestart}
|
||||||
><Power class="mr-2 h-5 w-5" /><span>Restart</span></button
|
><Power class="mr-2 h-5 w-5" /><span>Restart</span></button
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,15 +2,17 @@
|
|||||||
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 { user } from '$lib/stores/user';
|
||||||
import { page } from '$app/stores';
|
import { useFeatureFlags } from '$lib/stores';
|
||||||
|
|
||||||
|
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 $page.data.features.download_firmware && (!$page.data.features.security || $user.admin)}
|
{#if $features.download_firmware && (!$features.security || $user.admin)}
|
||||||
<GithubFirmwareManager />
|
<GithubFirmwareManager />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $page.data.features.upload_firmware && (!$page.data.features.security || $user.admin)}
|
{#if $features.upload_firmware && (!$features.security || $user.admin)}
|
||||||
<UploadFirmware />
|
<UploadFirmware />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
import InfoDialog from '$lib/components/InfoDialog.svelte';
|
import InfoDialog from '$lib/components/InfoDialog.svelte';
|
||||||
import Check from '~icons/tabler/check';
|
import Check from '~icons/tabler/check';
|
||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
|
import { useFeatureFlags } from '$lib/stores';
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
async function getGithubAPI() {
|
async function getGithubAPI() {
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -45,7 +48,7 @@
|
|||||||
// check if the asset is of type *.bin
|
// check if the asset is of type *.bin
|
||||||
if (
|
if (
|
||||||
assets[i].name.includes('.bin') &&
|
assets[i].name.includes('.bin') &&
|
||||||
assets[i].name.includes($page.data.features.firmware_built_target)
|
assets[i].name.includes($features.firmware_built_target)
|
||||||
) {
|
) {
|
||||||
url = assets[i].browser_download_url;
|
url = assets[i].browser_download_url;
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{#each githubReleases as release}
|
{#each githubReleases as release}
|
||||||
<tr
|
<tr
|
||||||
class={compareVersions($page.data.features.firmware_version, release.tag_name) === 0
|
class={compareVersions($features.firmware_version, release.tag_name) === 0
|
||||||
? 'bg-primary text-primary-content'
|
? 'bg-primary text-primary-content'
|
||||||
: 'bg-base-100 h-14'}
|
: 'bg-base-100 h-14'}
|
||||||
>
|
>
|
||||||
@@ -124,7 +127,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
{#if compareVersions($page.data.features.firmware_version, release.tag_name) != 0}
|
{#if compareVersions($features.firmware_version, release.tag_name) != 0}
|
||||||
<button
|
<button
|
||||||
class="btn btn-ghost btn-circle btn-sm"
|
class="btn btn-ghost btn-circle btn-sm"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|||||||
@@ -14,6 +14,9 @@
|
|||||||
import Devices from '~icons/tabler/devices';
|
import Devices from '~icons/tabler/devices';
|
||||||
import type { ApSettings, ApStatus } from '$lib/types/models';
|
import type { ApSettings, ApStatus } from '$lib/types/models';
|
||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
|
import { useFeatureFlags } from '$lib/stores';
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
let apSettings: ApSettings;
|
let apSettings: ApSettings;
|
||||||
let apStatus: ApStatus;
|
let apStatus: ApStatus;
|
||||||
@@ -47,7 +50,7 @@
|
|||||||
onDestroy(() => clearInterval(interval));
|
onDestroy(() => clearInterval(interval));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!$page.data.features.security || $user.admin) {
|
if (!$features.security || $user.admin) {
|
||||||
getAPSettings();
|
getAPSettings();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -221,7 +224,7 @@
|
|||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !$page.data.features.security || $user.admin}
|
{#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"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import Cancel from '~icons/tabler/x';
|
import Cancel from '~icons/tabler/x';
|
||||||
import Reload from '~icons/tabler/reload';
|
import Reload from '~icons/tabler/reload';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import RssiIndicator from '$lib/components/RSSIIndicator.svelte';
|
import RssiIndicator from '$lib/components/statusbar/RSSIIndicator.svelte';
|
||||||
import type { NetworkItem } from '$lib/types/models';
|
import type { NetworkItem } from '$lib/types/models';
|
||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
import type { NetworkList } from '$lib/models';
|
import type { NetworkList } from '$lib/models';
|
||||||
|
|||||||
@@ -33,9 +33,11 @@
|
|||||||
import Check from '~icons/tabler/check';
|
import Check from '~icons/tabler/check';
|
||||||
import InfoDialog from '$lib/components/InfoDialog.svelte';
|
import InfoDialog from '$lib/components/InfoDialog.svelte';
|
||||||
import type { KnownNetworkItem, WifiSettings, WifiStatus } from '$lib/types/models';
|
import type { KnownNetworkItem, WifiSettings, WifiStatus } from '$lib/types/models';
|
||||||
import { socket } from '$lib/stores';
|
import { socket, useFeatureFlags } from '$lib/stores';
|
||||||
import { api } from '$lib/api';
|
import { api } from '$lib/api';
|
||||||
|
|
||||||
|
const features = useFeatureFlags();
|
||||||
|
|
||||||
let networkEditable: KnownNetworkItem = {
|
let networkEditable: KnownNetworkItem = {
|
||||||
ssid: '',
|
ssid: '',
|
||||||
password: '',
|
password: '',
|
||||||
@@ -435,7 +437,7 @@
|
|||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !$page.data.features.security || $user.admin}
|
{#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"
|
||||||
@@ -489,7 +491,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="font-bold">{dndNetworkList[index].ssid}</div>
|
<div class="font-bold">{dndNetworkList[index].ssid}</div>
|
||||||
</div>
|
</div>
|
||||||
{#if !$page.data.features.security || $user.admin}
|
{#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
|
||||||
|
|||||||
Reference in New Issue
Block a user