feat(core): 添加模型下载路径配置功能
- 新增 get_default_save_dir Tauri 命令用于获取默认保存目录 - 修改 download_model 命令支持自定义保存路径参数 - 在模型页面集成本地存储的保存路径配置 - 添加 MSW 到 pnpm 工作区允许构建列表 feat(ui): 添加模型设置页面 - 创建新的设置页面用于配置模型下载路径 - 实现路径选择、保存和重置功能 - 集成到侧边栏导航菜单中 - 使用 localStorage 持久化保存用户配置 refactor(routes): 重构设置页面路由结构 - 移除旧的侧边栏导航组件 - 更新路由配置以支持新的模型设置页面 - 生成相应的路由类型定义更新 feat(server): 添加 OpenAI 兼容的模型接口 - 在 /v1 路径下添加 models 接口端点 - 提供与 OpenAI API 兼容的模型列表功能
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
allowBuilds:
|
allowBuilds:
|
||||||
esbuild: true
|
esbuild: true
|
||||||
|
msw: true
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ struct ServerStatusResponse {
|
|||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_default_save_dir() -> Result<String, String> {
|
||||||
|
aha::utils::get_default_save_dir().ok_or_else(|| "无法获取 home 目录".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_save_dir() -> Result<String, String> {
|
fn get_save_dir() -> Result<String, String> {
|
||||||
aha::utils::get_default_save_dir().ok_or_else(|| "无法获取 home 目录".to_string())
|
aha::utils::get_default_save_dir().ok_or_else(|| "无法获取 home 目录".to_string())
|
||||||
}
|
}
|
||||||
@@ -206,10 +211,13 @@ fn get_model_detail(model_id: String) -> Result<ModelDetail, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn download_model(model_id: String) -> Result<(), String> {
|
async fn download_model(model_id: String, save_dir: Option<String>) -> Result<(), String> {
|
||||||
use aha::utils::download_model;
|
use aha::utils::download_model;
|
||||||
|
|
||||||
let save_dir = get_save_dir()?;
|
let save_dir = match save_dir {
|
||||||
|
Some(dir) => dir,
|
||||||
|
None => get_save_dir()?,
|
||||||
|
};
|
||||||
download_model(&model_id, &save_dir, 3)
|
download_model(&model_id, &save_dir, 3)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("下载失败: {}", e))
|
.map_err(|e| format!("下载失败: {}", e))
|
||||||
@@ -417,6 +425,7 @@ pub fn run() {
|
|||||||
stop_server,
|
stop_server,
|
||||||
get_server_status,
|
get_server_status,
|
||||||
clear_logs,
|
clear_logs,
|
||||||
|
get_default_save_dir,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Palette,
|
Palette,
|
||||||
Bell,
|
Bell,
|
||||||
Monitor,
|
Monitor,
|
||||||
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { type SidebarData } from '../types'
|
import { type SidebarData } from '../types'
|
||||||
|
|
||||||
@@ -52,6 +53,11 @@ export const sidebarData: SidebarData = {
|
|||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Model',
|
||||||
|
url: '/settings/model',
|
||||||
|
icon: Package,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Profile',
|
title: 'Profile',
|
||||||
url: '/settings',
|
url: '/settings',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { Main } from "@/components/layout/main"
|
|||||||
import { ProfileDropdown } from "@/components/profile-dropdown"
|
import { ProfileDropdown } from "@/components/profile-dropdown"
|
||||||
import { ThemeSwitch } from "@/components/theme-switch"
|
import { ThemeSwitch } from "@/components/theme-switch"
|
||||||
|
|
||||||
|
const SAVE_DIR_KEY = "aha-model-save-dir"
|
||||||
|
|
||||||
interface ModelInfo {
|
interface ModelInfo {
|
||||||
model_id: string
|
model_id: string
|
||||||
owner: string
|
owner: string
|
||||||
@@ -67,7 +69,8 @@ export function ModelsPage() {
|
|||||||
setDownloading(modelId)
|
setDownloading(modelId)
|
||||||
setError(null)
|
setError(null)
|
||||||
try {
|
try {
|
||||||
await invoke("download_model", { modelId })
|
const saveDir = localStorage.getItem(SAVE_DIR_KEY) || null
|
||||||
|
await invoke("download_model", { modelId, saveDir })
|
||||||
await loadModels()
|
await loadModels()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(String(e))
|
setError(String(e))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Outlet } from '@tanstack/react-router'
|
import { Outlet } from '@tanstack/react-router'
|
||||||
import { Monitor, Bell, Palette, Wrench, UserCog } from 'lucide-react'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { ConfigDrawer } from '@/components/config-drawer'
|
import { ConfigDrawer } from '@/components/config-drawer'
|
||||||
import { Header } from '@/components/layout/header'
|
import { Header } from '@/components/layout/header'
|
||||||
@@ -7,35 +6,6 @@ import { Main } from '@/components/layout/main'
|
|||||||
import { ProfileDropdown } from '@/components/profile-dropdown'
|
import { ProfileDropdown } from '@/components/profile-dropdown'
|
||||||
import { Search } from '@/components/search'
|
import { Search } from '@/components/search'
|
||||||
import { ThemeSwitch } from '@/components/theme-switch'
|
import { ThemeSwitch } from '@/components/theme-switch'
|
||||||
import { SidebarNav } from './components/sidebar-nav'
|
|
||||||
|
|
||||||
const sidebarNavItems = [
|
|
||||||
{
|
|
||||||
title: 'Profile',
|
|
||||||
href: '/settings',
|
|
||||||
icon: <UserCog size={18} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Account',
|
|
||||||
href: '/settings/account',
|
|
||||||
icon: <Wrench size={18} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Appearance',
|
|
||||||
href: '/settings/appearance',
|
|
||||||
icon: <Palette size={18} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Notifications',
|
|
||||||
href: '/settings/notifications',
|
|
||||||
icon: <Bell size={18} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Display',
|
|
||||||
href: '/settings/display',
|
|
||||||
icon: <Monitor size={18} />,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
return (
|
return (
|
||||||
@@ -58,13 +28,8 @@ export function Settings() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator className='my-4 lg:my-6' />
|
<Separator className='my-4 lg:my-6' />
|
||||||
<div className='flex flex-1 flex-col space-y-2 overflow-hidden md:space-y-2 lg:flex-row lg:space-y-0 lg:space-x-12'>
|
<div className='flex w-full overflow-y-hidden p-1'>
|
||||||
<aside className='top-0 lg:sticky lg:w-1/5'>
|
<Outlet />
|
||||||
<SidebarNav items={sidebarNavItems} />
|
|
||||||
</aside>
|
|
||||||
<div className='flex w-full overflow-y-hidden p-1'>
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Main>
|
</Main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { invoke } from "@tauri-apps/api/core"
|
||||||
|
import { FolderOpen } from "lucide-react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { ContentSection } from "../components/content-section"
|
||||||
|
|
||||||
|
const SAVE_DIR_KEY = "aha-model-save-dir"
|
||||||
|
|
||||||
|
export function SettingsModel() {
|
||||||
|
const [saveDir, setSaveDir] = useState("")
|
||||||
|
const [defaultDir, setDefaultDir] = useState("")
|
||||||
|
const [saved, setSaved] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
invoke<string>("get_default_save_dir")
|
||||||
|
.then(setDefaultDir)
|
||||||
|
.catch(() => {})
|
||||||
|
|
||||||
|
const stored = localStorage.getItem(SAVE_DIR_KEY)
|
||||||
|
if (stored) setSaveDir(stored)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (saveDir) {
|
||||||
|
localStorage.setItem(SAVE_DIR_KEY, saveDir)
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(SAVE_DIR_KEY)
|
||||||
|
}
|
||||||
|
setSaved(true)
|
||||||
|
setTimeout(() => setSaved(false), 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setSaveDir("")
|
||||||
|
localStorage.removeItem(SAVE_DIR_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentSection
|
||||||
|
title='Model'
|
||||||
|
desc='配置模型下载位置和相关设置'
|
||||||
|
>
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='save-dir' className='flex items-center gap-1.5'>
|
||||||
|
<FolderOpen className='w-4 h-4' />
|
||||||
|
模型下载路径
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id='save-dir'
|
||||||
|
value={saveDir}
|
||||||
|
onChange={(e) => setSaveDir(e.target.value)}
|
||||||
|
placeholder={defaultDir || "~/.aha/"}
|
||||||
|
/>
|
||||||
|
<p className='text-xs text-muted-foreground'>
|
||||||
|
留空则使用默认路径:{defaultDir || "~/.aha/"}
|
||||||
|
。模型将下载到该目录下的 {`{model_id}`} 子文件夹中。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<Button onClick={handleSave}>
|
||||||
|
{saved ? "已保存" : "保存"}
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline' onClick={handleReset}>
|
||||||
|
恢复默认
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ContentSection>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import { Route as authForgotPasswordRouteImport } from './routes/(auth)/forgot-p
|
|||||||
import { Route as AuthenticatedSettingsRouteRouteImport } from './routes/_authenticated/settings/route'
|
import { Route as AuthenticatedSettingsRouteRouteImport } from './routes/_authenticated/settings/route'
|
||||||
import { Route as AuthenticatedSettingsIndexRouteImport } from './routes/_authenticated/settings/index'
|
import { Route as AuthenticatedSettingsIndexRouteImport } from './routes/_authenticated/settings/index'
|
||||||
import { Route as AuthenticatedSettingsNotificationsRouteImport } from './routes/_authenticated/settings/notifications'
|
import { Route as AuthenticatedSettingsNotificationsRouteImport } from './routes/_authenticated/settings/notifications'
|
||||||
|
import { Route as AuthenticatedSettingsModelRouteImport } from './routes/_authenticated/settings/model'
|
||||||
import { Route as AuthenticatedSettingsDisplayRouteImport } from './routes/_authenticated/settings/display'
|
import { Route as AuthenticatedSettingsDisplayRouteImport } from './routes/_authenticated/settings/display'
|
||||||
import { Route as AuthenticatedSettingsAppearanceRouteImport } from './routes/_authenticated/settings/appearance'
|
import { Route as AuthenticatedSettingsAppearanceRouteImport } from './routes/_authenticated/settings/appearance'
|
||||||
import { Route as AuthenticatedSettingsAccountRouteImport } from './routes/_authenticated/settings/account'
|
import { Route as AuthenticatedSettingsAccountRouteImport } from './routes/_authenticated/settings/account'
|
||||||
@@ -117,6 +118,12 @@ const AuthenticatedSettingsNotificationsRoute =
|
|||||||
path: '/notifications',
|
path: '/notifications',
|
||||||
getParentRoute: () => AuthenticatedSettingsRouteRoute,
|
getParentRoute: () => AuthenticatedSettingsRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthenticatedSettingsModelRoute =
|
||||||
|
AuthenticatedSettingsModelRouteImport.update({
|
||||||
|
id: '/model',
|
||||||
|
path: '/model',
|
||||||
|
getParentRoute: () => AuthenticatedSettingsRouteRoute,
|
||||||
|
} as any)
|
||||||
const AuthenticatedSettingsDisplayRoute =
|
const AuthenticatedSettingsDisplayRoute =
|
||||||
AuthenticatedSettingsDisplayRouteImport.update({
|
AuthenticatedSettingsDisplayRouteImport.update({
|
||||||
id: '/display',
|
id: '/display',
|
||||||
@@ -154,6 +161,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/settings/account': typeof AuthenticatedSettingsAccountRoute
|
'/settings/account': typeof AuthenticatedSettingsAccountRoute
|
||||||
'/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
'/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
||||||
'/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
'/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
||||||
|
'/settings/model': typeof AuthenticatedSettingsModelRoute
|
||||||
'/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
'/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
||||||
'/settings/': typeof AuthenticatedSettingsIndexRoute
|
'/settings/': typeof AuthenticatedSettingsIndexRoute
|
||||||
}
|
}
|
||||||
@@ -174,6 +182,7 @@ export interface FileRoutesByTo {
|
|||||||
'/settings/account': typeof AuthenticatedSettingsAccountRoute
|
'/settings/account': typeof AuthenticatedSettingsAccountRoute
|
||||||
'/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
'/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
||||||
'/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
'/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
||||||
|
'/settings/model': typeof AuthenticatedSettingsModelRoute
|
||||||
'/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
'/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
||||||
'/settings': typeof AuthenticatedSettingsIndexRoute
|
'/settings': typeof AuthenticatedSettingsIndexRoute
|
||||||
}
|
}
|
||||||
@@ -197,6 +206,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticated/settings/account': typeof AuthenticatedSettingsAccountRoute
|
'/_authenticated/settings/account': typeof AuthenticatedSettingsAccountRoute
|
||||||
'/_authenticated/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
'/_authenticated/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
||||||
'/_authenticated/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
'/_authenticated/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
||||||
|
'/_authenticated/settings/model': typeof AuthenticatedSettingsModelRoute
|
||||||
'/_authenticated/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
'/_authenticated/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
||||||
'/_authenticated/settings/': typeof AuthenticatedSettingsIndexRoute
|
'/_authenticated/settings/': typeof AuthenticatedSettingsIndexRoute
|
||||||
}
|
}
|
||||||
@@ -220,6 +230,7 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/account'
|
| '/settings/account'
|
||||||
| '/settings/appearance'
|
| '/settings/appearance'
|
||||||
| '/settings/display'
|
| '/settings/display'
|
||||||
|
| '/settings/model'
|
||||||
| '/settings/notifications'
|
| '/settings/notifications'
|
||||||
| '/settings/'
|
| '/settings/'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
@@ -240,6 +251,7 @@ export interface FileRouteTypes {
|
|||||||
| '/settings/account'
|
| '/settings/account'
|
||||||
| '/settings/appearance'
|
| '/settings/appearance'
|
||||||
| '/settings/display'
|
| '/settings/display'
|
||||||
|
| '/settings/model'
|
||||||
| '/settings/notifications'
|
| '/settings/notifications'
|
||||||
| '/settings'
|
| '/settings'
|
||||||
id:
|
id:
|
||||||
@@ -262,6 +274,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticated/settings/account'
|
| '/_authenticated/settings/account'
|
||||||
| '/_authenticated/settings/appearance'
|
| '/_authenticated/settings/appearance'
|
||||||
| '/_authenticated/settings/display'
|
| '/_authenticated/settings/display'
|
||||||
|
| '/_authenticated/settings/model'
|
||||||
| '/_authenticated/settings/notifications'
|
| '/_authenticated/settings/notifications'
|
||||||
| '/_authenticated/settings/'
|
| '/_authenticated/settings/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
@@ -401,6 +414,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedSettingsNotificationsRouteImport
|
preLoaderRoute: typeof AuthenticatedSettingsNotificationsRouteImport
|
||||||
parentRoute: typeof AuthenticatedSettingsRouteRoute
|
parentRoute: typeof AuthenticatedSettingsRouteRoute
|
||||||
}
|
}
|
||||||
|
'/_authenticated/settings/model': {
|
||||||
|
id: '/_authenticated/settings/model'
|
||||||
|
path: '/model'
|
||||||
|
fullPath: '/settings/model'
|
||||||
|
preLoaderRoute: typeof AuthenticatedSettingsModelRouteImport
|
||||||
|
parentRoute: typeof AuthenticatedSettingsRouteRoute
|
||||||
|
}
|
||||||
'/_authenticated/settings/display': {
|
'/_authenticated/settings/display': {
|
||||||
id: '/_authenticated/settings/display'
|
id: '/_authenticated/settings/display'
|
||||||
path: '/display'
|
path: '/display'
|
||||||
@@ -429,6 +449,7 @@ interface AuthenticatedSettingsRouteRouteChildren {
|
|||||||
AuthenticatedSettingsAccountRoute: typeof AuthenticatedSettingsAccountRoute
|
AuthenticatedSettingsAccountRoute: typeof AuthenticatedSettingsAccountRoute
|
||||||
AuthenticatedSettingsAppearanceRoute: typeof AuthenticatedSettingsAppearanceRoute
|
AuthenticatedSettingsAppearanceRoute: typeof AuthenticatedSettingsAppearanceRoute
|
||||||
AuthenticatedSettingsDisplayRoute: typeof AuthenticatedSettingsDisplayRoute
|
AuthenticatedSettingsDisplayRoute: typeof AuthenticatedSettingsDisplayRoute
|
||||||
|
AuthenticatedSettingsModelRoute: typeof AuthenticatedSettingsModelRoute
|
||||||
AuthenticatedSettingsNotificationsRoute: typeof AuthenticatedSettingsNotificationsRoute
|
AuthenticatedSettingsNotificationsRoute: typeof AuthenticatedSettingsNotificationsRoute
|
||||||
AuthenticatedSettingsIndexRoute: typeof AuthenticatedSettingsIndexRoute
|
AuthenticatedSettingsIndexRoute: typeof AuthenticatedSettingsIndexRoute
|
||||||
}
|
}
|
||||||
@@ -438,6 +459,7 @@ const AuthenticatedSettingsRouteRouteChildren: AuthenticatedSettingsRouteRouteCh
|
|||||||
AuthenticatedSettingsAccountRoute: AuthenticatedSettingsAccountRoute,
|
AuthenticatedSettingsAccountRoute: AuthenticatedSettingsAccountRoute,
|
||||||
AuthenticatedSettingsAppearanceRoute: AuthenticatedSettingsAppearanceRoute,
|
AuthenticatedSettingsAppearanceRoute: AuthenticatedSettingsAppearanceRoute,
|
||||||
AuthenticatedSettingsDisplayRoute: AuthenticatedSettingsDisplayRoute,
|
AuthenticatedSettingsDisplayRoute: AuthenticatedSettingsDisplayRoute,
|
||||||
|
AuthenticatedSettingsModelRoute: AuthenticatedSettingsModelRoute,
|
||||||
AuthenticatedSettingsNotificationsRoute:
|
AuthenticatedSettingsNotificationsRoute:
|
||||||
AuthenticatedSettingsNotificationsRoute,
|
AuthenticatedSettingsNotificationsRoute,
|
||||||
AuthenticatedSettingsIndexRoute: AuthenticatedSettingsIndexRoute,
|
AuthenticatedSettingsIndexRoute: AuthenticatedSettingsIndexRoute,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { SettingsModel } from '@/features/settings/model'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_authenticated/settings/model')({
|
||||||
|
component: SettingsModel,
|
||||||
|
})
|
||||||
@@ -72,6 +72,8 @@ pub(crate) async fn start_http_server(
|
|||||||
|
|
||||||
// Health check and model info endpoints
|
// Health check and model info endpoints
|
||||||
builder = builder.mount("/", routes![api::health, api::models]);
|
builder = builder.mount("/", routes![api::health, api::models]);
|
||||||
|
// OpenAI-compatible model listing endpoint: /v1/models
|
||||||
|
builder = builder.mount("/v1", routes![api::models]);
|
||||||
// Shutdown endpoint
|
// Shutdown endpoint
|
||||||
builder = builder.manage(shutdown_flag);
|
builder = builder.manage(shutdown_flag);
|
||||||
builder = builder.mount("/", routes![api::shutdown]);
|
builder = builder.mount("/", routes![api::shutdown]);
|
||||||
|
|||||||
Reference in New Issue
Block a user