Skip to content

Commit 4fb20dc

Browse files
committed
feat: liveman nodes list
1 parent c18582c commit 4fb20dc

10 files changed

Lines changed: 142 additions & 38 deletions

File tree

web/liveion/vite.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ const configDir = import.meta.dirname
1010
// https://vitejs.dev/config/
1111
export default mergeConfig(CommonConfig, defineConfig({
1212
root: configDir,
13+
server: {
14+
proxy: {
15+
'^.*/admin/.*': 'http://localhost:7777',
16+
'^/api/.*': 'http://localhost:7777',
17+
'^/session/.*': 'http://localhost:7777',
18+
'^/whip/.*': 'http://localhost:7777',
19+
'^/whep/.*': 'http://localhost:7777',
20+
},
21+
},
1322
build: {
1423
outDir: resolve(ProjectRoot, 'assets/liveion'),
1524
rollupOptions: {

web/liveman/api.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Stream } from '../shared/api';
2+
3+
export interface Node {
4+
alias: string;
5+
url: string;
6+
pub_max: number;
7+
sub_max: number;
8+
status: 'running' | 'stopped';
9+
}
10+
11+
export async function getNodes(): Promise<Node[]> {
12+
return (await fetch('/api/nodes/')).json()
13+
}
14+
15+
16+
export interface StreamDetail {
17+
[key: string]: Stream;
18+
}
19+
20+
export async function getStreamDetail(streamId: string): Promise<StreamDetail> {
21+
return (await fetch(`/api/streams/${streamId}`)).json()
22+
}

web/liveman/liveman.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Live777Logo } from '../shared/components/live777-logo'
2+
import { NodesTable } from './nodes-table'
23
import { StreamsTable } from '../shared/components/streams-table'
34

45
export function Liveman() {
56
return (
67
<>
78
<Live777Logo />
9+
<NodesTable />
810
<StreamsTable />
911
</>
1012
)

web/liveman/nodes-table.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useRefreshTimer } from '../shared/hooks/use-refresh-timer';
2+
import { StyledCheckbox } from '../shared/components/styled-checkbox';
3+
4+
import { getNodes } from './api';
5+
6+
export function NodesTable() {
7+
const[nodes, isRefreshingNodes, toggleRefreshNodes] = useRefreshTimer([], getNodes)
8+
9+
return (
10+
<>
11+
<fieldset>
12+
<legend class="inline-flex items-center">
13+
<span>Nodes (total: {nodes.length})</span>
14+
<StyledCheckbox label="Auto Refresh" checked={isRefreshingNodes} onClick={toggleRefreshNodes}></StyledCheckbox>
15+
</legend>
16+
<table>
17+
<thead>
18+
<tr>
19+
<th class="min-w-24">Alias</th>
20+
<th class="min-w-24">Status</th>
21+
<th>Max Publish Cnt.</th>
22+
<th>Max subscribe Cnt.</th>
23+
<th class="min-w-72">API URL</th>
24+
</tr>
25+
</thead>
26+
<tbody>
27+
{nodes.map(n => (
28+
<tr>
29+
<td class="text-center">{n.alias}</td>
30+
<td class="text-center">{n.status}</td>
31+
<td class="text-center">{n.pub_max}</td>
32+
<td class="text-center">{n.sub_max}</td>
33+
<td class="text-center"><a href={n.url} target="blank">{n.url}</a></td>
34+
</tr>
35+
))}
36+
</tbody>
37+
</table>
38+
</fieldset>
39+
</>
40+
)
41+
}

web/liveman/vite.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ const configDir = import.meta.dirname
1010
// https://vitejs.dev/config/
1111
export default mergeConfig(CommonConfig, defineConfig({
1212
root: configDir,
13+
server: {
14+
proxy: {
15+
'^/whip/.*': 'http://localhost:8888',
16+
'^/whep/.*': 'http://localhost:8888',
17+
'^/session/.*': 'http://localhost:8888',
18+
'^/api/.*': 'http://localhost:8888',
19+
},
20+
},
1321
build: {
1422
outDir: resolve(ProjectRoot, 'assets/liveman'),
1523
rollupOptions: {

web/shared/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface Session {
3535
};
3636
}
3737

38-
export interface Cascade{
38+
export interface Cascade {
3939
token?: string;
4040
src?: string;
4141
dst?: string;

web/shared/components/streams-table.tsx

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { useState, useRef, useEffect } from 'preact/hooks'
22

3-
import { Stream, allStream, delStream } from '../api'
3+
import { allStream, delStream } from '../api'
44
import { formatTime } from '../utils'
5-
5+
import { useRefreshTimer } from '../hooks/use-refresh-timer'
6+
import { StyledCheckbox } from './styled-checkbox'
67
import { IClientsDialog, ClientsDialog } from './dialog-clients'
78
import { ICascadeDialog, CascadePullDialog, CascadePushDialog } from './dialog-cascade'
89
import { IPreviewDialog, PreviewDialog } from './dialog-preview'
910
import { IWebStreamDialog, WebStreamDialog } from './dialog-web-stream'
1011
import { INewStreamDialog, NewStreamDialog } from './dialog-new-stream'
1112

1213
export function StreamsTable() {
13-
const [streams, setStreams] = useState<Stream[]>([])
14+
const [streams, isRefreshingStreams, toggleRefreshStreams] = useRefreshTimer([], allStream)
1415
const [selectedStreamId, setSelectedStreamId] = useState('')
15-
const [refreshTimer, setRefershTimer] = useState(-1)
1616
const refCascadePull = useRef<ICascadeDialog>(null)
1717
const refCascadePush = useRef<ICascadeDialog>(null)
1818
const refClients = useRef<IClientsDialog>(null)
@@ -24,23 +24,6 @@ export function StreamsTable() {
2424
const [previewStreamId, setPreviewStreamId] = useState('')
2525
const refPreviewStreams = useRef<Map<string, IPreviewDialog>>(new Map())
2626

27-
const updateAllStreams = async () => {
28-
setStreams(await allStream())
29-
}
30-
31-
// fetch all streams on component mount
32-
useEffect(() => { updateAllStreams() }, [])
33-
34-
const toggleTimer = () => {
35-
if (refreshTimer > 0) {
36-
clearInterval(refreshTimer)
37-
setRefershTimer(-1)
38-
} else {
39-
updateAllStreams()
40-
setRefershTimer(window.setInterval(updateAllStreams, 3000))
41-
}
42-
}
43-
4427
const handleViewClients = (id: string) => {
4528
setSelectedStreamId(id)
4629
refClients.current?.show()
@@ -141,13 +124,7 @@ export function StreamsTable() {
141124
<fieldset>
142125
<legend class="inline-flex items-center">
143126
<span>Streams (total: {streams.length})</span>
144-
<label class="ml-10 inline-flex items-center cursor-pointer">
145-
<input type="checkbox" class="sr-only peer" checked={refreshTimer > 0} onClick={toggleTimer} />
146-
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
147-
<span class="ml-2">Auto Refresh</span>
148-
</label>
149-
</legend>
150-
<legend>
127+
<StyledCheckbox label="Auto Refresh" checked={isRefreshingStreams} onClick={toggleRefreshStreams}></StyledCheckbox>
151128
</legend>
152129
<table>
153130
<thead>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TargetedEvent } from 'preact/compat';
2+
3+
export interface StyledCheckboxProps {
4+
label: string;
5+
checked: boolean;
6+
onClick(ev: TargetedEvent<HTMLInputElement>): void
7+
}
8+
9+
export function StyledCheckbox({ label, checked, onClick }: StyledCheckboxProps) {
10+
return (
11+
<label class="ml-10 inline-flex items-center cursor-pointer">
12+
<input type="checkbox" class="sr-only peer" checked={checked} onClick={onClick} />
13+
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
14+
<span class="ml-2">{label}</span>
15+
</label>
16+
)
17+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect, useState } from 'preact/hooks';
2+
3+
export function useRefreshTimer<T>(initial: T, fetchContent: () => Promise<T>, timeout = 3000, immediate = true): [T, boolean, () => void] {
4+
const [content, setContent] = useState<T>(initial)
5+
const [isImmediate, _setIsImmediate] = useState(immediate)
6+
const [refreshTimer, setRefreshTimer] = useState(-1)
7+
const isRefreshing = refreshTimer > 0
8+
const updateContent = async () => {
9+
setContent(await fetchContent())
10+
}
11+
useEffect(() => {
12+
if (isImmediate) {
13+
updateContent()
14+
}
15+
return () => {
16+
if (isRefreshing) {
17+
window.clearInterval(refreshTimer)
18+
}
19+
}
20+
}, [])
21+
useEffect(() => {
22+
if (isRefreshing) {
23+
clearInterval(refreshTimer)
24+
setRefreshTimer(window.setInterval(updateContent, timeout))
25+
}
26+
}, [timeout])
27+
const toggleTimer = () => {
28+
if (isRefreshing) {
29+
clearInterval(refreshTimer)
30+
setRefreshTimer(-1)
31+
} else {
32+
updateContent()
33+
setRefreshTimer(window.setInterval(updateContent, timeout))
34+
}
35+
}
36+
return [content, isRefreshing, toggleTimer]
37+
}

web/vite.config.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,6 @@ export const ProjectRoot = resolve(import.meta.dirname, '..')
1212
*/
1313
export default defineConfig({
1414
publicDir: resolve(ProjectRoot, 'public'),
15-
server: {
16-
proxy: {
17-
'^.*/admin/.*': 'http://localhost:7777',
18-
'^/api/.*': 'http://localhost:7777',
19-
'^/session/.*': 'http://localhost:7777',
20-
'^/whip/.*': 'http://localhost:7777',
21-
'^/whep/.*': 'http://localhost:7777',
22-
},
23-
},
2415
build: {
2516
emptyOutDir: true,
2617
},

0 commit comments

Comments
 (0)