mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-10 17:42:50 +08:00
feat(settings): add new AccountSettingsSystem.vue
- 将 原system更名为service,原service更名为scheduler - 增加新的 AccountSettingSystem.vue。 - 调整 menu.ts 与settings.vue,适配新的 system 标签页
This commit is contained in:
@@ -9,9 +9,9 @@ import AccountSettingSearch from '@/views/setting/AccountSettingSearch.vue'
|
||||
import AccountSettingSubscribe from '@/views/setting/AccountSettingSubscribe.vue'
|
||||
import AccountSettingService from '@/views/setting/AccountSettingService.vue'
|
||||
import AccountSettingSystem from '@/views/setting/AccountSettingSystem.vue'
|
||||
import AccountSettingScheduler from '@/views/setting/AccountSettingScheduler.vue'
|
||||
import AccountSettingDirectory from '@/views/setting/AccountSettingDirectory.vue'
|
||||
import AccountSettingRule from '@/views/setting/AccountSettingRule.vue'
|
||||
import AccountSettingNetwork from '@/views/setting/AccountSettingNetwork.vue'
|
||||
import { SettingTabs } from '@/router/menu'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -41,7 +41,7 @@ function jumpTab(tab: string) {
|
||||
</VTabs>
|
||||
|
||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||
<!-- 连接 -->
|
||||
<!-- 系统 -->
|
||||
<VWindowItem value="system">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
@@ -50,11 +50,11 @@ function jumpTab(tab: string) {
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 网络 -->
|
||||
<VWindowItem value="network">
|
||||
<!-- 连接 -->
|
||||
<VWindowItem value="service">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<AccountSettingNetwork />
|
||||
<AccountSettingService />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
@@ -103,10 +103,10 @@ function jumpTab(tab: string) {
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 服务 -->
|
||||
<VWindowItem value="service">
|
||||
<VWindowItem value="scheduler">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<AccountSettingService />
|
||||
<AccountSettingScheduler />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
@@ -132,16 +132,16 @@ export const UserfulMenus = [
|
||||
// 设定标签页
|
||||
export const SettingTabs = [
|
||||
{
|
||||
title: '连接',
|
||||
icon: 'mdi-server-network',
|
||||
title: '系统',
|
||||
icon: 'mdi-cog',
|
||||
tab: 'system',
|
||||
description: '下载器(Qbittorrent、Transmission)、媒体服务器(Emby、Jellyfin、Plex)',
|
||||
description: '基本设定、网络设定、高级设定',
|
||||
},
|
||||
{
|
||||
title: '网络',
|
||||
icon: 'mdi-access-point-network',
|
||||
tab: 'network',
|
||||
description: 'Github、PIP、OCR、DOH',
|
||||
title: '连接',
|
||||
icon: 'mdi-server-network',
|
||||
tab: 'service',
|
||||
description: '下载器(Qbittorrent、Transmission)、媒体服务器(Emby、Jellyfin、Plex)',
|
||||
},
|
||||
{
|
||||
title: '存储 & 目录',
|
||||
@@ -174,9 +174,9 @@ export const SettingTabs = [
|
||||
description: '订阅站点、订阅模式、订阅优先级、洗版优先级、默认过滤规则',
|
||||
},
|
||||
{
|
||||
title: '服务',
|
||||
title: '调度',
|
||||
icon: 'mdi-list-box',
|
||||
tab: 'service',
|
||||
tab: 'scheduler',
|
||||
description: '定时作业',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,350 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import {useToast} from 'vue-toast-notification'
|
||||
import {VRow} from 'vuetify/lib/components/index.mjs'
|
||||
import api from '@/api'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
// 系统设置项
|
||||
const SystemSettings = ref({
|
||||
DOH_ENABLE: false,
|
||||
GITHUB_PROXY: '',
|
||||
GITHUB_TOKEN: '',
|
||||
PIP_PROXY: '',
|
||||
|
||||
DOH_SERVER: '',
|
||||
DOH_DOMAINS: '',
|
||||
OCR_HOST: '',
|
||||
})
|
||||
|
||||
// 高级设置项
|
||||
const AdvancedSettings = ref({
|
||||
DOH_SERVER: '',
|
||||
DOH_DOMAINS: '',
|
||||
OCR_HOST: '',
|
||||
})
|
||||
|
||||
// 防抖时间
|
||||
const debounceTime = 500
|
||||
|
||||
// 高级设置弹窗
|
||||
const isAdvanced = ref(false)
|
||||
|
||||
// 是否发送请求的总开关
|
||||
const isRequest = ref(true)
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载系统设置
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
const {
|
||||
// 需要查询的变量
|
||||
DOH_ENABLE,
|
||||
GITHUB_PROXY,
|
||||
GITHUB_TOKEN,
|
||||
PIP_PROXY,
|
||||
|
||||
DOH_SERVER,
|
||||
DOH_DOMAINS,
|
||||
OCR_HOST,
|
||||
} = result.data
|
||||
SystemSettings.value = {
|
||||
DOH_ENABLE: DOH_ENABLE || false,
|
||||
GITHUB_PROXY: GITHUB_PROXY || null,
|
||||
GITHUB_TOKEN: GITHUB_TOKEN || null,
|
||||
PIP_PROXY: PIP_PROXY || null,
|
||||
|
||||
DOH_SERVER: DOH_SERVER || null,
|
||||
DOH_DOMAINS: DOH_DOMAINS || null,
|
||||
OCR_HOST: OCR_HOST || null,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存系统设置
|
||||
const saveSystemSetting = debounce(async () => {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', SystemSettings.value)
|
||||
if (result.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
} else $toast.error('保存设置失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, debounceTime)
|
||||
|
||||
// 预设部分Github加速站
|
||||
const githubMirrorsItems = [
|
||||
'https://mirror.ghproxy.com/', // GitHub Proxy
|
||||
'https://ghp.ci/', // GitHub Proxy 子站
|
||||
]
|
||||
|
||||
// 预设部分PIP镜像站
|
||||
const pipMirrorsItems = [
|
||||
'https://pypi.tuna.tsinghua.edu.cn/simple', // 清华大学
|
||||
'https://pypi.mirrors.ustc.edu.cn/simple', // 中国科技大学
|
||||
'https://mirrors.pku.edu.cn/pypi/web/simple', // 北京大学
|
||||
'https://mirrors.aliyun.com/pypi/simple', // 阿里云
|
||||
'https://mirrors.cloud.tencent.com/pypi/simple', // 腾讯云
|
||||
'https://mirrors.163.com/pypi/simple', // 网易云
|
||||
'https://pypi.doubanio.com/simple', // 豆瓣
|
||||
'https://mirrors.hust.edu.cn/pypi/web/simple', // 华中理工大学
|
||||
'https://mirrors.bfsu.edu.cn/pypi/web/simple', // 北京外国语大学
|
||||
]
|
||||
|
||||
// 打开高级设置弹窗
|
||||
const openAdvancedSettings = () => {
|
||||
for (const key in SystemSettings.value) {
|
||||
AdvancedSettings.value[key] = SystemSettings.value[key]
|
||||
}
|
||||
isAdvanced.value = true
|
||||
}
|
||||
|
||||
// 直接关闭高级设置弹窗
|
||||
const closeAdvancedSettings = () => {
|
||||
isAdvanced.value = false
|
||||
for (const key in SystemSettings.value) {
|
||||
AdvancedSettings.value[key] = SystemSettings.value[key]
|
||||
}
|
||||
}
|
||||
|
||||
// 保存高级设置
|
||||
const saveAdvancedSettings = debounce(async () => {
|
||||
$toast.info('高级设置确定成功,待保存后生效')
|
||||
for (const key in AdvancedSettings.value) {
|
||||
SystemSettings.value[key] = AdvancedSettings.value[key]
|
||||
}
|
||||
isAdvanced.value = false
|
||||
}, debounceTime)
|
||||
|
||||
// 恢复高级设置默认值,直接赋值
|
||||
const loadAdvancedSettings = () => {
|
||||
AdvancedSettings.value = {
|
||||
DOH_SERVER: "1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112",
|
||||
DOH_DOMAINS: "api.themoviedb.org,api.tmdb.org,webservice.fanart.tv,api.github.com,github.com,raw.githubusercontent.com,api.telegram.org",
|
||||
OCR_HOST: "https://movie-pilot.org",
|
||||
}
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadSystemSettings()
|
||||
})
|
||||
|
||||
// 当isAdvanced变化时,且为true时,重新从SystemSettings赋值到AdvancedSettings
|
||||
watch(isAdvanced, (value) => {
|
||||
if (value) openAdvancedSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- DOH -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设置DNS over HTTPS服务器,对特定域名使用DOH解析以避免DNS污染。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6" class="flex align-center">
|
||||
<div>
|
||||
<VSwitch
|
||||
v-model="SystemSettings.DOH_ENABLE"
|
||||
label="启用 DNS over HTTPS"
|
||||
hint="启用后,系统将使用DOH服务器解析域名"
|
||||
persistent-hint
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<VAlert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="whitespace-pre-line"
|
||||
style="width: fit-content"
|
||||
text="如果已经配置了 PROXY_HOST 则建议关闭 DOH 功能。"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6" class="flex align-center">
|
||||
<div>
|
||||
<VSwitch
|
||||
v-model="isAdvanced"
|
||||
label="高级设置"
|
||||
hint="进入高级设置弹窗页面"
|
||||
persistent-hint
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<VAlert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="whitespace-pre-line mr-3"
|
||||
style="width: fit-content"
|
||||
text="该页面包含一些高级设置,不建议修改,除非它们的使用方法!"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn mtype="submit" @click="saveSystemSetting"> 保存</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 加速 -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>Github 与 PIP</VCardTitle>
|
||||
<VCardSubtitle>设置PIP加速站、Github加速站、Github Token增加连接的限流阈值,保证连通性。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.GITHUB_PROXY"
|
||||
label="Github加速站"
|
||||
placeholder="格式:https://mirror.ghproxy.com/"
|
||||
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点。格式:https://mirror.ghproxy.com/ 末尾需要带 /"
|
||||
persistent-hint
|
||||
clearable
|
||||
:items="githubMirrorsItems"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.GITHUB_TOKEN"
|
||||
label="Github Token"
|
||||
placeholder="ghp_**** 或 github_pat_****"
|
||||
hint="用于提高Github API访问限流阈值"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.PIP_PROXY"
|
||||
label="PIP加速站"
|
||||
placeholder="格式:https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点,格式: https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
persistent-hint
|
||||
clearable
|
||||
:items="pipMirrorsItems"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn mtype="submit" @click="saveSystemSetting"> 保存</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 高级设置弹窗 -->
|
||||
<VDialog v-model="isAdvanced" scrollable max-width="40rem" persistent>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>网络高级设置</VCardTitle>
|
||||
<VCardSubtitle>修改前,请先了解清除这些设置的作用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<DialogCloseBtn @click="closeAdvancedSettings"/>
|
||||
<VDivider/>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="AdvancedSettings.OCR_HOST"
|
||||
label="验证码识别服务器"
|
||||
hint="用于识别验证码"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="AdvancedSettings.DOH_SERVER"
|
||||
label="DOH 服务器"
|
||||
placeholder="格式:https://dns.google/dns-query,1.1.1.1"
|
||||
hint="多个服务器使用逗号分隔"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="AdvancedSettings.DOH_DOMAINS"
|
||||
label="DOH 域名"
|
||||
placeholder="格式:example.com,example2.com"
|
||||
hint="多个域名使用逗号分隔"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VBtn
|
||||
color="info"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-reload"
|
||||
@click="loadAdvancedSettings"
|
||||
class="px-5"
|
||||
>
|
||||
默认值
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="saveAdvancedSettings"
|
||||
class="px-5"
|
||||
>
|
||||
确定
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</VRow>
|
||||
</template>
|
||||
124
src/views/setting/AccountSettingScheduler.vue
Normal file
124
src/views/setting/AccountSettingScheduler.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
import type { ScheduleInfo } from '@/api/types'
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 定时服务列表
|
||||
const schedulerList = ref<ScheduleInfo[]>([])
|
||||
|
||||
// 定时器
|
||||
let refreshTimer: NodeJS.Timeout | null = null
|
||||
|
||||
// 调用API加载定时服务列表
|
||||
async function loadSchedulerList() {
|
||||
try {
|
||||
const res: ScheduleInfo[] = await api.get('dashboard/schedule')
|
||||
|
||||
schedulerList.value = res
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 任务状态颜色
|
||||
function getSchedulerColor(status: string) {
|
||||
switch (status) {
|
||||
case '正在运行':
|
||||
return 'success'
|
||||
case '已停止':
|
||||
return 'error'
|
||||
case '等待':
|
||||
return ''
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
function runCommand(id: string) {
|
||||
try {
|
||||
// 异步提交
|
||||
api.get('system/runscheduler', {
|
||||
params: {
|
||||
jobid: id,
|
||||
},
|
||||
})
|
||||
$toast.success('定时作业执行请求提交成功!')
|
||||
// 1秒后刷新数据
|
||||
setTimeout(() => {
|
||||
loadSchedulerList()
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSchedulerList()
|
||||
|
||||
// 启动定时器
|
||||
refreshTimer = setInterval(() => {
|
||||
loadSchedulerList()
|
||||
}, 5000)
|
||||
})
|
||||
|
||||
// 组件卸载时停止定时器
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>定时作业</VCardTitle>
|
||||
<VCardSubtitle>包含系统内置服务以及插件提供的服务,手动执行不会影响作业正常的时间表。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">提供者</th>
|
||||
<th scope="col">任务名称</th>
|
||||
<th scope="col">任务状态</th>
|
||||
<th scope="col">下一次执行时间</th>
|
||||
<th scope="col" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="scheduler in schedulerList" :key="scheduler.id">
|
||||
<td>
|
||||
{{ scheduler.provider }}
|
||||
</td>
|
||||
<td>
|
||||
{{ scheduler.name }}
|
||||
</td>
|
||||
<td>
|
||||
<VChip :color="getSchedulerColor(scheduler.status)">
|
||||
{{ scheduler.status }}
|
||||
</VChip>
|
||||
</td>
|
||||
<td>
|
||||
{{ scheduler.next_run }}
|
||||
</td>
|
||||
<td>
|
||||
<VBtn size="small" :disabled="scheduler.status === '正在运行'" @click="runCommand(scheduler.id)">
|
||||
<template #prepend>
|
||||
<VIcon>mdi-play</VIcon>
|
||||
</template>
|
||||
执行
|
||||
</VBtn>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="schedulerList.length === 0">
|
||||
<td colspan="4" class="text-center">没有后台服务</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -1,124 +1,356 @@
|
||||
<!-- eslint-disable sonarjs/no-duplicate-string -->
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { VRow } from 'vuetify/lib/components/index.mjs'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import type { ScheduleInfo } from '@/api/types'
|
||||
import { DownloaderConf, MediaServerConf } from '@/api/types'
|
||||
import DownloaderCard from '@/components/cards/DownloaderCard.vue'
|
||||
import MediaServerCard from '@/components/cards/MediaServerCard.vue'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
// 防抖时间
|
||||
const debounceTime = 500
|
||||
|
||||
// 系统设置项
|
||||
const SystemSettings = ref<any>({
|
||||
MEDIASERVER_SYNC_INTERVAL: 6,
|
||||
})
|
||||
|
||||
// 是否发送请求的总开关
|
||||
const isRequest = ref(true)
|
||||
|
||||
// 选中的媒体服务器
|
||||
const mediaServers = ref<MediaServerConf[]>([])
|
||||
|
||||
// 下载器
|
||||
const downloaders = ref<DownloaderConf[]>([])
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 定时服务列表
|
||||
const schedulerList = ref<ScheduleInfo[]>([])
|
||||
|
||||
// 定时器
|
||||
let refreshTimer: NodeJS.Timeout | null = null
|
||||
|
||||
// 调用API加载定时服务列表
|
||||
async function loadSchedulerList() {
|
||||
// 调用API查询下载器设置
|
||||
async function loadDownloaderSetting() {
|
||||
try {
|
||||
const res: ScheduleInfo[] = await api.get('dashboard/schedule')
|
||||
|
||||
schedulerList.value = res
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
const result: { [key: string]: any } = await api.get('system/setting/Downloaders')
|
||||
downloaders.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 任务状态颜色
|
||||
function getSchedulerColor(status: string) {
|
||||
switch (status) {
|
||||
case '正在运行':
|
||||
return 'success'
|
||||
case '已停止':
|
||||
return 'error'
|
||||
case '等待':
|
||||
return ''
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
function runCommand(id: string) {
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
// 异步提交
|
||||
api.get('system/runscheduler', {
|
||||
params: {
|
||||
jobid: id,
|
||||
},
|
||||
})
|
||||
$toast.success('定时作业执行请求提交成功!')
|
||||
// 1秒后刷新数据
|
||||
setTimeout(() => {
|
||||
loadSchedulerList()
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSchedulerList()
|
||||
// 调用API保存下载器设置
|
||||
const saveDownloaderSetting = debounce(async () => {
|
||||
try {
|
||||
// 提取启用的下载器
|
||||
const enabledDownloaders = downloaders.value.filter(item => item.enabled);
|
||||
// 有启动的下载器时
|
||||
if (enabledDownloaders.length > 0) {
|
||||
downloaders.value = handleDefaultDownloaders(enabledDownloaders, downloaders.value);
|
||||
}
|
||||
const result: { [key: string]: any } = await api.post('system/setting/Downloaders', downloaders.value)
|
||||
if (result.success) $toast.success('下载器设置保存成功')
|
||||
else $toast.error('下载器设置保存失败!')
|
||||
|
||||
// 启动定时器
|
||||
refreshTimer = setInterval(() => {
|
||||
loadSchedulerList()
|
||||
}, 5000)
|
||||
await loadDownloaderSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, debounceTime)
|
||||
|
||||
// 处理默认下载器状态
|
||||
function handleDefaultDownloaders(enabledDownloaders: any[], downloaders: any[]) {
|
||||
const enabledDefaultDownloader = enabledDownloaders.find(item => item.default);
|
||||
if (enabledDownloaders.length > 0 && !enabledDefaultDownloader) {
|
||||
downloaders = downloaders.map(item => {
|
||||
if (item === enabledDownloaders[0]) {
|
||||
$toast.info(`未设置默认下载器,已将【${item.name}】作为默认下载器`);
|
||||
return {...item, default: true };
|
||||
}
|
||||
// 清除其他下载器的默认下载器状态
|
||||
return {...item, default: false };
|
||||
});
|
||||
}
|
||||
return downloaders;
|
||||
}
|
||||
|
||||
// 调用API查询媒体服务器设置
|
||||
async function loadMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/MediaServers')
|
||||
mediaServers.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存媒体服务器设置
|
||||
const saveMediaServerSetting = debounce(async () => {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/MediaServers', mediaServers.value)
|
||||
if (result.success) $toast.success('媒体服务器设置保存成功')
|
||||
else $toast.error('媒体服务器设置保存失败!')
|
||||
|
||||
await loadMediaServerSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, debounceTime)
|
||||
|
||||
// 加载系统设置
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
const {
|
||||
MEDIASERVER_SYNC_INTERVAL,
|
||||
} = result.data
|
||||
SystemSettings.value = {
|
||||
MEDIASERVER_SYNC_INTERVAL,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存系统设置
|
||||
const saveSystemSetting = debounce(async () => {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', SystemSettings.value)
|
||||
|
||||
if (result.success) $toast.success('保存设置成功')
|
||||
else $toast.error('保存设置失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}, debounceTime)
|
||||
|
||||
// 添加下载器
|
||||
function addDownloader(downloader: string) {
|
||||
let name = `下载器${downloaders.value.length + 1}`;
|
||||
while (downloaders.value.some(item => item.name === name)) {
|
||||
name = `下载器${parseInt(name.split('下载器')[1]) + 1}`;
|
||||
}
|
||||
downloaders.value.push({
|
||||
name: name,
|
||||
type: downloader,
|
||||
default: false,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除下载器
|
||||
function removeDownloader(ele: DownloaderConf) {
|
||||
const index = downloaders.value.indexOf(ele)
|
||||
downloaders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 下载器变化
|
||||
function onDownloaderChange(downloader: DownloaderConf) {
|
||||
const index = downloaders.value.findIndex(item => item.name === downloader.name)
|
||||
downloaders.value[index] = downloader
|
||||
}
|
||||
|
||||
// 添加媒体服务器
|
||||
const addMediaServer = debounce( (mediaserver: string) => {
|
||||
let name = `服务器${mediaServers.value.length + 1}`;
|
||||
while (mediaServers.value.some(item => item.name === name)) {
|
||||
name = `服务器${parseInt(name.split('服务器')[1]) + 1}`;
|
||||
}
|
||||
mediaServers.value.push({
|
||||
name: name,
|
||||
type: mediaserver,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}, debounceTime)
|
||||
|
||||
// 删除媒体服务器
|
||||
function removeMediaServer(ele: MediaServerConf) {
|
||||
const index = mediaServers.value.indexOf(ele)
|
||||
mediaServers.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 变更媒体服务器
|
||||
function onMediaServerChange(mediaserver: MediaServerConf) {
|
||||
const index = mediaServers.value.findIndex(item => item.name === mediaserver.name)
|
||||
mediaServers.value[index] = mediaserver
|
||||
}
|
||||
|
||||
// 禁止保存
|
||||
const isSystemSettingsSaveDisabled = computed(() => {
|
||||
return SystemSettings.value.MEDIASERVER_SYNC_INTERVAL < 1
|
||||
})
|
||||
|
||||
// 组件卸载时停止定时器
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDownloaderSetting()
|
||||
loadMediaServerSetting()
|
||||
loadSystemSettings()
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
isRequest.value = true
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
isRequest.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>定时作业</VCardTitle>
|
||||
<VCardSubtitle>包含系统内置服务以及插件提供的服务,手动执行不会影响作业正常的时间表。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">提供者</th>
|
||||
<th scope="col">任务名称</th>
|
||||
<th scope="col">任务状态</th>
|
||||
<th scope="col">下一次执行时间</th>
|
||||
<th scope="col" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="scheduler in schedulerList" :key="scheduler.id">
|
||||
<td>
|
||||
{{ scheduler.provider }}
|
||||
</td>
|
||||
<td>
|
||||
{{ scheduler.name }}
|
||||
</td>
|
||||
<td>
|
||||
<VChip :color="getSchedulerColor(scheduler.status)">
|
||||
{{ scheduler.status }}
|
||||
</VChip>
|
||||
</td>
|
||||
<td>
|
||||
{{ scheduler.next_run }}
|
||||
</td>
|
||||
<td>
|
||||
<VBtn size="small" :disabled="scheduler.status === '正在运行'" @click="runCommand(scheduler.id)">
|
||||
<template #prepend>
|
||||
<VIcon>mdi-play</VIcon>
|
||||
</template>
|
||||
执行
|
||||
</VBtn>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="schedulerList.length === 0">
|
||||
<td colspan="4" class="text-center">没有后台服务</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCard>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设置服务器的全局功能。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.MEDIASERVER_SYNC_INTERVAL"
|
||||
label="媒体服务器同步间隔"
|
||||
hint="不宜设置间隔过短的时间,这会导致服务器性能占用增高。"
|
||||
persistent-hint
|
||||
clearable
|
||||
suffix="小时"
|
||||
type="number"
|
||||
min="1"
|
||||
style="width: fit-content"
|
||||
:rules="[
|
||||
v => !!v || '必选项,请勿留空',
|
||||
v => !isNaN(v) || '仅支持输入数字,请勿输入其他字符',
|
||||
v => v >= 1 || '间隔不能小于1个小时',
|
||||
]"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSetting" :disabled="isSystemSettingsSaveDisabled"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>下载器</VCardTitle>
|
||||
<VCardSubtitle>只有默认下载器才会被默认使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="downloaders"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<DownloaderCard
|
||||
:downloader="element"
|
||||
:downloaders="downloaders"
|
||||
@close="removeDownloader(element)"
|
||||
@change="onDownloaderChange"
|
||||
:allow-refresh="isRequest"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveDownloaderSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addDownloader('qbittorrent')">
|
||||
<VListItemTitle>Qbittorrent</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addDownloader('transmission')">
|
||||
<VListItemTitle>Transmission</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>媒体服务器</VCardTitle>
|
||||
<VCardSubtitle>所有启用的媒体服务器都会被使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="mediaServers"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<MediaServerCard
|
||||
:mediaserver="element"
|
||||
:mediaservers="mediaServers"
|
||||
@close="removeMediaServer(element)"
|
||||
@change="onMediaServerChange"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveMediaServerSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addMediaServer('emby')">
|
||||
<VListItemTitle>Emby</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('jellyfin')">
|
||||
<VListItemTitle>Jellyfin</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('plex')">
|
||||
<VListItemTitle>Plex</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
@@ -1,106 +1,68 @@
|
||||
<!-- eslint-disable sonarjs/no-duplicate-string -->
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { VRow } from 'vuetify/lib/components/index.mjs'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import { DownloaderConf, MediaServerConf } from '@/api/types'
|
||||
import DownloaderCard from '@/components/cards/DownloaderCard.vue'
|
||||
import MediaServerCard from '@/components/cards/MediaServerCard.vue'
|
||||
import { copyToClipboard } from '@/@core/utils/navigator'
|
||||
import debounce from 'lodash/debounce'
|
||||
import AdvancedNetworkSettingsDialog from '@/components/dialog/AdvancedNetworkSettingsDialog.vue'
|
||||
import AdvancedSystemSettingsDialog from '@/components/dialog/AdvancedSystemSettingsDialog.vue'
|
||||
|
||||
// 系统设置项
|
||||
const SystemSettings = ref({
|
||||
APP_DOMAIN: '',
|
||||
// 系统设置默认值
|
||||
const SystemSettings = ref<any>({
|
||||
// 系统设置
|
||||
Basis: {
|
||||
// 基础设置
|
||||
AUXILIARY_AUTH_ENABLE: false,
|
||||
GLOBAL_IMAGE_CACHE: false,
|
||||
APP_DOMAIN: '',
|
||||
API_TOKEN: '',
|
||||
WALLPAPER: 'tmdb',
|
||||
PLUGIN_MARKET: '',
|
||||
},
|
||||
// 高级系统设置
|
||||
Advanced: {
|
||||
DEV: false,
|
||||
DEBUG: false,
|
||||
PLUGIN_AUTO_RELOAD: false,
|
||||
REPO_GITHUB_TOKEN: '',
|
||||
},
|
||||
// 网络设置
|
||||
Network: {
|
||||
// 基础网络设置
|
||||
TMDB_API_DOMAIN: '',
|
||||
TMDB_IMAGE_DOMAIN: '',
|
||||
DOH_ENABLE: true,
|
||||
GITHUB_PROXY: '',
|
||||
GITHUB_TOKEN: null,
|
||||
PIP_PROXY: null,
|
||||
// 隐藏设置,不在页面显示
|
||||
PROXY_HOST: '',
|
||||
},
|
||||
// 高级网络设置
|
||||
AdvancedNetwork: {
|
||||
DOH_RESOLVERS: '',
|
||||
DOH_DOMAINS: '',
|
||||
OCR_HOST: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 是否发送请求的总开关
|
||||
const isRequest = ref(true)
|
||||
// 防抖时间
|
||||
const debounceTime = 500
|
||||
|
||||
// 选中的媒体服务器
|
||||
const mediaServers = ref<MediaServerConf[]>([])
|
||||
|
||||
// 下载器
|
||||
const downloaders = ref<DownloaderConf[]>([])
|
||||
// 高级设置弹窗
|
||||
const isAdvancedSystemSettingsDialogOpen = ref(false)
|
||||
const isAdvancedNetworkSettingsDialogOpen = ref(false)
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 调用API查询下载器设置
|
||||
async function loadDownloaderSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/Downloaders')
|
||||
downloaders.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存下载器设置
|
||||
async function saveDownloaderSetting() {
|
||||
try {
|
||||
// 提取启用的下载器
|
||||
const enabledDownloaders = downloaders.value.filter(item => item.enabled);
|
||||
// 有启动的下载器时
|
||||
if (enabledDownloaders.length > 0) {
|
||||
downloaders.value = handleDefaultDownloaders(enabledDownloaders, downloaders.value);
|
||||
}
|
||||
const result: { [key: string]: any } = await api.post('system/setting/Downloaders', downloaders.value)
|
||||
if (result.success) $toast.success('下载器设置保存成功')
|
||||
else $toast.error('下载器设置保存失败!')
|
||||
|
||||
loadDownloaderSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理默认下载器状态
|
||||
function handleDefaultDownloaders(enabledDownloaders: any[], downloaders: any[]) {
|
||||
const enabledDefaultDownloader = enabledDownloaders.find(item => item.default);
|
||||
if (enabledDownloaders.length > 0 && !enabledDefaultDownloader) {
|
||||
downloaders = downloaders.map(item => {
|
||||
if (item === enabledDownloaders[0]) {
|
||||
$toast.info(`未设置默认下载器,已将【${item.name}】作为默认下载器`);
|
||||
return {...item, default: true };
|
||||
}
|
||||
// 清除其他下载器的默认下载器状态
|
||||
return {...item, default: false };
|
||||
});
|
||||
}
|
||||
return downloaders;
|
||||
}
|
||||
|
||||
// 调用API查询媒体服务器设置
|
||||
async function loadMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/MediaServers')
|
||||
mediaServers.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存媒体服务器设置
|
||||
async function saveMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/MediaServers', mediaServers.value)
|
||||
if (result.success) $toast.success('媒体服务器设置保存成功')
|
||||
else $toast.error('媒体服务器设置保存失败!')
|
||||
|
||||
loadMediaServerSetting()
|
||||
await reloadSystem()
|
||||
if (result.success) {
|
||||
$toast.success('系统配置已生效')
|
||||
await loadSystemSettings()
|
||||
} else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -111,114 +73,199 @@ async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
const { APP_DOMAIN } = result.data
|
||||
SystemSettings.value = {
|
||||
APP_DOMAIN,
|
||||
// 将API返回的值赋值给SystemSettings
|
||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||
let v: any
|
||||
if (result.data.hasOwnProperty(key)) {
|
||||
v = result.data[key]
|
||||
// 空字符串转为null,避免空字符串导致前端显示问题
|
||||
if (v === '') { v = null }
|
||||
(SystemSettings.value[sectionKey] as any)[key] = v
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
$toast.error('系统设置加载失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存系统设置
|
||||
async function saveSystemSetting() {
|
||||
// 调用API保存设置
|
||||
async function saveSystemSetting(value: { [key: string]: any }) {
|
||||
console.log(`正在保存设置:${JSON.stringify(value)}`)
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', SystemSettings.value)
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
|
||||
if (result.success) $toast.success('保存设置成功')
|
||||
else $toast.error('保存设置失败!')
|
||||
if (result.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
await loadSystemSettings()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
$toast.error('保存设置失败!')
|
||||
}
|
||||
}
|
||||
|
||||
// 保存系统设置
|
||||
const saveSystemSettings = debounce(async () => {
|
||||
const Settings = { ...SystemSettings.value.Basis, ...SystemSettings.value.Advanced }
|
||||
await saveSystemSetting(Settings)
|
||||
}, debounceTime)
|
||||
|
||||
// 保存网络设置
|
||||
const saveNetworkSettings = debounce(async () => {
|
||||
const Settings = { ...SystemSettings.value.Network, ...SystemSettings.value.AdvancedNetwork }
|
||||
// 查找PROXY_HOST,并删除,避免意外覆盖
|
||||
if (Settings.PROXY_HOST) delete Settings.PROXY_HOST
|
||||
await saveSystemSetting(Settings)
|
||||
}, debounceTime)
|
||||
|
||||
// 高级设置变化,等待保存
|
||||
function saveAdvancedSettings(Settings: any, key: string) {
|
||||
if (!Settings) return
|
||||
if (!key) return
|
||||
// 检查Settings中的键是否在SystemSettings的[key]中存在,有则使用Settings的值替换SystemSettings中的值
|
||||
for (const settingKey in Settings) {
|
||||
if (SystemSettings.value[key].hasOwnProperty(settingKey)) {
|
||||
(SystemSettings.value[key] as any)[settingKey] = Settings[settingKey]
|
||||
}
|
||||
}
|
||||
$toast.info('高级设置已更改,待保存后生效')
|
||||
console.log(`保存后的SystemSettings:${JSON.stringify(SystemSettings.value[key])}`)
|
||||
}
|
||||
|
||||
// 快捷复制到剪贴板
|
||||
function copyValue(value: string) {
|
||||
try {
|
||||
copyToClipboard(value)
|
||||
$toast.success('已复制到剪贴板')
|
||||
} catch (error) {
|
||||
$toast.error('复制失败!')
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加下载器
|
||||
function addDownloader(downloader: string) {
|
||||
let name = `下载器${downloaders.value.length + 1}`;
|
||||
while (downloaders.value.some(item => item.name === name)) {
|
||||
name = `下载器${parseInt(name.split('下载器')[1]) + 1}`;
|
||||
}
|
||||
downloaders.value.push({
|
||||
name: name,
|
||||
type: downloader,
|
||||
default: false,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
// 登录首页壁纸来源
|
||||
const wallpaperItems = [
|
||||
{ title: 'TheMovieDB电影海报', value: 'tmdb' },
|
||||
{ title: 'Bing每日壁纸', value: 'bing' },
|
||||
]
|
||||
|
||||
// 创建随机字符串
|
||||
function createRandomString() {
|
||||
const charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||
const array = new Uint8Array(16)
|
||||
window.crypto.getRandomValues(array)
|
||||
SystemSettings.value.Basis.API_TOKEN = Array.from(array, byte => charset[byte % charset.length]).join('')
|
||||
}
|
||||
|
||||
// 删除下载器
|
||||
function removeDownloader(ele: DownloaderConf) {
|
||||
const index = downloaders.value.indexOf(ele)
|
||||
downloaders.value.splice(index, 1)
|
||||
}
|
||||
// 预设部分Github加速站
|
||||
const githubMirrorsItems = [
|
||||
'https://mirror.ghproxy.com/', // GitHub Proxy
|
||||
'https://ghp.ci/', // GitHub Proxy 子站
|
||||
]
|
||||
|
||||
// 下载器变化
|
||||
function onDownloaderChange(downloader: DownloaderConf) {
|
||||
const index = downloaders.value.findIndex(item => item.name === downloader.name)
|
||||
downloaders.value[index] = downloader
|
||||
}
|
||||
|
||||
// 添加媒体服务器
|
||||
function addMediaServer(mediaserver: string) {
|
||||
let name = `服务器${mediaServers.value.length + 1}`;
|
||||
while (mediaServers.value.some(item => item.name === name)) {
|
||||
name = `服务器${parseInt(name.split('服务器')[1]) + 1}`;
|
||||
}
|
||||
mediaServers.value.push({
|
||||
name: name,
|
||||
type: mediaserver,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除媒体服务器
|
||||
function removeMediaServer(ele: MediaServerConf) {
|
||||
const index = mediaServers.value.indexOf(ele)
|
||||
mediaServers.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 变更媒体服务器
|
||||
function onMediaServerChange(mediaserver: MediaServerConf) {
|
||||
const index = mediaServers.value.findIndex(item => item.name === mediaserver.name)
|
||||
mediaServers.value[index] = mediaserver
|
||||
}
|
||||
// 预设部分PIP镜像站
|
||||
const pipMirrorsItems = [
|
||||
'https://pypi.tuna.tsinghua.edu.cn/simple', // 清华大学
|
||||
'https://pypi.mirrors.ustc.edu.cn/simple', // 中国科技大学
|
||||
'https://mirrors.pku.edu.cn/pypi/web/simple', // 北京大学
|
||||
'https://mirrors.aliyun.com/pypi/simple', // 阿里云
|
||||
'https://mirrors.cloud.tencent.com/pypi/simple', // 腾讯云
|
||||
'https://mirrors.163.com/pypi/simple', // 网易云
|
||||
'https://pypi.doubanio.com/simple', // 豆瓣
|
||||
'https://mirrors.hust.edu.cn/pypi/web/simple', // 华中理工大学
|
||||
'https://mirrors.bfsu.edu.cn/pypi/web/simple', // 北京外国语大学
|
||||
]
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDownloaderSetting()
|
||||
loadMediaServerSetting()
|
||||
loadSystemSettings()
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
isRequest.value = true
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
isRequest.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 系统设置 -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>系统</VCardTitle>
|
||||
<VCardSubtitle>设置服务使用的域名等基础信息。</VCardSubtitle>
|
||||
<VCardTitle>基础系统设置</VCardTitle>
|
||||
<VCardSubtitle>设置用户辅助认证、登录首页壁纸、访问域名、插件市场等基础化设置。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basis.AUXILIARY_AUTH_ENABLE"
|
||||
label="用户辅助认证"
|
||||
hint="允许通过外部服务,进行认证、单点登录以及自动创建用户"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basis.GLOBAL_IMAGE_CACHE"
|
||||
label="全局图片缓存"
|
||||
hint="将媒体图片缓存到本地,增强用户体验"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="isAdvancedSystemSettingsDialogOpen"
|
||||
label="高级系统设置"
|
||||
hint="进入高级系统设置页面"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSelect
|
||||
v-model="SystemSettings.Basis.WALLPAPER"
|
||||
label="登录首页壁纸"
|
||||
hint="选择登陆页面背景来源"
|
||||
persistent-hint
|
||||
:items="wallpaperItems"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.APP_DOMAIN"
|
||||
v-model="SystemSettings.Basis.APP_DOMAIN"
|
||||
label="访问域名"
|
||||
hint="用于通知跳转,格式:http(s)://domain:port"
|
||||
placeholder="格式:http(s)://domain:port"
|
||||
hint="用于发送通知时,添加快捷跳转地址"
|
||||
persistent-hint
|
||||
clearable
|
||||
:appendInnerIcon="SystemSettings.Basis.APP_DOMAIN ? 'mdi-content-copy' : ''"
|
||||
@click:appendInner="SystemSettings.Basis.APP_DOMAIN && copyValue(SystemSettings.Basis.APP_DOMAIN)"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basis.API_TOKEN"
|
||||
label="API Token"
|
||||
hint="不得低于16位,用于Jellyseerr/Overseerr、媒体服务器Webhook等配置以及部分支持API_TOKEN的API请求"
|
||||
persistent-hint
|
||||
clearable
|
||||
prependInnerIcon="mdi-reload"
|
||||
:appendInnerIcon="SystemSettings.Basis.API_TOKEN ? 'mdi-content-copy' : ''"
|
||||
@click:prependInner="createRandomString"
|
||||
@click:appendInner="SystemSettings.Basis.API_TOKEN && copyValue(SystemSettings.Basis.API_TOKEN)"
|
||||
:rules="[(v: string) => !!v || '必填项;请输入API Token', (v: string) => v.length >= 16 || 'API Token不得低于16位']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Basis.PLUGIN_MARKET"
|
||||
label="插件市场"
|
||||
placeholder="格式:https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/"
|
||||
hint="插件市场仓库地址,多个地址使用逗号分隔,确保每个地址以/结尾,仅支持Github仓库"
|
||||
persistent-hint
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -227,109 +274,162 @@ onDeactivated(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn mtype="submit" @click="saveSystemSetting"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>下载器</VCardTitle>
|
||||
<VCardSubtitle>只有默认下载器才会被默认使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="downloaders"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<DownloaderCard
|
||||
:downloader="element"
|
||||
:downloaders="downloaders"
|
||||
@close="removeDownloader(element)"
|
||||
@change="onDownloaderChange"
|
||||
:allow-refresh="isRequest"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn mtype="submit" @click="saveDownloaderSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addDownloader('qbittorrent')">
|
||||
<VListItemTitle>Qbittorrent</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addDownloader('transmission')">
|
||||
<VListItemTitle>Transmission</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
<VBtn type="submit" @click="saveSystemSettings"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>媒体服务器</VCardTitle>
|
||||
<VCardSubtitle>所有启用的媒体服务器都会被使用。</VCardSubtitle>
|
||||
<VCardTitle>基础网络设置</VCardTitle>
|
||||
<VCardSubtitle>设置DOH、PIP加速站、Github加速站、Github Token等,增加网络稳定性,保证连通性。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="mediaServers"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<MediaServerCard
|
||||
:mediaserver="element"
|
||||
:mediaservers="mediaServers"
|
||||
@close="removeMediaServer(element)"
|
||||
@change="onMediaServerChange"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6" class="flex align-center">
|
||||
<div>
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Network.DOH_ENABLE"
|
||||
label="DNS over HTTPS解析"
|
||||
hint="使用DOH服务器解析域名"
|
||||
persistent-hint
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<VAlert type="info" variant="tonal" class="whitespace-pre-line" style="inline-size: fit-content">
|
||||
<span v-if="SystemSettings.Network.PROXY_HOST"
|
||||
>当前已成功配置 PROXY_HOST ,建议关闭 DOH 功能。</span
|
||||
>
|
||||
<span v-else>暂未配置 PROXY_HOST,如出现网络连通性问题,可考虑开启 DOH 功能。 </span>
|
||||
</VAlert>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6" class="flex align-center">
|
||||
<div>
|
||||
<VSwitch
|
||||
v-model="isAdvancedNetworkSettingsDialogOpen"
|
||||
label="高级网络设置"
|
||||
hint="进入高级网络设置页面"
|
||||
persistent-hint
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<VAlert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="whitespace-pre-line mr-3"
|
||||
style="inline-size: fit-content"
|
||||
text="不建议修改,除非你知道它们的使用方法!"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.TMDB_API_DOMAIN"
|
||||
label="TMDB API域名"
|
||||
placeholder="格式:api.themoviedb.org"
|
||||
hint="可替换为自定义的API域名"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
:items="['api.themoviedb.org']"
|
||||
:rules="[(v: string) => !!v || '必填项;请输入TMDB API域名']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.TMDB_IMAGE_DOMAIN"
|
||||
label="TMDB 图片服务器"
|
||||
placeholder="格式:image.tmdb.org"
|
||||
hint="可替换为自定义的图片域名"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
:items="['image.tmdb.org', 'static-mdb.v.geilijiasu.com']"
|
||||
:rules="[(v: string) => !!v || '必填项;请输入TMDB API域名']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.GITHUB_PROXY"
|
||||
label="Github加速站"
|
||||
placeholder="格式:https://mirror.ghproxy.com/"
|
||||
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点。格式:https://mirror.ghproxy.com/ 末尾需要带 /"
|
||||
persistent-hint
|
||||
clearable
|
||||
:items="githubMirrorsItems"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Network.GITHUB_TOKEN"
|
||||
label="Github Token"
|
||||
placeholder="ghp_**** 或 github_pat_****"
|
||||
hint="用于提高Github API访问限流阈值"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
:appendInnerIcon="SystemSettings.Network.GITHUB_TOKEN ? 'mdi-content-copy' : ''"
|
||||
@click:appendInner="
|
||||
SystemSettings.Network.GITHUB_TOKEN && copyValue(SystemSettings.Network.GITHUB_TOKEN)
|
||||
"
|
||||
>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.PIP_PROXY"
|
||||
label="PIP加速站"
|
||||
placeholder="格式:https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点,格式: https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
persistent-hint
|
||||
clearable
|
||||
:items="pipMirrorsItems"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn mtype="submit" @click="saveMediaServerSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addMediaServer('emby')">
|
||||
<VListItemTitle>Emby</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('jellyfin')">
|
||||
<VListItemTitle>Jellyfin</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('plex')">
|
||||
<VListItemTitle>Plex</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
<VBtn type="submit" @click="saveNetworkSettings"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- 高级系统设置 -->
|
||||
<AdvancedSystemSettingsDialog
|
||||
v-if="isAdvancedSystemSettingsDialogOpen"
|
||||
v-model="isAdvancedSystemSettingsDialogOpen"
|
||||
max-width="60rem"
|
||||
persistent
|
||||
z-index="1010"
|
||||
:AdvancedSystemSettings="SystemSettings.Advanced"
|
||||
@close="isAdvancedSystemSettingsDialogOpen = false"
|
||||
@change="saveAdvancedSettings"
|
||||
/>
|
||||
|
||||
<!-- 高级网络设置 -->
|
||||
<AdvancedNetworkSettingsDialog
|
||||
v-if="isAdvancedNetworkSettingsDialogOpen"
|
||||
v-model="isAdvancedNetworkSettingsDialogOpen"
|
||||
max-width="60rem"
|
||||
persistent
|
||||
z-index="1010"
|
||||
:AdvancedNetworkSettings="SystemSettings.AdvancedNetwork"
|
||||
@close="isAdvancedNetworkSettingsDialogOpen = false"
|
||||
@change="saveAdvancedSettings"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user