Merge pull request #15 from gurucomputing/feat-user-searching

Feat user searching
This commit is contained in:
routerino 2022-08-02 17:10:18 +10:00 committed by GitHub
commit 00ece42a88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 234 additions and 111 deletions

29
package-lock.json generated
View file

@ -15,6 +15,7 @@
"@vitejs/plugin-basic-ssl": "^0.1.1",
"autoprefixer": "^10.4.4",
"daisyui": "^2.19.0",
"fuse.js": "^6.6.2",
"postcss": "^8.4.12",
"postcss-load-config": "^3.1.4",
"prettier": "^2.6.2",
@ -185,10 +186,11 @@
}
},
"node_modules/@sveltejs/kit": {
"version": "1.0.0-next.398",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.398.tgz",
"integrity": "sha512-ONjHoRurnFUe+0lv0P9rZQpDuuJy4Cr/lDneoG+lkOR5/7gcvd03TlVdxLz1ZQAWPbgovm/PpshOvIUeC4n62A==",
"version": "1.0.0-next.401",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.401.tgz",
"integrity": "sha512-VrpFtSSu1ADRVWWDxLl218GKf9l+TA7tNCCAmVFAuJdj5zVxPgJflu4Dh1p5C4efKP+x1v6W5nvxmNkbZLPoag==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"chokidar": "^3.5.3",
@ -1275,6 +1277,15 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@ -2900,9 +2911,9 @@
}
},
"@sveltejs/kit": {
"version": "1.0.0-next.398",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.398.tgz",
"integrity": "sha512-ONjHoRurnFUe+0lv0P9rZQpDuuJy4Cr/lDneoG+lkOR5/7gcvd03TlVdxLz1ZQAWPbgovm/PpshOvIUeC4n62A==",
"version": "1.0.0-next.401",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.401.tgz",
"integrity": "sha512-VrpFtSSu1ADRVWWDxLl218GKf9l+TA7tNCCAmVFAuJdj5zVxPgJflu4Dh1p5C4efKP+x1v6W5nvxmNkbZLPoag==",
"dev": true,
"requires": {
"@sveltejs/vite-plugin-svelte": "^1.0.1",
@ -3601,6 +3612,12 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"fuse.js": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz",
"integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==",
"dev": true
},
"gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",

View file

@ -7,7 +7,6 @@
"package": "vite package",
"preview": "vite preview --https --port 443 --host 0.0.0.0",
"stage": "/usr/bin/caddy run --adapter caddyfile --config ./Caddyfile",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .",
@ -18,8 +17,10 @@
"@sveltejs/adapter-static": "^1.0.0-next.34",
"@sveltejs/kit": "next",
"@tailwindcss/typography": "github:tailwindcss/typography",
"@vitejs/plugin-basic-ssl": "^0.1.1",
"autoprefixer": "^10.4.4",
"daisyui": "^2.19.0",
"fuse.js": "^6.6.2",
"postcss": "^8.4.12",
"postcss-load-config": "^3.1.4",
"prettier": "^2.6.2",
@ -28,8 +29,7 @@
"svelte-check": "^2.7.1",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "^3.0.23",
"typescript": "^4.7.2",
"@vitejs/plugin-basic-ssl": "^0.1.1"
"typescript": "^4.7.2"
},
"type": "module",
"dependencies": {

View file

@ -1,32 +1,32 @@
<script>
import { onMount } from 'svelte';
import { headscaleDeviceSortStore, headscaleDeviceSortDirectionStore, headscaleUserSortStore, headscaleUserSortDirectionStore, headscaleThemeStore } from '$lib/common/stores.js';
import { headscaleURLStore } from '$lib/common/stores.js';
import { headscaleAPIKeyStore } from '$lib/common/stores.js';
import { deviceSortStore, deviceSortDirectionStore, userSortStore, sortDirectionStore, themeStore } from '$lib/common/stores.js';
import { URLStore } from '$lib/common/stores.js';
import { APIKeyStore } from '$lib/common/stores.js';
import { preAuthHideStore } from '$lib/common/stores.js';
onMount(async () => {
// stores headscale theme
headscaleThemeStore.set(localStorage.getItem('headscaleTheme') || 'hsui');
headscaleThemeStore.subscribe((val) => localStorage.setItem('headscaleTheme', val));
themeStore.set(localStorage.getItem('headscaleTheme') || 'hsui');
themeStore.subscribe((val) => localStorage.setItem('headscaleTheme', val));
// stores device sort preferences
headscaleDeviceSortStore.set(localStorage.getItem('headscaleDeviceSort') || 'id');
headscaleDeviceSortStore.subscribe((val) => localStorage.setItem('headscaleDeviceSort', val));
headscaleDeviceSortDirectionStore.set(localStorage.getItem('headscaleDeviceSortDirection') || 'ascending');
headscaleDeviceSortDirectionStore.subscribe((val) => localStorage.setItem('headscaleDeviceSortDirection', val));
deviceSortStore.set(localStorage.getItem('headscaleDeviceSort') || 'id');
deviceSortStore.subscribe((val) => localStorage.setItem('headscaleDeviceSort', val));
deviceSortDirectionStore.set(localStorage.getItem('headscaleDeviceSortDirection') || 'ascending');
deviceSortDirectionStore.subscribe((val) => localStorage.setItem('headscaleDeviceSortDirection', val));
// stores user sort preferences
headscaleUserSortStore.set(localStorage.getItem('headscaleUserSort') || 'id');
headscaleUserSortStore.subscribe((val) => localStorage.setItem('headscaleUserSort', val));
headscaleUserSortDirectionStore.set(localStorage.getItem('headscaleUserSortDirection') || 'ascending');
headscaleUserSortDirectionStore.subscribe((val) => localStorage.setItem('headscaleUserSortDirection', val));
userSortStore.set(localStorage.getItem('headscaleUserSort') || 'id');
userSortStore.subscribe((val) => localStorage.setItem('headscaleUserSort', val));
sortDirectionStore.set(localStorage.getItem('headscaleUserSortDirection') || 'ascending');
sortDirectionStore.subscribe((val) => localStorage.setItem('headscaleUserSortDirection', val));
// stores URL and API key
headscaleURLStore.set(localStorage.getItem('headscaleURL') || '');
headscaleURLStore.subscribe((val) => localStorage.setItem('headscaleURL', val));
headscaleAPIKeyStore.set(localStorage.getItem('headscaleAPIKey') || '');
headscaleAPIKeyStore.subscribe((val) => localStorage.setItem('headscaleAPIKey', val));
URLStore.set(localStorage.getItem('headscaleURL') || '');
URLStore.subscribe((val) => localStorage.setItem('headscaleURL', val));
APIKeyStore.set(localStorage.getItem('headscaleAPIKey') || '');
APIKeyStore.subscribe((val) => localStorage.setItem('headscaleAPIKey', val));
// stores whether preauthkeys get hidden when expired/used
preAuthHideStore.set((localStorage.getItem('headscalePreAuthHide') || 'false') == 'true');

View file

@ -1,6 +1,7 @@
<script context="module" lang="ts">
import { Device, PreAuthKey, Route, User } from '$lib/common/classes';
import { headscaleDeviceStore, headscaleUserStore, apiTestStore } from '$lib/common/stores.js'
import { deviceStore, userStore, apiTestStore } from '$lib/common/stores.js';
import { filterDevices, filterUsers } from './searching.svelte';
export async function getUsers(): Promise<any> {
// variables in local storage
@ -48,7 +49,9 @@
});
// Set the store
apiTestStore.set('succeeded');
headscaleUserStore.set(headscaleUsers);
userStore.set(headscaleUsers);
// Filter the store
filterUsers();
}
export async function editUser(currentUsername: string, newUsername: string): Promise<any> {
@ -220,7 +223,9 @@
});
// set the stores
apiTestStore.set('succeeded');
headscaleDeviceStore.set(headscaleDevices);
deviceStore.set(headscaleDevices);
// filter the store
filterDevices();
}
export async function getDeviceRoutes(deviceID: string): Promise<Route> {

View file

@ -0,0 +1,38 @@
<script context="module" lang="ts">
import { userFilterStore, userStore, userSearchStore, deviceFilterStore, deviceSearchStore, deviceStore } from './stores';
import { get } from 'svelte/store';
import Fuse from 'fuse.js';
import type { Device, User } from './classes';
export function filterUsers() {
// only run if we have search contents set
if (get(userSearchStore)) {
let options: Fuse.IFuseOptions<User> = {
keys: ['id', 'name']
};
let searcher = new Fuse(get(userStore), options);
// search using the searchstore term, and take the resultant array contents and set it to userFilterStore
userFilterStore.set(searcher.search(get(userSearchStore)).map((a) => a.item));
} else {
// if we have no search parameters, just copy across the whole object
userFilterStore.set(get(userStore));
}
}
export function filterDevices() {
// only run if we have search contents set
if (get(deviceSearchStore)) {
let options: Fuse.IFuseOptions<Device> = {
keys: ['id', 'givenName', 'name', 'forcedTags', 'validTags', 'namespace.name']
};
let searcher = new Fuse(get(deviceStore), options);
// search using the searchstore term, and take the resultant array contents and set it to userFilterStore
deviceFilterStore.set(searcher.search(get(deviceSearchStore)).map((a) => a.item));
} else {
// if we have no search parameters, just copy across the whole object
deviceFilterStore.set(get(deviceStore));
}
}
</script>

View file

@ -6,17 +6,22 @@ export const alertStore = writable('');
// used to determine if the API is functioning
export const apiTestStore = writable('');
// stores the theme
export const headscaleThemeStore = writable('');
export const themeStore = writable('');
// stores URL and API Key
export const headscaleURLStore = writable('');
export const headscaleAPIKeyStore = writable('');
export const URLStore = writable('');
export const APIKeyStore = writable('');
// stores preauth key preference
export const preAuthHideStore = writable(false);
// stores user and device data
export const headscaleUserStore = writable([new User()]);
export const headscaleDeviceStore = writable([new Device()]);
export const userStore = writable([new User()]);
export const userFilterStore = writable([new User()]);
export const deviceStore = writable([new Device()]);
export const deviceFilterStore = writable([new Device()]);
// stores search state
export const userSearchStore = writable('');
export const deviceSearchStore = writable('');
// stores sorting preferences
export const headscaleDeviceSortStore = writable('id');
export const headscaleDeviceSortDirectionStore = writable('ascending');
export const headscaleUserSortStore = writable('id');
export const headscaleUserSortDirectionStore = writable('ascending');
export const deviceSortStore = writable('id');
export const deviceSortDirectionStore = writable('ascending');
export const userSortStore = writable('id');
export const sortDirectionStore = writable('ascending');

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { headscaleUserStore, headscaleDeviceStore } from '$lib/common/stores';
import { userStore, deviceStore } from '$lib/common/stores';
import { getDevices, newDevice } from '$lib/common/apiFunctions.svelte';
import { alertStore } from '$lib/common/stores.js';
import { base } from '$app/paths';
@ -55,7 +55,7 @@
<div class="flex-none">
<label class="block text-secondary text-sm font-bold mb-2" for="select">Select User</label>
<select class="card-select mr-3" required bind:value={selectedUser}>
{#each $headscaleUserStore as user}
{#each $userStore as user}
<option>{user.name}</option>
{/each}
</select>

View file

@ -1,7 +1,7 @@
<script>
import { fade } from 'svelte/transition';
import { Device } from '$lib/common/classes';
import { headscaleUserStore, alertStore } from '$lib/common/stores';
import { userStore, alertStore } from '$lib/common/stores';
import { moveDevice, getDevices } from '$lib/common/apiFunctions.svelte';
export let device = new Device();
@ -38,7 +38,7 @@
{:else}
<form on:submit|preventDefault={moveDeviceAction}>
<select class="card-select mr-3" required bind:value={selectedUser}>
{#each $headscaleUserStore as user}
{#each $userStore as user}
<option>{user.name}</option>
{/each}
</select>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { getDevices, removeDevice } from '$lib/common/apiFunctions.svelte';
import { headscaleDeviceStore, alertStore } from '$lib/common/stores.js';
import { deviceStore, alertStore } from '$lib/common/stores.js';
import { Device } from '$lib/common/classes';
export let device = new Device();

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { filterDevices } from '$lib/common/searching.svelte';
import { deviceSearchStore } from '$lib/common/stores';
export let overrideSort = false;
// called when search changes
deviceSearchStore.subscribe((value) => {
if (value != '') {
overrideSort = true;
} else {
overrideSort = false;
}
filterDevices();
});
</script>
<!-- search icon -->
<div class="flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-3 mr-1 inline flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg> <input type="text" placeholder="search" bind:value={$deviceSearchStore} class="input input-bordered input-xs w-full max-w-xs" />
</div>

View file

@ -1,12 +1,12 @@
<script lang="ts">
import { getDevices } from '$lib/common/apiFunctions.svelte';
import { headscaleDeviceSortDirectionStore, headscaleDeviceSortStore } from '$lib/common/stores.js';
import { deviceSortDirectionStore, deviceSortStore } from '$lib/common/stores.js';
function sortAction() {
if ($headscaleDeviceSortDirectionStore == 'ascending') {
$headscaleDeviceSortDirectionStore = 'descending';
if ($deviceSortDirectionStore == 'ascending') {
$deviceSortDirectionStore = 'descending';
} else {
$headscaleDeviceSortDirectionStore = 'ascending';
$deviceSortDirectionStore = 'ascending';
}
getDevices();
}
@ -19,7 +19,7 @@
}}
class="mx-1"
>
{#if $headscaleDeviceSortDirectionStore == 'ascending'}
{#if $deviceSortDirectionStore == 'ascending'}
<!-- ascending sort icon -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
@ -34,8 +34,8 @@
</button>
<span class="btn-group">
<button class:btn-active="{$headscaleDeviceSortStore == 'id'}" on:click="{() => {$headscaleDeviceSortStore = 'id'; getDevices()}}" class="btn btn-xs">ID</button>
<button class:btn-active="{$headscaleDeviceSortStore == 'givenName'}" on:click="{() => {$headscaleDeviceSortStore = 'givenName'; getDevices()}}" class="btn btn-xs capitalize">Device Name</button>
<button class:btn-active="{$headscaleDeviceSortStore == 'lastSeen'}" on:click="{() => {$headscaleDeviceSortStore = 'lastSeen'; getDevices()}}" class="btn btn-xs capitalize">Last Seen</button>
<button class:btn-active="{$deviceSortStore == 'id'}" on:click="{() => {$deviceSortStore = 'id'; getDevices()}}" class="btn btn-xs">ID</button>
<button class:btn-active="{$deviceSortStore == 'givenName'}" on:click="{() => {$deviceSortStore = 'givenName'; getDevices()}}" class="btn btn-xs capitalize">Device Name</button>
<button class:btn-active="{$deviceSortStore == 'lastSeen'}" on:click="{() => {$deviceSortStore = 'lastSeen'; getDevices()}}" class="btn btn-xs capitalize">Last Seen</button>
</span>
</span>

View file

@ -1,12 +1,12 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { headscaleURLStore } from '$lib/common/stores.js';
import { headscaleAPIKeyStore } from '$lib/common/stores.js';
import { URLStore } from '$lib/common/stores.js';
import { APIKeyStore } from '$lib/common/stores.js';
import { getUsers } from '$lib/common/apiFunctions.svelte';
// Server Settings
let headscaleURL = $headscaleURLStore;
let headscaleAPIKey = $headscaleAPIKeyStore;
let headscaleURL = $URLStore;
let headscaleAPIKey = $APIKeyStore;
let serverSettingsForm: HTMLFormElement;
let apiStatus = 'untested';
@ -22,16 +22,16 @@
function SaveServerSettings(): void {
if (serverSettingsForm.reportValidity()) {
$headscaleURLStore = headscaleURL;
$headscaleAPIKeyStore = headscaleAPIKey;
$URLStore = headscaleURL;
$APIKeyStore = headscaleAPIKey;
}
}
function ClearServerSettings() {
headscaleURL = '';
headscaleAPIKey = '';
$headscaleURLStore = headscaleURL;
$headscaleAPIKeyStore = headscaleAPIKey;
$URLStore = headscaleURL;
$APIKeyStore = headscaleAPIKey;
}
</script>
@ -45,7 +45,7 @@
<p class="text-xs text-base-content text-italics mb-8">Generate an API key for your headscale instance and place it here.</p>
<!-- disable the SaveServerSettings button if nothing has changed from stored values, or the dependent inputs do not validate -->
<div class="tooltip z-10" data-tip="Note: API Key and URL currently save to localStorage (IE: Your Browser) Make sure you are using a trusted computer">
<button disabled={headscaleAPIKey === $headscaleAPIKeyStore && headscaleURL === $headscaleURLStore} on:click={() => SaveServerSettings()} class="btn btn-sm btn-accent capitalize" type="button">Save Server Settings</button>
<button disabled={headscaleAPIKey === $APIKeyStore && headscaleURL === $URLStore} on:click={() => SaveServerSettings()} class="btn btn-sm btn-accent capitalize" type="button">Save Server Settings</button>
</div>
<button on:click={() => ClearServerSettings()} class="btn btn-sm btn-primary capitalize" type="button">Clear Server Settings</button>
<button on:click={() => TestServerSettings()} class="btn btn-sm btn-secondary capitalize" type="button">Test Server Settings</button>

View file

@ -1,10 +1,10 @@
<script lang="ts">
import { headscaleThemeStore } from '$lib/common/stores.js'
import { themeStore } from '$lib/common/stores.js'
let themes = ['hsui', 'light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter'];
</script>
<h1 class="text-2xl bold text-primary mb-4">Theme Settings</h1>
<select bind:value={$headscaleThemeStore} class="select select-bordered w-full select-sm max-w-xs">
<select bind:value={$themeStore} class="select select-bordered w-full select-sm max-w-xs">
{#each themes as localTheme}
<option value={localTheme}>{localTheme}</option>
{/each}

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { filterUsers } from '$lib/common/searching.svelte';
import { userSearchStore } from '$lib/common/stores';
export let overrideSort = false;
// called when search changes
userSearchStore.subscribe((value) => {
if (value != '') {
overrideSort = true;
} else {
overrideSort = false;
}
filterUsers();
});
</script>
<!-- search icon -->
<div class="flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-3 mr-1 inline flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg> <input type="text" placeholder="search" bind:value={$userSearchStore} class="input input-bordered input-xs w-full max-w-xs" />
</div>

View file

@ -1,12 +1,12 @@
<script lang="ts">
import { getUsers } from '$lib/common/apiFunctions.svelte';
import { headscaleUserSortDirectionStore, headscaleUserSortStore } from '$lib/common/stores.js';
import { sortDirectionStore, userSortStore } from '$lib/common/stores.js';
function sortAction() {
if ($headscaleUserSortDirectionStore == 'ascending') {
$headscaleUserSortDirectionStore = 'descending';
if ($sortDirectionStore == 'ascending') {
$sortDirectionStore = 'descending';
} else {
$headscaleUserSortDirectionStore = 'ascending';
$sortDirectionStore = 'ascending';
}
getUsers();
}
@ -19,7 +19,7 @@
}}
class="mx-1"
>
{#if $headscaleUserSortDirectionStore == 'ascending'}
{#if $sortDirectionStore == 'ascending'}
<!-- ascending sort icon -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
@ -34,8 +34,8 @@
</button>
<span class="btn-group">
<button class:btn-active="{$headscaleUserSortStore == 'id'}" on:click="{() => {$headscaleUserSortStore = 'id'; getUsers()}}" class="btn btn-xs">ID</button>
<button class:btn-active="{$headscaleUserSortStore == 'name'}" on:click="{() => {$headscaleUserSortStore = 'name'; getUsers()}}" class="btn btn-xs capitalize">User Name</button>
<button class:btn-active="{$headscaleUserSortStore == 'createdAt'}" on:click="{() => {$headscaleUserSortStore = 'createdAt'; getUsers()}}" class="btn btn-xs capitalize">Creation Date</button>
<button class:btn-active="{$userSortStore == 'id'}" on:click="{() => {$userSortStore = 'id'; getUsers()}}" class="btn btn-xs">ID</button>
<button class:btn-active="{$userSortStore == 'name'}" on:click="{() => {$userSortStore = 'name'; getUsers()}}" class="btn btn-xs capitalize">User Name</button>
<button class:btn-active="{$userSortStore == 'createdAt'}" on:click="{() => {$userSortStore = 'createdAt'; getUsers()}}" class="btn btn-xs capitalize">Creation Date</button>
</span>
</span>

View file

@ -4,6 +4,7 @@
import RemoveUser from '$lib/users/UserCard/RemoveUser.svelte';
import PreauthKeys from '$lib/users/UserCard/PreAuthKeys.svelte';
import { User } from '$lib/common/classes';
import { userStore } from '$lib/common/stores';
// function for refreshing users from parent
export let user = new User();
@ -41,7 +42,9 @@
<th>User Creation Date</th>
<td>{new Date(user.createdAt)}</td>
</tr>
<PreauthKeys {user}></PreauthKeys>
{#key $userStore}
<PreauthKeys {user} />
{/key}
</tbody>
</table>
</div>

View file

@ -3,13 +3,13 @@
import Nav from '$lib/common/nav.svelte';
import Alert from '$lib/common/Alert.svelte';
import Stores from '$lib/common/Stores.svelte';
import { headscaleThemeStore } from '$lib/common/stores.js'
import { themeStore } from '$lib/common/stores.js'
// NOTE: the element that is using one of the theme attributes must be in the DOM on mount
</script>
<main data-theme={$headscaleThemeStore} class="flex flex-col">
<main data-theme={$themeStore} class="flex flex-col">
<!-- initialize localStorage -->
<Stores></Stores>
<div class="flex h-screen">

View file

@ -1,13 +1,14 @@
<!-- typescript -->
<script lang="ts">
import DeviceCard from '$lib/devices/DeviceCard.svelte';
import { base } from '$app/paths';
import { getDevices, getUsers } from '$lib/common/apiFunctions.svelte';
import { apiTestStore, deviceFilterStore, deviceStore } from '$lib/common/stores.js';
import CreateDevice from '$lib/devices/CreateDevice.svelte';
import DeviceCard from '$lib/devices/DeviceCard.svelte';
import SearchDevices from '$lib/devices/SearchDevices.svelte';
import SortDevices from '$lib/devices/SortDevices.svelte';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { apiTestStore, headscaleDeviceStore } from '$lib/common/stores.js';
import { getUsers, getDevices } from '$lib/common/apiFunctions.svelte';
import { base } from '$app/paths';
let newDeviceCardVisible = false;
@ -49,13 +50,16 @@
<button on:click={() => (newDeviceCardVisible = false)} class="btn btn-secondary btn-xs capitalize" type="button">- Hide New Device</button>
{/if}
</div></td
><td><SortDevices /></td></tr
><td><SortDevices /></td><td><SearchDevices /></td></tr
>
</table>
<CreateDevice bind:newDeviceCardVisible />
{#each $headscaleDeviceStore as device}
<DeviceCard {device} />
{#each $deviceStore as device}
{#if $deviceFilterStore.includes(device)}
<DeviceCard {device} />
{/if}
{/each}
{/if}
{#if $apiTestStore === 'failed'}

View file

@ -1,13 +1,14 @@
<!-- typescript -->
<script lang="ts">
import { base } from '$app/paths';
import { fade } from 'svelte/transition';
import { getUsers } from '$lib/common/apiFunctions.svelte';
import { headscaleUserStore, apiTestStore } from '$lib/common/stores';
import { onMount } from 'svelte';
import { apiTestStore, userFilterStore, userStore } from '$lib/common/stores';
import CreateUser from '$lib/users/CreateUser.svelte';
import SearchUsers from '$lib/users/SearchUsers.svelte';
import SortUsers from '$lib/users/SortUsers.svelte';
import UserCard from '$lib/users/UserCard.svelte';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
//
// Component Variables
@ -30,33 +31,37 @@
<!-- html -->
{#if componentLoaded}
<div in:fade class="px-4 pt-4">
<h1 class="text-2xl bold text-primary">User View</h1>
</div>
{#if $apiTestStore === 'succeeded'}
<!-- instantiate user based components -->
<table>
<tr
><td
><!-- device creation visibility button -->
<div class="p-4">
{#if newUserCardVisible == false}
<button on:click={() => (newUserCardVisible = true)} class="btn btn-primary btn-xs capitalize" type="button">+ New User</button>
{:else}
<button on:click={() => (newUserCardVisible = false)} class="btn btn-secondary btn-xs capitalize" type="button">- Hide New User</button>
{/if}
</div></td
><td><SortUsers /></td></tr
>
</table>
<CreateUser bind:newUserCardVisible />
{#each $headscaleUserStore as user}
<UserCard {user} />
{/each}
{/if}
{#if $apiTestStore === 'failed'}
<div in:fade class="max-w-lg mx-auto p-4 border-4 text-sm text-base-content shadow-lg text-center">
<p>API test did not succeed.<br />Headscale might be down or API settings may need to be set<br />change server settings in the <a href="{base}/settings.html" class="link link-primary">settings</a> page</p>
<div in:fade>
<div class="px-4 pt-4">
<h1 class="text-2xl bold text-primary">User View</h1>
</div>
{/if}
{#if $apiTestStore === 'succeeded'}
<!-- instantiate user based components -->
<table>
<tr
><td
><!-- device creation visibility button -->
<div class="p-4">
{#if newUserCardVisible == false}
<button on:click={() => (newUserCardVisible = true)} class="btn btn-primary btn-xs capitalize" type="button">+ New User</button>
{:else}
<button on:click={() => (newUserCardVisible = false)} class="btn btn-secondary btn-xs capitalize" type="button">- Hide New User</button>
{/if}
</div></td
><td><SortUsers /></td><td><SearchUsers /></td></tr
>
</table>
<CreateUser bind:newUserCardVisible />
{#each $userStore as user}
{#if $userFilterStore.includes(user)}
<UserCard {user} />
{/if}
{/each}
{/if}
{#if $apiTestStore === 'failed'}
<div in:fade class="max-w-lg mx-auto p-4 border-4 text-sm text-base-content shadow-lg text-center">
<p>API test did not succeed.<br />Headscale might be down or API settings may need to be set<br />change server settings in the <a href="{base}/settings.html" class="link link-primary">settings</a> page</p>
</div>
{/if}
</div>
{/if}