Skip to content

Commit

Permalink
Merge pull request #240 from binbat/feat/web-refactor-ui
Browse files Browse the repository at this point in the history
WebUI new design
  • Loading branch information
a-wing authored Nov 3, 2024
2 parents a13682e + 9f9c1c4 commit d4487cb
Show file tree
Hide file tree
Showing 36 changed files with 3,063 additions and 3,410 deletions.
6 changes: 4 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export default ts.config(
{
ignores: [
'assets/',
'docs/.vitepress/cache/',
'docs/.vitepress/dist/',
'web/shared/tools/debugger/**/*.js'
]
},
Expand All @@ -17,9 +19,9 @@ export default ts.config(
'@stylistic/js': stylisticJs
},
rules: {
'@stylistic/js/semi': ['error', 'always'],
'@stylistic/js/semi': ['warn', 'always'],
'@stylistic/js/quotes': ['error', 'single', { 'avoidEscape': true }],
'@stylistic/js/indent': ['error', 4, { 'SwitchCase': 1 }],
'@stylistic/js/indent': ['warn', 4, { 'SwitchCase': 1 }],
'@stylistic/js/jsx-quotes': ['error', 'prefer-double']
}
},
Expand Down
5,196 changes: 2,364 additions & 2,832 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 26 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"preview": "npm run preview:liveion",
"preview:liveion": "vite preview -c web/liveion/vite.config.ts",
"preview:liveman": "vite preview -c web/liveman/vite.config.ts",
"build": "npm run build:liveion && npm run build:liveman",
"lint": "eslint && tsc --noEmit",
"build": "npm run lint && npm run build:liveion && npm run build:liveman",
"build:liveion": "vite build -c web/liveion/vite.config.ts",
"build:liveman": "vite build -c web/liveman/vite.config.ts",
"e2e:cluster": "vitest",
Expand All @@ -20,21 +21,32 @@
},
"dependencies": {
"@binbat/whip-whep": "^1.1.1-sdp-trickle-throw",
"@nuintun/qrcode": "^4.1.5",
"preact": "^10.23.2",
"@nuintun/qrcode": "^4.1.6",
"@heroicons/react": "^2.1.5",
"preact": "^10.24.3",
"react-daisyui": "^5.0.5",
"typescript-event-target": "^1.1.1",
"wretch": "^2.9.1"
"wretch": "^2.11.0"
},
"devDependencies": {
"@eslint/js": "^9.10.0",
"@preact/preset-vite": "^2.9.0",
"@stylistic/eslint-plugin-js": "^2.7.2",
"eslint": "^9.10.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"unocss": "^0.62.3",
"vite": "^5.4.3",
"vitepress": "^1.3.4",
"vitest": "^2.0.5"
"@eslint/js": "^9.13.0",
"@preact/preset-vite": "^2.9.1",
"@stylistic/eslint-plugin-js": "^2.10.1",
"@types/node": "^22.8.6",
"daisyui": "^4.12.14",
"eslint": "^9.13.0",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"typescript-eslint": "^8.12.2",
"vite": "^5.4.10",
"vitepress": "^1.4.3",
"vitest": "^2.1.4"
},
"postcss": {
"plugins": {
"tailwindcss": {
"config": "./web/tailwind.config.ts"
}
}
}
}
5 changes: 5 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"paths": {
"@/*": ["./web/*"],
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
},
Expand All @@ -26,5 +27,9 @@
"noFallthroughCasesInSwitch": true
},
"include": ["web"],
"exclude": [
"./web/**/vite.config.ts",
"./web/tailwind.config.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
}
5 changes: 2 additions & 3 deletions tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
"strict": true
},
"include": [
"./web/uno.config.ts",
"./web/vite.config.ts",
"./web/*/vite.config.ts"
"./web/**/vite.config.ts",
"./web/tailwind.config.ts"
]
}
59 changes: 47 additions & 12 deletions web/liveion/components/login.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
import { useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'preact/hooks';
import { TargetedEvent } from 'preact/compat';
import { Alert, Button, Modal } from 'react-daisyui';
import { WretchError } from 'wretch/resolver';

import * as api from '../../shared/api';
import { alertError } from '../../shared/utils';
import * as api from '@/shared/api';

export interface LoginProps {
show: boolean;
onSuccess?: (token: string) => void;
}

export function Login({ onSuccess }: LoginProps) {
export function Login({ show, onSuccess }: LoginProps) {
const refDialog = useRef<HTMLDialogElement>(null);
const [token, setToken] = useState('');
const [loading, setLoading] = useState(false);
const [errMsg, setErrMsg] = useState<string | null>(null);

useEffect(() => {
if (show) {
refDialog.current?.showModal();
} else {
refDialog.current?.close();
}
}, [show]);

const handleDialogClose = () => {
if (show) {
refDialog.current?.showModal();
}
};

const onTokenSubmit = async (e: TargetedEvent) => {
setLoading(true);
e.preventDefault();
const tk = token.indexOf(' ') < 0 ? `Bearer ${token}` : token;
api.setAuthToken(tk);
Expand All @@ -20,19 +40,34 @@ export function Login({ onSuccess }: LoginProps) {
onSuccess?.(token);
} catch (e) {
api.setAuthToken('');
alertError(e);
if (e instanceof WretchError) {
setErrMsg(e.json?.error ?? e.text ?? `Status: ${e.status}`);
} else if (e instanceof Error) {
setErrMsg(e.message);
} else {
setErrMsg(String(e));
}
}
setLoading(false);
};

return (
<fieldset>
<legend>Authorization Required</legend>
<Modal ref={refDialog} onClose={handleDialogClose}>
<Modal.Header>
<h3 className="font-bold">Authorization Required</h3>
</Modal.Header>
{typeof errMsg === 'string' ? <Alert status="error" >{errMsg}</Alert> : null}
<form onSubmit={onTokenSubmit}>
<span class="inline-block min-w-24 font-bold">Token</span>
<input value={token} onInput={e => setToken(e.currentTarget?.value)} />
<br />
<input type="submit" value="Login" />
<label class="input input-bordered flex items-center gap-2 my-4">
<span>Token</span>
<input class="grow" value={token} onInput={e => setToken(e.currentTarget?.value)} />
</label>
<Button type="submit" color="primary" className="w-full text-base" disabled={loading}>
{/* @ts-expect-error -- size */}
{loading ? <Loading size="sm" /> : null}
<span>Login</span>
</Button>
</form>
</fieldset>
</Modal>
);
}
24 changes: 12 additions & 12 deletions web/liveion/liveion.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useState } from 'preact/hooks';

import * as api from '../shared/api';
import { Live777Logo } from '../shared/components/live777-logo';
import { StreamsTable } from '../shared/components/streams-table';
import { TokenContext } from '../shared/context';
import { useNeedAuthorization } from '../shared/hooks/use-need-authorization';
import * as api from '@/shared/api';
import { useNeedAuthorization } from '@/shared/hooks/use-need-authorization';
import { PageLayout } from '@/shared/components/page-layout';
import { StreamsTable } from '@/shared/components/streams-table';

import { Login } from './components/login';

Expand All @@ -18,13 +17,14 @@ export function Liveion() {
};

return (
<TokenContext.Provider value={{ token }}>
<Live777Logo />
{needsAuthorizaiton ? (
<Login onSuccess={onLoginSuccess} />
) : (
<>
<PageLayout token={token}>
<StreamsTable showCascade />
)}
</TokenContext.Provider>
</PageLayout>
<Login
show={needsAuthorizaiton}
onSuccess={onLoginSuccess}
/>
</>
);
}
4 changes: 2 additions & 2 deletions web/liveion/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from 'preact';

import 'virtual:uno.css';
import '../shared/index.css';
import '../shared/tailwind.css';

import { Liveion } from './liveion';

render(<Liveion />, document.getElementById('app')!);
2 changes: 1 addition & 1 deletion web/liveman/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface Node {
alias: string;
url: string;
duration: string;
strategy: Record<string, string | number | boolean>,
strategy?: Record<string, string | number | boolean>,
status: 'running' | 'stopped';
}

Expand Down
79 changes: 40 additions & 39 deletions web/liveman/components/dialog-token.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useRef, useImperativeHandle, useEffect } from 'preact/hooks';
import { forwardRef } from 'preact/compat';
import { Button, Checkbox, Input, Modal } from 'react-daisyui';

import { createStreamToken } from '../api';

Expand All @@ -9,12 +10,6 @@ enum StreamTokenPermission {
Admin = 'admin'
}

const AllPermissions = [
StreamTokenPermission.Sub,
StreamTokenPermission.Pub,
StreamTokenPermission.Admin
];

export interface IStreamTokenDialog {
show(id: string): void
}
Expand Down Expand Up @@ -62,43 +57,49 @@ export const StreamTokenDialog = forwardRef<IStreamTokenDialog>((_, ref) => {
};

return (
<dialog class="min-w-96" ref={refDialog} onClose={onClose}>
<h3>Create Token for stream {streamId}</h3>
<p>
<label>
<span>Duration: </span>
<br />
<input type="number" value={duration} onInput={e => setDuration(e.currentTarget.valueAsNumber)} />
<span>(seconds)</span>
</label>
</p>
<p>
<span>Permissions:</span>
<br />
{AllPermissions.map(p => (
<label>
<Modal ref={refDialog} className="max-w-md" onClose={onClose}>
<Modal.Header className="mb-2">
<h3 className="font-bold">Create Token for stream {streamId}</h3>
</Modal.Header>
<Modal.Body>
<label className="form-control">
<label className="label px-0">Duration:</label>
<label class="input input-bordered flex items-center gap-2">
<input
type="checkbox" name={p} checked={permissions[p]}
onChange={e => setPermissions({ ...permissions, [p]: e.currentTarget.checked })}
className="grow"
type="number"
value={duration} onInput={e => setDuration(e.currentTarget.valueAsNumber)}
/>
<span>{p}</span>
<br />
<span>Seconds</span>
</label>
))}
</p>
<form method="dialog">
<button>Cancel</button>
<button onClick={onConfirm}>Confirm</button>
</form>
</label>
<label className="form-control mt-4">
<label className="label px-0 pb-0">Permissions:</label>
{Object.values(StreamTokenPermission).map(p =>
<label class="label justify-start gap-2">
<Checkbox
size="xs"
name={p}
checked={permissions[p]}
onChange={e => setPermissions({ ...permissions, [p]: e.currentTarget.checked })}
/>
<span>{p}</span>
</label>
)}
</label>
</Modal.Body>
<Modal.Actions>
<form method="dialog" className="flex gap-2">
<Button onClick={onConfirm}>Confirm</Button>
<Button>Cancel</Button>
</form>
</Modal.Actions>
{token ? (
<>
<hr />
<label class="flex flex-col">
<span>Token:</span>
<input type="text" class="grow" value={token} ref={refTokenResult} />
</label>
</>
<label className="form-control">
<label className="label px-0">Token:</label>
<Input borderOffset type="text" value={token} ref={refTokenResult} />
</label>
) : null}
</dialog>
</Modal>
);
});
Loading

0 comments on commit d4487cb

Please sign in to comment.