feat: 网站默认设置

This commit is contained in:
耗子
2026-01-09 18:04:13 +08:00
parent f48c975de3
commit 3168e6efe3
8 changed files with 166 additions and 122 deletions
+16 -14
View File
@@ -9,20 +9,22 @@ import (
type SettingKey string
const (
SettingKeyName SettingKey = "name"
SettingKeyVersion SettingKey = "version"
SettingKeyChannel SettingKey = "channel"
SettingKeyMonitor SettingKey = "monitor"
SettingKeyMonitorDays SettingKey = "monitor_days"
SettingKeyBackupPath SettingKey = "backup_path"
SettingKeyWebsitePath SettingKey = "website_path"
SettingKeyMySQLRootPassword SettingKey = "mysql_root_password"
SettingKeyOfflineMode SettingKey = "offline_mode"
SettingKeyAutoUpdate SettingKey = "auto_update"
SettingKeyWebserver SettingKey = "webserver"
SettingKeyPublicIPs SettingKey = "public_ips"
SettingHiddenMenu SettingKey = "hidden_menu"
SettingKeyCustomLogo SettingKey = "custom_logo"
SettingKeyName SettingKey = "name"
SettingKeyVersion SettingKey = "version"
SettingKeyChannel SettingKey = "channel"
SettingKeyMonitor SettingKey = "monitor"
SettingKeyMonitorDays SettingKey = "monitor_days"
SettingKeyBackupPath SettingKey = "backup_path"
SettingKeyWebsitePath SettingKey = "website_path"
SettingKeyWebsiteTLSVersions SettingKey = "website_tls_versions"
SettingKeyWebsiteCipherSuites SettingKey = "website_tls_cipher_suites"
SettingKeyMySQLRootPassword SettingKey = "mysql_root_password"
SettingKeyOfflineMode SettingKey = "offline_mode"
SettingKeyAutoUpdate SettingKey = "auto_update"
SettingKeyWebserver SettingKey = "webserver"
SettingKeyPublicIPs SettingKey = "public_ips"
SettingHiddenMenu SettingKey = "hidden_menu"
SettingKeyCustomLogo SettingKey = "custom_logo"
)
type Setting struct {
+30 -12
View File
@@ -84,6 +84,17 @@ func (r *websiteRepo) UpdateDefaultConfig(req *request.WebsiteDefaultConfig) err
if err := io.Write(filepath.Join(app.Root, "server/nginx/html/stop.html"), req.Stop, 0644); err != nil {
return err
}
if req.NotFound != "" {
if err := io.Write(filepath.Join(app.Root, "server/nginx/html/404.html"), req.NotFound, 0644); err != nil {
return err
}
}
if err := r.setting.SetSlice(biz.SettingKeyWebsiteTLSVersions, req.TLSVersions); err != nil {
return err
}
if err := r.setting.Set(biz.SettingKeyWebsiteCipherSuites, req.CipherSuites); err != nil {
return err
}
return r.reloadWebServer()
}
@@ -342,17 +353,22 @@ location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.env) {
return nil, err
}
var notFound []byte
switch app.Locale {
case "zh_CN":
notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404_zh_CN.html"))
case "zh_TW":
notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404_zh_TW.html"))
default:
notFound, err = embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html"))
}
if err != nil {
return nil, errors.New(r.t.Get("failed to get 404 template file: %v", err))
// 如果存在自定义 404 页面,则使用自定义的
// TODO 需要兼容 Apache
if io.Exists(filepath.Join(app.Root, "server/nginx/html/404.html")) {
notFound, _ = os.ReadFile(filepath.Join(app.Root, "server/nginx/html/404.html"))
} else {
switch app.Locale {
case "zh_CN":
notFound, _ = embed.WebsiteFS.ReadFile(filepath.Join("website", "404_zh_CN.html"))
case "zh_TW":
notFound, _ = embed.WebsiteFS.ReadFile(filepath.Join("website", "404_zh_TW.html"))
default:
notFound, _ = embed.WebsiteFS.ReadFile(filepath.Join("website", "404.html"))
}
}
if err = io.Write(filepath.Join(req.Path, "404.html"), string(notFound), 0644); err != nil {
return nil, err
}
@@ -487,11 +503,13 @@ func (r *websiteRepo) Update(req *request.WebsiteUpdate) error {
break
}
}
defaultTLSVersions, _ := r.setting.GetSlice(biz.SettingKeyWebsiteTLSVersions)
defaultCipherSuites, _ := r.setting.Get(biz.SettingKeyWebsiteCipherSuites)
if err = vhost.SetSSLConfig(&webservertypes.SSLConfig{
Cert: certPath,
Key: keyPath,
Protocols: lo.If(len(req.SSLProtocols) > 0, req.SSLProtocols).Else([]string{"TLSv1.2", "TLSv1.3"}),
Ciphers: lo.If(req.SSLCiphers != "", req.SSLCiphers).Else("ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305"),
Protocols: lo.If(len(req.SSLProtocols) > 0, req.SSLProtocols).Else(defaultTLSVersions),
Ciphers: lo.If(req.SSLCiphers != "", req.SSLCiphers).Else(defaultCipherSuites),
HSTS: req.HSTS,
OCSP: req.OCSP,
HTTPRedirect: req.HTTPRedirect,
+5 -2
View File
@@ -5,8 +5,11 @@ import (
)
type WebsiteDefaultConfig struct {
Index string `json:"index" form:"index" validate:"required"`
Stop string `json:"stop" form:"stop" validate:"required"`
Index string `json:"index" form:"index" validate:"required"`
Stop string `json:"stop" form:"stop" validate:"required"`
NotFound string `json:"not_found" form:"not_found"`
TLSVersions []string `json:"tls_versions" form:"tls_versions" validate:"required|isSlice"`
CipherSuites string `json:"cipher_suites" form:"cipher_suites" validate:"required"`
}
type WebsiteList struct {
+2
View File
@@ -921,6 +921,8 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
{Key: biz.SettingKeyMonitorDays, Value: "30"},
{Key: biz.SettingKeyBackupPath, Value: filepath.Join(app.Root, "backup")},
{Key: biz.SettingKeyWebsitePath, Value: filepath.Join(app.Root, "sites")},
{Key: biz.SettingKeyWebsiteTLSVersions, Value: `["TLSv1.2","TLSv1.3"]`},
{Key: biz.SettingKeyWebsiteCipherSuites, Value: `ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305`},
{Key: biz.SettingKeyOfflineMode, Value: "false"},
{Key: biz.SettingKeyAutoUpdate, Value: "true"},
{Key: biz.SettingHiddenMenu, Value: "[]"},
+10 -12
View File
@@ -35,20 +35,18 @@ func (s *WebsiteService) GetRewrites(w http.ResponseWriter, r *http.Request) {
}
func (s *WebsiteService) GetDefaultConfig(w http.ResponseWriter, r *http.Request) {
index, err := io.Read(filepath.Join(app.Root, "server/nginx/html/index.html"))
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
stop, err := io.Read(filepath.Join(app.Root, "server/nginx/html/stop.html"))
if err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
index, _ := io.Read(filepath.Join(app.Root, "server/nginx/html/index.html"))
stop, _ := io.Read(filepath.Join(app.Root, "server/nginx/html/stop.html"))
notFound, _ := io.Read(filepath.Join(app.Root, "server/nginx/html/404.html"))
tlsVersions, _ := s.settingRepo.GetSlice(biz.SettingKeyWebsiteTLSVersions)
cipherSuites, _ := s.settingRepo.Get(biz.SettingKeyWebsiteCipherSuites)
Success(w, chix.M{
"index": index,
"stop": stop,
"index": index,
"stop": stop,
"not_found": notFound,
"tls_versions": tlsVersions,
"cipher_suites": cipherSuites,
})
}
+1 -2
View File
@@ -14,8 +14,7 @@ export default {
// 获取默认配置
defaultConfig: (): any => http.Get('/website/default_config'),
// 保存默认配置
saveDefaultConfig: (index: string, stop: string): any =>
http.Post('/website/default_config', { index, stop }),
saveDefaultConfig: (data: any): any => http.Post('/website/default_config', data),
// 网站配置
config: (id: number): any => http.Get('/website/' + id),
// 保存网站配置
+21 -19
View File
@@ -323,6 +323,26 @@ const hasArg = (args: string[], arg: string) => {
</n-form-item>
</n-form>
<n-form v-if="setting.ssl">
<n-form-item :label="$gettext('TLS Version')">
<n-select
v-model:value="setting.ssl_protocols"
:options="[
{ label: 'TLS 1.0', value: 'TLSv1.0' },
{ label: 'TLS 1.1', value: 'TLSv1.1' },
{ label: 'TLS 1.2', value: 'TLSv1.2' },
{ label: 'TLS 1.3', value: 'TLSv1.3' }
]"
multiple
/>
</n-form-item>
<n-form-item :label="$gettext('Cipher Suites')">
<n-input
type="textarea"
v-model:value="setting.ssl_ciphers"
:placeholder="$gettext('Enter the cipher suite, leave blank to reset to default')"
rows="4"
/>
</n-form-item>
<n-form-item :label="$gettext('Certificate')">
<n-input
v-model:value="setting.ssl_cert"
@@ -355,25 +375,7 @@ const hasArg = (args: string[], arg: string) => {
/>
</n-form-item>
</n-form>
<common-editor v-if="setting" v-model:content="setting.rewrite" height="60vh" />
</n-flex>
</n-tab-pane>
<n-tab-pane name="config" :tab="$gettext('Configuration')">
<n-flex vertical>
<n-alert type="info" w-full>
{{
$gettext(
'If you modify the original text, other modifications will not take effect after clicking save!'
)
}}
</n-alert>
<n-alert type="warning" w-full>
{{
$gettext(
'If you do not understand the configuration rules, please do not modify them arbitrarily, otherwise it may cause the website to be inaccessible or panel function abnormalities! If you have already encountered a problem, try resetting the configuration!'
)
}}
</n-alert>
<common-editor v-if="setting" v-model:value="setting.rewrite" height="60vh" />
</n-flex>
</n-tab-pane>
<n-tab-pane name="log" :tab="$gettext('Access Log')">
+81 -61
View File
@@ -6,51 +6,45 @@ const { $gettext } = useGettext()
const currentTab = ref('default-page')
const defaultPageModel = ref({
index: '',
not_found: '',
stop: ''
const { data: model } = useRequest(website.defaultConfig, {
initialData: {
index: '',
stop: '',
not_found: '',
tls_versions: ['TLSv1.2', 'TLSv1.3'],
cipher_suites: ''
}
})
const defaultSettingModel = ref({
tls_version: ['TLSv1.2', 'TLSv1.3'],
cipher_suites: ''
})
watch(
() => model.value.tls_versions,
(newVal) => {
if (!newVal.includes('TLSv1.1') && !newVal.includes('TLSv1.0')) {
// 不包含 TLSv1.0 和 TLSv1.1
model.value.cipher_suites =
'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305'
} else {
// 包含 TLSv1.0 或 TLSv1.1
model.value.cipher_suites =
'@SECLEVEL=0:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA'
}
}
)
const getDefaultPage = async () => {
defaultPageModel.value = await website.defaultConfig()
}
const handleSaveDefaultPage = () => {
useRequest(
website.saveDefaultConfig(defaultPageModel.value.index, defaultPageModel.value.stop)
).onSuccess(() => {
const handleSave = () => {
useRequest(website.saveDefaultConfig(model.value)).onSuccess(() => {
window.$message.success($gettext('Modified successfully'))
})
}
onMounted(() => {
getDefaultPage()
})
</script>
<template>
<n-tabs v-model:value="currentTab" type="line" placement="left" animated>
<n-tab-pane name="default-page" :tab="$gettext('Default Page')">
<n-flex vertical>
<common-editor v-model:value="defaultPageModel.index" height="60vh" />
<common-editor v-model:value="model.index" height="60vh" />
<n-flex>
<n-button type="primary">
{{ $gettext('Save Changes') }}
</n-button>
</n-flex>
</n-flex>
</n-tab-pane>
<n-tab-pane name="404-page" :tab="$gettext('404 Page')">
<n-flex>
<common-editor v-model:value="defaultPageModel.not_found" height="60vh" />
<n-flex>
<n-button type="primary">
<n-button type="primary" @click="handleSave">
{{ $gettext('Save Changes') }}
</n-button>
</n-flex>
@@ -58,9 +52,19 @@ onMounted(() => {
</n-tab-pane>
<n-tab-pane name="stop-page" :tab="$gettext('Stop Page')">
<n-flex>
<common-editor v-model:value="defaultPageModel.stop" height="60vh" />
<common-editor v-model:value="model.stop" height="60vh" />
<n-flex>
<n-button type="primary">
<n-button type="primary" @click="handleSave">
{{ $gettext('Save Changes') }}
</n-button>
</n-flex>
</n-flex>
</n-tab-pane>
<n-tab-pane name="404-page" :tab="$gettext('404 Page')">
<n-flex>
<common-editor v-model:value="model.not_found" height="60vh" />
<n-flex>
<n-button type="primary" @click="handleSave">
{{ $gettext('Save Changes') }}
</n-button>
</n-flex>
@@ -70,33 +74,49 @@ onMounted(() => {
<n-alert type="info">待开发</n-alert>
</n-tab-pane>
<n-tab-pane name="default-setting" :tab="$gettext('Default Settings')">
<n-form>
<n-form-item :label="$gettext('Default TLS Version')">
<n-select
v-model:value="defaultSettingModel.tls_version"
:options="[
{ label: 'TLS 1.0', value: 'TLSv1.0' },
{ label: 'TLS 1.1', value: 'TLSv1.1' },
{ label: 'TLS 1.2', value: 'TLSv1.2' },
{ label: 'TLS 1.3', value: 'TLSv1.3' }
]"
multiple
/>
</n-form-item>
<n-form-item :label="$gettext('Default Cipher Suites')">
<n-input
type="textarea"
v-model:value="defaultSettingModel.cipher_suites"
:placeholder="
$gettext('Enter the default cipher suite, leave blank to reset to default')
"
rows="4"
/>
</n-form-item>
<n-button type="primary">
{{ $gettext('Save Changes') }}
</n-button>
</n-form>
<n-flex vertical>
<n-alert type="info">
{{
$gettext(
'Modifying the default TLS version and cipher suites will affect all newly created websites. Existing websites will not be affected.'
)
}}
</n-alert>
<n-alert type="warning">
{{
$gettext(
'Please adjust the settings carefully, improper configuration may lead to website inaccessible.'
)
}}
</n-alert>
<n-form>
<n-form-item :label="$gettext('Default TLS Version')">
<n-select
v-model:value="model.tls_versions"
:options="[
{ label: 'TLS 1.0', value: 'TLSv1.0' },
{ label: 'TLS 1.1', value: 'TLSv1.1' },
{ label: 'TLS 1.2', value: 'TLSv1.2' },
{ label: 'TLS 1.3', value: 'TLSv1.3' }
]"
multiple
/>
</n-form-item>
<n-form-item :label="$gettext('Default Cipher Suites')">
<n-input
type="textarea"
v-model:value="model.cipher_suites"
:placeholder="
$gettext('Enter the default cipher suite, leave blank to reset to default')
"
rows="4"
/>
</n-form-item>
<n-button type="primary" @click="handleSave">
{{ $gettext('Save Changes') }}
</n-button>
</n-form>
</n-flex>
</n-tab-pane>
</n-tabs>
</template>