🪄 Adds api service with updates

This commit is contained in:
Rune Harlyk
2024-05-08 13:26:40 +02:00
committed by Rune Harlyk
parent 4c66c428e6
commit b7ae17f3bf
19 changed files with 391 additions and 573 deletions
+73
View File
@@ -0,0 +1,73 @@
import { user } from '$lib/stores/user';
import { get } from 'svelte/store';
import { Err, Ok, type Result } from './utilities';
export namespace api {
export function get<TResponse>(endpoint: string, params?: RequestInit) {
return sendRequest<TResponse>(endpoint, 'GET', null, params);
}
export function post<TResponse>(endpoint: string, data?: unknown) {
return sendRequest<TResponse>(endpoint, 'POST', data);
}
export function put<TResponse>(endpoint: string, data?: unknown) {
return sendRequest<TResponse>(endpoint, 'PUT', data);
}
export function remove<TResponse>(endpoint: string) {
return sendRequest<TResponse>(endpoint, 'DELETE');
}
}
async function sendRequest<TResponse>(
endpoint: string,
method: string,
data?: unknown,
params?: RequestInit
): Promise<Result<TResponse, Error>> {
const user_token = get(user).bearer_token;
const body = data !== null && typeof data !== 'undefined' ? JSON.stringify(data) : undefined;
const request = {
...params,
method,
body,
headers: {
...params?.headers,
Authorization: user_token ? 'Bearer ' + user_token : 'Basic',
'Content-Type': 'application/json'
}
};
let response;
try {
response = await fetch(endpoint, request);
} catch (error) {
return Err.new(new Error(), 'An error has occurred');
}
const isResponseOk = response.status >= 200 && response.status < 400;
if (!isResponseOk) {
if (response.status === 401) {
return Err.new(new ApiError(response), 'User was not authorized');
}
return Err.new(new ApiError(response), 'An error has occurred');
}
const contentType = response.headers.get('Content-Type') ?? response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
return Ok.new(data as TResponse);
} else {
// Handle empty object as response
return Ok.new(null as TResponse);
}
}
export class ApiError extends Error {
constructor(public readonly response: Response) {
super(`${response.status}`);
}
}
+42 -52
View File
@@ -10,6 +10,8 @@
import GithubUpdateDialog from '$lib/components/GithubUpdateDialog.svelte';
import { compareVersions } from 'compare-versions';
import { onMount } from 'svelte';
import { api } from '$lib/api';
import type { GithubRelease } from '$lib/models';
export let update = false;
@@ -17,67 +19,55 @@
let firmwareDownloadLink: string;
async function getGithubAPI() {
try {
const response = await fetch(
'https://api.github.com/repos/' + $page.data.github + '/releases/latest',
{
method: 'GET',
headers: {
accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
}
);
const results = await response.json();
if (results.message == "Not Found") {
console.error('Error: Could not find releases in the repository');
return;
const headers = {
accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
const result = await api.get<GithubRelease>(`https://api.github.com/repos/${$page.data.github}/releases/latest`, {headers})
if (result.inner.message === "404" || result.inner.message == "Not Found") {
console.warn('Error: Could not find releases in the repository');
return
}
if (result.isErr()) {
console.error('Error:', result.inner);
return
}
const results = result.inner;
update = false;
firmwareVersion = '';
if (compareVersions(results.tag_name, $page.data.features.firmware_version) === 1) {
// iterate over assets and find the correct one
for (let i = 0; i < results.assets.length; i++) {
// check if the asset is of type *.bin
if (
results.assets[i].name.includes('.bin') &&
results.assets[i].name.includes($page.data.features.firmware_built_target)
) {
update = true;
firmwareVersion = results.tag_name;
firmwareDownloadLink = results.assets[i].browser_download_url;
notifications.info('Firmware update available.', 5000);
}
}
update = false;
firmwareVersion = '';
if (compareVersions(results.tag_name, $page.data.features.firmware_version) === 1) {
// iterate over assets and find the correct one
for (let i = 0; i < results.assets.length; i++) {
// check if the asset is of type *.bin
if (
results.assets[i].name.includes('.bin') &&
results.assets[i].name.includes($page.data.features.firmware_built_target)
) {
update = true;
firmwareVersion = results.tag_name;
firmwareDownloadLink = results.assets[i].browser_download_url;
notifications.info('Firmware update available.', 5000);
}
}
}
} catch (error) {
console.error('Error:', error);
}
}
}
async function postGithubDownload(url: string) {
try {
const apiResponse = await fetch('/api/downloadUpdate', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify({ download_url: url })
});
} catch (error) {
console.error('Error:', error);
}
const result = await api.post('/api/downloadUpdate', { download_url: url });
if (result.isErr()){
console.error('Error:', result.inner);
return
}
}
onMount(() => {
onMount(async () => {
if ($page.data.features.download_firmware && (!$page.data.features.security || $user.admin)) {
getGithubAPI();
await getGithubAPI();
const interval = setInterval(
async () => {
getGithubAPI();
await getGithubAPI();
},
60 * 60 * 1000
); // once per hour
+16 -1
View File
@@ -7,6 +7,17 @@ export interface ControllerInput {
speed: number;
}
export type GithubRelease = {
message: string;
tag_name: string;
assets: Array<{
name: string;
browser_download_url: string;
}>;
};
export type JWT = { access_token: string };
export type angles = number[] | Int16Array;
export type WifiStatus = {
@@ -26,7 +37,11 @@ export type WifiStatus = {
export type WifiSettings = {
hostname: string;
priority_RSSI: boolean;
wifi_networks: networkItem[];
wifi_networks: NetworkItem[];
};
export type NetworkList = {
networks: NetworkItem[];
};
export type KnownNetworkItem = {
+27
View File
@@ -7,3 +7,30 @@ export const humanFileSize = (size: number): string => {
export const capitalize = (str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};
export const convertSeconds = (seconds: number) => {
// Calculate the number of seconds, minutes, hours, and days
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
let days = Math.floor(hours / 24);
// Calculate the remaining hours, minutes, and seconds
hours = hours % 24;
minutes = minutes % 60;
seconds = seconds % 60;
// Create the formatted string
let result = '';
if (days > 0) {
result += days + ' day' + (days > 1 ? 's' : '') + ' ';
}
if (hours > 0) {
result += hours + ' hour' + (hours > 1 ? 's' : '') + ' ';
}
if (minutes > 0) {
result += minutes + ' minute' + (minutes > 1 ? 's' : '') + ' ';
}
result += seconds + ' second' + (seconds > 1 ? 's' : '');
return result;
};
+6 -14
View File
@@ -15,6 +15,7 @@
import Login from './login.svelte';
import { ModesEnum, mode, outControllerData, servoAngles, servoAnglesOut, socket } from '$lib/stores';
import type { Analytics, Battery, DownloadOTA } from '$lib/types/models';
import { api } from '$lib/api';
onMount(async () => {
if ($user.bearer_token !== '') {
@@ -64,20 +65,11 @@
};
async function validateUser(userdata: userProfile) {
try {
const response = await fetch('/api/verifyAuthorization', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + userdata.bearer_token,
'Content-Type': 'application/json'
}
});
if (response.status !== 200) {
user.invalidate();
}
} catch (error) {
console.error('Error:', error);
}
const result = await api.get('/api/verifyAuthorization')
if (result.isErr()){
user.invalidate();
console.error('Error:', result.inner);
}
}
const handleOpen = () => {
+22 -43
View File
@@ -12,6 +12,7 @@
import MQTT from '~icons/tabler/topology-star-3';
import Client from '~icons/tabler/robot';
import type { MQTTSettings, MQTTStatus } from '$lib/models';
import { api } from '$lib/api';
let mqttSettings: MQTTSettings;
let mqttStatus: MQTTStatus;
@@ -19,34 +20,22 @@
let formField: any;
async function getMQTTStatus() {
try {
const response = await fetch('/api/mqttStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
mqttStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
const result = await api.get<MQTTStatus>('/api/mqttStatus');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
mqttStatus = result.inner
return mqttStatus;
}
async function getMQTTSettings() {
try {
const response = await fetch('/api/mqttSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
mqttSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
const result = await api.get<MQTTSettings>('/api/mqttSettings');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
mqttSettings = result.inner
return mqttSettings;
}
@@ -70,25 +59,15 @@
};
async function postMQTTSettings(data: MQTTSettings) {
try {
const response = await fetch('/api/mqttSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('MQTT settings updated.', 3000);
mqttSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
return;
const result = await api.post<MQTTSettings>('/api/mqttSettings', data);
if (result.isErr()){
console.error('Error:', result.inner);
notifications.error('User not authorized.', 3000);
return
}
notifications.success('MQTT settings updated.', 3000);
mqttSettings = result.inner
return mqttSettings;
}
function handleSubmitMQTT() {
@@ -2,32 +2,24 @@
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
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 Spinner from '$lib/components/Spinner.svelte';
import MQTT from '~icons/tabler/topology-star-3';
import Info from '~icons/tabler/info-circle';
import type { BrokerSettings } from '$lib/types/models';
import { api } from '$lib/api';
let brokerSettings: BrokerSettings;
let formField: any;
async function getBrokerSettings() {
try {
const response = await fetch('/api/brokerSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
brokerSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
const result = await api.get<BrokerSettings>('/api/brokerSettings');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
brokerSettings = result.inner
}
let formErrors = {
@@ -37,25 +29,14 @@
};
async function postBrokerSettings() {
try {
const response = await fetch('/api/brokerSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(brokerSettings)
});
if (response.status == 200) {
notifications.success('Broker settings updated.', 3000);
brokerSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
return;
const result = await api.post<BrokerSettings>('/api/brokerSettings', brokerSettings);
if (result.isErr()){
console.error('Error:', result.inner);
notifications.error('User not authorized.', 3000);
return
}
notifications.success('Broker settings updated.', 3000);
brokerSettings = result.inner
}
function handleSubmitBroker() {
+20 -45
View File
@@ -15,40 +15,27 @@
import UTC from '~icons/tabler/clock-pin';
import Stopwatch from '~icons/tabler/24-hours';
import type { NTPSettings, NTPStatus } from '$lib/types/models';
import { api } from '$lib/api';
let ntpSettings: NTPSettings;
let ntpStatus: NTPStatus;
async function getNTPStatus() {
try {
const response = await fetch('/api/ntpStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
ntpStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
const result = await api.get<NTPStatus>('/api/ntpStatus');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
ntpStatus = result.inner
}
async function getNTPSettings() {
try {
const response = await fetch('/api/ntpSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
ntpSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
const result = await api.get<NTPSettings>('/api/ntpSettings');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
ntpSettings = result.inner
}
const interval = setInterval(async () => {
@@ -70,25 +57,13 @@
};
async function postNTPSettings(data: NTPSettings) {
try {
const response = await fetch('/api/ntpSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('Security settings updated.', 3000);
ntpSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
const result = await api.post<NTPSettings>('/api/ntpSettings', data);
if (result.isErr()){
notifications.error('User not authorized.', 3000);
console.error('Error:', result.inner);
return
}
ntpSettings = result.inner
}
function handleSubmitNTP() {
+17 -25
View File
@@ -5,6 +5,8 @@
import { notifications } from '$lib/components/toasts/notifications';
import { fade, fly } from 'svelte/transition';
import Login from '~icons/tabler/login';
import { api } from '$lib/api';
import type { JWT } from '$lib/models';
type SignInData = {
password: string;
@@ -19,31 +21,21 @@
let token = { access_token: '' };
async function signInUser(data: SignInData) {
try {
const response = await fetch('/api/signIn', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status === 200) {
token = await response.json();
user.init(token.access_token);
let username = $user.username;
notifications.success('User ' + username + ' signed in', 5000);
} else {
username = '';
password = '';
notifications.error('Wrong Username or Password!', 5000);
loginFailed = true;
setTimeout(() => {
loginFailed = false;
}, 1500);
}
} catch (error) {
console.error('Error:', error);
}
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>
+2 -9
View File
@@ -2,7 +2,6 @@
import { page } from '$app/stores';
import { telemetry } from '$lib/stores/telemetry';
import { openModal, closeModal } from 'svelte-modals';
import { user } from '$lib/stores/user';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import WiFiOff from '~icons/tabler/wifi-off';
import Hamburger from '~icons/tabler/menu-2';
@@ -13,15 +12,9 @@
import UpdateIndicator from '$lib/components/UpdateIndicator.svelte';
import MdiWeatherSunny from '~icons/mdi/weather-sunny';
import MdiMoonAndStars from '~icons/mdi/moon-and-stars';
import { api } from '$lib/api';
async function postSleep() {
const response = await fetch('/api/sleep', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
}
});
}
const postSleep = async () => await api.post('/api/sleep')
function confirmSleep() {
openModal(ConfirmDialog, {
@@ -3,11 +3,12 @@
import Spinner from "$lib/components/Spinner.svelte";
import FolderIcon from '~icons/mdi/folder-outline';
import Folder from "./Folder.svelte";
import { api } from "$lib/api";
const getFiles = async () => {
const response = await fetch('/api/files/list');
if (response.ok) {
return response.json();
const result = await api.get('/api/files/list')
if (result.isOk()) {
return result.inner;
}
};
</script>
@@ -26,25 +26,25 @@
import SDK from '~icons/tabler/sdk';
import type { SystemInformation, Analytics } from '$lib/types/models';
import { socket } from '$lib/stores/socket';
import { api } from '$lib/api';
import { convertSeconds } from '$lib/utilities';
let systemInformation: SystemInformation;
async function getSystemStatus() {
try {
const response = await fetch('/api/systemStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
systemInformation = await response.json();
} catch (error) {
console.log('Error:', error);
}
const result = await api.get<SystemInformation>('/api/systemStatus');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
systemInformation = result.inner
return systemInformation;
}
const postFactoryReset = async () => await api.post('/api/factoryReset')
const postSleep = async () => await api.post('api/sleep')
onMount(() => socket.on('analytics', handleSystemData));
onDestroy(() => socket.off('analytics', handleSystemData));
@@ -52,14 +52,7 @@
const handleSystemData = (data: Analytics) =>
(systemInformation = { ...systemInformation, ...data });
async function postRestart() {
const response = await fetch('/api/restart', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
}
});
}
const postRestart = async () => await api.post('/api/restart');
function confirmRestart() {
openModal(ConfirmDialog, {
@@ -76,15 +69,6 @@
});
}
async function postFactoryReset() {
const response = await fetch('/api/factoryReset', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
}
});
}
function confirmReset() {
openModal(ConfirmDialog, {
title: 'Confirm Factory Reset',
@@ -100,15 +84,6 @@
});
}
async function postSleep() {
const response = await fetch('/api/sleep', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
}
});
}
function confirmSleep() {
openModal(ConfirmDialog, {
title: 'Confirm Going to Sleep',
@@ -123,33 +98,6 @@
}
});
}
function convertSeconds(seconds: number) {
// Calculate the number of seconds, minutes, hours, and days
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
let days = Math.floor(hours / 24);
// Calculate the remaining hours, minutes, and seconds
hours = hours % 24;
minutes = minutes % 60;
seconds = seconds % 60;
// Create the formatted string
let result = '';
if (days > 0) {
result += days + ' day' + (days > 1 ? 's' : '') + ' ';
}
if (hours > 0) {
result += hours + ' hour' + (hours > 1 ? 's' : '') + ' ';
}
if (minutes > 0) {
result += minutes + ' minute' + (minutes > 1 ? 's' : '') + ' ';
}
result += seconds + ' second' + (seconds > 1 ? 's' : '');
return result;
}
</script>
<SettingsCard collapsible={false}>
@@ -1,5 +1,4 @@
<script lang="ts">
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { openModal, closeModal, closeAllModals } from 'svelte-modals';
import { slide } from 'svelte/transition';
@@ -14,43 +13,29 @@
import Error from '~icons/tabler/circle-x';
import { compareVersions } from 'compare-versions';
import GithubUpdateDialog from '$lib/components/GithubUpdateDialog.svelte';
import { assets } from '$app/paths';
import InfoDialog from '$lib/components/InfoDialog.svelte';
import Check from '~icons/tabler/check';
import { api } from '$lib/api';
async function getGithubAPI() {
try {
const githubResponse = await fetch(
'https://api.github.com/repos/' + $page.data.github + '/releases',
{
method: 'GET',
headers: {
accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
}
);
const results = await githubResponse.json();
return results;
} catch (error) {
console.error('Error:', error);
}
return;
const headers = {
accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
}
const result = await api.get(`https://api.github.com/repos/${$page.data.github}/releases`, {headers})
if (result.isErr()) {
console.error('Error:', result.inner);
return
}
return result.inner as any;
}
async function postGithubDownload(url: string) {
try {
const apiResponse = await fetch('/api/downloadUpdate', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify({ download_url: url })
});
} catch (error) {
console.error('Error:', error);
}
const result = await api.post('/api/downloadUpdate', { download_url: url })
if (result.isErr()) {
console.error('Error:', result.inner);
return
}
}
function confirmGithubUpdate(assets: any) {
@@ -1,30 +1,20 @@
<script lang="ts">
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 SettingsCard from '$lib/components/SettingsCard.svelte';
import OTA from '~icons/tabler/file-upload';
import Warning from '~icons/tabler/alert-triangle';
import Cancel from '~icons/tabler/x';
import { api } from '$lib/api';
let files: FileList;
async function uploadBIN() {
try {
const formData = new FormData();
formData.append('file', files[0]);
const response = await fetch('/api/uploadFirmware', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic'
},
body: formData
});
const result = await response.json();
} catch (error) {
console.error('Error:', error);
}
const formData = new FormData();
formData.append('file', files[0]);
const result = await api.post('/api/uploadFirmware', formData)
if (result.isErr())
console.error('Error:', result.inner);
}
function confirmBinUpload() {
+21 -52
View File
@@ -20,6 +20,7 @@
import Warning from '~icons/tabler/alert-triangle';
import Cancel from '~icons/tabler/x';
import Check from '~icons/tabler/check';
import { api } from '$lib/api';
type userSetting = {
username: string;
@@ -35,63 +36,31 @@
let securitySettings: SecuritySettings;
async function getSecuritySettings() {
try {
const response = await fetch('/api/securitySettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
securitySettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
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) {
try {
const response = await fetch('/api/securitySettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
securitySettings = await response.json();
if (response.status == 200) {
if (await validateUser($user)) {
notifications.success('Security settings updated.', 3000);
}
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
return;
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(userdata: userProfile) {
try {
const response = await fetch('/api/verifyAuthorization', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + userdata.bearer_token,
'Content-Type': 'application/json'
}
});
if (response.status !== 200) {
user.invalidate();
return false;
}
} catch (error) {
console.error('Error:', error);
}
return true;
async function validateUser() {
const result = await api.get('/api/verifyAuthorization')
if (result.isErr()) user.invalidate();
return result.isOk();
}
function confirmDelete(index: number) {
+21 -42
View File
@@ -13,6 +13,7 @@
import Home from '~icons/tabler/home';
import Devices from '~icons/tabler/devices';
import type { ApSettings, ApStatus } from '$lib/types/models';
import { api } from '$lib/api';
let apSettings: ApSettings;
let apStatus: ApStatus;
@@ -20,34 +21,22 @@
let formField: any;
async function getAPStatus() {
try {
const response = await fetch('/api/apStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
apStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
const result = await api.get<ApStatus>('/api/apStatus');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
apStatus = result.inner
return apStatus;
}
async function getAPSettings() {
try {
const response = await fetch('/api/apSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
apSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
const result = await api.get<ApSettings>('/api/apSetting');
if (result.isErr()){
console.error('Error:', result.inner);
return
}
apSettings = result.inner
return apSettings;
}
@@ -94,24 +83,14 @@
};
async function postAPSettings(data: ApSettings) {
try {
const response = await fetch('/api/apSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('Access Point settings updated.', 3000);
apSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
const result = await api.post<ApSettings>('/api/apSettings', data);
if (result.isErr()){
notifications.error('User not authorized.', 3000);
console.error('Error:', result.inner);
return
}
notifications.success('Access Point settings updated.', 3000);
apSettings = result.inner
}
function handleSubmitAP() {
+17 -32
View File
@@ -2,8 +2,6 @@
import { closeModal } from 'svelte-modals';
import { focusTrap } from 'svelte-focus-trap';
import { fly } from 'svelte/transition';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import Network from '~icons/tabler/router';
import AP from '~icons/tabler/access-point';
import Cancel from '~icons/tabler/x';
@@ -11,6 +9,8 @@
import { onMount, onDestroy } from 'svelte';
import RssiIndicator from '$lib/components/RSSIIndicator.svelte';
import type { NetworkItem } from '$lib/types/models';
import { api } from '$lib/api';
import type { NetworkList } from '$lib/models';
// provided by <Modals />
export let isOpen: boolean;
@@ -36,13 +36,7 @@
async function scanNetworks() {
scanActive = true;
const scan = await fetch('/api/scanNetworks', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
await api.get('/api/scanNetworks');
if ((await pollingResults()) == false) {
pollingId = setInterval(() => pollingResults(), 1000);
}
@@ -50,28 +44,19 @@
}
async function pollingResults() {
const response = await fetch('/api/listNetworks', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
try {
const result = await response.json();
listOfNetworks = result.networks;
if (listOfNetworks.length) {
scanActive = false;
clearInterval(pollingId);
pollingId = 0;
return true;
} else {
scanActive = false;
return false;
}
} catch {
return false;
}
const result = await api.get<NetworkList>('/api/listNetworks');
if (result.isErr()){
console.error(`Error occurred while fetching: `, result.inner);
return false
}
let response = result.inner
listOfNetworks = response.networks;
scanActive = false;
if (listOfNetworks.length) {
clearInterval(pollingId);
pollingId = 0;
}
return listOfNetworks.length;
}
onMount(() => {
@@ -96,7 +81,7 @@
use:focusTrap
>
<div
class="bg-base-100 shadow rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
class="bg-base-100 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
>
<h2 class="text-base-content text-start text-2xl font-bold">Scan Networks</h2>
<div class="divider my-2" />
+22 -43
View File
@@ -34,6 +34,7 @@
import InfoDialog from '$lib/components/InfoDialog.svelte';
import type { KnownNetworkItem, WifiSettings, WifiStatus } from '$lib/types/models';
import { socket } from '$lib/stores';
import { api } from '$lib/api';
let networkEditable: KnownNetworkItem = {
ssid: '',
@@ -72,35 +73,23 @@
let formErrorhostname = false;
async function getWifiStatus() {
try {
const response = await fetch('/api/wifiStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
wifiStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
const result = await api.get<WifiStatus>('/api/wifiStatus');
if (result.isErr()){
console.error(`Error occurred while fetching: `, result.inner);
return
}
wifiStatus = result.inner
return wifiStatus;
}
async function getWifiSettings() {
try {
const response = await fetch('/api/wifiSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
wifiSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
dndNetworkList = wifiSettings.wifi_networks;
const result = await api.get<WifiSettings>('/api/wifiSettings');
if (result.isErr()){
console.error(`Error occurred while fetching: `, result.inner);
return
}
wifiSettings = result.inner
dndNetworkList = wifiSettings.wifi_networks;
return wifiSettings;
}
@@ -114,24 +103,14 @@
});
async function postWiFiSettings(data: WifiSettings) {
try {
const response = await fetch('/api/wifiSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('Wi-Fi settings updated.', 3000);
wifiSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
const result = await api.post<WifiSettings>('/api/wifiSettings', data);
if (result.isErr()){
console.error(`Error occurred while fetching: `, result.inner);
notifications.error('User not authorized.', 3000);
return
}
wifiSettings = result.inner
notifications.success('Wi-Fi settings updated.', 3000);
}
function validateHostName() {
+31 -66
View File
@@ -7,6 +7,7 @@
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
* Copyright (C) 2024 runeharlyk
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
@@ -14,74 +15,38 @@
#include <FeaturesService.h>
FeaturesService::FeaturesService(PsychicHttpServer *server) : _server(server)
{
FeaturesService::FeaturesService(PsychicHttpServer *server) : _server(server) {}
void FeaturesService::begin() {
_server->on(FEATURES_SERVICE_PATH, HTTP_GET, [&](PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
root["security"] = FT_SECURITY;
root["mqtt"] = FT_MQTT;
root["ntp"] = FT_NTP;
root["upload_firmware"] = FT_UPLOAD_FIRMWARE;
root["download_firmware"] = FT_DOWNLOAD_FIRMWARE;
root["sleep"] = FT_SLEEP;
root["battery"] = FT_BATTERY;
root["analytics"] = FT_ANALYTICS;
root["firmware_version"] = APP_VERSION;
root["firmware_name"] = APP_NAME;
root["firmware_built_target"] = BUILD_TARGET;
// Iterate over user features
for (auto &element : userFeatures) {
root[element.feature.c_str()] = element.enabled;
}
return response.send();
});
ESP_LOGV("FeaturesService", "Registered GET endpoint: %s",
FEATURES_SERVICE_PATH);
}
void FeaturesService::begin()
{
_server->on(FEATURES_SERVICE_PATH, HTTP_GET, [&](PsychicRequest *request)
{
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject root = response.getRoot();
#if FT_ENABLED(FT_SECURITY)
root["security"] = true;
#else
root["security"] = false;
#endif
#if FT_ENABLED(FT_MQTT)
root["mqtt"] = true;
#else
root["mqtt"] = false;
#endif
#if FT_ENABLED(FT_NTP)
root["ntp"] = true;
#else
root["ntp"] = false;
#endif
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
root["upload_firmware"] = true;
#else
root["upload_firmware"] = false;
#endif
#if FT_ENABLED(FT_DOWNLOAD_FIRMWARE)
root["download_firmware"] = true;
#else
root["download_firmware"] = false;
#endif
#if FT_ENABLED(FT_SLEEP)
root["sleep"] = true;
#else
root["sleep"] = false;
#endif
#if FT_ENABLED(FT_BATTERY)
root["battery"] = true;
#else
root["battery"] = false;
#endif
#if FT_ENABLED(FT_ANALYTICS)
root["analytics"] = true;
#else
root["analytics"] = false;
#endif
root["firmware_version"] = APP_VERSION;
root["firmware_name"] = APP_NAME;
root["firmware_built_target"] = BUILD_TARGET;
// Iterate over user features
for (auto &element : userFeatures)
{
root[element.feature.c_str()] = element.enabled;
}
return response.send(); });
ESP_LOGV("FeaturesService", "Registered GET endpoint: %s", FEATURES_SERVICE_PATH);
}
void FeaturesService::addFeature(String feature, bool enabled)
{
void FeaturesService::addFeature(String feature, bool enabled) {
UserFeature newFeature;
newFeature.feature = feature;
newFeature.enabled = enabled;