mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-20 16:00:51 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e95b95f50 | ||
|
|
04099ae2a2 | ||
|
|
c695a2a408 | ||
|
|
690cb48280 | ||
|
|
f226fed8cd |
@@ -0,0 +1,84 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary List quick directories
|
||||
// @Success 200 {array} model.QuickDirectory
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory [get]
|
||||
func (b *BaseApi) ListQuickDirectories(c *gin.Context) {
|
||||
items, err := dashboardService.ListQuickDirectories()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, items)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Create quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory [post]
|
||||
func (b *BaseApi) CreateQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
item, err := dashboardService.CreateQuickDirectory(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, item)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Update quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory/update [post]
|
||||
func (b *BaseApi) UpdateQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
item, err := dashboardService.UpdateQuickDirectory(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, item)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Delete quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory/del [post]
|
||||
func (b *BaseApi) DeleteQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := dashboardService.DeleteQuickDirectory(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -31,7 +31,27 @@ type DashboardBase struct {
|
||||
}
|
||||
|
||||
type ChangeQuicks struct {
|
||||
Quicks []QuickJump `json:"quicks"`
|
||||
Quicks []QuickVisibility `json:"quicks" validate:"required,dive"`
|
||||
}
|
||||
|
||||
type QuickVisibility struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
IsShow bool `json:"isShow"`
|
||||
}
|
||||
|
||||
type QuickDirectoryCreate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
||||
type QuickDirectoryDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Legacy bool `json:"legacy"`
|
||||
}
|
||||
|
||||
type QuickDirectoryUpdate struct {
|
||||
QuickDirectoryDelete
|
||||
QuickDirectoryCreate
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
|
||||
@@ -26,6 +26,7 @@ type FirewallSubsystemStatus struct {
|
||||
}
|
||||
|
||||
type FirewallLifecycleOperation struct {
|
||||
BackupFile string `json:"backupFile,omitempty" validate:"omitempty,max=255"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"`
|
||||
WithDockerRestart bool `json:"withDockerRestart"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
@@ -144,6 +145,11 @@ type MonitorVLLMCurrent struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorVLLMCurrentData struct {
|
||||
model.MonitorVLLM
|
||||
Metrics map[string]json.RawMessage `json:"metrics,omitempty"`
|
||||
}
|
||||
|
||||
type MonitorVLLMClean struct {
|
||||
AppInstallID uint `json:"appInstallID" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ type AppLauncher struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type QuickDirectory struct {
|
||||
BaseModel
|
||||
Path string `json:"path"`
|
||||
Alias string `json:"alias"`
|
||||
Legacy bool `json:"legacy" gorm:"-"`
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -86,7 +86,9 @@ type MonitorVLLM struct {
|
||||
RawMetrics string `json:"-"`
|
||||
HistogramDeltas string `json:"-"`
|
||||
IntervalSeconds float64 `json:"intervalSeconds" gorm:"-"`
|
||||
SampleIntervalSeconds int `json:"-"`
|
||||
RequestDeltas string `json:"-"`
|
||||
CompletedRequests *float64 `json:"completedRequests" gorm:"-"`
|
||||
QueueTime *float64 `json:"queueTime"`
|
||||
AverageGenerationTokens *float64 `json:"averageGenerationTokens"`
|
||||
PrefixCacheHitRate *float64 `json:"prefixCacheHitRate"`
|
||||
|
||||
@@ -3,6 +3,7 @@ package repo
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LauncherRepo struct{}
|
||||
@@ -17,6 +18,11 @@ type ILauncherRepo interface {
|
||||
GetQuickJump(opts ...DBOption) (model.QuickJump, error)
|
||||
ListQuickJump(withAll bool) []model.QuickJump
|
||||
UpdateQuicks(quicks []model.QuickJump) error
|
||||
ListQuickDirectories() ([]model.QuickDirectory, error)
|
||||
CountQuickDirectories() (int64, error)
|
||||
CreateQuickDirectory(item *model.QuickDirectory) error
|
||||
UpdateQuickDirectory(item *model.QuickDirectory) error
|
||||
DeleteQuickDirectory(id uint, legacy bool) error
|
||||
}
|
||||
|
||||
func NewILauncherRepo() ILauncherRepo {
|
||||
@@ -95,13 +101,73 @@ func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error {
|
||||
for _, item := range quicks {
|
||||
if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
|
||||
"is_show": item.IsShow,
|
||||
"detail": item.Detail,
|
||||
"alias": item.Alias,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
tx.Commit()
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
// Legacy entries remain in the overview configuration until explicitly deleted.
|
||||
func legacyQuickDirectories(db *gorm.DB) *gorm.DB {
|
||||
return db.Model(&model.QuickJump{}).Where("name = ? AND is_show = ? AND detail <> ?", "File", true, "")
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) ListQuickDirectories() ([]model.QuickDirectory, error) {
|
||||
items := make([]model.QuickDirectory, 0)
|
||||
if err := global.DB.Order("id ASC").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var quicks []model.QuickJump
|
||||
if err := legacyQuickDirectories(global.DB).Order("id ASC").Find(&quicks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, quick := range quicks {
|
||||
items = append(items, model.QuickDirectory{BaseModel: quick.BaseModel, Path: quick.Detail, Alias: quick.Alias, Legacy: true})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
func (u *LauncherRepo) CountQuickDirectories() (int64, error) {
|
||||
var count, legacyCount int64
|
||||
if err := global.DB.Model(&model.QuickDirectory{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := legacyQuickDirectories(global.DB).Count(&legacyCount).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count + legacyCount, nil
|
||||
}
|
||||
func (u *LauncherRepo) CreateQuickDirectory(item *model.QuickDirectory) error {
|
||||
return global.DB.Create(item).Error
|
||||
}
|
||||
func (u *LauncherRepo) UpdateQuickDirectory(item *model.QuickDirectory) error {
|
||||
var result *gorm.DB
|
||||
if item.Legacy {
|
||||
result = legacyQuickDirectories(global.DB).Where("id = ?", item.ID).Updates(map[string]interface{}{"detail": item.Path, "alias": item.Alias})
|
||||
} else {
|
||||
result = global.DB.Model(&model.QuickDirectory{}).Where("id = ?", item.ID).Updates(map[string]interface{}{"path": item.Path, "alias": item.Alias})
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *LauncherRepo) DeleteQuickDirectory(id uint, legacy bool) error {
|
||||
var result *gorm.DB
|
||||
if legacy {
|
||||
result = legacyQuickDirectories(global.DB).Where("id = ?", id).Updates(map[string]interface{}{"detail": nil, "alias": nil})
|
||||
} else {
|
||||
result = global.DB.Delete(&model.QuickDirectory{}, id)
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ type VLLMMonitorRepo struct{}
|
||||
|
||||
type VLLMHistoryPoint struct {
|
||||
model.MonitorVLLM
|
||||
Bucket int64
|
||||
Bucket int64
|
||||
CompletedRequests *float64
|
||||
}
|
||||
|
||||
func (r *VLLMMonitorRepo) Create(point *model.MonitorVLLM) error {
|
||||
@@ -77,12 +78,19 @@ func (r *VLLMMonitorRepo) History(ctx context.Context, id uint, start, end time.
|
||||
}
|
||||
db = db.Group("bucket").Order("bucket ASC")
|
||||
} else {
|
||||
columns = append([]string{"id", "created_at", "app_install_id", "status"}, metrics...)
|
||||
columns = append([]string{"id", "created_at", "app_install_id", "status", "sample_interval_seconds"}, metrics...)
|
||||
if group == "" || group == "requests" {
|
||||
columns = append(columns, "histogram_deltas", "request_deltas")
|
||||
}
|
||||
db = db.Order("created_at ASC, id ASC")
|
||||
}
|
||||
if group == "" || group == "overview" {
|
||||
count := "CASE WHEN json_valid(request_deltas) THEN CASE WHEN json_extract(request_deltas, '$.seconds') > 0 THEN ROUND(request_throughput * json_extract(request_deltas, '$.seconds')) END END"
|
||||
if seconds > 0 {
|
||||
count = "SUM(" + count + ")"
|
||||
}
|
||||
columns = append(columns, count+" AS completed_requests")
|
||||
}
|
||||
var points []VLLMHistoryPoint
|
||||
err := db.Select(strings.Join(columns, ", ")).Scan(&points).Error
|
||||
return points, err
|
||||
|
||||
@@ -43,6 +43,10 @@ type IDashboardService interface {
|
||||
|
||||
LoadQuickOptions() []dto.QuickJump
|
||||
ChangeQuick(req dto.ChangeQuicks) error
|
||||
ListQuickDirectories() ([]model.QuickDirectory, error)
|
||||
CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error)
|
||||
UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error)
|
||||
DeleteQuickDirectory(req dto.QuickDirectoryDelete) error
|
||||
|
||||
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
|
||||
ChangeShow(req dto.SettingUpdate) error
|
||||
@@ -397,16 +401,15 @@ func (u *DashboardService) LoadQuickOptions() []dto.QuickJump {
|
||||
}
|
||||
func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
|
||||
showCount := 0
|
||||
var quicks []model.QuickJump
|
||||
quicks := make([]model.QuickJump, 0, len(req.Quicks))
|
||||
for _, item := range req.Quicks {
|
||||
var quick model.QuickJump
|
||||
if item.IsShow {
|
||||
showCount++
|
||||
}
|
||||
if err := copier.Copy(&quick, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
quicks = append(quicks, quick)
|
||||
quicks = append(quicks, model.QuickJump{
|
||||
BaseModel: model.BaseModel{ID: item.ID},
|
||||
IsShow: item.IsShow,
|
||||
})
|
||||
}
|
||||
if showCount == 0 {
|
||||
return buserr.New("ErrMinQuickJump")
|
||||
@@ -706,6 +709,13 @@ func loadQuickJump(base *dto.DashboardBase) {
|
||||
quicks := launcherRepo.ListQuickJump(false)
|
||||
for i := 0; i < len(quicks); i++ {
|
||||
switch quicks[i].Name {
|
||||
case "File":
|
||||
count, err := launcherRepo.CountQuickDirectories()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load quick directory count: %v", err)
|
||||
}
|
||||
quicks[i].Detail = fmt.Sprintf("%d", count)
|
||||
quicks[i].Alias = ""
|
||||
case "Agent":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.AgentNumber)
|
||||
case "Website":
|
||||
|
||||
@@ -131,6 +131,9 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
|
||||
|
||||
func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOperation) (dto.FirewallLifecycleOperationResponse, error) {
|
||||
response := dto.FirewallLifecycleOperationResponse{}
|
||||
if request.BackupFile != "" && request.Operation != string(lifecycle.OperationStart) {
|
||||
return response, fmt.Errorf("backup restoration is only supported when starting firewalld or ufw")
|
||||
}
|
||||
if request.Operation == "disableBanPing" || request.Operation == "enableBanPing" {
|
||||
return response, s.OperateFirewall(request)
|
||||
}
|
||||
@@ -155,6 +158,19 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
var restoreRequest dto.FirewallRuleCreate
|
||||
if request.BackupFile != "" {
|
||||
if client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW {
|
||||
return response, fmt.Errorf("backup restoration is only supported when starting firewalld or ufw")
|
||||
}
|
||||
restoreRequest, err = prepareFirewallRuleCreate(dto.FirewallRuleCreate{BackupFile: request.BackupFile, Initialize: true})
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
if err := validateFirewallCreateBatch(restoreRequest, filter.Provider(client.Name())); err != nil {
|
||||
return response, err
|
||||
}
|
||||
}
|
||||
if (client.Name() != lifecycle.ProviderFirewalld && client.Name() != lifecycle.ProviderUFW) ||
|
||||
(request.Operation != string(lifecycle.OperationStart) && request.Operation != string(lifecycle.OperationStop) &&
|
||||
request.Operation != string(lifecycle.OperationRestart)) {
|
||||
@@ -173,7 +189,26 @@ func (s *FirewallService) QueueFirewallOperation(request dto.FirewallLifecycleOp
|
||||
return response, err
|
||||
}
|
||||
taskItem.AddSubTaskWithOps(name, func(t *task.Task) error {
|
||||
return s.runFirewallLifecycleTask(t, client, request, true)
|
||||
if request.BackupFile != "" {
|
||||
if err := s.checkSelectedProvider(t.TaskCtx, filter.Provider(client.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
prepareStart := func(lifecycle.Client) error {
|
||||
if request.BackupFile != "" {
|
||||
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallCreateRulesStep"), func() error {
|
||||
return s.runFirewallCreateRulesTask(t, restoreRequest, filter.Provider(client.Name()))
|
||||
})
|
||||
}
|
||||
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
|
||||
err := s.SyncPortWhitelist(t.TaskCtx)
|
||||
if err == nil && client.Name() == lifecycle.ProviderFirewalld {
|
||||
err = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
return s.runFirewallLifecycleTask(t, client, request, prepareStart)
|
||||
}, nil, 0, 0)
|
||||
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||
closeUnstartedFirewallTask(taskItem)
|
||||
@@ -554,29 +589,9 @@ func (s *FirewallService) LoadFirewallNativeDetail(ctx context.Context, request
|
||||
}
|
||||
|
||||
func (s *FirewallService) Create(ctx context.Context, request dto.FirewallRuleCreate) (dto.FirewallRuleCreateResponse, error) {
|
||||
if request.BackupFile != "" {
|
||||
if len(request.Items) != 0 {
|
||||
return dto.FirewallRuleCreateResponse{}, filter.ErrInvalidRule
|
||||
}
|
||||
items, err := readFirewallBackup(request.BackupFile)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleCreateResponse{}, err
|
||||
}
|
||||
for _, item := range items {
|
||||
request.Items = append(request.Items, dto.FirewallRuleCreateItem{Rule: item.FirewallRule, Raw: item.Raw, ParseStatus: item.ParseStatus, SourceKind: constant.FirewallRuleSourceImported})
|
||||
}
|
||||
}
|
||||
|
||||
for index, item := range request.Items {
|
||||
if item.SourceKind != constant.FirewallRuleSourceImported || item.Rule.Scope.Provider != filter.ProviderUFW || item.ParseStatus != filter.ParseStatusPartial || item.Raw == "" {
|
||||
continue
|
||||
}
|
||||
observed := filterufw.ParseRules(item.Rule.Scope, item.Raw)
|
||||
if len(observed) == 1 && observed[0].ParseStatus == filter.ParseStatusSupported {
|
||||
observed[0].Rule.Description = item.Rule.Description
|
||||
request.Items[index].Rule = observed[0].Rule
|
||||
request.Items[index].ParseStatus, request.Items[index].Raw = filter.ParseStatusSupported, ""
|
||||
}
|
||||
request, err := prepareFirewallRuleCreate(request)
|
||||
if err != nil {
|
||||
return dto.FirewallRuleCreateResponse{}, err
|
||||
}
|
||||
|
||||
provider, err := s.selectedProvider(ctx)
|
||||
@@ -609,30 +624,11 @@ func (s *FirewallService) Create(ctx context.Context, request dto.FirewallRuleCr
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.runFirewallLifecycleTask(t, client, dto.FirewallLifecycleOperation{Operation: "start"}, false)
|
||||
return s.runFirewallLifecycleTask(t, client, dto.FirewallLifecycleOperation{Operation: "start"}, nil)
|
||||
}, nil)
|
||||
}
|
||||
ruleState := make(map[string]filter.RuleSet)
|
||||
taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("FirewallCreateRulesStep"), func(t *task.Task) error {
|
||||
firewallRuleMutationMu.Lock()
|
||||
if err := s.checkSelectedProvider(t.TaskCtx, provider); err != nil {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
return err
|
||||
}
|
||||
_, err := s.createRules(t.TaskCtx, request, t, ruleState)
|
||||
firewallRuleMutationMu.Unlock()
|
||||
if !request.Initialize || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
ruleState = nil
|
||||
}
|
||||
whitelistErr := s.syncPortWhitelist(t.TaskCtx, ruleState)
|
||||
if whitelistErr == nil && provider == filter.ProviderFirewalld {
|
||||
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
|
||||
return errors.Join(err, whitelistErr)
|
||||
return s.runFirewallCreateRulesTask(t, request, provider)
|
||||
}, nil, 0, 0)
|
||||
if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil {
|
||||
taskItem.LogFailedWithErr(taskItem.Name, err)
|
||||
@@ -777,7 +773,11 @@ func (s *FirewallService) restoreFirewallAfterStart(client lifecycle.Client) err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
recordFailure("restore whitelist allowances", s.SyncPortWhitelist(ctx))
|
||||
whitelistErr := s.SyncPortWhitelist(ctx)
|
||||
if whitelistErr == nil && provider == filter.ProviderFirewalld {
|
||||
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
recordFailure("restore whitelist allowances", whitelistErr)
|
||||
return errors.Join(recoveryErrors...)
|
||||
}
|
||||
|
||||
@@ -805,17 +805,9 @@ func (s *FirewallService) restoreFirewalldRuntimeDependents(ctx context.Context,
|
||||
return restoreErr
|
||||
}
|
||||
|
||||
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation, syncWhitelist bool) error {
|
||||
func (s *FirewallService) runFirewallLifecycleTask(t *task.Task, client lifecycle.Client, request dto.FirewallLifecycleOperation, prepareStart func(lifecycle.Client) error) error {
|
||||
ctx := t.TaskCtx
|
||||
provider := filter.Provider(client.Name())
|
||||
var prepareStart func(lifecycle.Client) error
|
||||
if syncWhitelist {
|
||||
prepareStart = func(lifecycle.Client) error {
|
||||
return runFirewallLifecycleAction(t, i18n.GetMsgByKey("FirewallSyncWhitelistStep"), func() error {
|
||||
return s.SyncPortWhitelist(ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
operationErr := operateFirewallLifecycle(firewallLifecycleClient{client}, request.Operation, request.WithDockerRestart, prepareStart, t)
|
||||
if request.Operation == string(lifecycle.OperationStop) {
|
||||
return operationErr
|
||||
|
||||
@@ -554,6 +554,58 @@ func (s *FirewallService) checkSelectedProvider(ctx context.Context, requested f
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareFirewallRuleCreate(request dto.FirewallRuleCreate) (dto.FirewallRuleCreate, error) {
|
||||
if request.BackupFile != "" {
|
||||
if len(request.Items) != 0 {
|
||||
return request, filter.ErrInvalidRule
|
||||
}
|
||||
items, err := readFirewallBackup(request.BackupFile)
|
||||
if err != nil {
|
||||
return request, err
|
||||
}
|
||||
for _, item := range items {
|
||||
request.Items = append(request.Items, dto.FirewallRuleCreateItem{Rule: item.FirewallRule, Raw: item.Raw, ParseStatus: item.ParseStatus, SourceKind: constant.FirewallRuleSourceImported})
|
||||
}
|
||||
}
|
||||
|
||||
for index, item := range request.Items {
|
||||
if item.SourceKind != constant.FirewallRuleSourceImported || item.Rule.Scope.Provider != filter.ProviderUFW || item.ParseStatus != filter.ParseStatusPartial || item.Raw == "" {
|
||||
continue
|
||||
}
|
||||
observed := filterufw.ParseRules(item.Rule.Scope, item.Raw)
|
||||
if len(observed) == 1 && observed[0].ParseStatus == filter.ParseStatusSupported {
|
||||
observed[0].Rule.Description = item.Rule.Description
|
||||
request.Items[index].Rule = observed[0].Rule
|
||||
request.Items[index].ParseStatus, request.Items[index].Raw = filter.ParseStatusSupported, ""
|
||||
}
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (s *FirewallService) runFirewallCreateRulesTask(t *task.Task, request dto.FirewallRuleCreate, provider filter.Provider) error {
|
||||
ruleState := make(map[string]filter.RuleSet)
|
||||
firewallRuleMutationMu.Lock()
|
||||
if err := s.checkSelectedProvider(t.TaskCtx, provider); err != nil {
|
||||
firewallRuleMutationMu.Unlock()
|
||||
return err
|
||||
}
|
||||
_, err := s.createRules(t.TaskCtx, request, t, ruleState)
|
||||
firewallRuleMutationMu.Unlock()
|
||||
if !request.Initialize || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
ruleState = nil
|
||||
}
|
||||
whitelistErr := s.syncPortWhitelist(t.TaskCtx, ruleState)
|
||||
if whitelistErr == nil && provider == filter.ProviderFirewalld {
|
||||
whitelistErr = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
t.LogWithStatus(i18n.GetMsgByKey("FirewallSyncWhitelistStep"), whitelistErr)
|
||||
return errors.Join(err, whitelistErr)
|
||||
}
|
||||
|
||||
func (s *FirewallService) createRules(ctx context.Context, request dto.FirewallRuleCreate, t *task.Task, ruleState map[string]filter.RuleSet) (result dto.FirewallRuleCreateResponse, taskErr error) {
|
||||
var firstFailure error
|
||||
defer func() {
|
||||
@@ -1734,9 +1786,6 @@ func operateFirewallLifecycle(client lifecycle.Client, operation string, withDoc
|
||||
var recoveryErrors []error
|
||||
if prepareStart != nil {
|
||||
err := prepareStart(client)
|
||||
if err == nil && client.Name() == constant.FirewallProviderFirewalld {
|
||||
err = lifecycleproviders.RemoveFirewalldSSHService()
|
||||
}
|
||||
if err != nil {
|
||||
recoveryErrors = append(recoveryErrors, fmt.Errorf("prepare firewall after %s: %w", operation, err))
|
||||
}
|
||||
@@ -1758,17 +1807,82 @@ func operateFirewallLifecycle(client lifecycle.Client, operation string, withDoc
|
||||
}
|
||||
|
||||
func (c firewallLifecycleClient) Start() error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
if err := c.prepareAccessWhitelistBeforeStart(); err != nil {
|
||||
global.LOG.Warnf("prepare %s access whitelist before start failed: %v", c.Name(), err)
|
||||
}
|
||||
return c.Client.Start()
|
||||
}
|
||||
|
||||
func (c firewallLifecycleClient) Restart() error {
|
||||
firewallWhitelistMu.Lock()
|
||||
defer firewallWhitelistMu.Unlock()
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
if err := c.prepareAccessWhitelistBeforeStart(); err != nil {
|
||||
global.LOG.Warnf("prepare %s access whitelist before restart failed: %v", c.Name(), err)
|
||||
}
|
||||
return c.Client.Restart()
|
||||
}
|
||||
|
||||
func (c firewallLifecycleClient) prepareAccessWhitelistBeforeStart() error {
|
||||
provider := filter.Provider(c.Name())
|
||||
if provider != filter.ProviderFirewalld && provider != filter.ProviderUFW {
|
||||
return nil
|
||||
}
|
||||
ports, err := LoadRequiredFirewallPortWhiteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var adapter filter.Adapter
|
||||
var active bool
|
||||
if provider == filter.ProviderFirewalld {
|
||||
active, err = c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adapter = filterfirewalld.NewAdapter()
|
||||
} else {
|
||||
adapter = filterufw.NewAdapter()
|
||||
}
|
||||
var commands []filter.NativeCommand
|
||||
for _, port := range firewall.ExpandPortWhitelist(ports) {
|
||||
rule := firewall.RuleForSystemPort(provider, port)
|
||||
rule.Scope = rule.Scope.Normalize()
|
||||
rule.UUID = uuid.NewString()
|
||||
if provider == filter.ProviderUFW {
|
||||
position := int64(1)
|
||||
rule.OrderIndex = &position
|
||||
}
|
||||
plan, err := adapter.BuildCommands(filter.RuleSet{Scope: rule.Scope}, []filter.RuleChange{{Operation: filter.ChangeCreate, After: &rule}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range plan.Rules {
|
||||
for _, command := range item.Commands {
|
||||
if provider == filter.ProviderFirewalld {
|
||||
if active {
|
||||
command.Args = append(command.Args, "--permanent")
|
||||
} else {
|
||||
command.Executable = "firewall-offline-cmd"
|
||||
}
|
||||
}
|
||||
commands = append(commands, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
manager := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second), cmd.WithEnv("LC_ALL=C", "LANGUAGE=en_US:en"))
|
||||
for _, command := range commands {
|
||||
if err := manager.RunWithOptionalSudo(command.Executable, command.Args...); err != nil {
|
||||
return fmt.Errorf("prepare %s access whitelist before start: %w", provider, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runFirewallLifecycleAction(t *task.Task, name string, action func() error) error {
|
||||
t.Log(i18n.GetWithName("TaskStart", name))
|
||||
started := time.Now()
|
||||
|
||||
@@ -55,7 +55,7 @@ type IMonitorService interface {
|
||||
LoadIOOptions() []string
|
||||
LoadNetworkOptions() []string
|
||||
LoadVLLMMonitorData(ctx context.Context, req dto.MonitorVLLMSearch) (dto.MonitorVLLMData, error)
|
||||
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error)
|
||||
LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error)
|
||||
CleanVLLMMonitor(req dto.MonitorVLLMClean) error
|
||||
LoadGPUOptions() dto.MonitorGPUOptions
|
||||
LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error)
|
||||
|
||||
@@ -60,6 +60,9 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
for i := range points {
|
||||
points[i].MonitorVLLM.CompletedRequests = points[i].CompletedRequests
|
||||
}
|
||||
if data.BucketSeconds > 0 {
|
||||
aggregates := map[int64]*vllm.HistoryAccumulator{}
|
||||
err = vllmMonitorRepo.WalkDeltas(ctx, req.AppInstallID, req.StartTime, req.EndTime, data.BucketSeconds, req.Aggregation, req.Group, func(bucket int64, histograms, requests string) error {
|
||||
@@ -91,15 +94,6 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
data.Points = append(data.Points, point)
|
||||
}
|
||||
} else {
|
||||
setting, err := settingRepo.GetValueByKey("VLLMMonitorInterval")
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
intervalSeconds, err := strconv.Atoi(setting)
|
||||
if err != nil || intervalSeconds <= 0 {
|
||||
return data, fmt.Errorf("invalid vLLM monitoring interval: %s", setting)
|
||||
}
|
||||
interval := time.Duration(intervalSeconds) * time.Second
|
||||
for i, point := range points {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return data, err
|
||||
@@ -109,8 +103,20 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
return data, err
|
||||
}
|
||||
aggregate.Apply(&point.MonitorVLLM, "max")
|
||||
if i > 0 && point.CreatedAt.Sub(points[i-1].CreatedAt) > 2*interval {
|
||||
data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: points[i-1].CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
|
||||
if i > 0 {
|
||||
previous := points[i-1]
|
||||
interval := time.Duration(max(previous.SampleIntervalSeconds, point.SampleIntervalSeconds)) * time.Second
|
||||
if interval == 0 {
|
||||
if i > 1 {
|
||||
interval = previous.CreatedAt.Sub(points[i-2].CreatedAt)
|
||||
}
|
||||
if i+1 < len(points) {
|
||||
interval = max(interval, points[i+1].CreatedAt.Sub(point.CreatedAt))
|
||||
}
|
||||
}
|
||||
if interval > 0 && point.CreatedAt.Sub(previous.CreatedAt) > 2*interval {
|
||||
data.Points = append(data.Points, model.MonitorVLLM{CreatedAt: previous.CreatedAt.Add(interval), AppInstallID: req.AppInstallID})
|
||||
}
|
||||
}
|
||||
data.Points = append(data.Points, point.MonitorVLLM)
|
||||
}
|
||||
@@ -118,31 +124,30 @@ func (m *MonitorService) LoadVLLMMonitorData(ctx context.Context, req dto.Monito
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (model.MonitorVLLM, error) {
|
||||
func (m *MonitorService) LoadVLLMCurrent(ctx context.Context, req dto.MonitorVLLMCurrent) (dto.MonitorVLLMCurrentData, error) {
|
||||
install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID))
|
||||
if err != nil {
|
||||
return model.MonitorVLLM{}, err
|
||||
return dto.MonitorVLLMCurrentData{}, err
|
||||
}
|
||||
if install.App.Key != "vllm" {
|
||||
return model.MonitorVLLM{}, fmt.Errorf("not a vLLM installation")
|
||||
return dto.MonitorVLLMCurrentData{}, fmt.Errorf("not a vLLM installation")
|
||||
}
|
||||
previous, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
|
||||
point, err := collectVLLMMetrics(ctx, install, model.MonitorVLLM{})
|
||||
data := dto.MonitorVLLMCurrentData{MonitorVLLM: point}
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
|
||||
return previous, nil
|
||||
return data, nil
|
||||
}
|
||||
timer := time.NewTimer(time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return model.MonitorVLLM{}, ctx.Err()
|
||||
case <-timer.C:
|
||||
if err := json.Unmarshal([]byte(point.RawMetrics), &data.Metrics); err != nil {
|
||||
return data, err
|
||||
}
|
||||
point, err := collectVLLMMetrics(ctx, install, previous)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics on port %d failed: %v", install.HttpPort, err)
|
||||
for name := range data.Metrics {
|
||||
if name == "request_prompt_tokens_sum" || name == "request_prompt_tokens_count" ||
|
||||
(!strings.HasSuffix(name, "_total") && !strings.HasSuffix(name, "_sum") && !strings.HasSuffix(name, "_count")) {
|
||||
delete(data.Metrics, name)
|
||||
}
|
||||
}
|
||||
return point, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) CleanVLLMMonitor(req dto.MonitorVLLMClean) error {
|
||||
@@ -164,7 +169,7 @@ func StartVLLMMonitor(interval string) error {
|
||||
return fmt.Errorf("invalid vLLM monitoring interval: %s", interval)
|
||||
}
|
||||
service := &MonitorService{}
|
||||
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(service.saveVLLMData))
|
||||
job := cron.NewChain(cron.Recover(cron.DefaultLogger)).Then(cron.FuncJob(func() { service.saveVLLMData(seconds) }))
|
||||
id, err := global.Cron.AddFunc(fmt.Sprintf("@every %ds", seconds), func() { go job.Run() })
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -176,7 +181,7 @@ func StartVLLMMonitor(interval string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MonitorService) saveVLLMData() {
|
||||
func (m *MonitorService) saveVLLMData(intervalSeconds int) {
|
||||
if !vllmMonitorMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
@@ -228,6 +233,7 @@ func (m *MonitorService) saveVLLMData() {
|
||||
return err
|
||||
}
|
||||
point, err := collectVLLMMetrics(context.Background(), install, previous)
|
||||
point.SampleIntervalSeconds = intervalSeconds
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Collect vLLM metrics for %s failed: %v", install.Name, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
)
|
||||
|
||||
func (u *DashboardService) ListQuickDirectories() ([]model.QuickDirectory, error) {
|
||||
return launcherRepo.ListQuickDirectories()
|
||||
}
|
||||
|
||||
func quickDirectoryValues(req dto.QuickDirectoryCreate) (model.QuickDirectory, error) {
|
||||
item := model.QuickDirectory{Path: req.Path, Alias: strings.TrimSpace(req.Alias)}
|
||||
if strings.TrimSpace(item.Path) == "" {
|
||||
return item, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error) {
|
||||
item, err := quickDirectoryValues(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := launcherRepo.CreateQuickDirectory(&item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error) {
|
||||
item, err := quickDirectoryValues(req.QuickDirectoryCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.ID = req.ID
|
||||
item.Legacy = req.Legacy
|
||||
if err := launcherRepo.UpdateQuickDirectory(&item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) DeleteQuickDirectory(req dto.QuickDirectoryDelete) error {
|
||||
return launcherRepo.DeleteQuickDirectory(req.ID, req.Legacy)
|
||||
}
|
||||
@@ -114,6 +114,8 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddVLLMMonitor,
|
||||
migrations.AddMonitorSettings,
|
||||
migrations.AddVLLMRequestMetrics,
|
||||
migrations.AddVLLMSampleInterval,
|
||||
migrations.AddQuickDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1881,3 +1881,17 @@ var AddVLLMRequestMetrics = &gormigrate.Migration{
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddVLLMSampleInterval = &gormigrate.Migration{
|
||||
ID: "20261016-vllm-sample-interval",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddQuickDirectory = &gormigrate.Migration{
|
||||
ID: "20261016-add-quick-directory",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.QuickDirectory{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/quick/option", baseApi.LoadQuickOption)
|
||||
cmdRouter.POST("/quick/change", baseApi.UpdateQuickJump)
|
||||
cmdRouter.GET("/quick/directory", baseApi.ListQuickDirectories)
|
||||
cmdRouter.POST("/quick/directory", baseApi.CreateQuickDirectory)
|
||||
cmdRouter.POST("/quick/directory/update", baseApi.UpdateQuickDirectory)
|
||||
cmdRouter.POST("/quick/directory/del", baseApi.DeleteQuickDirectory)
|
||||
cmdRouter.GET("/app/launcher", baseApi.LoadAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/show", baseApi.UpdateAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/option", baseApi.LoadAppLauncherOption)
|
||||
|
||||
@@ -314,7 +314,17 @@ func (s *Session) detach(a *attachment, clean, revalidate bool, cursor uint64) {
|
||||
if !s.Persistent || revalidate {
|
||||
timeout = revalidateGrace
|
||||
}
|
||||
s.grace = time.AfterFunc(timeout, s.Close)
|
||||
// Stop cannot cancel a fired timer; a racing Attach clears s.grace.
|
||||
var grace *time.Timer
|
||||
grace = time.AfterFunc(timeout, func() {
|
||||
s.mu.Lock()
|
||||
expired := s.grace == grace
|
||||
s.mu.Unlock()
|
||||
if expired {
|
||||
s.Close()
|
||||
}
|
||||
})
|
||||
s.grace = grace
|
||||
}
|
||||
s.mu.Unlock()
|
||||
if clean {
|
||||
|
||||
@@ -8,6 +8,12 @@ export namespace Dashboard {
|
||||
|
||||
diskSize: number;
|
||||
}
|
||||
export interface QuickDirectory {
|
||||
id: number;
|
||||
legacy?: boolean;
|
||||
path: string;
|
||||
alias: string;
|
||||
}
|
||||
export interface QuickJump {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -8,7 +8,7 @@ export const loadOsInfo = () => {
|
||||
export const loadQuickOption = () => {
|
||||
return http.get<Array<Dashboard.QuickJump>>(`/dashboard/quick/option`);
|
||||
};
|
||||
export const changeQuick = (quicks: Array<Dashboard.QuickJump>) => {
|
||||
export const changeQuick = (quicks: Array<Pick<Dashboard.QuickJump, 'id' | 'isShow'>>) => {
|
||||
return http.post(`/dashboard/quick/change`, { quicks: quicks });
|
||||
};
|
||||
export const loadAppLauncher = () => {
|
||||
@@ -48,3 +48,11 @@ export const loadTopMem = () => {
|
||||
export const systemRestart = (operation: string) => {
|
||||
return http.post(`/dashboard/system/restart/${operation}`);
|
||||
};
|
||||
|
||||
export const loadQuickDirectories = () => http.get<Dashboard.QuickDirectory[]>('/dashboard/quick/directory');
|
||||
export const createQuickDirectory = (data: Omit<Dashboard.QuickDirectory, 'id'>) =>
|
||||
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory', data);
|
||||
export const updateQuickDirectory = (data: Dashboard.QuickDirectory) =>
|
||||
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory/update', data);
|
||||
export const deleteQuickDirectory = (id: number, legacy = false) =>
|
||||
http.post('/dashboard/quick/directory/del', { id, legacy });
|
||||
|
||||
@@ -21,10 +21,10 @@ export const loadForwardBaseInfo = () =>
|
||||
export const searchForwardRule = (request: Firewall.ForwardRuleSearch) =>
|
||||
http.post<ResPage<Firewall.RuleInfo>>('/hosts/firewall/forward/search', request, TimeoutEnum.T_40S);
|
||||
|
||||
export const operateFire = (operation: string, withDockerRestart: boolean) =>
|
||||
export const operateFire = (operation: string, withDockerRestart: boolean, backupFile?: string) =>
|
||||
http.post<Firewall.FilterChainOperationResult>(
|
||||
'/hosts/firewall/operate',
|
||||
{ operation, withDockerRestart },
|
||||
{ operation, withDockerRestart, backupFile },
|
||||
TimeoutEnum.T_10M,
|
||||
);
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ const favorites = ref<File.Favorite[]>([]);
|
||||
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchableForSelect(paths);
|
||||
const oldUrl = ref<string>('');
|
||||
|
||||
const em = defineEmits(['choose']);
|
||||
const em = defineEmits(['choose', 'close']);
|
||||
|
||||
const form = reactive({
|
||||
path: '/',
|
||||
@@ -245,6 +245,7 @@ const handleClose = () => {
|
||||
open.value = false;
|
||||
selectRow.value = { path: '', name: '' };
|
||||
selectRows.value = [];
|
||||
em('close');
|
||||
};
|
||||
|
||||
const openPage = () => {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
size="w-70"
|
||||
:show-close="false"
|
||||
:modal="false"
|
||||
modal-penetrable
|
||||
@opened="claim"
|
||||
>
|
||||
<template #header>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:ref="(el: any) => store.setInstance(item.key, el)"
|
||||
@session="(id: string) => store.setSessionId(item.key, id)"
|
||||
@expired="store.onExpired(item.key)"
|
||||
@reconnect="store.reconnect(item.key)"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -31,7 +31,7 @@ const { currentNode } = useGlobalStore();
|
||||
|
||||
// session: agent side session id known (fresh or reattached)
|
||||
// expired: the agent no longer has the session; a reconnect must open a new one
|
||||
const emit = defineEmits(['session', 'expired']);
|
||||
const emit = defineEmits(['session', 'expired', 'reconnect']);
|
||||
|
||||
// Close codes of the agent's session protocol (agent/utils/terminal/session.go).
|
||||
const CLOSE_SESSION_NOT_FOUND = 4404;
|
||||
@@ -56,6 +56,7 @@ let closing = false;
|
||||
let reconnecting = false;
|
||||
let reconnectNoticeShown = false;
|
||||
let revalidating = false;
|
||||
let expired = false;
|
||||
let reconnectStartedAt = 0;
|
||||
let reconnectDelay = 1000;
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -279,6 +280,7 @@ function resizeTerminal() {
|
||||
if (terminalElement.value.clientWidth <= 0 || terminalElement.value.clientHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
if (terminalElement.value.closest('.terminal-host')) return;
|
||||
|
||||
fitAddon.fit();
|
||||
if (isWsOpen()) {
|
||||
@@ -484,6 +486,7 @@ const closeRealTerminal = (ev: CloseEvent) => {
|
||||
'31',
|
||||
ev.code === 1000 ? 'The connection has been disconnected.' : i18n.global.t('terminal.sessionExpired'),
|
||||
);
|
||||
expired = true;
|
||||
emit('expired');
|
||||
return;
|
||||
case CLOSE_ATTACHED_ELSEWHERE:
|
||||
@@ -602,6 +605,13 @@ function sendMsg(data: string, line: string = '') {
|
||||
|
||||
function onTermData(data: string) {
|
||||
if (!data) return;
|
||||
if (expired) {
|
||||
if (isEnterInputData(data)) {
|
||||
expired = false;
|
||||
emit('reconnect');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (aiNotice.value.loading) return;
|
||||
sendMsg(data, isEnterInputData(data) ? getCurrentTerminalLine() : '');
|
||||
}
|
||||
|
||||
@@ -494,6 +494,7 @@ const message = {
|
||||
recommend: 'Recommended',
|
||||
dir: 'Directory',
|
||||
alias: 'Alias',
|
||||
quickDirDelete: 'Delete ({0})?',
|
||||
quickDir: 'Quick Access',
|
||||
minQuickJump: 'Add at least one quick access entry',
|
||||
maxQuickJump: 'You can add up to four quick access entries',
|
||||
@@ -1517,6 +1518,14 @@ const message = {
|
||||
extraHeaders: 'Extra Headers',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Current concurrency',
|
||||
sampleMoment: 'At sampling time',
|
||||
sampleWindow: 'Sample interval',
|
||||
requestUnit: 'requests',
|
||||
processing: 'Processing',
|
||||
queued: 'Queued',
|
||||
requestTiming: 'Request timing',
|
||||
completedRequests: 'Requests completed in interval',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Requests/s',
|
||||
finishReasonLabels: {
|
||||
@@ -4299,9 +4308,14 @@ const message = {
|
||||
familyIncomplete: '{0} chains are incomplete',
|
||||
resetWithBackup: 'Back up rules and reset',
|
||||
resetOnly: 'Reset only',
|
||||
resetWithBackupHelper: 'Save the current module rules as JSON for restoration during initialization.',
|
||||
resetWithBackupHelper:
|
||||
'Save the current module rules as JSON for restoration during initialization or startup.',
|
||||
resetOnlyHelper: 'Clear current rules without creating a new backup. Existing backups are kept.',
|
||||
backupDirectoryHelper: 'Valid rule backups in {0} are listed below. The newest file is selected by default.',
|
||||
startOnly: 'Start only',
|
||||
startAndRestore: 'Start and restore from backup',
|
||||
startWithBackupHelper:
|
||||
'Start the firewall normally, or choose an existing JSON backup to start and restore rules.',
|
||||
restoreAllRules: 'Initialize and restore rules',
|
||||
importRuleBackup: 'Import from backup',
|
||||
initializeOnly: 'Initialize only',
|
||||
|
||||
@@ -493,6 +493,7 @@ const message = {
|
||||
home: {
|
||||
recommend: 'Recomendado',
|
||||
dir: 'Directorio',
|
||||
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',
|
||||
@@ -1526,6 +1527,14 @@ const message = {
|
||||
extraHeaders: 'Encabezados adicionales',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Concurrencia actual',
|
||||
sampleMoment: 'Al tomar la muestra',
|
||||
sampleWindow: 'Intervalo de muestreo',
|
||||
requestUnit: 'solicitudes',
|
||||
processing: 'En proceso',
|
||||
queued: 'En cola',
|
||||
requestTiming: 'Tiempo de las solicitudes',
|
||||
completedRequests: 'Solicitudes completadas en el intervalo',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Solicitudes/s',
|
||||
finishReasonLabels: {
|
||||
@@ -4347,10 +4356,14 @@ const message = {
|
||||
resetWithBackup: 'Guardar reglas y restablecer',
|
||||
resetOnly: 'Solo restablecer',
|
||||
resetWithBackupHelper:
|
||||
'Guarda las reglas del módulo actual en JSON para restaurarlas durante la inicialización.',
|
||||
'Guarde las reglas del módulo actual en JSON para restaurarlas al inicializar o iniciar el cortafuegos.',
|
||||
resetOnlyHelper: 'Borrar las reglas actuales sin crear una copia nueva. Se conservan las copias existentes.',
|
||||
backupDirectoryHelper:
|
||||
'Se muestran las copias válidas de reglas en {0}. Se selecciona la más reciente por defecto.',
|
||||
startOnly: 'Solo iniciar',
|
||||
startAndRestore: 'Iniciar y restaurar desde una copia de seguridad',
|
||||
startWithBackupHelper:
|
||||
'Inicie el cortafuegos normalmente o elija una copia JSON existente para iniciar y restaurar reglas.',
|
||||
restoreAllRules: 'Inicializar y restaurar reglas',
|
||||
importRuleBackup: 'Importar desde copia de seguridad',
|
||||
initializeOnly: 'Solo inicializar',
|
||||
|
||||
@@ -489,6 +489,7 @@ const message = {
|
||||
recommend: 'پیشنهادی',
|
||||
dir: 'دایرکتوری',
|
||||
alias: 'نام مستعار',
|
||||
quickDirDelete: 'حذف ({0}) را تأیید میکنید؟',
|
||||
quickDir: 'دسترسی سریع',
|
||||
minQuickJump: 'حداقل یک ورودی دسترسی سریع اضافه کنید',
|
||||
maxQuickJump: 'میتوانید حداکثر چهار ورودی دسترسی سریع اضافه کنید',
|
||||
@@ -1502,6 +1503,14 @@ const message = {
|
||||
extraHeaders: 'هدرهای اضافی',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'درخواستهای همزمان فعلی',
|
||||
sampleMoment: 'در لحظه نمونهبرداری',
|
||||
sampleWindow: 'بازه نمونهبرداری',
|
||||
requestUnit: 'درخواست',
|
||||
processing: 'در حال پردازش',
|
||||
queued: 'در صف',
|
||||
requestTiming: 'زمان پردازش درخواست',
|
||||
completedRequests: 'تعداد درخواستهای تکمیلشده در بازه',
|
||||
tokensPerSecond: 'tokens/ثانیه',
|
||||
requestsPerSecond: 'درخواست/ثانیه',
|
||||
finishReasonLabels: {
|
||||
@@ -4259,10 +4268,15 @@ const message = {
|
||||
familyIncomplete: 'زنجیرههای {0} ناقص هستند',
|
||||
resetWithBackup: 'پشتیبانگیری قوانین و بازنشانی',
|
||||
resetOnly: 'فقط بازنشانی',
|
||||
resetWithBackupHelper: 'قوانین ماژول فعلی را بهصورت JSON ذخیره کنید تا هنگام مقداردهی اولیه بازیابی شوند.',
|
||||
resetWithBackupHelper:
|
||||
'قوانین ماژول فعلی را بهصورت JSON ذخیره کنید تا هنگام مقداردهی اولیه یا راهاندازی بازیابی شوند.',
|
||||
resetOnlyHelper: 'قوانین فعلی بدون ایجاد نسخه پشتیبان جدید پاک میشوند. نسخههای پشتیبان قبلی حفظ میشوند.',
|
||||
backupDirectoryHelper:
|
||||
'نسخههای پشتیبان معتبر قوانین در {0} نمایش داده میشوند. جدیدترین فایل بهطور پیشفرض انتخاب میشود.',
|
||||
startOnly: 'فقط راه\u200cاندازی',
|
||||
startAndRestore: 'راه\u200cاندازی و بازیابی از نسخه پشتیبان',
|
||||
startWithBackupHelper:
|
||||
'فایروال را بهطور معمول راهاندازی کنید یا یک نسخه پشتیبان JSON موجود را برای راهاندازی و بازیابی قوانین انتخاب کنید.',
|
||||
restoreAllRules: 'مقداردهی اولیه و بازیابی قوانین',
|
||||
importRuleBackup: 'ورود از نسخه پشتیبان',
|
||||
initializeOnly: 'فقط راهاندازی',
|
||||
|
||||
@@ -492,6 +492,7 @@ const message = {
|
||||
recommend: 'おすすめ',
|
||||
dir: 'ディレクトリ',
|
||||
alias: 'エイリアス',
|
||||
quickDirDelete: '({0})を削除しますか?',
|
||||
quickDir: 'クイックアクセス',
|
||||
minQuickJump: 'クイックアクセス項目を1つ以上追加してください',
|
||||
maxQuickJump: '最大4件のクイックアクセス項目を追加できます',
|
||||
@@ -1505,6 +1506,14 @@ const message = {
|
||||
extraHeaders: '追加ヘッダー',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '現在の同時リクエスト',
|
||||
sampleMoment: 'サンプリング時点',
|
||||
sampleWindow: '集計区間',
|
||||
requestUnit: '件',
|
||||
processing: '処理中',
|
||||
queued: '待機中',
|
||||
requestTiming: 'リクエスト処理時間',
|
||||
completedRequests: '区間内の完了リクエスト数',
|
||||
tokensPerSecond: 'tokens/秒',
|
||||
requestsPerSecond: 'リクエスト/秒',
|
||||
finishReasonLabels: {
|
||||
@@ -4282,9 +4291,12 @@ const message = {
|
||||
familyIncomplete: '{0} のチェーンが不完全です',
|
||||
resetWithBackup: 'ルールをバックアップしてリセット',
|
||||
resetOnly: 'リセットのみ',
|
||||
resetWithBackupHelper: '現在のモジュールのルールを JSON に保存し、初期化時に復元できます。',
|
||||
resetWithBackupHelper: '現在のモジュールのルールを JSON に保存し、初期化時または起動時に復元できます。',
|
||||
resetOnlyHelper: '新しいバックアップを作成せずに現在のルールを消去します。既存のバックアップは保持されます。',
|
||||
backupDirectoryHelper: '{0} 内の有効なルールバックアップを表示します。既定では最新のファイルが選択されます。',
|
||||
startOnly: '起動のみ',
|
||||
startAndRestore: '起動してバックアップから復元',
|
||||
startWithBackupHelper: '通常起動するか、既存の JSON バックアップを選んで起動とルールの復元を行います。',
|
||||
restoreAllRules: '初期化してルールを復元',
|
||||
importRuleBackup: 'バックアップからインポート',
|
||||
initializeOnly: '初期化のみ',
|
||||
|
||||
@@ -487,6 +487,7 @@ const message = {
|
||||
recommend: '추천',
|
||||
dir: '디렉토리',
|
||||
alias: '별칭',
|
||||
quickDirDelete: '({0})을(를) 삭제하시겠습니까?',
|
||||
quickDir: '빠른 액세스',
|
||||
minQuickJump: '최소 하나의 빠른 액세스 항목을 추가하세요',
|
||||
maxQuickJump: '빠른 액세스를 최대 4개까지 추가할 수 있습니다',
|
||||
@@ -1490,6 +1491,14 @@ const message = {
|
||||
extraHeaders: '추가 헤더',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '현재 동시 요청',
|
||||
sampleMoment: '샘플링 시점',
|
||||
sampleWindow: '집계 구간',
|
||||
requestUnit: '개 요청',
|
||||
processing: '처리 중',
|
||||
queued: '대기 중',
|
||||
requestTiming: '요청 소요 시간',
|
||||
completedRequests: '구간 내 완료 요청 수',
|
||||
tokensPerSecond: 'tokens/초',
|
||||
requestsPerSecond: '요청/초',
|
||||
finishReasonLabels: {
|
||||
@@ -4204,9 +4213,12 @@ const message = {
|
||||
familyIncomplete: '{0} 체인이 불완전합니다',
|
||||
resetWithBackup: '규칙 백업 후 초기화',
|
||||
resetOnly: '초기화만 수행',
|
||||
resetWithBackupHelper: '현재 모듈의 규칙을 JSON으로 저장하여 초기화 시 복원할 수 있습니다.',
|
||||
resetWithBackupHelper: '현재 모듈의 규칙을 JSON으로 저장하여 초기화 또는 시작 시 복원할 수 있습니다.',
|
||||
resetOnlyHelper: '새 백업 없이 현재 규칙을 삭제합니다. 기존 백업은 유지됩니다.',
|
||||
backupDirectoryHelper: '{0}의 유효한 규칙 백업을 표시합니다. 기본적으로 최신 파일이 선택됩니다.',
|
||||
startOnly: '시작만',
|
||||
startAndRestore: '시작 및 백업에서 복원',
|
||||
startWithBackupHelper: '방화벽을 일반적으로 시작하거나 기존 JSON 백업을 선택하여 시작하고 규칙을 복원합니다.',
|
||||
restoreAllRules: '초기화 및 규칙 복원',
|
||||
importRuleBackup: '백업에서 가져오기',
|
||||
initializeOnly: '초기화만 수행',
|
||||
|
||||
@@ -487,6 +487,7 @@ const message = {
|
||||
recommend: 'ແນະນຳ',
|
||||
dir: 'ໄດເຣັກທໍຣີ',
|
||||
alias: 'ຊື່ຫຼິ້ນ',
|
||||
quickDirDelete: 'ຢືນຢັນການລຶບ ({0}) ຫຼືບໍ່?',
|
||||
quickDir: 'ເຂົ້າເຖິງດ່ວນ',
|
||||
minQuickJump: 'ເພີ່ມການເຂົ້າເຖິງດ່ວນຢ່າງໜ້ອຍໜຶ່ງລາຍການ',
|
||||
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດສີ່ລາຍການ',
|
||||
@@ -1484,6 +1485,14 @@ const message = {
|
||||
extraHeaders: 'Headers ເພີ່ມເຕີມ',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'ຄຳຮ້ອງຂໍພ້ອມກັນໃນປັດຈຸບັນ',
|
||||
sampleMoment: 'ໃນເວລາເກັບຕົວຢ່າງ',
|
||||
sampleWindow: 'ຊ່ວງເວລາເກັບຕົວຢ່າງ',
|
||||
requestUnit: 'ຄຳຮ້ອງຂໍ',
|
||||
processing: 'ກຳລັງປະມວນຜົນ',
|
||||
queued: 'ກຳລັງລໍຖ້າ',
|
||||
requestTiming: 'ເວລາປະມວນຜົນຄຳຮ້ອງຂໍ',
|
||||
completedRequests: 'ຈຳນວນຄຳຮ້ອງຂໍທີ່ສຳເລັດໃນຊ່ວງເວລາ',
|
||||
tokensPerSecond: 'tokens/ວິນາທີ',
|
||||
requestsPerSecond: 'ຄຳຮ້ອງຂໍ/ວິນາທີ',
|
||||
finishReasonLabels: {
|
||||
@@ -4173,9 +4182,12 @@ const message = {
|
||||
familyIncomplete: 'ຊຸດກົດ {0} ບໍ່ຄົບຖ້ວນ',
|
||||
resetWithBackup: 'ສຳຮອງກົດແລ້ວຣີເຊັດ',
|
||||
resetOnly: 'ຣີເຊັດເທົ່ານັ້ນ',
|
||||
resetWithBackupHelper: 'ບັນທຶກກົດຂອງໂມດູນປັດຈຸບັນເປັນ JSON ເພື່ອກູ້ຄືນເມື່ອເລີ່ມຕົ້ນ.',
|
||||
resetWithBackupHelper: 'ບັນທຶກກົດຂອງໂມດູນປັດຈຸບັນເປັນ JSON ເພື່ອກູ້ຄືນເມື່ອເລີ່ມຕົ້ນ ຫຼື ເປີດໃຊ້ໄຟວໍ.',
|
||||
resetOnlyHelper: 'ລຶບກົດປັດຈຸບັນໂດຍບໍ່ສ້າງໄຟລ໌ສຳຮອງໃໝ່. ໄຟລ໌ສຳຮອງເກົ່າຍັງຄົງຢູ່.',
|
||||
backupDirectoryHelper: 'ສະແດງໄຟລ໌ສຳຮອງກົດທີ່ໃຊ້ໄດ້ໃນ {0}. ເລືອກໄຟລ໌ຫຼ້າສຸດເປັນຄ່າເລີ່ມຕົ້ນ.',
|
||||
startOnly: 'ເລີ່ມຕົ້ນເທົ່ານັ້ນ',
|
||||
startAndRestore: 'ເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນຈາກຂໍ້ມູນສຳຮອງ',
|
||||
startWithBackupHelper: 'ເປີດໄຟວໍຕາມປົກກະຕິ ຫຼື ເລືອກຂໍ້ມູນສຳຮອງ JSON ທີ່ມີຢູ່ເພື່ອເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນກົດ.',
|
||||
restoreAllRules: 'ເລີ່ມຕົ້ນ ແລະ ກູ້ຄືນກົດ',
|
||||
importRuleBackup: 'ນຳເຂົ້າຈາກຂໍ້ມູນສຳຮອງ',
|
||||
initializeOnly: 'ເລີ່ມຕົ້ນເທົ່ານັ້ນ',
|
||||
|
||||
@@ -499,6 +499,7 @@ const message = {
|
||||
recommend: 'Disyorkan',
|
||||
dir: 'Direktori',
|
||||
alias: 'Alias',
|
||||
quickDirDelete: 'Sahkan pemadaman ({0})?',
|
||||
quickDir: 'Akses Pantas',
|
||||
minQuickJump: 'Tambah sekurang-kurangnya satu entri akses pantas',
|
||||
maxQuickJump: 'Anda boleh menambah sehingga empat entri akses pantas',
|
||||
@@ -1531,6 +1532,14 @@ const message = {
|
||||
extraHeaders: 'Pengepala Tambahan',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Permintaan serentak semasa',
|
||||
sampleMoment: 'Pada masa pensampelan',
|
||||
sampleWindow: 'Selang pensampelan',
|
||||
requestUnit: 'permintaan',
|
||||
processing: 'Sedang diproses',
|
||||
queued: 'Dalam baris gilir',
|
||||
requestTiming: 'Tempoh permintaan',
|
||||
completedRequests: 'Permintaan selesai dalam selang masa',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Permintaan/s',
|
||||
finishReasonLabels: {
|
||||
@@ -4362,10 +4371,15 @@ const message = {
|
||||
familyIncomplete: 'Rantaian {0} tidak lengkap',
|
||||
resetWithBackup: 'Sandarkan peraturan dan tetapkan semula',
|
||||
resetOnly: 'Tetapkan semula sahaja',
|
||||
resetWithBackupHelper: 'Simpan peraturan modul semasa sebagai JSON untuk dipulihkan semasa pemulaan.',
|
||||
resetWithBackupHelper:
|
||||
'Simpan peraturan modul semasa sebagai JSON untuk dipulihkan semasa pemulaan atau semasa tembok api dimulakan.',
|
||||
resetOnlyHelper: 'Padam peraturan semasa tanpa sandaran baharu. Sandaran sedia ada dikekalkan.',
|
||||
backupDirectoryHelper:
|
||||
'Sandaran peraturan yang sah dalam {0} disenaraikan di bawah. Fail terkini dipilih secara lalai.',
|
||||
startOnly: 'Mula sahaja',
|
||||
startAndRestore: 'Mula dan pulihkan daripada sandaran',
|
||||
startWithBackupHelper:
|
||||
'Mulakan tembok api seperti biasa atau pilih sandaran JSON sedia ada untuk memulakan dan memulihkan peraturan.',
|
||||
restoreAllRules: 'Mulakan dan pulihkan peraturan',
|
||||
importRuleBackup: 'Import daripada sandaran',
|
||||
initializeOnly: 'Mulakan sahaja',
|
||||
|
||||
@@ -492,6 +492,7 @@ const message = {
|
||||
recommend: 'Recomendado',
|
||||
dir: 'Diretório',
|
||||
alias: 'Apelido',
|
||||
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',
|
||||
@@ -1527,6 +1528,14 @@ const message = {
|
||||
extraHeaders: 'Cabeçalhos extras',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Concorrência atual',
|
||||
sampleMoment: 'No momento da coleta',
|
||||
sampleWindow: 'Intervalo de coleta',
|
||||
requestUnit: 'solicitações',
|
||||
processing: 'Em processamento',
|
||||
queued: 'Na fila',
|
||||
requestTiming: 'Tempo das solicitações',
|
||||
completedRequests: 'Solicitações concluídas no intervalo',
|
||||
tokensPerSecond: 'tokens/s',
|
||||
requestsPerSecond: 'Solicitações/s',
|
||||
finishReasonLabels: {
|
||||
@@ -4384,10 +4393,15 @@ const message = {
|
||||
familyIncomplete: 'As cadeias de {0} estão incompletas',
|
||||
resetWithBackup: 'Salvar regras e redefinir',
|
||||
resetOnly: 'Apenas redefinir',
|
||||
resetWithBackupHelper: 'Salve as regras do módulo atual em JSON para restaurá-las durante a inicialização.',
|
||||
resetWithBackupHelper:
|
||||
'Salve as regras do módulo atual em JSON para restaurá-las ao inicializar ou iniciar o firewall.',
|
||||
resetOnlyHelper: 'Limpar as regras atuais sem criar um novo backup. Os backups existentes são mantidos.',
|
||||
backupDirectoryHelper:
|
||||
'Os backups válidos de regras em {0} são listados abaixo. O mais recente é selecionado por padrão.',
|
||||
startOnly: 'Apenas iniciar',
|
||||
startAndRestore: 'Iniciar e restaurar do backup',
|
||||
startWithBackupHelper:
|
||||
'Inicie o firewall normalmente ou escolha um backup JSON existente para iniciar e restaurar regras.',
|
||||
restoreAllRules: 'Inicializar e restaurar regras',
|
||||
importRuleBackup: 'Importar do backup',
|
||||
initializeOnly: 'Apenas inicializar',
|
||||
|
||||
@@ -491,6 +491,7 @@ const message = {
|
||||
recommend: 'Рекомендуемое',
|
||||
dir: 'Каталог',
|
||||
alias: 'Псевдоним',
|
||||
quickDirDelete: 'Подтвердить удаление ({0})?',
|
||||
quickDir: 'Быстрый доступ',
|
||||
minQuickJump: 'Добавьте хотя бы одну запись быстрого доступа',
|
||||
maxQuickJump: 'Вы можете добавить до четырех записей быстрого доступа',
|
||||
@@ -1520,6 +1521,14 @@ const message = {
|
||||
extraHeaders: 'Дополнительные заголовки',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Текущие параллельные запросы',
|
||||
sampleMoment: 'На момент замера',
|
||||
sampleWindow: 'Интервал замера',
|
||||
requestUnit: 'запросов',
|
||||
processing: 'В обработке',
|
||||
queued: 'В очереди',
|
||||
requestTiming: 'Время обработки запросов',
|
||||
completedRequests: 'Завершённые запросы за интервал',
|
||||
tokensPerSecond: 'tokens/с',
|
||||
requestsPerSecond: 'Запросов/с',
|
||||
finishReasonLabels: {
|
||||
@@ -4350,10 +4359,15 @@ const message = {
|
||||
familyIncomplete: 'Цепочки {0} неполны',
|
||||
resetWithBackup: 'Сохранить правила и сбросить',
|
||||
resetOnly: 'Только сбросить',
|
||||
resetWithBackupHelper: 'Сохраните правила текущего модуля в JSON для восстановления при инициализации.',
|
||||
resetWithBackupHelper:
|
||||
'Сохраните правила текущего модуля в JSON для восстановления при инициализации или запуске.',
|
||||
resetOnlyHelper: 'Удалить текущие правила без создания новой копии. Существующие копии сохраняются.',
|
||||
backupDirectoryHelper:
|
||||
'Ниже перечислены действительные резервные копии правил из {0}. По умолчанию выбрана самая новая.',
|
||||
startOnly: 'Только запуск',
|
||||
startAndRestore: 'Запустить и восстановить из резервной копии',
|
||||
startWithBackupHelper:
|
||||
'Запустите брандмауэр как обычно или выберите существующую копию JSON для запуска и восстановления правил.',
|
||||
restoreAllRules: 'Инициализировать и восстановить правила',
|
||||
importRuleBackup: 'Импортировать из резервной копии',
|
||||
initializeOnly: 'Только инициализировать',
|
||||
|
||||
@@ -495,6 +495,7 @@ const message = {
|
||||
recommend: 'Önerilen',
|
||||
dir: 'Dizin',
|
||||
alias: 'Takma Ad',
|
||||
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',
|
||||
@@ -1527,6 +1528,14 @@ const message = {
|
||||
extraHeaders: 'Ek Başlıklar',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: 'Mevcut eşzamanlı istekler',
|
||||
sampleMoment: 'Örnekleme anında',
|
||||
sampleWindow: 'Örnekleme aralığı',
|
||||
requestUnit: 'istek',
|
||||
processing: 'İşleniyor',
|
||||
queued: 'Kuyrukta',
|
||||
requestTiming: 'İstek süreleri',
|
||||
completedRequests: 'Aralıkta tamamlanan istek sayısı',
|
||||
tokensPerSecond: 'tokens/sn',
|
||||
requestsPerSecond: 'İstek/sn',
|
||||
finishReasonLabels: {
|
||||
@@ -4368,10 +4377,15 @@ const message = {
|
||||
familyIncomplete: '{0} zincirleri eksik',
|
||||
resetWithBackup: 'Kuralları yedekle ve sıfırla',
|
||||
resetOnly: 'Yalnızca sıfırla',
|
||||
resetWithBackupHelper: 'Geçerli modül kurallarını başlatma sırasında geri yüklemek için JSON olarak kaydedin.',
|
||||
resetWithBackupHelper:
|
||||
'Geçerli modül kurallarını ilk kurulumda veya güvenlik duvarı başlatılırken geri yüklemek için JSON olarak kaydedin.',
|
||||
resetOnlyHelper: 'Yeni yedek oluşturmadan mevcut kuralları temizleyin. Önceki yedekler korunur.',
|
||||
backupDirectoryHelper:
|
||||
'{0} içindeki geçerli kural yedekleri listelenir. Varsayılan olarak en yeni dosya seçilir.',
|
||||
startOnly: 'Yalnızca başlat',
|
||||
startAndRestore: 'Başlat ve yedekten geri yükle',
|
||||
startWithBackupHelper:
|
||||
'Güvenlik duvarını normal başlatın veya başlatıp kuralları geri yüklemek için mevcut bir JSON yedeği seçin.',
|
||||
restoreAllRules: 'Başlat ve kuralları geri yükle',
|
||||
importRuleBackup: 'Yedekten içe aktar',
|
||||
initializeOnly: 'Yalnızca başlat',
|
||||
|
||||
@@ -473,6 +473,7 @@ const message = {
|
||||
recommend: '推薦',
|
||||
dir: '目錄',
|
||||
alias: '別名',
|
||||
quickDirDelete: '確定刪除({0})',
|
||||
quickDir: '快捷目錄',
|
||||
minQuickJump: '請至少設定一個快速跳轉入口',
|
||||
maxQuickJump: '最多可設定四個快速跳轉入口',
|
||||
@@ -1437,6 +1438,14 @@ const message = {
|
||||
extraHeaders: '額外請求標頭',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '目前並行請求',
|
||||
sampleMoment: '取樣時刻',
|
||||
sampleWindow: '統計區間',
|
||||
requestUnit: '個請求',
|
||||
processing: '處理中',
|
||||
queued: '排隊中',
|
||||
requestTiming: '請求耗時',
|
||||
completedRequests: '區間完成請求數',
|
||||
tokensPerSecond: 'tokens/秒',
|
||||
requestsPerSecond: '請求/秒',
|
||||
finishReasonLabels: {
|
||||
@@ -4010,9 +4019,12 @@ const message = {
|
||||
familyIncomplete: '{0} 鏈不完整',
|
||||
resetWithBackup: '備份規則後重置',
|
||||
resetOnly: '僅重置',
|
||||
resetWithBackupHelper: '將目前模組的規則儲存為 JSON,之後可在初始化時選擇備份還原。',
|
||||
resetWithBackupHelper: '將目前模組的規則儲存為 JSON,之後可在初始化或啟動時選擇備份還原。',
|
||||
resetOnlyHelper: '不建立新備份,直接清理目前規則;保留既有歷史備份。',
|
||||
backupDirectoryHelper: '自動讀取 {0} 下的有效規則備份,預設選擇最新檔案。',
|
||||
startOnly: '僅啟動',
|
||||
startAndRestore: '啟動並從備份還原',
|
||||
startWithBackupHelper: '可正常啟動防火牆,或選擇現有的 JSON 備份啟動並還原規則。',
|
||||
restoreAllRules: '初始化並還原規則',
|
||||
importRuleBackup: '從備份匯入',
|
||||
initializeOnly: '僅初始化',
|
||||
|
||||
@@ -473,6 +473,7 @@ const message = {
|
||||
recommend: '推荐',
|
||||
dir: '目录',
|
||||
alias: '别名',
|
||||
quickDirDelete: '确定删除({0})',
|
||||
quickDir: '快捷目录',
|
||||
minQuickJump: '请至少设置一个快速跳转入口!',
|
||||
maxQuickJump: '最多可设置四个快速跳转入口!',
|
||||
@@ -1450,6 +1451,14 @@ const message = {
|
||||
extraHeaders: '额外请求头',
|
||||
},
|
||||
vllmMonitor: {
|
||||
concurrency: '当前并发',
|
||||
sampleMoment: '采样时刻',
|
||||
sampleWindow: '统计区间',
|
||||
requestUnit: '个请求',
|
||||
processing: '处理中',
|
||||
queued: '排队中',
|
||||
requestTiming: '请求耗时',
|
||||
completedRequests: '区间完成请求数',
|
||||
tokensPerSecond: 'tokens/秒',
|
||||
requestsPerSecond: '请求/秒',
|
||||
finishReasonLabels: {
|
||||
@@ -4055,9 +4064,12 @@ const message = {
|
||||
familyIncomplete: '{0} 链不完整',
|
||||
resetWithBackup: '备份规则后重置',
|
||||
resetOnly: '仅重置',
|
||||
resetWithBackupHelper: '将当前模块的规则保存为 JSON,后续可在初始化时选择备份恢复。',
|
||||
resetWithBackupHelper: '将当前模块的规则保存为 JSON,后续可在初始化或启动时选择备份恢复。',
|
||||
resetOnlyHelper: '不生成新备份,直接清理当前规则;已有历史备份保留。',
|
||||
backupDirectoryHelper: '自动读取 {0} 下的有效规则备份,默认选择最新文件。',
|
||||
startOnly: '仅启动',
|
||||
startAndRestore: '启动并从备份恢复',
|
||||
startWithBackupHelper: '可正常启动防火墙,或选择已有的 JSON 备份启动并恢复规则。',
|
||||
restoreAllRules: '初始化并恢复规则',
|
||||
importRuleBackup: '从备份导入',
|
||||
initializeOnly: '仅初始化',
|
||||
|
||||
@@ -50,20 +50,12 @@
|
||||
<el-col :span="6" v-for="item in baseInfo.quickJump" :key="item.name">
|
||||
<span>{{ $t(item.title, 2) }}</span>
|
||||
<div class="count">
|
||||
<el-tooltip
|
||||
v-if="item.alias || item.detail.length > 20"
|
||||
:content="item.detail"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
:disabled="!checkPermission('File')"
|
||||
type="primary"
|
||||
@click="quickJump(item)"
|
||||
>
|
||||
{{ item.alias || item.detail.substring(0, 18) + '...' }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<QuickDirectories
|
||||
v-if="item.name === 'File'"
|
||||
:count="item.detail"
|
||||
:disabled="!checkPermission('File')"
|
||||
@change="updateQuickDirectoryCount"
|
||||
/>
|
||||
<el-button
|
||||
link
|
||||
:disabled="!checkPermission(item.name)"
|
||||
@@ -450,6 +442,7 @@ import SystemStatus from '@/views/home/status/index.vue';
|
||||
import AppLauncher from '@/views/home/app/index.vue';
|
||||
import VCharts from '@/components/v-charts/index.vue';
|
||||
import QuickJump from '@/views/home/quick/index.vue';
|
||||
import QuickDirectories from '@/views/home/quick/directories.vue';
|
||||
import CardWithHeader from '@/components/card-with-header/index.vue';
|
||||
import MarkDownEditor from '@/components/mkdown-editor/index.vue';
|
||||
import i18n from '@/lang';
|
||||
@@ -470,7 +463,7 @@ import {
|
||||
updateMemo,
|
||||
updateSetting,
|
||||
} from '@/api/modules/setting';
|
||||
import { routerToFileWithPath, routerToNameWithQuery, routerToPath } from '@/utils/router';
|
||||
import { routerToNameWithQuery, routerToPath } from '@/utils/router';
|
||||
import { getWelcomePage } from '@/api/modules/auth';
|
||||
import {
|
||||
clearDashboardCache,
|
||||
@@ -823,10 +816,12 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateQuickDirectoryCount = (count: number) => {
|
||||
const item = baseInfo.value.quickJump.find((item) => item.name === 'File');
|
||||
if (item) item.detail = String(count);
|
||||
};
|
||||
|
||||
const quickJump = (item: any) => {
|
||||
if (item.name === 'File') {
|
||||
return routerToFileWithPath(item.detail);
|
||||
}
|
||||
return routerToPath(item.router);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<el-popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
:width="680"
|
||||
:popper-style="{ maxWidth: 'calc(100vw - 32px)' }"
|
||||
:visible="visible"
|
||||
@update:visible="setVisible"
|
||||
:disabled="disabled"
|
||||
:title="$t('home.quickDir')"
|
||||
@show="load"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="primary" :disabled="disabled">{{ count }}</el-button>
|
||||
</template>
|
||||
<div v-loading="loading" class="quick-directories">
|
||||
<div class="directory-search">
|
||||
<el-input
|
||||
v-model="searchInput"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
:placeholder="`${$t('file.path')} / ${$t('home.alias')}`"
|
||||
:aria-label="$t('commons.button.search')"
|
||||
@keyup.enter="search"
|
||||
@clear="search"
|
||||
/>
|
||||
<el-button :disabled="loading" @click="search">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</div>
|
||||
<div class="directory-row directory-header">
|
||||
<span>{{ $t('file.path') }}</span>
|
||||
<span>{{ $t('home.alias') }}</span>
|
||||
<span />
|
||||
</div>
|
||||
<div class="directory-list">
|
||||
<div v-if="!filteredRows.length" class="directory-empty">{{ $t('commons.msg.noneData') }}</div>
|
||||
<div v-for="row in filteredRows" :key="row.key" class="directory-row">
|
||||
<el-input
|
||||
v-model="row.path"
|
||||
:readonly="!row.editing"
|
||||
:disabled="row.busy"
|
||||
:aria-label="$t('file.path')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button
|
||||
icon="Folder"
|
||||
:disabled="!row.editing || row.busy"
|
||||
:aria-label="$t('file.list')"
|
||||
@click="selectPath(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="row.alias"
|
||||
:readonly="!row.editing"
|
||||
:disabled="row.busy"
|
||||
:aria-label="$t('home.alias')"
|
||||
/>
|
||||
<div class="directory-actions">
|
||||
<el-button
|
||||
link
|
||||
:type="row.editing ? 'success' : 'primary'"
|
||||
:icon="row.editing ? 'Check' : 'Edit'"
|
||||
:aria-label="$t(row.editing ? 'commons.button.save' : 'commons.button.edit')"
|
||||
:disabled="loading || row.busy || (row.editing && !row.path.trim())"
|
||||
@click="row.editing ? save(row) : (row.editing = true)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="row.id || rows.length > 1"
|
||||
link
|
||||
:type="row.editing ? 'info' : 'danger'"
|
||||
:icon="row.editing ? 'Close' : 'Delete'"
|
||||
:aria-label="$t('commons.button.delete')"
|
||||
:disabled="loading || row.busy"
|
||||
@click="remove(row)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="row.id"
|
||||
link
|
||||
type="primary"
|
||||
icon="Position"
|
||||
:title="$t('firewall.quickJump')"
|
||||
:aria-label="$t('firewall.quickJump')"
|
||||
:disabled="loading || row.busy"
|
||||
@click="routerToFileWithPath(row.savedPath)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="directory-add"
|
||||
link
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
:disabled="loading || !loaded"
|
||||
@click="add"
|
||||
>
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="directory-footer">
|
||||
<el-button @click="close">{{ $t('commons.button.close') }}</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<FileList ref="fileRef" @choose="loadPath" @close="selectingRow = undefined" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { Dashboard } from '@/api/interface/dashboard';
|
||||
import {
|
||||
loadQuickDirectories,
|
||||
createQuickDirectory,
|
||||
updateQuickDirectory,
|
||||
deleteQuickDirectory,
|
||||
} from '@/api/modules/dashboard';
|
||||
|
||||
defineProps<{ count: string; disabled: boolean }>();
|
||||
const emit = defineEmits<{ (e: 'change', count: number): void }>();
|
||||
type DirectoryRow = Dashboard.QuickDirectory & {
|
||||
key: number;
|
||||
savedPath: string;
|
||||
editing: boolean;
|
||||
busy: boolean;
|
||||
};
|
||||
const rows = ref<DirectoryRow[]>([]);
|
||||
const searchInput = ref('');
|
||||
const searchTerm = ref('');
|
||||
const filteredRows = computed(() =>
|
||||
rows.value.filter(
|
||||
(row) =>
|
||||
!row.id ||
|
||||
row.editing ||
|
||||
row.path.toLowerCase().includes(searchTerm.value) ||
|
||||
(row.alias ?? '').toLowerCase().includes(searchTerm.value),
|
||||
),
|
||||
);
|
||||
const search = () => {
|
||||
searchTerm.value = searchInput.value.trim().toLowerCase();
|
||||
};
|
||||
const fileRef = ref<InstanceType<typeof FileList>>();
|
||||
const selectingRow = ref<DirectoryRow>();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
let nextKey = 0;
|
||||
|
||||
const setVisible = (value: boolean) => {
|
||||
if (value) visible.value = true;
|
||||
};
|
||||
const close = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const selectPath = (row: DirectoryRow) => {
|
||||
if (!row.editing || row.busy) return;
|
||||
selectingRow.value = row;
|
||||
fileRef.value?.acceptParams({ path: row.path, dir: false, isAll: true });
|
||||
};
|
||||
const loadPath = (path: string) => {
|
||||
const row = selectingRow.value;
|
||||
if (row?.editing && !row.busy) row.path = path;
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
rows.value.push({
|
||||
key: nextKey++,
|
||||
id: 0,
|
||||
path: '',
|
||||
alias: '',
|
||||
savedPath: '',
|
||||
editing: true,
|
||||
busy: false,
|
||||
});
|
||||
};
|
||||
const updateCount = () => emit('change', rows.value.filter((row) => row.id).length);
|
||||
const load = async () => {
|
||||
if (loading.value || rows.value.some((row) => row.busy)) return;
|
||||
loading.value = true;
|
||||
loaded.value = false;
|
||||
searchInput.value = '';
|
||||
searchTerm.value = '';
|
||||
rows.value = [];
|
||||
try {
|
||||
const { data } = await loadQuickDirectories();
|
||||
rows.value = data.map((item) => ({
|
||||
...item,
|
||||
alias: item.alias ?? '',
|
||||
key: nextKey++,
|
||||
savedPath: item.path,
|
||||
editing: false,
|
||||
busy: false,
|
||||
}));
|
||||
if (!rows.value.length) add();
|
||||
loaded.value = true;
|
||||
updateCount();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
const save = async (row: DirectoryRow) => {
|
||||
if (row.busy || !row.path.trim()) return;
|
||||
row.busy = true;
|
||||
try {
|
||||
const payload = { path: row.path, alias: (row.alias ?? '').trim() };
|
||||
const { data } = row.id
|
||||
? await updateQuickDirectory({ id: row.id, legacy: row.legacy ?? false, ...payload })
|
||||
: await createQuickDirectory(payload);
|
||||
Object.assign(row, data, { savedPath: data.path, editing: false });
|
||||
updateCount();
|
||||
} finally {
|
||||
row.busy = false;
|
||||
}
|
||||
};
|
||||
const remove = async (row: DirectoryRow) => {
|
||||
if (row.busy) return;
|
||||
if (row.id) {
|
||||
row.busy = true;
|
||||
try {
|
||||
await ElMessageBox.confirm('', i18n.global.t('home.quickDirDelete', [row.savedPath]), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
customClass: 'quick-directory-delete',
|
||||
type: 'warning',
|
||||
center: true,
|
||||
});
|
||||
} catch {
|
||||
row.busy = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteQuickDirectory(row.id, row.legacy ?? false);
|
||||
} catch (error) {
|
||||
row.busy = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
rows.value = rows.value.filter((item) => item.key !== row.key);
|
||||
if (!rows.value.length) add();
|
||||
updateCount();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.quick-directories {
|
||||
padding: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.directory-search {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
.directory-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.directory-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 96px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.directory-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
.directory-list {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.directory-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
.directory-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.directory-add {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.quick-directory-delete {
|
||||
.el-message-box__title {
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 4px;
|
||||
background-color: #59472a;
|
||||
color: #f5cf87;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.el-message-box__status {
|
||||
flex-shrink: 0;
|
||||
font-size: 24px;
|
||||
color: #f2b329;
|
||||
}
|
||||
.el-message-box__content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,25 +10,6 @@
|
||||
<el-table-column prop="isShow" :label="$t('setting.ifShow')">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isShow" />
|
||||
<div v-if="row.name === 'File' && row.isShow">
|
||||
<el-input v-model="row.detail" class="w-full">
|
||||
<template #prepend>
|
||||
<el-button
|
||||
class="w-16"
|
||||
v-if="row.name === 'File' && row.isShow"
|
||||
icon="Folder"
|
||||
@click="fileRef.acceptParams({ path: row.detail, isAll: true })"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="row.alias" class="mt-1">
|
||||
<template #prepend>
|
||||
<el-button class="w-16">
|
||||
{{ $t('home.alias') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ComplexTable>
|
||||
@@ -39,14 +20,12 @@
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<FileList ref="fileRef" @choose="loadDir" />
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { changeQuick, loadQuickOption } from '@/api/modules/dashboard';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
@@ -57,7 +36,6 @@ const acceptParams = (): void => {
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const quickOptions = ref([]);
|
||||
const fileRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
@@ -86,26 +64,17 @@ const onChangeShow = async () => {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
await changeQuick(quickOptions.value)
|
||||
await changeQuick(quickOptions.value.map(({ id, isShow }) => ({ id, isShow })))
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
drawerVisible.value = false;
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadDir = async (path: string) => {
|
||||
for (const item of quickOptions.value) {
|
||||
if (item.name === 'File') {
|
||||
item.detail = path;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
|
||||
@@ -149,3 +149,97 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.firewall-initialize-dialog.el-dialog,
|
||||
.firewall-lifecycle-dialog.el-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: min(680px, calc(100vw - 32px));
|
||||
max-height: calc(92dvh - 24px);
|
||||
margin-top: max(24px, 8vh);
|
||||
margin-bottom: 24px;
|
||||
|
||||
.el-dialog__body {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__content {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.firewall-initialize-dialog__description {
|
||||
margin: 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__docker {
|
||||
padding: 14px 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--el-fill-color-light);
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
height: auto;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
display: block;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.el-radio-group {
|
||||
gap: 4px 24px;
|
||||
}
|
||||
|
||||
.el-radio {
|
||||
height: auto;
|
||||
min-height: 28px;
|
||||
margin-right: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__docker-helper {
|
||||
margin: 6px 0 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__confirmation {
|
||||
padding: 14px 0 16px;
|
||||
color: var(--el-text-color-regular);
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__files {
|
||||
max-height: min(240px, 30vh);
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__modes {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.firewall-initialize-dialog__backups {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.firewall-rule-dialog__file-name > .min-w-0 {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
<template>
|
||||
<DialogPro
|
||||
v-model="visible"
|
||||
class="firewall-rule-dialog"
|
||||
:title="initialize ? $t('commons.button.init') : $t('firewall.importRuleBackup')"
|
||||
class="firewall-rule-dialog firewall-initialize-dialog"
|
||||
:title="
|
||||
starting
|
||||
? $t('firewall.firewallHelper', [$t('commons.button.start')])
|
||||
: initialize
|
||||
? $t('commons.button.init')
|
||||
: $t('firewall.importRuleBackup')
|
||||
"
|
||||
:show-close="!loading"
|
||||
:close-on-press-escape="!loading"
|
||||
>
|
||||
<template #content>
|
||||
<div v-loading="loading" class="firewall-rule-dialog__content">
|
||||
<el-alert v-if="message" :title="message" type="warning" :closable="false" show-icon />
|
||||
<slot name="before-options" />
|
||||
<p v-if="message && starting" class="firewall-initialize-dialog__description">{{ message }}</p>
|
||||
<el-alert v-else-if="message" :title="message" type="warning" :closable="false" show-icon />
|
||||
<el-alert
|
||||
v-if="backupLoadFailed"
|
||||
:title="$t('firewall.ruleBackupLoadFailed')"
|
||||
@@ -24,56 +34,61 @@
|
||||
class="firewall-rule-dialog__modes"
|
||||
:disabled="loading"
|
||||
>
|
||||
<el-radio value="restore" border :disabled="!loaded || backups.files.length === 0">
|
||||
{{ $t('firewall.restoreAllRules') }}
|
||||
<el-radio value="initialize" border>
|
||||
<span class="firewall-rule-dialog__choice-title">{{ initializeLabel }}</span>
|
||||
</el-radio>
|
||||
<el-radio value="restore" border>
|
||||
<span class="firewall-rule-dialog__choice-title">{{ restoreLabel }}</span>
|
||||
</el-radio>
|
||||
<el-radio value="initialize" border>{{ $t('firewall.initializeOnly') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="loaded" class="firewall-rule-dialog__directory">
|
||||
<div v-if="loaded && mode === 'restore'" class="firewall-rule-dialog__directory">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
<span>{{ $t('firewall.backupDirectoryHelper', [backups.directory]) }}</span>
|
||||
</div>
|
||||
<el-radio-group
|
||||
v-if="mode === 'restore' && backups.files.length"
|
||||
v-model="selected"
|
||||
class="firewall-rule-dialog__files"
|
||||
:disabled="loading"
|
||||
:aria-label="$t('firewall.importRuleBackup')"
|
||||
>
|
||||
<el-radio v-for="file in backups.files" :key="file.name" :value="file.name" border>
|
||||
<span class="firewall-rule-dialog__file-name">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="min-w-0">{{ file.name }}</span>
|
||||
<el-button
|
||||
v-permission
|
||||
class="shrink-0"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
:disabled="loading"
|
||||
:aria-label="`${$t('firewall.downloadRuleBackup')}: ${file.name}`"
|
||||
@click.stop.prevent="
|
||||
downloadFile(`${backups.directory}/${file.name}`, globalStore.currentNode)
|
||||
"
|
||||
>
|
||||
{{ $t('commons.button.download') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<span class="firewall-rule-dialog__file-meta">
|
||||
<el-tag size="small" type="info" effect="plain">{{ file.provider }}</el-tag>
|
||||
<span>{{ $t('firewall.backupRuleCount') }}: {{ file.ruleCount }}</span>
|
||||
<span>{{ new Date(file.modifiedAt).toLocaleString() }}</span>
|
||||
</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-empty
|
||||
v-else-if="loaded && !backups.files.length"
|
||||
:description="$t('firewall.noRuleBackup')"
|
||||
:image-size="64"
|
||||
/>
|
||||
<div v-if="mode === 'restore'" class="firewall-initialize-dialog__backups">
|
||||
<el-radio-group
|
||||
v-if="backups.files.length"
|
||||
v-model="selected"
|
||||
class="firewall-rule-dialog__files"
|
||||
:disabled="loading"
|
||||
:aria-label="$t('firewall.importRuleBackup')"
|
||||
>
|
||||
<el-radio v-for="file in backups.files" :key="file.name" :value="file.name" border>
|
||||
<span class="firewall-rule-dialog__file-name">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="min-w-0">{{ file.name }}</span>
|
||||
<el-button
|
||||
v-permission
|
||||
class="shrink-0"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
:disabled="loading"
|
||||
:aria-label="`${$t('firewall.downloadRuleBackup')}: ${file.name}`"
|
||||
@click.stop.prevent="
|
||||
downloadFile(`${backups.directory}/${file.name}`, globalStore.currentNode)
|
||||
"
|
||||
>
|
||||
{{ $t('commons.button.download') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<span class="firewall-rule-dialog__file-meta">
|
||||
<el-tag size="small" type="info" effect="plain">{{ file.provider }}</el-tag>
|
||||
<span>{{ $t('firewall.backupRuleCount') }}: {{ file.ruleCount }}</span>
|
||||
<span>{{ new Date(file.modifiedAt).toLocaleString() }}</span>
|
||||
</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-empty
|
||||
v-else-if="!initialize && loaded && !backups.files.length"
|
||||
:description="$t('firewall.noRuleBackup')"
|
||||
:image-size="64"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<slot name="confirmation" />
|
||||
<el-button :disabled="loading" @click="visible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -81,13 +96,7 @@
|
||||
:disabled="loading || (mode === 'restore' && (!loaded || !selected))"
|
||||
@click="submit"
|
||||
>
|
||||
{{
|
||||
initialize
|
||||
? mode === 'restore'
|
||||
? $t('firewall.restoreAllRules')
|
||||
: $t('firewall.initializeOnly')
|
||||
: $t('commons.button.import')
|
||||
}}
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</DialogPro>
|
||||
@@ -95,7 +104,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { Document, FolderOpened } from '@element-plus/icons-vue';
|
||||
import DialogPro from '@/components/dialog-pro/index.vue';
|
||||
import { Firewall } from '@/api/interface/firewall';
|
||||
@@ -110,13 +120,24 @@ import { GlobalStore } from '@/store';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const emit = defineEmits<{ (event: 'initialize'): void; (event: 'complete'): void }>();
|
||||
const emit = defineEmits<{
|
||||
(event: 'initialize'): void;
|
||||
(event: 'restore', backupFile: string): void;
|
||||
(event: 'complete'): void;
|
||||
}>();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
const backupLoadFailed = ref(false);
|
||||
const message = ref('');
|
||||
const initialize = ref(true);
|
||||
const starting = ref(false);
|
||||
const initializeLabel = computed(() =>
|
||||
i18n.global.t(starting.value ? 'firewall.startOnly' : 'firewall.initializeOnly'),
|
||||
);
|
||||
const restoreLabel = computed(() =>
|
||||
i18n.global.t(starting.value ? 'firewall.startAndRestore' : 'firewall.restoreAllRules'),
|
||||
);
|
||||
const subsystem = ref<Firewall.BackendSubsystem>('system');
|
||||
const mode = ref<'restore' | 'initialize'>('initialize');
|
||||
const selected = ref('');
|
||||
@@ -127,8 +148,10 @@ const acceptParams = async (
|
||||
warning: string,
|
||||
withInitialization = true,
|
||||
target: Firewall.BackendSubsystem = 'system',
|
||||
start = false,
|
||||
) => {
|
||||
subsystem.value = target;
|
||||
starting.value = start;
|
||||
initialize.value = withInitialization;
|
||||
message.value = warning;
|
||||
visible.value = true;
|
||||
@@ -146,7 +169,7 @@ const loadBackups = async () => {
|
||||
try {
|
||||
backups.value = (await listFirewallRuleBackups(subsystem.value)).data;
|
||||
selected.value = backups.value.files[0]?.name || '';
|
||||
mode.value = selected.value || !initialize.value ? 'restore' : 'initialize';
|
||||
mode.value = !starting.value && (selected.value || !initialize.value) ? 'restore' : 'initialize';
|
||||
loaded.value = true;
|
||||
} catch {
|
||||
backupLoadFailed.value = true;
|
||||
@@ -163,6 +186,11 @@ const submit = async () => {
|
||||
return;
|
||||
}
|
||||
if (!loaded.value || !selected.value) return;
|
||||
if (starting.value) {
|
||||
visible.value = false;
|
||||
emit('restore', selected.value);
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const response =
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
v-node-admin
|
||||
type="primary"
|
||||
:disabled="lifecycleBusy"
|
||||
@click="onOperate('start')"
|
||||
@click="onInit"
|
||||
link
|
||||
>
|
||||
{{ $t('commons.button.start') }}
|
||||
@@ -145,18 +145,58 @@
|
||||
:title="$t('firewall.directBackendConflictWarning', [backendName, baseInfo.conflictBackend])"
|
||||
/>
|
||||
</div>
|
||||
<DockerRestart
|
||||
ref="dockerRef"
|
||||
v-model:withDockerRestart="withDockerRestart"
|
||||
@submit="onSubmit"
|
||||
<DialogPro
|
||||
v-model="operationVisible"
|
||||
class="firewall-rule-dialog firewall-lifecycle-dialog"
|
||||
:title="$t('firewall.firewallHelper', [$t('commons.button.' + operation)])"
|
||||
>
|
||||
<template #helper>
|
||||
<span>{{ $t('firewall.' + operation + 'FirewallHelper') }}</span>
|
||||
<template #content>
|
||||
<el-form class="firewall-rule-dialog__docker" label-position="top" @submit.prevent>
|
||||
<el-form-item :label="$t('firewall.dockerRestart')">
|
||||
<el-radio-group v-model="withDockerRestart">
|
||||
<el-radio :value="true">{{ $t('setting.restartNow') }}</el-radio>
|
||||
<el-radio :value="false">{{ $t('setting.restartLater') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<p class="firewall-rule-dialog__docker-helper">
|
||||
{{ $t(withDockerRestart ? 'xpack.node.syncProxyHelper1' : 'xpack.node.syncProxyHelper2') }}
|
||||
</p>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</DockerRestart>
|
||||
<template #footer>
|
||||
<div class="firewall-rule-dialog__confirmation">
|
||||
<span>{{ $t('firewall.' + operation + 'FirewallHelper') }}</span>
|
||||
</div>
|
||||
<el-button @click="operationVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</template>
|
||||
</DialogPro>
|
||||
<TaskLog ref="taskLogRef" @close="handleInitializationTaskClose" />
|
||||
<RuleInitialize ref="initializeRef" @initialize="initializeOnly" @complete="loadBaseInfo" />
|
||||
<RuleInitialize
|
||||
ref="initializeRef"
|
||||
@initialize="initializeOnly"
|
||||
@restore="onOperate('start', $event)"
|
||||
@complete="loadBaseInfo"
|
||||
>
|
||||
<template v-if="isServiceBackend && dockerStatus" #before-options>
|
||||
<el-form class="firewall-rule-dialog__docker" label-position="top" @submit.prevent>
|
||||
<el-form-item :label="$t('firewall.dockerRestart')">
|
||||
<el-radio-group v-model="withDockerRestart">
|
||||
<el-radio :value="true">{{ $t('setting.restartNow') }}</el-radio>
|
||||
<el-radio :value="false">{{ $t('setting.restartLater') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<p class="firewall-rule-dialog__docker-helper">
|
||||
{{ $t(withDockerRestart ? 'xpack.node.syncProxyHelper1' : 'xpack.node.syncProxyHelper2') }}
|
||||
</p>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<template v-if="isServiceBackend" #confirmation>
|
||||
<div class="firewall-rule-dialog__confirmation">
|
||||
<span>{{ $t('firewall.startFirewallHelper') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</RuleInitialize>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -171,7 +211,7 @@ import {
|
||||
} from '@/api/modules/firewall';
|
||||
import i18n from '@/lang';
|
||||
import NoSuchService from '@/components/layout-content/no-such-service.vue';
|
||||
import DockerRestart from '@/components/docker-proxy/docker-restart.vue';
|
||||
import DialogPro from '@/components/dialog-pro/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { computed, nextTick, onBeforeUnmount, ref } from 'vue';
|
||||
@@ -187,7 +227,7 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const props = defineProps({
|
||||
currentTab: String,
|
||||
});
|
||||
const { docsUrl } = useGlobalStore();
|
||||
const { docsUrl, isIntl } = useGlobalStore();
|
||||
|
||||
const goToFirewallSetting = () => routerToName('FirewallSetting');
|
||||
const goToScriptLibrary = () => routerToNameWithQuery('Library', { uncached: 'true' });
|
||||
@@ -206,8 +246,9 @@ const baseInfo = ref<Firewall.FirewallBase>({
|
||||
ipv4: { available: false, initialized: false, bound: false },
|
||||
ipv6: { available: false, initialized: false, bound: false },
|
||||
});
|
||||
const dockerRef = ref();
|
||||
const operationVisible = ref(false);
|
||||
const operation = ref('restart');
|
||||
const restoreBackupFile = ref('');
|
||||
const dockerStatus = ref();
|
||||
const withDockerRestart = ref(false);
|
||||
const taskLogRef = ref();
|
||||
@@ -276,7 +317,7 @@ const familyActions = computed(() =>
|
||||
message: familyIssueText(issue),
|
||||
helpUrl:
|
||||
issue.available && issue.reason === 'ipv6_ra_required'
|
||||
? `${docsUrl.value}/user_manual/hosts/firewall`
|
||||
? `${docsUrl.value}/user_manual/${isIntl.value ? 'system' : 'hosts'}/firewall`
|
||||
: undefined,
|
||||
action,
|
||||
};
|
||||
@@ -365,12 +406,20 @@ const loadDocker = async () => {
|
||||
const initializeRef = ref<InstanceType<typeof RuleInitialize>>();
|
||||
|
||||
const onInit = async () => {
|
||||
if (lifecycleBusy.value) return;
|
||||
if (isServiceBackend.value) withDockerRestart.value = !!dockerStatus.value;
|
||||
const name = baseInfo.value.name || backendName.value;
|
||||
const message =
|
||||
props.currentTab === 'base' && baseInfo.value.conflictBackend
|
||||
? i18n.global.t('firewall.initDirectBackendConflictMsg', [name, baseInfo.value.conflictBackend])
|
||||
: i18n.global.t('firewall.initMsg', [name]);
|
||||
await initializeRef.value?.acceptParams(message, true, props.currentTab === 'forward' ? 'forwarding' : 'system');
|
||||
const message = isServiceBackend.value
|
||||
? i18n.global.t('firewall.startWithBackupHelper')
|
||||
: props.currentTab === 'base' && baseInfo.value.conflictBackend
|
||||
? i18n.global.t('firewall.initDirectBackendConflictMsg', [name, baseInfo.value.conflictBackend])
|
||||
: i18n.global.t('firewall.initMsg', [name]);
|
||||
await initializeRef.value?.acceptParams(
|
||||
message,
|
||||
true,
|
||||
props.currentTab === 'forward' ? 'forwarding' : 'system',
|
||||
isServiceBackend.value,
|
||||
);
|
||||
};
|
||||
|
||||
const initializeOnly = async () => {
|
||||
@@ -422,17 +471,22 @@ const onUnBind = async () => {
|
||||
await loadBaseInfo();
|
||||
};
|
||||
|
||||
const onOperate = async (op: string) => {
|
||||
const onOperate = async (op: string, backupFile = '') => {
|
||||
if (lifecycleBusy.value) return;
|
||||
operation.value = op;
|
||||
if (backendName.value === 'iptables' || backendName.value === 'nftables' || !dockerStatus.value) {
|
||||
restoreBackupFile.value = backupFile;
|
||||
if (op === 'start') {
|
||||
await submitLifecycleOperation(!!dockerStatus.value && withDockerRestart.value);
|
||||
} else if (backendName.value === 'iptables' || backendName.value === 'nftables' || !dockerStatus.value) {
|
||||
await submitLifecycleOperation(false);
|
||||
} else {
|
||||
dockerRef.value.acceptParams({ title: i18n.global.t('firewall.dockerRestart') });
|
||||
withDockerRestart.value = true;
|
||||
operationVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
operationVisible.value = false;
|
||||
await submitLifecycleOperation(withDockerRestart.value);
|
||||
};
|
||||
|
||||
@@ -447,7 +501,7 @@ const submitLifecycleOperation = async (restartDocker: boolean) => {
|
||||
lifecycleSubmitting.value = true;
|
||||
emit('update:loading', true);
|
||||
try {
|
||||
const result = (await operateFire(operation.value, restartDocker)).data;
|
||||
const result = (await operateFire(operation.value, restartDocker, restoreBackupFile.value || undefined)).data;
|
||||
if (disposed) return;
|
||||
if (result?.queued && result.taskID) {
|
||||
baseInfo.value.lifecycleTaskID = result.taskID;
|
||||
|
||||
Reference in New Issue
Block a user