feat: 病毒扫描增加病毒库刷新状态 (#5710)

This commit is contained in:
ssongliu 2024-07-08 15:47:23 +08:00 committed by GitHub
parent 0c2653d270
commit 577dfadb9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 400 additions and 18 deletions

View File

@ -190,12 +190,12 @@ func (b *BaseApi) LoadClamRecordLog(c *gin.Context) {
// @Summary Load clam file // @Summary Load clam file
// @Description 获取扫描文件 // @Description 获取扫描文件
// @Accept json // @Accept json
// @Param request body dto.OperationWithName true "request" // @Param request body dto.ClamFileReq true "request"
// @Success 200 {object} dto.PageResult // @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /toolbox/clam/file/search [post] // @Router /toolbox/clam/file/search [post]
func (b *BaseApi) SearchClamFile(c *gin.Context) { func (b *BaseApi) SearchClamFile(c *gin.Context) {
var req dto.OperationWithName var req dto.ClamFileReq
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }

View File

@ -8,6 +8,10 @@ type ClamBaseInfo struct {
Version string `json:"version"` Version string `json:"version"`
IsActive bool `json:"isActive"` IsActive bool `json:"isActive"`
IsExist bool `json:"isExist"` IsExist bool `json:"isExist"`
FreshVersion string `json:"freshVersion"`
FreshIsActive bool `json:"freshIsActive"`
FreshIsExist bool `json:"freshIsExist"`
} }
type ClamInfo struct { type ClamInfo struct {
@ -36,6 +40,11 @@ type ClamLogReq struct {
RecordName string `json:"recordName"` RecordName string `json:"recordName"`
} }
type ClamFileReq struct {
Tail string `json:"tail"`
Name string `json:"name" validate:"required"`
}
type ClamLog struct { type ClamLog struct {
Name string `json:"name"` Name string `json:"name"`
ScanDate string `json:"scanDate"` ScanDate string `json:"scanDate"`

View File

@ -26,6 +26,7 @@ import (
const ( const (
clamServiceNameCentOs = "clamd@scan.service" clamServiceNameCentOs = "clamd@scan.service"
clamServiceNameUbuntu = "clamav-daemon.service" clamServiceNameUbuntu = "clamav-daemon.service"
freshClamService = "clamav-freshclam.service"
resultDir = "clamav" resultDir = "clamav"
) )
@ -41,7 +42,7 @@ type IClamService interface {
Update(req dto.ClamUpdate) error Update(req dto.ClamUpdate) error
Delete(req dto.ClamDelete) error Delete(req dto.ClamDelete) error
HandleOnce(req dto.OperateByID) error HandleOnce(req dto.OperateByID) error
LoadFile(req dto.OperationWithName) (string, error) LoadFile(req dto.ClamFileReq) (string, error)
UpdateFile(req dto.UpdateByNameAndFile) error UpdateFile(req dto.UpdateByNameAndFile) error
LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error) LoadRecords(req dto.ClamLogSearch) (int64, interface{}, error)
CleanRecord(req dto.OperateByID) error CleanRecord(req dto.OperateByID) error
@ -56,6 +57,7 @@ func NewIClamService() IClamService {
func (f *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) { func (f *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
var baseInfo dto.ClamBaseInfo var baseInfo dto.ClamBaseInfo
baseInfo.Version = "-" baseInfo.Version = "-"
baseInfo.FreshVersion = "-"
exist1, _ := systemctl.IsExist(clamServiceNameCentOs) exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
if exist1 { if exist1 {
f.serviceName = clamServiceNameCentOs f.serviceName = clamServiceNameCentOs
@ -68,6 +70,11 @@ func (f *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
baseInfo.IsExist = true baseInfo.IsExist = true
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu) baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
} }
freshExist, _ := systemctl.IsExist(freshClamService)
if freshExist {
baseInfo.FreshIsExist = true
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
}
if baseInfo.IsActive { if baseInfo.IsActive {
version, err := cmd.Exec("clamdscan --version") version, err := cmd.Exec("clamdscan --version")
@ -80,6 +87,17 @@ func (f *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
baseInfo.Version = strings.TrimPrefix(version, "ClamAV ") baseInfo.Version = strings.TrimPrefix(version, "ClamAV ")
} }
} }
if baseInfo.FreshIsActive {
version, err := cmd.Exec("freshclam --version")
if err != nil {
return baseInfo, nil
}
if strings.Contains(version, "/") {
baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ")
} else {
baseInfo.FreshVersion = strings.TrimPrefix(version, "ClamAV ")
}
}
return baseInfo, nil return baseInfo, nil
} }
@ -91,6 +109,12 @@ func (f *ClamService) Operate(operate string) error {
return fmt.Errorf("%s the %s failed, err: %s", operate, f.serviceName, stdout) return fmt.Errorf("%s the %s failed, err: %s", operate, f.serviceName, stdout)
} }
return nil return nil
case "fresh-start", "fresh-restart", "fresh-stop":
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
if err != nil {
return fmt.Errorf("%s the %s failed, err: %s", operate, f.serviceName, stdout)
}
return nil
default: default:
return fmt.Errorf("not support such operation: %v", operate) return fmt.Errorf("not support such operation: %v", operate)
} }
@ -296,7 +320,7 @@ func (u *ClamService) CleanRecord(req dto.OperateByID) error {
return nil return nil
} }
func (u *ClamService) LoadFile(req dto.OperationWithName) (string, error) { func (u *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
filePath := "" filePath := ""
switch req.Name { switch req.Name {
case "clamd": case "clamd":
@ -306,6 +330,10 @@ func (u *ClamService) LoadFile(req dto.OperationWithName) (string, error) {
filePath = "/etc/clamd.d/scan.conf" filePath = "/etc/clamd.d/scan.conf"
} }
case "clamd-log": case "clamd-log":
filePath = u.loadLogPath("clamd-log")
if len(filePath) != 0 {
break
}
if u.serviceName == clamServiceNameUbuntu { if u.serviceName == clamServiceNameUbuntu {
filePath = "/var/log/clamav/clamav.log" filePath = "/var/log/clamav/clamav.log"
} else { } else {
@ -318,6 +346,10 @@ func (u *ClamService) LoadFile(req dto.OperationWithName) (string, error) {
filePath = "/etc/freshclam.conf" filePath = "/etc/freshclam.conf"
} }
case "freshclam-log": case "freshclam-log":
filePath = u.loadLogPath("freshclam-log")
if len(filePath) != 0 {
break
}
if u.serviceName == clamServiceNameUbuntu { if u.serviceName == clamServiceNameUbuntu {
filePath = "/var/log/clamav/freshclam.log" filePath = "/var/log/clamav/freshclam.log"
} else { } else {
@ -329,11 +361,18 @@ func (u *ClamService) LoadFile(req dto.OperationWithName) (string, error) {
if _, err := os.Stat(filePath); err != nil { if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound") return "", buserr.New("ErrHttpReqNotFound")
} }
content, err := os.ReadFile(filePath) var tail string
if err != nil { if req.Tail != "0" {
return "", err tail = req.Tail
} else {
tail = "+1"
} }
return string(content), nil cmd := exec.Command("tail", "-n", tail, filePath)
stdout, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
}
return string(stdout), nil
} }
func (u *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error { func (u *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
@ -419,3 +458,42 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
} }
return data return data
} }
func (u *ClamService) loadLogPath(name string) string {
confPath := ""
if name == "clamd-log" {
if u.serviceName == clamServiceNameUbuntu {
confPath = "/etc/clamav/clamd.conf"
} else {
confPath = "/etc/clamd.d/scan.conf"
}
} else {
if u.serviceName == clamServiceNameUbuntu {
confPath = "/etc/clamav/freshclam.conf"
} else {
confPath = "/etc/freshclam.conf"
}
}
if _, err := os.Stat(confPath); err != nil {
return ""
}
content, err := os.ReadFile(confPath)
if err != nil {
return ""
}
lines := strings.Split(string(content), "\n")
if name == "clamd-log" {
for _, line := range lines {
if strings.HasPrefix(line, "LogFile ") {
return strings.Trim(strings.ReplaceAll(line, "LogFile ", ""), " ")
}
}
} else {
for _, line := range lines {
if strings.HasPrefix(line, "UpdateLogFile ") {
return strings.Trim(strings.ReplaceAll(line, "UpdateLogFile ", ""), " ")
}
}
}
return ""
}

View File

@ -11212,7 +11212,7 @@ const docTemplate = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.OperationWithName" "$ref": "#/definitions/dto.ClamFileReq"
} }
} }
], ],
@ -12928,6 +12928,57 @@ const docTemplate = `{
} }
} }
}, },
"/websites/ca/download": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "下载 CA 证书文件",
"consumes": [
"application/json"
],
"tags": [
"Website CA"
],
"summary": "Download CA file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "website_cas",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "download ca file [name]",
"formatZH": "下载 CA 证书文件 [name]",
"paramKeys": []
}
}
},
"/websites/ca/obtain": { "/websites/ca/obtain": {
"post": { "post": {
"security": [ "security": [
@ -15482,6 +15533,15 @@ const docTemplate = `{
"dto.ClamBaseInfo": { "dto.ClamBaseInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
"freshIsActive": {
"type": "boolean"
},
"freshIsExist": {
"type": "boolean"
},
"freshVersion": {
"type": "string"
},
"isActive": { "isActive": {
"type": "boolean" "type": "boolean"
}, },
@ -15533,6 +15593,20 @@ const docTemplate = `{
} }
} }
}, },
"dto.ClamFileReq": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"tail": {
"type": "string"
}
}
},
"dto.ClamLogReq": { "dto.ClamLogReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -19778,6 +19852,9 @@ const docTemplate = `{
"group": { "group": {
"type": "string" "type": "string"
}, },
"isDetail": {
"type": "boolean"
},
"isDir": { "isDir": {
"type": "boolean" "type": "boolean"
}, },
@ -20610,6 +20687,9 @@ const docTemplate = `{
"path" "path"
], ],
"properties": { "properties": {
"isDetail": {
"type": "boolean"
},
"path": { "path": {
"type": "string" "type": "string"
} }
@ -20765,6 +20845,9 @@ const docTemplate = `{
"expand": { "expand": {
"type": "boolean" "type": "boolean"
}, },
"isDetail": {
"type": "boolean"
},
"page": { "page": {
"type": "integer" "type": "integer"
}, },
@ -22265,6 +22348,9 @@ const docTemplate = `{
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"sni": {
"type": "boolean"
} }
} }
}, },
@ -22833,6 +22919,9 @@ const docTemplate = `{
"group": { "group": {
"type": "string" "type": "string"
}, },
"isDetail": {
"type": "boolean"
},
"isDir": { "isDir": {
"type": "boolean" "type": "boolean"
}, },

View File

@ -11205,7 +11205,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.OperationWithName" "$ref": "#/definitions/dto.ClamFileReq"
} }
} }
], ],
@ -12921,6 +12921,57 @@
} }
} }
}, },
"/websites/ca/download": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "下载 CA 证书文件",
"consumes": [
"application/json"
],
"tags": [
"Website CA"
],
"summary": "Download CA file",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [
{
"db": "website_cas",
"input_column": "id",
"input_value": "id",
"isList": false,
"output_column": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "download ca file [name]",
"formatZH": "下载 CA 证书文件 [name]",
"paramKeys": []
}
}
},
"/websites/ca/obtain": { "/websites/ca/obtain": {
"post": { "post": {
"security": [ "security": [
@ -15475,6 +15526,15 @@
"dto.ClamBaseInfo": { "dto.ClamBaseInfo": {
"type": "object", "type": "object",
"properties": { "properties": {
"freshIsActive": {
"type": "boolean"
},
"freshIsExist": {
"type": "boolean"
},
"freshVersion": {
"type": "string"
},
"isActive": { "isActive": {
"type": "boolean" "type": "boolean"
}, },
@ -15526,6 +15586,20 @@
} }
} }
}, },
"dto.ClamFileReq": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
},
"tail": {
"type": "string"
}
}
},
"dto.ClamLogReq": { "dto.ClamLogReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -19771,6 +19845,9 @@
"group": { "group": {
"type": "string" "type": "string"
}, },
"isDetail": {
"type": "boolean"
},
"isDir": { "isDir": {
"type": "boolean" "type": "boolean"
}, },
@ -20603,6 +20680,9 @@
"path" "path"
], ],
"properties": { "properties": {
"isDetail": {
"type": "boolean"
},
"path": { "path": {
"type": "string" "type": "string"
} }
@ -20758,6 +20838,9 @@
"expand": { "expand": {
"type": "boolean" "type": "boolean"
}, },
"isDetail": {
"type": "boolean"
},
"page": { "page": {
"type": "integer" "type": "integer"
}, },
@ -22258,6 +22341,9 @@
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"sni": {
"type": "boolean"
} }
} }
}, },
@ -22826,6 +22912,9 @@
"group": { "group": {
"type": "string" "type": "string"
}, },
"isDetail": {
"type": "boolean"
},
"isDir": { "isDir": {
"type": "boolean" "type": "boolean"
}, },

View File

@ -218,6 +218,12 @@ definitions:
type: object type: object
dto.ClamBaseInfo: dto.ClamBaseInfo:
properties: properties:
freshIsActive:
type: boolean
freshIsExist:
type: boolean
freshVersion:
type: string
isActive: isActive:
type: boolean type: boolean
isExist: isExist:
@ -251,6 +257,15 @@ definitions:
required: required:
- ids - ids
type: object type: object
dto.ClamFileReq:
properties:
name:
type: string
tail:
type: string
required:
- name
type: object
dto.ClamLogReq: dto.ClamLogReq:
properties: properties:
clamName: clamName:
@ -3119,6 +3134,8 @@ definitions:
type: string type: string
group: group:
type: string type: string
isDetail:
type: boolean
isDir: isDir:
type: boolean type: boolean
isHidden: isHidden:
@ -3668,6 +3685,8 @@ definitions:
type: object type: object
request.FileContentReq: request.FileContentReq:
properties: properties:
isDetail:
type: boolean
path: path:
type: string type: string
required: required:
@ -3773,6 +3792,8 @@ definitions:
type: boolean type: boolean
expand: expand:
type: boolean type: boolean
isDetail:
type: boolean
page: page:
type: integer type: integer
pageSize: pageSize:
@ -4780,6 +4801,8 @@ definitions:
additionalProperties: additionalProperties:
type: string type: string
type: object type: object
sni:
type: boolean
required: required:
- id - id
- match - match
@ -5167,6 +5190,8 @@ definitions:
type: string type: string
group: group:
type: string type: string
isDetail:
type: boolean
isDir: isDir:
type: boolean type: boolean
isHidden: isHidden:
@ -12558,7 +12583,7 @@ paths:
name: request name: request
required: true required: true
schema: schema:
$ref: '#/definitions/dto.OperationWithName' $ref: '#/definitions/dto.ClamFileReq'
responses: responses:
"200": "200":
description: OK description: OK
@ -13665,6 +13690,39 @@ paths:
formatEN: Delete website ca [name] formatEN: Delete website ca [name]
formatZH: 删除网站 ca [name] formatZH: 删除网站 ca [name]
paramKeys: [] paramKeys: []
/websites/ca/download:
post:
consumes:
- application/json
description: 下载 CA 证书文件
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteResourceReq'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Download CA file
tags:
- Website CA
x-panel-log:
BeforeFunctions:
- db: website_cas
input_column: id
input_value: id
isList: false
output_column: name
output_value: name
bodyKeys:
- id
formatEN: download ca file [name]
formatZH: 下载 CA 证书文件 [name]
paramKeys: []
/websites/ca/obtain: /websites/ca/obtain:
post: post:
consumes: consumes:

View File

@ -121,6 +121,10 @@ export namespace Toolbox {
version: string; version: string;
isActive: boolean; isActive: boolean;
isExist: boolean; isExist: boolean;
freshVersion: string;
freshIsExist: boolean;
freshIsActive: boolean;
} }
export interface ClamInfo { export interface ClamInfo {
id: number; id: number;

View File

@ -117,8 +117,8 @@ export const searchClamRecord = (param: Toolbox.ClamSearchLog) => {
export const getClamRecordLog = (param: Toolbox.ClamRecordReq) => { export const getClamRecordLog = (param: Toolbox.ClamRecordReq) => {
return http.post<string>(`/toolbox/clam/record/log`, param); return http.post<string>(`/toolbox/clam/record/log`, param);
}; };
export const searchClamFile = (name: string) => { export const searchClamFile = (name: string, tail: string) => {
return http.post<string>(`/toolbox/clam/file/search`, { name: name }); return http.post<string>(`/toolbox/clam/file/search`, { name: name, tail: tail });
}; };
export const updateClamFile = (name: string, file: string) => { export const updateClamFile = (name: string, file: string) => {
return http.post(`/toolbox/clam/file/update`, { name: name, file: file }); return http.post(`/toolbox/clam/file/update`, { name: name, file: file });

View File

@ -1074,6 +1074,8 @@ const message = {
}, },
clam: { clam: {
clam: 'Virus Scan', clam: 'Virus Scan',
showFresh: 'Show Virus Database Service',
hideFresh: 'Hide Virus Database Service',
clamHelper: clamHelper:
'The minimum recommended configuration for ClamAV is: 3 GiB of RAM or more, single-core CPU with 2.0 GHz or higher, and at least 5 GiB of available hard disk space.', 'The minimum recommended configuration for ClamAV is: 3 GiB of RAM or more, single-core CPU with 2.0 GHz or higher, and at least 5 GiB of available hard disk space.',
noClam: 'ClamAV service not detected, please refer to the official documentation for installation!', noClam: 'ClamAV service not detected, please refer to the official documentation for installation!',

View File

@ -1016,6 +1016,8 @@ const message = {
}, },
clam: { clam: {
clam: '病毒掃描', clam: '病毒掃描',
showFresh: '顯示病毒庫服務',
hideFresh: '隱藏病毒庫服務',
clamHelper: clamHelper:
'ClamAV 的最低建議配置為3 GiB 以上的 RAM2.0 GHz 以上的單核 CPU以及至少 5 GiB 的可用硬盤空間', 'ClamAV 的最低建議配置為3 GiB 以上的 RAM2.0 GHz 以上的單核 CPU以及至少 5 GiB 的可用硬盤空間',
noClam: '未檢測到 ClamAV 服務請參考官方文檔進行安裝', noClam: '未檢測到 ClamAV 服務請參考官方文檔進行安裝',

View File

@ -1017,6 +1017,8 @@ const message = {
}, },
clam: { clam: {
clam: '病毒扫描', clam: '病毒扫描',
showFresh: '显示病毒库服务',
hideFresh: '隐藏病毒库服务',
clamHelper: clamHelper:
'ClamAV 的最低建议配置为3 GiB 以上的 RAM2.0 GHz 以上的单核 CPU以及至少 5 GiB 的可用硬盘空间', 'ClamAV 的最低建议配置为3 GiB 以上的 RAM2.0 GHz 以上的单核 CPU以及至少 5 GiB 的可用硬盘空间',
doc: '帮助文档', doc: '帮助文档',

View File

@ -29,6 +29,15 @@
<template #main> <template #main>
<div> <div>
<el-select style="width: 20%" @change="search" v-model.number="tail">
<template #prefix>{{ $t('toolbox.clam.scanResult') }}</template>
<el-option :value="0" :label="$t('commons.table.all')" />
<el-option :value="10" :label="10" />
<el-option :value="100" :label="100" />
<el-option :value="200" :label="200" />
<el-option :value="500" :label="500" />
<el-option :value="1000" :label="1000" />
</el-select>
<codemirror <codemirror
:autofocus="true" :autofocus="true"
:placeholder="$t('commons.msg.noneData')" :placeholder="$t('commons.msg.noneData')"
@ -75,13 +84,14 @@ const handleReady = (payload) => {
}; };
const activeName = ref('clamd'); const activeName = ref('clamd');
const tail = ref(0);
const content = ref(); const content = ref();
const confirmRef = ref(); const confirmRef = ref();
const loadHeight = () => { const loadHeight = () => {
let height = globalStore.openMenuTabs ? '405px' : '375px'; let height = globalStore.openMenuTabs ? '425px' : '395px';
if (canUpdate()) { if (canUpdate()) {
height = globalStore.openMenuTabs ? '363px' : '333px'; height = globalStore.openMenuTabs ? '383px' : '353px';
} }
return height; return height;
}; };
@ -93,7 +103,7 @@ const canUpdate = () => {
const search = async (itemName: string) => { const search = async (itemName: string) => {
loading.value = true; loading.value = true;
activeName.value = itemName; activeName.value = itemName;
await searchClamFile(activeName.value) await searchClamFile(activeName.value, tail.value + '')
.then((res) => { .then((res) => {
loading.value = false; loading.value = false;
content.value = res.data; content.value = res.data;

View File

@ -3,14 +3,14 @@
<div class="app-status tool-status" v-if="data.isExist"> <div class="app-status tool-status" v-if="data.isExist">
<el-card> <el-card>
<div> <div>
<el-tag effect="dark" type="success">ClamAV</el-tag> <el-tag class="w-17" effect="dark" type="success">ClamAV</el-tag>
<el-tag round class="status-content" v-if="data.isActive" type="success"> <el-tag round class="status-content" v-if="data.isActive" type="success">
{{ $t('commons.status.running') }} {{ $t('commons.status.running') }}
</el-tag> </el-tag>
<el-tag round class="status-content" v-if="!data.isActive" type="info"> <el-tag round class="status-content" v-if="!data.isActive" type="info">
{{ $t('commons.status.stopped') }} {{ $t('commons.status.stopped') }}
</el-tag> </el-tag>
<el-tag class="status-content">{{ $t('app.version') }}:{{ data.version }}</el-tag> <el-tag class="status-content w-24">{{ $t('app.version') }}:{{ data.version }}</el-tag>
<span class="buttons"> <span class="buttons">
<el-button type="primary" v-if="!data.isActive" link @click="onOperate('start')"> <el-button type="primary" v-if="!data.isActive" link @click="onOperate('start')">
{{ $t('app.start') }} {{ $t('app.start') }}
@ -26,6 +26,35 @@
<el-button type="primary" link @click="setting"> <el-button type="primary" link @click="setting">
{{ $t('commons.button.set') }} {{ $t('commons.button.set') }}
</el-button> </el-button>
<el-divider direction="vertical" />
<el-button type="primary" v-if="showFresh" link @click="changeShow(false)">
{{ $t('toolbox.clam.hideFresh') }}
</el-button>
<el-button type="primary" v-if="!showFresh" link @click="changeShow(true)">
{{ $t('toolbox.clam.showFresh') }}
</el-button>
</span>
</div>
<div class="mt-4" v-if="showFresh">
<el-tag class="w-16" effect="dark" type="success">FreshClam</el-tag>
<el-tag round class="status-content" v-if="data.freshIsActive" type="success">
{{ $t('commons.status.running') }}
</el-tag>
<el-tag round class="status-content" v-if="!data.freshIsActive" type="info">
{{ $t('commons.status.stopped') }}
</el-tag>
<el-tag class="status-content w-24">{{ $t('app.version') }}:{{ data.freshVersion }}</el-tag>
<span class="buttons">
<el-button type="primary" v-if="!data.freshIsActive" link @click="onOperate('fresh-start')">
{{ $t('app.start') }}
</el-button>
<el-button type="primary" v-if="data.freshIsActive" link @click="onOperate('fresh-stop')">
{{ $t('app.stop') }}
</el-button>
<el-divider direction="vertical" />
<el-button type="primary" link @click="onOperate('fresh-restart')">
{{ $t('app.restart') }}
</el-button>
</span> </span>
</div> </div>
</el-card> </el-card>
@ -59,8 +88,13 @@ const data = ref({
isExist: false, isExist: false,
isActive: false, isActive: false,
version: '', version: '',
freshIsExist: false,
freshIsActive: false,
freshVersion: '',
}); });
const loading = ref(false); const loading = ref(false);
const showFresh = ref(localStorage.getItem('clam-fresh-show') !== 'hide');
const em = defineEmits(['setting', 'getStatus', 'update:loading', 'update:maskShow']); const em = defineEmits(['setting', 'getStatus', 'update:loading', 'update:maskShow']);
@ -72,6 +106,11 @@ const toDoc = async () => {
window.open('https://1panel.cn/docs/user_manual/toolbox/clam/', '_blank', 'noopener,noreferrer'); window.open('https://1panel.cn/docs/user_manual/toolbox/clam/', '_blank', 'noopener,noreferrer');
}; };
const changeShow = (val: boolean) => {
showFresh.value = val;
localStorage.setItem('clam-fresh-show', showFresh.value ? 'show' : 'hide');
};
const onOperate = async (operation: string) => { const onOperate = async (operation: string) => {
em('update:maskShow', false); em('update:maskShow', false);
ElMessageBox.confirm( ElMessageBox.confirm(