mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 04:22:33 +08:00
feat: 支持修改Docker基本设置
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
@@ -19,6 +22,8 @@ func NewApp() *App {
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
r.Get("/settings", s.GetSettings)
|
||||
r.Post("/settings", s.UpdateSettings)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -50,3 +55,213 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetSettings 获取 Docker 设置
|
||||
func (s *App) GetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
configPath := "/etc/docker/daemon.json"
|
||||
|
||||
// 读取配置文件
|
||||
content, err := io.Read(configPath)
|
||||
if err != nil {
|
||||
// 如果文件不存在,返回默认设置
|
||||
if os.IsNotExist(err) {
|
||||
service.Success(w, Settings{})
|
||||
return
|
||||
}
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析 JSON
|
||||
var daemonConfig DaemonConfig
|
||||
if err = json.Unmarshal([]byte(content), &daemonConfig); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为 Settings 结构
|
||||
settings := Settings{
|
||||
RegistryMirrors: daemonConfig.RegistryMirrors,
|
||||
InsecureRegistries: daemonConfig.InsecureRegistries,
|
||||
LiveRestore: daemonConfig.LiveRestore,
|
||||
LogDriver: daemonConfig.LogDriver,
|
||||
Hosts: daemonConfig.Hosts,
|
||||
DataRoot: daemonConfig.DataRoot,
|
||||
StorageDriver: daemonConfig.StorageDriver,
|
||||
DNS: daemonConfig.DNS,
|
||||
FirewallBackend: daemonConfig.FirewallBackend,
|
||||
Iptables: daemonConfig.Iptables,
|
||||
Ip6tables: daemonConfig.Ip6tables,
|
||||
IpForward: daemonConfig.IpForward,
|
||||
IPv6: daemonConfig.IPv6,
|
||||
Bip: daemonConfig.Bip,
|
||||
}
|
||||
|
||||
// 解析 log-opts
|
||||
if daemonConfig.LogOpts != nil {
|
||||
settings.LogOpts = LogOpts{
|
||||
MaxSize: daemonConfig.LogOpts["max-size"],
|
||||
MaxFile: daemonConfig.LogOpts["max-file"],
|
||||
}
|
||||
}
|
||||
|
||||
// 从 exec-opts 中提取 cgroup-driver
|
||||
for _, opt := range daemonConfig.ExecOpts {
|
||||
if strings.HasPrefix(opt, "native.cgroupdriver=") {
|
||||
settings.CgroupDriver = strings.TrimPrefix(opt, "native.cgroupdriver=")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, settings)
|
||||
}
|
||||
|
||||
// UpdateSettings 更新 Docker 设置
|
||||
func (s *App) UpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateSettings](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
configPath := "/etc/docker/daemon.json"
|
||||
settings := req.Settings
|
||||
|
||||
// 读取现有配置(保留其他字段)
|
||||
var existingConfig map[string]any
|
||||
content, err := io.Read(configPath)
|
||||
if err == nil && content != "" {
|
||||
if err = json.Unmarshal([]byte(content), &existingConfig); err != nil {
|
||||
existingConfig = make(map[string]any)
|
||||
}
|
||||
} else {
|
||||
existingConfig = make(map[string]any)
|
||||
}
|
||||
|
||||
// 更新设置字段
|
||||
if len(settings.RegistryMirrors) > 0 {
|
||||
existingConfig["registry-mirrors"] = settings.RegistryMirrors
|
||||
} else {
|
||||
delete(existingConfig, "registry-mirrors")
|
||||
}
|
||||
|
||||
if len(settings.InsecureRegistries) > 0 {
|
||||
existingConfig["insecure-registries"] = settings.InsecureRegistries
|
||||
} else {
|
||||
delete(existingConfig, "insecure-registries")
|
||||
}
|
||||
|
||||
if settings.LiveRestore {
|
||||
existingConfig["live-restore"] = true
|
||||
} else {
|
||||
delete(existingConfig, "live-restore")
|
||||
}
|
||||
|
||||
if settings.LogDriver != "" {
|
||||
existingConfig["log-driver"] = settings.LogDriver
|
||||
} else {
|
||||
delete(existingConfig, "log-driver")
|
||||
}
|
||||
|
||||
// 日志配置
|
||||
if settings.LogOpts.MaxSize != "" || settings.LogOpts.MaxFile != "" {
|
||||
logOpts := make(map[string]string)
|
||||
if settings.LogOpts.MaxSize != "" {
|
||||
logOpts["max-size"] = settings.LogOpts.MaxSize
|
||||
}
|
||||
if settings.LogOpts.MaxFile != "" {
|
||||
logOpts["max-file"] = settings.LogOpts.MaxFile
|
||||
}
|
||||
existingConfig["log-opts"] = logOpts
|
||||
} else {
|
||||
delete(existingConfig, "log-opts")
|
||||
}
|
||||
|
||||
// cgroup-driver
|
||||
if settings.CgroupDriver != "" {
|
||||
existingConfig["exec-opts"] = []string{"native.cgroupdriver=" + settings.CgroupDriver}
|
||||
} else {
|
||||
delete(existingConfig, "exec-opts")
|
||||
}
|
||||
|
||||
if len(settings.Hosts) > 0 {
|
||||
existingConfig["hosts"] = settings.Hosts
|
||||
} else {
|
||||
delete(existingConfig, "hosts")
|
||||
}
|
||||
|
||||
if settings.DataRoot != "" {
|
||||
existingConfig["data-root"] = settings.DataRoot
|
||||
} else {
|
||||
delete(existingConfig, "data-root")
|
||||
}
|
||||
|
||||
if settings.StorageDriver != "" {
|
||||
existingConfig["storage-driver"] = settings.StorageDriver
|
||||
} else {
|
||||
delete(existingConfig, "storage-driver")
|
||||
}
|
||||
|
||||
if len(settings.DNS) > 0 {
|
||||
existingConfig["dns"] = settings.DNS
|
||||
} else {
|
||||
delete(existingConfig, "dns")
|
||||
}
|
||||
|
||||
// 防火墙后端
|
||||
if settings.FirewallBackend != "" {
|
||||
existingConfig["firewall-backend"] = settings.FirewallBackend
|
||||
} else {
|
||||
delete(existingConfig, "firewall-backend")
|
||||
}
|
||||
|
||||
if settings.Iptables != nil {
|
||||
existingConfig["iptables"] = *settings.Iptables
|
||||
} else {
|
||||
delete(existingConfig, "iptables")
|
||||
}
|
||||
|
||||
if settings.Ip6tables != nil {
|
||||
existingConfig["ip6tables"] = *settings.Ip6tables
|
||||
} else {
|
||||
delete(existingConfig, "ip6tables")
|
||||
}
|
||||
|
||||
if settings.IpForward != nil {
|
||||
existingConfig["ip-forward"] = *settings.IpForward
|
||||
} else {
|
||||
delete(existingConfig, "ip-forward")
|
||||
}
|
||||
|
||||
if settings.IPv6 != nil {
|
||||
existingConfig["ipv6"] = *settings.IPv6
|
||||
} else {
|
||||
delete(existingConfig, "ipv6")
|
||||
}
|
||||
|
||||
if settings.Bip != "" {
|
||||
existingConfig["bip"] = settings.Bip
|
||||
} else {
|
||||
delete(existingConfig, "bip")
|
||||
}
|
||||
|
||||
// 序列化并写入文件
|
||||
newContent, err := json.MarshalIndent(existingConfig, "", " ")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(configPath, string(newContent), 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 重启 Docker 服务
|
||||
if err = systemctl.Restart("docker"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -3,3 +3,34 @@ package docker
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
// LogOpts 日志配置选项
|
||||
type LogOpts struct {
|
||||
MaxSize string `json:"max-size,omitempty"` // 日志文件最大大小,如 "10m"
|
||||
MaxFile string `json:"max-file,omitempty"` // 保存的日志文件份数,如 "3"
|
||||
}
|
||||
|
||||
// Settings Docker daemon 设置
|
||||
type Settings struct {
|
||||
RegistryMirrors []string `json:"registry-mirrors,omitempty"` // 注册表镜像
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"` // 非安全镜像仓库
|
||||
LiveRestore bool `json:"live-restore,omitempty"` // Live restore
|
||||
LogDriver string `json:"log-driver,omitempty"` // 日志驱动
|
||||
LogOpts LogOpts `json:"log-opts,omitempty"` // 日志配置选项
|
||||
CgroupDriver string `json:"cgroup-driver,omitempty"` // cgroup 驱动(从 exec-opts 中提取)
|
||||
Hosts []string `json:"hosts,omitempty"` // Socket 路径
|
||||
DataRoot string `json:"data-root,omitempty"` // 数据目录
|
||||
StorageDriver string `json:"storage-driver,omitempty"` // 存储驱动
|
||||
DNS []string `json:"dns,omitempty"` // DNS 配置
|
||||
FirewallBackend string `json:"firewall-backend,omitempty"` // 防火墙后端 (iptables/nftables)
|
||||
Iptables *bool `json:"iptables,omitempty"` // iptables 规则
|
||||
Ip6tables *bool `json:"ip6tables,omitempty"` // ip6tables 规则
|
||||
IpForward *bool `json:"ip-forward,omitempty"` // IP 转发
|
||||
IPv6 *bool `json:"ipv6,omitempty"` // IPv6 支持
|
||||
Bip string `json:"bip,omitempty"` // 默认 bridge 网络 IP 段
|
||||
}
|
||||
|
||||
// UpdateSettings 更新设置请求
|
||||
type UpdateSettings struct {
|
||||
Settings Settings `json:"settings" validate:"required"`
|
||||
}
|
||||
|
||||
23
internal/apps/docker/types.go
Normal file
23
internal/apps/docker/types.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package docker
|
||||
|
||||
// DaemonConfig Docker daemon.json 完整配置结构
|
||||
type DaemonConfig struct {
|
||||
RegistryMirrors []string `json:"registry-mirrors,omitempty"`
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"`
|
||||
LiveRestore bool `json:"live-restore,omitempty"`
|
||||
LogDriver string `json:"log-driver,omitempty"`
|
||||
LogOpts map[string]string `json:"log-opts,omitempty"`
|
||||
ExecOpts []string `json:"exec-opts,omitempty"`
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
DataRoot string `json:"data-root,omitempty"`
|
||||
StorageDriver string `json:"storage-driver,omitempty"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
FirewallBackend string `json:"firewall-backend,omitempty"`
|
||||
Iptables *bool `json:"iptables,omitempty"`
|
||||
Ip6tables *bool `json:"ip6tables,omitempty"`
|
||||
IpForward *bool `json:"ip-forward,omitempty"`
|
||||
IPv6 *bool `json:"ipv6,omitempty"`
|
||||
Bip string `json:"bip,omitempty"`
|
||||
// 其他原有配置字段保留
|
||||
Extra map[string]any `json:"-"`
|
||||
}
|
||||
@@ -2,5 +2,8 @@ import { http } from '@/utils'
|
||||
|
||||
export default {
|
||||
config: (): any => http.Get('/apps/docker/config'),
|
||||
updateConfig: (config: string): any => http.Post('/apps/docker/config', { config })
|
||||
updateConfig: (config: string): any => http.Post('/apps/docker/config', { config }),
|
||||
settings: (): any => http.Get('/apps/docker/settings'),
|
||||
updateSettings: (settings: any): any =>
|
||||
http.Post('/apps/docker/settings', { settings })
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ defineOptions({
|
||||
name: 'apps-docker-index'
|
||||
})
|
||||
|
||||
import { NButton } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import docker from '@/api/apps/docker'
|
||||
@@ -18,11 +17,172 @@ const { data: config } = useRequest(docker.config, {
|
||||
}
|
||||
})
|
||||
|
||||
// 基本设置
|
||||
const settingsLoading = ref(false)
|
||||
const settings = ref<any>({
|
||||
'registry-mirrors': [],
|
||||
'insecure-registries': [],
|
||||
'live-restore': false,
|
||||
'log-driver': 'json-file',
|
||||
'log-opts': {
|
||||
'max-size': '',
|
||||
'max-file': ''
|
||||
},
|
||||
'cgroup-driver': '',
|
||||
hosts: [],
|
||||
'data-root': '',
|
||||
'storage-driver': '',
|
||||
dns: [],
|
||||
'firewall-backend': '',
|
||||
'ip-forward': true,
|
||||
ipv6: false,
|
||||
bip: ''
|
||||
})
|
||||
|
||||
// 镜像输入
|
||||
const mirrorInput = ref('')
|
||||
const insecureRegistryInput = ref('')
|
||||
const dnsInput = ref('')
|
||||
const hostInput = ref('')
|
||||
|
||||
// 日志驱动选项
|
||||
const logDriverOptions = [
|
||||
{ label: 'json-file', value: 'json-file' },
|
||||
{ label: 'local', value: 'local' },
|
||||
{ label: 'journald', value: 'journald' },
|
||||
{ label: 'syslog', value: 'syslog' },
|
||||
{ label: 'fluentd', value: 'fluentd' },
|
||||
{ label: 'gelf', value: 'gelf' },
|
||||
{ label: 'splunk', value: 'splunk' },
|
||||
{ label: 'awslogs', value: 'awslogs' },
|
||||
{ label: 'none', value: 'none' }
|
||||
]
|
||||
|
||||
// cgroup 驱动选项
|
||||
const cgroupDriverOptions = [
|
||||
{ label: $gettext('Default'), value: '' },
|
||||
{ label: 'systemd', value: 'systemd' },
|
||||
{ label: 'cgroupfs', value: 'cgroupfs' }
|
||||
]
|
||||
|
||||
// 存储驱动选项
|
||||
const storageDriverOptions = [
|
||||
{ label: $gettext('Default'), value: '' },
|
||||
{ label: 'overlay2', value: 'overlay2' },
|
||||
{ label: 'fuse-overlayfs', value: 'fuse-overlayfs' },
|
||||
{ label: 'btrfs', value: 'btrfs' },
|
||||
{ label: 'zfs', value: 'zfs' },
|
||||
{ label: 'vfs', value: 'vfs' }
|
||||
]
|
||||
|
||||
// 防火墙后端选项
|
||||
const firewallBackendOptions = [
|
||||
{ label: 'iptables (' + $gettext('Default') + ')', value: '' },
|
||||
{ label: 'iptables', value: 'iptables' },
|
||||
{ label: 'nftables (' + $gettext('Experimental') + ')', value: 'nftables' },
|
||||
{ label: $gettext('None'), value: 'none' }
|
||||
]
|
||||
|
||||
// 常用镜像源预设
|
||||
const mirrorPresets = [
|
||||
{ label: $gettext('China - Millisecond'), value: 'https://docker.1ms.run' },
|
||||
{ label: $gettext('China - DaoCloud'), value: 'https://docker.m.daocloud.io' },
|
||||
{
|
||||
label: $gettext('China - Tencent (Internal only)'),
|
||||
value: 'https://mirror.ccs.tencentyun.com'
|
||||
}
|
||||
]
|
||||
|
||||
// 获取设置
|
||||
const fetchSettings = () => {
|
||||
settingsLoading.value = true
|
||||
useRequest(docker.settings())
|
||||
.onSuccess((res) => {
|
||||
settings.value = {
|
||||
'registry-mirrors': res.data['registry-mirrors'] || [],
|
||||
'insecure-registries': res.data['insecure-registries'] || [],
|
||||
'live-restore': res.data['live-restore'] || false,
|
||||
'log-driver': res.data['log-driver'] || 'json-file',
|
||||
'log-opts': {
|
||||
'max-size': res.data['log-opts']?.['max-size'] || '',
|
||||
'max-file': res.data['log-opts']?.['max-file'] || ''
|
||||
},
|
||||
'cgroup-driver': res.data['cgroup-driver'] || '',
|
||||
hosts: res.data.hosts || [],
|
||||
'data-root': res.data['data-root'] || '',
|
||||
'storage-driver': res.data['storage-driver'] || '',
|
||||
dns: res.data.dns || [],
|
||||
'firewall-backend': res.data['firewall-backend'] || '',
|
||||
'ip-forward': res.data['ip-forward'] ?? true,
|
||||
ipv6: res.data.ipv6 ?? false,
|
||||
bip: res.data.bip || ''
|
||||
}
|
||||
})
|
||||
.onComplete(() => {
|
||||
settingsLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 添加镜像
|
||||
const addMirror = () => {
|
||||
if (mirrorInput.value && !settings.value['registry-mirrors']?.includes(mirrorInput.value)) {
|
||||
settings.value['registry-mirrors'] = [
|
||||
...(settings.value['registry-mirrors'] || []),
|
||||
mirrorInput.value
|
||||
]
|
||||
mirrorInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 添加非安全镜像仓库
|
||||
const addInsecureRegistry = () => {
|
||||
if (
|
||||
insecureRegistryInput.value &&
|
||||
!settings.value['insecure-registries']?.includes(insecureRegistryInput.value)
|
||||
) {
|
||||
settings.value['insecure-registries'] = [
|
||||
...(settings.value['insecure-registries'] || []),
|
||||
insecureRegistryInput.value
|
||||
]
|
||||
insecureRegistryInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 DNS
|
||||
const addDns = () => {
|
||||
if (dnsInput.value && !settings.value.dns?.includes(dnsInput.value)) {
|
||||
settings.value.dns = [...(settings.value.dns || []), dnsInput.value]
|
||||
dnsInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 Host
|
||||
const addHost = () => {
|
||||
if (hostInput.value && !settings.value.hosts?.includes(hostInput.value)) {
|
||||
settings.value.hosts = [...(settings.value.hosts || []), hostInput.value]
|
||||
hostInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
const handleSaveSettings = () => {
|
||||
useRequest(docker.updateSettings(settings.value)).onSuccess(() => {
|
||||
window.$message.success($gettext('Saved successfully'))
|
||||
})
|
||||
}
|
||||
|
||||
const handleSaveConfig = () => {
|
||||
useRequest(docker.updateConfig(config.value)).onSuccess(() => {
|
||||
window.$message.success($gettext('Saved successfully'))
|
||||
})
|
||||
}
|
||||
|
||||
// 监听 tab 切换,加载设置
|
||||
watch(currentTab, (tab) => {
|
||||
if (tab === 'settings') {
|
||||
fetchSettings()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -31,7 +191,277 @@ const handleSaveConfig = () => {
|
||||
<n-tab-pane name="status" :tab="$gettext('Running Status')">
|
||||
<service-status service="docker" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="config" :tab="$gettext('Configuration')">
|
||||
<n-tab-pane name="settings" :tab="$gettext('Basic Settings')">
|
||||
<n-spin :show="settingsLoading">
|
||||
<n-flex vertical :size="20">
|
||||
<!-- 注册表镜像 -->
|
||||
<n-card :title="$gettext('Registry Mirrors')">
|
||||
<template #header-extra>
|
||||
<n-popover trigger="click">
|
||||
<template #trigger>
|
||||
<n-button size="small" quaternary>
|
||||
{{ $gettext('Presets') }}
|
||||
</n-button>
|
||||
</template>
|
||||
<n-flex vertical>
|
||||
<n-button
|
||||
v-for="preset in mirrorPresets"
|
||||
:key="preset.value"
|
||||
size="small"
|
||||
@click="
|
||||
() => {
|
||||
mirrorInput = preset.value
|
||||
addMirror()
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ preset.label }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-popover>
|
||||
</template>
|
||||
<n-flex vertical>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
{{
|
||||
$gettext(
|
||||
'Configure registry mirrors to speed up image downloads. Domestic users can configure domestic mirrors.'
|
||||
)
|
||||
}}
|
||||
</n-alert>
|
||||
<n-input-group>
|
||||
<n-input
|
||||
v-model:value="mirrorInput"
|
||||
:placeholder="
|
||||
$gettext('Enter mirror address, e.g., https://registry.example.com')
|
||||
"
|
||||
@keydown.enter.prevent="addMirror"
|
||||
/>
|
||||
<n-button type="primary" @click="addMirror">{{ $gettext('Add') }}</n-button>
|
||||
</n-input-group>
|
||||
<n-dynamic-tags
|
||||
:value="settings['registry-mirrors']"
|
||||
@update:value="settings['registry-mirrors'] = $event"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<!-- 日志切割 -->
|
||||
<n-card :title="$gettext('Log Configuration')">
|
||||
<n-flex vertical>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
{{
|
||||
$gettext(
|
||||
'Configure log driver and rotation settings. Setting max-size and max-file can prevent log files from growing indefinitely.'
|
||||
)
|
||||
}}
|
||||
</n-alert>
|
||||
<n-form label-placement="left" label-width="120">
|
||||
<n-form-item :label="$gettext('Log Driver')">
|
||||
<n-select
|
||||
v-model:value="settings['log-driver']"
|
||||
:options="logDriverOptions"
|
||||
:placeholder="$gettext('Select log driver')"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-row :gutter="[24, 0]">
|
||||
<n-col :span="12">
|
||||
<n-form-item :label="$gettext('Max Size')">
|
||||
<n-input
|
||||
v-model:value="settings['log-opts']!['max-size']"
|
||||
:placeholder="$gettext('e.g., 10m, 100m, 1g')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="12">
|
||||
<n-form-item :label="$gettext('Max Files')">
|
||||
<n-input
|
||||
v-model:value="settings['log-opts']!['max-file']"
|
||||
:placeholder="$gettext('e.g., 3, 5, 10')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<!-- 运行时选项 -->
|
||||
<n-card :title="$gettext('Runtime Options')">
|
||||
<n-form label-placement="left" label-width="120">
|
||||
<n-row :gutter="[24, 0]">
|
||||
<n-col :span="12">
|
||||
<n-form-item :label="$gettext('Live Restore')">
|
||||
<n-switch v-model:value="settings['live-restore']" />
|
||||
<span class="text-gray-400 ml-2">
|
||||
{{ $gettext('Keep containers alive during daemon downtime') }}
|
||||
</span>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="12">
|
||||
<n-form-item :label="$gettext('Cgroup Driver')">
|
||||
<n-select
|
||||
v-model:value="settings['cgroup-driver']"
|
||||
:options="cgroupDriverOptions"
|
||||
:placeholder="$gettext('Select cgroup driver')"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-row :gutter="[24, 0]">
|
||||
<n-col :span="12">
|
||||
<n-form-item :label="$gettext('IPv6')">
|
||||
<n-switch v-model:value="settings.ipv6" />
|
||||
<span class="text-gray-400 ml-2">
|
||||
{{ $gettext('Requires additional configuration.') }}
|
||||
<n-button
|
||||
text
|
||||
tag="a"
|
||||
href="https://docs.docker.com/engine/daemon/ipv6/"
|
||||
target="_blank"
|
||||
type="info"
|
||||
>
|
||||
{{ $gettext('Docs') }}
|
||||
</n-button>
|
||||
</span>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="12">
|
||||
<n-form-item :label="$gettext('IP Forward')">
|
||||
<n-switch v-model:value="settings['ip-forward']" />
|
||||
<span class="text-gray-400 ml-2">
|
||||
{{ $gettext('Enable IP forwarding') }}
|
||||
</span>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 防火墙配置 -->
|
||||
<n-card :title="$gettext('Firewall Configuration')">
|
||||
<n-flex vertical>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
{{
|
||||
$gettext(
|
||||
'Configure Docker firewall backend. nftables is experimental and does not support Swarm mode.'
|
||||
)
|
||||
}}
|
||||
</n-alert>
|
||||
<n-form label-placement="left" label-width="140">
|
||||
<n-form-item :label="$gettext('Firewall Backend')">
|
||||
<n-select
|
||||
v-model:value="settings['firewall-backend']"
|
||||
:options="firewallBackendOptions"
|
||||
:placeholder="$gettext('Select firewall backend')"
|
||||
style="width: 280px"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<!-- 存储与路径 -->
|
||||
<n-card :title="$gettext('Storage & Paths')">
|
||||
<n-form label-placement="left" label-width="120">
|
||||
<n-form-item :label="$gettext('Storage Driver')">
|
||||
<n-select
|
||||
v-model:value="settings['storage-driver']"
|
||||
:options="storageDriverOptions"
|
||||
:placeholder="$gettext('Select storage driver')"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Data Root')">
|
||||
<n-input
|
||||
v-model:value="settings['data-root']"
|
||||
:placeholder="$gettext('Docker data directory, default is /var/lib/docker')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Socket/Hosts')">
|
||||
<n-flex vertical class="w-full">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
v-model:value="hostInput"
|
||||
:placeholder="
|
||||
$gettext('e.g., unix:///var/run/docker.sock, tcp://0.0.0.0:2375')
|
||||
"
|
||||
@keydown.enter.prevent="addHost"
|
||||
/>
|
||||
<n-button type="primary" @click="addHost">{{ $gettext('Add') }}</n-button>
|
||||
</n-input-group>
|
||||
<n-dynamic-tags
|
||||
:value="settings.hosts"
|
||||
@update:value="settings.hosts = $event"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 网络配置 -->
|
||||
<n-card :title="$gettext('Network Configuration')">
|
||||
<n-form label-placement="left" label-width="120">
|
||||
<n-form-item :label="$gettext('Bridge IP')">
|
||||
<n-input
|
||||
v-model:value="settings.bip"
|
||||
:placeholder="$gettext('Default bridge network IP range, e.g., 172.17.0.1/16')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('DNS Servers')">
|
||||
<n-flex vertical class="w-full">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
v-model:value="dnsInput"
|
||||
:placeholder="$gettext('e.g., 8.8.8.8, 114.114.114.114')"
|
||||
@keydown.enter.prevent="addDns"
|
||||
/>
|
||||
<n-button type="primary" @click="addDns">{{ $gettext('Add') }}</n-button>
|
||||
</n-input-group>
|
||||
<n-dynamic-tags :value="settings.dns" @update:value="settings.dns = $event" />
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 非安全镜像仓库 -->
|
||||
<n-card :title="$gettext('Insecure Registries')">
|
||||
<n-flex vertical>
|
||||
<n-alert type="warning" :show-icon="false">
|
||||
{{
|
||||
$gettext(
|
||||
'Insecure registries allow Docker to communicate with registries using HTTP or self-signed certificates. Use with caution.'
|
||||
)
|
||||
}}
|
||||
</n-alert>
|
||||
<n-input-group>
|
||||
<n-input
|
||||
v-model:value="insecureRegistryInput"
|
||||
:placeholder="$gettext('e.g., 192.168.1.100:5000')"
|
||||
@keydown.enter.prevent="addInsecureRegistry"
|
||||
/>
|
||||
<n-button type="primary" @click="addInsecureRegistry">
|
||||
{{ $gettext('Add') }}
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<n-dynamic-tags
|
||||
:value="settings['insecure-registries']"
|
||||
@update:value="settings['insecure-registries'] = $event"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<n-flex>
|
||||
<n-button type="primary" @click="handleSaveSettings">
|
||||
{{ $gettext('Save') }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-spin>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="config" :tab="$gettext('Configuration File')">
|
||||
<n-flex vertical>
|
||||
<n-alert type="warning">
|
||||
{{ $gettext('This modifies the Docker configuration file (/etc/docker/daemon.json)') }}
|
||||
|
||||
Reference in New Issue
Block a user