refactor: 合并为单 crate,前端改为内嵌 React 控制台
合并 stash-cli / stash-core 为根目录单 crate(lib + bin),新增 React + Vite + TS 控制台替换 Askama 模板。 build.rs 用 walkdir 把 stash-web/dist 同步到 $OUT_DIR/web,由 include_dir! 编译期内嵌,产出单文件二进制。 路由改为 / 与 /assets/** 走内嵌前端,其余交给 ServeDir,缺失路径返回内置 404。同步更新依赖、.gitignore 与 README。
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
export type Project = {
|
||||
name: string
|
||||
route_path: string
|
||||
}
|
||||
|
||||
type ProjectsPayload = {
|
||||
projects?: Project[]
|
||||
}
|
||||
|
||||
const PROJECTS_ENDPOINT = '/api/projects'
|
||||
const EVENTS_ENDPOINT = '/api/projects/events'
|
||||
|
||||
/** 读取服务端扫描出的项目列表。 */
|
||||
export async function fetchProjects(signal?: AbortSignal): Promise<Project[]> {
|
||||
const response = await fetch(PROJECTS_ENDPOINT, {
|
||||
signal,
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`请求 ${PROJECTS_ENDPOINT} 失败:HTTP ${response.status}`)
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as ProjectsPayload
|
||||
return Array.isArray(payload.projects) ? payload.projects : []
|
||||
}
|
||||
|
||||
export type ProjectEventsHandlers = {
|
||||
onProjects: (projects: Project[]) => void
|
||||
/** SSE 连接建立 / 断开时回调,浏览器的 EventSource 会自行重连。 */
|
||||
onConnectionChange?: (connected: boolean) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅服务端的项目快照推送。
|
||||
* 浏览器不支持 SSE 时静默降级为空清理函数,页面仍可用手动刷新。
|
||||
*/
|
||||
export function subscribeProjects(handlers: ProjectEventsHandlers): () => void {
|
||||
if (typeof EventSource === 'undefined') {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
const source = new EventSource(EVENTS_ENDPOINT)
|
||||
|
||||
const handleProjects = (event: Event) => {
|
||||
try {
|
||||
const payload = JSON.parse((event as MessageEvent<string>).data) as ProjectsPayload
|
||||
if (Array.isArray(payload.projects)) {
|
||||
handlers.onProjects(payload.projects)
|
||||
}
|
||||
} catch {
|
||||
// 单条事件格式异常时保留旧数据,等待下一次推送
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpen = () => handlers.onConnectionChange?.(true)
|
||||
const handleError = () => handlers.onConnectionChange?.(false)
|
||||
|
||||
source.addEventListener('projects', handleProjects)
|
||||
source.addEventListener('open', handleOpen)
|
||||
source.addEventListener('error', handleError)
|
||||
|
||||
return () => {
|
||||
source.removeEventListener('projects', handleProjects)
|
||||
source.removeEventListener('open', handleOpen)
|
||||
source.removeEventListener('error', handleError)
|
||||
source.close()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user