Skip to content

Commit

Permalink
Added filtering by online/offline
Browse files Browse the repository at this point in the history
  • Loading branch information
Austin Archer committed Jan 16, 2025
1 parent c2e2278 commit 4ea3e2c
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 79 deletions.
3 changes: 1 addition & 2 deletions src/lib/States.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class StateLocal<T> {
}

save(value: T) {
debug(`Saving '${this.#key}' as '${this.serialize(value)}' in localStorage...`);
debug(`Saving '${this.#key}' in localStorage...`);
localStorage.setItem(this.#key, this.serialize(value));
}

Expand Down Expand Up @@ -112,7 +112,6 @@ export class HeadscaleAdmin {
// theme information
theme = new StateLocal<string>('theme', 'skeleton', (themeName) => {
if(themeName !== undefined) {
console.log("Setting the theme to", themeName)
document.body.setAttribute('data-theme', themeName);
}
})
Expand Down
87 changes: 81 additions & 6 deletions src/lib/common/funcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import IPAddr from 'ipaddr.js';
import { debug } from './debug';
import { get } from 'svelte/store';
// import { ApiKeyStore } from '$lib/Stores';
import type { Direction, Node } from './types';
import type { Direction, Node, OnlineStatus, User } from './types';
import { App } from '$lib/States.svelte';
// import { createApiKey } from './api';
// import { ApiKeyStore } from '$lib/Stores';
Expand Down Expand Up @@ -245,6 +245,38 @@ export function toOptions(values: string[]): {label: string, value:string}[] {
}))
}

export function getSortedUsers(users: User[], sortMethod: string, sortDirection: Direction): User[] {
if (sortMethod === 'id') {
users = users.sort((a: User, b: User) => {
const aid = parseInt(a.id);
const bid = parseInt(b.id);
if (aid < bid) {
return -1;
}
if (aid > bid) {
return 1;
}
return 0;
});
}
if (sortMethod === 'name') {
users = users.sort((a: User, b: User) => {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
}
if (sortDirection === 'down') {
return users.reverse();
}
return users;
}


export function getSortedNodes(nodes: Node[], sortMethod: string, sortDirection: Direction): Node[] {
if (sortMethod === 'id') {
nodes = nodes.sort((a: Node, b: Node) => {
Expand Down Expand Up @@ -275,14 +307,35 @@ export function getSortedNodes(nodes: Node[], sortMethod: string, sortDirection:
return nodes;
}

export function filterNode(node: Node, filterString: string): boolean {
const routes = App.routes.value.filter((r) => (r.node ?? r.machine).id == node.id)
if (routes.length == 0) {
export function filterUser(user: User, filterString: string, onlineStatus: OnlineStatus = "all"): boolean {
try {
if (
(onlineStatus === 'online' && !App.nodes.value.filter((n) => n.user.id === user.id).some((n) => n.online)) ||
(onlineStatus === 'offline' && App.nodes.value.filter((n) => n.user.id === user.id).some((n) => n.online))
) {
return false;
}

if (filterString === '') {
return true;
}

const r = RegExp(filterString);
return r.test(user.name) || r.test(user.name.toLowerCase());
} catch (error) {
return true;
}
}

export function filterNode(node: Node, filterString: string, onlineStatus: OnlineStatus = "all"): boolean {
if((onlineStatus === "online" && !node.online) || (onlineStatus === "offline" && node.online)){
return false
}

if (filterString === '') {
return true;
}

try {
const r = RegExp(filterString);
const getTag = (tag: string) => {
Expand All @@ -302,15 +355,37 @@ export function filterNode(node: Node, filterString: string): boolean {
}
}

export function getSortedFilteredUsers(
users: User[],
filterString:string,
sortMethod: string,
sortDirection: Direction,
onlineStatus: OnlineStatus,
){
return getSortedUsers(
users.filter((user)=> filterUser(user, filterString, onlineStatus)),
sortMethod,
sortDirection,
)
}

export function getSortedFilteredNodes(
nodes: Node[],
filterString:string,
sortMethod: string,
sortDirection: Direction,
onlineStatus: OnlineStatus,
ignoreRouteless: boolean = false,
){
return getSortedNodes(
nodes.filter((node)=> filterNode(node, filterString)),
let nodesSortedFiltered = getSortedNodes(
nodes.filter((node)=> filterNode(node, filterString, onlineStatus)),
sortMethod,
sortDirection,
)
if(ignoreRouteless === true){
return nodesSortedFiltered.filter((n) => {
return App.routes.value.some((r) => r.node.id === n.id)
})
}
return nodesSortedFiltered
}
17 changes: 2 additions & 15 deletions src/lib/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,7 @@ export class PreAuthKeys {
constructor(public preAuthKeys: PreAuthKey[]) { }
}

type RouteMachine = {
id: string;
createdAt: string;
deletedAt: string;
machine: Node;
node: never;
prefix: string;
advertised: boolean;
enabled: boolean;
isPrimary: boolean;
};

type RouteNode = {
export type Route = {
id: string;
createdAt: string;
deletedAt: string;
Expand All @@ -108,8 +96,6 @@ type RouteNode = {
isPrimary: boolean;
};

export type Route = RouteMachine | RouteNode;

export type ApiRoute = {
route: Route;
};
Expand Down Expand Up @@ -182,6 +168,7 @@ export type ApiKeyInfo = {
};

export type Direction = 'up' | 'down';
export type OnlineStatus = 'online' | 'offline' | 'all';

export type Deployment = {
// general
Expand Down
27 changes: 27 additions & 0 deletions src/lib/parts/FilterOnlineBtn.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import type { Direction, OnlineStatus } from '$lib/common/types';
import RawMdiBroadcast from '~icons/mdi/broadcast';
type SortBtnProps = {
value: OnlineStatus,
name: string,
status: OnlineStatus,
}
let {
value = $bindable(),
name,
status,
}: SortBtnProps = $props()
const color = $derived(
status === "all" ? '' :
(status ==="online" ? 'text-success-600 dark:text-success-400' : 'text-error-500 dark:text-error-400')
)
</script>

<button onclick={() => { if(value !== status) value = status }}>
<span class={"flex flex-row items-center " + (value === status ? 'opacity-50' : '')}>
<RawMdiBroadcast class={'pr-1 ' + color} />
{name}
</span>
</button>
13 changes: 11 additions & 2 deletions src/routes/nodes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,24 @@
import NodeTileCard from '$lib/cards/node/NodeTileCard.svelte';
import NodeCreate from '$lib/cards/node/NodeCreate.svelte';
import Page from '$lib/page/Page.svelte';
import type { Direction } from '$lib/common/types';
import type { OnlineStatus, Direction } from '$lib/common/types';
import SortBtn from '$lib/parts/SortBtn.svelte';
import { getSortedFilteredNodes } from '$lib/common/funcs';
import { App } from '$lib/States.svelte';
import FilterOnlineBtn from '$lib/parts/FilterOnlineBtn.svelte';
let showCreate = $state(false);
let sortMethod = $state('id');
let sortDirection = $state<Direction>('up');
let filterOnlineStatus = $state<OnlineStatus>('all');
let filterString = $state('');
const Outer = $derived(App.layoutNode.value === 'list' ? CardListPage : CardTilePage);
const Inner = $derived(App.layoutNode.value === 'list' ? NodeListCard : NodeTileCard);
const nodesSortedFiltered = $derived(
getSortedFilteredNodes(App.nodes.value, filterString, sortMethod, sortDirection)
getSortedFilteredNodes(App.nodes.value, filterString, sortMethod, sortDirection, filterOnlineStatus)
)
function toggle(method: string) {
Expand All @@ -48,6 +50,13 @@
<SortBtn bind:value={sortMethod} direction={sortDirection} name="Name" {toggle} />
<SortBtn bind:value={sortMethod} direction={sortDirection} name="Last Seen" {toggle} />
</div>
<div
class="btn-group ml-2 px-0 mx-0 py-0 my-0 rounded-md variant-ghost-secondary [&>*+*]:border-primary-500"
>
<FilterOnlineBtn bind:value={filterOnlineStatus} status="all" name="All" />
<FilterOnlineBtn bind:value={filterOnlineStatus} status="online" name="Online" />
<FilterOnlineBtn bind:value={filterOnlineStatus} status="offline" name="Offline" />
</div>

<Outer>
{#each nodesSortedFiltered as node}
Expand Down
13 changes: 11 additions & 2 deletions src/routes/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@
import RouteTileCard from '$lib/cards/route/RouteTileCard.svelte';
import Page from '$lib/page/Page.svelte';
import SortBtn from '$lib/parts/SortBtn.svelte';
import type { Direction } from '$lib/common/types';
import type { OnlineStatus, Direction } from '$lib/common/types';
import { getSortedFilteredNodes } from '$lib/common/funcs';
import { App } from '$lib/States.svelte';
import FilterOnlineBtn from '$lib/parts/FilterOnlineBtn.svelte';
// Sort & Filter
let sortMethod = $state('id');
let sortDirection = $state<Direction>('up');
let filterOnlineStatus = $state<OnlineStatus>('all');
let filterString = $state('');
const Outer = $derived(App.layoutRoute.value === 'list' ? CardListPage : CardTilePage);
const Inner = $derived(App.layoutRoute.value === 'list' ? RouteListCard : RouteTileCard);
const nodesSortedFiltered = $derived(
getSortedFilteredNodes(App.nodes.value, filterString, sortMethod, sortDirection)
getSortedFilteredNodes(App.nodes.value, filterString, sortMethod, sortDirection, filterOnlineStatus, true)
)
function toggle(method: string) {
Expand All @@ -45,6 +47,13 @@
<SortBtn bind:value={sortMethod} direction={sortDirection} name="ID" {toggle} />
<SortBtn bind:value={sortMethod} direction={sortDirection} name="Name" {toggle} />
</div>
<div
class="btn-group ml-2 px-0 mx-0 py-0 my-0 rounded-md variant-ghost-secondary [&>*+*]:border-primary-500"
>
<FilterOnlineBtn bind:value={filterOnlineStatus} status="all" name="All" />
<FilterOnlineBtn bind:value={filterOnlineStatus} status="online" name="Online" />
<FilterOnlineBtn bind:value={filterOnlineStatus} status="offline" name="Offline" />
</div>

<Outer>
{#each nodesSortedFiltered as node}
Expand Down
69 changes: 17 additions & 52 deletions src/routes/users/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,28 @@
import PageHeader from '$lib/page/PageHeader.svelte';
import Page from '$lib/page/Page.svelte';
import type { User, Direction } from '$lib/common/types';
import type { User, Direction, OnlineStatus } from '$lib/common/types';
import SortBtn from '$lib/parts/SortBtn.svelte';
import { App } from '$lib/States.svelte';
import { getSortedFilteredUsers } from '$lib/common/funcs';
import FilterOnlineBtn from '$lib/parts/FilterOnlineBtn.svelte';
let showCreate = $state(false);
const layout = $derived(App.layoutUser.value)
// Sort & Filter
let filterString = $state('');
const filteredUsers = $derived(getFilteredUsers(App.users.value, filterString)); // react on users or filterString change
let sortMethod = $state('id');
let sortDirection = $state<Direction>('up');
let filterOnlineStatus = $state<OnlineStatus>('all');
let filterString = $state('');
const usersSortedFiltered = $derived(
getSortedFilteredUsers(App.users.value, filterString, sortMethod, sortDirection, filterOnlineStatus)
)
const Outer = $derived(layout == 'list' ? CardListPage : CardTilePage);
const Inner = $derived(layout == 'list' ? UserListCard : UserTileCard);
function filter(user: User, filterString: string): boolean {
try {
if (filterString === '') {
return true;
}
// use filterString as regex if people want it
const r = RegExp(filterString);
return r.test(user.name) || r.test(user.name.toLowerCase());
} catch (error) {
return true;
}
}
function getSortedUsers(users: User[], sortMethod: string, sortDirection: Direction): User[] {
if (sortMethod === 'id') {
users = users.sort((a: User, b: User) => {
const aid = parseInt(a.id);
const bid = parseInt(b.id);
if (aid < bid) {
return -1;
}
if (aid > bid) {
return 1;
}
return 0;
});
}
if (sortMethod === 'name') {
users = users.sort((a: User, b: User) => {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
}
if (sortDirection === 'down') {
return users.reverse();
}
return users;
}
function toggle(method: string) {
if (method != sortMethod) {
sortMethod = method;
Expand All @@ -75,10 +37,6 @@
sortDirection = sortDirection === 'up' ? 'down' : 'up';
}
}
function getFilteredUsers(users: User[], filterString: string): User[] {
return users.filter((user) => filter(user, filterString));
}
</script>

<Page>
Expand All @@ -94,9 +52,16 @@
<SortBtn bind:value={sortMethod} direction={sortDirection} name="ID" {toggle} />
<SortBtn bind:value={sortMethod} direction={sortDirection} name="Name" {toggle} />
</div>
<div
class="btn-group ml-2 px-0 mx-0 py-0 my-0 rounded-md variant-ghost-secondary [&>*+*]:border-primary-500"
>
<FilterOnlineBtn bind:value={filterOnlineStatus} status="all" name="All" />
<FilterOnlineBtn bind:value={filterOnlineStatus} status="online" name="Online" />
<FilterOnlineBtn bind:value={filterOnlineStatus} status="offline" name="Offline" />
</div>

<Outer>
{#each getSortedUsers(filteredUsers, sortMethod, sortDirection) as user}
{#each usersSortedFiltered as user}
<Inner {user}></Inner>
{/each}
</Outer>
Expand Down

0 comments on commit 4ea3e2c

Please sign in to comment.