Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2668,6 +2668,13 @@ const message = {
noDefaultServer: 'Not set',
defaultServerHelper:
'After setting the default site, all unbinded domain names and IPs will be redirected to the default site\nThis can effectively prevent malicious resolution\nHowever, it will also cause the WAF unauthorized domain name interception to fail',
defaultPage: 'Default Page',
defaultPageType: 'Default Page Type',
customHtml: 'Custom HTML',
noResponse: 'No Response (444)',
noResponseHelper: 'Returns no response to the client. More secure when ports are scanned, as it reveals no server information.',
redirectTo: 'Redirect',
redirectUrl: 'Redirect URL',
restoreHelper: 'Are you sure to restore using this backup?',
websiteDeploymentHelper: 'Use an installed application or create a new application to create a website.',
websiteStatictHelper: 'Create a website directory on the host.',
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,13 @@ const message = {
noDefaultServer: '未设置',
defaultServerHelper:
'设置默认站点后,所有未绑定的域名和IP都被定向到默认站点\n可有效防止恶意解析\n但同时会导致 WAF 未授权域名拦截失败',
defaultPage: '默认页',
defaultPageType: '默认页类型',
customHtml: '自定义 HTML',
noResponse: '不响应 (444)',
noResponseHelper: '不响应客户端请求。端口被扫描时更安全,不会暴露服务器信息。',
redirectTo: '跳转',
redirectUrl: '跳转地址',
websiteDeploymentHelper: '使用从 1Panel 部署的应用创建网站',
websiteStatictHelper: '在主机上创建网站目录',
websiteProxyHelper:
Expand Down
160 changes: 140 additions & 20 deletions frontend/src/views/website/website/default/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,53 @@
<DrawerPro v-model="open" :header="$t('website.defaultServer')" size="normal" @close="handleClose">
<el-form @submit.prevent label-position="top" v-loading="loading">
<el-form-item :label="$t('website.defaultServer')">
<el-select v-model="defaultId">
<el-option :value="0" :key="-1" :label="$t('website.noDefaultServer')"></el-option>
<el-select v-model="defaultMode" @change="onModeChange">
<el-option value="none" :label="$t('website.noDefaultServer')" />
<el-option value="defaultPage" :label="$t('website.defaultPage')" />
<el-option
v-for="(website, key) in websites"
:key="key"
:value="website.id"
:value="'site:' + website.id"
:label="website.primaryDomain"
></el-option>
/>
</el-select>
</el-form-item>

<template v-if="defaultMode === 'defaultPage'">
<el-form-item :label="$t('website.defaultPageType')">
<el-select v-model="pageType">
<el-option value="html" :label="$t('website.customHtml')" />
<el-option value="noResponse" :label="$t('website.noResponse')" />
<el-option value="redirect" :label="$t('website.redirectTo')" />
</el-select>
</el-form-item>

<el-form-item v-if="pageType === 'redirect'" :label="$t('website.redirectUrl')">
<el-input v-model="redirectUrl" placeholder="https://example.com" />
</el-form-item>

<template v-if="pageType === 'html'">
<el-form-item :label="$t('website.defaultHtml')">
<el-select v-model="htmlType" @change="loadHtml" class="w-full">
<el-option value="404" :label="$t('website.website404')" />
<el-option value="index" :label="$t('website.indexHtml')" />
<el-option value="stop" :label="$t('website.stopHtml')" />
</el-select>
</el-form-item>
<div ref="htmlRef" class="default-html"></div>
</template>
</template>
</el-form>
<el-alert :closable="false">

<el-alert :closable="false" class="mt-4">
<template #default>
<span class="whitespace-pre-line">{{ $t('website.defaultServerHelper') }}</span>
<div v-if="pageType === 'noResponse'" class="mt-2">
<span>{{ $t('website.noResponseHelper') }}</span>
</div>
</template>
</el-alert>

<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
Expand All @@ -29,47 +60,136 @@
</DrawerPro>
</template>
<script lang="ts" setup>
import { reactive, ref, nextTick } from 'vue';
import { Website } from '@/api/interface/website';
import { changeDefaultServer, listWebsites } from '@/api/modules/website';
import { changeDefaultServer, listWebsites, getDefaultHtml, updateDefaultHtml } from '@/api/modules/website';
import i18n from '@/lang';
import { ref } from 'vue';
import { MsgSuccess } from '@/utils/message';
import { EditorState } from '@codemirror/state';
import { basicSetup, EditorView } from 'codemirror';
import { html } from '@codemirror/lang-html';
import { oneDark } from '@codemirror/theme-one-dark';

let open = ref(false);
let websites = ref<any>();
let defaultId = ref(-1);
let websites = ref<any>([]);
let loading = ref(false);
let defaultMode = ref('none');
let pageType = ref('html');
let htmlType = ref('404');
let redirectUrl = ref('');

const htmlRef = ref();
const view = ref();
const content = ref('');

const acceptParams = () => {
defaultId.value = 0;
defaultMode.value = 'none';
pageType.value = 'html';
htmlType.value = '404';
redirectUrl.value = '';
get();
open.value = true;
};

const handleClose = () => {
if (view.value) {
view.value.destroy();
view.value = null;
}
open.value = false;
};

const get = async () => {
const res = await listWebsites();
websites.value = res.data;
defaultMode.value = 'none';
websites.value.forEach((website: Website.WebsiteDTO) => {
if (website.defaultServer) {
defaultId.value = website.id;
defaultMode.value = 'site:' + website.id;
}
});
if (defaultMode.value === 'none') {
// Check if default page config exists (default_server on 00.default.conf)
// Keep as 'none' - user can switch to defaultPage if desired
}
};

const submit = () => {
loading.value = true;
changeDefaultServer({ id: defaultId.value })
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
const onModeChange = () => {
if (defaultMode.value === 'defaultPage') {
nextTick(() => {
if (pageType.value === 'html') {
loadHtml();
}
});
}
};

const loadHtml = async () => {
try {
const res = await getDefaultHtml(htmlType.value);
content.value = res.data.content;
initEditor();
} catch (e) {
content.value = '';
}
};

const initEditor = () => {
if (view.value) {
view.value.destroy();
view.value = null;
}
nextTick(() => {
if (!htmlRef.value) return;
const startState = EditorState.create({
doc: content.value,
extensions: [basicSetup, oneDark, html()],
});
view.value = new EditorView({
state: startState,
parent: htmlRef.value,
});
});
};

const submit = async () => {
loading.value = true;
try {
if (defaultMode.value === 'none') {
await changeDefaultServer({ id: 0 });
} else if (defaultMode.value === 'defaultPage') {
// Set no specific website as default (use default config)
await changeDefaultServer({ id: 0 });

if (pageType.value === 'html' && view.value) {
const htmlContent = view.value.state.doc.toString();
await updateDefaultHtml({ type: htmlType.value, content: htmlContent, sync: false });
} else if (pageType.value === 'noResponse') {
// Write a 444 return config as the default page
const noResponseHtml = '<html><head><meta http-equiv="refresh" content="0;url=about:blank"></head><body></body></html>';
await updateDefaultHtml({ type: 'index', content: noResponseHtml, sync: false });
} else if (pageType.value === 'redirect' && redirectUrl.value) {
const redirectHtml = `<html><head><meta http-equiv="refresh" content="0;url=${redirectUrl.value}"></head><body>Redirecting...</body></html>`;
await updateDefaultHtml({ type: 'index', content: redirectHtml, sync: false });
}
} else if (defaultMode.value.startsWith('site:')) {
const siteId = parseInt(defaultMode.value.replace('site:', ''));
await changeDefaultServer({ id: siteId });
}
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
} finally {
loading.value = false;
}
};

defineExpose({ acceptParams });
</script>

<style scoped lang="scss">
.default-html {
width: 100%;
min-height: 300px;
margin-top: 10px;
}
</style>