Add streamer pause/resume feature and UI updates

Introduces a pause/resume button for streamers in the UI and backend, including API endpoint and status handling. Refactors streamer status display, updates related TypeScript interfaces, and improves modal forms. Also updates @douyinfe/semi-ui and related dependencies to v2.86.0, and bumps workspace version to 1.1.10. Minor logging and trait changes in Rust backend for improved clarity and error handling.
This commit is contained in:
ForgQi committed 2025-09-29 03:18:21 +08:00
1 parent d4b6a5fb22
commit 92b5a30df5
21 files changed
+270 -148

No files matched your search

+41 -25
View File
@@ -1,21 +1,21 @@
'use client'
import {
Layout,
Nav,
Button,
Tag,
Typography,
Popconfirm,
Notification,
Card,
Layout,
Nav,
Button,
Tag,
Typography,
Popconfirm,
Notification,
Card, Dropdown,
} from '@douyinfe/semi-ui'
import {
IconHelpCircle,
IconPlusCircle,
IconVideoListStroked,
IconEdit2Stroked,
IconDeleteStroked,
IconWrench,
IconHelpCircle,
IconPlusCircle,
IconVideoListStroked,
IconEdit2Stroked,
IconDeleteStroked,
IconWrench, IconTreeTriangleDown, IconPause, IconPlay,
} from '@douyinfe/semi-icons'
import { List, ButtonGroup } from '@douyinfe/semi-ui'
import React, { useState } from 'react'
@@ -24,6 +24,7 @@ import TemplateModal from '../../ui/TemplateModal'
import OverrideModal from '../../ui/OverrideModal'
import { LiveStreamerEntity, put, requestDelete, sendRequest } from '../../lib/api-streamer'
import useSWRMutation from 'swr/mutation'
import {PauseButton} from "@/app/ui/StreamerActions/PauseButton";
export default function Home() {
const { Header, Content } = Layout
@@ -54,25 +55,28 @@ export default function Home() {
return values
}
const data: LiveStreamerEntity[] | undefined = streamers?.map(live => {
let status
let statusTag
switch (live.status) {
case 'Working':
status = <Tag color="red">直播中</Tag>
statusTag = <Tag color="red">直播中</Tag>
break
case 'Idle':
status = <Tag color="green">空闲</Tag>
statusTag = <Tag color="green">空闲</Tag>
break
case 'Pending':
status = <Tag color="grey">未知</Tag>
statusTag = <Tag color="grey">等待下载</Tag>
break
case 'Inspecting':
status = <Tag color="indigo">检测/上传中</Tag>
statusTag = <Tag color="indigo">检测/上传中</Tag>
break
case 'OutOfSchedule':
status = <Tag color="green">非录播时间</Tag>
statusTag = <Tag color="green">非录播时间</Tag>
break
case 'Pause':
statusTag = <Tag color="pink">暂停中</Tag>
break
}
return { ...handleEntityPostprocessor(live), status }
return { ...handleEntityPostprocessor(live), statusTag }
})
const handleOk = async (values: any) => {
@@ -96,6 +100,7 @@ export default function Home() {
const handleUpdate = async (values: any) => {
console.log(values);
delete values.status
delete values.statusTag
if (values?.postprocessor) {
values.postprocessor = values.postprocessor.map(
({ cmd, value }: { cmd: string; value: string }) => (cmd === 'rm' ? 'rm' : { [cmd]: value })
@@ -112,6 +117,7 @@ export default function Home() {
throw e
}
}
return (
<>
<Header
@@ -214,7 +220,7 @@ export default function Home() {
}
}
>
<div style={{ position: 'absolute', right: 20, top: 9 }}>{item.status}</div>
<div style={{ position: 'absolute', right: 20, top: 9 }}>{item.statusTag}</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<h3
style={{
@@ -248,9 +254,7 @@ export default function Home() {
<Button theme="borderless" icon={<IconEdit2Stroked />}></Button>
</TemplateModal>
<span className="semi-button-group-line semi-button-group-line-borderless semi-button-group-line-primary"></span>
<OverrideModal onOk={handleUpdate} entity={item}>
<Button theme="borderless" icon={<IconWrench />}></Button>
</OverrideModal>
<PauseButton streamer={item}/>
<span className="semi-button-group-line semi-button-group-line-borderless semi-button-group-line-primary"></span>
<Popconfirm
title="确定是否要删除?"
@@ -260,6 +264,18 @@ export default function Home() {
>
<Button theme="borderless" icon={<IconDeleteStroked />}></Button>
</Popconfirm>
<span className="semi-button-group-line semi-button-group-line-borderless semi-button-group-line-primary"></span>
<Dropdown render={
<Dropdown.Menu>
<Dropdown.Item>
<OverrideModal onOk={handleUpdate} entity={item}>
<Button theme="borderless" icon={<IconWrench />}></Button>
</OverrideModal>
</Dropdown.Item>
</Dropdown.Menu>
} trigger="click" position="bottomRight">
<Button theme="borderless" type="primary" icon={<IconTreeTriangleDown />}></Button>
</Dropdown>
</ButtonGroup>
</div>
</Card>
+2 -1
View File
@@ -100,7 +100,8 @@ export interface LiveStreamerEntity {
split_time?: number;
split_size?: number;
upload_id?: number;
status?: string | React.ReactNode;
status?: string;
statusTag?: React.ReactNode;
format?: string;
time_range?: string | Date[];
excluded_keywords?: string[];
-6
View File
@@ -222,12 +222,6 @@ const OverrideModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
stopValidateWithError={true}
/>
<Form.Input
field="filename_prefix"
label="文件名模板(filename_prefix)"
placeholder="{streamer}%Y-%m-%dT%H_%M_%S"
/>
<Form.InputNumber
field="filtering_threshold"
label="碎片过滤(filtering_threshold)"
+52
View File
@@ -0,0 +1,52 @@
import React, { useState } from 'react';
import { useSWRConfig } from 'swr';
import useSWRMutation from 'swr/mutation';
import {Button} from "@douyinfe/semi-ui";
import {IconPause, IconPlay} from "@douyinfe/semi-icons";
import {API_BASE, LiveStreamerEntity} from "@/app/lib/api-streamer";
interface PauseButtonProps {
streamer: LiveStreamerEntity;
onSuccess?: () => void;
onError?: (error: Error) => void;
}
// 暂停主播
export const pauseStreamer = async (url: string, ) => {
const response = await fetch(API_BASE + url,
{
method: 'PUT',
// headers: {'Content-Type': 'application/json'},
}
);
return response;
};
export const PauseButton: React.FC<PauseButtonProps> = ({
streamer,
onSuccess,
onError
}) => {
const { mutate } = useSWRConfig();
const { trigger: pauseTrigger } = useSWRMutation(
`/v1/streamers/${streamer.id}/pause`,
pauseStreamer
);
const handlePause = async () => {
try {
await pauseTrigger();
// 重新加载列表数据
await mutate('/v1/streamers');
onSuccess?.();
} catch (error) {
console.error('暂停失败:', error);
onError?.(error as Error);
}
};
console.log("????????????", streamer.status)
return (
<Button onClick={handlePause} icon={streamer.status === 'Pause'? <IconPlay />: <IconPause />} theme="borderless" aria-label="暂停" />
);
};
+6
View File
@@ -155,6 +155,12 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
rules={[{ required: true, message }]}
/>
<Form.Input
field="filename_prefix"
label="文件名模板(可选)"
placeholder="{streamer}%Y-%m-%dT%H_%M_%S"
/>
<Form.Select
showClear
field="upload_streamers_id"