feat(dashboard): refine monitoring, quick access and installation messages (#14256)

This commit is contained in:
ssongliu
2026-10-30 22:46:20 +08:00
committed by GitHub
parent 832d367428
commit c6329a62db
33 changed files with 211 additions and 182 deletions
+7
View File
@@ -18,6 +18,7 @@ type IAgentAccountRepo interface {
Save(account *model.AgentAccount) error
DeleteByID(id uint) error
List(opts ...DBOption) ([]model.AgentAccount, error)
Count() (int64, error)
CountTextByProviders(providers []string) (map[string]int64, error)
SaveLocal(account *model.AgentAccount, models []model.AgentAccountModel) error
}
@@ -69,6 +70,12 @@ func (a AgentAccountRepo) List(opts ...DBOption) ([]model.AgentAccount, error) {
return accounts, nil
}
func (a AgentAccountRepo) Count() (int64, error) {
var count int64
err := getDb().Model(&model.AgentAccount{}).Count(&count).Error
return count, err
}
func (a AgentAccountRepo) CountTextByProviders(providers []string) (map[string]int64, error) {
normalizedProviders := normalizeProviders(providers)
counts := make(map[string]int64, len(normalizedProviders))
+1 -1
View File
@@ -86,10 +86,10 @@ func (u *LauncherRepo) ListQuickJump(withAll bool) []model.QuickJump {
}
if !withAll && len(quicks) == 0 {
return []model.QuickJump{
{Name: "ModelServices", Title: "aiTools.model.model", Recommend: 0, IsShow: true, Router: "/ai/model"},
{Name: "Agent", Title: "aiTools.agents.agent", Recommend: 1, IsShow: true, Router: "/ai/agents/agent"},
{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"},
{Name: "Database", Title: "menu.database", Recommend: 30, IsShow: true, Router: "/databases"},
{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: false, Router: "/cronjobs"},
{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"},
}
}
+7 -1
View File
@@ -414,7 +414,7 @@ func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
if showCount == 0 {
return buserr.New("ErrMinQuickJump")
}
if showCount > 4 {
if showCount > 5 {
return buserr.New("ErrMaxQuickJump")
}
@@ -709,6 +709,12 @@ func loadQuickJump(base *dto.DashboardBase) {
quicks := launcherRepo.ListQuickJump(false)
for i := 0; i < len(quicks); i++ {
switch quicks[i].Name {
case "ModelServices":
count, err := agentAccountRepo.Count()
if err != nil {
global.LOG.Errorf("load model service count: %v", err)
}
quicks[i].Detail = fmt.Sprintf("%d", count)
case "File":
count, err := launcherRepo.CountQuickDirectories()
if err != nil {
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'Requested resource not found: {{ .err }}'
# quick jump
ErrMinQuickJump: 'Set at least one quick jump entry'
ErrMaxQuickJump: 'At most 4 quick jump entries'
ErrMaxQuickJump: 'At most 5 quick jump entries'
# common
ErrShutDown: 'Command stopped manually'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'The certificate file does not exist'
ErrSSLKeyFormat: 'Private key file verification failed'
ErrSSLCertificateFormat: 'Invalid certificate format; use PEM'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be blank'
ErrOpenrestyInstall: "Please install OpenResty from the App Store first"
ErrOpenrestyNotFound: 'HTTP mode requires OpenResty'
ApplySSLStart: 'Starting certificate request. Domain: [{{ .domain }}], method: [{{ .type }}]'
dnsAccount: 'DNS Auto'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'No se pudo encontrar el recurso solicitado {{ .err }}'
# salto rápido
ErrMinQuickJump: 'Agrega al menos una entrada rápida'
ErrMaxQuickJump: 'Máximo 4 entradas rápidas'
ErrMaxQuickJump: 'Máximo 5 entradas rápidas'
# común
ErrShutDown: 'Comando detenido manualmente'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'El archivo de certificado no existe'
ErrSSLKeyFormat: 'Error en la verificación del archivo de clave privada'
ErrSSLCertificateFormat: 'Formato de certificado incorrecto, use formato pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid o EabHmacKey no pueden estar en blanco'
ErrOpenrestyInstall: "Instale primero OpenResty desde la tienda de aplicaciones"
ErrOpenrestyNotFound: 'El modo HTTP requiere OpenResty'
ApplySSLStart: 'Iniciando solicitud de certificado, dominio [{{ .domain }}] método de solicitud [{{ .type }}]'
dnsAccount: 'DNS Automático'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'منبع درخواستی یافت نشد: {{ .err }}'
# پرش سریع
ErrMinQuickJump: 'حداقل یک ورودی پرش سریع تنظیم کنید'
ErrMaxQuickJump: 'حداکثر ۴ ورودی پرش سریع'
ErrMaxQuickJump: 'حداکثر ۵ ورودی پرش سریع'
# عمومی
ErrShutDown: 'دستور به صورت دستی متوقف شد'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'فایل گواهی وجود ندارد'
ErrSSLKeyFormat: 'تأیید فایل کلید خصوصی ناموفق بود'
ErrSSLCertificateFormat: 'فرمت گواهی نامعتبر است؛ از PEM استفاده کنید'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid یا EabHmacKey نمی‌توانند خالی باشند'
ErrOpenrestyInstall: "لطفاً ابتدا OpenResty را از فروشگاه برنامه‌ها نصب کنید"
ErrOpenrestyNotFound: 'حالت HTTP نیاز به OpenResty دارد'
ApplySSLStart: 'شروع درخواست گواهی. دامنه: [{{ .domain }}]، روش: [{{ .type }}]'
dnsAccount: 'خودکار DNS'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: '要求されたリソース {{ .err }} が見つかりま
# クイックジャンプ
ErrMinQuickJump: 'クイックアクセスを1件以上追加'
ErrMaxQuickJump: '最大4件のクイックアクセス'
ErrMaxQuickJump: '最大5件のクイックアクセス'
# 共通
ErrShutDown: 'コマンドは手動で停止されました'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '証明書ファイルが存在しません'
ErrSSLKeyFormat: '秘密鍵ファイルの検証に失敗しました'
ErrSSLCertificateFormat: '証明書ファイルの形式が正しくありません。pem 形式を使用してください'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid または EabHmacKey は空白にできません'
ErrOpenrestyInstall: "先にアプリストアから OpenResty をインストールしてください"
ErrOpenrestyNotFound: 'HTTP モードでは OpenResty が必要です'
ApplySSLStart: '証明書の申請を開始します。ドメイン名 [{{ .domain }}] 申請方法 [{{ .type }}]'
dnsAccount: 'DNS 自動'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: '요청한 리소스 {{ .err }}을 찾을 수 없습니다.'
# 빠른 이동
ErrMinQuickJump: '퀵 액세스를 하나 이상 추가하세요'
ErrMaxQuickJump: '퀵 액세스는 최대 4개'
ErrMaxQuickJump: '퀵 액세스는 최대 5개'
# 공통
ErrShutDown: '명령이 수동으로 중단되었습니다'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '인증서 파일이 존재하지 않습니다'
ErrSSLKeyFormat: '개인 키 파일 검증에 실패했습니다'
ErrSSLCertificateFormat: '인증서 파일 형식이 올바르지 않습니다. pem 형식을 사용하세요.'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 또는 EabHmacKey는 비워둘 수 없습니다'
ErrOpenrestyInstall: "먼저 앱 스토어에서 OpenResty를 설치하세요"
ErrOpenrestyNotFound: 'HTTP 모드에서는 OpenResty가 필요합니다'
ApplySSLStart: '인증서 신청 시작, 도메인 이름 [{{ .domain }}] 신청 방법 [{{ .type }}]'
dnsAccount: 'DNS 자동'
+2 -1
View File
@@ -32,7 +32,7 @@ ErrApiConfigKeyTimeInvalid: 'ເວລາ (Timestamp) ຂອງ API ບໍ່ຖ
ErrShutDown: 'ຄຳສັ່ງຖືກຢຸດດ້ວຍຕົນເອງ'
ErrMinQuickJump: 'ກະລຸນາຕັ້ງຄ່າລາຍການທາງລັດຢ່າງໜ້ອຍໜຶ່ງລາຍການ'
ErrMaxQuickJump: 'ສາມາດຕັ້ງຄ່າລາຍການທາງລັດໄດ້ສູງສຸດ 4 ລາຍການ'
ErrMaxQuickJump: 'ສາມາດຕັ້ງຄ່າລາຍການທາງລັດໄດ້ສູງສຸດ 5 ລາຍການ'
#common
ErrUsernameIsExist: 'ຊື່ຜູ້ໃຊ້ມີຢູ່ໃນລະບົບແລ້ວ'
@@ -242,6 +242,7 @@ ErrSSLCertificateNotFound: 'ບໍ່ມີໄຟລ໌ໃບຮັບຮອງ
ErrSSLKeyFormat: 'ການກວດສອບໄຟລ໌ກຸນແຈສ່ວນຕົວລົ້ມເຫຼວ'
ErrSSLCertificateFormat: 'ຮູບແບບໃບຮັບຮອງບໍ່ຖືກຕ້ອງ; ກະລຸນາໃຊ້ PEM'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid ຫຼື EabHmacKey ບໍ່ສາມາດປະຫວ່າງໄດ້'
ErrOpenrestyInstall: "ກະລຸນາຕິດຕັ້ງ OpenResty ຈາກຮ້ານແອັບກ່ອນ"
ErrOpenrestyNotFound: 'ໂໝດ HTTP ຕ້ອງການ OpenResty'
ApplySSLStart: 'ກຳລັງເລີ່ມການຮ້ອງຂໍໃບຮັບຮອງ. ໂດເມນ: [{{ .domain }}], ວິທີການ: [{{ .type }}]'
dnsAccount: 'DNS ອັດຕະໂນມັດ'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'Sumber yang diminta {{ .err }} tidak dapat ditemui'
# lompat pantas
ErrMinQuickJump: 'Tambah sekurang-kurangnya satu pintasan pantas'
ErrMaxQuickJump: 'Maksimum 4 pintasan pantas'
ErrMaxQuickJump: 'Maksimum 5 pintasan pantas'
# umum
ErrShutDown: 'Arahan diberhentikan manual'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'Fail sijil tidak wujud'
ErrSSLKeyFormat: 'Pengesahan fail kunci peribadi gagal'
ErrSSLCertificateFormat: 'Format fail sijil tidak betul, sila gunakan format pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid atau EabHmacKey tidak boleh kosong'
ErrOpenrestyInstall: "Sila pasang OpenResty daripada gedung aplikasi terlebih dahulu"
ErrOpenrestyNotFound: 'Mod HTTP memerlukan OpenResty dipasang terlebih dahulu'
ApplySSLStart: 'Mula memohon sijil, nama domain [{{ .domain }}] kaedah permohonan [{{ .type }}]'
dnsAccount: 'DNS automatik'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'O recurso solicitado {{ .err }} não pôde ser encontrado'
# atalho rápido
ErrMinQuickJump: 'Adicione pelo menos um atalho rápido'
ErrMaxQuickJump: 'Máximo 4 atalhos rápidos'
ErrMaxQuickJump: 'Máximo 5 atalhos rápidos'
# comum
ErrShutDown: 'Comando parado manualmente'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'O arquivo de certificado não existe'
ErrSSLKeyFormat: 'Falha na verificação do arquivo de chave privada'
ErrSSLCertificateFormat: 'O formato do arquivo de certificado está incorreto, use o formato pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid ou EabHmacKey não podem estar em branco'
ErrOpenrestyInstall: "Instale primeiro o OpenResty pela loja de aplicativos"
ErrOpenrestyNotFound: 'O modo HTTP requer o OpenResty instalado'
ApplySSLStart: 'Comece a solicitar um certificado, nome de domínio [{{ .domain }}] método de solicitação [{{ .type }}]'
dnsAccount: 'DNS Automático'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'Запрошенный ресурс {{ .err }} не най
# быстрый переход
ErrMinQuickJump: 'Добавьте хотя бы одну быструю ссылку'
ErrMaxQuickJump: 'Не более 4 быстрых ссылок'
ErrMaxQuickJump: 'Не более 5 быстрых ссылок'
# общее
ErrShutDown: 'Команда остановлена вручную'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'Файл сертификата не существ
ErrSSLKeyFormat: 'Проверка файла закрытого ключа не удалась'
ErrSSLCertificateFormat: 'Неверный формат файла сертификата, используйте формат pem'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid или EabHmacKey не могут быть пустыми'
ErrOpenrestyInstall: "Сначала установите OpenResty из магазина приложений"
ErrOpenrestyNotFound: 'Для режима HTTP требуется OpenResty'
ApplySSLStart: 'Начать подачу заявки на сертификат, доменное имя [{{ .domain }}], метод подачи заявки [{{ .type }}]'
dnsAccount: 'DNS Авто'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
# hızlı geçiş
ErrMinQuickJump: 'En az bir hızlı erişim ekle'
ErrMaxQuickJump: '4 hızlı erişim hakkı var'
ErrMaxQuickJump: '5 hızlı erişim hakkı var'
# genel
ErrShutDown: 'Komut manuel durduruldu'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: 'Sertifika dosyası mevcut değil'
ErrSSLKeyFormat: 'Özel anahtar dosyası doğrulama başarısız'
ErrSSLCertificateFormat: 'Sertifika dosyası formatı yanlış, lütfen pem formatını kullanın'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid veya EabHmacKey boş olamaz'
ErrOpenrestyInstall: "Lütfen önce uygulama mağazasından OpenResty yükleyin"
ErrOpenrestyNotFound: 'HTTP modu için OpenResty gereklidir'
ApplySSLStart: 'Sertifika başvurusu başlatılıyor, alan adı [{{ .domain }}] başvuru yöntemi [{{ .type }}]'
dnsAccount: 'DNS Otomatik'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: '無法找到要求的資源{{ .err }}'
# 快速跳轉
ErrMinQuickJump: '請至少設定一個快速跳轉入口!'
ErrMaxQuickJump: '最多可設定四個快速跳轉入口!'
ErrMaxQuickJump: '最多可設定五個快速跳轉入口!'
# 通用
ErrShutDown: '指令被手動結束!'
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '憑證檔案不存在'
ErrSSLKeyFormat: '私鑰檔案驗證失敗'
ErrSSLCertificateFormat: '憑證檔案格式錯誤,請使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不可為空'
ErrOpenrestyInstall: "請先在應用程式商店安裝 OpenResty"
ErrOpenrestyNotFound: 'HTTP 模式需要先安裝 OpenResty'
ApplySSLStart: '開始申請憑證,網域名稱[{{ .domain }}] 申請方式[{{ .type }}]'
dnsAccount: 'DNS 自動'
+2 -1
View File
@@ -46,7 +46,7 @@ ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}"
# 快速跳转
ErrMinQuickJump: "至少设置 1 个快速跳转入口"
ErrMaxQuickJump: "最多设置 4 个快速跳转入口"
ErrMaxQuickJump: "最多设置 5 个快速跳转入口"
# 通用
ErrShutDown: "命令已手动终止"
@@ -252,6 +252,7 @@ ErrSSLCertificateNotFound: '证书文件不存在'
ErrSSLKeyFormat: '私钥文件校验失败'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空'
ErrOpenrestyInstall: "请先在应用商店安装 OpenResty"
ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty'
ApplySSLStart: '开始申请证书,域名 [{{ .domain }}],方式 [{{ .type }}]'
dnsAccount: "DNS 自动"
+1
View File
@@ -119,6 +119,7 @@ func agentDBMigrations() []*gormigrate.Migration {
migrations.AddQuickDirectory,
migrations.AddLocalModelAccountLink,
migrations.AddOllamaModelAccountSync,
migrations.AddModelQuickJump,
}
}
+13
View File
@@ -1939,3 +1939,16 @@ var AddOllamaModelAccountSync = &gormigrate.Migration{
return nil
},
}
var AddModelQuickJump = &gormigrate.Migration{
ID: "20261030-add-model-quick-jump",
Migrate: func(tx *gorm.DB) error {
return tx.Where("name = ?", "ModelServices").FirstOrCreate(&model.QuickJump{
Name: "ModelServices",
Title: "aiTools.model.model",
Recommend: 0,
IsShow: true,
Router: "/ai/model",
}).Error
},
}
+2 -1
View File
@@ -498,7 +498,7 @@ const message = {
quickDirDelete: 'Delete ({0})?',
quickDir: 'Quick Access',
minQuickJump: 'Add at least one quick access entry',
maxQuickJump: 'You can add up to four quick access entries',
maxQuickJump: 'You can add up to five quick access entries',
database: 'Database - All',
restart_1panel: 'Restart panel',
restart_system: 'Restart server',
@@ -1757,6 +1757,7 @@ const message = {
migModeNA: 'Not Supported',
current: 'Real-time Status',
history: 'Monitoring Data',
viewHistory: 'View monitoring data',
notSupport: 'The current version or driver does not support displaying this parameter.',
},
mcp: {
+2 -1
View File
@@ -497,7 +497,7 @@ const message = {
quickDirDelete: '¿Confirmar la eliminación de ({0})?',
quickDir: 'Acceso rápido',
minQuickJump: 'Agrega al menos una entrada de acceso rápido',
maxQuickJump: 'Puedes agregar hasta cuatro entradas de acceso rápido',
maxQuickJump: 'Puedes agregar hasta cinco entradas de acceso rápido',
database: 'Database - All',
restart_1panel: 'Reiniciar panel',
restart_system: 'Reiniciar servidor',
@@ -1769,6 +1769,7 @@ const message = {
migModeNA: 'No Compatible',
current: 'Estado en tiempo real',
history: 'Datos de monitoreo',
viewHistory: 'Ver datos de monitoreo',
notSupport: 'La versión actual o el controlador no admiten mostrar este parámetro.',
processCount: 'Procesos',
},
+2 -1
View File
@@ -493,7 +493,7 @@ const message = {
quickDirDelete: 'حذف ({0}) را تأیید می‌کنید؟',
quickDir: 'دسترسی سریع',
minQuickJump: 'حداقل یک ورودی دسترسی سریع اضافه کنید',
maxQuickJump: 'می‌توانید حداکثر چهار ورودی دسترسی سریع اضافه کنید',
maxQuickJump: 'می‌توانید حداکثر پنج ورودی دسترسی سریع اضافه کنید',
database: 'پایگاه داده - همه',
restart_1panel: 'راه‌اندازی مجدد پنل',
restart_system: 'راه‌اندازی مجدد سرور',
@@ -1739,6 +1739,7 @@ const message = {
migModeNA: 'پشتیبانی نمی‌شود',
current: 'وضعیت بلادرنگ',
history: 'داده‌های نظارتی',
viewHistory: 'مشاهده داده‌های نظارتی',
notSupport: 'نسخه فعلی یا درایور از نمایش این پارامتر پشتیبانی نمی‌کند.',
},
mcp: {
+2 -1
View File
@@ -496,7 +496,7 @@ const message = {
quickDirDelete: '({0})を削除しますか?',
quickDir: 'クイックアクセス',
minQuickJump: 'クイックアクセス項目を1つ以上追加してください',
maxQuickJump: '最大4件のクイックアクセス項目を追加できます',
maxQuickJump: '最大5件のクイックアクセス項目を追加できます',
database: 'データベース - すべて',
restart_1panel: 'パネルを再起動します',
restart_system: 'サーバーを再起動します',
@@ -1739,6 +1739,7 @@ const message = {
migModeNA: 'サポートされていません',
current: 'リアルタイム状態',
history: '監視データ',
viewHistory: '監視データを表示',
notSupport: '現在のバージョンまたはドライバーはこのパラメータの表示をサポートしていません。',
processCount: 'プロセス数',
},
+2 -1
View File
@@ -491,7 +491,7 @@ const message = {
quickDirDelete: '({0})을(를) 삭제하시겠습니까?',
quickDir: '빠른 액세스',
minQuickJump: '최소 하나의 빠른 액세스 항목을 추가하세요',
maxQuickJump: '빠른 액세스를 최대 4개까지 추가할 수 있습니다',
maxQuickJump: '빠른 액세스를 최대 5개까지 추가할 수 있습니다',
database: '데이터베이스 - 전체',
restart_1panel: '패널 재시작',
restart_system: '서버 재시작',
@@ -1724,6 +1724,7 @@ const message = {
migModeNA: '지원되지 않음',
current: '실시간 상태',
history: '모니터링 데이터',
viewHistory: '모니터링 데이터 보기',
notSupport: '현재 버전 또는 드라이버는 이 매개변수 표시를 지원하지 않습니다.',
processCount: '프로세스 수',
},
+2 -1
View File
@@ -492,7 +492,7 @@ const message = {
quickDirDelete: 'ຢືນຢັນການລຶບ ({0}) ຫຼືບໍ່?',
quickDir: 'ເຂົ້າເຖິງດ່ວນ',
minQuickJump: 'ເພີ່ມການເຂົ້າເຖິງດ່ວນຢ່າງໜ້ອຍໜຶ່ງລາຍການ',
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດສີ່ລາຍການ',
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດຫ້າລາຍການ',
database: 'ຖານຂໍ້ມູນ - ທັງໝົດ',
restart_1panel: 'ຣີສະຕາດແພນເນວ',
restart_system: 'ຣີສະຕາດເຊີເວີ',
@@ -1716,6 +1716,7 @@ const message = {
migModeNA: 'ບໍ່ຮອງຮັບ',
current: 'ສະຖານະແບບທັນທີ',
history: 'ຂໍ້ມູນການຕິດຕາມ',
viewHistory: 'ເບິ່ງຂໍ້ມູນການຕິດຕາມ',
notSupport: 'ເວີຊັນ ຫຼື driver ປັດຈຸບັນບໍ່ຮອງຮັບການສະແດງຄ່ານີ້.',
},
mcp: {
+2 -1
View File
@@ -503,7 +503,7 @@ const message = {
quickDirDelete: 'Sahkan pemadaman ({0})?',
quickDir: 'Akses Pantas',
minQuickJump: 'Tambah sekurang-kurangnya satu entri akses pantas',
maxQuickJump: 'Anda boleh menambah sehingga empat entri akses pantas',
maxQuickJump: 'Anda boleh menambah sehingga lima entri akses pantas',
database: 'Pangkalan Data - Semua',
restart_1panel: 'Mulakan semula panel',
restart_system: 'Mulakan semula pelayan',
@@ -1770,6 +1770,7 @@ const message = {
migModeNA: 'Tidak Disokong',
current: 'Status Masa Nyata',
history: 'Data Pemantauan',
viewHistory: 'Lihat data pemantauan',
notSupport: 'Versi atau pemacu semasa tidak menyokong paparan parameter ini.',
processCount: 'Bilangan proses',
},
+2 -1
View File
@@ -496,7 +496,7 @@ const message = {
quickDirDelete: 'Confirmar a exclusão de ({0})?',
quickDir: 'Acesso rápido',
minQuickJump: 'Adicione pelo menos uma entrada de acesso rápido',
maxQuickJump: 'Você pode adicionar até quatro entradas de acesso rápido',
maxQuickJump: 'Você pode adicionar até cinco entradas de acesso rápido',
database: 'Banco de Dados - Todos',
restart_1panel: 'Reiniciar painel',
restart_system: 'Reiniciar servidor',
@@ -1770,6 +1770,7 @@ const message = {
migModeNA: 'Não Suportado',
current: 'Status em tempo real',
history: 'Dados de monitoramento',
viewHistory: 'Ver dados de monitoramento',
notSupport: 'A versão atual ou o driver não suportam exibir este parâmetro.',
processCount: 'Qtd. de processos',
},
+2 -1
View File
@@ -494,7 +494,7 @@ const message = {
quickDirDelete: 'Подтвердить удаление ({0})?',
quickDir: 'Быстрый доступ',
minQuickJump: 'Добавьте хотя бы одну запись быстрого доступа',
maxQuickJump: 'Вы можете добавить до четырех записей быстрого доступа',
maxQuickJump: 'Вы можете добавить до пяти записей быстрого доступа',
database: 'База данных - Все',
restart_1panel: 'Перезапустить панель',
restart_system: 'Перезапустить сервер',
@@ -1759,6 +1759,7 @@ const message = {
migModeNA: 'Не Поддерживается',
current: 'Состояние в реальном времени',
history: 'Данные мониторинга',
viewHistory: 'Просмотреть данные мониторинга',
notSupport: 'Текущая версия или драйвер не поддерживает отображение этого параметра.',
processCount: 'Кол-во процессов',
},
+2 -1
View File
@@ -499,7 +499,7 @@ const message = {
quickDirDelete: '({0}) silinsin mi?',
quickDir: 'Hızlı Erişim',
minQuickJump: 'En az bir hızlı erişim girdisi ekleyin',
maxQuickJump: 'En fazla dört hızlı erişim girdisi ekleyebilirsiniz',
maxQuickJump: 'En fazla beş hızlı erişim girdisi ekleyebilirsiniz',
database: 'Veritabanı - Tümü',
restart_1panel: 'Paneli yeniden başlat',
restart_system: 'Sunucuyu yeniden başlat',
@@ -1766,6 +1766,7 @@ const message = {
migModeNA: 'Desteklenmiyor',
current: 'Gerçek Zamanlı Durum',
history: 'İzleme Verileri',
viewHistory: 'İzleme verilerini görüntüle',
notSupport: 'Mevcut sürüm veya sürücü bu parametrenin görüntülenmesini desteklemiyor.',
processCount: 'Süreç sayısı',
},
+2 -1
View File
@@ -477,7 +477,7 @@ const message = {
quickDirDelete: '確定刪除({0})',
quickDir: '快捷目錄',
minQuickJump: '請至少設定一個快速跳轉入口',
maxQuickJump: '最多可設定四個快速跳轉入口',
maxQuickJump: '最多可設定五個快速跳轉入口',
database: '資料庫 - 全部',
restart_1panel: '重新啟動面板',
restart_system: '重新啟動伺服器',
@@ -1664,6 +1664,7 @@ const message = {
migModeNA: '不支援',
current: '即時狀態',
history: '監控資料',
viewHistory: '查看監控資料',
notSupport: '當前版本或驅動不支援顯示該參數。',
processCount: '行程數',
},
+2 -1
View File
@@ -477,7 +477,7 @@ const message = {
quickDirDelete: '确定删除({0})',
quickDir: '快捷目录',
minQuickJump: '请至少设置一个快速跳转入口!',
maxQuickJump: '最多可设置四个快速跳转入口!',
maxQuickJump: '最多可设置五个快速跳转入口!',
database: '数据库 - 所有',
restart_1panel: '重启面板',
restart_system: '重启服务器',
@@ -1679,6 +1679,7 @@ const message = {
migModeNA: '不支持',
current: '实时状态',
history: '监控数据',
viewHistory: '查看监控数据',
notSupport: '当前版本或驱动不支持显示该参数。',
},
mcp: {
+126 -152
View File
@@ -47,7 +47,7 @@
<template #body>
<div class="h-overview">
<el-row>
<el-col :span="6" v-for="item in baseInfo.quickJump" :key="item.name">
<el-col class="overview-item" v-for="item in baseInfo.quickJump" :key="item.name">
<span>{{ $t(item.title, 2) }}</span>
<div class="count">
<QuickDirectories
@@ -86,92 +86,6 @@
/>
</template>
</CardWithHeader>
<CardWithHeader
:header="$t('menu.monitor')"
class="card-interval chart-card"
@mouseenter="refreshOptionsOnHover"
>
<template #header-r>
<el-radio-group
style="float: right; margin-left: 5px"
v-model="chartOption"
@change="changeOption"
>
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
</el-radio-group>
<el-select
v-if="chartOption === 'network'"
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
v-for="item in netOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
<el-select
v-if="chartOption === 'io'"
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
v-for="item in ioOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</template>
<template #body>
<div class="monitor-chart-content">
<div class="monitor-tags" :style="monitorTagsStyle" v-if="chartOption === 'network'">
<el-tag>
{{ $t('monitor.up') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesSent) }}
</el-tag>
<el-tag>
{{ $t('monitor.down') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesRecv) }}
</el-tag>
<el-tag>{{ $t('home.totalSend') }}: {{ computeSize(currentInfo.netBytesSent) }}</el-tag>
<el-tag>{{ $t('home.totalRecv') }}: {{ computeSize(currentInfo.netBytesRecv) }}</el-tag>
</div>
<div class="monitor-tags" :style="monitorTagsStyle" v-if="chartOption === 'io'">
<el-tag>{{ $t('monitor.read') }}: {{ currentChartInfo.ioReadBytes }} MB</el-tag>
<el-tag>{{ $t('monitor.write') }}: {{ currentChartInfo.ioWriteBytes }} MB</el-tag>
<el-tag>
{{ $t('home.rwPerSecond') }}: {{ currentChartInfo.ioCount }}
{{ $t('commons.units.time') }}/s
</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentChartInfo.ioTime }} ms</el-tag>
</div>
<div v-if="chartOption === 'io'" class="mobile-monitor-chart">
<v-charts
height="383px"
id="ioChart"
type="line"
:option="chartsOption['ioChart']"
:dataZoom="true"
/>
</div>
<div v-if="chartOption === 'network'" class="mobile-monitor-chart">
<v-charts
height="383px"
id="networkChart"
type="line"
:option="chartsOption['networkChart']"
:dataZoom="true"
/>
</div>
</div>
</template>
</CardWithHeader>
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8" class="dashboard-right">
<el-carousel
@@ -438,6 +352,82 @@
</el-col>
</el-row>
<CardWithHeader :header="$t('menu.monitor')" class="card-interval" @mouseenter="refreshOptionsOnHover">
<template #body>
<div class="monitor-grid">
<section class="monitor-panel">
<div class="monitor-panel-header">
<span>{{ $t('home.network') }}</span>
<el-select
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
class="monitor-select"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
v-for="item in netOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</div>
<div class="monitor-tags">
<el-tag>
{{ $t('monitor.up') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesSent) }}
</el-tag>
<el-tag>
{{ $t('monitor.down') }}: {{ computeSizeFromKBs(currentChartInfo.netBytesRecv) }}
</el-tag>
<el-tag>{{ $t('home.totalSend') }}: {{ computeSize(currentInfo.netBytesSent) }}</el-tag>
<el-tag>{{ $t('home.totalRecv') }}: {{ computeSize(currentInfo.netBytesRecv) }}</el-tag>
</div>
<v-charts
height="383px"
id="networkChart"
type="line"
:option="chartsOption['networkChart']"
:dataZoom="true"
/>
</section>
<section class="monitor-panel">
<div class="monitor-panel-header">
<span>{{ $t('home.io') }}</span>
<el-select
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
class="monitor-select"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
v-for="item in ioOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</div>
<div class="monitor-tags">
<el-tag>{{ $t('monitor.read') }}: {{ currentChartInfo.ioReadBytes }} MB</el-tag>
<el-tag>{{ $t('monitor.write') }}: {{ currentChartInfo.ioWriteBytes }} MB</el-tag>
<el-tag>
{{ $t('home.rwPerSecond') }}: {{ currentChartInfo.ioCount }}
{{ $t('commons.units.time') }}/s
</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentChartInfo.ioTime }} ms</el-tag>
</div>
<v-charts
height="383px"
id="ioChart"
type="line"
:option="chartsOption['ioChart']"
:dataZoom="true"
/>
</section>
</div>
</template>
</CardWithHeader>
<QuickJump @search="onLoadBaseInfo(false, 'all')" ref="quickJumpRef" />
<DialogPro v-model="welcomeOpen" size="w-70" id="welcomeDialog">
@@ -501,12 +491,8 @@ const DASHBOARD_CACHE_TTL = {
netOptions: 60 * 60 * 1000,
ioOptions: 60 * 60 * 1000,
};
const monitorChartGrid = { left: 65, right: 65, bottom: '20%' };
const monitorChartGrid = { left: 55, right: 25, bottom: '20%' };
const monitorChartLegend = { top: 0, bottom: 'auto' };
const monitorTagsStyle = {
left: `${monitorChartGrid.left}px`,
right: `${monitorChartGrid.right}px`,
};
const monitorChartEmptyLength = 20;
const loadMonitorEmptyData = () => Array.from({ length: monitorChartEmptyLength }, () => null);
const loadMonitorEmptyTime = () => Array.from({ length: monitorChartEmptyLength }, () => '');
@@ -553,11 +539,9 @@ const isSafety = ref();
const welcomeOpen = ref();
const shadowContainer = ref();
const chartOption = ref('network');
const statusOption = ref(localStorage.getItem('dashboard_status_view') === 'gpu' ? 'gpu' : 'system');
let timer: ReturnType<typeof setInterval> | null = null;
let initialSampleTimer: ReturnType<typeof setTimeout> | null = null;
let isInit = ref<boolean>(true);
let isStatusInit = ref<boolean>(true);
let isActive = ref(true);
let isCurrentActive = ref(true);
@@ -587,6 +571,7 @@ const quickJumpPermissionMap = Object.fromEntries(
['Cronjob', 'cronjob_view'],
['AppInstalled', 'app_view'],
['File', 'host_file_view'],
['ModelServices', 'ai_model_view'],
].map(([name, permission]) => [name, useCan(permission)]),
) as Record<string, ReturnType<typeof useCan>>;
@@ -723,11 +708,6 @@ const updateCurrentInfo = (data: Dashboard.CurrentInfo) => {
};
};
const changeOption = async () => {
isInit.value = true;
loadData();
};
const applyDefaultNetOption = () => {
if (!netOptions.value || netOptions.value.length === 0) return;
const defaultNet = defaultNetwork.value || netOptions.value[0];
@@ -1032,12 +1012,9 @@ const saveMemo = async () => {
}
};
const loadData = async () => {
if (chartOption.value === 'io') {
chartsOption.value['ioChart'] = loadIOChartOption();
} else {
chartsOption.value['networkChart'] = loadNetworkChartOption();
}
const loadData = () => {
chartsOption.value['ioChart'] = loadIOChartOption();
chartsOption.value['networkChart'] = loadNetworkChartOption();
};
const hideEntrance = () => {
@@ -1205,15 +1182,6 @@ onBeforeUnmount(() => {
font-size: 12px;
}
.monitor-chart-content {
position: relative;
margin-top: 60px;
}
.mobile-monitor-chart {
margin-top: 40px;
}
@media only screen and (max-width: 767px) {
.dashboard-entrance-alert {
padding-right: 40px;
@@ -1255,6 +1223,11 @@ onBeforeUnmount(() => {
.h-overview {
text-align: center;
.overview-item {
flex: 1;
min-width: 0;
}
span:first-child {
font-size: 14px;
color: var(--el-text-color-regular);
@@ -1355,49 +1328,54 @@ onBeforeUnmount(() => {
border-top: var(--panel-border);
}
.monitor-tags {
position: absolute;
top: -10px;
.monitor-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
}
.monitor-panel {
min-width: 0;
}
.monitor-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
gap: 8px;
margin-bottom: 12px;
font-weight: 500;
}
@media only screen and (max-width: 1024px) {
.monitor-chart-content {
margin-top: 20px;
}
.monitor-select {
width: 200px;
max-width: 100%;
}
.monitor-chart-content .monitor-tags {
position: static;
display: grid;
.monitor-tags {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-bottom: 16px;
:deep(.el-tag) {
width: 100%;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-bottom: 12px;
:deep(.el-tag) {
width: 100%;
max-width: 100%;
height: auto;
min-height: 24px;
justify-content: center;
padding-block: 3px;
line-height: 1.3;
text-align: center;
white-space: normal;
overflow-wrap: anywhere;
}
}
.mobile-monitor-chart {
margin-top: 0;
max-width: 100%;
height: auto;
min-height: 24px;
justify-content: center;
padding-block: 3px;
line-height: 1.3;
text-align: center;
white-space: normal;
overflow-wrap: anywhere;
}
}
@media only screen and (min-width: 768px) and (max-width: 1024px) {
.monitor-chart-content .monitor-tags {
grid-template-columns: repeat(4, minmax(0, 1fr));
@media only screen and (max-width: 991px) {
.monitor-grid {
grid-template-columns: minmax(0, 1fr);
}
}
@@ -1420,10 +1398,6 @@ onBeforeUnmount(() => {
}
}
.chart-card {
min-height: 383px;
}
.memo-container {
height: 270px;
}
+1 -1
View File
@@ -59,7 +59,7 @@ const onChangeShow = async () => {
MsgInfo(i18n.global.t('home.minQuickJump'));
return;
}
if (i > 4) {
if (i > 5) {
MsgInfo(i18n.global.t('home.maxQuickJump'));
return;
}
+1 -1
View File
@@ -16,7 +16,7 @@
</el-icon>
</el-button>
<el-button link type="primary" size="small" @click="$emit('open')">
{{ $t('aiTools.gpu.gpu') }}
{{ $t('aiTools.gpu.viewHistory') }}
<el-icon><ArrowRight /></el-icon>
</el-button>
</div>
+6 -2
View File
@@ -318,7 +318,11 @@
<span class="input-help">{{ computeSize(item.used) }} / {{ computeSize(item.total) }}</span>
</el-col>
</template>
<GPUStatus v-if="view === 'gpu'" :current="currentInfo" @open="routerToName('GPU')" />
<GPUStatus
v-if="view === 'gpu'"
:current="currentInfo"
@open="routerToNameWithQuery('GPUHistory', { uncached: 'true' })"
/>
<el-col
:xs="6"
:sm="6"
@@ -348,7 +352,7 @@ import i18n from '@/lang';
import { nextTick, onBeforeUnmount, ref, watch } from 'vue';
import GPUStatus from './gpu.vue';
import { useMediaQuery } from '@vueuse/core';
import { routerToFileWithPath, routerToName } from '@/utils/router';
import { routerToFileWithPath, routerToNameWithQuery } from '@/utils/router';
import { stopProcess } from '@/api/modules/process';
import { loadTopCPU, loadTopMem } from '@/api/modules/dashboard';
import { MsgSuccess } from '@/utils/message';