timon_playgroud/frontend/src/components/admin/domains/mailstorage/Settings.vue

1072 lines
29 KiB
Vue

<template>
<div>
<a-card class="panel-content">
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.Type')"
>
<a-select v-model:value="page.new.Type" @change="getNew">
<a-select-option value="pgStorage">PostgreSQL</a-select-option>
<a-select-option value="maildirStorage">Maildir</a-select-option>
</a-select>
</a-form-item>
<a-collapse
:collapsible="!page.contentLoaded ? 'disabled' : undefined"
v-model:activeKey="page.openPanel"
accordion
>
<a-collapse-panel
key="1"
:header="
$t('components.admin.domains.mailstorage.settings.panel_connect')
"
>
<template v-if="page.new.Type === 'pgStorage'">
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.multi_storage.col_access_key'
)
"
>
<a-flex gap="small" justify="space-between">
<a-input disabled v-model:value="page.new.Settings.AccessKey">
</a-input>
<a-button
:disabled="page.old.Type != page.new.Type"
@click="showEditAccessKey(page.new.Settings.AccessKey)"
>
<EditOutlined />
</a-button>
</a-flex>
</a-form-item>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.Host')
"
>
<a-input v-model:value="page.new.Settings.Host"> </a-input>
</a-form-item>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.Port')
"
>
<a-input-number
class="form-input-number"
v-model:value="page.new.Settings.Port"
>
</a-input-number>
</a-form-item>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.MboxesDb')
"
>
<a-input v-model:value="page.new.Settings.MboxesDb"> </a-input>
</a-form-item>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.User')
"
>
<a-input v-model:value="page.new.Settings.User"> </a-input>
</a-form-item>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.Pass')
"
>
<a-input-password
autocomplete="off"
v-model:value="page.new.Settings.Pass"
>
</a-input-password>
</a-form-item>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.MaxConn')
"
>
<a-input-number
:min="1"
class="form-input-number"
v-model:value="page.new.Settings.MaxConn"
>
</a-input-number>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.multi_storage.mailbox_distribution_pattern'
)
"
>
<a-input
v-model:value="page.new.Settings.MailboxDistributionPattern"
>
</a-input>
</a-form-item>
<a-collapse
:collapsible="!page.contentLoaded ? 'disabled' : undefined"
accordion
>
<a-collapse-panel
:header="
page.renderMulti
? $t(
'components.admin.domains.mailstorage.settings.panel_multi'
) +
` (` +
page.new.Settings.MultiStorageConnects.length +
` ` +
$t(
'components.admin.domains.mailstorage.settings.multi_storage.conns_nums'
) +
`)`
: ''
"
>
<a-flex gap="middle" vertical>
<a-table
:columns="multiStorageColumns"
:data-source="page.new.Settings.MultiStorageConnects"
bordered
size="small"
:pagination="false"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button
@click="editMultiConnect(record.AccessKey)"
>
<EditOutlined />
{{
$t(
"components.admin.domains.mailstorage.settings.multi_storage.edit_action"
)
}}
</a-button>
<a-popconfirm
:title="t('common.misc.delete') + '?'"
:ok-text="t('common.misc.ok')"
:cancel-text="t('common.misc.cancel')"
@confirm="deleteMultiConnect(record.AccessKey)"
>
<a-button danger>
<DeleteOutlined />
{{
$t(
"components.admin.domains.mailstorage.settings.multi_storage.delete_action"
)
}}
</a-button>
</a-popconfirm>
</a-space>
</template>
<template v-if="column.key === 'hostport'">
{{ record.Host + ":" + record.Port }}
</template>
</template>
</a-table>
<a-button @click="showAddMultiStorageConnect">
<PlusOutlined />
{{
$t(
"components.admin.domains.mailstorage.settings.multi_storage.add_connect"
)
}}
</a-button>
</a-flex>
</a-collapse-panel>
</a-collapse>
</template>
<template v-if="page.new.Type === 'maildirStorage'">
<a-form-item
style="margin-bottom: -14px"
:label="
$t(
'components.admin.domains.mailstorage.settings.MailDirRoot'
)
"
>
<a-input v-model:value="page.new.Settings.MailDirRoot">
</a-input>
</a-form-item>
</template>
</a-collapse-panel>
<a-collapse-panel
key="2"
:header="
$t(
'components.admin.domains.mailstorage.settings.panel_additional'
)
"
>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.Archive'
)
"
>
<a-input
v-model:value="page.new.Settings.IMAPDefaultFolders.Archive"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.Drafts'
)
"
>
<a-input
v-model:value="page.new.Settings.IMAPDefaultFolders.Drafts"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.Junk'
)
"
>
<a-input
v-model:value="page.new.Settings.IMAPDefaultFolders.Junk"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.Sent'
)
"
>
<a-input
v-model:value="page.new.Settings.IMAPDefaultFolders.Sent"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.Trash'
)
"
>
<a-input
v-model:value="page.new.Settings.IMAPDefaultFolders.Trash"
>
</a-input>
</a-form-item>
<template v-if="page.new.Type === 'pgStorage'">
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.SharedRoot'
)
"
>
<a-input
v-model:value="
page.new.Settings.IMAPDefaultFolders.SharedRoot
"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.IMAPDefaultFolders.Recovery'
)
"
>
<a-input
disabled
v-model:value="page.new.Settings.IMAPDefaultFolders.Recovery"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.RecoveryEnabled'
)
"
>
<a-switch v-model:checked="page.new.Settings.RecoveryEnabled">
</a-switch>
</a-form-item>
</template>
<a-form-item
:label="
$t('components.admin.domains.mailstorage.settings.SmartHost')
"
>
<a-input
:placeholder="
$t(
'components.admin.domains.mailstorage.settings.SmartHostFormat'
)
"
v-model:value="page.new.Settings.rawSmartHost"
>
</a-input>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.DefaultUserQuota'
)
"
>
<a-input-number
class="form-input-number"
:addon-after="$t('common.suffixes.mb')"
v-model:value="page.new.Settings.DefaultUserQuota"
>
</a-input-number>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.TrashAdditionalQuota'
)
"
>
<a-input-number
:addon-after="$t('common.suffixes.mb')"
class="form-input-number"
v-model:value="page.new.Settings.TrashAdditionalQuota"
>
</a-input-number>
</a-form-item>
<a-form-item
style="margin-bottom: -14px"
:label="
$t(
'components.admin.domains.mailstorage.settings.QuotaNotifyThreshold'
)
"
>
<a-input-number
:min="0"
:max="100"
addon-after="%"
class="form-input-number"
v-model:value="page.new.Settings.QuotaNotifyThreshold"
>
</a-input-number>
</a-form-item>
</a-collapse-panel>
</a-collapse>
<br />
<a-space style="display: flex; justify-content: center">
<a-button
class="save-button"
:loading="page.checkLoading"
size="large"
@click="check"
>
{{ $t("components.admin.domains.mailstorage.settings.check") }}
</a-button>
<a-button
:disabled="!page.new.Type || !page.contentLoaded"
type="primary"
size="large"
class="save-button"
style="width: fit-content"
@click="save"
>
{{
page.old.Type != page.new.Type
? $t("components.admin.domains.mailstorage.settings.change")
: $t("components.admin.domains.mailstorage.settings.save")
}}
</a-button>
</a-space>
</a-form>
</a-card>
<a-modal
v-model:open="page.showAddEditMultiStorageModal"
:title="
page.multiStorageEdit
? $t(
`components.admin.domains.mailstorage.settings.multi_storage.edit_connect`
)
: $t(
`components.admin.domains.mailstorage.settings.multi_storage.add_connect`
)
"
>
<a-divider></a-divider>
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.multi_storage.col_access_key'
)
"
>
<a-flex gap="small" justify="space-between">
<a-input
:disabled="page.multiStorageEdit"
v-model:value="page.addEditMultistorageConnect.AccessKey"
>
</a-input>
<a-button
v-if="page.multiStorageEdit"
@click="
showEditAccessKey(page.addEditMultistorageConnect.AccessKey)
"
>
<EditOutlined />
</a-button>
</a-flex>
</a-form-item>
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.Host')"
>
<a-input v-model:value="page.addEditMultistorageConnect.Host">
</a-input>
</a-form-item>
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.Port')"
>
<a-input-number
class="form-input-number"
v-model:value="page.addEditMultistorageConnect.Port"
>
</a-input-number>
</a-form-item>
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.MboxesDb')"
>
<a-input v-model:value="page.addEditMultistorageConnect.MboxesDb">
</a-input>
</a-form-item>
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.User')"
>
<a-input v-model:value="page.addEditMultistorageConnect.User">
</a-input>
</a-form-item>
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.Pass')"
>
<a-input-password
autocomplete="off"
v-model:value="page.addEditMultistorageConnect.Pass"
>
</a-input-password>
</a-form-item>
<a-form-item
:label="$t('components.admin.domains.mailstorage.settings.MaxConn')"
>
<a-input-number
:min="1"
class="form-input-number"
v-model:value="page.addEditMultistorageConnect.MaxConn"
>
</a-input-number>
</a-form-item>
<a-form-item
:label="
$t(
'components.admin.domains.mailstorage.settings.multi_storage.mailbox_distribution_pattern'
)
"
>
<a-input
v-model:value="
page.addEditMultistorageConnect.MailboxDistributionPattern
"
>
</a-input>
</a-form-item>
</a-form>
<template #footer>
<div style="display: flex; justify-content: center">
<a-button
v-if="page.renderMulti"
key="submit"
size="large"
@click="saveMultiConnect"
>{{
$t(
`components.admin.domains.mailstorage.settings.multi_storage.save_connect`
)
}}</a-button
>
</div>
</template>
</a-modal>
<a-modal
v-model:open="page.editAccessKey.show"
:title="
$t(
`components.admin.domains.mailstorage.settings.multi_storage.edit_access_key`
)
"
style="width: 350px"
>
<a-input v-model:value="page.editAccessKey.new"> </a-input>
<template #footer>
<div>
<a-button
type="primary"
:loading="page.editAccessKey.loading"
@click="saveNewAccessKey"
>{{
$t(
`components.admin.domains.mailstorage.settings.multi_storage.save`
)
}}</a-button
>
</div>
</template>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { notifyError, notifySuccess } from "@/composables/alert";
import { apiFetch } from "@/composables/apiFetch";
import {
DeleteOutlined,
EditOutlined,
ExclamationCircleOutlined,
PlusOutlined,
} from "@ant-design/icons-vue";
import { Modal } from "ant-design-vue";
import { createVNode, nextTick, onMounted, reactive } from "vue";
import { saveAndRestart } from "@/composables/restart";
import { message } from "ant-design-vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import router from "@/router";
import type { ColumnType } from "ant-design-vue/es/table";
import { RouteAdminDashboard } from "@/router/consts";
import { genRandomString } from "@/composables/misc";
const route = useRoute();
const { t } = useI18n();
const multiStorageColumns: ColumnType<any>[] = [
{
title: t(
"components.admin.domains.mailstorage.settings.multi_storage.col_access_key"
),
dataIndex: "AccessKey",
},
{
title: t(
"components.admin.domains.mailstorage.settings.multi_storage.col_hostport"
),
key: "hostport",
},
{
key: "action",
align: "right",
},
];
interface SmartHost {
Host: string;
Port: number;
User: string;
Pass: string;
}
interface MultiStorageConnect {
AccessKey: string;
MailboxDistributionPattern: string;
Host: string;
Port: number;
User: string;
Pass: string;
MaxConn: number;
MboxesDb: string;
}
interface Settings {
AccessKey: string;
MailboxDistributionPattern: string;
Host: string;
Port: number;
MboxesDb: string;
User: string;
Pass: string;
MaxConn: number;
IMAPDefaultFolders: {
Archive: string;
Drafts: string;
Junk: string;
Sent: string;
Trash: string;
SharedRoot: string;
Recovery: string;
};
MailDirRoot: string;
MaildirInboxPath: string;
RecoveryEnabled: boolean;
rawSmartHost: string;
SmartHost: SmartHost | null;
DefaultUserQuota: number;
TrashAdditionalQuota: number;
QuotaNotifyThreshold: number;
MultiStorageConnects: MultiStorageConnect[];
}
function getEmptySettings(): Settings {
return {
AccessKey: "",
MailboxDistributionPattern: "",
Host: "",
Port: 0,
MboxesDb: "",
User: "",
Pass: "",
MaxConn: 0,
IMAPDefaultFolders: {
Archive: "",
Drafts: "",
Junk: "",
Sent: "",
Trash: "",
SharedRoot: "",
Recovery: "",
},
MailDirRoot: "",
MaildirInboxPath: "",
RecoveryEnabled: false,
rawSmartHost: "",
SmartHost: {
Host: "",
Port: 0,
User: "",
Pass: "",
},
DefaultUserQuota: 0,
TrashAdditionalQuota: 0,
QuotaNotifyThreshold: 0,
MultiStorageConnects: [],
};
}
function getEmptyMultiStorageConnect(): MultiStorageConnect {
return {
AccessKey: "",
MailboxDistributionPattern: "*",
Host: "",
Port: 0,
MboxesDb: "",
User: "",
Pass: "",
MaxConn: 0,
};
}
const page = reactive<{
renderMulti: boolean;
showAddEditMultiStorageModal: boolean;
multiStorageEdit: boolean;
addEditMultistorageConnect: MultiStorageConnect;
openPanel: any;
domain: string;
contentLoaded: boolean;
checkLoading: boolean;
new: {
Type: string;
Settings: Settings;
};
old: {
Type: string;
Settings: Settings;
};
editAccessKey: {
show: boolean;
old: string;
new: string;
loading: boolean;
};
}>({
renderMulti: true,
showAddEditMultiStorageModal: false,
multiStorageEdit: false,
addEditMultistorageConnect: getEmptyMultiStorageConnect(),
openPanel: ["1"],
domain: "",
contentLoaded: false,
checkLoading: false,
new: {
Type: "",
Settings: getEmptySettings(),
},
old: {
Type: "",
Settings: getEmptySettings(),
},
editAccessKey: {
show: false,
old: "",
new: "",
loading: false,
},
});
const labelCol = { span: 16, style: { "text-align": "left" } };
const wrapperCol = { span: 8 };
onMounted(() => {
page.domain = route.params.domain as string;
get();
});
function marshalSmarthost(sh: SmartHost | null): string {
if (!sh) {
return "";
}
let res = "";
if (sh.User != "") {
res = sh.User + ":" + sh.Pass + "@";
}
return res + sh.Host + ":" + sh.Port;
}
function unmarshalSmarthost(sh: string): SmartHost {
let res: SmartHost = {
Host: "",
Port: 0,
User: "",
Pass: "",
};
if (!sh) {
return res;
}
let parts = sh.split("@");
if (parts.length == 2) {
let auth = parts[0].split(":");
if (auth.length == 2) {
res.User = auth[0];
res.Pass = auth[1];
} else {
throw "Unknown smarthost format";
}
parts[0] = parts[1];
}
let main = parts[0].split(":");
if (main.length == 2) {
res.Host = main[0];
res.Port = Number(main[1]);
} else {
throw "Unknown smarthost format";
}
return res;
}
async function get() {
const res = await apiFetch(
`/admin/domains/${page.domain}/mailstorage/settings`
);
if (res.error) {
notifyError(res.error);
return;
}
res.data.Settings.rawSmartHost = marshalSmarthost(
res.data.Settings.SmartHost
);
if (res.data.Settings.MultiStorageConnects === undefined) {
res.data.Settings.MultiStorageConnects = [];
}
page.new = { ...res.data };
page.old = { ...res.data };
page.contentLoaded = true;
return;
}
async function getNew() {
if (page.new.Type === page.old.Type) {
page.new = { ...page.old };
return;
}
const res = await apiFetch(
`/admin/domains/${page.domain}/mailstorage/settings/new?type=${page.new.Type}`
);
if (res.error) {
notifyError(res.error);
return;
}
page.contentLoaded = true;
if (res.data.Settings.MultiStorageConnects === undefined) {
res.data.Settings.MultiStorageConnects = [];
}
page.new = res.data;
return;
}
async function save() {
try {
page.new.Settings.SmartHost = unmarshalSmarthost(
page.new.Settings.rawSmartHost
);
} catch {
notifyError("Unknown smarthost format");
return;
}
const res = await apiFetch(
`/admin/domains/${page.domain}/mailstorage/settings`,
{
method: "POST",
body: page.new,
}
);
if (res.error) {
notifyError(res.error);
return;
}
notifySuccess(
t("components.admin.domains.mailstorage.settings.save_success")
);
router.go(0);
return;
}
async function check() {
page.checkLoading = true;
const res = await apiFetch(
`/admin/domains/${page.domain}/mailstorage/settings/check`,
{
method: "POST",
body: page.new,
}
);
page.checkLoading = false;
if (res.error) {
notifyError(
t("components.admin.domains.mailstorage.settings.check_error") + res.error
);
return;
}
notifySuccess(
t("components.admin.domains.mailstorage.settings.check_success")
);
return;
}
function deleteMultiConnect(accessKey: string) {
let newConnects: MultiStorageConnect[];
newConnects = [];
page.new.Settings.MultiStorageConnects.forEach((msc) => {
if (msc.AccessKey !== accessKey) {
newConnects.push(msc);
}
});
page.new.Settings.MultiStorageConnects = newConnects;
page.renderMulti = false;
nextTick(() => {
page.renderMulti = true;
});
}
function editMultiConnect(accessKey: string) {
for (let i = 0; i < page.new.Settings.MultiStorageConnects.length; i++) {
const el = page.new.Settings.MultiStorageConnects[i];
if (el.AccessKey === accessKey) {
page.addEditMultistorageConnect = {
...page.new.Settings.MultiStorageConnects[i],
};
break;
}
}
page.multiStorageEdit = true;
page.showAddEditMultiStorageModal = true;
}
function saveMultiConnect() {
if (page.multiStorageEdit) {
for (let i = 0; i < page.new.Settings.MultiStorageConnects.length; i++) {
const el = page.new.Settings.MultiStorageConnects[i];
if (el.AccessKey === page.addEditMultistorageConnect.AccessKey) {
page.new.Settings.MultiStorageConnects[i] =
page.addEditMultistorageConnect;
break;
}
}
} else {
let allAccessKeys = [...page.new.Settings.AccessKey];
for (let i = 0; i < page.new.Settings.MultiStorageConnects.length; i++) {
allAccessKeys.push(page.new.Settings.MultiStorageConnects[i].AccessKey);
}
for (let i = 0; i < allAccessKeys.length; i++) {
if (allAccessKeys[i] === page.addEditMultistorageConnect.AccessKey) {
notifyError("Key must be unique");
return;
}
}
page.new.Settings.MultiStorageConnects.push(
page.addEditMultistorageConnect
);
}
page.showAddEditMultiStorageModal = false;
page.renderMulti = false;
nextTick(() => {
page.renderMulti = true;
});
}
async function showAddMultiStorageConnect() {
page.addEditMultistorageConnect = getEmptyMultiStorageConnect();
const res = await apiFetch(
`/admin/domains/${page.domain}/mailstorage/settings/new?type=${page.new.Type}`
);
if (res.error) {
notifyError(res.error);
return;
}
page.addEditMultistorageConnect.Host = res.data.Settings.Host;
page.addEditMultistorageConnect.Port = res.data.Settings.Port;
page.addEditMultistorageConnect.User = res.data.Settings.User;
page.addEditMultistorageConnect.Pass = res.data.Settings.Pass;
page.addEditMultistorageConnect.MaxConn = res.data.Settings.MaxConn;
page.addEditMultistorageConnect.MboxesDb = res.data.Settings.MboxesDb;
page.addEditMultistorageConnect.MailboxDistributionPattern =
res.data.Settings.MailboxDistributionPattern;
page.addEditMultistorageConnect.AccessKey = res.data.Settings.AccessKey;
page.multiStorageEdit = false;
page.showAddEditMultiStorageModal = true;
}
async function showEditAccessKey(old: string) {
page.editAccessKey.old = old;
page.editAccessKey.new = old;
page.editAccessKey.show = true;
}
async function saveNewAccessKey() {
page.editAccessKey.loading = true;
const res = await apiFetch(
`/admin/domains/${page.domain}/mailstorage/settings/change_access_key`,
{
method: "POST",
body: {
old: page.editAccessKey.old,
new: page.editAccessKey.new,
},
}
);
page.editAccessKey.loading = false;
if (res.error) {
notifyError(res.error);
return;
}
page.editAccessKey.show = false;
notifySuccess(
t("components.admin.domains.mailstorage.settings.save_success")
);
router.go(0);
return;
}
// function generateNewAccessKey(): string {
// let key = "";
// while (key === "") {
// key = genRandomString(8);
// console.log(page.new.Settings.MultiStorageConnects);
// page.new.Settings.MultiStorageConnects.forEach((msc) => {
// if (msc.AccessKey === key) {
// key = "";
// }
// });
// }
// return key;
// }
</script>
<style scoped>
.panel-content {
min-width: 600px;
max-width: 60%;
margin: auto;
}
.input-number {
width: 50%;
}
.save-button {
}
</style>