mirror of
https://github.com/gurucomputing/headscale-ui.git
synced 2026-07-17 16:39:45 +00:00
Merge pull request #15 from gurucomputing/feat-user-searching
Feat user searching
This commit is contained in:
commit
00ece42a88
19 changed files with 234 additions and 111 deletions
29
package-lock.json
generated
29
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
38
src/lib/common/searching.svelte
Normal file
38
src/lib/common/searching.svelte
Normal 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>
|
||||
|
|
@ -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');
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
23
src/lib/devices/SearchDevices.svelte
Normal file
23
src/lib/devices/SearchDevices.svelte
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
23
src/lib/users/SearchUsers.svelte
Normal file
23
src/lib/users/SearchUsers.svelte
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue