mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 05:31:44 +08:00
feat: 防火墙完善
This commit is contained in:
@@ -3,8 +3,9 @@ package middleware
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
"github.com/TheTNB/panel/internal/app"
|
||||
)
|
||||
|
||||
// Status 检查程序状态
|
||||
|
||||
@@ -5,9 +5,11 @@ type FirewallStatus struct {
|
||||
}
|
||||
|
||||
type FirewallRule struct {
|
||||
PortStart uint `json:"port_start" validate:"required,gte=1,lte=65535"`
|
||||
PortEnd uint `json:"port_end" validate:"required,gte=1,lte=65535"`
|
||||
Protocols []string `json:"protocols" validate:"min=1,dive,oneof=tcp udp"`
|
||||
Address string `json:"address"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
Family string `json:"family" validate:"required,oneof=ipv4 ipv6"`
|
||||
PortStart uint `json:"port_start" validate:"required,gte=1,lte=65535"`
|
||||
PortEnd uint `json:"port_end" validate:"required,gte=1,lte=65535"`
|
||||
Protocol string `json:"protocol" validate:"min=1,oneof=tcp udp tcp/udp"`
|
||||
Address string `json:"address"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop reject"`
|
||||
Direction string `json:"direction"`
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func Bind[T any](r *http.Request) (*T, error) {
|
||||
if err := binder.Query(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Contains([]string{"POST", "PUT", "PATCH"}, strings.ToUpper(r.Method)) {
|
||||
if slices.Contains([]string{"POST", "PUT", "PATCH", "DELETE"}, strings.ToUpper(r.Method)) {
|
||||
if err := binder.Body(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/go-rat/chix"
|
||||
|
||||
@@ -80,13 +79,11 @@ func (s *FirewallService) CreateRule(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
for protocol := range slices.Values(req.Protocols) {
|
||||
if err = s.firewall.Port(firewall.FireInfo{
|
||||
PortStart: req.PortStart, PortEnd: req.PortEnd, Protocol: protocol, Address: req.Address, Strategy: req.Strategy,
|
||||
}, firewall.OperationAdd); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = s.firewall.Port(firewall.FireInfo{
|
||||
Family: req.Family, PortStart: req.PortStart, PortEnd: req.PortEnd, Protocol: req.Protocol, Address: req.Address, Strategy: req.Strategy, Direction: req.Direction,
|
||||
}, firewall.OperationAdd); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
@@ -99,13 +96,11 @@ func (s *FirewallService) DeleteRule(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
for protocol := range slices.Values(req.Protocols) {
|
||||
if err = s.firewall.Port(firewall.FireInfo{
|
||||
PortStart: req.PortStart, PortEnd: req.PortEnd, Protocol: protocol, Address: req.Address, Strategy: req.Strategy,
|
||||
}, firewall.OperationRemove); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = s.firewall.Port(firewall.FireInfo{
|
||||
Family: req.Family, PortStart: req.PortStart, PortEnd: req.PortEnd, Protocol: req.Protocol, Address: req.Address, Strategy: req.Strategy, Direction: req.Direction,
|
||||
}, firewall.OperationRemove); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
|
||||
@@ -6,7 +6,7 @@ type FireInfo struct {
|
||||
PortStart uint `json:"port_start"` // 1-65535
|
||||
PortEnd uint `json:"port_end"` // 1-65535
|
||||
Protocol string `json:"protocol"` // tcp udp tcp/udp
|
||||
Strategy string `json:"strategy"` // accept drop
|
||||
Strategy string `json:"strategy"` // accept drop reject
|
||||
Direction string `json:"direction"` // in out 入站或出站
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -28,7 +29,7 @@ type Firewall struct {
|
||||
func NewFirewall() *Firewall {
|
||||
firewall := &Firewall{
|
||||
forwardListRegex: regexp.MustCompile(`^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$`),
|
||||
richRuleRegex: regexp.MustCompile(`^rule family="([^"]+)" (?:source address="([^"]+)" )?(?:port port="([^"]+)" )?(?:protocol="([^"]+)" )?(accept|drop|reject)$`),
|
||||
richRuleRegex: regexp.MustCompile(`^rule family="([^"]+)"(?: .*?(source|destination) address="([^"]+)")?(?: .*?port port="([^"]+)")?(?: .*?protocol="([^"]+)")?.*?(accept|drop|reject)$`),
|
||||
}
|
||||
|
||||
return firewall
|
||||
@@ -71,7 +72,9 @@ func (r *Firewall) ListRule() ([]FireInfo, error) {
|
||||
}
|
||||
item.Protocol = ruleItem[1]
|
||||
}
|
||||
item.Family = "ipv4"
|
||||
item.Strategy = "accept"
|
||||
item.Direction = "in"
|
||||
data = append(data, item)
|
||||
}
|
||||
}()
|
||||
@@ -131,8 +134,8 @@ func (r *Firewall) ListRichRule() ([]FireInfo, error) {
|
||||
if len(rule) == 0 {
|
||||
continue
|
||||
}
|
||||
if itemRule, err := r.parseRichRule(rule); err == nil {
|
||||
data = append(data, *itemRule)
|
||||
if richRules, err := r.parseRichRule(rule); err == nil {
|
||||
data = append(data, richRules)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,40 +146,47 @@ func (r *Firewall) Port(rule FireInfo, operation Operation) error {
|
||||
if rule.PortEnd == 0 {
|
||||
rule.PortEnd = rule.PortStart
|
||||
}
|
||||
if rule.PortStart > rule.PortEnd {
|
||||
return fmt.Errorf("invalid port range: %d-%d", rule.PortStart, rule.PortEnd)
|
||||
}
|
||||
// 不支持的切换使用rich rules
|
||||
if rule.Direction != "in" || rule.Family != "ipv4" || rule.Address != "" || rule.Strategy != "accept" {
|
||||
return r.RichRules(rule, operation)
|
||||
}
|
||||
stdout, err := shell.Execf("firewall-cmd --zone=public --%s-port=%d-%d/%s --permanent", operation, rule.PortStart, rule.PortEnd, rule.Protocol)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s port %d-%d/%s failed, err: %s", operation, rule.PortStart, rule.PortEnd, rule.Protocol, stdout)
|
||||
|
||||
protocols := strings.Split(rule.Protocol, "/")
|
||||
for protocol := range slices.Values(protocols) {
|
||||
stdout, err := shell.Execf("firewall-cmd --zone=public --%s-port=%d-%d/%s --permanent", operation, rule.PortStart, rule.PortEnd, protocol)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s port %d-%d/%s failed, err: %s", operation, rule.PortStart, rule.PortEnd, protocol, stdout)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = shell.Execf("firewall-cmd --reload")
|
||||
_, err := shell.Execf("firewall-cmd --reload")
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Firewall) RichRules(rule FireInfo, operation Operation) error {
|
||||
families := strings.Split(rule.Family, "/") // ipv4 ipv6
|
||||
|
||||
for _, family := range families {
|
||||
protocols := strings.Split(rule.Protocol, "/")
|
||||
for protocol := range slices.Values(protocols) {
|
||||
var ruleBuilder strings.Builder
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`rule family="%s" `, family))
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`rule family="%s" `, rule.Family))
|
||||
|
||||
if len(rule.Address) != 0 {
|
||||
if rule.Direction == "in" {
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`source address="%s" `, rule.Address))
|
||||
} else if rule.Direction == "out" {
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`destination address="%s" `, rule.Address))
|
||||
} else {
|
||||
return fmt.Errorf("invalid direction: %s", rule.Direction)
|
||||
}
|
||||
}
|
||||
if rule.PortStart != 0 && rule.PortEnd != 0 {
|
||||
if rule.PortStart != 0 && rule.PortEnd != 0 && (rule.PortStart != 1 && rule.PortEnd != 65535) { // 1-65535是解析出来无端口规则的情况
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`port port="%d-%d" `, rule.PortStart, rule.PortEnd))
|
||||
}
|
||||
if len(rule.Protocol) != 0 {
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`protocol="%s" `, rule.Protocol))
|
||||
if operation == OperationRemove && protocol != "" && rule.Protocol != "tcp/udp" { // 删除操作,可以不指定协议
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`protocol="%s" `, protocol))
|
||||
}
|
||||
if operation == OperationAdd && protocol != "" {
|
||||
ruleBuilder.WriteString(fmt.Sprintf(`protocol="%s" `, protocol))
|
||||
}
|
||||
|
||||
ruleBuilder.WriteString(rule.Strategy)
|
||||
@@ -213,29 +223,46 @@ func (r *Firewall) PortForward(info Forward, operation Operation) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Firewall) parseRichRule(line string) (*FireInfo, error) {
|
||||
itemRule := new(FireInfo)
|
||||
if r.richRuleRegex.MatchString(line) {
|
||||
match := r.richRuleRegex.FindStringSubmatch(line)
|
||||
if len(match) < 6 {
|
||||
return nil, errors.New("invalid rich rule")
|
||||
}
|
||||
|
||||
itemRule.Family = match[1]
|
||||
itemRule.Address = match[2]
|
||||
ports := strings.Split(match[3], "-")
|
||||
if len(ports) > 1 {
|
||||
itemRule.PortStart = cast.ToUint(ports[0])
|
||||
itemRule.PortEnd = cast.ToUint(ports[1])
|
||||
} else {
|
||||
itemRule.PortStart = cast.ToUint(match[3])
|
||||
itemRule.PortEnd = cast.ToUint(match[3])
|
||||
}
|
||||
itemRule.Protocol = match[4]
|
||||
itemRule.Strategy = match[5]
|
||||
func (r *Firewall) parseRichRule(line string) (FireInfo, error) {
|
||||
if !r.richRuleRegex.MatchString(line) {
|
||||
return FireInfo{}, errors.New("invalid rich rule format")
|
||||
}
|
||||
|
||||
return itemRule, nil
|
||||
match := r.richRuleRegex.FindStringSubmatch(line)
|
||||
if len(match) < 7 {
|
||||
return FireInfo{}, errors.New("invalid rich rule")
|
||||
}
|
||||
|
||||
fireInfo := FireInfo{
|
||||
Family: match[1],
|
||||
Address: match[3],
|
||||
Protocol: match[5],
|
||||
Strategy: match[6],
|
||||
}
|
||||
|
||||
if match[2] == "destination" {
|
||||
fireInfo.Direction = "out"
|
||||
} else {
|
||||
fireInfo.Direction = "in"
|
||||
}
|
||||
if fireInfo.Protocol == "" {
|
||||
fireInfo.Protocol = "tcp/udp"
|
||||
}
|
||||
|
||||
ports := strings.Split(match[4], "-")
|
||||
if len(ports) == 2 { // 添加端口范围
|
||||
fireInfo.PortStart = cast.ToUint(ports[0])
|
||||
fireInfo.PortEnd = cast.ToUint(ports[1])
|
||||
} else if len(ports) == 1 && ports[0] != "" { // 添加单个端口
|
||||
port := cast.ToUint(ports[0])
|
||||
fireInfo.PortStart = port
|
||||
fireInfo.PortEnd = port
|
||||
} else if len(ports) == 1 && ports[0] == "" { // 未添加端口规则,表示所有端口
|
||||
fireInfo.PortStart = 1
|
||||
fireInfo.PortEnd = 65535
|
||||
}
|
||||
|
||||
return fireInfo, nil
|
||||
}
|
||||
|
||||
func (r *Firewall) enableForward() error {
|
||||
|
||||
@@ -8,16 +8,10 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TheTNB/panel/pkg/slice"
|
||||
)
|
||||
|
||||
// Execf 执行 shell 命令
|
||||
func Execf(shell string, args ...any) (string, error) {
|
||||
if !CheckArgs(slice.ToString(args)...) {
|
||||
return "", errors.New("发现危险的命令参数,中止执行")
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
@@ -36,10 +30,6 @@ func Execf(shell string, args ...any) (string, error) {
|
||||
|
||||
// ExecfAsync 异步执行 shell 命令
|
||||
func ExecfAsync(shell string, args ...any) error {
|
||||
if !CheckArgs(slice.ToString(args)...) {
|
||||
return errors.New("发现危险的命令参数,中止执行")
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
@@ -60,10 +50,6 @@ func ExecfAsync(shell string, args ...any) error {
|
||||
|
||||
// ExecfWithTimeout 执行 shell 命令并设置超时时间
|
||||
func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string, error) {
|
||||
if !CheckArgs(slice.ToString(args)...) {
|
||||
return "", errors.New("发现危险的命令参数,中止执行")
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(shell, args...))
|
||||
@@ -94,21 +80,3 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
|
||||
|
||||
return strings.TrimSpace(stdout.String()), err
|
||||
}
|
||||
|
||||
// CheckArgs 检查危险的参数
|
||||
func CheckArgs(args ...string) bool {
|
||||
if len(args) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
dangerous := []string{"&", "|", ";", "$", "'", `"`, "(", ")", "`", "\n", "\r", ">", "<", "{", "}", "[", "]", "\\"}
|
||||
for _, arg := range args {
|
||||
for _, char := range dangerous {
|
||||
if strings.Contains(arg, char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
"github.com/shirou/gopsutil/host"
|
||||
"github.com/shirou/gopsutil/load"
|
||||
"github.com/shirou/gopsutil/mem"
|
||||
"github.com/shirou/gopsutil/net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CurrentInfo 监控信息
|
||||
|
||||
@@ -11,12 +11,12 @@ export default {
|
||||
// 获取防火墙规则
|
||||
firewallRules: (page: number, limit: number): Promise<AxiosResponse<any>> =>
|
||||
request.get('/firewall/rule', { params: { page, limit } }),
|
||||
// 添加防火墙规则
|
||||
addFirewallRule: (port: number, protocol: string): Promise<AxiosResponse<any>> =>
|
||||
request.post('/firewall/rule', { port, protocol }),
|
||||
// 创建防火墙规则
|
||||
createFirewallRule: (rule: any): Promise<AxiosResponse<any>> =>
|
||||
request.post('/firewall/rule', rule),
|
||||
// 删除防火墙规则
|
||||
deleteFirewallRule: (port: number, protocol: string): Promise<AxiosResponse<any>> =>
|
||||
request.delete('/firewall/rule', { params: { port, protocol } }),
|
||||
deleteFirewallRule: (rule: any): Promise<AxiosResponse<any>> =>
|
||||
request.delete('/firewall/rule', { data: rule }),
|
||||
// 获取SSH
|
||||
ssh: (): Promise<AxiosResponse<any>> => request.get('/safe/ssh'),
|
||||
// 设置SSH
|
||||
|
||||
@@ -64,64 +64,6 @@
|
||||
"actions": "操作"
|
||||
}
|
||||
},
|
||||
"safeIndex": {
|
||||
"title": "系统安全",
|
||||
"buttons": {
|
||||
"delete": "删除",
|
||||
"add": "添加",
|
||||
"batchDelete": "批量删除"
|
||||
},
|
||||
"columns": {
|
||||
"port": "端口",
|
||||
"protocol": "协议",
|
||||
"actions": "操作"
|
||||
},
|
||||
"confirm": {
|
||||
"delete": "确定删除规则吗?",
|
||||
"batchDelete": " 高危操作!确定删除选中的规则吗?"
|
||||
},
|
||||
"alerts": {
|
||||
"undelete": "取消删除",
|
||||
"delete": "删除成功",
|
||||
"add": "添加成功",
|
||||
"setup": "设置成功",
|
||||
"select": "请选择要删除的规则",
|
||||
"ruleDelete": "规则 {rule} 删除成功"
|
||||
},
|
||||
"filter": {
|
||||
"fields": {
|
||||
"firewall": {
|
||||
"label": "防火墙状态",
|
||||
"checked": "开启",
|
||||
"unchecked": "关闭"
|
||||
},
|
||||
"ssh": {
|
||||
"label": "SSH状态",
|
||||
"checked": "开启",
|
||||
"unchecked": "关闭"
|
||||
},
|
||||
"ping": {
|
||||
"label": "Ping状态",
|
||||
"checked": "开启",
|
||||
"unchecked": "关闭"
|
||||
},
|
||||
"port": {
|
||||
"label": "SSH端口"
|
||||
}
|
||||
}
|
||||
},
|
||||
"portControl": {
|
||||
"title": "端口控制",
|
||||
"fields": {
|
||||
"port": {
|
||||
"placeholder": "例如:3306, 1000-2000"
|
||||
},
|
||||
"protocol": {
|
||||
"placeholder": "协议"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingIndex": {
|
||||
"title": "面板设置",
|
||||
"info": "修改面板端口 / 入口后,需要在浏览器地址栏做相应修改方可打开面板!",
|
||||
|
||||
@@ -228,7 +228,7 @@ onMounted(() => {
|
||||
<template #action>
|
||||
<div flex items-center>
|
||||
<n-button class="ml-16" type="primary" @click="handleUpdateCache">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:refresh" />
|
||||
<TheIcon :size="18" icon="material-symbols:refresh" />
|
||||
{{ $t('appIndex.buttons.updateCache') }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
@@ -309,7 +309,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveWhiteList"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存白名单
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -318,7 +318,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="addJailModal = true"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
添加规则
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -338,32 +338,24 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 Fail2ban 会导致所有规则失效,确定停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" class="mr-5" icon="material-symbols:refresh-rounded" />
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -112,28 +112,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart('frps')">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop('frps')">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
确定要停止 Frps 吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart('frps')">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
@@ -142,7 +134,7 @@ onMounted(() => {
|
||||
<n-card title="修改配置" rounded-10>
|
||||
<template #header-extra>
|
||||
<n-button type="primary" @click="handleSaveConfig('frps')">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline-rounded" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -176,28 +168,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart('frpc')">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop('frpc')">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
确定要停止 Frpc 吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart('frpc')">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
@@ -206,7 +190,7 @@ onMounted(() => {
|
||||
<n-card title="修改配置" rounded-10>
|
||||
<template #header-extra>
|
||||
<n-button type="primary" @click="handleSaveConfig('frpc')">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline-rounded" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
@@ -82,7 +82,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -101,24 +101,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon :size="24" class="mr-5" icon="material-symbols:stop-outline-rounded" />
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
确定要停止 Gitea 吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -154,7 +154,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -163,7 +163,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleClearErrorLog"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空日志
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -172,7 +172,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleClearSlowLog"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空慢日志
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -192,32 +192,24 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 MySQL 会导致使用 MySQL 的网站无法访问,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" class="mr-5" icon="material-symbols:refresh-rounded" />
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -125,7 +125,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -134,7 +134,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleClearErrorLog"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空日志
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -153,28 +153,24 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon :size="24" class="mr-5" icon="material-symbols:stop-outline-rounded" />
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 OpenResty 会导致所有网站无法访问,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" class="mr-5" icon="material-symbols:refresh-rounded" />
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -242,7 +242,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -251,7 +251,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveFPMConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -260,7 +260,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleClearErrorLog"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空错误日志
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -269,7 +269,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleClearSlowLog"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空慢日志
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -289,32 +289,24 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 PHP {{ version }} 会导致使用 PHP {{ version }} 的网站无法访问,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" class="mr-5" icon="material-symbols:refresh-rounded" />
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -50,7 +50,7 @@ onMounted(() => {
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button v-if="currentTab == 'status'" class="ml-16" type="primary" @click="handleSave">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -59,7 +59,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
@@ -91,7 +91,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveRegistryConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -100,7 +100,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveStorageConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -123,28 +123,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
确定要停止 Podman 吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -136,7 +136,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -145,11 +145,11 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveUserConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'log'" class="ml-16" type="primary" @click="handleClearLog">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空日志
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -169,32 +169,24 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 PostgreSQL 会导致使用 PostgreSQL 的网站无法访问,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" class="mr-5" icon="material-symbols:refresh-rounded" />
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -222,7 +222,7 @@ onMounted(() => {
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button v-if="currentTab == 'status'" class="ml-16" type="primary" @click="handleSavePort">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -231,7 +231,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="addUserModal = true"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
添加用户
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -251,28 +251,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 Pure-Ftpd 会导致无法使用 FTP 服务,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -100,7 +100,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -120,28 +120,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 Redis 会导致使用 Redis 的网站无法访问,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -244,7 +244,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -253,7 +253,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="addModuleModal = true"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
添加模块
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -273,28 +273,20 @@ onMounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 Rsync 服务后,将无法使用 Rsync 功能,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -116,7 +116,7 @@ onMounted(() => {
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button class="ml-16" type="primary" @click="addMountModal = true">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
添加挂载
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
@@ -368,7 +368,7 @@ onUnmounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveConfig"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -377,11 +377,11 @@ onUnmounted(() => {
|
||||
type="primary"
|
||||
@click="addProcessModal = true"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
添加进程
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'log'" class="ml-16" type="primary" @click="handleClearLog">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清空日志
|
||||
</n-button>
|
||||
</template>
|
||||
@@ -401,32 +401,24 @@ onUnmounted(() => {
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button type="success" @click="handleStart">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:play-arrow-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:play-arrow-outline-rounded" />
|
||||
启动
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="handleStop">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon
|
||||
:size="24"
|
||||
class="mr-5"
|
||||
icon="material-symbols:stop-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="24" icon="material-symbols:stop-outline-rounded" />
|
||||
停止
|
||||
</n-button>
|
||||
</template>
|
||||
停止 Supervisor 会导致 Supervisor 管理的所有进程被杀死,确定要停止吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="warning" @click="handleRestart">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:replay-rounded" />
|
||||
<TheIcon :size="18" icon="material-symbols:replay-rounded" />
|
||||
重启
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleReload">
|
||||
<TheIcon :size="20" class="mr-5" icon="material-symbols:refresh-rounded" />
|
||||
<TheIcon :size="20" icon="material-symbols:refresh-rounded" />
|
||||
重载
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -82,15 +82,15 @@ onMounted(() => {
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button v-if="currentTab == 'dns'" class="ml-16" type="primary" @click="handleSaveDNS">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'swap'" class="ml-16" type="primary" @click="handleSaveSwap">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'hosts'" class="ml-16" type="primary" @click="handleSaveHosts">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -99,7 +99,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveTimezone"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
修改
|
||||
</n-button>
|
||||
<n-button
|
||||
@@ -108,7 +108,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click="handleSaveRootPassword"
|
||||
>
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
修改
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
@@ -30,7 +30,7 @@ const handleCreate = () => {
|
||||
<template #action>
|
||||
<div flex items-center>
|
||||
<n-button class="ml-16" type="primary" @click="createModal = true">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:add" />
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建备份
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<the-icon :size="48" class="mr-5" icon="bi:arrow-up-square" />
|
||||
<the-icon :size="48" icon="bi:arrow-up-square" />
|
||||
</div>
|
||||
<NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传</NText>
|
||||
<NP depth="3" style="margin: 8px 0 0 0"> 不支持断点续传,大文件建议使用 FTP 上传 </NP>
|
||||
|
||||
@@ -674,18 +674,14 @@ if (import.meta.hot) {
|
||||
<n-popconfirm @positive-click="handleRestartPanel">
|
||||
<template #trigger>
|
||||
<n-button type="warning" size="small">
|
||||
<TheIcon :size="20" class="mr-5" icon="mdi:restart" />
|
||||
<TheIcon :size="20" icon="mdi:restart" />
|
||||
重启面板
|
||||
</n-button>
|
||||
</template>
|
||||
确定要重启面板吗?
|
||||
</n-popconfirm>
|
||||
<n-button type="success" @click="handleUpdate" size="small">
|
||||
<TheIcon
|
||||
:size="20"
|
||||
class="mr-5"
|
||||
icon="mdi:arrow-up-bold-circle-outline"
|
||||
/>
|
||||
<TheIcon :size="20" icon="mdi:arrow-up-bold-circle-outline" />
|
||||
检查更新
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -62,11 +62,7 @@ onMounted(() => {
|
||||
<template #action>
|
||||
<div>
|
||||
<n-button v-if="versions" class="ml-16" type="primary" @click="handleUpdate">
|
||||
<TheIcon
|
||||
:size="18"
|
||||
class="mr-5"
|
||||
icon="material-symbols:arrow-circle-up-outline-rounded"
|
||||
/>
|
||||
<TheIcon :size="18" icon="material-symbols:arrow-circle-up-outline-rounded" />
|
||||
{{ $t('homeUpdate.button.update') }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
@@ -498,7 +498,7 @@ onMounted(() => {
|
||||
<n-popconfirm @positive-click="handleClear">
|
||||
<template #trigger>
|
||||
<n-button type="error">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:delete-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
清除监控记录
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
149
web/src/views/safe/CreateModal.vue
Normal file
149
web/src/views/safe/CreateModal.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
import safe from '@/api/panel/safe'
|
||||
import { NButton } from 'naive-ui'
|
||||
|
||||
const show = defineModel<boolean>('show', { type: Boolean, required: true })
|
||||
const loading = ref(false)
|
||||
|
||||
const protocols = [
|
||||
{
|
||||
label: 'TCP',
|
||||
value: 'tcp'
|
||||
},
|
||||
{
|
||||
label: 'UDP',
|
||||
value: 'udp'
|
||||
},
|
||||
{
|
||||
label: 'TCP/UDP',
|
||||
value: 'tcp/udp'
|
||||
}
|
||||
]
|
||||
|
||||
const families = [
|
||||
{
|
||||
label: 'IPv4',
|
||||
value: 'ipv4'
|
||||
},
|
||||
{
|
||||
label: 'IPv6',
|
||||
value: 'ipv6'
|
||||
}
|
||||
]
|
||||
|
||||
const strategies = [
|
||||
{
|
||||
label: '接受',
|
||||
value: 'accept'
|
||||
},
|
||||
{
|
||||
label: '丢弃',
|
||||
value: 'drop'
|
||||
},
|
||||
{
|
||||
label: '拒绝',
|
||||
value: 'reject'
|
||||
}
|
||||
]
|
||||
|
||||
const directions = [
|
||||
{
|
||||
label: '传入',
|
||||
value: 'in'
|
||||
},
|
||||
{
|
||||
label: '传出',
|
||||
value: 'out'
|
||||
}
|
||||
]
|
||||
|
||||
const createModel = ref({
|
||||
family: 'ipv4',
|
||||
protocol: 'tcp',
|
||||
port_start: 80,
|
||||
port_end: 80,
|
||||
address: '',
|
||||
strategy: 'accept',
|
||||
direction: 'in'
|
||||
})
|
||||
|
||||
const handleCreate = async () => {
|
||||
await safe.createFirewallRule(createModel.value).then(() => {
|
||||
window.$message.success('创建成功')
|
||||
createModel.value = {
|
||||
family: 'ipv4',
|
||||
protocol: 'tcp',
|
||||
port_start: 80,
|
||||
port_end: 80,
|
||||
address: '',
|
||||
strategy: 'accept',
|
||||
direction: 'in'
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="show"
|
||||
preset="card"
|
||||
title="创建规则"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="show = false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="protocols" label="传输协议">
|
||||
<n-select v-model:value="createModel.protocol" :options="protocols" />
|
||||
</n-form-item>
|
||||
<n-form-item path="family" label="网络协议">
|
||||
<n-select v-model:value="createModel.family" :options="families" />
|
||||
</n-form-item>
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="12">
|
||||
<n-form-item path="port_start" label="起始端口">
|
||||
<n-input-number
|
||||
v-model:value="createModel.port_start"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
placeholder="80"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="12">
|
||||
<n-form-item path="port_end" label="结束端口">
|
||||
<n-input-number
|
||||
v-model:value="createModel.port_end"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
placeholder="80"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-form-item path="address" label="目标">
|
||||
<n-input
|
||||
v-model:value="createModel.address"
|
||||
placeholder="可选输入 IP 或 IP 段:127.0.0.1 或 172.16.0.0/24(多个以英文逗号隔开)"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="strategy" label="策略">
|
||||
<n-select v-model:value="createModel.strategy" :options="strategies" />
|
||||
</n-form-item>
|
||||
<n-form-item path="strategy" label="方向">
|
||||
<n-select v-model:value="createModel.direction" :options="directions" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="24">
|
||||
<n-button type="info" :loading="loading" :disabled="loading" block @click="handleCreate">
|
||||
提交
|
||||
</n-button>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NDataTable, NPopconfirm, NSpace } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { NButton, NDataTable, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||
|
||||
import safe from '@/api/panel/safe'
|
||||
import { renderIcon } from '@/utils'
|
||||
import CreateModal from '@/views/safe/CreateModal.vue'
|
||||
import type { FirewallRule } from '@/views/safe/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const createModalShow = ref(false)
|
||||
|
||||
const model = ref({
|
||||
firewallStatus: false,
|
||||
@@ -18,38 +18,130 @@ const model = ref({
|
||||
const columns: any = [
|
||||
{ type: 'selection', fixed: 'left' },
|
||||
{
|
||||
title: t('safeIndex.columns.port'),
|
||||
key: 'port',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: t('safeIndex.columns.protocol'),
|
||||
title: '传输协议',
|
||||
key: 'protocol',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any): any {
|
||||
return h(NTag, null, {
|
||||
default: () => {
|
||||
if (row.protocol !== '') {
|
||||
return row.protocol
|
||||
}
|
||||
return '无'
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('safeIndex.columns.actions'),
|
||||
title: '网络协议',
|
||||
key: 'family',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any): any {
|
||||
return h(NTag, null, {
|
||||
default: () => {
|
||||
if (row.family !== '') {
|
||||
return row.family
|
||||
}
|
||||
return '无'
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '端口',
|
||||
key: 'port',
|
||||
width: 250,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any): any {
|
||||
if (row.port_start == row.port_end) {
|
||||
return row.port_start
|
||||
}
|
||||
return `${row.port_start}-${row.port_end}`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '策略',
|
||||
key: 'strategy',
|
||||
width: 150,
|
||||
render(row: any): any {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type:
|
||||
row.strategy === 'accept' ? 'success' : row.strategy === 'drop' ? 'warning' : 'error'
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
switch (row.strategy) {
|
||||
case 'accept':
|
||||
return '接受'
|
||||
case 'drop':
|
||||
return '丢弃'
|
||||
case 'reject':
|
||||
return '拒绝'
|
||||
default:
|
||||
return '未知'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '方向',
|
||||
key: 'direction',
|
||||
width: 150,
|
||||
render(row: any): any {
|
||||
return h(NTag, null, {
|
||||
default: () => {
|
||||
switch (row.direction) {
|
||||
case 'in':
|
||||
return '传入'
|
||||
case 'out':
|
||||
return '传出'
|
||||
default:
|
||||
return '未知'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '目标',
|
||||
key: 'address',
|
||||
render(row: any): any {
|
||||
return h(NTag, null, {
|
||||
default: () => {
|
||||
if (row.address === '') {
|
||||
return '所有'
|
||||
}
|
||||
return row.address
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 140,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
hideInExcel: true,
|
||||
render(row: any) {
|
||||
return [
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleDelete(row),
|
||||
onNegativeClick: () => {
|
||||
window.$message.info(t('safeIndex.alerts.undelete'))
|
||||
}
|
||||
onPositiveClick: () => handleDelete(row)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return t('safeIndex.confirm.delete')
|
||||
return '确定要删除吗?'
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
@@ -60,7 +152,7 @@ const columns: any = [
|
||||
style: 'margin-left: 15px;'
|
||||
},
|
||||
{
|
||||
default: () => t('safeIndex.buttons.delete'),
|
||||
default: () => '删除',
|
||||
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
|
||||
}
|
||||
)
|
||||
@@ -86,41 +178,23 @@ const pagination = reactive({
|
||||
|
||||
const selectedRowKeys = ref<any>([])
|
||||
|
||||
const addModel = ref({
|
||||
port: 80,
|
||||
protocol: 'tcp'
|
||||
})
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
await safe.deleteFirewallRule(row.port, row.protocol).then(() => {
|
||||
window.$message.success(t('safeIndex.alerts.delete'))
|
||||
await safe.deleteFirewallRule(row).then(() => {
|
||||
window.$message.success('删除成功')
|
||||
})
|
||||
getFirewallRules(pagination.page, pagination.pageSize).then((res) => {
|
||||
fetchFirewallRules(pagination.page, pagination.pageSize).then((res) => {
|
||||
data.value = res.items
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = res.total / pagination.pageSize + 1
|
||||
})
|
||||
}
|
||||
|
||||
const handleAdd = async () => {
|
||||
await safe.addFirewallRule(addModel.value.port, addModel.value.protocol).then(() => {
|
||||
window.$message.success(t('safeIndex.alerts.add'))
|
||||
addModel.value.port = 80
|
||||
addModel.value.protocol = 'tcp'
|
||||
})
|
||||
getFirewallRules(pagination.page, pagination.pageSize).then((res) => {
|
||||
data.value = res.items
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = res.total / pagination.pageSize + 1
|
||||
})
|
||||
}
|
||||
|
||||
const getFirewallRules = async (page: number, limit: number) => {
|
||||
const fetchFirewallRules = async (page: number, limit: number) => {
|
||||
const { data } = await safe.firewallRules(page, limit)
|
||||
return data
|
||||
}
|
||||
|
||||
const getSetting = async () => {
|
||||
const fetchSetting = async () => {
|
||||
safe.firewallStatus().then((res) => {
|
||||
model.value.firewallStatus = res.data
|
||||
})
|
||||
@@ -135,44 +209,39 @@ const getSetting = async () => {
|
||||
|
||||
const handleFirewallStatus = () => {
|
||||
safe.setFirewallStatus(model.value.firewallStatus).then(() => {
|
||||
window.$message.success(t('safeIndex.alerts.setup'))
|
||||
window.$message.success('设置成功')
|
||||
})
|
||||
}
|
||||
|
||||
const handleSsh = () => {
|
||||
safe.setSsh(model.value.sshStatus, model.value.sshPort).then(() => {
|
||||
window.$message.success(t('safeIndex.alerts.setup'))
|
||||
window.$message.success('设置成功')
|
||||
})
|
||||
}
|
||||
|
||||
const handlePingStatus = () => {
|
||||
safe.setPingStatus(model.value.pingStatus).then(() => {
|
||||
window.$message.success(t('safeIndex.alerts.setup'))
|
||||
window.$message.success('设置成功')
|
||||
})
|
||||
}
|
||||
|
||||
const batchDelete = async () => {
|
||||
if (selectedRowKeys.value.length === 0) {
|
||||
window.$message.info(t('safeIndex.alerts.select'))
|
||||
window.$message.info('请选择要删除的规则')
|
||||
return
|
||||
}
|
||||
|
||||
for (const key of selectedRowKeys.value) {
|
||||
// 通过 / 分割端口和协议
|
||||
const [port, protocol] = key.split('/')
|
||||
if (!port || !protocol) {
|
||||
continue
|
||||
}
|
||||
|
||||
await safe.deleteFirewallRule(port, protocol).then(() => {
|
||||
let rule = data.value.find((item) => item.port === port && item.protocol === protocol)
|
||||
window.$message.success(
|
||||
t('safeIndex.alerts.ruleDelete', { rule: rule?.port + '/' + rule?.protocol })
|
||||
)
|
||||
// 解析json
|
||||
const rule = JSON.parse(key)
|
||||
await safe.deleteFirewallRule(rule).then(() => {
|
||||
let port =
|
||||
rule.port_start == rule.port_end ? rule.port_start : `${rule.port_start}-${rule.port_end}`
|
||||
window.$message.success(`${rule.family} 规则 ${port}/${rule.protocol} 删除成功`)
|
||||
})
|
||||
}
|
||||
|
||||
getFirewallRules(pagination.page, pagination.pageSize).then((res) => {
|
||||
fetchFirewallRules(pagination.page, pagination.pageSize).then((res) => {
|
||||
data.value = res.items
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = res.total / pagination.pageSize + 1
|
||||
@@ -185,7 +254,7 @@ const onChecked = (rowKeys: any) => {
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
pagination.page = page
|
||||
getFirewallRules(page, pagination.pageSize).then((res) => {
|
||||
fetchFirewallRules(page, pagination.pageSize).then((res) => {
|
||||
data.value = res.items
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = res.total / pagination.pageSize + 1
|
||||
@@ -197,13 +266,13 @@ const onPageSizeChange = (pageSize: number) => {
|
||||
onPageChange(1)
|
||||
}
|
||||
|
||||
watch(createModalShow, () => {
|
||||
onPageChange(1)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getSetting()
|
||||
getFirewallRules(pagination.page, pagination.pageSize).then((res) => {
|
||||
data.value = res.items
|
||||
pagination.itemCount = res.total
|
||||
pagination.pageCount = res.total / pagination.pageSize + 1
|
||||
})
|
||||
fetchSetting()
|
||||
onPageChange(1)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -212,61 +281,34 @@ onMounted(() => {
|
||||
<n-space vertical>
|
||||
<n-card flex-1 rounded-10>
|
||||
<n-form inline>
|
||||
<n-form-item :label="$t('safeIndex.filter.fields.firewall.label')">
|
||||
<n-switch
|
||||
v-model:value="model.firewallStatus"
|
||||
@update:value="handleFirewallStatus"
|
||||
:checkedChildren="$t('safeIndex.filter.fields.firewall.checked')"
|
||||
:unCheckedChildren="$t('safeIndex.filter.fields.firewall.unchecked')"
|
||||
/>
|
||||
<n-form-item label="防火墙">
|
||||
<n-switch v-model:value="model.firewallStatus" @update:value="handleFirewallStatus" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('safeIndex.filter.fields.ssh.label')">
|
||||
<n-switch
|
||||
v-model:value="model.sshStatus"
|
||||
@update:value="handleSsh"
|
||||
:checkedChildren="$t('safeIndex.filter.fields.ssh.checked')"
|
||||
:unCheckedChildren="$t('safeIndex.filter.fields.ssh.unchecked')"
|
||||
/>
|
||||
<n-form-item label="SSH">
|
||||
<n-switch v-model:value="model.sshStatus" @update:value="handleSsh" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('safeIndex.filter.fields.ping.label')">
|
||||
<n-switch
|
||||
v-model:value="model.pingStatus"
|
||||
@update:value="handlePingStatus"
|
||||
:checkedChildren="$t('safeIndex.filter.fields.ping.checked')"
|
||||
:unCheckedChildren="$t('safeIndex.filter.fields.ping.unchecked')"
|
||||
/>
|
||||
<n-form-item label="Ping">
|
||||
<n-switch v-model:value="model.pingStatus" @update:value="handlePingStatus" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('safeIndex.filter.fields.port.label')">
|
||||
<n-form-item label="SSH端口">
|
||||
<n-input-number v-model:value="model.sshPort" @blur="handleSsh" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<n-space flex items-center>
|
||||
<n-button type="primary" @click="createModalShow = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建规则
|
||||
</n-button>
|
||||
<n-popconfirm @positive-click="batchDelete">
|
||||
<template #trigger>
|
||||
<n-button type="warning"> {{ $t('safeIndex.buttons.batchDelete') }} </n-button>
|
||||
<n-button type="warning">
|
||||
<TheIcon :size="18" icon="material-symbols:delete-outline" />
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
{{ $t('safeIndex.confirm.batchDelete') }}
|
||||
确定要批量删除吗?
|
||||
</n-popconfirm>
|
||||
<n-text>{{ $t('safeIndex.portControl.title') }}</n-text>
|
||||
<n-input-number
|
||||
v-model:value="addModel.port"
|
||||
:placeholder="$t('safeIndex.portControl.fields.port.placeholder')"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
/>
|
||||
<n-select
|
||||
v-model:value="addModel.protocol"
|
||||
:placeholder="$t('safeIndex.portControl.fields.protocol.placeholder')"
|
||||
style="width: 120px"
|
||||
:options="[
|
||||
{ label: 'TCP', value: 'tcp' },
|
||||
{ label: 'UDP', value: 'udp' }
|
||||
]"
|
||||
/>
|
||||
<n-button type="primary" @click="handleAdd">
|
||||
{{ $t('safeIndex.buttons.add') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-data-table
|
||||
@@ -276,7 +318,7 @@ onMounted(() => {
|
||||
:scroll-x="1200"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-key="(row: any) => row.port + '/' + row.protocol"
|
||||
:row-key="(row: any) => JSON.stringify(row)"
|
||||
:pagination="pagination"
|
||||
@update:checked-row-keys="onChecked"
|
||||
@update:page="onPageChange"
|
||||
@@ -284,6 +326,7 @@ onMounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
</common-page>
|
||||
<create-modal v-model:show="createModalShow" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
path: '',
|
||||
component: () => import('./IndexView.vue'),
|
||||
meta: {
|
||||
title: 'safeIndex.title',
|
||||
title: '系统安全',
|
||||
icon: 'mdi:server-security',
|
||||
role: ['admin'],
|
||||
requireAuth: true
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export interface FirewallRule {
|
||||
port: string
|
||||
protocol: string
|
||||
port_start: number
|
||||
port_end: number
|
||||
protocols: string[]
|
||||
address: string
|
||||
strategy: string
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ onMounted(() => {
|
||||
<div flex items-center>
|
||||
<n-tag type="warning">如果您修改了原文,那么点击保存后,其余的修改将不会生效!</n-tag>
|
||||
<n-button class="ml-16" type="primary" @click="handleSave">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:save-outline" />
|
||||
<TheIcon :size="18" icon="material-symbols:save-outline" />
|
||||
保存
|
||||
</n-button>
|
||||
</div>
|
||||
@@ -287,7 +287,7 @@ onMounted(() => {
|
||||
<n-popconfirm @positive-click="handleReset">
|
||||
<template #trigger>
|
||||
<n-button type="success">
|
||||
<TheIcon :size="18" class="mr-5" icon="material-symbols:refresh" />
|
||||
<TheIcon :size="18" icon="material-symbols:refresh" />
|
||||
重置配置
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user