reorganised helper functions

This commit is contained in:
Chris Bisset 2025-03-22 23:55:03 +00:00
parent 0636ea9419
commit e855f0694e
6 changed files with 193 additions and 181 deletions

View file

@ -0,0 +1,52 @@
<script module lang="ts">
import { SvelteMap } from 'svelte/reactivity';
export class PersistentAppSettingsObject {
daisyUITheme = ''; // for setting the UI theme. See https://daisyui.com/docs/themes/
headscaleAPIKey = ''; // sensitive, allows for administrative access to headscale
headscaleURL = ''; // url for headscale to use
debugLogging = false; // to turn on additional messages
public constructor(init?: Partial<PersistentAppSettingsObject>) {
Object.assign(this, init);
}
}
export class AppSettingsObject {
navbarTitle = ''; // for setting the title of the page
appLoaded = false; // for hiding the screen until hydration has completed
sidebarDrawerOpen = false; // for determining if the sidebar is open when on a small screen
toastAlerts = new SvelteMap<string, toastAlert>(); // for adding or removing alerts
apiTested = true; // used to hide the app if the api tests are failing
apiKeyList: APIKey[] = []; //list of apikeys retrieved from headscale API
apiKeyExpiration?: number = undefined; // number of days left until the key in use expires
public constructor(init?: Partial<AppSettingsObject>) {
Object.assign(this, init);
}
}
// alert used for populating toasts in the layout
export class toastAlert {
message = ''; //message to display
notificationType = 'alert'; //to style the toast
id = ''; //UUID generated to reference the toast
public constructor(init?: Partial<toastAlert>) {
Object.assign(this, init);
}
}
// retrieved as an array from headscale
export class APIKey {
id = ''; // unique identifier for headscale
prefix = ''; // beginning of key to match full string
expiration = ''; // when key expires, formatting as datetime
createdAt = ''; // date of creation
lastSeen = ''; // date last seen, seems to be always null?
public constructor(init?: Partial<APIKey>) {
Object.assign(this, init);
}
}
</script>

View file

@ -1,50 +0,0 @@
import { SvelteMap } from "svelte/reactivity";
export class PersistentAppSettingsObject {
daisyUITheme = "" // for setting the UI theme. See https://daisyui.com/docs/themes/
headscaleAPIKey = "" // sensitive, allows for administrative access to headscale
headscaleURL = "" // url for headscale to use
debugLogging = false // to turn on additional messages
public constructor(init?: Partial<PersistentAppSettingsObject>) {
Object.assign(this, init);
}
}
export class AppSettingsObject {
navbarTitle = "" // for setting the title of the page
appLoaded = false // for hiding the screen until hydration has completed
sidebarDrawerOpen = false // for determining if the sidebar is open when on a small screen
toastAlerts = new SvelteMap<string, toastAlert>(); // for adding or removing alerts
apiTested = true // used to hide the app if the api tests are failing
apiKeyList: APIKey[] = [] //list of apikeys retrieved from headscale API
apiKeyExpiration?: number = undefined // number of days left until the key in use expires
public constructor(init?: Partial<AppSettingsObject>) {
Object.assign(this, init);
}
}
// alert used for populating toasts in the layout
export class toastAlert {
message = "" //message to display
notificationType = "alert" //to style the toast
id = "" //UUID generated to reference the toast
public constructor(init?: Partial<toastAlert>) {
Object.assign(this, init);
}
}
// retrieved as an array from headscale
export class APIKey {
id = '' // unique identifier for headscale
prefix = '' // beginning of key to match full string
expiration = '' // when key expires, formatting as datetime
createdAt = '' // date of creation
lastSeen = '' // date last seen, seems to be always null?
public constructor(init?: Partial<APIKey>) {
Object.assign(this, init);
}
}

View file

@ -1,3 +1,4 @@
<script module lang=ts>
import { appSettings } from "../common/state.svelte";
import { toastAlert } from "../common/classes.svelte";
@ -7,4 +8,5 @@ export function newToastAlert(message: string) {
id: uuid,
message: message
}));
}
}
</script>

View file

@ -0,0 +1,137 @@
<script module lang="ts">
import { newToastAlert } from '../layout/toast-functions.svelte';
import { appSettings, persistentAppSettings } from '../common/state.svelte';
export async function getAPIKeys() {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey`, {
method: 'GET',
headers: {
Authorization: `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
newToastAlert(`API test failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
} else {
appSettings.apiKeyList = (await response.json()).apiKeys;
appSettings.apiTested = true;
// determine the remaining time for the key we are currently using
appSettings.apiKeyList.forEach((key) => {
if (persistentAppSettings.headscaleAPIKey.startsWith(key.prefix)) {
getKeyRemainingTime(new Date(key.expiration));
}
});
}
} catch (error) {
let message: string;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
newToastAlert(`API test failed (check your server settings): ${message}`);
appSettings.apiTested = false;
}
}
function getKeyRemainingTime(expiration: Date) {
let currentTime = new Date();
// gets time difference in seconds
appSettings.apiKeyExpiration = Math.round((expiration.getTime() - currentTime.getTime()) / 1000 / 60 / 60 / 24);
if (appSettings.apiKeyExpiration < 30) {
newToastAlert(`${appSettings.apiKeyExpiration} days left before API Key expiry, consider rolling your key`);
}
}
export function rotateAPIKey() {
appSettings.apiKeyList.forEach((key) => {
// select the current key being used
if (persistentAppSettings.headscaleAPIKey.startsWith(key.prefix)) {
let currentKey = key;
let newExpiration = new Date();
newExpiration.setDate(newExpiration.getDate() + 90);
// create a new API key with the new new expiration, set it as the current API key,
// and then expire the previous API key
createNewAPIKey(newExpiration).then((apiKey) => {
if (apiKey == undefined) {
throw new Error('expecting API key string, string was undefined');
}
persistentAppSettings.headscaleAPIKey = apiKey;
expireAPIKey(currentKey.prefix).then(() => {
getAPIKeys().then(() => {
// console.log(appSettings.apiKeyList);
});
});
});
}
});
}
export async function createNewAPIKey(expireDate: Date) {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey`, {
method: 'POST',
headers: {
Authorization: `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
expiration: expireDate.toISOString()
})
});
if (!response.ok) {
newToastAlert(`Creating new API Key Failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
} else {
let apiKey = '';
apiKey = (await response.json()).apiKey;
return apiKey;
}
} catch (error) {
let message: string;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
newToastAlert(`API Call Failed (check your server settings): ${message}`);
appSettings.apiTested = false;
}
}
export async function expireAPIKey(apiPrefix: string) {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey/expire`, {
method: 'POST',
headers: {
Authorization: `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
prefix: apiPrefix
})
});
if (!response.ok) {
newToastAlert(`API test failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
}
} catch (error) {
let message: string;
if (error instanceof Error) {
message = error.message;
} else {
message = String(error);
}
newToastAlert(`API test failed (check your server settings): ${message}`);
appSettings.apiTested = false;
}
}
</script>

View file

@ -1,129 +0,0 @@
import { newToastAlert } from "../layout/toast.svelte.ts";
import { appSettings, persistentAppSettings } from "../common/state.svelte";
export async function getAPIKeys() {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
newToastAlert(`API test failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
} else {
appSettings.apiKeyList = (await response.json()).apiKeys;
appSettings.apiTested = true;
// determine the remaining time for the key we are currently using
appSettings.apiKeyList.forEach(key => {
if (persistentAppSettings.headscaleAPIKey.startsWith(key.prefix)) {
getKeyRemainingTime(new Date(key.expiration));
}
})
}
} catch (error) {
let message: string;
if (error instanceof Error) { message = error.message }
else { message = String(error) }
newToastAlert(`API test failed (check your server settings): ${message}`)
appSettings.apiTested = false;
}
}
function getKeyRemainingTime(expiration: Date) {
let currentTime = new Date();
// gets time difference in seconds
appSettings.apiKeyExpiration = Math.round((expiration.getTime() - currentTime.getTime()) / 1000 / 60 / 60 / 24);
if (appSettings.apiKeyExpiration < 30) {
newToastAlert(`${appSettings.apiKeyExpiration} days left before API Key expiry, consider rolling your key`);
}
}
export function rotateAPIKey() {
appSettings.apiKeyList.forEach(key => {
// select the current key being used
if (persistentAppSettings.headscaleAPIKey.startsWith(key.prefix)) {
let currentKey = key;
let newExpiration = new Date();
newExpiration.setDate(newExpiration.getDate() + 90);
// create a new API key with the new new expiration, set it as the current API key,
// and then expire the previous API key
createNewAPIKey(newExpiration).then((apiKey) => {
if (apiKey == undefined) {
throw new Error("expecting API key string, string was undefined");
}
persistentAppSettings.headscaleAPIKey = apiKey;
expireAPIKey(currentKey.prefix).then(() => {
getAPIKeys().then(() => {
// console.log(appSettings.apiKeyList);
});
});
});
}
})
}
export async function createNewAPIKey(expireDate: Date) {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"expiration": expireDate.toISOString()
})
});
if (!response.ok) {
newToastAlert(`Creating new API Key Failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
} else {
let apiKey = '';
apiKey = (await response.json()).apiKey;
return apiKey;
}
} catch (error) {
let message: string;
if (error instanceof Error) { message = error.message }
else { message = String(error) }
newToastAlert(`API Call Failed (check your server settings): ${message}`)
appSettings.apiTested = false;
}
}
export async function expireAPIKey(apiPrefix: string) {
try {
const response = await fetch(`${persistentAppSettings.headscaleURL}/api/v1/apikey/expire`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${persistentAppSettings.headscaleAPIKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"prefix": apiPrefix
})
});
if (!response.ok) {
newToastAlert(`API test failed (check your server settings): ${response.status}`);
appSettings.apiTested = false;
}
} catch (error) {
let message: string;
if (error instanceof Error) { message = error.message }
else { message = String(error) }
newToastAlert(`API test failed (check your server settings): ${message}`)
appSettings.apiTested = false;
}
}

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { persistentAppSettings } from '$lib/components/common/state.svelte';
import { getAPIKeys, rotateAPIKey } from './server-settings-functions.svelte.ts';
import { getAPIKeys, rotateAPIKey } from './server-settings-functions.svelte';
import { appSettings } from '$lib/components/common/state.svelte';
import { fly } from 'svelte/transition';