mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 16:00:51 +00:00
feat: improve firewall status and UI translations (#13630)
This commit is contained in:
@@ -35,8 +35,10 @@ type FirewallBackendOption struct {
|
||||
}
|
||||
|
||||
type FirewallBackendFamilyStatus struct {
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Available bool `json:"available"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type FirewallBackendGroup struct {
|
||||
@@ -61,7 +63,7 @@ type FirewallBackendOperation struct {
|
||||
|
||||
type FilterChainOperation struct {
|
||||
Name string `json:"name" validate:"required,eq=1PANEL_BASIC"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base init-ipv6-base bind-base unbind-base"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base bind-base unbind-base"`
|
||||
}
|
||||
|
||||
type FirewallSystemPort struct {
|
||||
@@ -100,6 +102,7 @@ type FirewallNativeDetail struct {
|
||||
|
||||
type DockerPortGuardBase struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Initialized bool `json:"initialized"`
|
||||
Bound bool `json:"bound"`
|
||||
IPv4 DockerPortGuardFamilyStatus `json:"ipv4"`
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
agenti18n "github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -44,6 +45,7 @@ type DockerPortGuardService struct {
|
||||
policies repo.IDockerPortGuardRepo
|
||||
runtime dockerGuardRuntime
|
||||
client func() (*client.Client, error)
|
||||
version func(string) string
|
||||
}
|
||||
|
||||
type normalizedDockerGuardPolicy struct {
|
||||
@@ -71,7 +73,11 @@ type IDockerPortGuardService interface {
|
||||
}
|
||||
|
||||
func NewIDockerPortGuardService() IDockerPortGuardService {
|
||||
return &DockerPortGuardService{policies: repo.NewIDockerPortGuardRepo(), client: docker.NewDockerClient}
|
||||
return &DockerPortGuardService{
|
||||
policies: repo.NewIDockerPortGuardRepo(),
|
||||
client: docker.NewDockerClient,
|
||||
version: dockerFirewallVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func ReconcileDockerPortGuard(ctx context.Context) error {
|
||||
@@ -90,6 +96,7 @@ func ReconcileDockerPortGuardBestEffort(ctx context.Context) {
|
||||
func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPortGuardList, error) {
|
||||
selectedBackend := selectedDockerFirewallBackend("")
|
||||
base := s.runtimeStatus(s.guardRuntime(selectedBackend), selectedBackend)
|
||||
base.Version = s.loadFirewallVersion(selectedBackend)
|
||||
policies, err := s.policies.List(ctx)
|
||||
if err != nil {
|
||||
return dto.DockerPortGuardList{}, err
|
||||
@@ -107,6 +114,7 @@ func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPo
|
||||
}
|
||||
base.Backend = selectedDockerFirewallBackend(dockerFirewallBackend(info))
|
||||
base = s.runtimeStatus(s.guardRuntime(base.Backend), base.Backend)
|
||||
base.Version = s.loadFirewallVersion(base.Backend)
|
||||
if reconcileErr := lastDockerPortGuardReconcileError(); reconcileErr != nil {
|
||||
base.Message = reconcileErr.Error()
|
||||
markDockerGuardReconcileFailure(&base, reconcileErr)
|
||||
@@ -369,8 +377,8 @@ func (s *DockerPortGuardService) runtimeStatus(runtime dockerGuardRuntime, backe
|
||||
return dto.DockerPortGuardBase{
|
||||
Name: dockerFirewallDisplayName(backend),
|
||||
Backend: backend,
|
||||
Initialized: ipv4.Initialized,
|
||||
Bound: ipv4.Bound,
|
||||
Initialized: ipv4.Initialized || ipv6.Initialized,
|
||||
Bound: ipv4.Bound || ipv6.Bound,
|
||||
IPv4: dto.DockerPortGuardFamilyStatus{State: ipv4.State, Reason: ipv4.Reason, Initialized: ipv4.Initialized, Bound: ipv4.Bound, Effective: ipv4.Effective},
|
||||
IPv6: dto.DockerPortGuardFamilyStatus{State: ipv6.State, Reason: ipv6.Reason, Initialized: ipv6.Initialized, Bound: ipv6.Bound, Effective: ipv6.Effective},
|
||||
}
|
||||
@@ -422,6 +430,25 @@ func dockerFirewallDisplayName(backend string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerPortGuardService) loadFirewallVersion(backend string) string {
|
||||
if s.version == nil {
|
||||
return "-"
|
||||
}
|
||||
return s.version(backend)
|
||||
}
|
||||
|
||||
func dockerFirewallVersion(backend string) string {
|
||||
client, err := lifecycle.NewClientFor(backend)
|
||||
if err != nil {
|
||||
return "-"
|
||||
}
|
||||
version, err := client.Version()
|
||||
if err != nil || strings.TrimSpace(version) == "" {
|
||||
return "-"
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func discoverDockerEndpoints(ctx context.Context, cli *client.Client) ([]dto.DockerPortGuardEndpoint, error) {
|
||||
containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: true})
|
||||
if err != nil {
|
||||
|
||||
@@ -26,6 +26,7 @@ type persistentDockerGuardRuntime struct {
|
||||
bind int
|
||||
unbind int
|
||||
policies []docker_guard.Policy
|
||||
statuses map[string]docker_guard.FamilyStatus
|
||||
}
|
||||
|
||||
func (r *persistentDockerGuardRuntime) Initialize(policies []docker_guard.Policy) error {
|
||||
@@ -57,7 +58,10 @@ func (r *persistentDockerGuardRuntime) Initialized(string) (bool, error) {
|
||||
return r.initialized, nil
|
||||
}
|
||||
|
||||
func (r *persistentDockerGuardRuntime) Status(string) docker_guard.FamilyStatus {
|
||||
func (r *persistentDockerGuardRuntime) Status(family string) docker_guard.FamilyStatus {
|
||||
if r.statuses != nil {
|
||||
return r.statuses[family]
|
||||
}
|
||||
return docker_guard.FamilyStatus{Initialized: r.initialized, Bound: r.initialized, Effective: r.initialized}
|
||||
}
|
||||
|
||||
@@ -78,6 +82,7 @@ func TestDockerGuardOverviewLocalizesUnavailableDocker(t *testing.T) {
|
||||
service := &DockerPortGuardService{
|
||||
policies: &persistentDockerGuardPolicies{},
|
||||
runtime: &persistentDockerGuardRuntime{},
|
||||
version: func(string) string { return "1.8.10" },
|
||||
client: func() (*client.Client, error) {
|
||||
return nil, errors.New("Cannot connect to the Docker daemon at unix:///var/run/docker.sock")
|
||||
},
|
||||
@@ -89,6 +94,19 @@ func TestDockerGuardOverviewLocalizesUnavailableDocker(t *testing.T) {
|
||||
if overview.Base.Message != agenti18n.Get("ErrDockerFailed") {
|
||||
t.Fatalf("message = %q, want localized Docker failure", overview.Base.Message)
|
||||
}
|
||||
if overview.Base.Version != "1.8.10" {
|
||||
t.Fatalf("version = %q, want 1.8.10", overview.Base.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGuardRuntimeStatusAggregatesAvailableFamilies(t *testing.T) {
|
||||
runtime := &persistentDockerGuardRuntime{statuses: map[string]docker_guard.FamilyStatus{
|
||||
docker_guard.FamilyIPv6: {Initialized: true, Bound: true, Effective: true},
|
||||
}}
|
||||
base := (&DockerPortGuardService{}).runtimeStatus(runtime, constant.FirewallProviderNftables)
|
||||
if !base.Initialized || !base.Bound || base.IPv4.Initialized || !base.IPv6.Initialized {
|
||||
t.Fatalf("unexpected aggregate Docker guard status: %#v", base)
|
||||
}
|
||||
}
|
||||
|
||||
func setupDockerGuardSettingsDB(t *testing.T) {
|
||||
|
||||
@@ -82,15 +82,18 @@ func (s *FirewallService) LoadBaseInfo(chainGroup string) (dto.FirewallSubsystem
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
initialized, bound, err := loadFirewallInitStatus(runtimeStatus.Name, chainGroup)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
status.Name, status.Backend = runtimeStatus.Name, runtimeStatus.Name
|
||||
status.Version, status.PingStatus = runtimeStatus.Version, ping.LoadStatus()
|
||||
status.IsActive, status.IsInit, status.IsBind = runtimeStatus.IsActive, initialized, bound
|
||||
status.IPv4.Initialized, status.IPv4.Bound, _ = loadSystemFirewallFamilyStatus(status.Name, constant.FirewallFamilyIPv4)
|
||||
status.IPv6.Initialized, status.IPv6.Bound, _ = loadSystemFirewallFamilyStatus(status.Name, constant.FirewallFamilyIPv6)
|
||||
status.IsActive = runtimeStatus.IsActive
|
||||
if supportsManagedFilterChains(runtimeStatus.Name) {
|
||||
initialized, bound, err := loadFirewallInitStatus(runtimeStatus.Name, chainGroup)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
status.IsInit, status.IsBind = initialized, bound
|
||||
status.IPv4 = loadSystemFirewallFamilyInfo(status.Name, constant.FirewallFamilyIPv4)
|
||||
status.IPv6 = loadSystemFirewallFamilyInfo(status.Name, constant.FirewallFamilyIPv6)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
@@ -125,13 +128,8 @@ func (s *FirewallService) OperateFilterChain(request dto.FilterChainOperation) e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if request.Operate == "init-ipv6-base" {
|
||||
if provider != constant.FirewallProviderIptables {
|
||||
return fmt.Errorf("IPv6 base-chain initialization is only supported for iptables")
|
||||
}
|
||||
firewallRuleMutationMu.Lock()
|
||||
defer firewallRuleMutationMu.Unlock()
|
||||
return s.iptablesHelper.RepairIPv6BaseChains()
|
||||
if !supportsManagedFilterChains(provider) {
|
||||
return fmt.Errorf("filter chain operations are not supported for %s", provider)
|
||||
}
|
||||
if provider == constant.FirewallProviderNftables {
|
||||
if err := newNftablesHelperManager().Operate(firewall.BaseOperation(request.Operate)); err != nil {
|
||||
@@ -2573,8 +2571,6 @@ func loadDirectFirewallInitStatus(provider string) (bool, bool, error) {
|
||||
|
||||
func loadFirewallInitStatus(provider, tab string) (bool, bool, error) {
|
||||
switch provider {
|
||||
case constant.FirewallProviderFirewalld, constant.FirewallProviderUFW:
|
||||
return true, true, nil
|
||||
case constant.FirewallProviderNftables:
|
||||
return nftables_helper.LoadInitStatus(tab)
|
||||
case constant.FirewallProviderIptables:
|
||||
@@ -2584,6 +2580,10 @@ func loadFirewallInitStatus(provider, tab string) (bool, bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func supportsManagedFilterChains(provider string) bool {
|
||||
return provider == constant.FirewallProviderIptables || provider == constant.FirewallProviderNftables
|
||||
}
|
||||
|
||||
func (s *FirewallService) addPortsBeforeStart(client lifecycle.Client) error {
|
||||
if client.Name() == constant.FirewallProviderIptables || client.Name() == constant.FirewallProviderNftables {
|
||||
isInit, _, err := loadDirectFirewallInitStatus(client.Name())
|
||||
|
||||
@@ -1220,15 +1220,15 @@ func TestFirewallRuntimeRejectsUnavailableManagedScopeForMutation(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterChainOperationValidationIncludesIPv6Repair(t *testing.T) {
|
||||
func TestFilterChainOperationValidationAllowsOnlyUnifiedOperations(t *testing.T) {
|
||||
validate := validator.New()
|
||||
for _, operation := range []string{"init-base", "init-ipv6-base", "bind-base", "unbind-base"} {
|
||||
for _, operation := range []string{"init-base", "bind-base", "unbind-base"} {
|
||||
request := dto.FilterChainOperation{Name: constant.FirewallBasicChain, Operate: operation}
|
||||
if err := validate.Struct(request); err != nil {
|
||||
t.Fatalf("operation %q rejected by API contract: %v", operation, err)
|
||||
}
|
||||
}
|
||||
for _, operation := range []string{"", "init-forward", "repair-anything"} {
|
||||
for _, operation := range []string{"", "init-ipv6-base", "init-forward", "repair-anything"} {
|
||||
request := dto.FilterChainOperation{Name: constant.FirewallBasicChain, Operate: operation}
|
||||
if err := validate.Struct(request); err == nil {
|
||||
t.Fatalf("operation %q accepted by API contract", operation)
|
||||
@@ -1236,6 +1236,28 @@ func TestFilterChainOperationValidationIncludesIPv6Repair(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSystemFirewallFamilyInfoExcludesServiceBackends(t *testing.T) {
|
||||
for _, provider := range []string{constant.FirewallProviderFirewalld, constant.FirewallProviderUFW, "unsupported"} {
|
||||
status := loadSystemFirewallFamilyInfo(provider, constant.FirewallFamilyIPv6)
|
||||
if status.Available || status.Initialized || status.Bound {
|
||||
t.Fatalf("%s IPv6 status = %#v, want unavailable", provider, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportsManagedFilterChains(t *testing.T) {
|
||||
for _, provider := range []string{constant.FirewallProviderIptables, constant.FirewallProviderNftables} {
|
||||
if !supportsManagedFilterChains(provider) {
|
||||
t.Fatalf("%s should support managed filter chains", provider)
|
||||
}
|
||||
}
|
||||
for _, provider := range []string{constant.FirewallProviderFirewalld, constant.FirewallProviderUFW} {
|
||||
if supportsManagedFilterChains(provider) {
|
||||
t.Fatalf("%s should not support managed filter chains", provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirewallRuleServiceChecksManagedUpdateWithoutApplying(t *testing.T) {
|
||||
rule := executorTestRule("8080")
|
||||
adapter := newFakeFilterAdapter(t, rule.Scope, nil)
|
||||
|
||||
@@ -50,11 +50,15 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
client, err := lifecycle.NewClientFor(name)
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
} else {
|
||||
option.Active, _ = client.Status()
|
||||
option.Initialized, option.Bound, _ = loadFirewallInitStatus(name, "base")
|
||||
option.IPv4.Initialized, option.IPv4.Bound, _ = loadSystemFirewallFamilyStatus(name, constant.FirewallFamilyIPv4)
|
||||
option.IPv6.Initialized, option.IPv6.Bound, _ = loadSystemFirewallFamilyStatus(name, constant.FirewallFamilyIPv6)
|
||||
} else if supportsManagedFilterChains(name) {
|
||||
option.Initialized, option.Bound, err = loadFirewallInitStatus(name, "base")
|
||||
if err != nil {
|
||||
option.Message = err.Error()
|
||||
}
|
||||
option.IPv4 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv4)
|
||||
option.IPv6 = loadSystemFirewallFamilyInfo(name, constant.FirewallFamilyIPv6)
|
||||
} else if option.Active, err = client.Status(); err != nil {
|
||||
option.Message = err.Error()
|
||||
}
|
||||
}
|
||||
if name == constant.FirewallProviderIptables {
|
||||
@@ -85,9 +89,23 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
} else if status, err := manager.Status(); err != nil {
|
||||
option.Message = err.Error()
|
||||
} else {
|
||||
option.Active, option.Initialized, option.Bound = status.IsActive, status.IsInit, status.IsBind
|
||||
option.IPv4.Initialized, option.IPv4.Bound, _ = manager.FamilyStatus(constant.FirewallFamilyIPv4)
|
||||
option.IPv6.Initialized, option.IPv6.Bound, _ = manager.FamilyStatus(constant.FirewallFamilyIPv6)
|
||||
option.Initialized, option.Bound = status.IsInit, status.IsBind
|
||||
ipv4Init, ipv4Bound, ipv4Err := manager.FamilyStatus(constant.FirewallFamilyIPv4)
|
||||
ipv6Init, ipv6Bound, ipv6Err := manager.FamilyStatus(constant.FirewallFamilyIPv6)
|
||||
option.IPv4 = dto.FirewallBackendFamilyStatus{
|
||||
Available: ipv4Err == nil, Initialized: ipv4Init, Bound: ipv4Bound,
|
||||
}
|
||||
option.IPv6 = dto.FirewallBackendFamilyStatus{
|
||||
Available: ipv6Err == nil, Initialized: ipv6Init, Bound: ipv6Bound,
|
||||
}
|
||||
if name == constant.FirewallProviderIptables {
|
||||
if commands, commandErr := lifecycle.ResolveIptablesCommands(); commandErr == nil {
|
||||
option.IPv6.Available = option.IPv6.Available && commands.IPv6Available()
|
||||
if !commands.IPv6Available() {
|
||||
option.IPv6.Reason = docker_guard.ReasonCommandMissing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if option.IPv4.Initialized || option.IPv6.Initialized {
|
||||
@@ -146,6 +164,9 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
option.Bound = ipv4.Bound || ipv6.Bound
|
||||
option.IPv4.Initialized, option.IPv4.Bound = ipv4.Initialized, ipv4.Bound
|
||||
option.IPv6.Initialized, option.IPv6.Bound = ipv6.Initialized, ipv6.Bound
|
||||
option.IPv4.Available = ipv4.Reason != docker_guard.ReasonCommandMissing
|
||||
option.IPv6.Available = ipv6.Reason != docker_guard.ReasonCommandMissing
|
||||
option.IPv4.Reason, option.IPv6.Reason = ipv4.Reason, ipv6.Reason
|
||||
result.Docker.Options = append(result.Docker.Options, option)
|
||||
}
|
||||
|
||||
@@ -154,8 +175,6 @@ func (s *FirewallSettingService) Load(ctx context.Context) (dto.FirewallSettings
|
||||
|
||||
func loadSystemFirewallFamilyStatus(provider, family string) (bool, bool, error) {
|
||||
switch provider {
|
||||
case constant.FirewallProviderFirewalld, constant.FirewallProviderUFW:
|
||||
return true, true, nil
|
||||
case constant.FirewallProviderIptables:
|
||||
return iptables_helper.LoadFamilyInitStatus(family, "base")
|
||||
case constant.FirewallProviderNftables:
|
||||
@@ -165,10 +184,28 @@ func loadSystemFirewallFamilyStatus(provider, family string) (bool, bool, error)
|
||||
}
|
||||
}
|
||||
|
||||
func loadSystemFirewallFamilyInfo(provider, family string) dto.FirewallBackendFamilyStatus {
|
||||
if provider == constant.FirewallProviderIptables && family == constant.FirewallFamilyIPv6 {
|
||||
commands, err := lifecycle.ResolveIptablesCommands()
|
||||
if err != nil || !commands.IPv6Available() {
|
||||
return dto.FirewallBackendFamilyStatus{Reason: docker_guard.ReasonCommandMissing}
|
||||
}
|
||||
}
|
||||
initialized, bound, err := loadSystemFirewallFamilyStatus(provider, family)
|
||||
return dto.FirewallBackendFamilyStatus{
|
||||
Available: err == nil,
|
||||
Initialized: initialized,
|
||||
Bound: bound,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FirewallSettingService) Operate(ctx context.Context, request dto.FirewallBackendOperation) error {
|
||||
if request.Subsystem != "system" && request.Backend != constant.FirewallProviderIptables && request.Backend != constant.FirewallProviderNftables {
|
||||
return fmt.Errorf("%s only supports iptables or nftables", request.Subsystem)
|
||||
}
|
||||
if request.Subsystem == "system" && !supportsManagedFilterChains(request.Backend) && request.Operation != "select" {
|
||||
return fmt.Errorf("%s does not support initialization or cleanup", request.Backend)
|
||||
}
|
||||
switch request.Subsystem {
|
||||
case "system":
|
||||
return s.operateSystem(request)
|
||||
@@ -231,21 +268,13 @@ func (s *FirewallSettingService) operateSystem(request dto.FirewallBackendOperat
|
||||
if request.Operation == "select" {
|
||||
return nil
|
||||
}
|
||||
var initErr error
|
||||
if request.Backend == constant.FirewallProviderIptables || request.Backend == constant.FirewallProviderNftables {
|
||||
initErr = newFirewallService().OperateFilterChain(dto.FilterChainOperation{
|
||||
Name: constant.FirewallBasicChain, Operate: string(firewall.BaseOperationInit),
|
||||
})
|
||||
} else {
|
||||
initErr = newFirewallService().OperateFirewall(dto.FirewallLifecycleOperation{Operation: "start"})
|
||||
}
|
||||
initErr := newFirewallService().OperateFilterChain(dto.FilterChainOperation{
|
||||
Name: constant.FirewallBasicChain, Operate: string(firewall.BaseOperationInit),
|
||||
})
|
||||
if initErr != nil {
|
||||
return rollback(initErr)
|
||||
}
|
||||
if request.Backend == constant.FirewallProviderIptables || request.Backend == constant.FirewallProviderNftables {
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallFilterInitializedKey, constant.StatusEnable)
|
||||
}
|
||||
return nil
|
||||
return settingRepo.UpdateOrCreate(constant.FirewallFilterInitializedKey, constant.StatusEnable)
|
||||
}
|
||||
|
||||
func cleanupSystemBackend(backend string) error {
|
||||
|
||||
@@ -75,3 +75,18 @@ func TestFirewallSettingServiceSelectsGuardBackendWithoutDockerRestart(t *testin
|
||||
t.Fatal("Docker port guard did not use the selected nftables runtime")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirewallSettingServiceRejectsServiceBackendInitialization(t *testing.T) {
|
||||
for _, backend := range []string{"firewalld", "ufw"} {
|
||||
for _, operation := range []string{"initialize", "cleanup"} {
|
||||
err := (&FirewallSettingService{}).Operate(context.Background(), dto.FirewallBackendOperation{
|
||||
Subsystem: "system",
|
||||
Backend: backend,
|
||||
Operation: operation,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("%s %s unexpectedly succeeded", backend, operation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +81,22 @@ func (s *ForwardingService) LoadBaseInfo() (dto.FirewallSubsystemStatus, error)
|
||||
baseInfo.Name, baseInfo.Backend = forwardingDisplayName(status.Name), status.Name
|
||||
baseInfo.Version = status.Version
|
||||
baseInfo.PingStatus = ping.LoadStatus()
|
||||
baseInfo.IsActive, baseInfo.IsInit, baseInfo.IsBind = status.IsActive, status.IsInit, status.IsBind
|
||||
baseInfo.IsInit, baseInfo.IsBind = status.IsInit, status.IsBind
|
||||
baseInfo.IPv4 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv4)
|
||||
baseInfo.IPv6 = loadForwardingFamilyInfo(manager, status.Name, constant.FirewallFamilyIPv6)
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
func loadForwardingFamilyInfo(manager *forwarding.Manager, backend, family string) dto.FirewallBackendFamilyStatus {
|
||||
initialized, bound, err := manager.FamilyStatus(family)
|
||||
available := err == nil
|
||||
if backend == constant.FirewallProviderIptables && family == constant.FirewallFamilyIPv6 {
|
||||
commands, commandErr := lifecycle.ResolveIptablesCommands()
|
||||
available = available && commandErr == nil && commands.IPv6Available()
|
||||
}
|
||||
return dto.FirewallBackendFamilyStatus{Available: available, Initialized: initialized, Bound: bound}
|
||||
}
|
||||
|
||||
func forwardingDisplayName(backend string) string {
|
||||
switch backend {
|
||||
case constant.FirewallProviderIptables, constant.FirewallProviderNftables:
|
||||
|
||||
@@ -182,6 +182,23 @@ func TestForwardingDisplayName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingBaseInfoIncludesFamilyStatus(t *testing.T) {
|
||||
adapter := &fakeForwardingAdapter{
|
||||
name: "nftables",
|
||||
familyInit: map[string]bool{forwardClient.FamilyIPv4: true},
|
||||
}
|
||||
base, err := forwardingServiceWithAdapter(adapter).LoadBaseInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !base.IPv4.Available || !base.IPv4.Initialized || !base.IPv4.Bound {
|
||||
t.Fatalf("unexpected IPv4 status: %#v", base.IPv4)
|
||||
}
|
||||
if !base.IPv6.Available || base.IPv6.Initialized || base.IPv6.Bound {
|
||||
t.Fatalf("unexpected IPv6 status: %#v", base.IPv6)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardingSearchPreservesAPIShapeAndPagination(t *testing.T) {
|
||||
adapter := &fakeForwardingAdapter{name: "iptables", rules: []forwardClient.Rule{
|
||||
{Num: "1", Family: forwardClient.FamilyIPv6, Protocol: "tcp", Port: "8080", TargetIP: "2001:db8::2", TargetPort: "80", Interface: "eth0"},
|
||||
|
||||
@@ -9,16 +9,13 @@ import (
|
||||
var ErrRuleExists = errors.New("forwarding rule already exists")
|
||||
|
||||
type Status struct {
|
||||
Name string
|
||||
Version string
|
||||
IsActive bool
|
||||
IsInit bool
|
||||
IsBind bool
|
||||
Name string
|
||||
Version string
|
||||
IsInit bool
|
||||
IsBind bool
|
||||
}
|
||||
|
||||
type RuntimeClient interface {
|
||||
Name() string
|
||||
Status() (bool, error)
|
||||
Version() (string, error)
|
||||
}
|
||||
|
||||
@@ -33,25 +30,23 @@ func NewManager(adapter Adapter, runtime RuntimeClient) *Manager {
|
||||
|
||||
func (m *Manager) Status() (Status, error) {
|
||||
status := Status{Name: m.adapter.Name(), Version: "-"}
|
||||
if m.runtime == nil {
|
||||
return status, nil
|
||||
}
|
||||
var versionErr error
|
||||
var statusErr error
|
||||
var initErr error
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
wg.Add(1)
|
||||
if m.runtime != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
status.Version, versionErr = m.runtime.Version()
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
status.Version, versionErr = m.runtime.Version()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
status.IsActive, statusErr = m.runtime.Status()
|
||||
status.IsInit, status.IsBind, initErr = m.adapter.InitStatus()
|
||||
}()
|
||||
wg.Wait()
|
||||
return status, errors.Join(versionErr, statusErr, initErr)
|
||||
return status, errors.Join(versionErr, initErr)
|
||||
}
|
||||
|
||||
func (m *Manager) List(info, strategy string) ([]Rule, error) {
|
||||
|
||||
@@ -13,10 +13,6 @@ func (m *Manager) EnsureIPv6BaseChains() error {
|
||||
return EnsureIPv6BaseChains(m.panelPort())
|
||||
}
|
||||
|
||||
func (m *Manager) RepairIPv6BaseChains() error {
|
||||
return RepairIPv6BaseChains(m.panelPort())
|
||||
}
|
||||
|
||||
func RepairIPv6BaseChains(panelPort string) error {
|
||||
initialized, bound, err := LoadFamilyInitStatus(constant.FirewallFamilyIPv6, "base")
|
||||
if err != nil {
|
||||
|
||||
@@ -17,8 +17,10 @@ export namespace Firewall {
|
||||
ipv6: BackendFamilyStatus;
|
||||
}
|
||||
export interface BackendFamilyStatus {
|
||||
available: boolean;
|
||||
initialized: boolean;
|
||||
bound: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
export interface BackendGroup {
|
||||
selected: string;
|
||||
@@ -275,6 +277,7 @@ export namespace Firewall {
|
||||
|
||||
export interface DockerGuardBase {
|
||||
name: string;
|
||||
version: string;
|
||||
initialized: boolean;
|
||||
bound: boolean;
|
||||
ipv4: DockerGuardFamilyStatus;
|
||||
|
||||
@@ -946,6 +946,7 @@ const message = {
|
||||
from_remote: 'This model was not downloaded via 1Panel, no related pull logs.',
|
||||
no_logs: 'The pull logs for this model have been deleted and cannot be viewed.',
|
||||
vllmVersionHelper: 'For FusionXpark GB 10 servers, please select the -cu130 version.',
|
||||
ascendVisibleDevices: 'Ascend Visible Devices',
|
||||
vllmCommandPortHelper:
|
||||
'The startup command must use port {0}; otherwise, the service will be inaccessible.',
|
||||
syncModelAccount: 'Sync to model account',
|
||||
@@ -1134,6 +1135,7 @@ const message = {
|
||||
cachedToken: 'Cached Tokens',
|
||||
cacheHitRate: 'Cache Hit Rate',
|
||||
activeUsers: 'Active Users',
|
||||
activeStreamingRequests: 'Active Streaming Requests',
|
||||
activeModels: 'Active Models',
|
||||
failedRequests: 'Failed Requests',
|
||||
averageTokenPerRequest: 'Avg Tokens/Request',
|
||||
@@ -1433,7 +1435,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU Monitoring',
|
||||
gpuHelper: 'The system did not detect NVIDIA-SMI or XPU-SMI commands. Please check and try again!',
|
||||
gpuHelper: 'No supported GPU/XPU/NPU management command was detected. Please check and try again!',
|
||||
process: 'Process Information',
|
||||
processCount: 'Process Count',
|
||||
type: 'Type',
|
||||
@@ -4127,10 +4129,9 @@ const message = {
|
||||
clearAllRules: 'Clear all rules',
|
||||
importBackendHelper: 'Imported rules are converted for the current {0} backend. Source rules are not changed.',
|
||||
clearAllRulesHelper: 'Delete all {0} manageable rules from the current backend? This cannot be undone.',
|
||||
switchBackendHelper:
|
||||
'This only changes the firewall backend managed by 1Panel. The original backend will not be migrated, stopped, or cleaned up. Export the current rules first. After switching, initialize the target backend and manually import or verify its rules. External and system-native rules are not exported. Continue switching to {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel now manages {0}. The original {1} backend was not stopped or cleaned up and may still be running and effective. Initialize the target backend, import or verify its rules, and manually handle the original backend only after confirming access works normally.',
|
||||
backendSwitchNotice:
|
||||
'Switching only changes the firewall backend currently in use. Existing rules are not migrated or cleaned up. After switching, synchronize the rules to the target firewall and verify that they are effective before cleaning up the original firewall rules.',
|
||||
switchBackendHelper: 'Switch to {0}?',
|
||||
uninstalledStatus: 'Not installed',
|
||||
initializedStatus: 'Initialized',
|
||||
partiallyInitialized: 'Partially initialized',
|
||||
@@ -4139,6 +4140,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'Host INPUT rules do not directly protect this Docker published port. Click to open Container Port Guard.',
|
||||
notInitialized: 'Not initialized',
|
||||
familyUnsupported: 'The system does not support {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'After unbinding, all container ports temporarily return to Docker default access behavior. Existing protection settings are retained. Continue?',
|
||||
deleteDockerGuardPolicyConfirm: 'This endpoint will return to Docker default access behavior. Continue?',
|
||||
@@ -4188,7 +4190,7 @@ const message = {
|
||||
exportHelper: 'About to export {0} firewall rules. Continue?',
|
||||
importSuccess: 'Successfully imported {0} rules',
|
||||
importPartialSuccess: 'Import completed: {0} succeeded, {1} failed',
|
||||
basicStatus: 'Current chain {0} is unbound, please bind first!',
|
||||
basicStatus: 'The current firewall is unbound. Bind it first.',
|
||||
baseIptables: 'iptables Service',
|
||||
forwardIptables: 'iptables Port Forwarding Service',
|
||||
initMsg: 'About to initialize {0}, continue?',
|
||||
@@ -4210,6 +4212,8 @@ const message = {
|
||||
reject: 'Reject',
|
||||
allPorts: 'All Ports',
|
||||
allProtocolHelper: 'All protocols and ports',
|
||||
sourceAddressPlaceholder: 'e.g. 172.16.10.11, 172.16.0.0/24, 2001:db8::1, or 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'e.g. 80, 80,443, or 8080-8089',
|
||||
deleteRuleConfirm: 'Will delete {0} rules. Continue?',
|
||||
deleteUsedRuleConfirm:
|
||||
'This port is used by {0}. Deleting its allow rule may make the service unreachable. Continue?',
|
||||
@@ -6831,9 +6835,15 @@ const message = {
|
||||
partial_file: 'Incomplete File',
|
||||
},
|
||||
diskSize: 'Disk Size',
|
||||
isoHelper: 'The ISO is used to boot the VM for the first time and install an operating system.',
|
||||
osType: 'Operating System Type',
|
||||
osOther: 'Other',
|
||||
diskBus: 'Disk Bus',
|
||||
diskPath: 'Disk Path',
|
||||
isoPath: 'ISO Path',
|
||||
storagePool: 'Storage Pool',
|
||||
networkHelper: 'The network provides network connectivity for the VM.',
|
||||
storagePoolHelper: 'The storage pool stores VM disks and ISO files.',
|
||||
network: 'Network',
|
||||
bridgeName: 'Bridge Name',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -950,6 +950,7 @@ const message = {
|
||||
from_remote: 'Este modelo no fue descargado vía 1Panel, no hay registros de descarga relacionados.',
|
||||
no_logs: 'Los registros de descarga de este modelo han sido eliminados y no se pueden consultar.',
|
||||
vllmVersionHelper: 'Para servidores FusionXpark GB 10, seleccione la versión -cu130.',
|
||||
ascendVisibleDevices: 'Dispositivos Ascend visibles',
|
||||
vllmCommandPortHelper:
|
||||
'El comando de inicio debe usar el puerto {0}; de lo contrario, no se podrá acceder al servicio.',
|
||||
syncModelAccount: 'Sincronizar con cuenta de modelo',
|
||||
@@ -1140,6 +1141,7 @@ const message = {
|
||||
cachedToken: 'Tokens en caché',
|
||||
cacheHitRate: 'Tasa de acierto de caché',
|
||||
activeUsers: 'Usuarios activos',
|
||||
activeStreamingRequests: 'Solicitudes de streaming activas',
|
||||
activeModels: 'Modelos activos',
|
||||
failedRequests: 'Solicitudes fallidas',
|
||||
averageTokenPerRequest: 'Tokens promedio/solicitud',
|
||||
@@ -1444,7 +1446,8 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Monitoreo de GPU',
|
||||
gpuHelper: 'El sistema no detectó comandos NVIDIA-SMI o XPU-SMI. ¡Compruebe e inténtelo de nuevo!',
|
||||
gpuHelper:
|
||||
'No se detectó ningún comando de administración de GPU/XPU/NPU compatible. ¡Compruebe e inténtelo de nuevo!',
|
||||
process: 'Información del Proceso',
|
||||
type: 'Tipo',
|
||||
typeG: 'Gráficos',
|
||||
@@ -4173,10 +4176,9 @@ const message = {
|
||||
'Las reglas importadas se convierten para el backend actual {0}. Las reglas de origen no se modifican.',
|
||||
clearAllRulesHelper:
|
||||
'¿Eliminar las {0} reglas administrables del backend actual? Esta acción no se puede deshacer.',
|
||||
switchBackendHelper:
|
||||
'Esto solo cambia el backend de firewall administrado por 1Panel. El backend original no se migrará, detendrá ni limpiará. Exporte primero las reglas actuales. Después del cambio, inicialice el backend de destino e importe o verifique manualmente sus reglas. Las reglas externas y nativas del sistema no se exportan. ¿Continuar con el cambio a {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel ahora administra {0}. El backend original {1} no se detuvo ni limpió y aún puede estar activo. Inicialice el backend de destino, importe o verifique sus reglas y gestione manualmente el backend original solo después de confirmar que el acceso funciona correctamente.',
|
||||
backendSwitchNotice:
|
||||
'El cambio solo modifica el backend de firewall utilizado actualmente; no migra ni elimina las reglas existentes. Después del cambio, sincronice las reglas con el firewall de destino y confirme que estén activas antes de eliminar las reglas del firewall original.',
|
||||
switchBackendHelper: '¿Cambiar a {0}?',
|
||||
uninstalledStatus: 'No instalado',
|
||||
initializedStatus: 'Inicializado',
|
||||
partiallyInitialized: 'Parcialmente inicializado',
|
||||
@@ -4185,6 +4187,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'Las reglas INPUT del host no protegen directamente este puerto publicado por Docker. Haz clic para abrir la protección de puertos de contenedores.',
|
||||
notInitialized: 'No inicializado',
|
||||
familyUnsupported: 'El sistema no admite {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'Después de desvincular, todos los puertos de contenedores volverán temporalmente al acceso predeterminado de Docker. Se conservarán los ajustes de protección existentes. ¿Continuar?',
|
||||
deleteDockerGuardPolicyConfirm:
|
||||
@@ -4238,7 +4241,7 @@ const message = {
|
||||
exportHelper: 'A punto de exportar {0} reglas de firewall. ¿Continuar?',
|
||||
importSuccess: 'Se importaron correctamente {0} reglas',
|
||||
importPartialSuccess: 'Importación completada: {0} correctas, {1} fallidas',
|
||||
basicStatus: 'La cadena actual {0} no está vinculada, ¡vincule primero!',
|
||||
basicStatus: 'El firewall actual no está vinculado. Vincúlelo primero.',
|
||||
baseIptables: 'Servicio iptables',
|
||||
forwardIptables: 'Servicio de Reenvío de Puertos iptables',
|
||||
initMsg: 'A punto de inicializar {0}, ¿continuar?',
|
||||
@@ -4261,6 +4264,8 @@ const message = {
|
||||
reject: 'Rechazar',
|
||||
allPorts: 'Todos los Puertos',
|
||||
allProtocolHelper: 'Todos los protocolos y puertos',
|
||||
sourceAddressPlaceholder: 'p. ej. 172.16.10.11, 172.16.0.0/24, 2001:db8::1 o 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'p. ej. 80, 80,443 o 8080-8089',
|
||||
deleteRuleConfirm: 'Se eliminarán {0} reglas. ¿Continuar?',
|
||||
deleteUsedRuleConfirm:
|
||||
'Este puerto está siendo utilizado por {0}. Eliminar la regla de acceso puede dejar el servicio inaccesible. ¿Continuar?',
|
||||
@@ -6941,9 +6946,17 @@ const message = {
|
||||
partial_file: 'Archivo incompleto',
|
||||
},
|
||||
diskSize: 'Tamaño del disco',
|
||||
isoHelper:
|
||||
'La ISO se utiliza para iniciar la máquina virtual por primera vez e instalar un sistema operativo.',
|
||||
osType: 'Tipo de sistema operativo',
|
||||
osOther: 'Otro',
|
||||
diskBus: 'Bus de disco',
|
||||
diskPath: 'Ruta del disco',
|
||||
isoPath: 'Ruta ISO',
|
||||
storagePool: 'Pool de almacenamiento',
|
||||
networkHelper: 'La red proporciona conectividad de red a la máquina virtual.',
|
||||
storagePoolHelper:
|
||||
'El pool de almacenamiento guarda los discos de las máquinas virtuales y los archivos ISO.',
|
||||
network: 'Red',
|
||||
bridgeName: 'Nombre del bridge',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -933,6 +933,7 @@ const message = {
|
||||
from_remote: 'این مدل از طریق 1Panel دانلود نشده است، لاگ مربوط به دریافت وجود ندارد.',
|
||||
no_logs: 'لاگ دریافت این مدل حذف شده است و قابل مشاهده نیست.',
|
||||
vllmVersionHelper: 'برای سرورهای FusionXpark GB 10، لطفاً نسخه -cu130 را انتخاب کنید.',
|
||||
ascendVisibleDevices: 'دستگاههای Ascend قابل مشاهده',
|
||||
vllmCommandPortHelper:
|
||||
'فرمان راهاندازی باید از پورت {0} استفاده کند؛ در غیر این صورت سرویس قابل دسترسی نخواهد بود.',
|
||||
syncModelAccount: 'همگامسازی با حساب مدل',
|
||||
@@ -1120,6 +1121,7 @@ const message = {
|
||||
cachedToken: 'توکنهای کش شده',
|
||||
cacheHitRate: 'نرخ برخورد کش',
|
||||
activeUsers: 'کاربران فعال',
|
||||
activeStreamingRequests: 'درخواستهای جریانی فعال',
|
||||
activeModels: 'مدلهای فعال',
|
||||
failedRequests: 'درخواستهای ناموفق',
|
||||
averageTokenPerRequest: 'میانگین توکن در هر درخواست',
|
||||
@@ -1419,7 +1421,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'نظارت بر GPU',
|
||||
gpuHelper: 'سیستم دستورات NVIDIA-SMI یا XPU-SMI را شناسایی نکرد. لطفاً بررسی و دوباره تلاش کنید!',
|
||||
gpuHelper: 'هیچ دستور مدیریت GPU/XPU/NPU پشتیبانیشدهای شناسایی نشد. لطفاً بررسی و دوباره تلاش کنید!',
|
||||
process: 'اطلاعات فرآیند',
|
||||
processCount: 'تعداد فرآیندها',
|
||||
type: 'نوع',
|
||||
@@ -4081,10 +4083,9 @@ const message = {
|
||||
clearAllRules: 'پاککردن همه قوانین',
|
||||
importBackendHelper: 'قوانین واردشده برای بکاند فعلی {0} تبدیل میشوند. قوانین مبدأ تغییر نمیکنند.',
|
||||
clearAllRulesHelper: 'همه {0} قانون قابل مدیریت از بکاند فعلی حذف شوند؟ این عملیات قابل بازگشت نیست.',
|
||||
switchBackendHelper:
|
||||
'این عملیات فقط بکاند فایروالی را تغییر میدهد که از این پس توسط 1Panel مدیریت میشود. بکاند قبلی منتقل، متوقف یا پاکسازی نمیشود. ابتدا قوانین فعلی را خروجی بگیرید. پس از تغییر، بکاند مقصد را راهاندازی کرده و قوانین را دستی وارد یا بررسی کنید. قوانین خارجی و بومی سیستم خروجی گرفته نمیشوند. تغییر به {0} ادامه یابد؟',
|
||||
switchBackendSuccessHelper:
|
||||
'اکنون 1Panel، {0} را مدیریت میکند. بکاند قبلی {1} متوقف یا پاکسازی نشده و ممکن است هنوز در حال اجرا و مؤثر باشد. بکاند مقصد را راهاندازی کنید، قوانین را وارد یا بررسی کنید و فقط پس از اطمینان از دسترسی عادی، بکاند قبلی را دستی مدیریت کنید.',
|
||||
backendSwitchNotice:
|
||||
'تغییر فقط بکاند فایروالِ در حال استفاده را عوض میکند و قوانین موجود منتقل یا پاکسازی نمیشوند. پس از تغییر، قوانین را با فایروال مقصد همگام و اعمال شدن آنها را تأیید کنید، سپس قوانین فایروال قبلی را پاکسازی کنید.',
|
||||
switchBackendHelper: 'به {0} تغییر داده شود؟',
|
||||
uninstalledStatus: 'نصبنشده',
|
||||
initializedStatus: 'راهاندازی شده',
|
||||
partiallyInitialized: 'تا حدی راهاندازی شده',
|
||||
@@ -4093,6 +4094,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'قوانین INPUT میزبان مستقیماً از این پورت منتشرشده Docker محافظت نمیکنند. برای باز کردن محافظت از پورت کانتینر کلیک کنید.',
|
||||
notInitialized: 'راهاندازی نشده',
|
||||
familyUnsupported: 'سیستم از {0} پشتیبانی نمیکند',
|
||||
dockerGuardUnbindConfirm:
|
||||
'پس از لغو اتصال، همه پورتهای کانتینر موقتاً به رفتار دسترسی پیشفرض Docker بازمیگردند. تنظیمات محافظت موجود حفظ میشوند. ادامه میدهید؟',
|
||||
deleteDockerGuardPolicyConfirm: 'این نقطه پایانی به رفتار دسترسی پیشفرض Docker بازمیگردد. ادامه میدهید؟',
|
||||
@@ -4141,7 +4143,7 @@ const message = {
|
||||
exportHelper: 'در حال خروجی {0} قوانین دیواره آتش. ادامه میدهید؟',
|
||||
importSuccess: 'واردات {0} قانون با موفقیت انجام شد',
|
||||
importPartialSuccess: 'واردات انجام شد: {0} موفق، {1} ناموفق',
|
||||
basicStatus: 'زنجیره فعلی {0} متصل نیست، لطفاً ابتدا آن را متصل کنید!',
|
||||
basicStatus: 'دیواره آتش فعلی متصل نیست. ابتدا آن را متصل کنید!',
|
||||
baseIptables: 'سرویس iptables',
|
||||
forwardIptables: 'سرویس انتقال پورت iptables',
|
||||
initMsg: 'در حال راهاندازی {0}، ادامه میدهید؟',
|
||||
@@ -4163,6 +4165,8 @@ const message = {
|
||||
reject: 'رد',
|
||||
allPorts: 'همه پورتها',
|
||||
allProtocolHelper: 'همه پروتکلها و پورتها',
|
||||
sourceAddressPlaceholder: 'مثلاً: 172.16.10.11، 172.16.0.0/24، 2001:db8::1 یا 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'مثلاً: 80، 80,443 یا 8080-8089',
|
||||
deleteRuleConfirm: '{0} قانون حذف خواهند شد. ادامه میدهید؟',
|
||||
deleteUsedRuleConfirm:
|
||||
'این پورت توسط {0} استفاده میشود. حذف قانون مجاز ممکن است سرویس را غیرقابل دسترس کند. ادامه میدهید؟',
|
||||
@@ -6780,9 +6784,15 @@ const message = {
|
||||
partial_file: 'فایل ناتمام',
|
||||
},
|
||||
diskSize: 'ظرفیت دیسک',
|
||||
isoHelper: 'فایل ISO برای راهاندازی اولیه ماشین مجازی و نصب سیستمعامل استفاده میشود.',
|
||||
osType: 'نوع سیستمعامل',
|
||||
osOther: 'سایر',
|
||||
diskBus: 'گذرگاه دیسک',
|
||||
diskPath: 'مسیر دیسک',
|
||||
isoPath: 'مسیر ISO',
|
||||
storagePool: 'استخر ذخیرهسازی',
|
||||
networkHelper: 'شبکه، اتصال شبکهای ماشین مجازی را فراهم میکند.',
|
||||
storagePoolHelper: 'استخر ذخیرهسازی، دیسکهای ماشین مجازی و فایلهای ISO را ذخیره میکند.',
|
||||
network: 'شبکه',
|
||||
bridgeName: 'نام Bridge',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -937,6 +937,7 @@ const message = {
|
||||
from_remote: 'このモデルは1Panelを介してダウンロードされておらず、関連するプルログはありません。',
|
||||
no_logs: 'このモデルのプルログは削除されており、関連するログを表示できません。',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 サーバーでは -cu130 バージョンを選択してください。',
|
||||
ascendVisibleDevices: 'Ascend 可視デバイス',
|
||||
vllmCommandPortHelper:
|
||||
'起動コマンドではポート {0} を使用する必要があります。使用しない場合、サービスにアクセスできません。',
|
||||
syncModelAccount: 'モデルアカウントに同期',
|
||||
@@ -1123,6 +1124,7 @@ const message = {
|
||||
cachedToken: 'キャッシュ Token',
|
||||
cacheHitRate: 'キャッシュヒット率',
|
||||
activeUsers: 'アクティブユーザー',
|
||||
activeStreamingRequests: 'アクティブなストリーミングリクエスト',
|
||||
activeModels: 'アクティブモデル',
|
||||
failedRequests: '失敗リクエスト',
|
||||
averageTokenPerRequest: '平均 Token/リクエスト',
|
||||
@@ -1424,8 +1426,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 監視',
|
||||
gpuHelper:
|
||||
'システムが NVIDIA-SMI または XPU-SMI コマンドを検出しませんでした。確認して再試行してください!',
|
||||
gpuHelper: '対応する GPU/XPU/NPU 管理コマンドが検出されませんでした。確認して再試行してください!',
|
||||
process: 'プロセス情報',
|
||||
type: 'タイプ',
|
||||
typeG: 'グラフィックス',
|
||||
@@ -4112,10 +4113,9 @@ const message = {
|
||||
'インポートしたルールは現在の {0} バックエンド向けに変換されます。移行元のルールは変更されません。',
|
||||
clearAllRulesHelper:
|
||||
'現在のバックエンドにある管理可能な {0} 件のルールを削除します。この操作は元に戻せません。続行しますか?',
|
||||
switchBackendHelper:
|
||||
'この操作は 1Panel が今後管理するファイアウォールバックエンドのみを切り替えます。元のバックエンドの移行、停止、クリーンアップは行いません。先に現在のルールをエクスポートしてください。切り替え後、対象バックエンドを初期化し、ルールを手動でインポートまたは確認してください。外部ルールとシステム固有ルールはエクスポートされません。{0} への切り替えを続行しますか?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel は {0} を管理するように切り替わりました。元の {1} バックエンドは停止またはクリーンアップされておらず、依然として動作し、有効な場合があります。対象バックエンドを初期化し、ルールをインポートまたは確認し、アクセスが正常であることを確認してから元のバックエンドを手動で処理してください。',
|
||||
backendSwitchNotice:
|
||||
'切り替えでは現在使用するファイアウォールバックエンドのみが変更され、既存のルールは移行も削除もされません。切り替え後、対象ファイアウォールにルールを同期して有効であることを確認してから、元のファイアウォールルールを削除してください。',
|
||||
switchBackendHelper: '{0} に切り替えますか?',
|
||||
uninstalledStatus: '未インストール',
|
||||
initializedStatus: '初期化済み',
|
||||
partiallyInitialized: '一部初期化済み',
|
||||
@@ -4124,6 +4124,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'ホストの INPUT ルールでは、この Docker 公開ポートを直接保護できません。クリックしてコンテナポート保護を開きます。',
|
||||
notInitialized: '未初期化',
|
||||
familyUnsupported: 'システムは {0} をサポートしていません',
|
||||
dockerGuardUnbindConfirm:
|
||||
'バインドを解除すると、すべてのコンテナポートは一時的に Docker のデフォルト動作に戻ります。既存の保護設定は保持されます。続行しますか?',
|
||||
deleteDockerGuardPolicyConfirm: 'このエンドポイントは Docker のデフォルト動作に戻ります。続行しますか?',
|
||||
@@ -4173,7 +4174,7 @@ const message = {
|
||||
exportHelper: '{0} 件のファイアウォールルールをエクスポートします。続行しますか?',
|
||||
importSuccess: '{0} 件のルールを正常にインポートしました',
|
||||
importPartialSuccess: 'インポート完了: {0} 件成功、{1} 件失敗',
|
||||
basicStatus: '現在のチェーン {0} は未バインドです。まずバインドしてください!',
|
||||
basicStatus: '現在のファイアウォールはバインドされていません。先にバインドしてください。',
|
||||
baseIptables: 'iptables サービス',
|
||||
forwardIptables: 'iptables ポート転送サービス',
|
||||
initMsg: '{0} を初期化します。続行しますか?',
|
||||
@@ -4194,6 +4195,8 @@ const message = {
|
||||
reject: '拒否',
|
||||
allPorts: 'すべてのポート',
|
||||
allProtocolHelper: 'すべてのプロトコルとポート',
|
||||
sourceAddressPlaceholder: '例: 172.16.10.11、172.16.0.0/24、2001:db8::1、2001:db8::/64',
|
||||
destinationPortPlaceholder: '例: 80、80,443、8080-8089',
|
||||
deleteRuleConfirm: '{0} 個のルールを削除します。続行しますか?',
|
||||
deleteUsedRuleConfirm:
|
||||
'このポートは {0} が使用中です。許可ルールを削除するとサービスにアクセスできなくなる可能性があります。続行しますか?',
|
||||
@@ -6820,9 +6823,15 @@ const message = {
|
||||
partial_file: '未完了ファイル',
|
||||
},
|
||||
diskSize: 'ディスク容量',
|
||||
isoHelper: 'ISO は仮想マシンの初回起動と OS のインストールに使用します。',
|
||||
osType: 'OS タイプ',
|
||||
osOther: 'その他',
|
||||
diskBus: 'ディスクバス',
|
||||
diskPath: 'ディスクパス',
|
||||
isoPath: 'ISO パス',
|
||||
storagePool: 'ストレージプール',
|
||||
networkHelper: 'ネットワークは仮想マシンにネットワーク接続を提供します。',
|
||||
storagePoolHelper: 'ストレージプールは仮想マシンのディスクと ISO ファイルを保存します。',
|
||||
network: 'ネットワーク',
|
||||
bridgeName: 'ブリッジ名',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -927,6 +927,7 @@ const message = {
|
||||
from_remote: '이 모델은 1Panel을 통해 다운로드되지 않았으며 관련 풀 로그가 없습니다.',
|
||||
no_logs: '이 모델의 풀 로그가 삭제되어 관련 로그를 볼 수 없습니다.',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 서버는 -cu130 버전을 선택하세요.',
|
||||
ascendVisibleDevices: 'Ascend 표시 장치',
|
||||
vllmCommandPortHelper: '시작 명령은 {0} 포트를 사용해야 하며, 그렇지 않으면 서비스에 접근할 수 없습니다.',
|
||||
syncModelAccount: '모델 계정에 동기화',
|
||||
modelAccountAddressHelper:
|
||||
@@ -1112,6 +1113,7 @@ const message = {
|
||||
cachedToken: '캐시 Token',
|
||||
cacheHitRate: '캐시 적중률',
|
||||
activeUsers: '활성 사용자',
|
||||
activeStreamingRequests: '활성 스트리밍 요청',
|
||||
activeModels: '활성 모델',
|
||||
failedRequests: '실패한 요청',
|
||||
averageTokenPerRequest: '평균 Token/요청',
|
||||
@@ -1410,7 +1412,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 모니터링',
|
||||
gpuHelper: '시스템에서 NVIDIA-SMI 또는 XPU-SMI 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!',
|
||||
gpuHelper: '지원되는 GPU/XPU/NPU 관리 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!',
|
||||
process: '프로세스 정보',
|
||||
type: '유형',
|
||||
typeG: '그래픽',
|
||||
@@ -4033,10 +4035,9 @@ const message = {
|
||||
importBackendHelper: '가져온 규칙은 현재 {0} 백엔드에 맞게 변환됩니다. 원본 백엔드의 규칙은 변경되지 않습니다.',
|
||||
clearAllRulesHelper:
|
||||
'현재 백엔드의 관리 가능한 규칙 {0}개를 삭제합니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?',
|
||||
switchBackendHelper:
|
||||
'이 작업은 1Panel이 이후 관리할 방화벽 백엔드만 변경합니다. 기존 백엔드는 이전·중지·정리되지 않습니다. 먼저 현재 규칙을 내보내십시오. 전환 후 대상 백엔드를 초기화하고 규칙을 수동으로 가져오거나 확인하십시오. 외부 규칙과 시스템 기본 규칙은 내보내지지 않습니다. {0}(으)로 계속 전환하시겠습니까?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel이 이제 {0}을(를) 관리합니다. 기존 {1} 백엔드는 중지되거나 정리되지 않아 여전히 실행 및 적용될 수 있습니다. 대상 백엔드를 초기화하고 규칙을 가져오거나 확인한 뒤, 접속이 정상인지 확인한 후에만 기존 백엔드를 수동으로 처리하십시오.',
|
||||
backendSwitchNotice:
|
||||
'전환은 현재 사용하는 방화벽 백엔드만 변경하며 기존 규칙을 이전하거나 정리하지 않습니다. 전환 후 대상 방화벽에 규칙을 동기화하고 적용 여부를 확인한 다음 기존 방화벽 규칙을 정리하십시오.',
|
||||
switchBackendHelper: '{0}(으)로 전환하시겠습니까?',
|
||||
uninstalledStatus: '설치되지 않음',
|
||||
initializedStatus: '초기화됨',
|
||||
partiallyInitialized: '일부 초기화됨',
|
||||
@@ -4045,6 +4046,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'호스트 INPUT 규칙은 이 Docker 게시 포트를 직접 보호하지 않습니다. 클릭하여 컨테이너 포트 보호를 여세요.',
|
||||
notInitialized: '초기화되지 않음',
|
||||
familyUnsupported: '시스템에서 {0}을 지원하지 않습니다',
|
||||
dockerGuardUnbindConfirm:
|
||||
'바인딩을 해제하면 모든 컨테이너 포트가 일시적으로 Docker 기본 접근 방식으로 돌아갑니다. 기존 보호 설정은 유지됩니다. 계속하시겠습니까?',
|
||||
deleteDockerGuardPolicyConfirm: '이 엔드포인트가 Docker 기본 접근 방식으로 돌아갑니다. 계속하시겠습니까?',
|
||||
@@ -4093,7 +4095,7 @@ const message = {
|
||||
exportHelper: '{0}개의 방화벽 규칙을 내보내려고 합니다. 계속하시겠습니까?',
|
||||
importSuccess: '{0}개의 규칙을 성공적으로 가져왔습니다',
|
||||
importPartialSuccess: '가져오기 완료: 성공 {0}건, 실패 {1}건',
|
||||
basicStatus: '현재 체인 {0}이(가) 바인딩되지 않았습니다. 먼저 바인딩하세요!',
|
||||
basicStatus: '현재 방화벽이 바인딩되지 않았습니다. 먼저 바인딩하세요!',
|
||||
baseIptables: 'iptables 서비스',
|
||||
forwardIptables: 'iptables 포트 포워딩 서비스',
|
||||
initMsg: '{0}을(를) 초기화하려고 합니다. 계속하시겠습니까?',
|
||||
@@ -4113,6 +4115,8 @@ const message = {
|
||||
reject: '거부',
|
||||
allPorts: '모든 포트',
|
||||
allProtocolHelper: '모든 프로토콜 및 포트',
|
||||
sourceAddressPlaceholder: '예: 172.16.10.11, 172.16.0.0/24, 2001:db8::1 또는 2001:db8::/64',
|
||||
destinationPortPlaceholder: '예: 80, 80,443 또는 8080-8089',
|
||||
deleteRuleConfirm: '{0}개의 규칙을 삭제합니다. 계속하시겠습니까?',
|
||||
deleteUsedRuleConfirm:
|
||||
'이 포트는 {0}에서 사용 중입니다. 허용 규칙을 삭제하면 서비스에 접근하지 못할 수 있습니다. 계속하시겠습니까?',
|
||||
@@ -6682,9 +6686,15 @@ const message = {
|
||||
partial_file: '미완료 파일',
|
||||
},
|
||||
diskSize: '디스크 용량',
|
||||
isoHelper: 'ISO는 가상 머신을 처음 부팅하고 운영 체제를 설치하는 데 사용됩니다.',
|
||||
osType: '운영 체제 유형',
|
||||
osOther: '기타',
|
||||
diskBus: '디스크 버스',
|
||||
diskPath: '디스크 경로',
|
||||
isoPath: 'ISO 경로',
|
||||
storagePool: '스토리지 풀',
|
||||
networkHelper: '네트워크는 가상 머신에 네트워크 연결을 제공합니다.',
|
||||
storagePoolHelper: '스토리지 풀은 가상 머신 디스크와 ISO 파일을 저장합니다.',
|
||||
network: '네트워크',
|
||||
bridgeName: '브리지 이름',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -927,6 +927,7 @@ const message = {
|
||||
from_remote: 'ໂມເດວນີ້ບໍ່ໄດ້ດາວໂຫຼດຜ່ານ 1Panel, ບໍ່ມີລັອກການດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງ.',
|
||||
no_logs: 'ລັອກການດຶງຂໍ້ມູນຂອງໂມເດວນີ້ຖືກລຶບແລ້ວ ແລະ ບໍ່ສາມາດເບິ່ງໄດ້.',
|
||||
vllmVersionHelper: 'ສຳລັບເຊີເວີ FusionXpark GB 10, ກະລຸນາເລືອກເວີຊັນ -cu130.',
|
||||
ascendVisibleDevices: 'ອຸປະກອນ Ascend ທີ່ເຫັນໄດ້',
|
||||
vllmCommandPortHelper: 'ຄຳສັ່ງເລີ່ມຕົ້ນຕ້ອງໃຊ້ພອດ {0}; ບໍ່ດັ່ງນັ້ນຈະບໍ່ສາມາດເຂົ້າເຖິງບໍລິການໄດ້.',
|
||||
syncModelAccount: 'ຊິ້ງຄ໌ໄປຍັງບັນຊີໂມເດວ',
|
||||
modelAccountAddressHelper:
|
||||
@@ -1113,6 +1114,7 @@ const message = {
|
||||
cachedToken: 'Token ທີ່ແຄດໄວ້',
|
||||
cacheHitRate: 'ອັດຕາການພົບ Cache',
|
||||
activeUsers: 'ຜູ້ໃຊ້ທີ່ເຄື່ອນໄຫວ',
|
||||
activeStreamingRequests: 'ຄຳຮ້ອງຂໍສະຕຣີມທີ່ກຳລັງເຮັດວຽກ',
|
||||
activeModels: 'ໂມເດວທີ່ເຄື່ອນໄຫວ',
|
||||
failedRequests: 'ຄຳຂໍທີ່ລົ້ມເຫຼວ',
|
||||
averageTokenPerRequest: 'Token ສະເລ່ຍ/ຄຳຂໍ',
|
||||
@@ -1405,7 +1407,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'ຕິດຕາມ GPU',
|
||||
gpuHelper: 'ລະບົບກວດບໍ່ພົບຄຳສັ່ງ NVIDIA-SMI ຫຼື XPU-SMI. ກະລຸນາກວດສອບ ແລະ ລອງໃໝ່!',
|
||||
gpuHelper: 'ກວດບໍ່ພົບຄຳສັ່ງຈັດການ GPU/XPU/NPU ທີ່ຮອງຮັບ. ກະລຸນາກວດສອບ ແລະ ລອງໃໝ່!',
|
||||
process: 'ຂໍ້ມູນໂປຣເຊສ',
|
||||
processCount: 'ຈຳນວນໂປຣເຊສ',
|
||||
type: 'ປະເພດ',
|
||||
@@ -4002,10 +4004,9 @@ const message = {
|
||||
clearAllRules: 'ລ້າງກົດທັງໝົດ',
|
||||
importBackendHelper: 'ກົດທີ່ນຳເຂົ້າຈະຖືກປ່ຽນໃຫ້ເໝາະກັບແບັກເອນ {0} ປັດຈຸບັນ. ກົດຕົ້ນທາງຈະບໍ່ຖືກປ່ຽນ.',
|
||||
clearAllRulesHelper: 'ລຶບກົດທີ່ຈັດການໄດ້ {0} ລາຍການຈາກແບັກເອນປັດຈຸບັນບໍ? ການດຳເນີນການນີ້ບໍ່ສາມາດຍ້ອນກັບໄດ້.',
|
||||
switchBackendHelper:
|
||||
'ການດຳເນີນການນີ້ຈະປ່ຽນສະເພາະແບັກເອັນໄຟຣ໌ວໍທີ່ 1Panel ຈະຈັດການຕໍ່ໄປ. ແບັກເອັນເດີມຈະບໍ່ຖືກຍ້າຍ, ຢຸດ ຫຼື ລ້າງ. ກະລຸນາສົ່ງອອກກົດປັດຈຸບັນກ່ອນ. ຫຼັງຈາກປ່ຽນ ໃຫ້ເລີ່ມແບັກເອັນເປົ້າໝາຍ ແລະ ນຳເຂົ້າ ຫຼື ກວດສອບກົດດ້ວຍຕົນເອງ. ກົດພາຍນອກ ແລະ ກົດດັ້ງເດີມຂອງລະບົບຈະບໍ່ຖືກສົ່ງອອກ. ສືບຕໍ່ປ່ຽນເປັນ {0} ບໍ?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel ຕອນນີ້ຈັດການ {0}. ແບັກເອັນເດີມ {1} ບໍ່ຖືກຢຸດ ຫຼື ລ້າງ ແລະ ອາດຍັງເຮັດວຽກຢູ່. ໃຫ້ເລີ່ມແບັກເອັນເປົ້າໝາຍ, ນຳເຂົ້າ ຫຼື ກວດສອບກົດ ແລະ ຈັດການແບັກເອັນເດີມດ້ວຍຕົນເອງຫຼັງຈາກຢືນຢັນວ່າການເຂົ້າເຖິງເຮັດວຽກປົກກະຕິ.',
|
||||
backendSwitchNotice:
|
||||
'ການສະຫຼັບຈະປ່ຽນສະເພາະແບັກເອັນໄຟຣ໌ວໍທີ່ກຳລັງໃຊ້ງານ ແລະ ຈະບໍ່ຍ້າຍ ຫຼື ລ້າງກົດທີ່ມີຢູ່. ຫຼັງຈາກສະຫຼັບ ໃຫ້ຊິງກົດໄປຫາໄຟຣ໌ວໍເປົ້າໝາຍ ແລະ ຢືນຢັນວ່າກົດມີຜົນກ່ອນລ້າງກົດຂອງໄຟຣ໌ວໍເດີມ.',
|
||||
switchBackendHelper: 'ຢືນຢັນການສະຫຼັບເປັນ {0} ບໍ?',
|
||||
uninstalledStatus: 'ຍັງບໍ່ໄດ້ຕິດຕັ້ງ',
|
||||
initializedStatus: 'ເລີ່ມຕົ້ນແລ້ວ',
|
||||
partiallyInitialized: 'ເລີ່ມຕົ້ນບາງສ່ວນ',
|
||||
@@ -4014,6 +4015,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'ກົດ INPUT ຂອງໂຮສບໍ່ໄດ້ປ້ອງກັນພອດ Docker ນີ້ໂດຍກົງ. ຄລິກເພື່ອເປີດການປ້ອງກັນພອດຄອນເທນເນີ.',
|
||||
notInitialized: 'ຍັງບໍ່ໄດ້ເລີ່ມຕົ້ນ',
|
||||
familyUnsupported: 'ລະບົບບໍ່ຮອງຮັບ {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'ຫຼັງຈາກຍົກເລີກການຜູກ, ພອດຄອນເທນເນີທັງໝົດຈະກັບໄປໃຊ້ການເຂົ້າເຖິງເລີ່ມຕົ້ນຂອງ Docker ຊົ່ວຄາວ. ການຕັ້ງຄ່າປ້ອງກັນຈະຖືກຮັກສາໄວ້. ສືບຕໍ່ບໍ?',
|
||||
deleteDockerGuardPolicyConfirm: 'ຈຸດເຊື່ອມຕໍ່ນີ້ຈະກັບໄປໃຊ້ການເຂົ້າເຖິງເລີ່ມຕົ້ນຂອງ Docker. ສືບຕໍ່ບໍ?',
|
||||
@@ -4059,7 +4061,7 @@ const message = {
|
||||
exportHelper: 'ກຳລັງຈະສົ່ງອອກກົດລະບຽບໄຟວໍ {0} ລາຍການ. ຕ້ອງການຕໍ່ຫຼືບໍ່?',
|
||||
importSuccess: 'ນຳເຂົ້າ {0} ກົດລະບຽບສຳເລັດແລ້ວ',
|
||||
importPartialSuccess: 'ນຳເຂົ້າສຳເລັດ: ສຳເລັດ {0}, ລົ້ມເຫຼວ {1}',
|
||||
basicStatus: 'Chain {0} ຍັງບໍ່ໄດ້ຜູກມັດ, ກະລຸນາຜູກມັດກ່ອນ!',
|
||||
basicStatus: 'ໄຟວໍປັດຈຸບັນຍັງບໍ່ໄດ້ຜູກມັດ. ກະລຸນາຜູກມັດກ່ອນ!',
|
||||
baseIptables: 'ບໍລິການ iptables',
|
||||
forwardIptables: 'ບໍລິການສົ່ງຕໍ່ພອດ iptables',
|
||||
initMsg: 'ກຳລັງຈະເລີ່ມຕົ້ນ {0}, ຕ້ອງການຕໍ່ຫຼືບໍ່?',
|
||||
@@ -4079,6 +4081,8 @@ const message = {
|
||||
reject: 'ປະຕິເສດ (Reject)',
|
||||
allPorts: 'ທຸກໆພອດ',
|
||||
allProtocolHelper: 'ໂປຣໂຕຄໍ ແລະ ພອດທັງໝົດ',
|
||||
sourceAddressPlaceholder: 'ຕົວຢ່າງ: 172.16.10.11, 172.16.0.0/24, 2001:db8::1 ຫຼື 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'ຕົວຢ່າງ: 80, 80,443 ຫຼື 8080-8089',
|
||||
deleteRuleConfirm: 'ຈະລຶບ {0} ກົດລະບຽບ. ຕ້ອງການຕໍ່ຫຼືບໍ່?',
|
||||
deleteUsedRuleConfirm: 'ພອດນີ້ກຳລັງຖືກໃຊ້ໂດຍ {0}. ການລຶບກົດອະນຸຍາດອາດເຮັດໃຫ້ບໍລິການເຂົ້າເຖິງບໍ່ໄດ້. ສືບຕໍ່ບໍ?',
|
||||
editRuleConfirm: 'ຈະປ່ຽນຟິວຕໍ່ໄປນີ້: {0}. ກົດຈະຖືກນຳໃຊ້ແລະກວດສອບທັນທີ. ສືບຕໍ່ບໍ?',
|
||||
@@ -6597,9 +6601,15 @@ const message = {
|
||||
partial_file: 'ໄຟລ໌ບໍ່ສົມບູນ',
|
||||
},
|
||||
diskSize: 'ຂະໜາດດິສກ໌',
|
||||
isoHelper: 'ISO ໃຊ້ສຳລັບເລີ່ມເຄື່ອງສະເໝືອນຄັ້ງທຳອິດ ແລະ ຕິດຕັ້ງລະບົບປະຕິບັດການ.',
|
||||
osType: 'ປະເພດລະບົບປະຕິບັດການ',
|
||||
osOther: 'ອື່ນໆ',
|
||||
diskBus: 'ບັສດິສກ໌',
|
||||
diskPath: 'ເສັ້ນທາງດິສກ໌',
|
||||
isoPath: 'ເສັ້ນທາງ ISO',
|
||||
storagePool: 'ພູລຈັດເກັບ',
|
||||
networkHelper: 'ເຄືອຂ່າຍສະໜອງການເຊື່ອມຕໍ່ໃຫ້ເຄື່ອງສະເໝືອນ.',
|
||||
storagePoolHelper: 'ພູລຈັດເກັບໃຊ້ເກັບດິສກ໌ເຄື່ອງສະເໝືອນ ແລະ ໄຟລ໌ ISO.',
|
||||
network: 'ເຄືອຂ່າຍ',
|
||||
bridgeName: 'ຊື່ Bridge',
|
||||
natNetworkHelper: 'VM ສາມາດເຂົ້າເຄືອຂ່າຍຜ່ານ Host, ແຕ່ອຸປະກອນພາຍນອກໂດຍທົ່ວໄປບໍ່ສາມາດເຂົ້າ VM ໂດຍກົງໄດ້.',
|
||||
|
||||
@@ -958,6 +958,7 @@ const message = {
|
||||
from_remote: 'Model ini tidak dimuat turun melalui 1Panel; tiada log muat turun berkaitan.',
|
||||
no_logs: 'Log muat turun model ini telah dipadam dan tidak boleh dilihat.',
|
||||
vllmVersionHelper: 'Untuk pelayan FusionXpark GB 10, sila pilih versi -cu130.',
|
||||
ascendVisibleDevices: 'Peranti Ascend yang kelihatan',
|
||||
vllmCommandPortHelper:
|
||||
'Perintah permulaan mesti menggunakan port {0}; jika tidak, perkhidmatan tidak dapat diakses.',
|
||||
syncModelAccount: 'Segerakkan ke akaun model',
|
||||
@@ -1145,6 +1146,7 @@ const message = {
|
||||
cachedToken: 'Token cache',
|
||||
cacheHitRate: 'Kadar hit cache',
|
||||
activeUsers: 'Pengguna aktif',
|
||||
activeStreamingRequests: 'Permintaan penstriman aktif',
|
||||
activeModels: 'Model aktif',
|
||||
failedRequests: 'Permintaan gagal',
|
||||
averageTokenPerRequest: 'Purata Token/permintaan',
|
||||
@@ -1447,7 +1449,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Pemantauan GPU',
|
||||
gpuHelper: 'Sistem tidak mengesan arahan NVIDIA-SMI atau XPU-SMI. Sila periksa dan cuba lagi!',
|
||||
gpuHelper: 'Tiada arahan pengurusan GPU/XPU/NPU yang disokong dikesan. Sila periksa dan cuba lagi!',
|
||||
process: 'Maklumat Proses',
|
||||
type: 'Jenis',
|
||||
typeG: 'Grafik',
|
||||
@@ -4195,10 +4197,9 @@ const message = {
|
||||
'Peraturan yang diimport ditukar untuk bahagian belakang semasa {0}. Peraturan sumber tidak diubah.',
|
||||
clearAllRulesHelper:
|
||||
'Padam semua {0} peraturan yang boleh diurus daripada bahagian belakang semasa? Tindakan ini tidak boleh dibuat asal.',
|
||||
switchBackendHelper:
|
||||
'Operasi ini hanya menukar bahagian belakang tembok api yang akan diurus oleh 1Panel selepas ini. Bahagian belakang asal tidak akan dipindahkan, dihentikan atau dibersihkan. Eksport peraturan semasa terlebih dahulu. Selepas bertukar, mulakan bahagian belakang sasaran dan import atau semak peraturan secara manual. Peraturan luaran dan asli sistem tidak dieksport. Teruskan pertukaran kepada {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel kini mengurus {0}. Bahagian belakang asal {1} tidak dihentikan atau dibersihkan dan mungkin masih berjalan serta berkuat kuasa. Mulakan bahagian belakang sasaran, import atau semak peraturan, dan urus bahagian belakang asal secara manual hanya selepas mengesahkan akses berfungsi seperti biasa.',
|
||||
backendSwitchNotice:
|
||||
'Penukaran hanya mengubah bahagian belakang tembok api yang sedang digunakan; peraturan sedia ada tidak dipindahkan atau dibersihkan. Selepas menukar, segerakkan peraturan ke tembok api sasaran dan sahkan ia berkuat kuasa sebelum membersihkan peraturan tembok api asal.',
|
||||
switchBackendHelper: 'Tukar kepada {0}?',
|
||||
uninstalledStatus: 'Belum dipasang',
|
||||
initializedStatus: 'Dimulakan',
|
||||
partiallyInitialized: 'Dimulakan sebahagian',
|
||||
@@ -4207,6 +4208,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'Peraturan INPUT hos tidak melindungi port terbitan Docker ini secara langsung. Klik untuk membuka perlindungan port bekas.',
|
||||
notInitialized: 'Belum dimulakan',
|
||||
familyUnsupported: 'Sistem tidak menyokong {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'Selepas dinyahikat, semua port bekas akan kembali sementara kepada akses lalai Docker. Tetapan perlindungan sedia ada dikekalkan. Teruskan?',
|
||||
deleteDockerGuardPolicyConfirm: 'Titik akhir ini akan kembali kepada akses lalai Docker. Teruskan?',
|
||||
@@ -4255,7 +4257,7 @@ const message = {
|
||||
exportHelper: 'Akan mengeksport {0} peraturan firewall. Teruskan?',
|
||||
importSuccess: '{0} peraturan berjaya diimport',
|
||||
importPartialSuccess: 'Import selesai: {0} berjaya, {1} gagal',
|
||||
basicStatus: 'Rantaian semasa {0} tidak terikat, sila ikat dahulu!',
|
||||
basicStatus: 'Firewall semasa tidak terikat. Sila ikat dahulu.',
|
||||
baseIptables: 'Perkhidmatan iptables',
|
||||
forwardIptables: 'Perkhidmatan Penerusan Port iptables',
|
||||
initMsg: 'Akan memulakan {0}, teruskan?',
|
||||
@@ -4276,6 +4278,8 @@ const message = {
|
||||
reject: 'Tolak',
|
||||
allPorts: 'Semua Port',
|
||||
allProtocolHelper: 'Semua protokol dan port',
|
||||
sourceAddressPlaceholder: 'contoh: 172.16.10.11, 172.16.0.0/24, 2001:db8::1 atau 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'contoh: 80, 80,443 atau 8080-8089',
|
||||
deleteRuleConfirm: 'Akan memadam {0} peraturan. Teruskan?',
|
||||
deleteUsedRuleConfirm:
|
||||
'Port ini sedang digunakan oleh {0}. Memadam peraturan benarkan mungkin menyebabkan perkhidmatan tidak dapat dicapai. Teruskan?',
|
||||
@@ -6938,9 +6942,15 @@ const message = {
|
||||
partial_file: 'Fail tidak lengkap',
|
||||
},
|
||||
diskSize: 'Saiz Cakera',
|
||||
isoHelper: 'ISO digunakan untuk memulakan mesin maya buat kali pertama dan memasang sistem pengendalian.',
|
||||
osType: 'Jenis sistem pengendalian',
|
||||
osOther: 'Lain-lain',
|
||||
diskBus: 'Bas cakera',
|
||||
diskPath: 'Laluan Cakera',
|
||||
isoPath: 'Laluan ISO',
|
||||
storagePool: 'Kolam Storan',
|
||||
networkHelper: 'Rangkaian menyediakan sambungan rangkaian untuk mesin maya.',
|
||||
storagePoolHelper: 'Kolam storan menyimpan cakera mesin maya dan fail ISO.',
|
||||
network: 'Rangkaian',
|
||||
bridgeName: 'Nama Bridge',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -954,6 +954,7 @@ const message = {
|
||||
from_remote: 'Este modelo não foi baixado pelo 1Panel; não há logs de download relacionados.',
|
||||
no_logs: 'Os logs de download deste modelo foram excluídos e não podem ser visualizados.',
|
||||
vllmVersionHelper: 'Para servidores FusionXpark GB 10, selecione a versão -cu130.',
|
||||
ascendVisibleDevices: 'Dispositivos Ascend visíveis',
|
||||
vllmCommandPortHelper:
|
||||
'O comando de inicialização deve usar a porta {0}; caso contrário, o serviço ficará inacessível.',
|
||||
syncModelAccount: 'Sincronizar com conta de modelo',
|
||||
@@ -1142,6 +1143,7 @@ const message = {
|
||||
cachedToken: 'Tokens em cache',
|
||||
cacheHitRate: 'Taxa de acerto do cache',
|
||||
activeUsers: 'Usuários ativos',
|
||||
activeStreamingRequests: 'Solicitações de streaming ativas',
|
||||
activeModels: 'Modelos ativos',
|
||||
failedRequests: 'Requisições com falha',
|
||||
averageTokenPerRequest: 'Média de Token/requisição',
|
||||
@@ -1445,7 +1447,8 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Monitoramento de GPU',
|
||||
gpuHelper: 'O sistema não detectou comandos NVIDIA-SMI ou XPU-SMI. Verifique e tente novamente!',
|
||||
gpuHelper:
|
||||
'Nenhum comando de gerenciamento de GPU/XPU/NPU compatível foi detectado. Verifique e tente novamente!',
|
||||
process: 'Informações do Processo',
|
||||
type: 'Tipo',
|
||||
typeG: 'Gráficos',
|
||||
@@ -4213,10 +4216,9 @@ const message = {
|
||||
importBackendHelper:
|
||||
'As regras importadas são convertidas para o backend atual {0}. As regras de origem não são alteradas.',
|
||||
clearAllRulesHelper: 'Excluir as {0} regras gerenciáveis do backend atual? Esta ação não pode ser desfeita.',
|
||||
switchBackendHelper:
|
||||
'Esta operação altera apenas o backend de firewall que o 1Panel gerenciará daqui em diante. O backend original não será migrado, interrompido nem limpo. Exporte primeiro as regras atuais. Após a troca, inicialize o backend de destino e importe ou verifique manualmente as regras. Regras externas e nativas do sistema não são exportadas. Continuar a troca para {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'O 1Panel agora gerencia {0}. O backend original {1} não foi interrompido nem limpo e ainda pode estar em execução e em vigor. Inicialize o backend de destino, importe ou verifique as regras e trate manualmente o backend original somente após confirmar que o acesso funciona normalmente.',
|
||||
backendSwitchNotice:
|
||||
'A troca altera apenas o backend de firewall atualmente utilizado; as regras existentes não são migradas nem removidas. Após a troca, sincronize as regras com o firewall de destino e confirme que estão ativas antes de remover as regras do firewall original.',
|
||||
switchBackendHelper: 'Mudar para {0}?',
|
||||
uninstalledStatus: 'Não instalado',
|
||||
initializedStatus: 'Inicializado',
|
||||
partiallyInitialized: 'Parcialmente inicializado',
|
||||
@@ -4225,6 +4227,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'As regras INPUT do host não protegem diretamente esta porta publicada pelo Docker. Clique para abrir a proteção de portas de contêineres.',
|
||||
notInitialized: 'Não inicializado',
|
||||
familyUnsupported: 'O sistema não oferece suporte a {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'Após desvincular, todas as portas de contêineres retornarão temporariamente ao acesso padrão do Docker. As configurações de proteção existentes serão mantidas. Continuar?',
|
||||
deleteDockerGuardPolicyConfirm: 'Este endpoint retornará ao acesso padrão do Docker. Continuar?',
|
||||
@@ -4275,7 +4278,7 @@ const message = {
|
||||
exportHelper: 'Prestes a exportar {0} regras de firewall. Continuar?',
|
||||
importSuccess: '{0} regras importadas com sucesso',
|
||||
importPartialSuccess: 'Importação concluída: {0} sucesso, {1} falha',
|
||||
basicStatus: 'A cadeia atual {0} não está vinculada, vincule primeiro!',
|
||||
basicStatus: 'O firewall atual não está vinculado. Vincule-o primeiro.',
|
||||
baseIptables: 'Serviço iptables',
|
||||
forwardIptables: 'Serviço de Encaminhamento de Porta iptables',
|
||||
initMsg: 'Prestes a inicializar {0}, continuar?',
|
||||
@@ -4298,6 +4301,8 @@ const message = {
|
||||
reject: 'Rejeitar',
|
||||
allPorts: 'Todas as Portas',
|
||||
allProtocolHelper: 'Todos os protocolos e portas',
|
||||
sourceAddressPlaceholder: 'por exemplo: 172.16.10.11, 172.16.0.0/24, 2001:db8::1 ou 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'por exemplo: 80, 80,443 ou 8080-8089',
|
||||
deleteRuleConfirm: 'Excluirá {0} regras. Continuar?',
|
||||
deleteUsedRuleConfirm:
|
||||
'Esta porta está sendo usada por {0}. Excluir a regra de liberação pode tornar o serviço inacessível. Continuar?',
|
||||
@@ -6981,9 +6986,16 @@ const message = {
|
||||
partial_file: 'Arquivo incompleto',
|
||||
},
|
||||
diskSize: 'Tamanho do Disco',
|
||||
isoHelper:
|
||||
'A ISO é usada para iniciar a máquina virtual pela primeira vez e instalar um sistema operacional.',
|
||||
osType: 'Tipo de sistema operacional',
|
||||
osOther: 'Outro',
|
||||
diskBus: 'Barramento de disco',
|
||||
diskPath: 'Caminho do Disco',
|
||||
isoPath: 'Caminho do ISO',
|
||||
storagePool: 'Pool de Armazenamento',
|
||||
networkHelper: 'A rede fornece conectividade de rede para a máquina virtual.',
|
||||
storagePoolHelper: 'O pool de armazenamento guarda os discos das máquinas virtuais e os arquivos ISO.',
|
||||
network: 'Rede',
|
||||
bridgeName: 'Nome da Bridge',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -947,6 +947,7 @@ const message = {
|
||||
from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.',
|
||||
no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.',
|
||||
vllmVersionHelper: 'Для серверов FusionXpark GB 10 выберите версию -cu130.',
|
||||
ascendVisibleDevices: 'Видимые устройства Ascend',
|
||||
vllmCommandPortHelper: 'Команда запуска должна использовать порт {0}, иначе сервис будет недоступен.',
|
||||
syncModelAccount: 'Синхронизировать с аккаунтом модели',
|
||||
modelAccountAddressHelper:
|
||||
@@ -1133,6 +1134,7 @@ const message = {
|
||||
cachedToken: 'Кэшированные Token',
|
||||
cacheHitRate: 'Попадания в кэш',
|
||||
activeUsers: 'Активные пользователи',
|
||||
activeStreamingRequests: 'Активные потоковые запросы',
|
||||
activeModels: 'Активные модели',
|
||||
failedRequests: 'Неуспешные запросы',
|
||||
averageTokenPerRequest: 'Среднее Token/запрос',
|
||||
@@ -1437,7 +1439,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'Мониторинг GPU',
|
||||
gpuHelper: 'Система не обнаружила команды NVIDIA-SMI или XPU-SMI. Проверьте и повторите попытку!',
|
||||
gpuHelper: 'Поддерживаемая команда управления GPU/XPU/NPU не обнаружена. Проверьте и повторите попытку!',
|
||||
process: 'Информация о Процессе',
|
||||
type: 'Тип',
|
||||
typeG: 'Графика',
|
||||
@@ -4181,10 +4183,9 @@ const message = {
|
||||
importBackendHelper:
|
||||
'Импортируемые правила преобразуются для текущего бэкенда {0}. Исходные правила не изменяются.',
|
||||
clearAllRulesHelper: 'Удалить все управляемые правила ({0}) из текущего бэкенда? Это действие нельзя отменить.',
|
||||
switchBackendHelper:
|
||||
'Эта операция меняет только бэкенд брандмауэра, которым в дальнейшем будет управлять 1Panel. Исходный бэкенд не будет перенесён, остановлен или очищен. Сначала экспортируйте текущие правила. После переключения инициализируйте целевой бэкенд и вручную импортируйте или проверьте правила. Внешние и системные правила не экспортируются. Продолжить переключение на {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'Теперь 1Panel управляет {0}. Исходный бэкенд {1} не был остановлен или очищен и всё ещё может работать. Инициализируйте целевой бэкенд, импортируйте или проверьте правила и обрабатывайте исходный бэкенд вручную только после проверки доступа.',
|
||||
backendSwitchNotice:
|
||||
'Переключение меняет только используемый бэкенд брандмауэра; существующие правила не переносятся и не удаляются. После переключения синхронизируйте правила с целевым брандмауэром и убедитесь, что они действуют, прежде чем удалять правила исходного брандмауэра.',
|
||||
switchBackendHelper: 'Переключиться на {0}?',
|
||||
uninstalledStatus: 'Не установлено',
|
||||
initializedStatus: 'Инициализировано',
|
||||
partiallyInitialized: 'Частично инициализировано',
|
||||
@@ -4193,6 +4194,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'Правила INPUT хоста не защищают этот опубликованный Docker-порт напрямую. Нажмите, чтобы открыть защиту портов контейнеров.',
|
||||
notInitialized: 'Не инициализировано',
|
||||
familyUnsupported: 'Система не поддерживает {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'После отвязки все порты контейнеров временно вернутся к стандартному режиму доступа Docker. Существующие настройки защиты сохранятся. Продолжить?',
|
||||
deleteDockerGuardPolicyConfirm: 'Эта конечная точка вернется к стандартному режиму доступа Docker. Продолжить?',
|
||||
@@ -4244,7 +4246,7 @@ const message = {
|
||||
exportHelper: 'Собираюсь экспортировать {0} правил брандмауэра. Продолжить?',
|
||||
importSuccess: 'Успешно импортировано {0} правил',
|
||||
importPartialSuccess: 'Импорт завершён: {0} успешно, {1} с ошибкой',
|
||||
basicStatus: 'Текущая цепочка {0} не привязана, сначала привяжите!',
|
||||
basicStatus: 'Текущий межсетевой экран не привязан. Сначала привяжите его.',
|
||||
baseIptables: 'Сервис iptables',
|
||||
forwardIptables: 'Сервис Переадресации Порта iptables',
|
||||
initMsg: 'Собираюсь инициализировать {0}, продолжить?',
|
||||
@@ -4266,6 +4268,8 @@ const message = {
|
||||
reject: 'Отклонить',
|
||||
allPorts: 'Все Порта',
|
||||
allProtocolHelper: 'Все протоколы и порты',
|
||||
sourceAddressPlaceholder: 'например: 172.16.10.11, 172.16.0.0/24, 2001:db8::1 или 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'например: 80, 80,443 или 8080-8089',
|
||||
deleteRuleConfirm: 'Удалит {0} правил. Продолжить?',
|
||||
deleteUsedRuleConfirm:
|
||||
'Этот порт используется: {0}. Удаление разрешающего правила может сделать сервис недоступным. Продолжить?',
|
||||
@@ -6946,9 +6950,15 @@ const message = {
|
||||
partial_file: 'Незавершенный файл',
|
||||
},
|
||||
diskSize: 'Размер диска',
|
||||
isoHelper: 'ISO используется для первого запуска виртуальной машины и установки операционной системы.',
|
||||
osType: 'Тип операционной системы',
|
||||
osOther: 'Другая',
|
||||
diskBus: 'Шина диска',
|
||||
diskPath: 'Путь к диску',
|
||||
isoPath: 'Путь к ISO',
|
||||
storagePool: 'Пул хранения',
|
||||
networkHelper: 'Сеть обеспечивает сетевое подключение виртуальной машины.',
|
||||
storagePoolHelper: 'Пул хранения хранит диски виртуальных машин и ISO-файлы.',
|
||||
network: 'Сеть',
|
||||
bridgeName: 'Имя моста',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -956,6 +956,7 @@ const message = {
|
||||
from_remote: 'Bu model 1Panel aracılığıyla indirilmedi, ilgili çekme logları yok.',
|
||||
no_logs: 'Bu modelin çekme logları silindi ve görüntülenemiyor.',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 sunucuları için lütfen -cu130 sürümünü seçin.',
|
||||
ascendVisibleDevices: 'Görünür Ascend cihazları',
|
||||
vllmCommandPortHelper:
|
||||
'Başlatma komutu {0} numaralı bağlantı noktasını kullanmalıdır; aksi halde hizmete erişilemez.',
|
||||
syncModelAccount: 'Model hesabına senkronize et',
|
||||
@@ -1142,6 +1143,7 @@ const message = {
|
||||
cachedToken: 'Önbellek Token',
|
||||
cacheHitRate: 'Önbellek İsabet Oranı',
|
||||
activeUsers: 'Aktif kullanıcılar',
|
||||
activeStreamingRequests: 'Etkin akış istekleri',
|
||||
activeModels: 'Aktif modeller',
|
||||
failedRequests: 'Başarısız istekler',
|
||||
averageTokenPerRequest: 'Ortalama Token/istek',
|
||||
@@ -1442,7 +1444,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU İzleme',
|
||||
gpuHelper: 'Sistem NVIDIA-SMI veya XPU-SMI komutlarını algılamadı. Lütfen kontrol edip tekrar deneyin!',
|
||||
gpuHelper: 'Desteklenen bir GPU/XPU/NPU yönetim komutu algılanmadı. Lütfen kontrol edip tekrar deneyin!',
|
||||
process: 'İşlem Bilgisi',
|
||||
type: 'Tür',
|
||||
typeG: 'Grafik',
|
||||
@@ -4192,10 +4194,9 @@ const message = {
|
||||
importBackendHelper:
|
||||
'İçe aktarılan kurallar geçerli {0} arka ucu için dönüştürülür. Kaynak kurallar değiştirilmez.',
|
||||
clearAllRulesHelper: 'Geçerli arka uçtaki yönetilebilir {0} kural silinsin mi? Bu işlem geri alınamaz.',
|
||||
switchBackendHelper:
|
||||
'Bu işlem yalnızca 1Panel tarafından bundan sonra yönetilecek güvenlik duvarı arka ucunu değiştirir. Eski arka uç taşınmaz, durdurulmaz veya temizlenmez. Önce mevcut kuralları dışa aktarın. Geçişten sonra hedef arka ucu başlatın ve kuralları elle içe aktarın veya doğrulayın. Harici ve sisteme özgü kurallar dışa aktarılmaz. {0} arka ucuna geçilsin mi?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel artık {0} arka ucunu yönetiyor. Eski {1} arka ucu durdurulmadı veya temizlenmedi ve hâlâ çalışıyor olabilir. Hedef arka ucu başlatın, kuralları içe aktarın veya doğrulayın ve erişimin normal olduğunu doğruladıktan sonra eski arka ucu elle yönetin.',
|
||||
backendSwitchNotice:
|
||||
'Geçiş yalnızca kullanılmakta olan güvenlik duvarı arka ucunu değiştirir; mevcut kurallar taşınmaz veya temizlenmez. Geçişten sonra kuralları hedef güvenlik duvarıyla eşitleyin ve eski güvenlik duvarı kurallarını temizlemeden önce etkin olduklarını doğrulayın.',
|
||||
switchBackendHelper: '{0} arka ucuna geçilsin mi?',
|
||||
uninstalledStatus: 'Yüklü değil',
|
||||
initializedStatus: 'Başlatıldı',
|
||||
partiallyInitialized: 'Kısmen başlatıldı',
|
||||
@@ -4204,6 +4205,7 @@ const message = {
|
||||
dockerInputNotProtected:
|
||||
'Ana makine INPUT kuralları bu Docker yayımlanmış portunu doğrudan korumaz. Konteyner portu korumasını açmak için tıklayın.',
|
||||
notInitialized: 'Başlatılmadı',
|
||||
familyUnsupported: 'Sistem {0} desteğini sunmuyor',
|
||||
dockerGuardUnbindConfirm:
|
||||
'Bağ kaldırıldıktan sonra tüm konteyner portları geçici olarak Docker varsayılan erişim davranışına döner. Mevcut koruma ayarları korunur. Devam edilsin mi?',
|
||||
deleteDockerGuardPolicyConfirm: 'Bu uç nokta Docker varsayılan erişim davranışına döner. Devam edilsin mi?',
|
||||
@@ -4252,7 +4254,7 @@ const message = {
|
||||
exportHelper: '{0} güvenlik duvarı kuralını dışa aktarmak üzere. Devam etmek istiyor musunuz?',
|
||||
importSuccess: '{0} kural başarıyla içe aktarıldı',
|
||||
importPartialSuccess: 'İçe aktarma tamamlandı: {0} başarılı, {1} başarısız',
|
||||
basicStatus: 'Mevcut zincir {0} bağlı değil, lütfen önce bağlayın!',
|
||||
basicStatus: 'Mevcut güvenlik duvarı bağlı değil. Önce bağlayın.',
|
||||
baseIptables: 'iptables Servisi',
|
||||
forwardIptables: 'iptables Port Yönlendirme Servisi',
|
||||
initMsg: '{0} başlatılmak üzere, devam etmek istiyor musunuz?',
|
||||
@@ -4274,6 +4276,8 @@ const message = {
|
||||
reject: 'Reddet',
|
||||
allPorts: 'Tüm Portlar',
|
||||
allProtocolHelper: 'Tüm protokoller ve portlar',
|
||||
sourceAddressPlaceholder: 'örn. 172.16.10.11, 172.16.0.0/24, 2001:db8::1 veya 2001:db8::/64',
|
||||
destinationPortPlaceholder: 'örn. 80, 80,443 veya 8080-8089',
|
||||
deleteRuleConfirm: '{0} kural silinecek. Devam etmek istiyor musunuz?',
|
||||
deleteUsedRuleConfirm:
|
||||
'Bu port {0} tarafından kullanılıyor. İzin kuralını silmek hizmeti erişilemez hale getirebilir. Devam edilsin mi?',
|
||||
@@ -6935,9 +6939,15 @@ const message = {
|
||||
partial_file: 'Tamamlanmamış dosya',
|
||||
},
|
||||
diskSize: 'Disk Boyutu',
|
||||
isoHelper: 'ISO, sanal makineyi ilk kez başlatmak ve bir işletim sistemi kurmak için kullanılır.',
|
||||
osType: 'İşletim sistemi türü',
|
||||
osOther: 'Diğer',
|
||||
diskBus: 'Disk veri yolu',
|
||||
diskPath: 'Disk Yolu',
|
||||
isoPath: 'ISO Yolu',
|
||||
storagePool: 'Depolama Havuzu',
|
||||
networkHelper: 'Ağ, sanal makineye ağ bağlantısı sağlar.',
|
||||
storagePoolHelper: 'Depolama havuzu, sanal makine disklerini ve ISO dosyalarını saklar.',
|
||||
network: 'Ağ',
|
||||
bridgeName: 'Bridge Adı',
|
||||
natNetworkHelper:
|
||||
|
||||
@@ -898,6 +898,7 @@ const message = {
|
||||
from_remote: '該模型並非透過 1Panel 下載,無相關拉取日誌。',
|
||||
no_logs: '該模型的拉取日誌已被刪除,無法檢視相關日誌。',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 伺服器請選擇 -cu130 版本',
|
||||
ascendVisibleDevices: 'Ascend 可見裝置',
|
||||
vllmCommandPortHelper: '啟動命令必須使用 {0} 連接埠,否則服務將無法存取。',
|
||||
syncModelAccount: '同步到模型帳號',
|
||||
modelAccountAddressHelper:
|
||||
@@ -1077,6 +1078,7 @@ const message = {
|
||||
cachedToken: '快取 Token',
|
||||
cacheHitRate: '快取命中率',
|
||||
activeUsers: '活躍使用者',
|
||||
activeStreamingRequests: '活躍串流請求',
|
||||
activeModels: '活躍模型',
|
||||
failedRequests: '失敗請求',
|
||||
averageTokenPerRequest: '平均 Token/請求',
|
||||
@@ -1363,7 +1365,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 監控',
|
||||
gpuHelper: '目前系統未偵測到 NVIDIA-SMI 或 XPU-SMI 指令,請檢查後重試',
|
||||
gpuHelper: '目前系統未偵測到支援的 GPU/XPU/NPU 管理指令,請檢查後重試',
|
||||
process: '行程資訊',
|
||||
type: '類型',
|
||||
typeG: '圖形',
|
||||
@@ -3853,16 +3855,16 @@ const message = {
|
||||
clearAllRules: '清除全部規則',
|
||||
importBackendHelper: '匯入規則會轉換並寫入目前後端 {0},來源後端規則不會被修改。',
|
||||
clearAllRulesHelper: '即將刪除目前後端的 {0} 條可管理規則,此操作無法復原,是否繼續?',
|
||||
switchBackendHelper:
|
||||
'此操作僅切換 1Panel 後續管理的防火牆後端,不會移轉、停用或清理原後端。建議先匯出目前規則;切換後請初始化目標後端並手動匯入或核對規則。外部規則與系統原生規則不會被匯出。是否繼續切換為 {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel 已切換為管理 {0}。原後端 {1} 未被停用或清理,仍可能運行並生效。請初始化目標後端,匯入或核對規則,確認存取正常後再手動處理原後端。',
|
||||
backendSwitchNotice:
|
||||
'切換僅變更目前使用的防火牆後端,不會移轉或清理現有規則。切換後請先在目標防火牆同步規則並確認生效,再清理原防火牆規則。',
|
||||
switchBackendHelper: '確認切換為 {0}?',
|
||||
uninstalledStatus: '未安裝',
|
||||
initializedStatus: '已初始化',
|
||||
partiallyInitialized: '部分初始化',
|
||||
dockerGuardHelper: '為 Docker 容器發佈到主機的連接埠設定存取限制;未設定防護的連接埠維持 Docker 預設存取方式。',
|
||||
dockerInputNotProtected: '主機 INPUT 規則無法直接保護此 Docker 發佈連接埠,點擊前往容器連接埠防護。',
|
||||
notInitialized: '未初始化',
|
||||
familyUnsupported: '系統不支援 {0}',
|
||||
dockerGuardUnbindConfirm:
|
||||
'解除綁定後,所有容器連接埠將暫時恢復 Docker 預設存取方式,現有防護設定會保留。是否繼續?',
|
||||
deleteDockerGuardPolicyConfirm: '刪除後,該發佈端點將恢復 Docker 預設存取行為。是否繼續?',
|
||||
@@ -3908,7 +3910,7 @@ const message = {
|
||||
exportHelper: '即將匯出 {0} 條防火牆規則,是否繼續?',
|
||||
importSuccess: '成功匯入 {0} 條規則',
|
||||
importPartialSuccess: '匯入完成:成功 {0} 條,失敗 {1} 條',
|
||||
basicStatus: '目前未綁定鏈 {0} ,請先綁定',
|
||||
basicStatus: '目前防火牆尚未綁定,請先綁定',
|
||||
baseIptables: 'iptables 服務',
|
||||
forwardIptables: 'iptables 埠轉發服務',
|
||||
initMsg: '即將初始化 {0}, 是否繼續?',
|
||||
@@ -3927,6 +3929,8 @@ const message = {
|
||||
reject: '拒絕',
|
||||
allPorts: '所有埠',
|
||||
allProtocolHelper: '所有協定和連接埠',
|
||||
sourceAddressPlaceholder: '例如:172.16.10.11、172.16.0.0/24、2001:db8::1 或 2001:db8::/64',
|
||||
destinationPortPlaceholder: '例如:80、80,443 或 8080-8089',
|
||||
deleteRuleConfirm: '將刪除 {0} 條規則,是否繼續?',
|
||||
deleteUsedRuleConfirm: '該連接埠正被 {0} 使用。刪除放行規則可能導致服務無法存取,是否繼續?',
|
||||
editRuleConfirm: '將修改以下欄位:{0}。提交後會立即套用並回讀驗證,是否繼續?',
|
||||
@@ -6360,9 +6364,15 @@ const message = {
|
||||
partial_file: '未完成檔案',
|
||||
},
|
||||
diskSize: '磁碟容量',
|
||||
isoHelper: '鏡像用於虛擬機首次啟動並安裝作業系統。',
|
||||
osType: '作業系統類型',
|
||||
osOther: '其他',
|
||||
diskBus: '磁碟匯流排',
|
||||
diskPath: '磁碟路徑',
|
||||
isoPath: '鏡像路徑',
|
||||
storagePool: '儲存池',
|
||||
networkHelper: '網路用於為虛擬機提供網路連線。',
|
||||
storagePoolHelper: '儲存池用於儲存虛擬機磁碟與鏡像檔案。',
|
||||
network: '網路',
|
||||
bridgeName: '橋接名稱',
|
||||
natNetworkHelper: '虛擬機可透過主機存取網路,外部裝置通常無法直接存取虛擬機。',
|
||||
|
||||
@@ -911,6 +911,7 @@ const message = {
|
||||
from_remote: '该模型并非通过 1Panel 下载,无相关拉取日志。',
|
||||
no_logs: '该模型的拉取日志已被删除,无法查看相关日志。',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 服务器请选择 -cu130 版本',
|
||||
ascendVisibleDevices: 'Ascend 可见设备',
|
||||
vllmCommandPortHelper: '启动命令必须使用 {0} 端口,否则服务将无法访问。',
|
||||
syncModelAccount: '同步到模型账号',
|
||||
modelAccountAddressHelper:
|
||||
@@ -1091,6 +1092,7 @@ const message = {
|
||||
cachedToken: '缓存 Token',
|
||||
cacheHitRate: '缓存命中率',
|
||||
activeUsers: '活跃用户',
|
||||
activeStreamingRequests: '活跃流式请求',
|
||||
activeModels: '活跃模型',
|
||||
failedRequests: '失败请求',
|
||||
averageTokenPerRequest: '平均 Token/请求',
|
||||
@@ -1376,7 +1378,7 @@ const message = {
|
||||
},
|
||||
gpu: {
|
||||
gpu: 'GPU 监控',
|
||||
gpuHelper: '当前系统未检测到 NVIDIA-SMI 或 XPU-SMI 指令,请检查后重试!',
|
||||
gpuHelper: '当前系统未检测到支持的 GPU/XPU/NPU 管理指令,请检查后重试!',
|
||||
process: '进程信息',
|
||||
processCount: '进程数',
|
||||
type: '类型',
|
||||
@@ -3899,16 +3901,16 @@ const message = {
|
||||
clearAllRules: '清除全部规则',
|
||||
importBackendHelper: '导入规则会转换并写入当前后端 {0},源后端规则不会被修改。',
|
||||
clearAllRulesHelper: '即将删除当前后端的 {0} 条可管理规则,此操作不可恢复,是否继续?',
|
||||
switchBackendHelper:
|
||||
'本操作仅切换 1Panel 后续管理的防火墙后端,不会迁移、停用或清理原后端。建议先导出当前规则;切换后请初始化目标后端并手动导入或核对规则。外部规则和系统原生规则不会被导出。是否继续切换为 {0}?',
|
||||
switchBackendSuccessHelper:
|
||||
'1Panel 已切换为管理 {0}。原后端 {1} 未被停用或清理,仍可能运行并生效。请初始化目标后端,导入或核对规则,确认访问正常后再手动处理原后端。',
|
||||
backendSwitchNotice:
|
||||
'切换仅变更当前使用的防火墙后端,不会迁移或清理现有规则。切换后请先在目标防火墙同步规则并确认生效,再清理原防火墙规则。',
|
||||
switchBackendHelper: '确认切换为 {0}?',
|
||||
uninstalledStatus: '未安装',
|
||||
initializedStatus: '已初始化',
|
||||
partiallyInitialized: '部分初始化',
|
||||
dockerGuardHelper: '为 Docker 容器发布到宿主机的端口配置访问限制;未设置防护的端口保持 Docker 默认访问方式。',
|
||||
dockerInputNotProtected: '宿主机 INPUT 规则不能直接保护此 Docker 发布端口,点击前往容器端口防护。',
|
||||
notInitialized: '未初始化',
|
||||
familyUnsupported: '系统不支持 {0}',
|
||||
dockerGuardUnbindConfirm: '解绑后,所有容器端口将暂时恢复 Docker 默认访问方式,现有防护设置会保留。是否继续?',
|
||||
deleteDockerGuardPolicyConfirm: '删除后该发布端点将恢复 Docker 默认访问行为。是否继续?',
|
||||
clearDockerGuardPoliciesConfirm: '清空后,所选发布端点将恢复 Docker 默认访问行为。是否继续?',
|
||||
@@ -3954,7 +3956,7 @@ const message = {
|
||||
importSuccess: '成功导入 {0} 条规则',
|
||||
importPartialSuccess: '导入完成:成功 {0} 条,失败 {1} 条',
|
||||
|
||||
basicStatus: '当前未绑定链 {0} ,请先绑定!',
|
||||
basicStatus: '当前防火墙未绑定,请先绑定!',
|
||||
baseIptables: 'iptables 服务',
|
||||
forwardIptables: 'iptables 端口转发服务',
|
||||
initMsg: '即将初始化 {0}, 是否继续?',
|
||||
@@ -3973,6 +3975,8 @@ const message = {
|
||||
reject: '拒绝',
|
||||
allPorts: '所有端口',
|
||||
allProtocolHelper: '所有协议和端口',
|
||||
sourceAddressPlaceholder: '例如:172.16.10.11、172.16.0.0/24、2001:db8::1 或 2001:db8::/64',
|
||||
destinationPortPlaceholder: '例如:80、80,443 或 8080-8089',
|
||||
deleteRuleConfirm: '将删除 {0} 条规则,是否继续?',
|
||||
deleteUsedRuleConfirm: '该端口正在被 {0} 使用。删除放行规则可能导致服务无法访问,是否继续?',
|
||||
editRuleConfirm: '将修改以下字段:{0}。提交后会立即应用并回读验证,是否继续?',
|
||||
@@ -6421,9 +6425,15 @@ const message = {
|
||||
partial_file: '未完成文件',
|
||||
},
|
||||
diskSize: '磁盘容量',
|
||||
isoHelper: '镜像用于虚拟机首次启动并安装操作系统。',
|
||||
osType: '操作系统类型',
|
||||
osOther: '其他',
|
||||
diskBus: '磁盘总线',
|
||||
diskPath: '磁盘路径',
|
||||
isoPath: '镜像源文件',
|
||||
storagePool: '存储池',
|
||||
networkHelper: '网络用于为虚拟机提供网络连接。',
|
||||
storagePoolHelper: '存储池用于保存虚拟机磁盘和镜像文件。',
|
||||
network: '网络',
|
||||
bridgeName: '桥接名称',
|
||||
natNetworkHelper: '虚拟机可通过宿主机访问网络,外部设备通常无法直接访问虚拟机。',
|
||||
|
||||
@@ -10,7 +10,7 @@ const errorRouter = {
|
||||
hidden: true,
|
||||
component: () => import('@/components/error-message/404.vue'),
|
||||
meta: {
|
||||
title: '404页面',
|
||||
title: 'commons.msg.notFound',
|
||||
key: '404',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
size="small"
|
||||
@click.stop="remove(group.endpoints, false)"
|
||||
>
|
||||
{{ $t('commons.button.clean') }}
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -120,6 +120,7 @@ const selectedFilters = ref<string[]>([]);
|
||||
const data = reactive<Firewall.DockerGuardList>({
|
||||
base: {
|
||||
name: 'iptables-docker',
|
||||
version: '-',
|
||||
initialized: false,
|
||||
bound: false,
|
||||
ipv4: { state: 'disabled', initialized: false, bound: false, effective: false },
|
||||
|
||||
@@ -4,20 +4,12 @@
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4 ml-3">
|
||||
<el-tag effect="dark" type="success">{{ base.name }}</el-tag>
|
||||
<Status class="mt-0.5" :status="base.bound ? 'enable' : 'disable'" />
|
||||
<el-tag v-if="!base.initialized" type="info">
|
||||
{{ $t('firewall.notInitialized') }}
|
||||
</el-tag>
|
||||
<el-tooltip :content="familyStatusDescription('IPv4', base.ipv4)" placement="bottom">
|
||||
<el-tag :type="familyStatusType(base.ipv4)">IPv4: {{ familyStatusLabel(base.ipv4) }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tooltip :content="familyStatusDescription('IPv6', base.ipv6)" placement="bottom">
|
||||
<el-tag :type="familyStatusType(base.ipv6)">IPv6: {{ familyStatusLabel(base.ipv6) }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag>{{ $t('app.version') }}: {{ base.version || '-' }}</el-tag>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<div class="mt-0.5 flex items-center">
|
||||
<el-divider v-if="anyFamilyBound" direction="vertical" />
|
||||
<el-button
|
||||
v-if="!base.initialized"
|
||||
v-if="!anyFamilyInitialized"
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
@@ -27,7 +19,7 @@
|
||||
{{ $t('commons.button.init') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="!base.bound"
|
||||
v-else-if="!anyFamilyBound"
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
@@ -39,6 +31,49 @@
|
||||
<el-button v-else v-permission v-node-admin type="primary" link @click="emit('operate', 'unbind')">
|
||||
{{ $t('commons.button.unbind') }}
|
||||
</el-button>
|
||||
<el-popover
|
||||
v-if="familyIssues.length"
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
:width="380"
|
||||
:show-after="120"
|
||||
:hide-after="120"
|
||||
popper-class="docker-firewall-family-issue-popper"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="docker-firewall-family-warning-trigger"
|
||||
type="warning"
|
||||
link
|
||||
:aria-label="$t('commons.status.exceptional')"
|
||||
>
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="docker-firewall-family-issue-list">
|
||||
<div
|
||||
v-for="issue in familyIssues"
|
||||
:key="issue.family"
|
||||
class="docker-firewall-family-issue-item"
|
||||
>
|
||||
<span class="docker-firewall-family-name">{{ issue.family }}</span>
|
||||
<span class="docker-firewall-family-issue-text">
|
||||
{{ familyStatusDescription(issue.family, issue.status) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="retryableFamilyIssues.length" class="docker-firewall-family-issue-footer">
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
size="small"
|
||||
type="primary"
|
||||
@click.stop="emit('operate', familyRetryOperation)"
|
||||
>
|
||||
{{ $t('commons.button.retry') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -48,21 +83,94 @@
|
||||
<script lang="ts" setup>
|
||||
import { Firewall } from '@/api/interface/firewall';
|
||||
import i18n from '@/lang';
|
||||
import { WarningFilled } from '@element-plus/icons-vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineProps<{ base: Firewall.DockerGuardBase }>();
|
||||
const props = defineProps<{ base: Firewall.DockerGuardBase }>();
|
||||
const emit = defineEmits<{ operate: [operation: 'initialize' | 'bind' | 'unbind'] }>();
|
||||
|
||||
const familyStatusLabel = (status: Firewall.DockerGuardFamilyStatus) => {
|
||||
if (status.state === 'effective') return i18n.global.t('firewall.effective');
|
||||
if (status.state === 'disabled') return i18n.global.t('firewall.notEnabled');
|
||||
return i18n.global.t('firewall.notEffective');
|
||||
};
|
||||
const familyStatusType = (status: Firewall.DockerGuardFamilyStatus) => {
|
||||
if (status.state === 'effective') return 'success';
|
||||
return status.state === 'disabled' ? 'info' : 'warning';
|
||||
};
|
||||
const familyStatuses = computed(
|
||||
() =>
|
||||
[
|
||||
{ family: 'IPv4', status: props.base.ipv4 },
|
||||
{ family: 'IPv6', status: props.base.ipv6 },
|
||||
] as const,
|
||||
);
|
||||
const availableFamilies = computed(() =>
|
||||
familyStatuses.value.filter((item) => item.status.reason !== 'command_missing'),
|
||||
);
|
||||
const anyFamilyInitialized = computed(() => availableFamilies.value.some((item) => item.status.initialized));
|
||||
const anyFamilyBound = computed(() => availableFamilies.value.some((item) => item.status.bound));
|
||||
const familyIssues = computed(() => {
|
||||
if (!anyFamilyBound.value) return [];
|
||||
return familyStatuses.value.filter((item) => !item.status.effective);
|
||||
});
|
||||
const retryableFamilyIssues = computed(() =>
|
||||
familyIssues.value.filter(
|
||||
(item) => item.status.reason !== 'command_missing' && item.status.reason !== 'docker_chain_missing',
|
||||
),
|
||||
);
|
||||
const familyRetryOperation = computed<'initialize' | 'bind'>(() =>
|
||||
retryableFamilyIssues.value.some((item) => !item.status.initialized) ? 'initialize' : 'bind',
|
||||
);
|
||||
const familyStatusDescription = (family: string, status: Firewall.DockerGuardFamilyStatus) => {
|
||||
if (status.state === 'effective') return i18n.global.t('firewall.dockerGuardStatusEffective', [family]);
|
||||
return i18n.global.t(`firewall.dockerGuardStatusReason.${status.reason || 'inspect_failed'}`, [family]);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.docker-firewall-family-warning-trigger {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin-left: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--el-color-warning-light-9);
|
||||
font-size: 16px;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background: var(--el-color-warning-light-8);
|
||||
}
|
||||
}
|
||||
|
||||
.docker-firewall-family-issue-popper.el-popover {
|
||||
padding: 12px;
|
||||
border-color: var(--el-color-warning-light-7);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.docker-firewall-family-issue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.docker-firewall-family-issue-item {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.docker-firewall-family-issue-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.docker-firewall-family-name {
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.docker-firewall-family-issue-text {
|
||||
color: var(--el-color-warning-dark-2);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,17 +7,13 @@
|
||||
ref="fireStatusRef"
|
||||
@search="search"
|
||||
v-model:loading="loading"
|
||||
v-model:mask-show="maskShow"
|
||||
v-model:is-active="isActive"
|
||||
v-model:is-init="isInit"
|
||||
v-model:is-bind="isBind"
|
||||
v-model:name="fireName"
|
||||
current-tab="forward"
|
||||
/>
|
||||
<div v-if="fireName !== '-'">
|
||||
<el-card v-if="!isActive && maskShow" class="mask-prompt">
|
||||
<span>{{ $t('firewall.firewallNotStart') }}</span>
|
||||
</el-card>
|
||||
|
||||
<LayoutContent :title="$t('firewall.forwardRule', 2)" :class="{ mask: !isActive }">
|
||||
<LayoutContent :title="$t('firewall.forwardRule', 2)" :class="{ mask: !isInit || !isBind }">
|
||||
<template #leftToolBar>
|
||||
<el-button v-permission v-node-admin type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.create') }}
|
||||
@@ -144,8 +140,8 @@ const selects = ref<any>([]);
|
||||
const searchName = ref();
|
||||
const searchStrategy = ref('');
|
||||
|
||||
const maskShow = ref(true);
|
||||
const isActive = ref(false);
|
||||
const isInit = ref(false);
|
||||
const isBind = ref(false);
|
||||
const fireName = ref();
|
||||
const fireStatusRef = ref();
|
||||
|
||||
@@ -173,7 +169,7 @@ const paginationConfig = reactive({
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
if (!isActive.value || fireName.value === '-') {
|
||||
if (!isInit.value || !isBind.value || fireName.value === '-') {
|
||||
loading.value = false;
|
||||
data.value = [];
|
||||
paginationConfig.total = 0;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<FireStatus
|
||||
ref="fireStatusRef"
|
||||
v-model:loading="loading"
|
||||
v-model:mask-show="maskShow"
|
||||
v-model:is-active="isActive"
|
||||
v-model:is-init="isInit"
|
||||
v-model:is-bind="isBind"
|
||||
v-model:name="provider"
|
||||
v-model:version="firewallVersion"
|
||||
@@ -16,20 +16,12 @@
|
||||
/>
|
||||
|
||||
<div v-if="provider !== '-'">
|
||||
<el-card v-if="!isActive && maskShow" class="mask-prompt">
|
||||
<span>{{ $t('firewall.firewallNotStart') }}</span>
|
||||
</el-card>
|
||||
<el-card
|
||||
v-if="(provider === 'iptables' || provider === 'nftables') && !isBind && maskShow"
|
||||
class="mask-prompt"
|
||||
>
|
||||
<span>{{ $t('firewall.basicStatus', ['1PANEL_BASIC']) }}</span>
|
||||
<el-card v-if="isDirectBackend && (!isInit || !isBind)" class="mask-prompt">
|
||||
<span v-if="!isInit">{{ $t('firewall.initHelper', [provider]) }}</span>
|
||||
<span v-else>{{ $t('firewall.basicStatus') }}</span>
|
||||
</el-card>
|
||||
|
||||
<LayoutContent
|
||||
:title="$t('menu.firewall')"
|
||||
:class="{ mask: !isActive || ((provider === 'iptables' || provider === 'nftables') && !isBind) }"
|
||||
>
|
||||
<LayoutContent :title="$t('menu.firewall')" :class="{ mask: !isFirewallReady }">
|
||||
<template #prompt>
|
||||
<el-alert
|
||||
v-for="notice in notices"
|
||||
@@ -419,10 +411,12 @@ const ruleOperateRef = ref<InstanceType<typeof RuleOperate>>();
|
||||
const ruleImportRef = ref<InstanceType<typeof RuleImport>>();
|
||||
const processDetailRef = ref<InstanceType<typeof ProcessDetail>>();
|
||||
const loading = ref(false);
|
||||
const maskShow = ref(true);
|
||||
const isActive = ref(false);
|
||||
const isInit = ref(false);
|
||||
const isBind = ref(false);
|
||||
const provider = ref('');
|
||||
const isDirectBackend = computed(() => provider.value === 'iptables' || provider.value === 'nftables');
|
||||
const isFirewallReady = computed(() => (isDirectBackend.value ? isInit.value && isBind.value : isActive.value));
|
||||
const firewallVersion = ref('');
|
||||
const selectedRuleFilters = ref<RuleFilter[]>([]);
|
||||
const iptablesChains = ['1PANEL_BASIC_BEFORE', '1PANEL_BASIC', '1PANEL_BASIC_AFTER'] as const;
|
||||
@@ -480,7 +474,7 @@ const providerScopes = (): Firewall.Scope[] => {
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
if (!isActive.value) {
|
||||
if (!isFirewallReady.value) {
|
||||
loading.value = false;
|
||||
inventoryItems.value = [];
|
||||
scopeNotices.value = [];
|
||||
@@ -843,6 +837,7 @@ watch(
|
||||
const notices = computed<DisplayNotice[]>(() => {
|
||||
const unique = new Map<string, DisplayNotice>();
|
||||
scopeNotices.value.forEach((notice) => {
|
||||
if (notice.code === 'managed_scope_missing') return;
|
||||
const key = `${notice.code}:${(notice.values || []).join(',')}`;
|
||||
if (!unique.has(key)) {
|
||||
unique.set(key, { key, text: scopeNoticeText(notice) });
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
v-model.trim="item.address"
|
||||
class="source-address-select"
|
||||
clearable
|
||||
placeholder="172.16.10.11、172.16.0.0/24、2001:db8::1 或 2001:db8::/64"
|
||||
:placeholder="$t('firewall.sourceAddressPlaceholder')"
|
||||
@keyup.enter.prevent="addSourceAddressOnEnter(index)"
|
||||
>
|
||||
<template #append>
|
||||
@@ -66,7 +66,7 @@
|
||||
class="destination-port-input"
|
||||
clearable
|
||||
:disabled="!portProtocol"
|
||||
placeholder="80、80,443 或 8080-8089"
|
||||
:placeholder="$t('firewall.destinationPortPlaceholder')"
|
||||
@keyup.enter.prevent="addDestinationPortOnEnter(index)"
|
||||
>
|
||||
<template #append>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<FireRouter />
|
||||
<LayoutContent :title="$t('commons.button.set')" :divider="true">
|
||||
<LayoutContent :title="$t('commons.button.set')">
|
||||
<template #prompt>
|
||||
<el-alert type="warning" show-icon :closable="false" :title="$t('firewall.backendSwitchNotice')" />
|
||||
</template>
|
||||
<template #main>
|
||||
<el-form :label-position="isMobile ? 'top' : 'left'" label-width="150px">
|
||||
<el-row>
|
||||
@@ -28,27 +31,74 @@
|
||||
<el-tag v-if="!option.installed" size="small" type="info">
|
||||
{{ $t('firewall.uninstalledStatus') }}
|
||||
</el-tag>
|
||||
<template v-else>
|
||||
<el-tag v-if="option.active" size="small" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="family in backendFamilies"
|
||||
:key="family.key"
|
||||
size="small"
|
||||
:type="option[family.key].initialized ? 'success' : 'info'"
|
||||
effect="plain"
|
||||
>
|
||||
{{ family.label }}
|
||||
<el-popover
|
||||
v-else-if="option.message"
|
||||
placement="right"
|
||||
trigger="hover"
|
||||
:width="220"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag size="small" type="warning">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
{{ $t('commons.status.exceptional') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<div class="backend-issue-list">
|
||||
<div class="backend-issue-item">{{ option.message }}</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
<template v-else-if="isServiceBackend(option.name)">
|
||||
<el-tag size="small" :type="option.active ? 'success' : 'info'">
|
||||
{{
|
||||
$t(
|
||||
option[family.key].initialized
|
||||
? 'firewall.initializedStatus'
|
||||
: 'firewall.notInitialized',
|
||||
option.active
|
||||
? 'commons.status.running'
|
||||
: 'commons.status.stopped',
|
||||
)
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-popover
|
||||
v-if="showBackendFamilyDetails(option)"
|
||||
placement="right"
|
||||
trigger="hover"
|
||||
:width="360"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag
|
||||
size="small"
|
||||
:type="backendDisplayState(option).type"
|
||||
>
|
||||
<el-icon v-if="backendDisplayState(option).exceptional">
|
||||
<WarningFilled />
|
||||
</el-icon>
|
||||
{{ $t(backendDisplayState(option).label) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<div class="backend-issue-list">
|
||||
<div
|
||||
v-for="familyState in backendFamilyStates(option)"
|
||||
:key="familyState.family"
|
||||
class="backend-issue-item"
|
||||
>
|
||||
{{
|
||||
backendFamilyStatusLabel(
|
||||
familyState.family,
|
||||
familyState.status,
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-tag
|
||||
v-else
|
||||
size="small"
|
||||
:type="backendDisplayState(option).type"
|
||||
>
|
||||
{{ $t(backendDisplayState(option).label) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-option>
|
||||
@@ -101,6 +151,7 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { WarningFilled } from '@element-plus/icons-vue';
|
||||
|
||||
const { isMobile } = useGlobalStore();
|
||||
const loading = ref(false);
|
||||
@@ -126,6 +177,60 @@ const providerOrder: Record<Firewall.Provider, number> = {
|
||||
ufw: 3,
|
||||
};
|
||||
|
||||
const isServiceBackend = (provider: Firewall.Provider) => {
|
||||
return provider === 'firewalld' || provider === 'ufw';
|
||||
};
|
||||
|
||||
type BackendTagType = 'success' | 'info' | 'warning';
|
||||
interface BackendDisplayState {
|
||||
label: string;
|
||||
type: BackendTagType;
|
||||
exceptional: boolean;
|
||||
}
|
||||
|
||||
const backendFamilyStates = (option: Firewall.BackendOption) =>
|
||||
backendFamilies.map((family) => ({ family: family.label, status: option[family.key] }));
|
||||
|
||||
const backendDisplayState = (option: Firewall.BackendOption): BackendDisplayState => {
|
||||
if (option.message) {
|
||||
return { label: 'commons.status.exceptional', type: 'warning', exceptional: true };
|
||||
}
|
||||
const families = backendFamilyStates(option);
|
||||
if (families.every(({ status }) => !status.initialized && !status.bound)) {
|
||||
return { label: 'firewall.notInitialized', type: 'info', exceptional: false };
|
||||
}
|
||||
if (families.every(({ status }) => status.available && status.initialized && status.bound && !status.reason)) {
|
||||
return { label: 'commons.status.bound', type: 'success', exceptional: false };
|
||||
}
|
||||
if (families.every(({ status }) => status.available && status.initialized && !status.bound && !status.reason)) {
|
||||
return { label: 'commons.status.unbind', type: 'info', exceptional: false };
|
||||
}
|
||||
return { label: 'commons.status.exceptional', type: 'warning', exceptional: true };
|
||||
};
|
||||
|
||||
const showBackendFamilyDetails = (option: Firewall.BackendOption) =>
|
||||
backendDisplayState(option).label !== 'commons.status.bound' ||
|
||||
backendFamilyStates(option).some(({ status }) => !status.available || Boolean(status.reason));
|
||||
|
||||
const dockerGuardReasons = new Set([
|
||||
'docker_chain_missing',
|
||||
'guard_chain_missing',
|
||||
'jump_missing',
|
||||
'jump_not_first',
|
||||
'jump_duplicate',
|
||||
'inspect_failed',
|
||||
]);
|
||||
|
||||
const backendFamilyStatusLabel = (family: 'IPv4' | 'IPv6', status: Firewall.BackendFamilyStatus) => {
|
||||
if (!status.available || status.reason === 'command_missing') {
|
||||
return i18n.global.t('firewall.familyUnsupported', [family]);
|
||||
}
|
||||
if (status.reason && dockerGuardReasons.has(status.reason)) {
|
||||
return i18n.global.t(`firewall.dockerGuardStatusReason.${status.reason}`, [family]);
|
||||
}
|
||||
return `${family} ${i18n.global.t(status.initialized ? (status.bound ? 'commons.status.bound' : 'commons.status.unbind') : 'firewall.notInitialized')}`;
|
||||
};
|
||||
|
||||
const groups = computed(() => {
|
||||
if (!settings.value) return [];
|
||||
return [
|
||||
@@ -177,7 +282,6 @@ const load = async () => {
|
||||
|
||||
const changeBackend = async (subsystem: Firewall.BackendSubsystem, group: Firewall.BackendGroup) => {
|
||||
const backend = group.selected;
|
||||
const previousBackend = savedBackends.value[subsystem];
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
i18n.global.t('firewall.switchBackendHelper', [backend]),
|
||||
@@ -195,14 +299,7 @@ const changeBackend = async (subsystem: Firewall.BackendSubsystem, group: Firewa
|
||||
operation: 'select',
|
||||
});
|
||||
await load();
|
||||
await ElMessageBox.alert(
|
||||
i18n.global.t('firewall.switchBackendSuccessHelper', [backend, previousBackend]),
|
||||
i18n.global.t('commons.msg.operationSuccess'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
type: 'info',
|
||||
},
|
||||
);
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
} catch {
|
||||
if (savedBackends.value[subsystem] !== backend) {
|
||||
group.selected = savedBackends.value[subsystem];
|
||||
@@ -242,4 +339,16 @@ load();
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.backend-issue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.backend-issue-item {
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,11 +5,15 @@
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4 ml-3">
|
||||
<el-tag effect="dark" type="success">{{ baseInfo.name }}</el-tag>
|
||||
<Status class="mt-0.5" :status="baseInfo.isActive ? 'enable' : 'disable'" />
|
||||
<Status
|
||||
v-if="isServiceBackend"
|
||||
class="mt-0.5"
|
||||
:status="baseInfo.isActive ? 'enable' : 'disable'"
|
||||
/>
|
||||
<el-tag>{{ $t('app.version') }}: {{ baseInfo.version }}</el-tag>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<template v-if="backendName !== 'iptables' && backendName !== 'nftables'">
|
||||
<template v-if="isServiceBackend">
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
@@ -20,64 +24,103 @@
|
||||
>
|
||||
{{ $t('commons.button.stop') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
<el-tooltip
|
||||
v-if="!baseInfo.isActive"
|
||||
@click="onOperate('start')"
|
||||
link
|
||||
:content="$t('firewall.firewallNotStart')"
|
||||
placement="bottom"
|
||||
>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
<el-button v-permission v-node-admin type="primary" @click="onOperate('start')" link>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button v-permission v-node-admin type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('commons.button.restart') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-if="!baseInfo.isInit || (props.currentTab === 'forward' && !baseInfo.isBind)">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button v-permission v-node-admin type="primary" link @click="onInit">
|
||||
{{ $t('commons.button.init') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
(backendName === 'iptables' || backendName === 'nftables') &&
|
||||
baseInfo.isInit &&
|
||||
props.currentTab == 'base'
|
||||
"
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
v-if="baseInfo.isBind"
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
link
|
||||
@click="onUnBind"
|
||||
<template v-if="isDirectManaged">
|
||||
<el-divider
|
||||
v-if="isDirectBase || !anyFamilyBound || familyIssues.length"
|
||||
direction="vertical"
|
||||
/>
|
||||
<template v-if="isDirectBase">
|
||||
<el-button
|
||||
v-if="anyFamilyBound"
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
link
|
||||
@click="onUnBind"
|
||||
>
|
||||
{{ $t('commons.button.unbind') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="allAvailableFamiliesInitialized"
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
link
|
||||
@click="onBind"
|
||||
>
|
||||
{{ $t('commons.button.bind') }}
|
||||
</el-button>
|
||||
<el-tooltip v-else :content="initActionHelper" placement="bottom">
|
||||
<el-button v-permission v-node-admin type="primary" link @click="onInit">
|
||||
{{ $t('commons.button.init') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-tooltip
|
||||
v-else-if="isDirectForward && !anyFamilyBound"
|
||||
:content="initActionHelper"
|
||||
placement="bottom"
|
||||
>
|
||||
{{ $t('commons.button.unbind') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!baseInfo.isBind"
|
||||
v-permission
|
||||
v-node-admin
|
||||
type="primary"
|
||||
link
|
||||
@click="onBind"
|
||||
>
|
||||
{{ $t('commons.button.bind') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-if="showIPv6Recovery">
|
||||
<el-divider direction="vertical" />
|
||||
<el-tooltip :content="ipv6RecoveryHelper" placement="bottom">
|
||||
<el-button v-permission v-node-admin type="primary" link @click="onRecoverIPv6">
|
||||
{{ $t(baseInfo.ipv6.initialized ? 'commons.button.bind' : 'commons.button.init') }}
|
||||
IPv6
|
||||
<el-button v-permission v-node-admin type="primary" link @click="onInit">
|
||||
{{ $t('commons.button.init') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-popover
|
||||
v-if="familyIssues.length"
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
:width="300"
|
||||
:show-after="120"
|
||||
:hide-after="120"
|
||||
popper-class="firewall-family-issue-popper"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="firewall-family-warning-trigger"
|
||||
type="warning"
|
||||
link
|
||||
:aria-label="$t('commons.status.exceptional')"
|
||||
>
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="firewall-family-issue-list">
|
||||
<div
|
||||
v-for="issue in familyIssues"
|
||||
:key="issue.family"
|
||||
class="firewall-family-issue-item"
|
||||
>
|
||||
<span class="firewall-family-name">{{ issue.family }}</span>
|
||||
<span class="firewall-family-issue-text">{{ familyIssueLabel(issue) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="retryableFamilyIssues.length" class="firewall-family-issue-footer">
|
||||
<el-button
|
||||
v-permission
|
||||
v-node-admin
|
||||
:loading="familyRetrying"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click.stop="onRetryFamilyIssues"
|
||||
>
|
||||
{{ $t('commons.button.retry') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,19 +143,6 @@
|
||||
"
|
||||
/>
|
||||
|
||||
<LayoutContent :divider="true" v-if="baseInfo.isExist && baseInfo.isActive && !baseInfo.isInit">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ loadInitMsg() }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<DockerRestart
|
||||
ref="dockerRef"
|
||||
v-model:withDockerRestart="withDockerRestart"
|
||||
@@ -142,6 +172,7 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import { loadDockerStatus } from '@/api/modules/container';
|
||||
import { WarningFilled } from '@element-plus/icons-vue';
|
||||
|
||||
const props = defineProps({
|
||||
currentTab: String,
|
||||
@@ -157,25 +188,62 @@ const baseInfo = ref<Firewall.FirewallBase>({
|
||||
version: '',
|
||||
pingStatus: '',
|
||||
syncError: '',
|
||||
ipv4: { initialized: false, bound: false },
|
||||
ipv6: { initialized: false, bound: false },
|
||||
ipv4: { available: false, initialized: false, bound: false },
|
||||
ipv6: { available: false, initialized: false, bound: false },
|
||||
});
|
||||
const dockerRef = ref();
|
||||
const operation = ref('restart');
|
||||
const dockerStatus = ref();
|
||||
const withDockerRestart = ref(false);
|
||||
const familyRetrying = ref(false);
|
||||
const backendName = computed(() => baseInfo.value.backend || baseInfo.value.name);
|
||||
const showIPv6Recovery = computed(
|
||||
() =>
|
||||
props.currentTab === 'base' &&
|
||||
backendName.value === 'iptables' &&
|
||||
baseInfo.value.isInit &&
|
||||
(!baseInfo.value.ipv6.initialized || !baseInfo.value.ipv6.bound),
|
||||
const isServiceBackend = computed(() => backendName.value === 'firewalld' || backendName.value === 'ufw');
|
||||
const isDirectBase = computed(
|
||||
() => props.currentTab === 'base' && (backendName.value === 'iptables' || backendName.value === 'nftables'),
|
||||
);
|
||||
const ipv6RecoveryHelper = computed(
|
||||
() =>
|
||||
`IPv6: ${i18n.global.t(baseInfo.value.ipv6.initialized ? 'commons.status.unbind' : 'firewall.notInitialized')}`,
|
||||
const isDirectForward = computed(
|
||||
() => props.currentTab === 'forward' && (backendName.value === 'iptables' || backendName.value === 'nftables'),
|
||||
);
|
||||
const isDirectManaged = computed(() => isDirectBase.value || isDirectForward.value);
|
||||
const familyStatuses = computed(
|
||||
() =>
|
||||
[
|
||||
{ family: 'IPv4', status: baseInfo.value.ipv4 },
|
||||
{ family: 'IPv6', status: baseInfo.value.ipv6 },
|
||||
] as const,
|
||||
);
|
||||
const availableFamilies = computed(() => familyStatuses.value.filter((item) => item.status.available));
|
||||
const anyFamilyInitialized = computed(() => availableFamilies.value.some((item) => item.status.initialized));
|
||||
const allAvailableFamiliesInitialized = computed(
|
||||
() => availableFamilies.value.length > 0 && availableFamilies.value.every((item) => item.status.initialized),
|
||||
);
|
||||
const anyFamilyBound = computed(() => availableFamilies.value.some((item) => item.status.bound));
|
||||
interface FamilyIssue {
|
||||
family: 'IPv4' | 'IPv6';
|
||||
available: boolean;
|
||||
initialized: boolean;
|
||||
}
|
||||
const familyIssues = computed<FamilyIssue[]>(() => {
|
||||
if (!isDirectManaged.value || !anyFamilyBound.value) return [];
|
||||
return familyStatuses.value
|
||||
.filter((item) => !item.status.available || !item.status.initialized || !item.status.bound)
|
||||
.map((item) => ({
|
||||
family: item.family,
|
||||
available: item.status.available,
|
||||
initialized: item.status.initialized,
|
||||
}));
|
||||
});
|
||||
const retryableFamilyIssues = computed(() => familyIssues.value.filter((item) => item.available));
|
||||
const familyIssueLabel = (issue: FamilyIssue) => {
|
||||
if (!issue.available) return i18n.global.t('firewall.familyUnsupported', [issue.family]);
|
||||
return i18n.global.t(issue.initialized ? 'commons.status.unbind' : 'firewall.notInitialized');
|
||||
};
|
||||
const initActionHelper = computed(() => {
|
||||
if (props.currentTab === 'forward' && baseInfo.value.isInit && !baseInfo.value.isBind) {
|
||||
return `${baseInfo.value.name || backendName.value}: ${i18n.global.t('commons.status.unbind')}`;
|
||||
}
|
||||
return `${baseInfo.value.name || backendName.value}: ${i18n.global.t('firewall.notInitialized')}`;
|
||||
});
|
||||
|
||||
const acceptParams = (): void => {
|
||||
loadBaseInfo(true);
|
||||
@@ -185,8 +253,8 @@ const emit = defineEmits([
|
||||
'search',
|
||||
'update:is-active',
|
||||
'update:is-bind',
|
||||
'update:is-init',
|
||||
'update:loading',
|
||||
'update:maskShow',
|
||||
'update:name',
|
||||
'update:version',
|
||||
]);
|
||||
@@ -197,16 +265,13 @@ const loadBaseInfo = async (search: boolean) => {
|
||||
.then(async (res) => {
|
||||
baseInfo.value = {
|
||||
...res.data,
|
||||
ipv4: res.data.ipv4 || { initialized: res.data.isInit, bound: res.data.isBind },
|
||||
ipv6: res.data.ipv6 || { initialized: false, bound: false },
|
||||
ipv4: res.data.ipv4 || { available: true, initialized: res.data.isInit, bound: res.data.isBind },
|
||||
ipv6: res.data.ipv6 || { available: false, initialized: false, bound: false },
|
||||
};
|
||||
if (baseInfo.value.isInit) {
|
||||
emit('update:name', backendName.value);
|
||||
} else {
|
||||
emit('update:name', '-');
|
||||
}
|
||||
emit('update:name', backendName.value);
|
||||
emit('update:is-active', baseInfo.value.isActive);
|
||||
emit('update:is-bind', baseInfo.value.isBind);
|
||||
emit('update:is-init', isDirectManaged.value ? anyFamilyInitialized.value : baseInfo.value.isInit);
|
||||
emit('update:is-bind', isDirectManaged.value ? anyFamilyBound.value : baseInfo.value.isBind);
|
||||
emit('update:version', baseInfo.value.version);
|
||||
|
||||
if (search) {
|
||||
@@ -218,7 +283,7 @@ const loadBaseInfo = async (search: boolean) => {
|
||||
})
|
||||
.catch(() => {
|
||||
emit('update:loading', false);
|
||||
emit('update:maskShow', true);
|
||||
emit('update:is-init', false);
|
||||
emit('update:name', '-');
|
||||
emit('update:version', '');
|
||||
});
|
||||
@@ -229,15 +294,6 @@ const loadDocker = async () => {
|
||||
dockerStatus.value = res.data.isExist;
|
||||
};
|
||||
|
||||
const loadInitMsg = () => {
|
||||
switch (props.currentTab) {
|
||||
case 'base':
|
||||
return i18n.global.t('firewall.initHelper', [baseInfo.value.name || backendName.value]);
|
||||
case 'forward':
|
||||
return i18n.global.t('firewall.initHelper', [baseInfo.value.name || backendName.value]);
|
||||
}
|
||||
};
|
||||
|
||||
const onInit = async () => {
|
||||
let chainName = '';
|
||||
let msg = '';
|
||||
@@ -253,68 +309,69 @@ const onInit = async () => {
|
||||
default:
|
||||
return;
|
||||
}
|
||||
ElMessageBox.confirm(msg, i18n.global.t('commons.button.init'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
const initializer =
|
||||
props.currentTab === 'forward'
|
||||
? enableForwarding()
|
||||
: operateFilterChain(chainName, 'init-' + props.currentTab);
|
||||
await initializer.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onRecoverIPv6 = async () => {
|
||||
const initialized = baseInfo.value.ipv6.initialized;
|
||||
const title = i18n.global.t(initialized ? 'commons.button.bind' : 'commons.button.init');
|
||||
const message = initialized
|
||||
? i18n.global.t('firewall.bindHelper')
|
||||
: i18n.global.t('firewall.initMsg', [`${baseInfo.value.name || backendName.value} IPv6`]);
|
||||
try {
|
||||
await ElMessageBox.confirm(message, title, {
|
||||
await ElMessageBox.confirm(msg, i18n.global.t('commons.button.init'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
await operateFilterChain('1PANEL_BASIC', 'init-ipv6-base');
|
||||
const initializer =
|
||||
props.currentTab === 'forward' ? enableForwarding() : operateFilterChain(chainName, 'init-' + props.currentTab);
|
||||
await initializer;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadBaseInfo(true);
|
||||
};
|
||||
|
||||
const onBind = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('firewall.bindHelper'), i18n.global.t('commons.button.bind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain('1PANEL_BASIC', 'bind-base').then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
try {
|
||||
await ElMessageBox.confirm(i18n.global.t('firewall.bindHelper'), i18n.global.t('commons.button.bind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
await operateFilterChain('1PANEL_BASIC', 'bind-base');
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadBaseInfo(true);
|
||||
};
|
||||
|
||||
const onRetryFamilyIssues = async () => {
|
||||
if (familyRetrying.value || retryableFamilyIssues.value.length === 0) return;
|
||||
familyRetrying.value = true;
|
||||
try {
|
||||
if (isDirectForward.value) {
|
||||
await enableForwarding();
|
||||
} else {
|
||||
await operateFilterChain('1PANEL_BASIC', 'bind-base');
|
||||
}
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadBaseInfo(true);
|
||||
} finally {
|
||||
familyRetrying.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onUnBind = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('firewall.unbindHelper'), i18n.global.t('commons.button.unbind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain('1PANEL_BASIC', 'unbind-base').then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
try {
|
||||
await ElMessageBox.confirm(i18n.global.t('firewall.unbindHelper'), i18n.global.t('commons.button.unbind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
await operateFilterChain('1PANEL_BASIC', 'unbind-base');
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadBaseInfo(true);
|
||||
};
|
||||
|
||||
const onOperate = async (op: string) => {
|
||||
operation.value = op;
|
||||
if (backendName.value === 'iptables' || backendName.value === 'nftables' || !dockerStatus.value) {
|
||||
emit('update:loading', true);
|
||||
emit('update:maskShow', true);
|
||||
await operateFire(operation.value, false)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
@@ -330,7 +387,6 @@ const onOperate = async (op: string) => {
|
||||
|
||||
const onSubmit = async () => {
|
||||
emit('update:loading', true);
|
||||
emit('update:maskShow', true);
|
||||
await operateFire(operation.value, withDockerRestart.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
@@ -345,3 +401,60 @@ defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.firewall-family-warning-trigger {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin-left: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--el-color-warning-light-9);
|
||||
font-size: 16px;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background: var(--el-color-warning-light-8);
|
||||
}
|
||||
}
|
||||
|
||||
.firewall-family-issue-popper.el-popover {
|
||||
padding: 12px;
|
||||
border-color: var(--el-color-warning-light-7);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.firewall-family-issue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.firewall-family-issue-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.firewall-family-issue-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.firewall-family-name {
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.firewall-family-issue-text {
|
||||
color: var(--el-color-warning-dark-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user