mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-05 23:47:50 +08:00
feat: admin cleanup tab && admin sendbox tab (#126)
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
name: Deploy Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "vitepress-docs/**"
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
name: Install pnpm
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Deploy Docs for ${{github.ref_name}}
|
||||||
|
run: |
|
||||||
|
cd vitepress-docs/
|
||||||
|
pnpm install --no-frozen-lockfile
|
||||||
|
if [[ ${{github.ref}} == refs/tags/* ]]; then
|
||||||
|
export TAG_NAME=${{github.ref_name}}
|
||||||
|
else
|
||||||
|
export TAG_NAME=$(git describe --tags --abbrev=0)
|
||||||
|
fi
|
||||||
|
echo "Deploying docs for tag $TAG_NAME"
|
||||||
|
pnpm run deploy
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
@@ -30,9 +30,12 @@ export const useGlobalState = createGlobalState(
|
|||||||
const auth = useStorage('auth', '');
|
const auth = useStorage('auth', '');
|
||||||
const adminAuth = useStorage('adminAuth', '');
|
const adminAuth = useStorage('adminAuth', '');
|
||||||
const jwt = useStorage('jwt', '');
|
const jwt = useStorage('jwt', '');
|
||||||
const localeCache = useStorage('locale', 'zhCN');
|
const localeCache = useStorage('locale', 'zh');
|
||||||
const themeSwitch = useStorage('themeSwitch', false);
|
const themeSwitch = useStorage('themeSwitch', false);
|
||||||
const showLogin = ref(false);
|
const showLogin = ref(false);
|
||||||
|
const adminTab = ref("account");
|
||||||
|
const adminMailTabAddress = ref("");
|
||||||
|
const adminSendBoxTabAddress = ref("");
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
settings,
|
settings,
|
||||||
@@ -45,6 +48,9 @@ export const useGlobalState = createGlobalState(
|
|||||||
adminAuth,
|
adminAuth,
|
||||||
showAdminAuth,
|
showAdminAuth,
|
||||||
showLogin,
|
showLogin,
|
||||||
|
adminTab,
|
||||||
|
adminMailTabAddress,
|
||||||
|
adminSendBoxTabAddress,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+30
-338
@@ -1,22 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, h, onMounted, watch } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { User, UserCheck, MailBulk } from '@vicons/fa'
|
|
||||||
|
|
||||||
import { useGlobalState } from '../store'
|
import { useGlobalState } from '../store'
|
||||||
import { api } from '../api'
|
|
||||||
import { processItem } from '../utils/email-parser'
|
|
||||||
import SenderAccess from './admin/SenderAccess.vue'
|
import SenderAccess from './admin/SenderAccess.vue'
|
||||||
|
import Statistics from "./admin/Statistics.vue"
|
||||||
|
import SendBox from './admin/SendBox.vue';
|
||||||
|
import Account from './admin/Account.vue';
|
||||||
|
import Mails from './admin/Mails.vue';
|
||||||
|
import MailsUnknow from './admin/MailsUnknow.vue';
|
||||||
|
import Maintenance from './admin/Maintenance.vue';
|
||||||
|
|
||||||
const { localeCache, adminAuth, showAdminAuth } = useGlobalState()
|
const {
|
||||||
const router = useRouter()
|
localeCache, adminAuth, showAdminAuth, adminTab
|
||||||
|
} = useGlobalState()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const showEmailPassword = ref(false)
|
|
||||||
const curEmailPassword = ref("")
|
|
||||||
const addressQuery = ref("")
|
|
||||||
|
|
||||||
const authFunc = async () => {
|
const authFunc = async () => {
|
||||||
try {
|
try {
|
||||||
location.reload()
|
location.reload()
|
||||||
@@ -29,243 +29,34 @@ const { t } = useI18n({
|
|||||||
locale: localeCache.value || 'zh',
|
locale: localeCache.value || 'zh',
|
||||||
messages: {
|
messages: {
|
||||||
en: {
|
en: {
|
||||||
title: 'Temp Email Admin',
|
|
||||||
auth: 'Admin Auth',
|
auth: 'Admin Auth',
|
||||||
home: 'Home',
|
|
||||||
authTip: 'Please enter the correct auth code',
|
authTip: 'Please enter the correct auth code',
|
||||||
name: 'Name',
|
|
||||||
created_at: 'Created At',
|
|
||||||
showPass: 'Show Passwrod',
|
|
||||||
password: 'Password',
|
|
||||||
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
|
||||||
delete: 'Delete',
|
|
||||||
deleteTip: 'Are you sure to delete this email?',
|
|
||||||
refresh: 'Refresh',
|
|
||||||
mails: 'Emails',
|
mails: 'Emails',
|
||||||
itemCount: 'itemCount',
|
|
||||||
query: 'Query',
|
|
||||||
userCount: 'User Count',
|
|
||||||
activeUser: '7 days Active User',
|
|
||||||
mailCount: 'Mail Count',
|
|
||||||
account: 'Account',
|
account: 'Account',
|
||||||
unknow: 'Unknow',
|
unknow: 'Mails with unknow receiver',
|
||||||
addressQueryTip: 'Leave blank to query all addresses',
|
|
||||||
senderAccess: 'Sender Access Control',
|
senderAccess: 'Sender Access Control',
|
||||||
|
sendBox: 'Send Box',
|
||||||
|
maintenance: 'Maintenance',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
title: '临时邮件 Admin',
|
|
||||||
auth: 'Admin 授权',
|
auth: 'Admin 授权',
|
||||||
home: '首页',
|
|
||||||
authTip: '请输入正确的授权码',
|
authTip: '请输入正确的授权码',
|
||||||
name: '名称',
|
|
||||||
created_at: '创建时间',
|
|
||||||
showPass: '显示密码',
|
|
||||||
password: '密码',
|
|
||||||
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
|
||||||
delete: '删除',
|
|
||||||
deleteTip: '确定要删除这个邮箱吗?',
|
|
||||||
refresh: '刷新',
|
|
||||||
mails: '邮件',
|
mails: '邮件',
|
||||||
itemCount: '总数',
|
|
||||||
query: '查询',
|
|
||||||
userCount: '用户总数',
|
|
||||||
activeUser: '周活跃用户',
|
|
||||||
mailCount: '邮件总数',
|
|
||||||
account: '账号',
|
account: '账号',
|
||||||
unknow: '未知',
|
unknow: '无收件人邮件',
|
||||||
addressQueryTip: '留空查询所有地址',
|
|
||||||
senderAccess: '发件权限控制',
|
senderAccess: '发件权限控制',
|
||||||
|
sendBox: '发件箱',
|
||||||
|
maintenance: '维护',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const data = ref([])
|
|
||||||
const count = ref(0)
|
|
||||||
const page = ref(1)
|
|
||||||
const pageSize = ref(20)
|
|
||||||
|
|
||||||
const showPassword = async (id) => {
|
|
||||||
try {
|
|
||||||
curEmailPassword.value = await api.adminShowPassword(id)
|
|
||||||
showEmailPassword.value = true
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error.message || "error");
|
|
||||||
showEmailPassword.value = false
|
|
||||||
curEmailPassword.value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteEmail = async (id) => {
|
|
||||||
try {
|
|
||||||
await api.adminDeleteAddress(id)
|
|
||||||
message.success("success");
|
|
||||||
await fetchData()
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const { results, count: addressCount } = await api.fetch(
|
|
||||||
`/admin/address`
|
|
||||||
+ `?limit=${pageSize.value}`
|
|
||||||
+ `&offset=${(page.value - 1) * pageSize.value}`
|
|
||||||
+ (addressQuery.value ? `&query=${addressQuery.value}` : "")
|
|
||||||
);
|
|
||||||
data.value = results;
|
|
||||||
if (addressCount > 0) {
|
|
||||||
count.value = addressCount;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: "ID",
|
|
||||||
key: "id"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('name'),
|
|
||||||
key: "name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('created_at'),
|
|
||||||
key: "created_at"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Action',
|
|
||||||
key: 'actions',
|
|
||||||
render(row) {
|
|
||||||
return h('div', [
|
|
||||||
h(NButton,
|
|
||||||
{
|
|
||||||
type: 'success',
|
|
||||||
ghost: true,
|
|
||||||
onClick: () => showPassword(row.id)
|
|
||||||
},
|
|
||||||
{ default: () => t('showPass') }
|
|
||||||
),
|
|
||||||
h(NButton,
|
|
||||||
{
|
|
||||||
type: 'success',
|
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
|
||||||
mailAddress.value = row.name
|
|
||||||
tab.value = "mails"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ default: () => t('mails') }
|
|
||||||
),
|
|
||||||
h(NPopconfirm,
|
|
||||||
{
|
|
||||||
onPositiveClick: () => deleteEmail(row.id)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
trigger: () => h(NButton, { type: "error" }, () => t('delete')),
|
|
||||||
default: () => t('deleteTip')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
watch([page, pageSize], async () => {
|
|
||||||
await fetchData()
|
|
||||||
})
|
|
||||||
|
|
||||||
const statistics = ref({
|
|
||||||
userCount: 0,
|
|
||||||
mailCount: 0,
|
|
||||||
activeUserCount7days: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchStatistics = async () => {
|
|
||||||
try {
|
|
||||||
const { userCount, activeUserCount7days, mailCount } = await api.fetch(`/admin/statistics`);
|
|
||||||
statistics.value.mailCount = mailCount || 0;
|
|
||||||
statistics.value.userCount = userCount || 0;
|
|
||||||
statistics.value.activeUserCount7days = activeUserCount7days || 0;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!adminAuth.value) {
|
if (!adminAuth.value) {
|
||||||
showAdminAuth.value = true
|
showAdminAuth.value = true;
|
||||||
} else {
|
return;
|
||||||
await fetchData()
|
|
||||||
await fetchStatistics()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const tab = ref("account")
|
|
||||||
const mailAddress = ref("")
|
|
||||||
const mailData = ref([])
|
|
||||||
const mailCount = ref(0)
|
|
||||||
const mailPage = ref(1)
|
|
||||||
const mailPageSize = ref(20)
|
|
||||||
|
|
||||||
watch([mailPage, mailPageSize, mailAddress], async () => {
|
|
||||||
await fetchMailData()
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchMailData = async () => {
|
|
||||||
if (!mailAddress.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const { results, count } = await api.fetch(
|
|
||||||
`/admin/mails`
|
|
||||||
+ `?address=${mailAddress.value}`
|
|
||||||
+ `&limit=${mailPageSize.value}`
|
|
||||||
+ `&offset=${(mailPage.value - 1) * mailPageSize.value}`
|
|
||||||
);
|
|
||||||
mailData.value = await Promise.all(results.map(async (item) => {
|
|
||||||
return await processItem(item);
|
|
||||||
}));
|
|
||||||
if (count > 0) {
|
|
||||||
mailCount.value = count;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const mailUnknowData = ref([])
|
|
||||||
const mailUnknowCount = ref(0)
|
|
||||||
const mailUnknowPage = ref(1)
|
|
||||||
const mailUnknowPageSize = ref(20)
|
|
||||||
|
|
||||||
watch([mailUnknowPage, mailUnknowPageSize], async () => {
|
|
||||||
await fetchMailUnknowData()
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchMailUnknowData = async () => {
|
|
||||||
try {
|
|
||||||
const { results, count } = await api.fetch(
|
|
||||||
`/admin/mails_unknow`
|
|
||||||
+ `?limit=${mailPageSize.value}`
|
|
||||||
+ `&offset=${(mailPage.value - 1) * mailPageSize.value}`
|
|
||||||
);
|
|
||||||
mailUnknowData.value = await Promise.all(results.map(async (item) => {
|
|
||||||
return await processItem(item);
|
|
||||||
}));
|
|
||||||
if (count > 0) {
|
|
||||||
mailUnknowCount.value = count;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -283,125 +74,26 @@ const fetchMailUnknowData = async () => {
|
|||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
<n-modal v-model:show="showEmailPassword" preset="dialog" title="Dialog">
|
<Statistics />
|
||||||
<template #header>
|
<n-tabs type="card" v-model:value="adminTab">
|
||||||
<div>{{ t("password") }}</div>
|
|
||||||
</template>
|
|
||||||
<span>
|
|
||||||
<p>{{ t("passwordTip") }}</p>
|
|
||||||
</span>
|
|
||||||
<n-card>
|
|
||||||
<b>{{ curEmailPassword }}</b>
|
|
||||||
</n-card>
|
|
||||||
<template #action>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
<n-row>
|
|
||||||
<n-col :span="8">
|
|
||||||
<n-statistic :label="t('userCount')" :value="statistics.userCount">
|
|
||||||
<template #prefix>
|
|
||||||
<n-icon :component="User" />
|
|
||||||
</template>
|
|
||||||
</n-statistic>
|
|
||||||
</n-col>
|
|
||||||
<n-col :span="8">
|
|
||||||
<n-statistic :label="t('activeUser')" :value="statistics.activeUserCount7days">
|
|
||||||
<template #prefix>
|
|
||||||
<n-icon :component="UserCheck" />
|
|
||||||
</template>
|
|
||||||
</n-statistic>
|
|
||||||
</n-col>
|
|
||||||
<n-col :span="8">
|
|
||||||
<n-statistic :label="t('mailCount')" :value="statistics.mailCount">
|
|
||||||
<template #prefix>
|
|
||||||
<n-icon :component="MailBulk" />
|
|
||||||
</template>
|
|
||||||
</n-statistic>
|
|
||||||
</n-col>
|
|
||||||
</n-row>
|
|
||||||
<n-tabs type="segment" v-model:value="tab">
|
|
||||||
<n-tab-pane name="account" :tab="t('account')">
|
<n-tab-pane name="account" :tab="t('account')">
|
||||||
<n-input-group>
|
<Account />
|
||||||
<n-input v-model:value="addressQuery" clearable :placeholder="t('addressQueryTip')" />
|
|
||||||
<n-button @click="fetchData" type="primary" ghost>
|
|
||||||
{{ t('query') }}
|
|
||||||
</n-button>
|
|
||||||
</n-input-group>
|
|
||||||
<div style="display: inline-block;">
|
|
||||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" :page-sizes="[20, 50, 100]"
|
|
||||||
show-size-picker>
|
|
||||||
<template #prefix="{ itemCount }">
|
|
||||||
{{ t('itemCount') }}: {{ itemCount }}
|
|
||||||
</template>
|
|
||||||
</n-pagination>
|
|
||||||
</div>
|
|
||||||
<n-data-table :columns="columns" :data="data" :bordered="false" />
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="mails" :tab="t('mails')">
|
<n-tab-pane name="mails" :tab="t('mails')">
|
||||||
<n-input-group>
|
<Mails />
|
||||||
<n-input v-model:value="mailAddress" />
|
|
||||||
<n-button @click="fetchMailData" type="primary" ghost>
|
|
||||||
{{ t('query') }}
|
|
||||||
</n-button>
|
|
||||||
</n-input-group>
|
|
||||||
<n-list hoverable clickable>
|
|
||||||
<div style="display: inline-block; margin-bottom: 10px;">
|
|
||||||
<n-pagination v-model:page="mailPage" v-model:page-size="mailPageSize" :item-count="mailCount" simple>
|
|
||||||
<template #prefix="{ itemCount }">
|
|
||||||
{{ t('itemCount') }}: {{ itemCount }}
|
|
||||||
</template>
|
|
||||||
</n-pagination>
|
|
||||||
</div>
|
|
||||||
<n-list-item v-for="row in mailData" v-bind:key="row.id">
|
|
||||||
<n-thing class="center" :title="row.subject">
|
|
||||||
<template #description>
|
|
||||||
<n-space>
|
|
||||||
<n-tag type="info">
|
|
||||||
FROM: {{ row.source }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
ID: {{ row.id }}
|
|
||||||
</n-tag>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
<div v-html="row.message"></div>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="unknow" :tab="t('unknow')">
|
<n-tab-pane name="unknow" :tab="t('unknow')">
|
||||||
<n-button @click="fetchMailUnknowData" type="primary" ghost>
|
<MailsUnknow />
|
||||||
{{ t('query') }}
|
|
||||||
</n-button>
|
|
||||||
<n-list hoverable clickable>
|
|
||||||
<div style="display: inline-block; margin-bottom: 10px;">
|
|
||||||
<n-pagination v-model:page="mailUnknowPage" v-model:page-size="mailUnknowPageSize"
|
|
||||||
:item-count="mailUnknowCount" simple>
|
|
||||||
<template #prefix="{ itemCount }">
|
|
||||||
{{ t('itemCount') }}: {{ itemCount }}
|
|
||||||
</template>
|
|
||||||
</n-pagination>
|
|
||||||
</div>
|
|
||||||
<n-list-item v-for="row in mailUnknowData" v-bind:key="row.id">
|
|
||||||
<n-thing class="center" :title="row.subject">
|
|
||||||
<template #description>
|
|
||||||
<n-space>
|
|
||||||
<n-tag type="info">
|
|
||||||
FROM: {{ row.source }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="info">
|
|
||||||
ID: {{ row.id }}
|
|
||||||
</n-tag>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
<div v-html="row.message"></div>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="senderAccess" :tab="t('senderAccess')">
|
<n-tab-pane name="senderAccess" :tab="t('senderAccess')">
|
||||||
<SenderAccess />
|
<SenderAccess />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="sendBox" :tab="t('sendBox')">
|
||||||
|
<SendBox />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="maintenance" :tab="t('maintenance')">
|
||||||
|
<Maintenance />
|
||||||
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ const menuOptions = computed(() => [
|
|||||||
label: () => h(
|
label: () => h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
tertiary: true,
|
bordered: false,
|
||||||
ghost: true,
|
ghost: true,
|
||||||
size: "small",
|
size: "small",
|
||||||
onClick: () => router.push('/sendbox')
|
onClick: () => router.push('/sendbox')
|
||||||
@@ -197,7 +197,7 @@ const menuOptions = computed(() => [
|
|||||||
label: () => h(
|
label: () => h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
tertiary: true,
|
bordered: false,
|
||||||
ghost: true,
|
ghost: true,
|
||||||
size: "small",
|
size: "small",
|
||||||
onClick: () => { showPassword.value = true }
|
onClick: () => { showPassword.value = true }
|
||||||
@@ -210,7 +210,7 @@ const menuOptions = computed(() => [
|
|||||||
label: () => h(
|
label: () => h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
tertiary: true,
|
bordered: false,
|
||||||
ghost: true,
|
ghost: true,
|
||||||
size: "small",
|
size: "small",
|
||||||
onClick: () => { router.push('/settings') }
|
onClick: () => { router.push('/settings') }
|
||||||
@@ -223,7 +223,7 @@ const menuOptions = computed(() => [
|
|||||||
label: () => h(
|
label: () => h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
tertiary: true,
|
bordered: false,
|
||||||
ghost: true,
|
ghost: true,
|
||||||
size: "small",
|
size: "small",
|
||||||
onClick: () => { showLogout.value = true }
|
onClick: () => { showLogout.value = true }
|
||||||
@@ -236,7 +236,7 @@ const menuOptions = computed(() => [
|
|||||||
label: () => h(
|
label: () => h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
tertiary: true,
|
bordered: false,
|
||||||
ghost: true,
|
ghost: true,
|
||||||
size: "small",
|
size: "small",
|
||||||
onClick: () => { showDelteAccount.value = true }
|
onClick: () => { showDelteAccount.value = true }
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, h, onMounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
import { api } from '../../api'
|
||||||
|
import { NMenu } from 'naive-ui';
|
||||||
|
import { MenuFilled } from '@vicons/material'
|
||||||
|
|
||||||
|
const {
|
||||||
|
localeCache, adminAuth, showAdminAuth,
|
||||||
|
adminTab, adminMailTabAddress, adminSendBoxTabAddress
|
||||||
|
} = useGlobalState()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
name: 'Name',
|
||||||
|
created_at: 'Created At',
|
||||||
|
showPass: 'Show Passwrod',
|
||||||
|
password: 'Password',
|
||||||
|
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
||||||
|
delete: 'Delete',
|
||||||
|
deleteTip: 'Are you sure to delete this email?',
|
||||||
|
delteAccount: 'Delete Account',
|
||||||
|
viewMails: 'View Mails',
|
||||||
|
viewSendBox: 'View SendBox',
|
||||||
|
itemCount: 'itemCount',
|
||||||
|
query: 'Query',
|
||||||
|
addressQueryTip: 'Leave blank to query all addresses',
|
||||||
|
actions: 'Actions'
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
name: '名称',
|
||||||
|
created_at: '创建时间',
|
||||||
|
showPass: '显示密码',
|
||||||
|
password: '密码',
|
||||||
|
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
||||||
|
delete: '删除',
|
||||||
|
deleteTip: '确定要删除这个邮箱吗?',
|
||||||
|
delteAccount: '删除邮箱',
|
||||||
|
viewMails: '查看邮件',
|
||||||
|
viewSendBox: '查看发件箱',
|
||||||
|
itemCount: '总数',
|
||||||
|
query: '查询',
|
||||||
|
addressQueryTip: '留空查询所有地址',
|
||||||
|
actions: '操作',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const showEmailPassword = ref(false)
|
||||||
|
const curEmailPassword = ref("")
|
||||||
|
const curDeleteAddressId = ref(0);
|
||||||
|
|
||||||
|
const addressQuery = ref("")
|
||||||
|
|
||||||
|
const data = ref([])
|
||||||
|
const count = ref(0)
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
const showDelteAccount = ref(false)
|
||||||
|
|
||||||
|
const showPassword = async (id) => {
|
||||||
|
try {
|
||||||
|
curEmailPassword.value = await api.adminShowPassword(id)
|
||||||
|
showEmailPassword.value = true
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
showEmailPassword.value = false
|
||||||
|
curEmailPassword.value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteEmail = async () => {
|
||||||
|
try {
|
||||||
|
await api.adminDeleteAddress(curDeleteAddressId.value)
|
||||||
|
message.success("success");
|
||||||
|
await fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const { results, count: addressCount } = await api.fetch(
|
||||||
|
`/admin/address`
|
||||||
|
+ `?limit=${pageSize.value}`
|
||||||
|
+ `&offset=${(page.value - 1) * pageSize.value}`
|
||||||
|
+ (addressQuery.value ? `&query=${addressQuery.value}` : "")
|
||||||
|
);
|
||||||
|
data.value = results;
|
||||||
|
if (addressCount > 0) {
|
||||||
|
count.value = addressCount;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "ID",
|
||||||
|
key: "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('name'),
|
||||||
|
key: "name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('created_at'),
|
||||||
|
key: "created_at"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('actions'),
|
||||||
|
key: 'actions',
|
||||||
|
render(row) {
|
||||||
|
return h('div', [
|
||||||
|
h(NMenu, {
|
||||||
|
mode: "horizontal",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('actions'),
|
||||||
|
icon: () => h(MenuFilled),
|
||||||
|
key: "action",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: () => h(NButton,
|
||||||
|
{
|
||||||
|
bordered: false,
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => showPassword(row.id)
|
||||||
|
},
|
||||||
|
{ default: () => t('showPass') }
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => h(NButton,
|
||||||
|
{
|
||||||
|
bordered: false,
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
adminMailTabAddress.value = row.name;
|
||||||
|
adminTab.value = "mails";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => t('viewMails') }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => h(NButton,
|
||||||
|
{
|
||||||
|
bordered: false,
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
adminSendBoxTabAddress.value = row.name;
|
||||||
|
adminTab.value = "sendBox";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => t('viewSendBox') }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => h(NButton,
|
||||||
|
{
|
||||||
|
bordered: false,
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
curDeleteAddressId.value = row.id;
|
||||||
|
showDelteAccount.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => t('delete') }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
watch([page, pageSize], async () => {
|
||||||
|
await fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!adminAuth.value) {
|
||||||
|
showAdminAuth.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-modal v-model:show="showEmailPassword" preset="dialog" title="Dialog">
|
||||||
|
<template #header>
|
||||||
|
<div>{{ t("password") }}</div>
|
||||||
|
</template>
|
||||||
|
<span>
|
||||||
|
<p>{{ t("passwordTip") }}</p>
|
||||||
|
</span>
|
||||||
|
<n-card>
|
||||||
|
<b>{{ curEmailPassword }}</b>
|
||||||
|
</n-card>
|
||||||
|
<template #action>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
<n-modal v-model:show="showDelteAccount" preset="dialog" title="Dialog">
|
||||||
|
<p>{{ t('deleteTip') }}</p>
|
||||||
|
<template #action>
|
||||||
|
<n-button @click="deleteEmail" size="small" tertiary round type="error">
|
||||||
|
{{ t('delteAccount') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
<n-input-group>
|
||||||
|
<n-input v-model:value="addressQuery" clearable :placeholder="t('addressQueryTip')" />
|
||||||
|
<n-button @click="fetchData" type="primary" ghost>
|
||||||
|
{{ t('query') }}
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count"
|
||||||
|
:page-sizes="[20, 50, 100]" show-size-picker>
|
||||||
|
<template #prefix="{ itemCount }">
|
||||||
|
{{ t('itemCount') }}: {{ itemCount }}
|
||||||
|
</template>
|
||||||
|
</n-pagination>
|
||||||
|
</div>
|
||||||
|
<n-data-table :columns="columns" :data="data" :bordered="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-pagination {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
import { api } from '../../api'
|
||||||
|
import { processItem } from '../../utils/email-parser'
|
||||||
|
|
||||||
|
const {
|
||||||
|
localeCache, adminAuth, showAdminAuth,
|
||||||
|
adminMailTabAddress
|
||||||
|
} = useGlobalState()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
mails: 'Emails',
|
||||||
|
itemCount: 'itemCount',
|
||||||
|
query: 'Query',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
mails: '邮件',
|
||||||
|
itemCount: '总数',
|
||||||
|
query: '查询',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mailData = ref([])
|
||||||
|
const mailCount = ref(0)
|
||||||
|
const mailPage = ref(1)
|
||||||
|
const mailPageSize = ref(20)
|
||||||
|
|
||||||
|
watch([mailPage, mailPageSize, adminMailTabAddress], async () => {
|
||||||
|
await fetchMailData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchMailData = async () => {
|
||||||
|
if (!adminMailTabAddress.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { results, count } = await api.fetch(
|
||||||
|
`/admin/mails`
|
||||||
|
+ `?address=${adminMailTabAddress.value}`
|
||||||
|
+ `&limit=${mailPageSize.value}`
|
||||||
|
+ `&offset=${(mailPage.value - 1) * mailPageSize.value}`
|
||||||
|
);
|
||||||
|
mailData.value = await Promise.all(results.map(async (item) => {
|
||||||
|
return await processItem(item);
|
||||||
|
}));
|
||||||
|
if (count > 0) {
|
||||||
|
mailCount.value = count;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!adminAuth.value) {
|
||||||
|
showAdminAuth.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchMailData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-input-group>
|
||||||
|
<n-input v-model:value="adminMailTabAddress" />
|
||||||
|
<n-button @click="fetchMailData" type="primary" ghost>
|
||||||
|
{{ t('query') }}
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
<n-list hoverable clickable>
|
||||||
|
<div style="display: inline-block; margin-bottom: 10px;">
|
||||||
|
<n-pagination v-model:page="mailPage" v-model:page-size="mailPageSize" :item-count="mailCount" simple>
|
||||||
|
<template #prefix="{ itemCount }">
|
||||||
|
{{ t('itemCount') }}: {{ itemCount }}
|
||||||
|
</template>
|
||||||
|
</n-pagination>
|
||||||
|
</div>
|
||||||
|
<n-list-item v-for="row in mailData" v-bind:key="row.id">
|
||||||
|
<n-thing class="center" :title="row.subject">
|
||||||
|
<template #description>
|
||||||
|
<n-space>
|
||||||
|
<n-tag type="info">
|
||||||
|
FROM: {{ row.source }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
ID: {{ row.id }}
|
||||||
|
</n-tag>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
<div v-html="row.message"></div>
|
||||||
|
</n-thing>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-pagination {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
import { api } from '../../api'
|
||||||
|
import { processItem } from '../../utils/email-parser'
|
||||||
|
|
||||||
|
const {
|
||||||
|
localeCache, adminAuth, showAdminAuth
|
||||||
|
} = useGlobalState()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
itemCount: 'itemCount',
|
||||||
|
refresh: 'Refresh'
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
itemCount: '总数',
|
||||||
|
refresh: '刷新'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mailUnknowData = ref([])
|
||||||
|
const mailUnknowCount = ref(0)
|
||||||
|
const mailUnknowPage = ref(1)
|
||||||
|
const mailUnknowPageSize = ref(20)
|
||||||
|
|
||||||
|
watch([mailUnknowPage, mailUnknowPageSize], async () => {
|
||||||
|
await fetchMailUnknowData()
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchMailUnknowData = async () => {
|
||||||
|
try {
|
||||||
|
const { results, count } = await api.fetch(
|
||||||
|
`/admin/mails_unknow`
|
||||||
|
+ `?limit=${mailUnknowPageSize.value}`
|
||||||
|
+ `&offset=${(mailUnknowPage.value - 1) * mailUnknowPage.value}`
|
||||||
|
);
|
||||||
|
mailUnknowData.value = await Promise.all(results.map(async (item) => {
|
||||||
|
return await processItem(item);
|
||||||
|
}));
|
||||||
|
if (count > 0) {
|
||||||
|
mailUnknowCount.value = count;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!adminAuth.value) {
|
||||||
|
showAdminAuth.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchMailUnknowData();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-button @click="fetchMailUnknowData" type="primary" ghost>
|
||||||
|
{{ t('refresh') }}
|
||||||
|
</n-button>
|
||||||
|
<n-list hoverable clickable>
|
||||||
|
<div style="display: inline-block; margin-bottom: 10px;">
|
||||||
|
<n-pagination v-model:page="mailUnknowPage" v-model:page-size="mailUnknowPageSize"
|
||||||
|
:item-count="mailUnknowCount" simple>
|
||||||
|
<template #prefix="{ itemCount }">
|
||||||
|
{{ t('itemCount') }}: {{ itemCount }}
|
||||||
|
</template>
|
||||||
|
</n-pagination>
|
||||||
|
</div>
|
||||||
|
<n-list-item v-for="row in mailUnknowData" v-bind:key="row.id">
|
||||||
|
<n-thing class="center" :title="row.subject">
|
||||||
|
<template #description>
|
||||||
|
<n-space>
|
||||||
|
<n-tag type="info">
|
||||||
|
FROM: {{ row.source }}
|
||||||
|
</n-tag>
|
||||||
|
<n-tag type="info">
|
||||||
|
ID: {{ row.id }}
|
||||||
|
</n-tag>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
<div v-html="row.message"></div>
|
||||||
|
</n-thing>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-pagination {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, h, onMounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { CleaningServicesFilled } from '@vicons/material'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
import { api } from '../../api'
|
||||||
|
|
||||||
|
const { localeCache, adminAuth, showAdminAuth } = useGlobalState()
|
||||||
|
const message = useMessage()
|
||||||
|
const cleanMailsDays = ref(30)
|
||||||
|
const cleanUnknowMailsDays = ref(30)
|
||||||
|
const cleanAddressDays = ref(30)
|
||||||
|
const cleanSendBoxDays = ref(30)
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
tip: 'Please input the cleanup days',
|
||||||
|
mailBoxTip: "Clean up {day} days ago mailbox",
|
||||||
|
mailUnknowTip: "Clean up {day} days ago mails with unknow receiver",
|
||||||
|
addressUnActiveTip: "Clean up {day} days ago unactive address",
|
||||||
|
sendBoxTip: "Clean up {day} days ago sendbox",
|
||||||
|
cleanupSuccess: "Cleanup success",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
tip: '请输入清理天数',
|
||||||
|
mailBoxTip: "清理{day}天前的收件箱",
|
||||||
|
mailUnknowTip: "清理{day}天前的无收件人邮件",
|
||||||
|
addressUnActiveTip: "清理{day}天前的未活动地址",
|
||||||
|
sendBoxTip: "清理{day}天前的发件箱",
|
||||||
|
cleanupSuccess: "清理成功",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanup = async (cleanType, cleanDays) => {
|
||||||
|
try {
|
||||||
|
await api.fetch('/admin/cleanup', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ cleanType, cleanDays })
|
||||||
|
});
|
||||||
|
message.success(t('cleanupSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!adminAuth.value) {
|
||||||
|
showAdminAuth.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="center">
|
||||||
|
<n-card>
|
||||||
|
<div class="item">
|
||||||
|
<n-input-number v-model:value="cleanMailsDays" :placeholder="t('tip')" />
|
||||||
|
<n-button @click="cleanup('mails', cleanMailsDays)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="CleaningServicesFilled" />
|
||||||
|
</template>
|
||||||
|
{{ t('mailBoxTip', { day: cleanMailsDays }) }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<n-input-number v-model:value="cleanUnknowMailsDays" :placeholder="t('tip')" />
|
||||||
|
<n-button @click="cleanup('mails_unknow', cleanUnknowMailsDays)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="CleaningServicesFilled" />
|
||||||
|
</template>
|
||||||
|
{{ t('mailUnknowTip', { day: cleanUnknowMailsDays }) }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<n-input-number v-model:value="cleanAddressDays" :placeholder="t('tip')" />
|
||||||
|
<n-button @click="cleanup('address', cleanAddressDays)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="CleaningServicesFilled" />
|
||||||
|
</template>
|
||||||
|
{{ t('addressUnActiveTip', { day: cleanAddressDays }) }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<n-input-number v-model:value="cleanSendBoxDays" :placeholder="t('tip')" />
|
||||||
|
<n-button @click="cleanup('sendbox', cleanSendBoxDays)">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon :component="CleaningServicesFilled" />
|
||||||
|
</template>
|
||||||
|
{{ t('sendBoxTip', { day: cleanSendBoxDays }) }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-card {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
place-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, h, onMounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
import { api } from '../../api'
|
||||||
|
|
||||||
|
const { localeCache, settings, adminAuth, adminSendBoxTabAddress } = useGlobalState()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
address: 'Address',
|
||||||
|
success: 'Success',
|
||||||
|
to_mail: 'To Mail',
|
||||||
|
subject: 'Subject',
|
||||||
|
created_at: 'Created At',
|
||||||
|
action: 'Action',
|
||||||
|
query: 'Query',
|
||||||
|
itemCount: 'itemCount',
|
||||||
|
view: 'View',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
address: '地址',
|
||||||
|
success: '成功',
|
||||||
|
to_mail: '收件人邮箱',
|
||||||
|
subject: '主题',
|
||||||
|
created_at: '创建时间',
|
||||||
|
action: '操作',
|
||||||
|
query: '查询',
|
||||||
|
itemCount: '总数',
|
||||||
|
view: '查看',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = ref([])
|
||||||
|
const count = ref(0)
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
const curRow = ref({})
|
||||||
|
const showModal = ref(false)
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!adminSendBoxTabAddress.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { results, count: addressCount } = await api.fetch(
|
||||||
|
`/admin/sendbox`
|
||||||
|
+ `?address=${adminSendBoxTabAddress.value}`
|
||||||
|
+ `&limit=${pageSize.value}`
|
||||||
|
+ `&offset=${(page.value - 1) * pageSize.value}`
|
||||||
|
);
|
||||||
|
data.value = results.map((item) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(item.raw);
|
||||||
|
item.to_mail = data?.personalizations?.map(
|
||||||
|
(p) => p.to?.map((t) => t.email).join(',')
|
||||||
|
).join(';');
|
||||||
|
item.subject = data.subject;
|
||||||
|
item.raw = JSON.stringify(data, null, 2);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
if (addressCount > 0) {
|
||||||
|
count.value = addressCount;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "ID",
|
||||||
|
key: "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('address'),
|
||||||
|
key: "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('to_mail'),
|
||||||
|
key: "to_mail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('subject'),
|
||||||
|
key: "subject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('created_at'),
|
||||||
|
key: "created_at"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('action'),
|
||||||
|
key: 'actions',
|
||||||
|
render(row) {
|
||||||
|
return h('div', [
|
||||||
|
h(NButton,
|
||||||
|
{
|
||||||
|
type: 'success',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
showModal.value = true;
|
||||||
|
curRow.value = row;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ default: () => t('view') }
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
watch([page, pageSize], async () => {
|
||||||
|
await fetchData()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!adminAuth.value) {
|
||||||
|
showAdminAuth.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="settings.address">
|
||||||
|
<n-modal v-model:show="showModal" preset="dialog">
|
||||||
|
<pre>{{ curRow.raw }}</pre>
|
||||||
|
</n-modal>
|
||||||
|
<n-input-group>
|
||||||
|
<n-input v-model:value="adminSendBoxTabAddress" />
|
||||||
|
<n-button @click="fetchData" type="primary" ghost>
|
||||||
|
{{ t('query') }}
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count"
|
||||||
|
:page-sizes="[20, 50, 100]" show-size-picker>
|
||||||
|
<template #prefix="{ itemCount }">
|
||||||
|
{{ t('itemCount') }}: {{ itemCount }}
|
||||||
|
</template>
|
||||||
|
</n-pagination>
|
||||||
|
</div>
|
||||||
|
<n-data-table :columns="columns" :data="data" :bordered="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-pagination {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, h, onMounted, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { User, UserCheck, MailBulk } from '@vicons/fa'
|
||||||
|
import { SendOutlined } from '@vicons/material'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
import { api } from '../../api'
|
||||||
|
|
||||||
|
const { localeCache, adminAuth } = useGlobalState()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
userCount: 'User Count',
|
||||||
|
activeUser: '7 days Active User',
|
||||||
|
mailCount: 'Mail Count',
|
||||||
|
sendMailCount: 'Send Mail Count'
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
userCount: '用户总数',
|
||||||
|
activeUser: '周活跃用户',
|
||||||
|
mailCount: '邮件总数',
|
||||||
|
sendMailCount: '发送邮件总数'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const statistics = ref({
|
||||||
|
userCount: 0,
|
||||||
|
mailCount: 0,
|
||||||
|
activeUserCount7days: 0,
|
||||||
|
sendMailCount: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchStatistics = async () => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
userCount, activeUserCount7days, mailCount, sendMailCount
|
||||||
|
} = await api.fetch(`/admin/statistics`);
|
||||||
|
statistics.value.mailCount = mailCount || 0;
|
||||||
|
statistics.value.userCount = userCount || 0;
|
||||||
|
statistics.value.activeUserCount7days = activeUserCount7days || 0;
|
||||||
|
statistics.value.sendMailCount = sendMailCount || 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!adminAuth.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchStatistics()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-row>
|
||||||
|
<n-col :span="6">
|
||||||
|
<n-statistic :label="t('userCount')" :value="statistics.userCount">
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon :component="User" />
|
||||||
|
</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-col>
|
||||||
|
<n-col :span="6">
|
||||||
|
<n-statistic :label="t('activeUser')" :value="statistics.activeUserCount7days">
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon :component="UserCheck" />
|
||||||
|
</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-col>
|
||||||
|
<n-col :span="6">
|
||||||
|
<n-statistic :label="t('mailCount')" :value="statistics.mailCount">
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon :component="MailBulk" />
|
||||||
|
</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-col>
|
||||||
|
<n-col :span="6">
|
||||||
|
<n-statistic :label="t('sendMailCount')" :value="statistics.sendMailCount">
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon :component="SendOutlined" />
|
||||||
|
</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-col>
|
||||||
|
</n-row>
|
||||||
|
</template>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"dev": "vitepress dev docs",
|
"dev": "vitepress dev docs",
|
||||||
"build": "vitepress build docs",
|
"build": "vitepress build docs",
|
||||||
"preview": "vitepress preview docs",
|
"preview": "vitepress preview docs",
|
||||||
"deploy": "npm run build && wrangler pages deploy ./docs/.vitepress/dist --branch production"
|
"deploy": "npm run build && wrangler pages deploy ./docs/.vitepress/dist --project-name=temp-mail-docs --branch production"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jszip": "^3.10.1"
|
"jszip": "^3.10.1"
|
||||||
|
|||||||
+47
-1
@@ -1,5 +1,6 @@
|
|||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
|
import { getSendbox } from './send_mail_api'
|
||||||
|
|
||||||
const api = new Hono()
|
const api = new Hono()
|
||||||
|
|
||||||
@@ -179,6 +180,11 @@ api.post('/admin/address_sender', async (c) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.get('/admin/sendbox', async (c) => {
|
||||||
|
const { address, limit, offset } = c.req.query();
|
||||||
|
return getSendbox(c, address, limit, offset);
|
||||||
|
})
|
||||||
|
|
||||||
api.get('/admin/statistics', async (c) => {
|
api.get('/admin/statistics', async (c) => {
|
||||||
const { count: mailCountV1 } = await c.env.DB.prepare(`
|
const { count: mailCountV1 } = await c.env.DB.prepare(`
|
||||||
SELECT count(*) as count FROM mails`
|
SELECT count(*) as count FROM mails`
|
||||||
@@ -192,11 +198,51 @@ api.get('/admin/statistics', async (c) => {
|
|||||||
const { count: activeUserCount7days } = await c.env.DB.prepare(`
|
const { count: activeUserCount7days } = await c.env.DB.prepare(`
|
||||||
SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
|
SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
|
||||||
).first();
|
).first();
|
||||||
|
const { count: sendMailCount } = await c.env.DB.prepare(`
|
||||||
|
SELECT count(*) as count FROM sendbox`
|
||||||
|
).first();
|
||||||
return c.json({
|
return c.json({
|
||||||
mailCount: (mailCountV1 || 0) + (mailCount || 0),
|
mailCount: (mailCountV1 || 0) + (mailCount || 0),
|
||||||
userCount: addressCount,
|
userCount: addressCount,
|
||||||
activeUserCount7days: activeUserCount7days
|
activeUserCount7days: activeUserCount7days,
|
||||||
|
sendMailCount: sendMailCount
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
api.post('/admin/cleanup', async (c) => {
|
||||||
|
const { cleanType, cleanDays } = await c.req.json();
|
||||||
|
if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) {
|
||||||
|
return c.text("Invalid cleanType or cleanDays", 400)
|
||||||
|
}
|
||||||
|
console.log(`Cleanup ${cleanType} before ${cleanDays} days`);
|
||||||
|
switch (cleanType) {
|
||||||
|
case "mails":
|
||||||
|
await c.env.DB.prepare(`
|
||||||
|
DELETE FROM raw_mails WHERE created_at < datetime('now', '-${cleanDays} day')`
|
||||||
|
).run();
|
||||||
|
break;
|
||||||
|
case "mails_unknow":
|
||||||
|
await c.env.DB.prepare(`
|
||||||
|
DELETE FROM raw_mails WHERE address NOT IN
|
||||||
|
(select concat('${c.env.PREFIX}', name) from address) AND created_at < datetime('now', '-${cleanDays} day')`
|
||||||
|
).run();
|
||||||
|
break;
|
||||||
|
case "address":
|
||||||
|
await c.env.DB.prepare(`
|
||||||
|
DELETE FROM address WHERE updated_at < datetime('now', '-${cleanDays} day')`
|
||||||
|
).run();
|
||||||
|
break;
|
||||||
|
case "sendbox":
|
||||||
|
await c.env.DB.prepare(`
|
||||||
|
DELETE FROM sendbox WHERE created_at < datetime('now', '-${cleanDays} day')`
|
||||||
|
).run();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return c.text("Invalid cleanType", 400)
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
export { api }
|
export { api }
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
|
|
||||||
|
import { getDomains, getPasswords } from './utils';
|
||||||
|
|
||||||
const api = new Hono()
|
const api = new Hono()
|
||||||
|
|
||||||
api.get('/api/mails', async (c) => {
|
api.get('/api/mails', async (c) => {
|
||||||
@@ -124,13 +126,14 @@ api.post('/api/settings', async (c) => {
|
|||||||
api.get('/open_api/settings', async (c) => {
|
api.get('/open_api/settings', async (c) => {
|
||||||
// check header x-custom-auth
|
// check header x-custom-auth
|
||||||
let needAuth = false;
|
let needAuth = false;
|
||||||
if (c.env.PASSWORDS && c.env.PASSWORDS.length > 0) {
|
const passwords = getPasswords(c);
|
||||||
|
if (passwords && passwords.length > 0) {
|
||||||
const auth = c.req.raw.headers.get("x-custom-auth");
|
const auth = c.req.raw.headers.get("x-custom-auth");
|
||||||
needAuth = !c.env.PASSWORDS.includes(auth);
|
needAuth = !passwords.includes(auth);
|
||||||
}
|
}
|
||||||
return c.json({
|
return c.json({
|
||||||
"prefix": c.env.PREFIX,
|
"prefix": c.env.PREFIX,
|
||||||
"domains": c.env.DOMAINS,
|
"domains": getDomains(c),
|
||||||
"needAuth": needAuth,
|
"needAuth": needAuth,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -151,8 +154,9 @@ api.get('/api/new_address', async (c) => {
|
|||||||
return c.text("Name too long (max 100)", 400)
|
return c.text("Name too long (max 100)", 400)
|
||||||
}
|
}
|
||||||
// check domain, generate random domain
|
// check domain, generate random domain
|
||||||
if (!domain || !c.env.DOMAINS.includes(domain)) {
|
const domains = getDomains(c);
|
||||||
domain = c.env.DOMAINS[Math.floor(Math.random() * c.env.DOMAINS.length)];
|
if (!domain || !domains.includes(domain)) {
|
||||||
|
domain = domains[Math.floor(Math.random() * domains.length)];
|
||||||
}
|
}
|
||||||
// create address
|
// create address
|
||||||
const emailAddress = c.env.PREFIX + name + "@" + domain
|
const emailAddress = c.env.PREFIX + name + "@" + domain
|
||||||
|
|||||||
@@ -110,12 +110,10 @@ api.post('/api/send_mail', async (c) => {
|
|||||||
return c.json({ status: "ok" });
|
return c.json({ status: "ok" });
|
||||||
})
|
})
|
||||||
|
|
||||||
api.get('/api/sendbox', async (c) => {
|
const getSendbox = async (c, address, limit, offset) => {
|
||||||
const { address } = c.get("jwtPayload")
|
|
||||||
if (!address) {
|
if (!address) {
|
||||||
return c.json({ "error": "No address" }, 400)
|
return c.json({ "error": "No address" }, 400)
|
||||||
}
|
}
|
||||||
const { limit, offset } = c.req.query();
|
|
||||||
if (!limit || limit < 0 || limit > 100) {
|
if (!limit || limit < 0 || limit > 100) {
|
||||||
return c.text("Invalid limit", 400)
|
return c.text("Invalid limit", 400)
|
||||||
}
|
}
|
||||||
@@ -137,6 +135,12 @@ api.get('/api/sendbox', async (c) => {
|
|||||||
results: results,
|
results: results,
|
||||||
count: count
|
count: count
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
api.get('/api/sendbox', async (c) => {
|
||||||
|
const { address } = c.get("jwtPayload")
|
||||||
|
const { limit, offset } = c.req.query();
|
||||||
|
return getSendbox(c, address, limit, offset);
|
||||||
})
|
})
|
||||||
|
|
||||||
export { api }
|
export { api, getSendbox }
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
export const getDomains = (c) => {
|
||||||
|
if (!c.env.DOMAINS) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// check if DOMAINS is an array, if not use json.parse
|
||||||
|
if (!Array.isArray(c.env.DOMAINS)) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(c.env.DOMAINS);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse DOMAINS", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.env.DOMAINS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPasswords = (c) => {
|
||||||
|
if (!c.env.PASSWORDS) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// check if PASSWORDS is an array, if not use json.parse
|
||||||
|
if (!Array.isArray(c.env.PASSWORDS)) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(c.env.PASSWORDS);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse PASSWORDS", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.env.PASSWORDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAdminPasswords = (c) => {
|
||||||
|
if (!c.env.ADMIN_PASSWORDS) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// check if ADMIN_PASSWORDS is an array, if not use json.parse
|
||||||
|
if (!Array.isArray(c.env.ADMIN_PASSWORDS)) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(c.env.ADMIN_PASSWORDS);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse ADMIN_PASSWORDS", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.env.ADMIN_PASSWORDS;
|
||||||
|
}
|
||||||
@@ -7,14 +7,16 @@ import { api as adminApi } from './admin_api';
|
|||||||
import { api as apiV1 } from './api_v1';
|
import { api as apiV1 } from './api_v1';
|
||||||
import { api as apiSendMail } from './send_mail_api'
|
import { api as apiSendMail } from './send_mail_api'
|
||||||
import { email } from './email';
|
import { email } from './email';
|
||||||
|
import { getAdminPasswords, getPasswords } from './utils';
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
app.use('/*', cors());
|
app.use('/*', cors());
|
||||||
app.use('/api/*', async (c, next) => {
|
app.use('/api/*', async (c, next) => {
|
||||||
// check header x-custom-auth
|
// check header x-custom-auth
|
||||||
if (c.env.PASSWORDS && c.env.PASSWORDS.length > 0) {
|
const passwords = getPasswords(c);
|
||||||
|
if (passwords && passwords.length > 0) {
|
||||||
const auth = c.req.raw.headers.get("x-custom-auth");
|
const auth = c.req.raw.headers.get("x-custom-auth");
|
||||||
if (!auth || !c.env.PASSWORDS.includes(auth)) {
|
if (!auth || !passwords.includes(auth)) {
|
||||||
return c.text("Need Password", 401)
|
return c.text("Need Password", 401)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,9 +29,10 @@ app.use('/api/*', async (c, next) => {
|
|||||||
|
|
||||||
app.use('/admin/*', async (c, next) => {
|
app.use('/admin/*', async (c, next) => {
|
||||||
// check header x-admin-auth
|
// check header x-admin-auth
|
||||||
if (c.env.ADMIN_PASSWORDS && c.env.ADMIN_PASSWORDS.length > 0) {
|
const adminPasswords = getAdminPasswords(c);
|
||||||
|
if (adminPasswords && adminPasswords.length > 0) {
|
||||||
const adminAuth = c.req.raw.headers.get("x-admin-auth");
|
const adminAuth = c.req.raw.headers.get("x-admin-auth");
|
||||||
if (adminAuth && c.env.ADMIN_PASSWORDS.includes(adminAuth)) {
|
if (adminAuth && adminPasswords.includes(adminAuth)) {
|
||||||
await next();
|
await next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user