Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): update ui after login or stream operation #260

Merged
merged 2 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/liveion/components/login.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'preact/hooks';
import { TargetedEvent } from 'preact/compat';
import { Alert, Button, Modal } from 'react-daisyui';
import { Alert, Button, Loading, Modal } from 'react-daisyui';
import { WretchError } from 'wretch/resolver';

import * as api from '@/shared/api';
Expand Down
15 changes: 13 additions & 2 deletions web/liveman/components/nodes-table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Button, Checkbox, Dropdown, Link, Table } from 'react-daisyui';
import { useContext, useEffect } from 'preact/hooks';

import { Badge, Button, Checkbox, Dropdown, Link, Table } from 'react-daisyui';
import { ArrowPathIcon, EllipsisHorizontalIcon } from '@heroicons/react/24/outline';

import { TokenContext } from '@/shared/context';
import { useRefreshTimer } from '@/shared/hooks/use-refresh-timer';

import { type Node, getNodes } from '../api';
Expand All @@ -12,11 +15,19 @@ async function getNodesSorted() {

export function NodesTable() {
const nodes = useRefreshTimer([], getNodesSorted);
const tokenContext = useContext(TokenContext);

useEffect(() => {
if (tokenContext.token.length > 0) {
nodes.updateData();
}
}, [tokenContext.token]);

return (
<>
<div className="flex items-center gap-2 px-4 h-12">
<span className="font-bold text-lg mr-auto">Nodes (total: {nodes.data.length})</span>
<span className="font-bold text-lg">Nodes</span>
<Badge color="ghost" className="font-bold mr-auto">{nodes.data.length}</Badge>
<Button
size="sm"
color="ghost"
Expand Down
2 changes: 1 addition & 1 deletion web/liveman/liveman.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useRef, useState } from 'preact/hooks';
import { Button } from 'react-daisyui';

import type { Stream } from '@/shared/api';
import { type Stream } from '@/shared/api';
import { useNeedAuthorization } from '@/shared/hooks/use-need-authorization';
import { StreamsTable } from '@/shared/components/streams-table';
import { PageLayout } from '@/shared/components/page-layout';
Expand Down
8 changes: 4 additions & 4 deletions web/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ export const addUnauthorizedCallback = authMiddleware.addUnauthorizedCallback;
export const removeUnauthorizedCallback = authMiddleware.removeUnauthorizedCallback;

export function deleteSession(streamId: string, clientId: string) {
return w.url(`/session/${streamId}/${clientId}`).delete();
return w.url(`/session/${streamId}/${clientId}`).delete().res();
}

export function createStream(streamId: string) {
return w.url(`/api/streams/${streamId}`).post();
return w.url(`/api/streams/${streamId}`).post().res();
}

export function deleteStream(streamId: string) {
return w.url(`/api/streams/${streamId}`).delete();
return w.url(`/api/streams/${streamId}`).delete().res();
}

type SessionConnectionState =
Expand Down Expand Up @@ -66,5 +66,5 @@ export function getStreams() {
}

export function cascade(streamId: string, params: Cascade) {
return w.url(`/api/cascade/${streamId}`).post(params);
return w.url(`/api/cascade/${streamId}`).post(params).res();
}
2 changes: 1 addition & 1 deletion web/shared/authorization-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConfiguredMiddleware } from 'wretch';
import { type ConfiguredMiddleware } from 'wretch';

const Authorization = 'Authorization';

Expand Down
8 changes: 7 additions & 1 deletion web/shared/components/dialog-clients.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { formatTime } from '../utils';
interface Props {
id: string
sessions: Session[]
onClientKicked: () => void
}

export interface IClientsDialog {
Expand All @@ -25,6 +26,11 @@ export const ClientsDialog = forwardRef<IClientsDialog, Props>((props: Props, re
};
});

const handleKickClient = async (streamId: string, clientId: string) => {
await deleteSession(streamId, clientId);
props.onClientKicked();
};

return (
<Modal ref={refDialog} className="min-w-md max-w-[unset] w-[unset]">
<Modal.Header className="mb-2">
Expand All @@ -44,7 +50,7 @@ export const ClientsDialog = forwardRef<IClientsDialog, Props>((props: Props, re
<span>{c.id + (c.reforward ? '(reforward)' : '')}</span>
<span>{c.state}</span>
<span>{formatTime(c.createdAt)}</span>
<Button size="sm" color="error" onClick={() => deleteSession(props.id, c.id)}>Kick</Button>
<Button size="sm" color="error" onClick={() => handleKickClient(props.id, c.id)}>Kick</Button>
</Table.Row>
): <tr><td colspan={4} className="text-center">N/A</td></tr>}
</Table.Body>
Expand Down
6 changes: 4 additions & 2 deletions web/shared/components/dialog-new-stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createStream } from '../api';

interface Props {
onNewStreamId(id: string): void
onStreamCreated(): void
}

export interface INewStreamDialog {
Expand All @@ -29,9 +30,10 @@ export const NewStreamDialog = forwardRef<INewStreamDialog, Props>((props, ref)
setStreamId(e.currentTarget.value);
};

const onConfirmNewStreamId = (_e: Event) => {
const onConfirmNewStreamId = async (_e: Event) => {
props.onNewStreamId(streamId);
createStream(streamId);
await createStream(streamId);
props.onStreamCreated();
};

return (
Expand Down
6 changes: 3 additions & 3 deletions web/shared/components/page-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { PropsWithChildren } from 'preact/compat';
import { type PropsWithChildren } from 'preact/compat';
import { PageHeader } from './page-header';

import { ITokenContext, TokenContext } from '../context';
import { type ITokenContext, TokenContext } from '../context';

export type PageLayoutProps = ITokenContext & PropsWithChildren;
export type PageLayoutProps = PropsWithChildren<ITokenContext>;

export function PageLayout({ token, children }: PageLayoutProps) {
return (
Expand Down
35 changes: 24 additions & 11 deletions web/shared/components/streams-table.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { useState, useRef, useEffect, useContext } from 'preact/hooks';
import { ReactNode } from 'preact/compat';
import { type ReactNode } from 'preact/compat';

import { Button, Checkbox, Table } from 'react-daisyui';
import { Badge, Button, Checkbox, Table } from 'react-daisyui';
import { ArrowPathIcon, ArrowRightEndOnRectangleIcon, PlusIcon } from '@heroicons/react/24/outline';

import { type Stream, getStreams, deleteStream } from '../api';
import { formatTime, nextSeqId } from '../utils';
import { useRefreshTimer } from '../hooks/use-refresh-timer';
import { TokenContext } from '../context';

import { IClientsDialog, ClientsDialog } from './dialog-clients';
import { ICascadeDialog, CascadePullDialog, CascadePushDialog } from './dialog-cascade';
import { IPreviewDialog, PreviewDialog } from './dialog-preview';
import { IWebStreamDialog, WebStreamDialog } from './dialog-web-stream';
import { INewStreamDialog, NewStreamDialog } from './dialog-new-stream';
import { type IClientsDialog, ClientsDialog } from './dialog-clients';
import { type ICascadeDialog, CascadePullDialog, CascadePushDialog } from './dialog-cascade';
import { type IPreviewDialog, PreviewDialog } from './dialog-preview';
import { type IWebStreamDialog, WebStreamDialog } from './dialog-web-stream';
import { type INewStreamDialog, NewStreamDialog } from './dialog-new-stream';

async function getStreamsSorted() {
const streams = await getStreams();
Expand All @@ -40,6 +40,12 @@ export function StreamsTable(props: StreamTableProps) {
const refPreviewStreams = useRef<Map<string, IPreviewDialog>>(new Map());
const tokenContext = useContext(TokenContext);

useEffect(() => {
if (tokenContext.token.length > 0) {
streams.updateData();
}
}, [tokenContext.token]);

const handleViewClients = (id: string) => {
setSelectedStreamId(id);
refClients.current?.show();
Expand Down Expand Up @@ -114,10 +120,16 @@ export function StreamsTable(props: StreamTableProps) {
window.open(url);
};

const handleDestroyStream = async (id: string) => {
await deleteStream(id);
await streams.updateData();
};

return (
<>
<div className="flex items-center gap-2 px-4 h-12">
<span className="font-bold text-lg mr-auto">Streams (total: {streams.data.length})</span>
<span className="font-bold text-lg">Streams</span>
<Badge color="ghost" className="font-bold mr-auto">{streams.data.length}</Badge>
{props.showCascade ? (
<Button
size="sm"
Expand Down Expand Up @@ -172,7 +184,7 @@ export function StreamsTable(props: StreamTableProps) {
<Button size="sm" onClick={() => handleOpenPlayerPage(i.id)}>Player</Button>
<Button size="sm" onClick={() => handleOpenDebuggerPage(i.id)}>Debugger</Button>
{props.renderExtraActions?.(i)}
<Button size="sm" color="error" onClick={() => deleteStream(i.id)}>Destroy</Button>
<Button size="sm" color="error" onClick={() => handleDestroyStream(i.id)}>Destroy</Button>
</div>
</Table.Row>
) : <tr><td colspan={6} className="text-center">N/A</td></tr>}
Expand All @@ -187,14 +199,15 @@ export function StreamsTable(props: StreamTableProps) {
onClick={handleNewStream}
>New Stream</Button>
{webStreams.map(s =>
<Button size="sm" onClick={() => { handleOpenWebStream(s); }}>{s}</Button>
<Button size="sm" onClick={() => handleOpenWebStream(s)}>{s}</Button>
)}
</div>

<ClientsDialog
ref={refClients}
id={selectedStreamId}
sessions={streams.data.find(s => s.id == selectedStreamId)?.subscribe.sessions ?? []}
onClientKicked={streams.updateData}
/>

{props.showCascade ? (
Expand All @@ -218,7 +231,7 @@ export function StreamsTable(props: StreamTableProps) {
/>
)}

<NewStreamDialog ref={refNewStream} onNewStreamId={handleNewStreamId} />
<NewStreamDialog ref={refNewStream} onNewStreamId={handleNewStreamId} onStreamCreated={streams.updateData} />

{webStreams.map(s =>
<WebStreamDialog
Expand Down
Loading