Merge remote-tracking branch 'source/main' into oauth
# Conflicts: # mail-worker/src/entity/setting.js # mail-worker/src/init/init.js # mail-worker/wrangler.toml
This commit is contained in:
@@ -25,7 +25,7 @@ jobs:
|
||||
KV_NAMESPACE_ID: ${{ secrets.KV_NAMESPACE_ID || vars.KV_NAMESPACE_ID }}
|
||||
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME || vars.R2_BUCKET_NAME }}
|
||||
PROJECT_LINK: ${{ secrets.PROJECT_LINK || vars.PROJECT_LINK }}
|
||||
AI_MODEL: ${{ secrets.AI_MODEL || vars.AI_MODEL || '@cf/meta/llama-3.1-8b-instruct' }}
|
||||
AI_MODEL: ${{ secrets.AI_MODEL || vars.AI_MODEL || '@cf/meta/llama-3.1-8b-instruct-fast' }}
|
||||
CLOUDFLARE_EMAIL: ${{ secrets.CF_EMAIL || vars.CF_EMAIL || false }}
|
||||
ANALYSIS_CACHE: ${{ secrets.ANALYSIS_CACHE || vars.ANALYSIS_CACHE || false }}
|
||||
LINUXDO_SWITCH: ${{ secrets.LINUXDO_SWITCH || vars.LINUXDO_SWITCH }}
|
||||
@@ -45,12 +45,12 @@ jobs:
|
||||
- name: ⚙ 设置 pnpm / Set up pnpm
|
||||
uses: pnpm/[email protected]
|
||||
with:
|
||||
version: 9
|
||||
version: 11
|
||||
|
||||
- name: ⚙ 设置 Node.js / Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
node-version: "24"
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "./mail-worker/pnpm-lock.yaml"
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
packages:
|
||||
- '.'
|
||||
allowBuilds:
|
||||
'@parcel/watcher': true
|
||||
esbuild: true
|
||||
vue-demi: true
|
||||
@@ -47,7 +47,9 @@
|
||||
:style="item.rightChecked ? 'background: #FDF6EC' : ''"
|
||||
>
|
||||
<el-checkbox :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'"
|
||||
v-model="item.checked" @click.stop></el-checkbox>
|
||||
v-model="item.checked"
|
||||
:disabled="!item.checked && isSelectMax"
|
||||
@click.stop></el-checkbox>
|
||||
<div @click.stop="starChange(item)" class="pc-star" v-if="showStar">
|
||||
<Icon v-if="item.isStar" icon="fluent-color:star-16" width="20" height="20"/>
|
||||
<Icon v-else icon="solar:star-line-duotone" width="18" height="18"/>
|
||||
@@ -324,7 +326,9 @@ const dropdownRef = ref(null);
|
||||
const dropdownCloseLock = ref(false);
|
||||
const dropdownShow = ref(false);
|
||||
const rightClickEmail = ref({});
|
||||
const MAX_SELECT_COUNT = 95;
|
||||
const checkedEmailCount = ref(0);
|
||||
const isSelectMax = computed(() => checkedEmailCount.value >= MAX_SELECT_COUNT);
|
||||
let timer = null
|
||||
const position = ref(
|
||||
DOMRect.fromRect({
|
||||
@@ -770,7 +774,19 @@ function addItem(email) {
|
||||
}
|
||||
|
||||
function handleCheckAllChange(val) {
|
||||
emailList.forEach(item => item.checked = val);
|
||||
if (val) {
|
||||
let count = 0;
|
||||
emailList.forEach(item => {
|
||||
if (count < MAX_SELECT_COUNT) {
|
||||
item.checked = true;
|
||||
count++;
|
||||
} else {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
emailList.forEach(item => item.checked = false);
|
||||
}
|
||||
isIndeterminate.value = false;
|
||||
}
|
||||
|
||||
@@ -786,8 +802,9 @@ function getSelectedDraftsIds() {
|
||||
function updateCheckStatus() {
|
||||
const checkedCount = emailList.filter(item => item.checked).length;
|
||||
checkedEmailCount.value = checkedCount;
|
||||
checkAll.value = checkedCount === emailList.length;
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < emailList.length;
|
||||
const atMax = checkedCount >= MAX_SELECT_COUNT;
|
||||
checkAll.value = emailList.length > 0 && (checkedCount === emailList.length || atMax);
|
||||
isIndeterminate.value = checkedCount > 0 && !checkAll.value;
|
||||
}
|
||||
|
||||
function jumpDetails(email) {
|
||||
|
||||
@@ -137,6 +137,8 @@ const en = {
|
||||
websiteSetting: 'Website',
|
||||
websiteReg: 'Sign Up',
|
||||
loginDomain: 'Hide Sign-In Domain',
|
||||
syncDelete: 'Sync Delete',
|
||||
syncDeleteDesc: 'Permanently delete emails and email addresses instead of marking them as deleted.',
|
||||
multipleEmail: 'Multiple Email Address',
|
||||
multipleEmailDesc: 'Enable this feature to allow users to add multiple email',
|
||||
customization: 'Customization',
|
||||
@@ -251,7 +253,7 @@ const en = {
|
||||
timeoutErrorMsg: 'Timeout. Try again later',
|
||||
serverBusyErrorMsg: 'Server busy. Please try again later',
|
||||
reqFailErrorMsg: 'Request failed. Try again later',
|
||||
message: 'Message',
|
||||
message: 'Mail',
|
||||
language: 'Language',
|
||||
totalUserAccount: '{msg}',
|
||||
sendBanned: 'Banned',
|
||||
|
||||
@@ -137,6 +137,8 @@ const zh = {
|
||||
websiteSetting: '网站设置',
|
||||
websiteReg: '用户注册',
|
||||
loginDomain: '隐藏登录域名',
|
||||
syncDelete: '同步删除',
|
||||
syncDeleteDesc: '开启后收件箱和邮箱不再假删',
|
||||
multipleEmail: '多号模式',
|
||||
multipleEmailDesc: '开启后账号栏出现一个用户可以添加多个邮箱',
|
||||
customization: '个性化设置',
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</el-scrollbar>
|
||||
<el-dialog v-model="showAdd" :title="$t('addAccount')">
|
||||
<div class="container">
|
||||
<el-input v-model="addForm.email" ref="addRef" type="text" :placeholder="$t('emailAccount')" autocomplete="off">
|
||||
<el-input v-model="addForm.email" ref="addRef" type="text" :placeholder="$t('emailAccount')" autocomplete="off" @keyup.enter="submit">
|
||||
<template #append>
|
||||
<div @click.stop="openSelect">
|
||||
<el-select
|
||||
@@ -116,7 +116,7 @@
|
||||
</el-dialog>
|
||||
<el-dialog v-model="setNameShow" :title="$t('changeUserName')">
|
||||
<div class="container">
|
||||
<el-input v-model="accountName" type="text" :placeholder="$t('username')" autocomplete="off">
|
||||
<el-input v-model="accountName" type="text" :placeholder="$t('username')" autocomplete="off" @keyup.enter="setName">
|
||||
</el-input>
|
||||
<el-button class="btn" type="primary" @click="setName" :loading="setNameLoading"
|
||||
>{{ $t('save') }}
|
||||
@@ -229,6 +229,8 @@ function getSkeletonRows() {
|
||||
|
||||
function setName() {
|
||||
|
||||
if (setNameLoading.value) return
|
||||
|
||||
let name = accountName.value
|
||||
|
||||
if (name === account.name) {
|
||||
@@ -427,6 +429,8 @@ function getAccountList() {
|
||||
|
||||
function submit() {
|
||||
|
||||
if (addLoading.value) return
|
||||
|
||||
if (!addForm.email) {
|
||||
ElMessage({
|
||||
message: t('emptyEmailMsg'),
|
||||
@@ -482,7 +486,6 @@ function submit() {
|
||||
addLoading.value = true
|
||||
accountAdd(addForm.email + addForm.suffix, verifyToken).then(account => {
|
||||
addLoading.value = false
|
||||
showAdd.value = false
|
||||
addForm.email = ''
|
||||
accounts.push(account)
|
||||
verifyToken = ''
|
||||
@@ -494,6 +497,9 @@ function submit() {
|
||||
})
|
||||
verifyShow.value = false
|
||||
userStore.refreshUserInfo()
|
||||
nextTick(() => {
|
||||
addRef.value.focus()
|
||||
})
|
||||
}).catch(res => {
|
||||
if (res.code === 400) {
|
||||
verifyToken = ''
|
||||
|
||||
@@ -80,7 +80,7 @@ const routers = {
|
||||
}
|
||||
}],
|
||||
'setting:query': [{
|
||||
path: '/system-setting',
|
||||
path: '/system-settings',
|
||||
name: 'sys-setting',
|
||||
component: () => import('@/views/sys-setting/index.vue'),
|
||||
meta: {
|
||||
|
||||
@@ -22,7 +22,7 @@ const routes = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/message',
|
||||
path: '/mail',
|
||||
name: 'content',
|
||||
component: () => import('@/views/content/index.vue'),
|
||||
meta: {
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
<el-dialog v-model="showBathDelete" :title="$t('clearEmail')" width="335"
|
||||
@closed="closedClear">
|
||||
<div class="clear-email">
|
||||
<el-input v-model="clearParams.sendName" :placeholder="$t('sender')"/>
|
||||
<el-input v-model="clearParams.subject" :placeholder="$t('subject')"/>
|
||||
<el-input v-model="clearParams.sendEmail" :placeholder="$t('sendEmailAddress')"/>
|
||||
<el-input v-model="clearParams.toEmail" :placeholder="$t('toEmail')"/>
|
||||
<el-input v-model="clearParams.sendName" :placeholder="$t('sender')" @keyup.enter="batchDelete"/>
|
||||
<el-input v-model="clearParams.subject" :placeholder="$t('subject')" @keyup.enter="batchDelete"/>
|
||||
<el-input v-model="clearParams.sendEmail" :placeholder="$t('sendEmailAddress')" @keyup.enter="batchDelete"/>
|
||||
<el-input v-model="clearParams.toEmail" :placeholder="$t('toEmail')" @keyup.enter="batchDelete"/>
|
||||
<el-date-picker popper-class="my-date-picker"
|
||||
v-model="clearTime"
|
||||
type="daterange"
|
||||
@@ -193,6 +193,8 @@ function openBathDelete() {
|
||||
|
||||
function batchDelete() {
|
||||
|
||||
if (clearLoading.value) return
|
||||
|
||||
if (clearTime.value) {
|
||||
clearParams.startTime = toUtc(clearTime.value[0]).format("YYYY-MM-DD HH:mm:ss")
|
||||
clearParams.endTime = toUtc(clearTime.value[1]).add(1, 'day').format("YYYY-MM-DD HH:mm:ss")
|
||||
|
||||
@@ -112,12 +112,23 @@ onMounted(() => {
|
||||
email.unread = EmailUnreadEnum.READ;
|
||||
emailRead([email.emailId]);
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
emailStore.contentData.showUnread = false;
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
})
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (showPreview.value) return;
|
||||
if (document.querySelector('.el-message-box')) return;
|
||||
const writeBox = document.querySelector('.write-box');
|
||||
if (writeBox && writeBox.offsetParent !== null) return;
|
||||
handleBack();
|
||||
}
|
||||
|
||||
function openReply() {
|
||||
uiStore.writerRef.openReply(email)
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ function jumpContent(email) {
|
||||
emailStore.contentData.showUnread = true
|
||||
emailStore.contentData.showStar = true
|
||||
emailStore.contentData.showReply = true
|
||||
router.push('/message')
|
||||
router.push('/mail')
|
||||
}
|
||||
|
||||
const existIds = new Set();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<span class="form-desc" v-else>{{ $t('regTitle') }}</span>
|
||||
<div v-show="show === 'login'">
|
||||
<el-input :class="!hideLoginDomain ? 'email-input' : ''" v-model="form.email"
|
||||
type="text" :placeholder="$t('emailAccount')" autocomplete="off">
|
||||
type="text" :placeholder="$t('emailAccount')" autocomplete="off" @keyup.enter="submit">
|
||||
<template #append v-if="!hideLoginDomain">
|
||||
<div @click.stop="openSelect">
|
||||
<el-select
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="form.password" :placeholder="$t('password')" type="password" autocomplete="off">
|
||||
<el-input v-model="form.password" :placeholder="$t('password')" type="password" autocomplete="off" @keyup.enter="submit">
|
||||
</el-input>
|
||||
<el-button class="btn" type="primary" @click="submit" :loading="loginLoading"
|
||||
>{{ $t('loginBtn') }}
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
<div v-show="show !== 'login'">
|
||||
<el-input :class="!hideLoginDomain ? 'email-input' : ''" v-model="registerForm.email" type="text" :placeholder="$t('emailAccount')"
|
||||
autocomplete="off">
|
||||
autocomplete="off" @keyup.enter="submitRegister">
|
||||
<template #append v-if="!hideLoginDomain">
|
||||
<div @click.stop="openSelect">
|
||||
<el-select
|
||||
@@ -76,13 +76,13 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="registerForm.password" :placeholder="$t('password')" type="password" autocomplete="off"/>
|
||||
<el-input v-model="registerForm.password" :placeholder="$t('password')" type="password" autocomplete="off" @keyup.enter="submitRegister"/>
|
||||
<el-input v-model="registerForm.confirmPassword" :placeholder="$t('confirmPwd')" type="password"
|
||||
autocomplete="off"/>
|
||||
autocomplete="off" @keyup.enter="submitRegister"/>
|
||||
<el-input v-if="settingStore.settings.regKey === 0" v-model="registerForm.code" :placeholder="$t('regKey')"
|
||||
type="text" autocomplete="off"/>
|
||||
type="text" autocomplete="off" @keyup.enter="submitRegister"/>
|
||||
<el-input v-if="settingStore.settings.regKey === 2" v-model="registerForm.code"
|
||||
:placeholder="$t('regKeyOptional')" type="text" autocomplete="off"/>
|
||||
:placeholder="$t('regKeyOptional')" type="text" autocomplete="off" @keyup.enter="submitRegister"/>
|
||||
<div v-show="verifyShow"
|
||||
class="register-turnstile"
|
||||
:data-sitekey="settingStore.settings.siteKey"
|
||||
@@ -112,7 +112,7 @@
|
||||
</div>
|
||||
<el-dialog class="bind-dialog" v-model="showBindForm" title="注册邮箱" >
|
||||
<div class="bind-container">
|
||||
<el-input :class="!hideLoginDomain ? 'email-input' : ''" v-model="bindForm.email" type="text" :placeholder="$t('emailAccount')" autocomplete="off">
|
||||
<el-input :class="!hideLoginDomain ? 'email-input' : ''" v-model="bindForm.email" type="text" :placeholder="$t('emailAccount')" autocomplete="off" @keyup.enter="bind">
|
||||
<template #append v-if="!hideLoginDomain">
|
||||
<div @click.stop="openSelect">
|
||||
<el-select
|
||||
@@ -136,9 +136,9 @@
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-if="settingStore.settings.regKey === 0" v-model="bindForm.code" :placeholder="$t('regKey')"
|
||||
type="text" autocomplete="off"/>
|
||||
type="text" autocomplete="off" @keyup.enter="bind"/>
|
||||
<el-input v-if="settingStore.settings.regKey === 2" v-model="bindForm.code"
|
||||
:placeholder="$t('regKeyOptional')" type="text" autocomplete="off"/>
|
||||
:placeholder="$t('regKeyOptional')" type="text" autocomplete="off" @keyup.enter="bind"/>
|
||||
<el-button class="btn" type="primary" @click="bind" :loading="bindLoading"
|
||||
>绑定
|
||||
</el-button>
|
||||
@@ -341,6 +341,8 @@ async function oauthGetUser() {
|
||||
|
||||
function bind() {
|
||||
|
||||
if (bindLoading.value) return
|
||||
|
||||
if (!bindForm.email) {
|
||||
ElMessage({
|
||||
message: t('emptyEmailMsg'),
|
||||
@@ -398,6 +400,8 @@ function bind() {
|
||||
|
||||
const submit = () => {
|
||||
|
||||
if (loginLoading.value) return
|
||||
|
||||
if (!form.email) {
|
||||
ElMessage({
|
||||
message: t('emptyEmailMsg'),
|
||||
@@ -468,6 +472,8 @@ function refreshWebsiteConfig() {
|
||||
|
||||
function submitRegister() {
|
||||
|
||||
if (registerLoading.value) return
|
||||
|
||||
if (!registerForm.email) {
|
||||
ElMessage({
|
||||
message: t('emptyEmailMsg'),
|
||||
@@ -777,6 +783,7 @@ function submitRegister() {
|
||||
width: 100px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.custom-style {
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</el-scrollbar>
|
||||
<el-dialog v-model="showAdd" :title="$t('addRegKey')">
|
||||
<div class="container">
|
||||
<el-input v-model="addForm.code" :placeholder="$t('regKey')">
|
||||
<el-input v-model="addForm.code" :placeholder="$t('regKey')" @keyup.enter="submit">
|
||||
<template #suffix>
|
||||
<Icon @click.stop="genCode" class="gen-code" icon="bitcoin-icons:refresh-filled" width="24" height="24"/>
|
||||
</template>
|
||||
@@ -75,7 +75,7 @@
|
||||
type="date"
|
||||
:placeholder="$t('validUntil')"
|
||||
/>
|
||||
<el-input-number v-model="addForm.count" :min="1" :max="99999"/>
|
||||
<el-input-number v-model="addForm.count" :min="1" :max="99999" @keyup.enter="submit"/>
|
||||
<el-button class="btn" type="primary" @click="submit" :loading="addLoading"
|
||||
>{{ $t('add') }}
|
||||
</el-button>
|
||||
@@ -308,6 +308,8 @@ function clearNotUse() {
|
||||
|
||||
function submit() {
|
||||
|
||||
if (addLoading.value) return
|
||||
|
||||
if (!addForm.code) {
|
||||
ElMessage({
|
||||
message: $('emptyRegKeyMsg'),
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
</template>
|
||||
<div class="dialog-box">
|
||||
<el-input class="dialog-input" v-model="form.name" type="text" :maxlength="12" :placeholder="$t('roleName')"
|
||||
autocomplete="off"/>
|
||||
autocomplete="off" @keyup.enter="roleFormClick"/>
|
||||
<el-input class="dialog-input" v-model="form.description" :maxlength="30" type="text"
|
||||
:placeholder="$t('description')" autocomplete="off"/>
|
||||
:placeholder="$t('description')" autocomplete="off" @keyup.enter="roleFormClick"/>
|
||||
<el-input-tag class="dialog-input" tag-type="warning" v-model="form.banEmail"
|
||||
@add-tag="banEmailAddTag" type="text" :placeholder="$t('emailInterception')" autocomplete="off"/>
|
||||
<el-select
|
||||
@@ -233,6 +233,7 @@ function banEmailAddTag(val) {
|
||||
|
||||
|
||||
function roleFormClick() {
|
||||
if (permLoading.value) return
|
||||
if (dialogType.type === 'add') {
|
||||
addRole()
|
||||
} else {
|
||||
|
||||
@@ -60,7 +60,7 @@ function jumpContent(email) {
|
||||
emailStore.contentData.delType = 'logic'
|
||||
emailStore.contentData.showStar = true
|
||||
emailStore.contentData.showReply = true
|
||||
router.push('/message')
|
||||
router.push('/mail')
|
||||
}
|
||||
|
||||
function addStar(email) {
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
</div>
|
||||
<el-dialog v-model="pwdShow" :title="$t('changePassword')" width="340">
|
||||
<div class="update-pwd">
|
||||
<el-input type="password" :placeholder="$t('newPassword')" v-model="form.password" autocomplete="off"/>
|
||||
<el-input type="password" :placeholder="$t('confirmPassword')" v-model="form.newPwd" autocomplete="off"/>
|
||||
<el-input type="password" :placeholder="$t('newPassword')" v-model="form.password" autocomplete="off" @keyup.enter="submitPwd"/>
|
||||
<el-input type="password" :placeholder="$t('confirmPassword')" v-model="form.newPwd" autocomplete="off" @keyup.enter="submitPwd"/>
|
||||
<el-button type="primary" :loading="setPwdLoading" @click="submitPwd">{{$t('save')}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@@ -160,6 +160,8 @@ const deleteConfirm = () => {
|
||||
|
||||
function submitPwd() {
|
||||
|
||||
if (setPwdLoading.value) return
|
||||
|
||||
if (!form.password) {
|
||||
ElMessage({
|
||||
message: t('emptyPwdMsg'),
|
||||
|
||||
@@ -32,7 +32,7 @@ function jumpContent(email) {
|
||||
emailStore.contentData.delType = 'logic'
|
||||
emailStore.contentData.showStar = true
|
||||
emailStore.contentData.showReply = true
|
||||
router.push('/message')
|
||||
router.push('/mail')
|
||||
}
|
||||
|
||||
function cancelStar(email) {
|
||||
|
||||
@@ -61,6 +61,18 @@
|
||||
v-model="setting.manyEmail"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div>
|
||||
<span>{{ $t('syncDelete') }}</span>
|
||||
<el-tooltip effect="dark" :content="$t('syncDeleteDesc')">
|
||||
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
|
||||
v-model="setting.syncDelete"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div>
|
||||
<span>{{ $t('emailPrefix') }}</span>
|
||||
@@ -457,13 +469,13 @@
|
||||
|
||||
<!-- Dialogs remain the same -->
|
||||
<el-dialog v-model="editTitleShow" :title="$t('changeTitle')" width="340" @closed="editTitle = setting.title">
|
||||
<form>
|
||||
<el-input type="text" :placeholder="$t('websiteTitle')" v-model="editTitle"/>
|
||||
<form @submit.prevent>
|
||||
<el-input type="text" :placeholder="$t('websiteTitle')" v-model="editTitle" @keyup.enter="saveTitle"/>
|
||||
<el-button type="primary" :loading="settingLoading" @click="saveTitle">{{ $t('save') }}</el-button>
|
||||
</form>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="resendTokenFormShow" :title="$t('resendToken')" width="340" @closed="cleanResendTokenForm">
|
||||
<form>
|
||||
<form @submit.prevent>
|
||||
<el-select style="margin-bottom: 15px" v-model="resendTokenForm.domain" placeholder="Select">
|
||||
<el-option
|
||||
v-for="item in settingStore.domainList"
|
||||
@@ -472,22 +484,22 @@
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input type="text" :placeholder="$t('addResendTokenDesc')" v-model="resendTokenForm.token"/>
|
||||
<el-input type="text" :placeholder="$t('addResendTokenDesc')" v-model="resendTokenForm.token" @keyup.enter="saveResendToken"/>
|
||||
<el-button type="primary" :loading="settingLoading" @click="saveResendToken">{{ $t('save') }}</el-button>
|
||||
</form>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="r2DomainShow" :title="$t('addOsDomain')" width="340"
|
||||
@closed="r2DomainInput = setting.r2Domain">
|
||||
<form>
|
||||
<el-input type="text" :placeholder="$t('domainDesc')" v-model="r2DomainInput"/>
|
||||
<form @submit.prevent>
|
||||
<el-input type="text" :placeholder="$t('domainDesc')" v-model="r2DomainInput" @keyup.enter="saveR2domain"/>
|
||||
<el-button type="primary" :loading="settingLoading" @click="saveR2domain">{{ $t('save') }}</el-button>
|
||||
</form>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="turnstileShow" :title="$t('addTurnstileSecret')" width="340"
|
||||
@closed="turnstileForm.secretKey = '';turnstileForm.siteKey = ''">
|
||||
<form>
|
||||
<el-input type="text" placeholder="Site Key" v-model="turnstileForm.siteKey"/>
|
||||
<el-input type="text" style="margin-top: 15px" placeholder="Secret Key" v-model="turnstileForm.secretKey"/>
|
||||
<form @submit.prevent>
|
||||
<el-input type="text" placeholder="Site Key" v-model="turnstileForm.siteKey" @keyup.enter="saveTurnstileKey"/>
|
||||
<el-input type="text" style="margin-top: 15px" placeholder="Secret Key" v-model="turnstileForm.secretKey" @keyup.enter="saveTurnstileKey"/>
|
||||
<el-button type="primary" :loading="settingLoading" @click="saveTurnstileKey">{{ $t('save') }}</el-button>
|
||||
</form>
|
||||
</el-dialog>
|
||||
@@ -521,7 +533,7 @@
|
||||
</span>
|
||||
</template>
|
||||
<el-input :placeholder="$t('backgroundUrlDesc')" v-model="backgroundUrl" v-if="!localUpShow"
|
||||
class="background-url"/>
|
||||
class="background-url" @keyup.enter="saveBackground"/>
|
||||
<el-image
|
||||
v-if="localUpShow"
|
||||
:preview-src-list="[backgroundImage]"
|
||||
@@ -553,10 +565,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="forward-set-body">
|
||||
<el-input :placeholder="setting.tgBotToken || $t('tgBotToken')" v-model="tgBotToken"></el-input>
|
||||
<el-input :placeholder="setting.tgBotToken || $t('tgBotToken')" v-model="tgBotToken" @keyup.enter="tgBotSave"></el-input>
|
||||
<el-input-tag tag-type="warning" :placeholder="$t('toBotTokenDesc')" v-model="tgChatId"
|
||||
@add-tag="addChatTag"></el-input-tag>
|
||||
<el-input tag-type="warning" :placeholder="$t('customDomainDesc')" v-model="customDomain" ></el-input>
|
||||
<el-input tag-type="warning" :placeholder="$t('customDomainDesc')" v-model="customDomain" @keyup.enter="tgBotSave"></el-input>
|
||||
<div class="tg-msg-label">
|
||||
<span>{{t('from')}}</span>
|
||||
<el-select v-model="tgMsgFrom" >
|
||||
@@ -665,23 +677,23 @@
|
||||
</el-dialog>
|
||||
<el-dialog v-model="regVerifyCountShow" :title="$t('rulesVerifyTitle',{count: regVerifyCount})"
|
||||
@closed="regVerifyCount = setting.regVerifyCount">
|
||||
<form>
|
||||
<el-input-number type="text" v-model="regVerifyCount" :min="1">
|
||||
<form @submit.prevent>
|
||||
<el-input-number type="text" v-model="regVerifyCount" :min="1" @keyup.enter="saveRegVerifyCount">
|
||||
</el-input-number>
|
||||
<el-button type="primary" :loading="settingLoading" @click="saveRegVerifyCount">{{ $t('save') }}</el-button>
|
||||
</form>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="addVerifyCountShow" :title="$t('rulesVerifyTitle',{count: addVerifyCount})"
|
||||
@closed="addVerifyCount = setting.addVerifyCount">
|
||||
<form>
|
||||
<el-input-number type="text" v-model="addVerifyCount" :min="1"/>
|
||||
<form @submit.prevent>
|
||||
<el-input-number type="text" v-model="addVerifyCount" :min="1" @keyup.enter="saveAddVerifyCount"/>
|
||||
<el-button type="primary" :loading="settingLoading" @click="saveAddVerifyCount">{{ $t('save') }}</el-button>
|
||||
</form>
|
||||
</el-dialog>
|
||||
<el-dialog top="5vh" v-model="noticePopupShow" :title="$t('noticePopup')" class="notice-popup"
|
||||
@closed="resetNoticeForm">
|
||||
<form>
|
||||
<el-input v-model="noticeForm.noticeTitle" :placeholder="t('titleDesc')"/>
|
||||
<form @submit.prevent>
|
||||
<el-input v-model="noticeForm.noticeTitle" :placeholder="t('titleDesc')" @keyup.enter="saveNoticePopup"/>
|
||||
<div class="notice-line-item">
|
||||
<el-select v-model="noticeForm.noticeType">
|
||||
<template #prefix>
|
||||
@@ -752,13 +764,13 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="addS3Show" :title="t('s3Configuration')" width="340" @closed="resetAddS3Form">
|
||||
<form>
|
||||
<el-input class="dialog-input" type="text" placeholder="Bucket" v-model="s3.bucket"/>
|
||||
<el-input class="dialog-input" type="text" placeholder="Endpoint" v-model="s3.endpoint"/>
|
||||
<el-input class="dialog-input" type="text" placeholder="Region" v-model="s3.region"/>
|
||||
<form @submit.prevent>
|
||||
<el-input class="dialog-input" type="text" placeholder="Bucket" v-model="s3.bucket" @keyup.enter="saveS3"/>
|
||||
<el-input class="dialog-input" type="text" placeholder="Endpoint" v-model="s3.endpoint" @keyup.enter="saveS3"/>
|
||||
<el-input class="dialog-input" type="text" placeholder="Region" v-model="s3.region" @keyup.enter="saveS3"/>
|
||||
<el-input class="dialog-input" type="text" :placeholder="setting.s3AccessKey || 'Access Key'"
|
||||
v-model="s3.s3AccessKey"/>
|
||||
<el-input style="margin-bottom: 10px" type="text" :placeholder="setting.s3SecretKey || 'Secret Key'" v-model="s3.s3SecretKey"/>
|
||||
v-model="s3.s3AccessKey" @keyup.enter="saveS3"/>
|
||||
<el-input style="margin-bottom: 10px" type="text" :placeholder="setting.s3SecretKey || 'Secret Key'" v-model="s3.s3SecretKey" @keyup.enter="saveS3"/>
|
||||
<div class="force-path-style">
|
||||
<div class="force-path-style-left">
|
||||
<span>ForcePathStyle</span>
|
||||
@@ -778,7 +790,7 @@
|
||||
<el-dialog v-model="emailPrefixShow" :title="t('emailPrefix')" @closed="resetEmailPrefix" >
|
||||
<div class="email-prefix">
|
||||
<div>{{ t('atLeast') }}</div>
|
||||
<el-input-number v-model="minEmailPrefix" :min="1" :max="20" style="width: 150px" >
|
||||
<el-input-number v-model="minEmailPrefix" :min="1" :max="20" style="width: 150px" @keyup.enter="saveEmailPrefix" >
|
||||
<template #suffix>
|
||||
<span>{{ t('character') }}</span>
|
||||
</template>
|
||||
@@ -854,7 +866,7 @@ defineOptions({
|
||||
name: 'sys-setting'
|
||||
})
|
||||
|
||||
const currentVersion = 'v3.0.0'
|
||||
const currentVersion = 'v3.1.0'
|
||||
const hasUpdate = ref(false)
|
||||
let getUpdateErrorCount = 1;
|
||||
const {t, locale} = useI18n();
|
||||
@@ -1405,6 +1417,8 @@ function saveTurnstileKey() {
|
||||
|
||||
async function saveBackground() {
|
||||
|
||||
if (settingLoading.value) return
|
||||
|
||||
let image = ''
|
||||
|
||||
if (localUpShow.value) {
|
||||
@@ -1615,14 +1629,11 @@ function editSetting(settingForm, refreshStatus = true) {
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
@media (max-width: 500px) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
@media (max-width: 1023px) {
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
@@ -1652,17 +1663,19 @@ function editSetting(settingForm, refreshStatus = true) {
|
||||
|
||||
.settings-card {
|
||||
background-color: var(--el-bg-color);
|
||||
border-radius: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
transition: all 300ms;
|
||||
overflow: hidden;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.card-title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
padding: 10px 20px;
|
||||
padding: 15px 20px 12px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
</el-scrollbar>
|
||||
<el-dialog class="dialog" v-model="setPwdShow" :title="$t('changePassword')" @closed="resetUserForm">
|
||||
<div class="dialog-box">
|
||||
<el-input v-model="userForm.password" type="password" :placeholder="$t('newPassword')" autocomplete="off">
|
||||
<el-input v-model="userForm.password" type="password" :placeholder="$t('newPassword')" autocomplete="off" @keyup.enter="updatePwd">
|
||||
</el-input>
|
||||
<el-button class="btn" type="primary" :loading="settingLoading" @click="updatePwd"
|
||||
>{{ $t('save') }}
|
||||
@@ -155,7 +155,7 @@
|
||||
</el-dialog>
|
||||
<el-dialog v-model="showAdd" :title="$t('addUser')" @closed="resetAddForm">
|
||||
<div class="container">
|
||||
<el-input v-model="addForm.email" type="text" :placeholder="$t('emailAccount')" autocomplete="off">
|
||||
<el-input v-model="addForm.email" type="text" :placeholder="$t('emailAccount')" autocomplete="off" @keyup.enter="submit">
|
||||
<template #append>
|
||||
<div @click.stop="openSelect">
|
||||
<el-select
|
||||
@@ -178,7 +178,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input type="password" v-model="addForm.password" :placeholder="$t('password')"/>
|
||||
<el-input type="password" v-model="addForm.password" :placeholder="$t('password')" @keyup.enter="submit"/>
|
||||
<el-select v-model="addForm.type" :placeholder="$t('perm')">
|
||||
<el-option v-for="item in roleList" :label="item.name" :value="item.roleId" :key="item.roleId"/>
|
||||
</el-select>
|
||||
@@ -693,6 +693,8 @@ function openAdd() {
|
||||
|
||||
function submit() {
|
||||
|
||||
if (addLoading.value) return
|
||||
|
||||
if (!addForm.email) {
|
||||
ElMessage({
|
||||
message: t('emptyEmailMsg'),
|
||||
@@ -895,6 +897,7 @@ function httpSetStatus(user) {
|
||||
}
|
||||
|
||||
function setType() {
|
||||
if (settingLoading.value) return
|
||||
settingLoading.value = true
|
||||
userSetType({type: userForm.type, userId: userForm.userId}).then(() => {
|
||||
chooseUser.type = userForm.type
|
||||
@@ -923,6 +926,8 @@ function search() {
|
||||
|
||||
function updatePwd() {
|
||||
|
||||
if (settingLoading.value) return
|
||||
|
||||
if (!userForm.password) {
|
||||
ElMessage({
|
||||
message: t('emptyPwdMsg'),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
packages:
|
||||
- '.'
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
sharp: true
|
||||
workerd: true
|
||||
@@ -132,6 +132,10 @@ export const settingConst = {
|
||||
OPEN: 0,
|
||||
CLOSE: 1
|
||||
},
|
||||
syncDelete: {
|
||||
OPEN: 0,
|
||||
CLOSE: 1
|
||||
},
|
||||
authRefresh: {
|
||||
OPEN: 1,
|
||||
CLOSE: 0
|
||||
|
||||
@@ -51,6 +51,7 @@ export const setting = sqliteTable('setting', {
|
||||
blackContent: text('black_content').default('').notNull(),
|
||||
blackFrom: text('black_from').default('').notNull(),
|
||||
aiCode: integer('ai_code').default(1).notNull(),
|
||||
syncDelete: integer('sync_delete').default(1).notNull(),
|
||||
aiCodeFilter: text('ai_code_filter').default('').notNull(),
|
||||
linuxdoClientId: text('linuxdo_client_id').default('').notNull(),
|
||||
linuxdoClientSecret: text('linuxdo_client_secret').default('').notNull(),
|
||||
|
||||
@@ -30,11 +30,12 @@ const dbInit = {
|
||||
await this.v2_9DB(c);
|
||||
await this.v3_0DB(c);
|
||||
await this.v3_1DB(c);
|
||||
await this.v3_2DB(c);
|
||||
await settingService.refresh(c);
|
||||
return c.text('success');
|
||||
},
|
||||
|
||||
async v3_1DB(c) {
|
||||
async v3_2DB(c) {
|
||||
const oauthFields = [
|
||||
'linuxdo_client_id', 'linuxdo_client_secret', 'linuxdo_callback_url',
|
||||
'github_client_id', 'github_client_secret', 'github_callback_url',
|
||||
@@ -50,6 +51,15 @@ const dbInit = {
|
||||
}
|
||||
},
|
||||
|
||||
async v3_1DB(c) {
|
||||
try {
|
||||
await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN sync_delete INTEGER NOT NULL DEFAULT 0;`).run();
|
||||
} catch (e) {
|
||||
console.warn(`跳过字段:${e.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async v3_0DB(c) {
|
||||
try {
|
||||
await c.env.db.batch([
|
||||
|
||||
@@ -155,6 +155,12 @@ const accountService = {
|
||||
throw new BizError(t('noUserAccount'));
|
||||
}
|
||||
|
||||
const { syncDelete } = await settingService.query(c);
|
||||
if (syncDelete === settingConst.syncDelete.OPEN) {
|
||||
await this.physicsDelete(c, { accountId });
|
||||
return;
|
||||
}
|
||||
|
||||
await orm(c).update(account).set({ isDel: isDel.DELETE }).where(
|
||||
and(eq(account.userId, userId),
|
||||
eq(account.accountId, accountId)))
|
||||
|
||||
@@ -19,7 +19,7 @@ const aiService = {
|
||||
return '';
|
||||
}
|
||||
|
||||
const result = await ai.run(c.env.ai_model || '@cf/meta/llama-3.1-8b-instruct', {
|
||||
const result = await ai.run(c.env.ai_model || '@cf/meta/llama-3.1-8b-instruct-fast', {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -137,6 +137,19 @@ const emailService = {
|
||||
async delete(c, params, userId) {
|
||||
const { emailIds } = params;
|
||||
const emailIdList = emailIds.split(',').map(Number);
|
||||
const { syncDelete } = await settingService.query(c);
|
||||
|
||||
if (syncDelete === settingConst.syncDelete.OPEN) {
|
||||
const owned = await orm(c).select({ emailId: email.emailId }).from(email)
|
||||
.where(and(eq(email.userId, userId), inArray(email.emailId, emailIdList)))
|
||||
.all();
|
||||
const ownedIds = owned.map(row => row.emailId);
|
||||
if (ownedIds.length) {
|
||||
await this.physicsDelete(c, { emailIds: ownedIds.join(',') });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await orm(c).update(email).set({ isDel: isDel.DELETE }).where(
|
||||
and(
|
||||
eq(email.userId, userId),
|
||||
|
||||
@@ -77,6 +77,10 @@ const starService = {
|
||||
},
|
||||
async removeByEmailIds(c, emailIds) {
|
||||
await orm(c).delete(star).where(inArray(star.emailId, emailIds)).run();
|
||||
},
|
||||
|
||||
async removeByUserIds(c, userIds) {
|
||||
await orm(c).delete(star).where(inArray(star.userId, userIds)).run();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import accountService from './account-service';
|
||||
import orm from '../entity/orm';
|
||||
import user from '../entity/user';
|
||||
import { and, asc, count, desc, eq, inArray, sql } from 'drizzle-orm';
|
||||
import { emailConst, isDel, roleConst, userConst } from '../const/entity-const';
|
||||
import { emailConst, isDel, roleConst, settingConst, userConst } from '../const/entity-const';
|
||||
import kvConst from '../const/kv-const';
|
||||
import KvConst from '../const/kv-const';
|
||||
import cryptoUtils from '../utils/crypto-utils';
|
||||
@@ -18,6 +18,8 @@ import { t } from '../i18n/i18n'
|
||||
import reqUtils from '../utils/req-utils';
|
||||
import {oauth} from "../entity/oauth";
|
||||
import oauthService from "./oauth-service";
|
||||
import settingService from './setting-service';
|
||||
import starService from './star-service';
|
||||
|
||||
const userService = {
|
||||
|
||||
@@ -95,6 +97,12 @@ const userService = {
|
||||
},
|
||||
|
||||
async delete(c, userId) {
|
||||
const { syncDelete } = await settingService.query(c);
|
||||
if (syncDelete === settingConst.syncDelete.OPEN) {
|
||||
await this.physicsDelete(c, { userIds: String(userId) });
|
||||
await c.env.kv.delete(kvConst.AUTH_INFO + userId)
|
||||
return;
|
||||
}
|
||||
await orm(c).update(user).set({ isDel: isDel.DELETE }).where(eq(user.userId, userId)).run();
|
||||
await c.env.kv.delete(kvConst.AUTH_INFO + userId)
|
||||
},
|
||||
@@ -102,6 +110,7 @@ const userService = {
|
||||
async physicsDelete(c, params) {
|
||||
let { userIds } = params;
|
||||
userIds = userIds.split(',').map(Number);
|
||||
await starService.removeByUserIds(c, userIds);
|
||||
await accountService.physicsDeleteByUserIds(c, userIds);
|
||||
await oauthService.deleteByUserIds(c, userIds);
|
||||
await orm(c).delete(user).where(inArray(user.userId, userIds)).run();
|
||||
|
||||
@@ -34,7 +34,7 @@ crons = ["0 16 * * *"] #每天晚上12点执行每日任务,刷新分析缓存
|
||||
|
||||
|
||||
[vars]
|
||||
ai_model = "" #ai模型,不填默认使用@cf/meta/llama-3.1-8b-instruct
|
||||
ai_model = "" #ai模型,不填默认使用@cf/meta/llama-3.1-8b-instruct-fast
|
||||
analysis_cache = false #是否开启分析数据缓存
|
||||
orm_log = false #是否sql日志
|
||||
domain = ["example.com"] #邮件域名可可配置多个 示例: ["example1.com","example2.com"]
|
||||
|
||||
@@ -36,8 +36,8 @@ crons = ["0 16 * * *"] #每天晚上12点执行每日任务,刷新分析缓存
|
||||
|
||||
|
||||
[vars]
|
||||
#ai_model = "" #ai模型,不填默认使用@cf/meta/llama-3.1-8b-instruct
|
||||
#analysis_cache = false #是否开启分析数据缓存
|
||||
#ai_model = "" #ai模型,不填默认使用@cf/meta/llama-3.1-8b-instruct-fast
|
||||
#analysis_cache = false #是否开启分析数据缓存
|
||||
#orm_log = false #是否sql日志
|
||||
#domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"]
|
||||
#admin = "" #管理员的邮箱 示例: [email protected]
|
||||
|
||||
Reference in New Issue
Block a user