This commit is contained in:
parent
986ab4d463
commit
415d35f6e2
|
@ -0,0 +1,71 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"myproject/tools"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAuthStore() *sessions.CookieStore {
|
||||||
|
auth := make([]byte, 32)
|
||||||
|
_, _ = rand.Read(auth)
|
||||||
|
enc := make([]byte, 16)
|
||||||
|
_, _ = rand.Read(enc)
|
||||||
|
s := sessions.NewCookieStore(auth, enc)
|
||||||
|
s.Options.Secure = false
|
||||||
|
s.Options.SameSite = http.SameSiteDefaultMode
|
||||||
|
s.MaxAge(3600)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAuth(onlyAdmin bool, g *echo.Group) *echo.Group {
|
||||||
|
g.Use(
|
||||||
|
func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
sess, err := c.Get("authStore").(*sessions.CookieStore).New(c.Request(), tools.SessionName)
|
||||||
|
if err != nil {
|
||||||
|
// journal.Debug(ctx, commerr.Trace(err).Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
userName := sess.Values[tools.UserNameSessionKey]
|
||||||
|
if userName == nil {
|
||||||
|
return echo.ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
email, lp, domain := SplitEmail(userName.(string))
|
||||||
|
c.Set(tools.UserCtxKey, email)
|
||||||
|
c.Set(tools.LpCtxKey, lp)
|
||||||
|
c.Set(tools.DomainCtxKey, domain)
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
if onlyAdmin && tools.GetUser(c) != "admin" {
|
||||||
|
return echo.ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitEmail(toSplit string) (email, user, domain string) {
|
||||||
|
email = strings.TrimSpace(toSplit)
|
||||||
|
email = strings.ToLower(email)
|
||||||
|
parts := strings.Split(email, "@")
|
||||||
|
user = parts[0]
|
||||||
|
if len(parts) > 1 {
|
||||||
|
domain = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"myproject/tools"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewEcho(ctx context.Context, restartMode *bool) *echo.Echo {
|
||||||
|
timeout := 5 * time.Minute
|
||||||
|
e := echo.New()
|
||||||
|
e.HideBanner = true
|
||||||
|
e.Server.ReadTimeout = timeout
|
||||||
|
e.Server.WriteTimeout = timeout
|
||||||
|
e.Server.BaseContext = func(listener net.Listener) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||||
|
httpError, ok := err.(*echo.HTTPError)
|
||||||
|
if ok {
|
||||||
|
errorCode := httpError.Code
|
||||||
|
switch errorCode {
|
||||||
|
case http.StatusServiceUnavailable:
|
||||||
|
tools.Serve503(c)
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
|
tools.Serve429(c)
|
||||||
|
case http.StatusForbidden:
|
||||||
|
tools.Serve403(c)
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
tools.Serve401(c)
|
||||||
|
case http.StatusNotFound, http.StatusMethodNotAllowed:
|
||||||
|
if strings.HasPrefix(c.Request().RequestURI, "/backend") {
|
||||||
|
switch errorCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
tools.Serve404(c)
|
||||||
|
case http.StatusMethodNotAllowed:
|
||||||
|
tools.Serve405(c)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("/?redirect=%s", c.Request().RequestURI))
|
||||||
|
default:
|
||||||
|
tools.Serve500(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authStore := NewAuthStore()
|
||||||
|
|
||||||
|
e.Use(
|
||||||
|
middleware.CORSWithConfig(middleware.CORSConfig{
|
||||||
|
AllowOrigins: []string{"http://localhost:7777", "http://localhost:8808"},
|
||||||
|
AllowHeaders: []string{
|
||||||
|
echo.HeaderOrigin,
|
||||||
|
echo.HeaderContentType,
|
||||||
|
echo.HeaderAccept,
|
||||||
|
echo.HeaderAccessControlAllowOrigin,
|
||||||
|
echo.HeaderAccessControlAllowCredentials,
|
||||||
|
echo.HeaderAccessControlAllowHeaders,
|
||||||
|
echo.HeaderAccessControlRequestHeaders,
|
||||||
|
echo.HeaderAuthorization,
|
||||||
|
},
|
||||||
|
AllowCredentials: true,
|
||||||
|
}),
|
||||||
|
middleware.GzipWithConfig(middleware.GzipConfig{
|
||||||
|
Level: 6,
|
||||||
|
}),
|
||||||
|
func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
c.Set("authStore", authStore)
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
c.Set(tools.RootCtxKey, ctx)
|
||||||
|
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_URL=http://localhost:8888
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_URL=
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
|
@ -1,23 +1,33 @@
|
||||||
# Vue 3 + TypeScript + Vite
|
# js
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
3 `<script setup>` SFCs, check out
|
|
||||||
the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
## Recommended IDE Setup
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
## Type Support For `.vue` Imports in TS
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
by default. In most cases this is fine if you don't really care about component prop types outside of templates.
|
|
||||||
However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using
|
|
||||||
manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
|
|
||||||
|
|
||||||
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look
|
## Customize configuration
|
||||||
for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default,
|
|
||||||
Take Over mode will enable itself if the default TypeScript extension is disabled.
|
|
||||||
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
|
|
||||||
|
|
||||||
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||||
|
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||||
|
AButton: typeof import('ant-design-vue/es')['Button']
|
||||||
|
ACalendar: typeof import('ant-design-vue/es')['Calendar']
|
||||||
|
ACard: typeof import('ant-design-vue/es')['Card']
|
||||||
|
ACardGrid: typeof import('ant-design-vue/es')['CardGrid']
|
||||||
|
ACol: typeof import('ant-design-vue/es')['Col']
|
||||||
|
ACollapse: typeof import('ant-design-vue/es')['Collapse']
|
||||||
|
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
|
||||||
|
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||||
|
Actions: typeof import('./src/components/common/rules/Actions.vue')['default']
|
||||||
|
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
|
||||||
|
Add: typeof import('./src/components/admin/domains/Add.vue')['default']
|
||||||
|
AddressBooks: typeof import('./src/components/common/AddressBooks.vue')['default']
|
||||||
|
AddressRules: typeof import('./src/components/admin/settings/AddressRules.vue')['default']
|
||||||
|
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
||||||
|
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
||||||
|
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||||
|
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||||
|
AFlex: typeof import('ant-design-vue/es')['Flex']
|
||||||
|
AForm: typeof import('ant-design-vue/es')['Form']
|
||||||
|
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||||
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
|
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||||
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
|
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||||
|
AList: typeof import('ant-design-vue/es')['List']
|
||||||
|
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||||
|
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||||
|
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||||
|
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||||
|
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||||
|
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||||
|
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||||
|
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||||
|
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||||
|
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||||
|
AResult: typeof import('ant-design-vue/es')['Result']
|
||||||
|
ARow: typeof import('ant-design-vue/es')['Row']
|
||||||
|
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||||
|
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||||
|
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||||
|
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||||
|
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||||
|
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||||
|
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||||
|
ATable: typeof import('ant-design-vue/es')['Table']
|
||||||
|
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||||
|
ATimePicker: typeof import('ant-design-vue/es')['TimePicker']
|
||||||
|
ATimeRangePicker: typeof import('ant-design-vue/es')['TimeRangePicker']
|
||||||
|
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||||
|
ATransfer: typeof import('ant-design-vue/es')['Transfer']
|
||||||
|
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||||
|
AvaliableToMe: typeof import('./src/components/user/AddressBooks/AvaliableToMe.vue')['default']
|
||||||
|
BlockedIPs: typeof import('./src/components/admin/security/BlockedIPs.vue')['default']
|
||||||
|
Calendars: typeof import('./src/components/common/Calendars.vue')['default']
|
||||||
|
CalendarsAccess: typeof import('./src/components/common/CalendarsAccess.vue')['default']
|
||||||
|
Categories: typeof import('./src/components/admin/domains/resources/Categories.vue')['default']
|
||||||
|
Conditions: typeof import('./src/components/common/rules/Conditions.vue')['default']
|
||||||
|
Dashboard: typeof import('./src/components/admin/Dashboard.vue')['default']
|
||||||
|
Day: typeof import('./src/components/common/approval/Day.vue')['default']
|
||||||
|
DKIM: typeof import('./src/components/admin/domains/DKIM.vue')['default']
|
||||||
|
Email: typeof import('./src/components/admin/security/black_list/Email.vue')['default']
|
||||||
|
Export: typeof import('./src/components/admin/domains/mailstorage/Export.vue')['default']
|
||||||
|
Groups: typeof import('./src/components/admin/domains/userdb/Groups.vue')['default']
|
||||||
|
IncomingRules: typeof import('./src/components/user/IncomingRules.vue')['default']
|
||||||
|
Interval: typeof import('./src/components/common/approval/Interval.vue')['default']
|
||||||
|
IP: typeof import('./src/components/admin/security/black_list/IP.vue')['default']
|
||||||
|
License: typeof import('./src/components/admin/settings/License.vue')['default']
|
||||||
|
List: typeof import('./src/components/user/Calendars/EventsPlanner/List.vue')['default']
|
||||||
|
Logo: typeof import('./src/components/Logo.vue')['default']
|
||||||
|
Mailboxes: typeof import('./src/components/admin/domains/mailstorage/Mailboxes.vue')['default']
|
||||||
|
MailboxSharedAccess: typeof import('./src/components/user/MailboxSharedAccess.vue')['default']
|
||||||
|
MailsPermDeletion: typeof import('./src/components/admin/domains/MailsPermDeletion.vue')['default']
|
||||||
|
Main: typeof import('./src/components/admin/settings/Main.vue')['default']
|
||||||
|
Manage: typeof import('./src/components/admin/settings/smtp_queue/Manage.vue')['default']
|
||||||
|
Migration: typeof import('./src/components/admin/domains/Migration.vue')['default']
|
||||||
|
MyBooks: typeof import('./src/components/user/AddressBooks/MyBooks.vue')['default']
|
||||||
|
MyCalendars: typeof import('./src/components/user/Calendars/MyCalendars.vue')['default']
|
||||||
|
NewOrEdit: typeof import('./src/components/user/Calendars/EventsPlanner/NewOrEdit.vue')['default']
|
||||||
|
NotImplemented: typeof import('./src/components/NotImplemented.vue')['default']
|
||||||
|
Offices: typeof import('./src/components/admin/domains/resources/Offices.vue')['default']
|
||||||
|
OutgoingRules: typeof import('./src/components/admin/domains/OutgoingRules.vue')['default']
|
||||||
|
PageNotFound: typeof import('./src/components/PageNotFound.vue')['default']
|
||||||
|
Policy: typeof import('./src/components/admin/domains/cidr_access/Policy.vue')['default']
|
||||||
|
Pools: typeof import('./src/components/admin/domains/cidr_access/Pools.vue')['default']
|
||||||
|
Profile: typeof import('./src/components/user/Profile.vue')['default']
|
||||||
|
RecoveryFolder: typeof import('./src/components/user/RecoveryFolder.vue')['default']
|
||||||
|
Redirects: typeof import('./src/components/admin/domains/userdb/Redirects.vue')['default']
|
||||||
|
Resources: typeof import('./src/components/admin/domains/resources/Resources.vue')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
Rules: typeof import('./src/components/common/rules/Rules.vue')['default']
|
||||||
|
ScriptErrorNotify: typeof import('./src/components/user/Calendars/EventsPlanner/ScriptErrorNotify.vue')['default']
|
||||||
|
Settings: typeof import('./src/components/admin/domains/mailstorage/Settings.vue')['default']
|
||||||
|
SettingsDB: typeof import('./src/components/admin/settings/SettingsDB.vue')['default']
|
||||||
|
SharedAddressBooks: typeof import('./src/components/admin/domains/SharedAddressBooks.vue')['default']
|
||||||
|
SharedCalendars: typeof import('./src/components/admin/domains/SharedCalendars.vue')['default']
|
||||||
|
SharedFolders: typeof import('./src/components/common/SharedFolders.vue')['default']
|
||||||
|
ShareFreeTime: typeof import('./src/components/user/Calendars/ShareFreeTime.vue')['default']
|
||||||
|
TimezoneSelect: typeof import('./src/components/common/TimezoneSelect.vue')['default']
|
||||||
|
Users: typeof import('./src/components/admin/domains/userdb/Users.vue')['default']
|
||||||
|
WorkDaysSelect: typeof import('./src/components/common/WorkDaysSelect.vue')['default']
|
||||||
|
WorkHoursRangePicker: typeof import('./src/components/common/WorkHoursRangePicker.vue')['default']
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
|
@ -1,13 +1,13 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<link rel="icon" href="/favicon.ico">
|
||||||
<title>myproject</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</head>
|
<title>Tegu</title>
|
||||||
<body>
|
</head>
|
||||||
<div id="app"></div>
|
<body>
|
||||||
<script src="./src/main.ts" type="module"></script>
|
<div id="app"></div>
|
||||||
</body>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,21 +1,34 @@
|
||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "js",
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --port 7777",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build --force"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.2.37"
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"ant-design-vue": "^4.2.3",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.13.1",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^3.0.3",
|
"@tsconfig/node20": "^20.1.4",
|
||||||
"typescript": "^4.6.4",
|
"@types/node": "^20.12.5",
|
||||||
"vite": "^3.0.7",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"vue-tsc": "^1.8.27",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"@babel/types": "^7.18.10"
|
"less": "^4.3.0",
|
||||||
|
"less-loader": "^12.2.0",
|
||||||
|
"npm-run-all2": "^6.1.2",
|
||||||
|
"typescript": "~5.4.0",
|
||||||
|
"vite": "^5.2.8",
|
||||||
|
"vue-tsc": "^2.0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
bb7ffb87329c9ad4990374471d4ce9a4
|
5b1aa7f7c882c7e08d0d390c73602d71
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -1,22 +1,83 @@
|
||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
import HelloWorld from "./components/HelloWorld.vue";
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { RouterLink, RouterView } from "vue-router";
|
||||||
|
import zhCN from "ant-design-vue/es/locale/zh_CN";
|
||||||
|
import ru from "ant-design-vue/es/locale/ru_RU";
|
||||||
|
import en from "ant-design-vue/es/locale/en_GB";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import "dayjs/locale/ru";
|
||||||
|
import "dayjs/locale/en";
|
||||||
|
import "dayjs/locale/zh-cn";
|
||||||
|
import updateLocale from "dayjs/plugin/updateLocale";
|
||||||
|
import localeData from "dayjs/plugin/localeData";
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
locale: any;
|
||||||
|
}>({
|
||||||
|
locale: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
dayjs.extend(updateLocale);
|
||||||
|
dayjs.extend(localeData);
|
||||||
|
|
||||||
|
let locales = getBrowserLocales({ languageCodeOnly: true });
|
||||||
|
let locale = "en";
|
||||||
|
if (locales && locales.length > 0) {
|
||||||
|
locale = locales[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
dayjs.updateLocale(locale, {
|
||||||
|
weekStart: 1,
|
||||||
|
});
|
||||||
|
dayjs.locale(locale);
|
||||||
|
switch (locale) {
|
||||||
|
case "ru":
|
||||||
|
page.locale = ru;
|
||||||
|
break;
|
||||||
|
case "zh":
|
||||||
|
page.locale = zhCN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
page.locale = en;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getBrowserLocales(options = {}) {
|
||||||
|
const defaultOptions = {
|
||||||
|
languageCodeOnly: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const opt = {
|
||||||
|
...defaultOptions,
|
||||||
|
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const browserLocales =
|
||||||
|
navigator.languages === undefined
|
||||||
|
? [navigator.language]
|
||||||
|
: navigator.languages;
|
||||||
|
|
||||||
|
if (!browserLocales) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return browserLocales.map((locale) => {
|
||||||
|
const trimmedLocale = locale.trim();
|
||||||
|
|
||||||
|
return opt.languageCodeOnly ? trimmedLocale.split(/-|_/)[0] : trimmedLocale;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img id="logo" alt="Wails logo" src="./assets/images/logo-universal.png" />
|
<a-config-provider :locale="page.locale">
|
||||||
<HelloWorld />
|
<div class="root-view">
|
||||||
|
<RouterView />
|
||||||
|
</div>
|
||||||
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style scoped></style>
|
||||||
#logo {
|
|
||||||
display: block;
|
|
||||||
width: 50%;
|
|
||||||
height: 50%;
|
|
||||||
margin: auto;
|
|
||||||
padding: 10% 0 0;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-origin: content-box;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
|
@ -1,93 +0,0 @@
|
||||||
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are
|
|
||||||
not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 136 KiB |
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,47 @@
|
||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
|
||||||
|
#nav.ant-menu-horizontal>.ant-menu-item:after,#nav.ant-menu-horizontal {
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-view {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input-number {
|
||||||
|
width: 50%;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-router-link {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-view {
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
max-height: calc(100vh - 70px);
|
||||||
|
overflow: auto;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bw-list-panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-div {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.middle-of-screen {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
|
@ -1,71 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import {reactive} from 'vue'
|
|
||||||
import {Greet} from '../../wailsjs/go/main/App'
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
name: "",
|
|
||||||
resultText: "Please enter your name below 👇",
|
|
||||||
})
|
|
||||||
|
|
||||||
function greet() {
|
|
||||||
Greet(data.name).then(result => {
|
|
||||||
data.resultText = result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<div id="result" class="result">{{ data.resultText }}</div>
|
|
||||||
<div id="input" class="input-box">
|
|
||||||
<input id="name" v-model="data.name" autocomplete="off" class="input" type="text"/>
|
|
||||||
<button class="btn" @click="greet">Greet</button>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.result {
|
|
||||||
height: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
margin: 1.5rem auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box .btn {
|
|
||||||
width: 60px;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: none;
|
|
||||||
margin: 0 0 0 20px;
|
|
||||||
padding: 0 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box .btn:hover {
|
|
||||||
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box .input {
|
|
||||||
border: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
outline: none;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
padding: 0 10px;
|
|
||||||
background-color: rgba(240, 240, 240, 1);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box .input:hover {
|
|
||||||
border: none;
|
|
||||||
background-color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-box .input:focus {
|
|
||||||
border: none;
|
|
||||||
background-color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<a href="https://mbk-lab.ru/en/" rel="home">
|
||||||
|
<a-flex style="height: 45px" justify="center" align="center">
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
style="margin-right: 4px; margin-top: 4px"
|
||||||
|
height="51px"
|
||||||
|
src="@/assets/logo-200x200.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="color: #2a4861">
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
height: 51px;
|
||||||
|
margin-top: -8px;
|
||||||
|
margin-bottom: -16px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
TEGU
|
||||||
|
</div>
|
||||||
|
<div style="font-size: small">{{ props.version }}</div>
|
||||||
|
</div>
|
||||||
|
</a-flex>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Flex } from "ant-design-vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
version: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div class="not-implemented">
|
||||||
|
<div class="content">
|
||||||
|
<div class="icon">🚧</div>
|
||||||
|
<h1>Not Implemented Yet</h1>
|
||||||
|
<p class="message">This feature is currently under development.</p>
|
||||||
|
<p class="sub-message">Please check back later for updates.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.not-implemented {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #2c3e50;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
color: #34495e;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-message {
|
||||||
|
color: #7f8c8d;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<a-result
|
||||||
|
status="404"
|
||||||
|
title="404"
|
||||||
|
:sub-title="t('common.not_found.page_not_found')"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<a-button v-if="page.authed" type="primary" @click="backHome">{{
|
||||||
|
t("common.not_found.return")
|
||||||
|
}}</a-button>
|
||||||
|
</template>
|
||||||
|
</a-result>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import router from "@/router";
|
||||||
|
import {
|
||||||
|
RouteAdminDashboard,
|
||||||
|
RouteLogin,
|
||||||
|
RouteUserProfile,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
authed: boolean;
|
||||||
|
}>({
|
||||||
|
authed: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.authed = useAuthStore().authed;
|
||||||
|
});
|
||||||
|
|
||||||
|
function backHome() {
|
||||||
|
if (useAuthStore().isAdmin) {
|
||||||
|
return router.push(RouteAdminDashboard);
|
||||||
|
} else {
|
||||||
|
return router.push(RouteUserProfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,465 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: inherit">
|
||||||
|
<a-row :gutter="[16, 16]" justify="space-around">
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6">
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title>
|
||||||
|
{{ t("components.admin.dashboard.license.title") }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.license.developer')"
|
||||||
|
>{{ page.licenseBlock.Developer }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.license.version')"
|
||||||
|
>
|
||||||
|
{{ page.licenseBlock.Version }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.license.license')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
page.contentLoaded &&
|
||||||
|
(page.licenseBlock.Expired ||
|
||||||
|
page.licenseBlock.Mismatch ||
|
||||||
|
!page.licenseBlock.License)
|
||||||
|
? 'warn'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.licenseBlock.Mismatch
|
||||||
|
? t("components.admin.dashboard.license.license_mismatch")
|
||||||
|
: !page.licenseBlock.License
|
||||||
|
? t("components.admin.dashboard.license.license_empty")
|
||||||
|
: page.licenseBlock.License
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6">
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title>
|
||||||
|
{{ t("components.admin.dashboard.mailboxes.title") }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.mailboxes.license_allowed')"
|
||||||
|
>{{ page.mailboxBlock.Total }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.mailboxes.used')"
|
||||||
|
>{{ page.mailboxBlock.Used }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.mailboxes.remains')"
|
||||||
|
>
|
||||||
|
<div :class="page.mailboxBlock.LowMailboxes ? 'warn' : ''">
|
||||||
|
{{ page.mailboxBlock.Remains }}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.mailboxes.archived')"
|
||||||
|
>
|
||||||
|
{{ page.mailboxBlock.Archived }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
v-if="page.mailboxBlock.UsedLastMonth"
|
||||||
|
:label="t('components.admin.dashboard.mailboxes.used_last_month')"
|
||||||
|
>{{ page.mailboxBlock.UsedLastMonth }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
v-if="page.mailboxBlock.UsedCurrentMonth"
|
||||||
|
:label="
|
||||||
|
t('components.admin.dashboard.mailboxes.used_current_month')
|
||||||
|
"
|
||||||
|
>{{ page.mailboxBlock.UsedCurrentMonth }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="12">
|
||||||
|
<a-card
|
||||||
|
hoverable
|
||||||
|
style="height: 100%"
|
||||||
|
:tab-list="tabListNoTitle"
|
||||||
|
:active-tab-key="page.detailsKey"
|
||||||
|
@tabChange="(key: string) =>page.detailsKey = key"
|
||||||
|
>
|
||||||
|
<div v-if="page.detailsKey === 'imap'">
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.imap.server')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.imapBlock.Server }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.imapBlock.Server)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.imap.port')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.imapBlock.Port }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.imapBlock.Port.toString())"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.imap.encryption')"
|
||||||
|
>{{ page.imapBlock.SSL }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.imap.auth')"
|
||||||
|
>{{ t("components.admin.dashboard.imap.auth_text") }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<div v-if="page.detailsKey === 'smtp'">
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.smtp.server')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.smtpBlock.Server }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.smtpBlock.Server)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.smtp.port')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.smtpBlock.Port }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.smtpBlock.Port.toString())"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.smtp.encryption')"
|
||||||
|
>{{ page.smtpBlock.SSL }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.smtp.auth')"
|
||||||
|
>{{ t("components.admin.dashboard.smtp.auth_text") }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<div v-if="page.detailsKey === 'queue'">
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.smtp_queue.type')"
|
||||||
|
>{{ page.smtpQueueBlock.Type }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
v-if="page.smtpQueueBlock.Host"
|
||||||
|
:label="t('components.admin.dashboard.smtp_queue.host')"
|
||||||
|
>
|
||||||
|
{{ page.smtpQueueBlock.Host }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
<div v-if="page.detailsKey === 'antispam'">
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.antispam.type')"
|
||||||
|
>
|
||||||
|
{{ page.antispamBlock.Type }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.antispam.status')"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.antispamBlock.Enabled
|
||||||
|
? t("components.admin.dashboard.antispam.status_enabled")
|
||||||
|
: t("components.admin.dashboard.antispam.status_disabled")
|
||||||
|
}}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.antispam.host')"
|
||||||
|
>
|
||||||
|
{{ page.antispamBlock.Host }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="page.detailsKey === 'dav'">
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.dav.books')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.davBlock.BooksUrl }}
|
||||||
|
<a-button
|
||||||
|
v-if="page.davBlock.BooksUrl.startsWith('http')"
|
||||||
|
@click="copyToClipboard(page.davBlock.BooksUrl)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.dav.calendars')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.davBlock.CalsUrl }}
|
||||||
|
<a-button
|
||||||
|
v-if="page.davBlock.CalsUrl.startsWith('http')"
|
||||||
|
@click="copyToClipboard(page.davBlock.CalsUrl)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<br />
|
||||||
|
<a-row :gutter="[16, 16]">
|
||||||
|
<a-col
|
||||||
|
v-for="domain in page.domains"
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="24"
|
||||||
|
:lg="12"
|
||||||
|
:xl="8"
|
||||||
|
>
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title> {{ domain.Domain }} </template>
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.domain.mailstorage')"
|
||||||
|
>{{ domain.MailStorage }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.domain.userdb_providers')"
|
||||||
|
>
|
||||||
|
<a-descriptions size="small" :column="1">
|
||||||
|
<a-descriptions-item v-for="prov in domain.UserDBProviders">
|
||||||
|
{{ prov }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.admin.dashboard.domain.dns_record')"
|
||||||
|
><a @click="page.dnsModal = { show: true, text: domain.DNS }">{{
|
||||||
|
t("components.admin.dashboard.domain.dns_show")
|
||||||
|
}}</a>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row justify="space-around">
|
||||||
|
<a-col> </a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
<a-modal style="width: 70%" v-model:open="page.dnsModal.show">
|
||||||
|
<a-textarea
|
||||||
|
style="text-wrap: nowrap; height: 200px"
|
||||||
|
v-model:value="page.dnsModal.text"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="copyToClipboard(page.dnsModal.text)">
|
||||||
|
<CopyOutlined />
|
||||||
|
{{ t("components.admin.dashboard.domain.dns_copy") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { copyToClipboard } from "@/composables/misc";
|
||||||
|
import { CopyOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const tabListNoTitle = [
|
||||||
|
{
|
||||||
|
key: "imap",
|
||||||
|
tab: t("components.admin.dashboard.imap.title"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "smtp",
|
||||||
|
tab: t("components.admin.dashboard.smtp.title"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "queue",
|
||||||
|
tab: t("components.admin.dashboard.smtp_queue.title"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "antispam",
|
||||||
|
tab: t("components.admin.dashboard.antispam.title"),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: "dav",
|
||||||
|
tab: t("components.admin.dashboard.dav.title"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
dnsModal: {
|
||||||
|
show: boolean;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
contentLoaded: boolean;
|
||||||
|
detailsKey: string;
|
||||||
|
licenseBlock: {
|
||||||
|
Developer: string;
|
||||||
|
License: string;
|
||||||
|
Expired: boolean;
|
||||||
|
Mismatch: boolean;
|
||||||
|
Version: string;
|
||||||
|
};
|
||||||
|
mailboxBlock: {
|
||||||
|
Total: number;
|
||||||
|
Used: number;
|
||||||
|
Remains: number;
|
||||||
|
Archived: number;
|
||||||
|
LowMailboxes: boolean;
|
||||||
|
UsedLastMonth: number;
|
||||||
|
UsedCurrentMonth: number;
|
||||||
|
};
|
||||||
|
smtpQueueBlock: {
|
||||||
|
Type: string;
|
||||||
|
Host: string;
|
||||||
|
};
|
||||||
|
antispamBlock: {
|
||||||
|
Type: string;
|
||||||
|
Enabled: boolean;
|
||||||
|
Host: string;
|
||||||
|
};
|
||||||
|
davBlock: {
|
||||||
|
BooksUrl: string;
|
||||||
|
CalsUrl: string;
|
||||||
|
};
|
||||||
|
imapBlock: {
|
||||||
|
Server: string;
|
||||||
|
Port: number;
|
||||||
|
SSL: string;
|
||||||
|
};
|
||||||
|
smtpBlock: {
|
||||||
|
Server: string;
|
||||||
|
Port: number;
|
||||||
|
SSL: string;
|
||||||
|
};
|
||||||
|
domains: {
|
||||||
|
Domain: string;
|
||||||
|
MailStorage: string;
|
||||||
|
UserDBProviders: string[];
|
||||||
|
DNS: string;
|
||||||
|
}[];
|
||||||
|
}>({
|
||||||
|
detailsKey: "imap",
|
||||||
|
licenseBlock: {
|
||||||
|
Developer: "",
|
||||||
|
License: "",
|
||||||
|
Expired: false,
|
||||||
|
Mismatch: false,
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
mailboxBlock: {
|
||||||
|
Total: 0,
|
||||||
|
Used: 0,
|
||||||
|
Remains: 0,
|
||||||
|
Archived: 0,
|
||||||
|
LowMailboxes: false,
|
||||||
|
UsedLastMonth: 0,
|
||||||
|
UsedCurrentMonth: 0,
|
||||||
|
},
|
||||||
|
smtpQueueBlock: {
|
||||||
|
Type: "",
|
||||||
|
Host: "",
|
||||||
|
},
|
||||||
|
antispamBlock: {
|
||||||
|
Type: "",
|
||||||
|
Enabled: false,
|
||||||
|
Host: "",
|
||||||
|
},
|
||||||
|
davBlock: {
|
||||||
|
BooksUrl: "",
|
||||||
|
CalsUrl: "",
|
||||||
|
},
|
||||||
|
imapBlock: {
|
||||||
|
Server: "",
|
||||||
|
Port: 0,
|
||||||
|
SSL: "",
|
||||||
|
},
|
||||||
|
smtpBlock: {
|
||||||
|
Server: "",
|
||||||
|
Port: 0,
|
||||||
|
SSL: "",
|
||||||
|
},
|
||||||
|
contentLoaded: false,
|
||||||
|
domains: [],
|
||||||
|
dnsModal: {
|
||||||
|
show: false,
|
||||||
|
text: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/admin/dashboard");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
|
||||||
|
page.licenseBlock = res.data.LicenseBlock;
|
||||||
|
page.mailboxBlock = res.data.MailboxBlock;
|
||||||
|
page.smtpQueueBlock = res.data.SmtpQueueBlock;
|
||||||
|
page.antispamBlock = res.data.AntispamBlock;
|
||||||
|
page.davBlock = res.data.DavBlock;
|
||||||
|
page.smtpBlock = res.data.SmtpBlock;
|
||||||
|
page.imapBlock = res.data.ImapBlock;
|
||||||
|
page.domains = res.data.Domains;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.warn {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.add.Domain')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.Domain"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.add.Type')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-select v-model:value="page.new.Type">
|
||||||
|
<a-select-option value="pgStorage">PostgreSQL</a-select-option>
|
||||||
|
<a-select-option value="maildirStorage">Maildir</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.new.Type || !page.new.Domain"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="save"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.add.create") }}
|
||||||
|
</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import router from "@/router";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageSettings,
|
||||||
|
} from "@/router/consts";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
interface ConnParams {
|
||||||
|
Host: string;
|
||||||
|
Port: number;
|
||||||
|
Db: string;
|
||||||
|
User: string;
|
||||||
|
Pass: string;
|
||||||
|
MaxConn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PathParams {
|
||||||
|
DatabaseDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
new: {
|
||||||
|
Type: string;
|
||||||
|
Domain: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
loading: boolean;
|
||||||
|
}>({
|
||||||
|
loading: false,
|
||||||
|
new: {
|
||||||
|
Type: "",
|
||||||
|
Domain: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const res = await apiFetch("/admin/domains/add", {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.add.success"));
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
path: RouteAdminDomainsDomainMailStorageSettings.replace(
|
||||||
|
DomainPlaceholder,
|
||||||
|
res.data
|
||||||
|
),
|
||||||
|
hash: "#refresh",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
width: 500px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,156 @@
|
||||||
|
<template style="max-width: 100vw">
|
||||||
|
<div>
|
||||||
|
<a-card v-if="page.contentLoaded" class="panel-content">
|
||||||
|
<template v-if="page.dkim !== ''">
|
||||||
|
<a-card
|
||||||
|
style="line-break: anywhere"
|
||||||
|
:title="$t('components.admin.domains.dkim.title')"
|
||||||
|
>
|
||||||
|
{{ page.dkim }}
|
||||||
|
</a-card>
|
||||||
|
<br />
|
||||||
|
<a-button size="large" class="save-button" danger @click="deleteDKIM">
|
||||||
|
{{ $t("components.admin.domains.dkim.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form
|
||||||
|
:label-col="labelCol"
|
||||||
|
:wrapper-col="wrapperCol"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-form-item :label="$t('components.admin.domains.dkim.selector')">
|
||||||
|
<a-input v-model:value="page.new.Selector"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="$t('components.admin.domains.dkim.key_size')">
|
||||||
|
<a-input-number
|
||||||
|
:min="0"
|
||||||
|
:addon-after="$t('common.suffixes.bits')"
|
||||||
|
class="form-input-number"
|
||||||
|
v-model:value="page.new.Bits"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="save"
|
||||||
|
:loading="page.loading"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.dkim.create") }}
|
||||||
|
</a-button>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import router from "@/router";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageSettings,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
domain: string;
|
||||||
|
dkim: string;
|
||||||
|
new: {
|
||||||
|
Selector: string;
|
||||||
|
Bits: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
loading: boolean;
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
domain: "",
|
||||||
|
dkim: "",
|
||||||
|
loading: false,
|
||||||
|
new: {
|
||||||
|
Selector: "mail",
|
||||||
|
Bits: 1024,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/dkim`);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.dkim = res.data;
|
||||||
|
page.contentLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/dkim`, {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.dkim.success"));
|
||||||
|
|
||||||
|
page.dkim = res.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteDKIM() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/dkim`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.dkim = res.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
width: 30%;
|
||||||
|
min-width: 150px;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Rules
|
||||||
|
:get-get-url="getUrl"
|
||||||
|
:get-delete-url="getUrl"
|
||||||
|
:get-save-url="getUrl"
|
||||||
|
:get-move-url="moveUrl"
|
||||||
|
:get-enable-url="enableUrl"
|
||||||
|
:conditions="conditionsList"
|
||||||
|
:actions="actionsList"
|
||||||
|
>
|
||||||
|
<template #condition-value="valueProps">
|
||||||
|
<ConditionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</ConditionValue>
|
||||||
|
</template>
|
||||||
|
<template #action-value="valueProps">
|
||||||
|
<ActionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
:get-folders-url="foldersUrl"
|
||||||
|
>
|
||||||
|
</ActionValue>
|
||||||
|
</template>
|
||||||
|
</Rules>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ConditionValue from "@/components/common/rules/incoming/Conditions.vue";
|
||||||
|
import ActionValue from "@/components/common/rules/incoming/Actions.vue";
|
||||||
|
import { conditionsList } from "@/components/common/rules/incoming/Conditions.vue";
|
||||||
|
import { actionsList } from "@/components/common/rules/incoming/Actions.vue";
|
||||||
|
import Rules from "@/components/common/rules/Rules.vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
function getUrl(): string {
|
||||||
|
return `/admin/domains/${route.params.domain as string}/incoming_rules`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUrl(): string {
|
||||||
|
return `/admin/domains/${route.params.domain as string}/incoming_rules/move`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function foldersUrl(): string {
|
||||||
|
return `/admin/domains/${
|
||||||
|
route.params.domain as string
|
||||||
|
}/incoming_rules/folders`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableUrl(): string {
|
||||||
|
return `/admin/domains/${
|
||||||
|
route.params.domain as string
|
||||||
|
}/incoming_rules/enable`;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,344 @@
|
||||||
|
<template style="max-width: 100vw">
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form
|
||||||
|
:class="page.status.Active ? 'disabled-div' : ''"
|
||||||
|
:label-col="labelCol"
|
||||||
|
:wrapper-col="wrapperCol"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="t(`components.admin.domains.mails_perm_deletion.filter`)"
|
||||||
|
>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<a-flex gap="middle" vertical>
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-radio-group v-model:value="page.status.Condition.Op">
|
||||||
|
<a-radio-button value="and">{{
|
||||||
|
t("components.common.rules.edit_conditions_all")
|
||||||
|
}}</a-radio-button>
|
||||||
|
<a-radio-button value="or">{{
|
||||||
|
t("components.common.rules.edit_conditions_any")
|
||||||
|
}}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px"> </a-col>
|
||||||
|
</a-row>
|
||||||
|
<template v-if="page.renderConditions">
|
||||||
|
<div v-for="(condition, i) in page.status.Condition.Conditions">
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<Conditions
|
||||||
|
:value="condition"
|
||||||
|
:update="(newVal: string[]) => updateCondition(i, newVal)"
|
||||||
|
:conditions="conditionsListForMailDeletion"
|
||||||
|
>
|
||||||
|
<template #condition-value="valueProps">
|
||||||
|
<ConditionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</ConditionValue>
|
||||||
|
</template>
|
||||||
|
</Conditions>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px">
|
||||||
|
<a-button
|
||||||
|
:disabled="i === 0"
|
||||||
|
danger
|
||||||
|
@click="deleteCondition(i)"
|
||||||
|
>
|
||||||
|
{{ t("components.common.rules.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-button
|
||||||
|
style="width: fit-content; margin: auto"
|
||||||
|
@click="addCondition"
|
||||||
|
>
|
||||||
|
<PlusOutlined> </PlusOutlined>
|
||||||
|
{{ t("components.common.rules.add_condition") }}
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px"> </a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-flex>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<div>
|
||||||
|
<a-space style="width: 100%" direction="vertical">
|
||||||
|
<a-alert
|
||||||
|
v-if="page.status.Error"
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
:message="page.status.Error"
|
||||||
|
/>
|
||||||
|
<a-alert
|
||||||
|
v-if="page.status.Percent === 100 && !page.status.OnlyFind"
|
||||||
|
type="success"
|
||||||
|
showIcon
|
||||||
|
:message="t(`components.admin.domains.mails_perm_deletion.success`)"
|
||||||
|
/>
|
||||||
|
<a-progress
|
||||||
|
v-if="
|
||||||
|
(page.status.Count || page.status.Percent) &&
|
||||||
|
!page.status.OnlyFind
|
||||||
|
"
|
||||||
|
:percent="page.status.Percent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="2"> </a-col>
|
||||||
|
<a-col>
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
:type="page.status.Count === 0 ? `primary` : undefined"
|
||||||
|
:loading="page.status.OnlyFind && page.status.Active"
|
||||||
|
:disabled="page.status.Active"
|
||||||
|
@click="start(true)"
|
||||||
|
>
|
||||||
|
{{ t("components.admin.domains.mails_perm_deletion.find") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
danger
|
||||||
|
:type="`primary`"
|
||||||
|
:disabled="page.status.Active || !page.status.Count"
|
||||||
|
:loading="!page.status.OnlyFind && page.status.Active"
|
||||||
|
@click="confirmDelete"
|
||||||
|
>
|
||||||
|
{{ t("components.admin.domains.mails_perm_deletion.delete") }}
|
||||||
|
{{ page.status.Count }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, onUnmounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import Conditions from "@/components/common/rules/Conditions.vue";
|
||||||
|
import ConditionValue from "@/components/common/rules/incoming/Conditions.vue";
|
||||||
|
import { conditionsListForMailDeletion } from "@/components/common/rules/incoming/Conditions.vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import router from "@/router";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageSettings,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
UpOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import type { Condition } from "@/components/common/rules/Conditions.vue";
|
||||||
|
import { nextTick } from "vue";
|
||||||
|
import { getStatusClassNames } from "ant-design-vue/es/_util/statusUtils";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 3, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 21, style: { "text-align": "center" } };
|
||||||
|
|
||||||
|
interface ConditionStruct {
|
||||||
|
Op: string;
|
||||||
|
Conditions: string[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteStatus {
|
||||||
|
Condition: ConditionStruct;
|
||||||
|
OnlyFind: boolean;
|
||||||
|
Active: boolean;
|
||||||
|
Count: number;
|
||||||
|
Percent: number;
|
||||||
|
Error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
// @ts-ignore
|
||||||
|
timeout: NodeJS.Timeout | undefined;
|
||||||
|
interval: number | undefined;
|
||||||
|
domain: string;
|
||||||
|
loading: boolean;
|
||||||
|
valid: boolean;
|
||||||
|
renderConditions: boolean;
|
||||||
|
|
||||||
|
status: DeleteStatus;
|
||||||
|
}>({
|
||||||
|
interval: 1000,
|
||||||
|
timeout: undefined,
|
||||||
|
domain: "",
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
renderConditions: true,
|
||||||
|
valid: true,
|
||||||
|
|
||||||
|
status: {
|
||||||
|
Condition: {
|
||||||
|
Op: "and",
|
||||||
|
Conditions: [[...conditionsListForMailDeletion[0].Args]],
|
||||||
|
},
|
||||||
|
OnlyFind: true,
|
||||||
|
Active: false,
|
||||||
|
Count: 0,
|
||||||
|
Percent: 0,
|
||||||
|
Error: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
page.interval = undefined;
|
||||||
|
clearTimeout(page.timeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
clearTimeout(page.timeout);
|
||||||
|
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mails_perm_deletion`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
page.status = res.data as DeleteStatus;
|
||||||
|
page.renderConditions = false;
|
||||||
|
validate();
|
||||||
|
nextTick(() => {
|
||||||
|
page.renderConditions = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (page.status.Active && page.interval) {
|
||||||
|
page.timeout = setTimeout(get, page.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(onlyFind: boolean) {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
page.status.OnlyFind = onlyFind;
|
||||||
|
if (page.status.OnlyFind) {
|
||||||
|
page.status.Count = 0;
|
||||||
|
}
|
||||||
|
page.status.Active = true;
|
||||||
|
page.status.Error = "";
|
||||||
|
page.status.Percent = 0;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mails_perm_deletion`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.status,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCondition(i: number, newVal: string[]) {
|
||||||
|
page.status.Condition.Conditions[i] = newVal;
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCondition(i: number) {
|
||||||
|
page.status.Condition.Conditions.splice(i, 1);
|
||||||
|
page.renderConditions = false;
|
||||||
|
validate();
|
||||||
|
nextTick(() => {
|
||||||
|
page.renderConditions = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
let ok = true;
|
||||||
|
|
||||||
|
page.status.Condition.Conditions.forEach((args) => {
|
||||||
|
let cond = conditionsListForMailDeletion.find(
|
||||||
|
(a: Condition) => a.Value === args[0]
|
||||||
|
) as Condition;
|
||||||
|
if (cond.ValIdx != -1 && args[cond.ValIdx] === "") {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.valid = ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCondition() {
|
||||||
|
page.status.Condition.Conditions.push(conditionsListForMailDeletion[0].Args);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
Modal.confirm({
|
||||||
|
title:
|
||||||
|
t("components.admin.domains.mails_perm_deletion.delete") +
|
||||||
|
page.status.Count,
|
||||||
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
content: t("components.admin.domains.mails_perm_deletion.delete_text"),
|
||||||
|
okText: t("components.admin.domains.mails_perm_deletion.ok"),
|
||||||
|
okType: "danger",
|
||||||
|
cancelText: t("components.admin.domains.mails_perm_deletion.cancel"),
|
||||||
|
async onOk() {
|
||||||
|
start(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 800px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
width: 30%;
|
||||||
|
min-width: 150px;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item :label="$t('components.admin.domains.migration.enabled')">
|
||||||
|
<a-switch v-model:checked="page.settings.Enabled"> </a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="page.settings.Enabled">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.migration.virtual_domain')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.VirtualDomain"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.migration.other_servers')"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="page.settings.OtherServersStr">
|
||||||
|
</a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.migration.local_emails')"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="page.settings.LocalEmailsStr">
|
||||||
|
</a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-space style="display: flex; justify-content: center">
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
(page.settings.Enabled &&
|
||||||
|
(!page.settings.VirtualDomain ||
|
||||||
|
!page.settings.OtherServersStr)) ||
|
||||||
|
!page.contentLoaded
|
||||||
|
"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
style="width: fit-content"
|
||||||
|
@click="save"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.migration.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
contentLoaded: boolean;
|
||||||
|
settings: {
|
||||||
|
Enabled: boolean;
|
||||||
|
VirtualDomain: string;
|
||||||
|
OtherServers: string[];
|
||||||
|
OtherServersStr: string;
|
||||||
|
LocalEmails: string[];
|
||||||
|
LocalEmailsStr: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
contentLoaded: false,
|
||||||
|
settings: {
|
||||||
|
Enabled: false,
|
||||||
|
VirtualDomain: "",
|
||||||
|
OtherServers: [],
|
||||||
|
OtherServersStr: "",
|
||||||
|
LocalEmails: [],
|
||||||
|
LocalEmailsStr: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8, style: { "text-align": "right" } };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadTextArea() {
|
||||||
|
page.settings.OtherServersStr = page.settings.OtherServers.join("\n");
|
||||||
|
page.settings.LocalEmailsStr = page.settings.LocalEmails.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTextArea() {
|
||||||
|
page.settings.OtherServers = page.settings.OtherServersStr.split("\n");
|
||||||
|
page.settings.LocalEmails = page.settings.LocalEmailsStr.split("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/migration`);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.settings = res.data;
|
||||||
|
page.contentLoaded = true;
|
||||||
|
|
||||||
|
loadTextArea();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
saveTextArea();
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/migration`, {
|
||||||
|
method: "POST",
|
||||||
|
body: page.settings,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.migration.success"));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 40%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.input-number {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.save-button {
|
||||||
|
width: 30%;
|
||||||
|
min-width: 150px;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Rules
|
||||||
|
:get-get-url="getUrl"
|
||||||
|
:get-delete-url="getUrl"
|
||||||
|
:get-save-url="getUrl"
|
||||||
|
:get-move-url="moveUrl"
|
||||||
|
:get-enable-url="enableUrl"
|
||||||
|
:conditions="conditionsList"
|
||||||
|
:actions="actionsList"
|
||||||
|
>
|
||||||
|
<template #condition-value="valueProps">
|
||||||
|
<ConditionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
:get-groups-url="groupsUrl"
|
||||||
|
>
|
||||||
|
</ConditionValue>
|
||||||
|
</template>
|
||||||
|
<template #action-value="valueProps">
|
||||||
|
<ActionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
:get-groups-url="groupsUrl"
|
||||||
|
:get-group-emails-url="groupEmailsUrl"
|
||||||
|
>
|
||||||
|
</ActionValue>
|
||||||
|
</template>
|
||||||
|
</Rules>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ConditionValue from "@/components/common/rules/outgoing/Conditions.vue";
|
||||||
|
import ActionValue from "@/components/common/rules/outgoing/Actions.vue";
|
||||||
|
import { conditionsList } from "@/components/common/rules/outgoing/Conditions.vue";
|
||||||
|
import { actionsList } from "@/components/common/rules/outgoing/Actions.vue";
|
||||||
|
import Rules from "@/components/common/rules/Rules.vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
function getUrl(): string {
|
||||||
|
return `/admin/domains/${route.params.domain as string}/outgoing_rules`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUrl(): string {
|
||||||
|
return `/admin/domains/${route.params.domain as string}/outgoing_rules/move`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupsUrl(): string {
|
||||||
|
return `/admin/domains/${
|
||||||
|
route.params.domain as string
|
||||||
|
}/outgoing_rules/groups`;
|
||||||
|
}
|
||||||
|
function groupEmailsUrl(): string {
|
||||||
|
return `/admin/domains/${
|
||||||
|
route.params.domain as string
|
||||||
|
}/outgoing_rules/group_emails`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableUrl(): string {
|
||||||
|
return `/admin/domains/${
|
||||||
|
route.params.domain as string
|
||||||
|
}/outgoing_rules/enable`;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<AddressBooks
|
||||||
|
v-if="page.loaded"
|
||||||
|
:domain="page.domain"
|
||||||
|
user="admin"
|
||||||
|
></AddressBooks>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AddressBooks from "@/components/common/AddressBooks.vue";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
loaded: boolean;
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
page.loaded = true;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<Calendars v-if="page.loaded" :domain="page.domain" user="admin"></Calendars>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Calendars from "@/components/common/Calendars.vue";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
loaded: boolean;
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
page.loaded = true;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<SharedFolders
|
||||||
|
v-if="page.loaded"
|
||||||
|
:domain="page.domain"
|
||||||
|
user="admin"
|
||||||
|
></SharedFolders>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SharedFolders from "@/components/common/SharedFolders.vue";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
loaded: boolean;
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
page.loaded = true;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,471 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form
|
||||||
|
style="margin-bottom: -24px"
|
||||||
|
:label-col="labelCol"
|
||||||
|
:wrapper-col="wrapperCol"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.cidr_access.policy.enabled')"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
:disabled="page.enabled === undefined"
|
||||||
|
@change="setEnabled"
|
||||||
|
v-model:checked="page.enabled"
|
||||||
|
>
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
<br />
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.admin.domains.cidr_access.policy.search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal(undefined)">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{
|
||||||
|
t("components.admin.domains.cidr_access.policy.add_policy")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.withSearch"
|
||||||
|
:title="
|
||||||
|
t(
|
||||||
|
'components.admin.domains.cidr_access.policy.delete_found'
|
||||||
|
) + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.domains.cidr_access.policy.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.domains.cidr_access.policy.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deletePolicy(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.domains.cidr_access.policy.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.admin.domains.cidr_access.policy.delete_all') +
|
||||||
|
'?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.domains.cidr_access.policy.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.domains.cidr_access.policy.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deletePolicy(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.domains.cidr_access.policy.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAddModal(record)">{{
|
||||||
|
t("components.admin.domains.cidr_access.policy.properties")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t('components.admin.domains.cidr_access.policy.delete') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.domains.cidr_access.policy.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.domains.cidr_access.policy.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deletePolicy(record.GroupName)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.admin.domains.cidr_access.policy.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 900px"
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="
|
||||||
|
page.add.isUpdate
|
||||||
|
? t('components.admin.domains.cidr_access.policy.update_policy') +
|
||||||
|
page.add.policy.GroupName
|
||||||
|
: t('components.admin.domains.cidr_access.policy.add_policy')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-space direction="vertical">
|
||||||
|
<a-select
|
||||||
|
v-if="!page.add.isUpdate"
|
||||||
|
:placeholder="
|
||||||
|
t('components.admin.domains.cidr_access.policy.select_group')
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="page.add.avaliableGroups.length === 0"
|
||||||
|
v-model:value="page.add.policy.GroupName"
|
||||||
|
:options="page.add.avaliableGroups"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
<a-transfer
|
||||||
|
v-model:target-keys="page.add.pools_ids"
|
||||||
|
:data-source="page.pools"
|
||||||
|
:render="(item: any) => item.name"
|
||||||
|
show-search
|
||||||
|
:titles="[' (avaliable pools)', ' (active pools)']"
|
||||||
|
:list-style="{
|
||||||
|
width: '405px',
|
||||||
|
height: '320px',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.add.policy.GroupName || page.add.pools_ids.length == 0"
|
||||||
|
@click="add"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.add.isUpdate
|
||||||
|
? t("components.admin.domains.cidr_access.policy.update_policy") +
|
||||||
|
page.add.policy.GroupName
|
||||||
|
: t("components.admin.domains.cidr_access.policy.add_policy")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8, style: { "text-align": "right" } };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.cidr_access.policy.col_name"),
|
||||||
|
dataIndex: "GroupName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.cidr_access.policy.col_count"),
|
||||||
|
dataIndex: "Count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Pool {
|
||||||
|
ID: number;
|
||||||
|
Name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Policy {
|
||||||
|
GroupName?: string;
|
||||||
|
Pools: Pool[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransferElem {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
pools: TransferElem[];
|
||||||
|
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
pools_ids: string[];
|
||||||
|
isUpdate: boolean;
|
||||||
|
avaliableGroups: any[];
|
||||||
|
policy: Policy;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
enabled: undefined,
|
||||||
|
pools: [],
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
pools_ids: [],
|
||||||
|
isUpdate: false,
|
||||||
|
avaliableGroups: [],
|
||||||
|
policy: {
|
||||||
|
GroupName: "",
|
||||||
|
Pools: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
getEnabled();
|
||||||
|
getPools();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getPools() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/cidr_access/pools`);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.pools = [];
|
||||||
|
page.pools = (res.data as Array<any>).map((e) => {
|
||||||
|
return {
|
||||||
|
key: String(e.ID),
|
||||||
|
name: e.Name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEnabled() {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/policy/enabled`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.enabled = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setEnabled() {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/policy/enabled`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Enabled: page.enabled,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/policy?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = (res.data as any[]).map((e) => {
|
||||||
|
return {
|
||||||
|
Count: (e.Pools as any[]).length,
|
||||||
|
...e,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
page.add.policy.Pools = page.add.pools_ids.map((e) => {
|
||||||
|
return {
|
||||||
|
ID: Number(e),
|
||||||
|
Name: "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/policy`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.add.policy,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal(policy: Policy | undefined) {
|
||||||
|
if (policy) {
|
||||||
|
page.add.policy.GroupName = policy.GroupName;
|
||||||
|
page.add.policy.Pools = policy.Pools;
|
||||||
|
page.add.isUpdate = true;
|
||||||
|
page.add.avaliableGroups = [{ value: policy.GroupName }];
|
||||||
|
page.add.pools_ids = policy.Pools.map((e) => {
|
||||||
|
return String(e.ID);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
page.add.policy.GroupName = undefined;
|
||||||
|
page.add.policy.Pools = [];
|
||||||
|
page.add.pools_ids = [];
|
||||||
|
page.add.isUpdate = false;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/policy/avaliable_groups`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.avaliableGroups = [];
|
||||||
|
page.add.avaliableGroups = (res.data as string[]).map((e: string) => {
|
||||||
|
return {
|
||||||
|
value: e,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePolicy(group: string | undefined) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/policy?` + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
GroupName: group,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,397 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.admin.domains.cidr_access.pools.search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ t("components.admin.domains.cidr_access.pools.add_pool") }}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.admin.domains.cidr_access.pools.delete_found') +
|
||||||
|
'?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.domains.cidr_access.pools.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.domains.cidr_access.pools.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deletePool(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.domains.cidr_access.pools.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.admin.domains.cidr_access.pools.delete_all') +
|
||||||
|
'?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.domains.cidr_access.pools.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.domains.cidr_access.pools.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deletePool(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.domains.cidr_access.pools.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showPropModal(record)">{{
|
||||||
|
t("components.admin.domains.cidr_access.pools.properties")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t('components.admin.domains.cidr_access.pools.delete') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.domains.cidr_access.pools.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.domains.cidr_access.pools.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deletePool(record.ID)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.admin.domains.cidr_access.pools.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 500px"
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="t('components.admin.domains.cidr_access.pools.add_pool')"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.cidr_access.pools.add_name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.pool.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.cidr_access.pools.add_ips')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
:placeholder="
|
||||||
|
$t('components.admin.domains.cidr_access.pools.add_ips_placeholder')
|
||||||
|
"
|
||||||
|
v-model:value="page.add.pool.IPsString"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.add.pool.Name || page.add.pool.IPsString == ''"
|
||||||
|
@click="add"
|
||||||
|
>
|
||||||
|
{{ t("components.admin.domains.cidr_access.pools.add_pool") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
style="width: 500px"
|
||||||
|
v-model:open="page.props.show"
|
||||||
|
:title="t('components.admin.domains.cidr_access.pools.properties')"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.cidr_access.pools.add_name')"
|
||||||
|
>
|
||||||
|
<a-input disabled v-model:value="page.props.pool.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.cidr_access.pools.add_ips')"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
:placeholder="
|
||||||
|
$t('components.admin.domains.cidr_access.pools.add_ips_placeholder')
|
||||||
|
"
|
||||||
|
v-model:value="page.props.pool.IPsString"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.props.pool.Name || page.props.pool.IPsString == ''"
|
||||||
|
@click="update"
|
||||||
|
>
|
||||||
|
{{ t("components.admin.domains.cidr_access.pools.update_pool") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.cidr_access.pools.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Pool {
|
||||||
|
Name: string;
|
||||||
|
IPs: string[];
|
||||||
|
IPsString: string;
|
||||||
|
ID: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
pool: Pool;
|
||||||
|
};
|
||||||
|
props: {
|
||||||
|
show: boolean;
|
||||||
|
pool: Pool;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
pool: {
|
||||||
|
ID: 0,
|
||||||
|
Name: "",
|
||||||
|
IPs: [],
|
||||||
|
IPsString: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
show: false,
|
||||||
|
pool: {
|
||||||
|
ID: 0,
|
||||||
|
Name: "",
|
||||||
|
IPs: [],
|
||||||
|
IPsString: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/pools?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
page.add.pool.IPs = page.add.pool.IPsString.split("\n");
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/pools`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.add.pool,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
page.props.pool.IPs = page.props.pool.IPsString.split("\n");
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/pools`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.props.pool,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.props.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showPropModal(record: any) {
|
||||||
|
page.props.pool = { ...record };
|
||||||
|
page.props.pool.IPsString = page.props.pool.IPs.join("\n");
|
||||||
|
page.props.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal() {
|
||||||
|
page.add.pool.Name = "";
|
||||||
|
page.add.pool.ID = 0;
|
||||||
|
page.add.pool.IPsString = "";
|
||||||
|
page.add.pool.IPs = [];
|
||||||
|
page.add.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deletePool(id: number | undefined) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/cidr_access/pools?` + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
ID: id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,158 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="open"
|
||||||
|
:title="
|
||||||
|
t('components.admin.domains.mailstorage.export.title') + ' ' + props.email
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form-item v-if="page.error">
|
||||||
|
<a-alert type="error" showIcon :message="page.error" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="page.duration">
|
||||||
|
<a-alert
|
||||||
|
type="success"
|
||||||
|
showIcon
|
||||||
|
:message="
|
||||||
|
t('components.admin.domains.mailstorage.export.success') +
|
||||||
|
page.duration
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
v-if="page.hostname"
|
||||||
|
:label="$t('components.admin.domains.mailstorage.export.hostname')"
|
||||||
|
>
|
||||||
|
<a-input :disabled="true" v-model:value="page.hostname"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.mailstorage.export.catalog')"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
:disabled="page.active || page.loading"
|
||||||
|
v-model:value="page.targetFolder"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-progress
|
||||||
|
v-if="page.active || page.percent !== 0"
|
||||||
|
:percent="page.percent"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
@click="start"
|
||||||
|
v-if="!page.active && !page.duration"
|
||||||
|
:disabled="page.loading"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{ t("components.admin.domains.mailstorage.export.start_export") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { onMounted, onUnmounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
email: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const open = defineModel("open");
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
// @ts-ignore
|
||||||
|
timeout: NodeJS.Timeout | undefined;
|
||||||
|
interval: number | undefined;
|
||||||
|
loading: boolean;
|
||||||
|
domain: string;
|
||||||
|
targetFolder: string;
|
||||||
|
hostname: string;
|
||||||
|
active: boolean;
|
||||||
|
percent: number;
|
||||||
|
error: string;
|
||||||
|
duration: string;
|
||||||
|
}>({
|
||||||
|
interval: 1000,
|
||||||
|
timeout: undefined,
|
||||||
|
loading: false,
|
||||||
|
percent: 0,
|
||||||
|
active: false,
|
||||||
|
domain: "",
|
||||||
|
targetFolder: "",
|
||||||
|
hostname: "",
|
||||||
|
error: "",
|
||||||
|
duration: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
page.interval = undefined;
|
||||||
|
clearTimeout(page.timeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
clearTimeout(page.timeout);
|
||||||
|
|
||||||
|
page.loading = true;
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/${props.email}/export`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
page.active = res.data.Active;
|
||||||
|
page.percent = res.data.Percent;
|
||||||
|
page.hostname = res.data.Hostname;
|
||||||
|
page.targetFolder = res.data.TargetFolder;
|
||||||
|
page.error = res.data.Error;
|
||||||
|
page.duration = res.data.Duration;
|
||||||
|
|
||||||
|
if (page.active && page.interval) {
|
||||||
|
page.timeout = setTimeout(get, page.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
page.loading = true;
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/${props.email}/export`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
TargetFolder: page.targetFolder,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,747 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="ant-table-striped"
|
||||||
|
:columns="page.columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
style="min-width: 830px"
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
:rowClassName="
|
||||||
|
(record: any, index: any) => (record.Archived ? 'striped' : undefined)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'email'">
|
||||||
|
<a-space>
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.in_archive`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<LockOutlined v-if="record.Archived" />
|
||||||
|
</a-tooltip>
|
||||||
|
{{ record.Email }}
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-dropdown>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu>
|
||||||
|
<a-menu-item key="1">
|
||||||
|
<a-button
|
||||||
|
style="width: 100%"
|
||||||
|
@click="showRenameWindow(record.Email as string)"
|
||||||
|
>{{
|
||||||
|
t("components.admin.domains.mailstorage.mailboxes.rename")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="2">
|
||||||
|
<a-button
|
||||||
|
style="width: 100%"
|
||||||
|
@click="
|
||||||
|
page.export.email = record.Email;
|
||||||
|
page.export.show = true;
|
||||||
|
"
|
||||||
|
>{{
|
||||||
|
t("components.admin.domains.mailstorage.mailboxes.export")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="page.type !== 'maildirStorage'" key="3">
|
||||||
|
<a-button
|
||||||
|
style="width: 100%"
|
||||||
|
@click="clearRecovery(record.Email)"
|
||||||
|
>{{
|
||||||
|
t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.clear_recovery"
|
||||||
|
)
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="4">
|
||||||
|
<a-button
|
||||||
|
:disabled="page.multistorageAccessKeys.length < 2"
|
||||||
|
style="width: 100%"
|
||||||
|
@click="
|
||||||
|
showChangeStorage(
|
||||||
|
record.Email,
|
||||||
|
record.MultistorageAccessKey
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>{{
|
||||||
|
t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.change_storage"
|
||||||
|
)
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="5">
|
||||||
|
<a-button
|
||||||
|
v-if="record.Archived"
|
||||||
|
style="width: 100%"
|
||||||
|
@click="setArchiveMailbox(record.Email, false)"
|
||||||
|
>{{
|
||||||
|
t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.unarchive"
|
||||||
|
)
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
v-else
|
||||||
|
style="width: 100%"
|
||||||
|
@click="setArchiveMailbox(record.Email, true)"
|
||||||
|
>{{
|
||||||
|
t("components.admin.domains.mailstorage.mailboxes.archive")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="6">
|
||||||
|
<a-button
|
||||||
|
@click="
|
||||||
|
deleteMailbox(
|
||||||
|
t(
|
||||||
|
'components.admin.domains.mailstorage.mailboxes.delete'
|
||||||
|
) +
|
||||||
|
' ' +
|
||||||
|
record.Email +
|
||||||
|
'?',
|
||||||
|
record.Email
|
||||||
|
)
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
danger
|
||||||
|
:disabled="record.Archived"
|
||||||
|
>{{
|
||||||
|
t("components.admin.domains.mailstorage.mailboxes.delete")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
<a>
|
||||||
|
{{
|
||||||
|
t("components.admin.domains.mailstorage.mailboxes.col_actions")
|
||||||
|
}}
|
||||||
|
<DownOutlined />
|
||||||
|
</a>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="12">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.admin.domains.mailstorage.mailboxes.search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="applySearch"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
style="width: 140px"
|
||||||
|
v-model:value="page.pagination.access_key"
|
||||||
|
v-if="page.multistorageAccessKeys.length > 1"
|
||||||
|
@change="applySearch"
|
||||||
|
>
|
||||||
|
<a-select-option value="">{{
|
||||||
|
t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.storage_filter"
|
||||||
|
)
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option
|
||||||
|
v-for="key in page.multistorageAccessKeys"
|
||||||
|
:value="key"
|
||||||
|
>{{ key }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-space
|
||||||
|
v-if="page.withSearch"
|
||||||
|
style="display: flex; justify-content: end"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
v-if="page.type !== 'maildirStorage'"
|
||||||
|
@click="clearRecovery('')"
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.clear_recovery_found"
|
||||||
|
)
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
@click="
|
||||||
|
deleteMailbox(
|
||||||
|
t(
|
||||||
|
'components.admin.domains.mailstorage.mailboxes.delete_found'
|
||||||
|
) + '?',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
"
|
||||||
|
danger
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.delete_found"
|
||||||
|
)
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
<a-space v-else style="display: flex; justify-content: end">
|
||||||
|
<a-button
|
||||||
|
v-if="page.type !== 'maildirStorage'"
|
||||||
|
@click="clearRecovery('')"
|
||||||
|
>{{
|
||||||
|
$t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.clear_recovery_all"
|
||||||
|
)
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
@click="
|
||||||
|
deleteMailbox(
|
||||||
|
t(
|
||||||
|
'components.admin.domains.mailstorage.mailboxes.delete_all'
|
||||||
|
) + '?',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
"
|
||||||
|
danger
|
||||||
|
>{{
|
||||||
|
$t("components.admin.domains.mailstorage.mailboxes.delete_all")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<Export
|
||||||
|
v-if="page.export.show"
|
||||||
|
v-model:open="page.export.show"
|
||||||
|
:email="page.export.email"
|
||||||
|
></Export>
|
||||||
|
<a-modal
|
||||||
|
:title="
|
||||||
|
$t('components.admin.domains.mailstorage.mailboxes.rename') +
|
||||||
|
' ' +
|
||||||
|
page.rename.oldEmail
|
||||||
|
"
|
||||||
|
v-model:open="page.rename.show"
|
||||||
|
style="width: 350px"
|
||||||
|
>
|
||||||
|
<a-space style="width: 100%" direction="vertical">
|
||||||
|
<a-alert
|
||||||
|
v-if="page.rename.error"
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
:message="page.rename.error"
|
||||||
|
closable
|
||||||
|
/>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.rename.newLp"
|
||||||
|
:addon-after="'@' + page.domain"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-space>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="page.rename.show = false">
|
||||||
|
{{
|
||||||
|
$t("components.admin.domains.mailstorage.mailboxes.cancel")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
:disabled="page.rename.newLp === ''"
|
||||||
|
:loading="page.rename.loading"
|
||||||
|
@click="renameMailbox"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t("components.admin.domains.mailstorage.mailboxes.rename")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
:title="
|
||||||
|
$t('components.admin.domains.mailstorage.mailboxes.change_storage_for') +
|
||||||
|
page.change_storage.email
|
||||||
|
"
|
||||||
|
v-model:open="page.change_storage.show"
|
||||||
|
style="width: 350px"
|
||||||
|
:maskClosable="
|
||||||
|
!page.change_storage.status.Active && !page.change_storage.loading
|
||||||
|
"
|
||||||
|
:closable="
|
||||||
|
!page.change_storage.status.Active && !page.change_storage.loading
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-alert
|
||||||
|
v-if="page.change_storage.status.Error"
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
:message="page.change_storage.status.Error"
|
||||||
|
/>
|
||||||
|
<a-progress
|
||||||
|
v-if="page.change_storage.status.Active"
|
||||||
|
:percent="page.change_storage.status.Percent"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-if="!page.change_storage.status.Active"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="page.change_storage.new_access_key"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="key in page.change_storage.accessKeysVariants"
|
||||||
|
:value="key"
|
||||||
|
>{{ key }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-space v-if="!page.change_storage.status.Active">
|
||||||
|
<a-button @click="page.change_storage.show = false">
|
||||||
|
{{
|
||||||
|
$t("components.admin.domains.mailstorage.mailboxes.cancel")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
:loading="page.change_storage.loading"
|
||||||
|
@click="changeStorage"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t("components.admin.domains.mailstorage.mailboxes.change")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const sqlColumns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_email"),
|
||||||
|
dataIndex: "Email",
|
||||||
|
key: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_msg_count"),
|
||||||
|
dataIndex: "Count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_size"),
|
||||||
|
dataIndex: "Size",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_quota"),
|
||||||
|
dataIndex: "Quota",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_quota_used"),
|
||||||
|
dataIndex: "QuotaUsed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.col_recovery_size"
|
||||||
|
),
|
||||||
|
dataIndex: "RecoverySize",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_table_prefix"),
|
||||||
|
dataIndex: "Prefix",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.col_table_access_key"
|
||||||
|
),
|
||||||
|
dataIndex: "MultistorageAccessKey",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const maildirColumns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.mailstorage.mailboxes.col_email"),
|
||||||
|
dataIndex: "Email",
|
||||||
|
key: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ChangeStorageStatus {
|
||||||
|
Active: boolean;
|
||||||
|
Error: string;
|
||||||
|
Percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
rename: {
|
||||||
|
show: boolean;
|
||||||
|
oldEmail: string;
|
||||||
|
newLp: string;
|
||||||
|
error: string;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
export: {
|
||||||
|
show: boolean;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
change_storage: {
|
||||||
|
started: boolean;
|
||||||
|
show: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
email: string;
|
||||||
|
new_access_key: string;
|
||||||
|
accessKeysVariants: string[];
|
||||||
|
status: ChangeStorageStatus;
|
||||||
|
timeout: number | undefined;
|
||||||
|
interval: number | undefined;
|
||||||
|
};
|
||||||
|
columns: ColumnType<any>[];
|
||||||
|
type: string;
|
||||||
|
multistorageAccessKeys: string[];
|
||||||
|
domain: string;
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
access_key: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
rename: {
|
||||||
|
show: false,
|
||||||
|
oldEmail: "",
|
||||||
|
newLp: "",
|
||||||
|
error: "",
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
export: {
|
||||||
|
show: false,
|
||||||
|
email: "",
|
||||||
|
},
|
||||||
|
change_storage: {
|
||||||
|
started: false,
|
||||||
|
interval: 1000,
|
||||||
|
timeout: undefined,
|
||||||
|
show: false,
|
||||||
|
loading: false,
|
||||||
|
email: "",
|
||||||
|
new_access_key: "",
|
||||||
|
accessKeysVariants: [],
|
||||||
|
status: {
|
||||||
|
Active: false,
|
||||||
|
Error: "",
|
||||||
|
Percent: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: [],
|
||||||
|
type: "",
|
||||||
|
multistorageAccessKeys: [],
|
||||||
|
domain: "",
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
access_key: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("access_key", page.pagination.access_key);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.type = res.data.Type;
|
||||||
|
page.multistorageAccessKeys = res.data.MultistorageAccessKeys;
|
||||||
|
|
||||||
|
if (page.type === "maildirStorage") {
|
||||||
|
page.columns = maildirColumns as [];
|
||||||
|
} else {
|
||||||
|
page.columns = sqlColumns as [];
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data.Mailboxes;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySearch() {
|
||||||
|
page.pagination.current = 1;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearRecovery(email: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("email", email);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/clear_recovery?` +
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setArchiveMailbox(email: string, archive: boolean) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.append("email", email);
|
||||||
|
params.append("archive", String(archive));
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/set_archive?` +
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRenameWindow(email: string) {
|
||||||
|
page.rename.oldEmail = email;
|
||||||
|
page.rename.newLp = email.split("@")[0];
|
||||||
|
page.rename.show = true;
|
||||||
|
page.rename.error = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renameMailbox() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("to", page.rename.newLp);
|
||||||
|
|
||||||
|
page.rename.loading = true;
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/${page.rename.oldEmail}/rename?` +
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
page.rename.loading = false;
|
||||||
|
if (res.error) {
|
||||||
|
if (res.error.includes("exists")) {
|
||||||
|
page.rename.error = t(
|
||||||
|
"components.admin.domains.mailstorage.mailboxes.rename_exists"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
notifyError(res.error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.rename.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMailbox(title: string, email: string) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: title,
|
||||||
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
async onOk() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("email", email);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes?` +
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showChangeStorage(email: string, mailboxAccessKey: string) {
|
||||||
|
page.change_storage.loading = false;
|
||||||
|
page.change_storage.email = email;
|
||||||
|
page.change_storage.new_access_key = "";
|
||||||
|
page.change_storage.accessKeysVariants = [];
|
||||||
|
for (let i = 0; i < page.multistorageAccessKeys.length; i++) {
|
||||||
|
const element = page.multistorageAccessKeys[i];
|
||||||
|
if (element != mailboxAccessKey) {
|
||||||
|
if (page.change_storage.new_access_key === "") {
|
||||||
|
page.change_storage.new_access_key = element;
|
||||||
|
}
|
||||||
|
page.change_storage.accessKeysVariants.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page.change_storage.show = await getChangeStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getChangeStorage(): Promise<boolean> {
|
||||||
|
clearTimeout(page.change_storage.timeout);
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/${page.change_storage.email}/change_storage`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.data.Percent === 100 && res.data.Error === "") {
|
||||||
|
router.go(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.change_storage.status = res.data;
|
||||||
|
|
||||||
|
if (page.change_storage.status.Active && page.change_storage.interval) {
|
||||||
|
page.change_storage.timeout = setTimeout(
|
||||||
|
getChangeStorage,
|
||||||
|
page.change_storage.interval
|
||||||
|
);
|
||||||
|
} else if (page.change_storage.started) {
|
||||||
|
router.go(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeStorage() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.append("new_access_key", page.change_storage.new_access_key);
|
||||||
|
|
||||||
|
page.change_storage.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/mailstorage/mailboxes/${page.change_storage.email}/change_storage?` +
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.change_storage.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
page.change_storage.started = true;
|
||||||
|
|
||||||
|
getChangeStorage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.ant-table-striped :deep(.striped) td {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,328 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
small
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('common.misc.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="newCategory">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"components.admin.domains.resources.categories.add_category"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="editCategory(record)">{{
|
||||||
|
t("common.misc.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('common.misc.delete') + '?'"
|
||||||
|
:ok-text="t('common.misc.ok')"
|
||||||
|
:cancel-text="t('common.misc.cancel')"
|
||||||
|
@confirm="deleteCategory(record.Id)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{ t("common.misc.delete") }}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'office_bind'">
|
||||||
|
{{ record.OfficeBind ? t("common.misc.yes") : t("common.misc.no") }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'script'">
|
||||||
|
{{ record.Script ? t("common.misc.yes") : t("common.misc.no") }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 400px"
|
||||||
|
v-model:open="page.addUpdate.show"
|
||||||
|
:title="
|
||||||
|
page.addUpdate.Id
|
||||||
|
? t(`components.admin.domains.resources.categories.edit_category`)
|
||||||
|
: t(`components.admin.domains.resources.categories.add_category`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.resources.categories.col_name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.addUpdate.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
t('components.admin.domains.resources.categories.col_office_bind')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.addUpdate.OfficeBind"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.admin.domains.resources.categories.col_script')"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.addUpdate.Script"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.addUpdate.Name"
|
||||||
|
@click="addUpdateCategory"
|
||||||
|
:loading="page.addUpdate.loading"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.addUpdate.Id
|
||||||
|
? t("common.misc.save_changes")
|
||||||
|
: t("components.admin.domains.resources.categories.add_category")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
EventIdPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
RouteUserCalendarsEventsPlannerEdit,
|
||||||
|
RouteUserCalendarsEventsPlannerNew,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
CheckOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { timeToDateTime } from "@/composables/misc";
|
||||||
|
import TimezoneSelect from "@/components/common/TimezoneSelect.vue";
|
||||||
|
import WorkDaysSelect from "@/components/common/WorkDaysSelect.vue";
|
||||||
|
import WorkHoursRangePicker from "@/components/common/WorkHoursRangePicker.vue";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 10, style: {} };
|
||||||
|
const wrapperCol = { span: 14, style: {} };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.categories.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.categories.col_office_bind"),
|
||||||
|
key: "office_bind",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.categories.col_script"),
|
||||||
|
key: "script",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
key: "action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
loading: boolean;
|
||||||
|
data: any[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
addUpdate: {
|
||||||
|
show: boolean;
|
||||||
|
Id: number;
|
||||||
|
Name: string;
|
||||||
|
OfficeBind: boolean;
|
||||||
|
Script: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
addUpdate: {
|
||||||
|
loading: false,
|
||||||
|
show: false,
|
||||||
|
Id: 0,
|
||||||
|
Name: "",
|
||||||
|
OfficeBind: false,
|
||||||
|
Script: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/categories?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteCategory(id: number) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/categories/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.categories.delete_success")
|
||||||
|
);
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editCategory(record: any) {
|
||||||
|
page.addUpdate = { ...record };
|
||||||
|
page.addUpdate.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addUpdateCategory() {
|
||||||
|
page.addUpdate.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/categories`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.addUpdate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addUpdate.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.addUpdate.Id !== 0) {
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.categories.update_success")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.categories.create_success")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.addUpdate.show = false;
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newCategory() {
|
||||||
|
page.addUpdate.Id = 0;
|
||||||
|
page.addUpdate.Name = "";
|
||||||
|
page.addUpdate.OfficeBind = false;
|
||||||
|
page.addUpdate.Script = false;
|
||||||
|
page.addUpdate.show = true;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,352 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
small
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('common.misc.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="newOffice">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ t("components.admin.domains.resources.offices.add_office") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="editOffice(record)">{{
|
||||||
|
t("common.misc.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('common.misc.delete') + '?'"
|
||||||
|
:ok-text="t('common.misc.ok')"
|
||||||
|
:cancel-text="t('common.misc.cancel')"
|
||||||
|
@confirm="deleteOffice(record.Id)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{ t("common.misc.delete") }}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'timezone'">
|
||||||
|
UTC{{ record.Timezone }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 400px"
|
||||||
|
v-model:open="page.addUpdate.show"
|
||||||
|
:title="
|
||||||
|
page.addUpdate.Id
|
||||||
|
? t(`components.admin.domains.resources.offices.edit_office`)
|
||||||
|
: t(`components.admin.domains.resources.offices.add_office`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.resources.offices.col_name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.addUpdate.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.user.calendars.share_free_time.timezone')"
|
||||||
|
>
|
||||||
|
<TimezoneSelect v-model:value="page.addUpdate.Timezone">
|
||||||
|
</TimezoneSelect>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.user.calendars.share_free_time.work_days')"
|
||||||
|
>
|
||||||
|
<WorkDaysSelect v-model:value="page.addUpdate.WorkDays">
|
||||||
|
</WorkDaysSelect>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.user.calendars.share_free_time.work_hours')"
|
||||||
|
>
|
||||||
|
<WorkHoursRangePicker v-model:value="page.addUpdate.workTimeValues">
|
||||||
|
</WorkHoursRangePicker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.addUpdate.Name"
|
||||||
|
@click="addUpdateOffice"
|
||||||
|
:loading="page.addUpdate.loading"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.addUpdate.Id
|
||||||
|
? t("common.misc.save_changes")
|
||||||
|
: t("components.admin.domains.resources.offices.add_office")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
EventIdPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
RouteUserCalendarsEventsPlannerEdit,
|
||||||
|
RouteUserCalendarsEventsPlannerNew,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { timeToDateTime } from "@/composables/misc";
|
||||||
|
import TimezoneSelect from "@/components/common/TimezoneSelect.vue";
|
||||||
|
import WorkDaysSelect from "@/components/common/WorkDaysSelect.vue";
|
||||||
|
import WorkHoursRangePicker from "@/components/common/WorkHoursRangePicker.vue";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 8, style: {} };
|
||||||
|
const wrapperCol = { span: 16, style: {} };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.offices.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.offices.col_timezone"),
|
||||||
|
key: "timezone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
key: "action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
loading: boolean;
|
||||||
|
data: any[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
addUpdate: {
|
||||||
|
show: boolean;
|
||||||
|
Id: number;
|
||||||
|
Name: string;
|
||||||
|
Timezone: string;
|
||||||
|
WorkDays: string[];
|
||||||
|
WorkTime: string[];
|
||||||
|
workTimeValues: any[];
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
addUpdate: {
|
||||||
|
loading: false,
|
||||||
|
show: false,
|
||||||
|
Id: 0,
|
||||||
|
Name: "",
|
||||||
|
Timezone: "",
|
||||||
|
WorkDays: [],
|
||||||
|
WorkTime: [],
|
||||||
|
workTimeValues: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/offices?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteOffice(id: number) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/offices/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.resources.offices.delete_success"));
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editOffice(record: any) {
|
||||||
|
page.addUpdate = { ...record };
|
||||||
|
page.addUpdate.workTimeValues = [
|
||||||
|
dayjs(Date.parse("1970-01-01 " + page.addUpdate.WorkTime[0])),
|
||||||
|
dayjs(Date.parse("1970-01-01 " + page.addUpdate.WorkTime[1])),
|
||||||
|
];
|
||||||
|
page.addUpdate.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addUpdateOffice() {
|
||||||
|
page.addUpdate.WorkTime[0] = page.addUpdate.workTimeValues[0].format("HH:mm");
|
||||||
|
page.addUpdate.WorkTime[1] = page.addUpdate.workTimeValues[1].format("HH:mm");
|
||||||
|
|
||||||
|
page.addUpdate.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/offices`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.addUpdate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addUpdate.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.addUpdate.Id !== 0) {
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.offices.update_success")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.offices.create_success")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.addUpdate.show = false;
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStringTimezone(): string {
|
||||||
|
let tz = new Date().getTimezoneOffset();
|
||||||
|
tz = tz / -60;
|
||||||
|
let str = "+";
|
||||||
|
if (tz < 0) {
|
||||||
|
str = "-";
|
||||||
|
tz *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tz < 10) {
|
||||||
|
str += "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str + tz.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newOffice() {
|
||||||
|
page.addUpdate.Id = 0;
|
||||||
|
page.addUpdate.Name = "";
|
||||||
|
page.addUpdate.Timezone = getStringTimezone();
|
||||||
|
page.addUpdate.WorkDays = ["1", "2", "3", "4", "5"];
|
||||||
|
page.addUpdate.WorkTime = ["10:00", "19:00"];
|
||||||
|
page.addUpdate.workTimeValues = [
|
||||||
|
dayjs(Date.parse("1970-01-01 " + page.addUpdate.WorkTime[0])),
|
||||||
|
dayjs(Date.parse("1970-01-01 " + page.addUpdate.WorkTime[1])),
|
||||||
|
];
|
||||||
|
page.addUpdate.show = true;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 400px;
|
||||||
|
max-width: 40%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,608 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
small
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('common.misc.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
<a-select v-model:value="page.pagination.category" @change="get">
|
||||||
|
<a-select-option :value="''">{{
|
||||||
|
t("components.admin.domains.resources.resources.any_category")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option
|
||||||
|
v-for="cat in page.categories"
|
||||||
|
:value="cat.Id.toString()"
|
||||||
|
>{{ cat.Name }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="page.pagination.office" @change="get">
|
||||||
|
<a-select-option :value="''">{{
|
||||||
|
t("components.admin.domains.resources.resources.any_office")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option
|
||||||
|
v-for="office in page.offices"
|
||||||
|
:value="office.Id.toString()"
|
||||||
|
>{{ office.Name }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="newResource">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{
|
||||||
|
t("components.admin.domains.resources.resources.add_resource")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAccessModal(record.Id)">{{
|
||||||
|
t("components.common.shared_calendars.edit_cal_access")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button @click="editResource(record)">{{
|
||||||
|
t("common.misc.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('common.misc.delete') + '?'"
|
||||||
|
:ok-text="t('common.misc.ok')"
|
||||||
|
:cancel-text="t('common.misc.cancel')"
|
||||||
|
@confirm="deleteResource(record.Id)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{ t("common.misc.delete") }}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
:width="page.addUpdate.hasScript ? 800 : 500"
|
||||||
|
v-model:open="page.addUpdate.show"
|
||||||
|
:title="
|
||||||
|
page.addUpdate.Id
|
||||||
|
? t(`components.admin.domains.resources.resources.edit_resource`)
|
||||||
|
: t(`components.admin.domains.resources.resources.add_resource`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.admin.domains.resources.resources.col_category')"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="page.addUpdate.categoryIdStr"
|
||||||
|
@change="updateCatMeta"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="cat in page.categories"
|
||||||
|
:value="cat.Id.toString()"
|
||||||
|
>{{ cat.Name }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
v-if="page.addUpdate.officeBind"
|
||||||
|
:label="t('components.admin.domains.resources.resources.col_office')"
|
||||||
|
>
|
||||||
|
<a-select v-model:value="page.addUpdate.officeIdStr">
|
||||||
|
<a-select-option
|
||||||
|
v-for="office in page.offices"
|
||||||
|
:value="office.Id.toString()"
|
||||||
|
>{{ office.Name }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
v-if="page.addUpdate.hasScript"
|
||||||
|
:label="$t('components.admin.domains.resources.resources.col_script')"
|
||||||
|
>
|
||||||
|
<a-flex gap="middle" vertical>
|
||||||
|
<a-alert
|
||||||
|
v-if="page.addUpdate.checkScript.error !== null"
|
||||||
|
:type="page.addUpdate.checkScript.error ? 'error' : 'success'"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template #message>
|
||||||
|
<div style="white-space: pre-line; font-family: monospace">
|
||||||
|
{{
|
||||||
|
page.addUpdate.checkScript.error
|
||||||
|
? page.addUpdate.checkScript.error
|
||||||
|
: $t(
|
||||||
|
"components.admin.domains.resources.resources.check_script_good"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
<a-textarea v-model:value="page.addUpdate.Script"> </a-textarea>
|
||||||
|
</a-flex>
|
||||||
|
</a-form-item>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
page.addUpdate.categoryIdStr &&
|
||||||
|
(!page.addUpdate.officeBind || page.addUpdate.officeIdStr)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.resources.resources.col_show_time_as')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-select v-model:value="page.addUpdate.transparentCalObjectsStr">
|
||||||
|
<a-select-option value="false">{{
|
||||||
|
$t("components.admin.domains.resources.resources.time_busy")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="true">{{
|
||||||
|
$t("components.admin.domains.resources.resources.time_free")
|
||||||
|
}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.resources.resources.col_name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.addUpdate.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
v-if="page.addUpdate.hasScript"
|
||||||
|
@click="checkScript"
|
||||||
|
:loading="page.addUpdate.checkScript.loading"
|
||||||
|
>
|
||||||
|
{{ t("components.admin.domains.resources.resources.check_script") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="
|
||||||
|
!page.addUpdate.Name ||
|
||||||
|
!page.addUpdate.categoryIdStr ||
|
||||||
|
(page.addUpdate.officeBind && !page.addUpdate.officeIdStr)
|
||||||
|
"
|
||||||
|
@click="addUpdateResource"
|
||||||
|
:loading="page.addUpdate.loading"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.addUpdate.Id
|
||||||
|
? t("common.misc.save_changes")
|
||||||
|
: t("components.admin.domains.resources.resources.add_resource")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<CalendarsAccess
|
||||||
|
v-if="page.access.show"
|
||||||
|
v-model:open="page.access.show"
|
||||||
|
:id="page.access.Id"
|
||||||
|
:domain="page.domain"
|
||||||
|
:type="'group'"
|
||||||
|
:resources="true"
|
||||||
|
>
|
||||||
|
</CalendarsAccess>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
EventIdPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
RouteUserCalendarsEventsPlannerEdit,
|
||||||
|
RouteUserCalendarsEventsPlannerNew,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
CheckOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { timeToDateTime } from "@/composables/misc";
|
||||||
|
import TimezoneSelect from "@/components/common/TimezoneSelect.vue";
|
||||||
|
import WorkDaysSelect from "@/components/common/WorkDaysSelect.vue";
|
||||||
|
import WorkHoursRangePicker from "@/components/common/WorkHoursRangePicker.vue";
|
||||||
|
import { _cf } from "ant-design-vue/es/_util/cssinjs/hooks/useStyleRegister";
|
||||||
|
import CalendarsAccess from "@/components/common/CalendarsAccess.vue";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 6, style: {} };
|
||||||
|
const wrapperCol = { span: 18, style: {} };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.resources.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.resources.col_category"),
|
||||||
|
dataIndex: "CategoryName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.resources.resources.col_office"),
|
||||||
|
dataIndex: "OfficeName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
key: "action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
Id: number;
|
||||||
|
Name: string;
|
||||||
|
OfficeBind: boolean;
|
||||||
|
Script: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Office {
|
||||||
|
Id: number;
|
||||||
|
Name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
loading: boolean;
|
||||||
|
data: any[];
|
||||||
|
categories: Category[];
|
||||||
|
catId2Name: Map<number, string>;
|
||||||
|
offices: Office[];
|
||||||
|
officeId2Name: Map<number, string>;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
category: string;
|
||||||
|
office: string;
|
||||||
|
};
|
||||||
|
addUpdate: {
|
||||||
|
show: boolean;
|
||||||
|
Id: number;
|
||||||
|
Name: string;
|
||||||
|
CategoryId?: number;
|
||||||
|
categoryIdStr: string;
|
||||||
|
OfficeId?: number;
|
||||||
|
officeIdStr: string;
|
||||||
|
Script: string | null;
|
||||||
|
TransparentCalObjects: boolean;
|
||||||
|
transparentCalObjectsStr: string;
|
||||||
|
loading: boolean;
|
||||||
|
officeBind: boolean;
|
||||||
|
hasScript: boolean;
|
||||||
|
checkScript: {
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
access: {
|
||||||
|
show: boolean;
|
||||||
|
Id: number;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
category: "",
|
||||||
|
office: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
categories: [],
|
||||||
|
catId2Name: new Map(),
|
||||||
|
officeId2Name: new Map(),
|
||||||
|
offices: [],
|
||||||
|
addUpdate: {
|
||||||
|
show: false,
|
||||||
|
Id: 0,
|
||||||
|
Name: "",
|
||||||
|
CategoryId: 0,
|
||||||
|
categoryIdStr: "",
|
||||||
|
OfficeId: 0,
|
||||||
|
officeIdStr: "",
|
||||||
|
Script: "",
|
||||||
|
loading: false,
|
||||||
|
officeBind: false,
|
||||||
|
hasScript: false,
|
||||||
|
TransparentCalObjects: false,
|
||||||
|
transparentCalObjectsStr: "false",
|
||||||
|
checkScript: {
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
show: false,
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
load();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
await loadCategoriesAndOffices();
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCategoriesAndOffices() {
|
||||||
|
loadCategories();
|
||||||
|
loadOffices();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCategories() {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/categories`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.categories = res.data;
|
||||||
|
page.categories.forEach((cat) => {
|
||||||
|
page.catId2Name.set(cat.Id, cat.Name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOffices() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/resources/offices`);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.offices = res.data;
|
||||||
|
page.offices.forEach((office) => {
|
||||||
|
page.officeId2Name.set(office.Id, office.Name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("category", page.pagination.category);
|
||||||
|
params.append("office", page.pagination.office);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/resources?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.data.forEach((e) => {
|
||||||
|
e.CategoryName = page.catId2Name.get(e.CategoryId);
|
||||||
|
e.OfficeName = page.officeId2Name.get(e.OfficeId);
|
||||||
|
});
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteResource(id: number) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/resources/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.resources.delete_success")
|
||||||
|
);
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editResource(record: any) {
|
||||||
|
page.addUpdate = { ...record };
|
||||||
|
page.addUpdate.checkScript = {
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
page.addUpdate.categoryIdStr = "";
|
||||||
|
if (page.addUpdate.CategoryId) {
|
||||||
|
page.addUpdate.categoryIdStr = page.addUpdate.CategoryId.toString();
|
||||||
|
}
|
||||||
|
page.addUpdate.officeIdStr = "";
|
||||||
|
if (page.addUpdate.OfficeId) {
|
||||||
|
page.addUpdate.officeIdStr = page.addUpdate.OfficeId.toString();
|
||||||
|
}
|
||||||
|
page.addUpdate.transparentCalObjectsStr = String(
|
||||||
|
page.addUpdate.TransparentCalObjects
|
||||||
|
);
|
||||||
|
|
||||||
|
updateCatMeta();
|
||||||
|
page.addUpdate.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addUpdateResource() {
|
||||||
|
page.addUpdate.CategoryId = undefined;
|
||||||
|
if (page.addUpdate.categoryIdStr) {
|
||||||
|
page.addUpdate.CategoryId = Number(page.addUpdate.categoryIdStr);
|
||||||
|
}
|
||||||
|
page.addUpdate.OfficeId = undefined;
|
||||||
|
if (page.addUpdate.officeIdStr) {
|
||||||
|
page.addUpdate.OfficeId = Number(page.addUpdate.officeIdStr);
|
||||||
|
}
|
||||||
|
if (!page.addUpdate.hasScript) {
|
||||||
|
page.addUpdate.Script = null;
|
||||||
|
}
|
||||||
|
page.addUpdate.TransparentCalObjects = Boolean(
|
||||||
|
page.addUpdate.transparentCalObjectsStr
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addUpdate.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/resources`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.addUpdate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addUpdate.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.addUpdate.Id !== 0) {
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.resources.update_success")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.domains.resources.resources.create_success")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.addUpdate.show = false;
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkScript() {
|
||||||
|
page.addUpdate.checkScript.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/resources/resources/check_script`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.addUpdate.Script,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addUpdate.checkScript.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.addUpdate.checkScript.error = res.data.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCatMeta() {
|
||||||
|
page.addUpdate.officeBind = false;
|
||||||
|
page.addUpdate.hasScript = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < page.categories.length; i++) {
|
||||||
|
const cat = page.categories[i];
|
||||||
|
if (cat.Id.toString() === page.addUpdate.categoryIdStr) {
|
||||||
|
page.addUpdate.officeBind = cat.OfficeBind;
|
||||||
|
page.addUpdate.hasScript = cat.Script;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newResource() {
|
||||||
|
page.addUpdate.Id = 0;
|
||||||
|
page.addUpdate.Name = "";
|
||||||
|
page.addUpdate.CategoryId = undefined;
|
||||||
|
page.addUpdate.categoryIdStr = "";
|
||||||
|
page.addUpdate.OfficeId = undefined;
|
||||||
|
page.addUpdate.officeIdStr = "";
|
||||||
|
page.addUpdate.officeBind = false;
|
||||||
|
page.addUpdate.hasScript = false;
|
||||||
|
page.addUpdate.Script = null;
|
||||||
|
page.addUpdate.show = true;
|
||||||
|
page.addUpdate.checkScript.loading = false;
|
||||||
|
page.addUpdate.checkScript.error = null;
|
||||||
|
page.addUpdate.TransparentCalObjects = false;
|
||||||
|
page.addUpdate.transparentCalObjectsStr = "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAccessModal(id: number) {
|
||||||
|
page.access.Id = id;
|
||||||
|
page.access.show = true;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 800px;
|
||||||
|
max-width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.add.Name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.add.Type')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-select v-model:value="page.new.Type">
|
||||||
|
<a-select-option value="ldapUserDB">LDAP</a-select-option>
|
||||||
|
<a-select-option value="jsonFileUserDB">JSON File</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.new.Type || !page.new.Name"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="save"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.userdb.add.create") }}
|
||||||
|
</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import router from "@/router";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageSettings,
|
||||||
|
RouteAdminDomainsDomainUserDBProviderSettings,
|
||||||
|
UserdbPlaceholder,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
new: {
|
||||||
|
Type: string;
|
||||||
|
Name: string;
|
||||||
|
};
|
||||||
|
domain: string;
|
||||||
|
loading: boolean;
|
||||||
|
}>({
|
||||||
|
loading: false,
|
||||||
|
new: {
|
||||||
|
Type: "",
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
domain: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const res = await apiFetch(`/admin/domains/${page.domain}/userdb/add`, {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.userdb.add.success"));
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
path: RouteAdminDomainsDomainUserDBProviderSettings.replace(
|
||||||
|
DomainPlaceholder,
|
||||||
|
page.domain
|
||||||
|
).replace(UserdbPlaceholder, res.data),
|
||||||
|
hash: "#refresh",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
width: 500px;
|
||||||
|
max-width: 50%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,434 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showMembersModal(record)">{{
|
||||||
|
t("components.admin.domains.userdb.groups.members")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button @click="showAddModal(record)">{{
|
||||||
|
t("components.admin.domains.userdb.groups.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.admin.domains.userdb.groups.delete') + '?'"
|
||||||
|
:ok-text="t('components.admin.domains.userdb.groups.ok')"
|
||||||
|
:cancel-text="t('components.admin.domains.userdb.groups.cancel')"
|
||||||
|
@confirm="deleteGroup(record.Gid)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.admin.domains.userdb.groups.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'users_count'">
|
||||||
|
{{ record.MemberUsers ? (record.MemberUsers as Array<any>).length : 0 }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'groups_count'">
|
||||||
|
{{ record.MemberGroups ? (record.MemberGroups as Array<any>).length : 0 }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<div style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal(undefined)">{{
|
||||||
|
$t("components.admin.domains.userdb.groups.add")
|
||||||
|
}}</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="
|
||||||
|
page.add.Gid
|
||||||
|
? t(`components.admin.domains.userdb.groups.edit`) + ' ' + page.add.Name
|
||||||
|
: t(`components.admin.domains.userdb.groups.add`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.groups.add_modal.name')
|
||||||
|
|
||||||
|
"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.groups.add_modal.email')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.Email"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.groups.add_modal.alt_emails')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="page.add.AlternateEmailsStr"> </a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="page.add.show = false">
|
||||||
|
{{ $t("components.admin.domains.userdb.groups.add_modal.cancel") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.add.Name || !page.add.Email"
|
||||||
|
type="primary"
|
||||||
|
@click="add"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.userdb.groups.add_modal.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal v-model:open="page.members.show" style="width: 70%" @cancel="get">
|
||||||
|
<a-transfer
|
||||||
|
:disabled="page.members.disabled"
|
||||||
|
v-model:target-keys="page.members.users_ids"
|
||||||
|
:data-source="page.all_users"
|
||||||
|
:render="(item: any) => item.name"
|
||||||
|
show-search
|
||||||
|
:titles="[' (all users)', ' (users in group)']"
|
||||||
|
:list-style="{
|
||||||
|
width: '100%',
|
||||||
|
height: '320px',
|
||||||
|
}"
|
||||||
|
@change="changeUserMember"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<a-transfer
|
||||||
|
:disabled="page.members.disabled"
|
||||||
|
v-model:target-keys="page.members.groups_ids"
|
||||||
|
:data-source="page.all_groups"
|
||||||
|
:render="(item: any) => item.name"
|
||||||
|
show-search
|
||||||
|
:titles="[' (all groups)', ' (groups in group)']"
|
||||||
|
:list-style="{
|
||||||
|
width: '100%',
|
||||||
|
height: '320px',
|
||||||
|
}"
|
||||||
|
@change="changeGroupMember"
|
||||||
|
/>
|
||||||
|
<template #footer> </template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 8, };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.groups.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.groups.col_email"),
|
||||||
|
dataIndex: "Email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.groups.col_users_count"),
|
||||||
|
key: "users_count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.groups.col_group_count"),
|
||||||
|
key: "groups_count",
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface TransferElem {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
sid: string;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
all_users: TransferElem[];
|
||||||
|
all_groups: TransferElem[];
|
||||||
|
members: {
|
||||||
|
show: boolean;
|
||||||
|
Name: string;
|
||||||
|
Gid: string;
|
||||||
|
users_ids: string[];
|
||||||
|
groups_ids: string[];
|
||||||
|
disabled: boolean;
|
||||||
|
};
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
Gid: string;
|
||||||
|
Name: string;
|
||||||
|
Email: string;
|
||||||
|
AlternateEmails: string[];
|
||||||
|
AlternateEmailsStr: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
sid: "",
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
Email: "",
|
||||||
|
AlternateEmails: [],
|
||||||
|
AlternateEmailsStr: "",
|
||||||
|
Gid: "",
|
||||||
|
},
|
||||||
|
members: {
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
Gid: "",
|
||||||
|
users_ids: [],
|
||||||
|
groups_ids: [],
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
all_users: [],
|
||||||
|
all_groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.sid = route.params.sid as string;
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/groups`
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUsers() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/users`
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.all_users = [];
|
||||||
|
page.all_users = (res.data as Array<any>).map((e) => {
|
||||||
|
return {
|
||||||
|
key: e.Uid,
|
||||||
|
name: e.DisplayName + " (" + e.Email + ")",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/groups`
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.all_groups = [];
|
||||||
|
page.all_groups = (res.data as Array<any>).map((e) => {
|
||||||
|
return {
|
||||||
|
key: e.Gid,
|
||||||
|
name: e.Name + " (" + e.Email + ")",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
page.all_groups = page.all_groups.filter((e) => e.key !== page.members.Gid);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal(record: any) {
|
||||||
|
if (record) {
|
||||||
|
page.add.Gid = record.Gid;
|
||||||
|
page.add.Email = record.Email;
|
||||||
|
page.add.Name = record.Name;
|
||||||
|
page.add.AlternateEmails = record.AlternateEmails;
|
||||||
|
page.add.AlternateEmailsStr = page.add.AlternateEmails.join("\n");
|
||||||
|
} else {
|
||||||
|
page.add.Gid = "";
|
||||||
|
page.add.Email = "";
|
||||||
|
page.add.Name = "";
|
||||||
|
page.add.AlternateEmails = [];
|
||||||
|
page.add.AlternateEmailsStr = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showMembersModal(record: any) {
|
||||||
|
page.members.Gid = record.Gid;
|
||||||
|
page.members.Name = record.Name;
|
||||||
|
page.members.show = true;
|
||||||
|
|
||||||
|
page.members.groups_ids = record.MemberGroups;
|
||||||
|
if (!page.members.groups_ids) {
|
||||||
|
page.members.groups_ids = [];
|
||||||
|
}
|
||||||
|
page.members.users_ids = record.MemberUsers;
|
||||||
|
if (!page.members.users_ids) {
|
||||||
|
page.members.users_ids = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await getUsers();
|
||||||
|
await getGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
page.add.AlternateEmails = page.add.AlternateEmailsStr.split("\n");
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/groups`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.add,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function deleteGroup(gid: string) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/groups`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Gid: gid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeUserMember(
|
||||||
|
nextTargetKeys: string[],
|
||||||
|
direction: string,
|
||||||
|
moveKeys: string[]
|
||||||
|
) {
|
||||||
|
page.members.disabled = true;
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/groups/${page.members.Gid}/user`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Action: direction === "right" ? "add" : "delete",
|
||||||
|
Ids: moveKeys,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
page.members.disabled = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeGroupMember(
|
||||||
|
nextTargetKeys: string[],
|
||||||
|
direction: string,
|
||||||
|
moveKeys: string[]
|
||||||
|
) {
|
||||||
|
page.members.disabled = true;
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/groups/${page.members.Gid}/group`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Action: direction === "right" ? "add" : "delete",
|
||||||
|
Ids: moveKeys,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
page.members.disabled = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,245 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="showAddModal(record)">{{
|
||||||
|
t("components.admin.domains.userdb.redirects.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.admin.domains.userdb.redirects.delete') + '?'"
|
||||||
|
:ok-text="t('components.admin.domains.userdb.redirects.ok')"
|
||||||
|
:cancel-text="t('components.admin.domains.userdb.redirects.cancel')"
|
||||||
|
@confirm="deleteRedirect(record.Rid)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.admin.domains.userdb.redirects.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<div style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal(undefined)">{{
|
||||||
|
$t("components.admin.domains.userdb.redirects.add")
|
||||||
|
}}</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="
|
||||||
|
page.add.Rid
|
||||||
|
? t(`components.admin.domains.userdb.redirects.edit`) +
|
||||||
|
' ' +
|
||||||
|
page.add.Name
|
||||||
|
: t(`components.admin.domains.userdb.redirects.add`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.redirects.add_modal.name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.redirects.add_modal.email')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.Email"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.redirects.add_modal.destinations')
|
||||||
|
"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="page.add.DestinationsStr"> </a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="page.add.show = false">
|
||||||
|
{{ $t("components.admin.domains.userdb.redirects.add_modal.cancel") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
!page.add.Name || !page.add.Email || !page.add.DestinationsStr
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="add"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.userdb.redirects.add_modal.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.redirects.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.redirects.col_email"),
|
||||||
|
dataIndex: "Email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
sid: string;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
Rid: string;
|
||||||
|
Name: string;
|
||||||
|
Email: string;
|
||||||
|
Destinations: string[];
|
||||||
|
DestinationsStr: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
sid: "",
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
Email: "",
|
||||||
|
Destinations: [],
|
||||||
|
DestinationsStr: "",
|
||||||
|
Rid: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.sid = route.params.sid as string;
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/redirects`
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal(record: any) {
|
||||||
|
if (record) {
|
||||||
|
page.add.Rid = record.Rid;
|
||||||
|
page.add.Email = record.Email;
|
||||||
|
page.add.Name = record.Name;
|
||||||
|
page.add.Destinations = record.Destinations;
|
||||||
|
page.add.DestinationsStr = page.add.Destinations.join("\n");
|
||||||
|
} else {
|
||||||
|
page.add.Rid = "";
|
||||||
|
page.add.Email = "";
|
||||||
|
page.add.Name = "";
|
||||||
|
page.add.Destinations = [];
|
||||||
|
page.add.DestinationsStr = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
page.add.Destinations = page.add.DestinationsStr.split("\n");
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/redirects`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.add,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function deleteRedirect(rid: string) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/redirects`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Rid: rid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,812 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.settings.Name')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.settings.UseInGal')"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.settings.UseInGal"> </a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<template v-if="page.type === 'jsonFileUserDB'">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.settings.Dir')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.Dir"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.MasterUsersGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.MasterUsersGroup">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.settings.CacheTTL')"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
:addon-after="$t('common.suffixes.sec')"
|
||||||
|
v-model:value="page.settings.Params.CacheTTL"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t('components.admin.domains.userdb.settings.ConnectionGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.ConnectURIs')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="page.settings.Params.ConnectURIsString"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.ConnectBindDN')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.ConnectBindDN">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.ConnectPassword')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="page.settings.Params.ConnectPassword"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.ConnectBaseDN')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.ConnectBaseDN">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.ConnectTLSMinVer'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-select v-model:value="page.settings.Params.ConnectTLSMinVer">
|
||||||
|
<a-select-option value="1.3"></a-select-option>
|
||||||
|
<a-select-option value="1.2"></a-select-option>
|
||||||
|
<a-select-option value="1.0"></a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="$t('components.admin.domains.userdb.settings.UserGroup')"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.UserObjectclass')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.UserObjectclass">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.UserMailAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.UserMailAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.UserQuotaAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.UserQuotaAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.MemberofAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.MemberofAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.UserAddSearchFilter'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.UserAddSearchFilter"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t('components.admin.domains.userdb.settings.GroupsGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.EmailGroups')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.settings.Params.EmailGroups">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GroupObjectclass'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.GroupObjectclass">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.GroupNameAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.GroupNameAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.GroupMailAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.GroupMailAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.GroupMemberAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.GroupMemberAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.GroupMemberDn')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.settings.Params.GroupMemberDn">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.UserRDNAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.UserRDNAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GroupAddSearchFilter'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GroupAddSearchFilter"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t('components.admin.domains.userdb.settings.RedirectGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.EmailRedirect')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.settings.Params.EmailRedirect">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.AliasObjectclass'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.AliasObjectclass">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.AliasMailAttr')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.AliasMailAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.AliasRedirectAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.AliasRedirectAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t('components.admin.domains.userdb.settings.AltEmailGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.EmailAlternate')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.settings.Params.EmailAlternate">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.AlternateMailAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.AlternateMailAttr">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t('components.admin.domains.userdb.settings.MasterGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.MasterUserGroupNameAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.MasterUserGroupNameAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.MasterUserGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.MasterUserGroup">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="$t('components.admin.domains.userdb.settings.GALGroup')"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.settings.GALCacheTTL')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
:addon-after="$t('common.suffixes.sec')"
|
||||||
|
v-model:value="page.settings.Params.GALCacheTTL"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALObjectsFilter'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.settings.Params.GALObjectsFilter">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-collapse>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t('components.admin.domains.userdb.settings.GALUserGroup')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserNameAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserNameAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserEmailAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserEmailAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserAltEmailAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserAltEmailAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserPhoneAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserPhoneAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserPhotoAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserPhotoAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserBirthdayAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserBirthdayAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserAddressAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserAddressAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserOrganizationAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="
|
||||||
|
page.settings.Params.GALUserOrganizationAttr
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALUserRoleAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALUserRoleAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALGroupsGroup'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALGroupNameAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALGroupNameAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALGroupEmailAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALGroupEmailAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel
|
||||||
|
:header="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALAliasesGroup'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALAliasNameAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALAliasNameAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.domains.userdb.settings.GALAliasEmailAttr'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.settings.Params.GALAliasEmailAttr"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<a-space style="display: flex; justify-content: center">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.settings.Email"
|
||||||
|
:placeholder="
|
||||||
|
t('components.admin.domains.userdb.settings.check_email')
|
||||||
|
"
|
||||||
|
style="width: 370px"
|
||||||
|
size="large"
|
||||||
|
allowClear
|
||||||
|
@search="check"
|
||||||
|
>
|
||||||
|
<template #enterButton>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.settings.Email"
|
||||||
|
:loading="page.checkLoading"
|
||||||
|
>{{
|
||||||
|
$t("components.admin.domains.userdb.settings.check")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-input-search>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.contentLoaded"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
class="save-button"
|
||||||
|
style="width: fit-content"
|
||||||
|
@click="save"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.userdb.settings.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import router from "@/router";
|
||||||
|
import { RouteAdminDashboard } from "@/router/consts";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
sid: string;
|
||||||
|
domain: string;
|
||||||
|
contentLoaded: boolean;
|
||||||
|
checkLoading: boolean;
|
||||||
|
type: string;
|
||||||
|
settings: {
|
||||||
|
Name: string;
|
||||||
|
UseInGal: boolean;
|
||||||
|
Email: string;
|
||||||
|
Params: {
|
||||||
|
//Maildir
|
||||||
|
Dir: string;
|
||||||
|
MasterUsersGroup: string;
|
||||||
|
|
||||||
|
//Ldap
|
||||||
|
|
||||||
|
CacheTTL: number;
|
||||||
|
ConnectURIs: string[];
|
||||||
|
ConnectURIsString: string;
|
||||||
|
ConnectBindDN: string;
|
||||||
|
ConnectPassword: string;
|
||||||
|
ConnectBaseDN: string;
|
||||||
|
ConnectTLSMinVer: string;
|
||||||
|
|
||||||
|
UserObjectclass: string;
|
||||||
|
UserMailAttr: string;
|
||||||
|
UserQuotaAttr: string;
|
||||||
|
MemberofAttr: string;
|
||||||
|
UserAddSearchFilter: string;
|
||||||
|
|
||||||
|
EmailGroups: boolean;
|
||||||
|
GroupObjectclass: string;
|
||||||
|
GroupNameAttr: string;
|
||||||
|
GroupMailAttr: string;
|
||||||
|
GroupMemberAttr: string;
|
||||||
|
GroupMemberDn: boolean;
|
||||||
|
UserRDNAttr: string;
|
||||||
|
GroupAddSearchFilter: string;
|
||||||
|
|
||||||
|
EmailRedirect: boolean;
|
||||||
|
AliasObjectclass: string;
|
||||||
|
AliasMailAttr: string;
|
||||||
|
AliasRedirectAttr: string;
|
||||||
|
|
||||||
|
EmailAlternate: boolean;
|
||||||
|
AlternateMailAttr: string;
|
||||||
|
|
||||||
|
MasterUserGroupNameAttr: string;
|
||||||
|
MasterUserGroup: string;
|
||||||
|
|
||||||
|
GALCacheTTL: number;
|
||||||
|
|
||||||
|
GALObjectsFilter: string;
|
||||||
|
GALUserNameAttr: string;
|
||||||
|
GALUserEmailAttr: string;
|
||||||
|
GALUserAltEmailAttr: string;
|
||||||
|
GALUserPhoneAttr: string;
|
||||||
|
GALUserPhotoAttr: string;
|
||||||
|
GALUserBirthdayAttr: string;
|
||||||
|
GALUserAddressAttr: string;
|
||||||
|
GALUserOrganizationAttr: string;
|
||||||
|
GALUserRoleAttr: string;
|
||||||
|
|
||||||
|
GALGroupNameAttr: string;
|
||||||
|
GALGroupEmailAttr: string;
|
||||||
|
|
||||||
|
GALAliasNameAttr: string;
|
||||||
|
GALAliasEmailAttr: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
sid: "",
|
||||||
|
contentLoaded: false,
|
||||||
|
checkLoading: false,
|
||||||
|
type: "",
|
||||||
|
settings: {
|
||||||
|
Name: "",
|
||||||
|
UseInGal: false,
|
||||||
|
Email: "",
|
||||||
|
Params: {
|
||||||
|
Dir: "",
|
||||||
|
MasterUsersGroup: "",
|
||||||
|
CacheTTL: 0,
|
||||||
|
ConnectURIs: [],
|
||||||
|
ConnectURIsString: "",
|
||||||
|
ConnectBindDN: "",
|
||||||
|
ConnectPassword: "",
|
||||||
|
ConnectBaseDN: "",
|
||||||
|
ConnectTLSMinVer: "",
|
||||||
|
UserObjectclass: "",
|
||||||
|
UserMailAttr: "",
|
||||||
|
UserQuotaAttr: "",
|
||||||
|
MemberofAttr: "",
|
||||||
|
UserAddSearchFilter: "",
|
||||||
|
EmailGroups: false,
|
||||||
|
GroupObjectclass: "",
|
||||||
|
GroupNameAttr: "",
|
||||||
|
GroupMailAttr: "",
|
||||||
|
GroupMemberAttr: "",
|
||||||
|
GroupMemberDn: false,
|
||||||
|
UserRDNAttr: "",
|
||||||
|
GroupAddSearchFilter: "",
|
||||||
|
EmailRedirect: false,
|
||||||
|
AliasObjectclass: "",
|
||||||
|
AliasMailAttr: "",
|
||||||
|
AliasRedirectAttr: "",
|
||||||
|
EmailAlternate: false,
|
||||||
|
AlternateMailAttr: "",
|
||||||
|
MasterUserGroupNameAttr: "",
|
||||||
|
MasterUserGroup: "",
|
||||||
|
GALCacheTTL: 0,
|
||||||
|
GALObjectsFilter: "",
|
||||||
|
GALUserNameAttr: "",
|
||||||
|
GALUserEmailAttr: "",
|
||||||
|
GALUserAltEmailAttr: "",
|
||||||
|
GALUserPhoneAttr: "",
|
||||||
|
GALUserPhotoAttr: "",
|
||||||
|
GALUserBirthdayAttr: "",
|
||||||
|
GALUserAddressAttr: "",
|
||||||
|
GALUserOrganizationAttr: "",
|
||||||
|
GALUserRoleAttr: "",
|
||||||
|
GALGroupNameAttr: "",
|
||||||
|
GALGroupEmailAttr: "",
|
||||||
|
GALAliasNameAttr: "",
|
||||||
|
GALAliasEmailAttr: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8 };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
page.sid = route.params.sid as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/settings`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
|
||||||
|
page.type = res.data.Type;
|
||||||
|
page.settings = res.data;
|
||||||
|
loadTextArea();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
saveTextArea();
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/settings`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.settings,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.userdb.settings.save_success"));
|
||||||
|
|
||||||
|
router.go(0);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
if (!page.settings.Email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveTextArea();
|
||||||
|
page.checkLoading = true;
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/settings/check`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.settings,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
page.checkLoading = false;
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(
|
||||||
|
t("components.admin.domains.userdb.settings.check_error") + res.error
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.domains.userdb.settings.check_success"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTextArea() {
|
||||||
|
if (page.settings.Params.ConnectURIs) {
|
||||||
|
page.settings.Params.ConnectURIsString =
|
||||||
|
page.settings.Params.ConnectURIs.join("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTextArea() {
|
||||||
|
if (page.settings.Params.ConnectURIsString) {
|
||||||
|
page.settings.Params.ConnectURIs =
|
||||||
|
page.settings.Params.ConnectURIsString.split("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,294 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="showAddModal(record)">{{
|
||||||
|
t("components.admin.domains.userdb.users.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.admin.domains.userdb.users.delete') + '?'"
|
||||||
|
:ok-text="t('components.admin.domains.userdb.users.ok')"
|
||||||
|
:cancel-text="t('components.admin.domains.userdb.users.cancel')"
|
||||||
|
@confirm="deleteUser(record.Uid)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.admin.domains.userdb.users.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<div style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal(undefined)">{{
|
||||||
|
$t("components.admin.domains.userdb.users.add")
|
||||||
|
}}</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="
|
||||||
|
page.add.Uid
|
||||||
|
? t(`components.admin.domains.userdb.users.edit`) +
|
||||||
|
' ' +
|
||||||
|
page.add.DisplayName
|
||||||
|
: t(`components.admin.domains.userdb.users.add`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.users.add_modal.display_name')
|
||||||
|
"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.DisplayName"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.users.add_modal.email')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.Email"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.users.add_modal.alt_emails')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="page.add.AlternateEmailsStr"> </a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.users.add_modal.quota')"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
v-model:value="page.add.Quota"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.domains.userdb.users.add_modal.password')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input-password autocomplete="off" v-model:value="page.add.Password">
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.domains.userdb.users.add_modal.password_again')
|
||||||
|
"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
autocomplete="off"
|
||||||
|
v-model:value="page.add.PasswordAgain"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="page.add.show = false">
|
||||||
|
{{ $t("components.admin.domains.userdb.users.add_modal.cancel") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
!page.add.DisplayName ||
|
||||||
|
!page.add.Email ||
|
||||||
|
(!page.add.Uid && (!page.add.Password || !page.add.PasswordAgain))
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="add"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.domains.userdb.users.add_modal.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 8 };
|
||||||
|
const wrapperCol = { span: 16 };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.users.col_name"),
|
||||||
|
dataIndex: "DisplayName",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.domains.userdb.users.col_email"),
|
||||||
|
dataIndex: "Email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
domain: string;
|
||||||
|
sid: string;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
Uid: string;
|
||||||
|
DisplayName: string;
|
||||||
|
Email: string;
|
||||||
|
AlternateEmails: string[];
|
||||||
|
AlternateEmailsStr: string;
|
||||||
|
Quota: number;
|
||||||
|
Password: string;
|
||||||
|
PasswordAgain: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
domain: "",
|
||||||
|
sid: "",
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
DisplayName: "",
|
||||||
|
Email: "",
|
||||||
|
AlternateEmails: [],
|
||||||
|
AlternateEmailsStr: "",
|
||||||
|
Quota: -1,
|
||||||
|
Password: "",
|
||||||
|
PasswordAgain: "",
|
||||||
|
Uid: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.sid = route.params.sid as string;
|
||||||
|
page.domain = route.params.domain as string;
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/users`
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal(record: any) {
|
||||||
|
if (record) {
|
||||||
|
page.add.Uid = record.Uid;
|
||||||
|
page.add.Email = record.Email;
|
||||||
|
page.add.DisplayName = record.DisplayName;
|
||||||
|
page.add.AlternateEmails = record.AlternateEmails;
|
||||||
|
page.add.AlternateEmailsStr = page.add.AlternateEmails.join("\n");
|
||||||
|
page.add.Quota = record.Quota;
|
||||||
|
page.add.Password = record.Password;
|
||||||
|
page.add.PasswordAgain = record.Password;
|
||||||
|
} else {
|
||||||
|
page.add.Uid = "";
|
||||||
|
page.add.Email = "";
|
||||||
|
page.add.DisplayName = "";
|
||||||
|
page.add.AlternateEmails = [];
|
||||||
|
page.add.AlternateEmailsStr = "";
|
||||||
|
page.add.Quota = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.Password = "";
|
||||||
|
page.add.PasswordAgain = "";
|
||||||
|
|
||||||
|
page.add.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
if (page.add.Password !== page.add.PasswordAgain) {
|
||||||
|
notifyError("Passwords mismatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.AlternateEmails = page.add.AlternateEmailsStr.split("\n");
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/users`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: page.add,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function deleteUser(uid: string) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/domains/${page.domain}/userdb/${page.sid}/users`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Uid: uid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,164 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button @click="unban(record.ip)">
|
||||||
|
{{ t("components.admin.security.blocked_ips.unblock") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="8">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.admin.security.blocked_ips.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button @click="unban('')">{{
|
||||||
|
page.withSearch
|
||||||
|
? $t("components.admin.security.blocked_ips.unblock_found")
|
||||||
|
: $t("components.admin.security.blocked_ips.unblock_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['2', '10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.security.blocked_ips.ip"),
|
||||||
|
dataIndex: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/blocked_ips?" + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = (res.data as Array<any>).map((i, idx) => {
|
||||||
|
return {
|
||||||
|
ip: i,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unban(ip: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("ip", ip);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/blocked_ips?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,215 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="bw-list-panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button @click="del(record.email)">
|
||||||
|
{{ t("components.admin.security.bw_list_email.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="8">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.admin.security.bw_list_email.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button @click="del('')">{{
|
||||||
|
page.withSearch
|
||||||
|
? $t("components.admin.security.bw_list_email.delete_found")
|
||||||
|
: $t("components.admin.security.bw_list_email.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button type="primary" @click="page.showAddModal = true">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{
|
||||||
|
$t("components.admin.security.bw_list_email.add_email")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.showAddModal"
|
||||||
|
:title="$t(`components.admin.security.bw_list_email.add_email`)"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.toAdd"
|
||||||
|
:placeholder="
|
||||||
|
$t(`components.admin.security.bw_list_email.add_email_placeholder`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
:loading="page.addLoading"
|
||||||
|
@click="add"
|
||||||
|
>{{ $t(`components.admin.security.bw_list_email.add`) }}</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { PlusOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.security.bw_list_email.email"),
|
||||||
|
dataIndex: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAddModal: boolean;
|
||||||
|
addLoading: boolean;
|
||||||
|
toAdd: string;
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
showAddModal: false,
|
||||||
|
addLoading: false,
|
||||||
|
toAdd: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/black_list/email?" + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = (res.data as Array<any>).map((i, idx) => {
|
||||||
|
return {
|
||||||
|
email: i,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del(email: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("email", email);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/black_list/email?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("email", page.toAdd);
|
||||||
|
|
||||||
|
page.addLoading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/black_list/email?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addLoading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.showAddModal = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,213 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="bw-list-panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button @click="del(record.ip)">
|
||||||
|
{{ t("components.admin.security.bw_list_ip.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="8">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.admin.security.bw_list_ip.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button @click="del('')">{{
|
||||||
|
page.withSearch
|
||||||
|
? $t("components.admin.security.bw_list_ip.delete_found")
|
||||||
|
: $t("components.admin.security.bw_list_ip.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button type="primary" @click="page.showAddModal = true">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ $t("components.admin.security.bw_list_ip.add_ip") }}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.showAddModal"
|
||||||
|
:title="$t(`components.admin.security.bw_list_ip.add_ip`)"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.toAdd"
|
||||||
|
:placeholder="
|
||||||
|
$t(`components.admin.security.bw_list_ip.add_ip_placeholder`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
:loading="page.addLoading"
|
||||||
|
@click="add"
|
||||||
|
>{{ $t(`components.admin.security.bw_list_ip.add`) }}</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { PlusOutlined } from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.security.bw_list_ip.ip"),
|
||||||
|
dataIndex: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAddModal: boolean;
|
||||||
|
addLoading: boolean;
|
||||||
|
toAdd: string;
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
showAddModal: false,
|
||||||
|
addLoading: false,
|
||||||
|
toAdd: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/black_list/ip?" + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = (res.data as Array<any>).map((i, idx) => {
|
||||||
|
return {
|
||||||
|
ip: i,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del(ip: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("ip", ip);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/black_list/ip?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("ip", page.toAdd);
|
||||||
|
|
||||||
|
page.addLoading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/black_list/ip?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addLoading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.showAddModal = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,215 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="bw-list-panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button @click="del(record.email)">
|
||||||
|
{{ t("components.admin.security.bw_list_email.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="8">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.admin.security.bw_list_email.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button @click="del('')">{{
|
||||||
|
page.withSearch
|
||||||
|
? $t("components.admin.security.bw_list_email.delete_found")
|
||||||
|
: $t("components.admin.security.bw_list_email.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button type="primary" @click="page.showAddModal = true">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{
|
||||||
|
$t("components.admin.security.bw_list_email.add_email")
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.showAddModal"
|
||||||
|
:title="$t(`components.admin.security.bw_list_email.add_email`)"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.toAdd"
|
||||||
|
:placeholder="
|
||||||
|
$t(`components.admin.security.bw_list_email.add_email_placeholder`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
:loading="page.addLoading"
|
||||||
|
@click="add"
|
||||||
|
>{{ $t(`components.admin.security.bw_list_email.add`) }}</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { PlusOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.security.bw_list_email.email"),
|
||||||
|
dataIndex: "email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAddModal: boolean;
|
||||||
|
addLoading: boolean;
|
||||||
|
toAdd: string;
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
showAddModal: false,
|
||||||
|
addLoading: false,
|
||||||
|
toAdd: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/white_list/email?" + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = (res.data as Array<any>).map((i, idx) => {
|
||||||
|
return {
|
||||||
|
email: i,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del(email: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("email", email);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/white_list/email?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("email", page.toAdd);
|
||||||
|
|
||||||
|
page.addLoading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/white_list/email?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addLoading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.showAddModal = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,213 @@
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="bw-list-panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-button @click="del(record.ip)">
|
||||||
|
{{ t("components.admin.security.bw_list_ip.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="8">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.admin.security.bw_list_ip.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button @click="del('')">{{
|
||||||
|
page.withSearch
|
||||||
|
? $t("components.admin.security.bw_list_ip.delete_found")
|
||||||
|
: $t("components.admin.security.bw_list_ip.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button type="primary" @click="page.showAddModal = true">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ $t("components.admin.security.bw_list_ip.add_ip") }}</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.showAddModal"
|
||||||
|
:title="$t(`components.admin.security.bw_list_ip.add_ip`)"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.toAdd"
|
||||||
|
:placeholder="
|
||||||
|
$t(`components.admin.security.bw_list_ip.add_ip_placeholder`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
:loading="page.addLoading"
|
||||||
|
@click="add"
|
||||||
|
>{{ $t(`components.admin.security.bw_list_ip.add`) }}</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { PlusOutlined } from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.security.bw_list_ip.ip"),
|
||||||
|
dataIndex: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAddModal: boolean;
|
||||||
|
addLoading: boolean;
|
||||||
|
toAdd: string;
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
showAddModal: false,
|
||||||
|
addLoading: false,
|
||||||
|
toAdd: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/white_list/ip?" + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = (res.data as Array<any>).map((i, idx) => {
|
||||||
|
return {
|
||||||
|
ip: i,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del(ip: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("ip", ip);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/white_list/ip?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("ip", page.toAdd);
|
||||||
|
|
||||||
|
page.addLoading = true;
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/security/white_list/ip?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
page.addLoading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.showAddModal = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Rules
|
||||||
|
:get-get-url="getUrl"
|
||||||
|
:get-delete-url="getUrl"
|
||||||
|
:get-save-url="getUrl"
|
||||||
|
:get-move-url="moveUrl"
|
||||||
|
:get-enable-url="enableUrl"
|
||||||
|
:conditions="conditionsList"
|
||||||
|
:actions="actionsList"
|
||||||
|
>
|
||||||
|
<template #condition-value="valueProps">
|
||||||
|
<ConditionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</ConditionValue>
|
||||||
|
</template>
|
||||||
|
<template #action-value="valueProps">
|
||||||
|
<ActionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</ActionValue>
|
||||||
|
</template>
|
||||||
|
</Rules>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ConditionValue from "@/components/common/rules/address/Conditions.vue";
|
||||||
|
import ActionValue from "@/components/common/rules/address/Actions.vue";
|
||||||
|
import { conditionsList } from "@/components/common/rules/address/Conditions.vue";
|
||||||
|
import { actionsList } from "@/components/common/rules/address/Actions.vue";
|
||||||
|
import Rules from "@/components/common/rules/Rules.vue";
|
||||||
|
|
||||||
|
function getUrl(): string {
|
||||||
|
return `/admin/settings/address_rules`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUrl(): string {
|
||||||
|
return `/admin/settings/address_rules/move`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableUrl(): string {
|
||||||
|
return `/admin/settings/address_rules/enable`;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item :label="t('components.admin.settings.calendars.freebusy')">
|
||||||
|
<a-switch v-model:checked="page.AllowFreebusy"> </a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('components.admin.settings.calendars.showfrom')">
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
:min="0"
|
||||||
|
v-model:value="page.ShowFromNWeeks"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('components.admin.settings.calendars.showto')">
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
:min="0"
|
||||||
|
v-model:value="page.ShowToNWeeks"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.admin.settings.calendars.localpart')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.SenderLP"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.contentLoaded"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="update"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.settings.calendars.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8, style: { "text-align": "left" } };
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
AllowFreebusy: boolean;
|
||||||
|
ShowFromNWeeks: number;
|
||||||
|
ShowToNWeeks: number;
|
||||||
|
SenderLP: string;
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
AllowFreebusy: false,
|
||||||
|
ShowFromNWeeks: 0,
|
||||||
|
ShowToNWeeks: 0,
|
||||||
|
SenderLP: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/admin/settings/calendars");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
page.AllowFreebusy = res.data.AllowFreebusy;
|
||||||
|
page.ShowFromNWeeks = res.data.ShowFromNWeeks;
|
||||||
|
page.ShowToNWeeks = res.data.ShowToNWeeks;
|
||||||
|
page.SenderLP = res.data.SenderLP;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
const res = await apiFetch("/admin/settings/calendars", {
|
||||||
|
method: "POST",
|
||||||
|
body: page,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.admin.settings.calendars.success"));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.save-button {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,272 @@
|
||||||
|
<template>
|
||||||
|
<a-card class="panel-content" v-if="page.contentLoaded">
|
||||||
|
<div v-if="page.accepted">
|
||||||
|
<a-descriptions
|
||||||
|
v-if="page.license"
|
||||||
|
:column="1"
|
||||||
|
:title="t(`components.admin.settings.license.title`)"
|
||||||
|
>
|
||||||
|
<a-descriptions-item :label="t(`components.admin.settings.license.id`)"
|
||||||
|
><div style="font-weight: bold">{{ page.license.ID }}</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.edition`)"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">{{ page.license.Edition }}</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.valid_till`)"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">
|
||||||
|
{{
|
||||||
|
page.license.ExpiresUnix > 0
|
||||||
|
? timeToFullDate(page.license.ExpiresUnix * 1000)
|
||||||
|
: $t("components.admin.settings.license.valid_forever")
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.max_mailbox_count`)"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">
|
||||||
|
{{ page.license.MailboxCount }}
|
||||||
|
</div></a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.send_report`)"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">
|
||||||
|
{{
|
||||||
|
page.license.SendReport && page.license.SendReportEmail !== ""
|
||||||
|
? page.license.SendReportEmail
|
||||||
|
: $t("components.admin.settings.license.send_report_no")
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.customer_name`)"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">
|
||||||
|
{{ page.license.CustomerName }}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.customer_type`)"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">
|
||||||
|
{{ page.license.CustomerTypeStr }}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t(`components.admin.settings.license.customer_data`)"
|
||||||
|
>
|
||||||
|
<a-descriptions :column="1">
|
||||||
|
<a-descriptions-item
|
||||||
|
v-for="{ Title, Value } in page.license.CustomerReqs"
|
||||||
|
:label="Title"
|
||||||
|
>
|
||||||
|
<div style="font-weight: bold">
|
||||||
|
{{ Value }}
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
<a-alert
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
v-else
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
:message="t(`components.admin.settings.license.no_install`)"
|
||||||
|
/>
|
||||||
|
<a-space style="display: flex; justify-content: center">
|
||||||
|
<a-button size="large" @click="page.showEula = true">
|
||||||
|
{{ $t("components.admin.settings.license.show_eula") }}
|
||||||
|
</a-button>
|
||||||
|
<a-upload
|
||||||
|
:file-list="page.files"
|
||||||
|
:before-upload="
|
||||||
|
(file: any) => {
|
||||||
|
upload(file)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-button size="large" :loading="page.uploading" type="primary">
|
||||||
|
<UploadOutlined />
|
||||||
|
{{ $t("components.admin.settings.license.load") }}
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<pre
|
||||||
|
style="
|
||||||
|
white-space: -moz-pre-wrap;
|
||||||
|
white-space: -pre-wrap;
|
||||||
|
white-space: -o-pre-wrap;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t("eula") }}
|
||||||
|
</pre>
|
||||||
|
<br />
|
||||||
|
<a-button
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
style="width: fit-content"
|
||||||
|
@click="accept"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.settings.license.accept") }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
<a-modal v-model:open="page.showEula" width="80%">
|
||||||
|
<template #footer></template>
|
||||||
|
<pre
|
||||||
|
style="
|
||||||
|
white-space: -moz-pre-wrap;
|
||||||
|
white-space: -pre-wrap;
|
||||||
|
white-space: -o-pre-wrap;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t("eula") }}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal, type UploadChangeParam } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { timeToDate, timeToDateTime, timeToFullDate } from "@/composables/misc";
|
||||||
|
import { uploadListProps } from "ant-design-vue/es/upload/interface";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
uploading: boolean;
|
||||||
|
contentLoaded: boolean;
|
||||||
|
accepted: boolean;
|
||||||
|
showEula: boolean;
|
||||||
|
files: any;
|
||||||
|
license: {
|
||||||
|
ID: string;
|
||||||
|
Edition: string;
|
||||||
|
ExpiresUnix: number;
|
||||||
|
MailboxCount: number;
|
||||||
|
SendReport: boolean;
|
||||||
|
SendReportEmail: string;
|
||||||
|
CustomerName: string;
|
||||||
|
CustomerType: number;
|
||||||
|
CustomerTypeStr: string;
|
||||||
|
CustomerReqs: {
|
||||||
|
Title: string;
|
||||||
|
Value: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
uploading: false,
|
||||||
|
files: [],
|
||||||
|
showEula: false,
|
||||||
|
contentLoaded: false,
|
||||||
|
accepted: false,
|
||||||
|
license: {
|
||||||
|
ID: "",
|
||||||
|
Edition: "",
|
||||||
|
ExpiresUnix: 0,
|
||||||
|
MailboxCount: 0,
|
||||||
|
SendReport: false,
|
||||||
|
SendReportEmail: "",
|
||||||
|
CustomerName: "",
|
||||||
|
CustomerType: 0,
|
||||||
|
CustomerTypeStr: "",
|
||||||
|
CustomerReqs: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/admin/settings/license");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.accepted = res.data.EulaAccepted;
|
||||||
|
page.license = res.data.License;
|
||||||
|
if (page.license)
|
||||||
|
if (page.license.CustomerType === 1) {
|
||||||
|
page.license.CustomerTypeStr = t(
|
||||||
|
"components.admin.settings.license.fiz_type"
|
||||||
|
);
|
||||||
|
} else if (page.license.CustomerType === 2) {
|
||||||
|
page.license.CustomerTypeStr = t(
|
||||||
|
"components.admin.settings.license.ur_type"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
page.contentLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function accept() {
|
||||||
|
const res = await apiFetch("/admin/settings/license/accept", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upload(file: any) {
|
||||||
|
page.uploading = true;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
const res = await apiFetch("/admin/settings/license", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
isFormData: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
page.uploading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
width: 30%;
|
||||||
|
min-width: 150px;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,257 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item :label="$t('components.admin.settings.settings_db.Type')">
|
||||||
|
<a-select v-model:value="page.new.Type" @change="getNew">
|
||||||
|
<a-select-option value="pgsqlSettingsDB"
|
||||||
|
>PostgreSQL</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="sqliteSettingsDB">SQLite</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="page.new.Type === 'pgsqlSettingsDB'">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.settings.settings_db.ConnParams.Host')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.ConnParams.Host"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.settings.settings_db.ConnParams.Port')"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
v-model:value="page.new.ConnParams.Port"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.settings.settings_db.ConnParams.Db')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.ConnParams.Db"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.settings.settings_db.ConnParams.User')"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.ConnParams.User"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.settings.settings_db.ConnParams.Pass')"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
autocomplete="off"
|
||||||
|
v-model:value="page.new.ConnParams.Pass"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.settings.settings_db.ConnParams.MaxConn')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
:min="1"
|
||||||
|
v-model:value="page.new.ConnParams.MaxConn"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.new.Type === 'sqliteSettingsDB'">
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.settings.settings_db.PathParams.DatabaseDir')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.PathParams.DatabaseDir"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-space style="display: flex; justify-content: center">
|
||||||
|
<a-button
|
||||||
|
:loading="page.checkLoading"
|
||||||
|
:disabled="!page.new.Type"
|
||||||
|
class="save-button"
|
||||||
|
size="large"
|
||||||
|
@click="check"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.settings.settings_db.check") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.new.Type || !page.contentLoaded"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
style="width: fit-content"
|
||||||
|
@click="saveAndRestart(save)"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.old.Type != page.new.Type
|
||||||
|
? $t("components.admin.settings.settings_db.change")
|
||||||
|
: $t("components.admin.settings.settings_db.save")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
interface ConnParams {
|
||||||
|
Host: string;
|
||||||
|
Port: number;
|
||||||
|
Db: string;
|
||||||
|
User: string;
|
||||||
|
Pass: string;
|
||||||
|
MaxConn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PathParams {
|
||||||
|
DatabaseDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
old: {
|
||||||
|
Type: string;
|
||||||
|
ConnParams: ConnParams;
|
||||||
|
PathParams: PathParams;
|
||||||
|
};
|
||||||
|
new: {
|
||||||
|
Type: string;
|
||||||
|
ConnParams: ConnParams;
|
||||||
|
PathParams: PathParams;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLoading: boolean;
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
new: {
|
||||||
|
Type: "",
|
||||||
|
ConnParams: {
|
||||||
|
Host: "",
|
||||||
|
Port: 0,
|
||||||
|
Db: "",
|
||||||
|
User: "",
|
||||||
|
Pass: "",
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
PathParams: {
|
||||||
|
DatabaseDir: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checkLoading: false,
|
||||||
|
old: {
|
||||||
|
Type: "",
|
||||||
|
ConnParams: {
|
||||||
|
Host: "",
|
||||||
|
Port: 0,
|
||||||
|
Db: "",
|
||||||
|
User: "",
|
||||||
|
Pass: "",
|
||||||
|
MaxConn: 0,
|
||||||
|
},
|
||||||
|
PathParams: {
|
||||||
|
DatabaseDir: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8 };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/admin/settings/settings_db");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.new = { ...res.data };
|
||||||
|
page.old = { ...res.data };
|
||||||
|
page.contentLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNew() {
|
||||||
|
if (page.new.Type === page.old.Type) {
|
||||||
|
page.new = { ...page.old };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/settings/settings_db/new?type=${page.new.Type}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.new = res.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save(): Promise<boolean> {
|
||||||
|
const res = await apiFetch("/admin/settings/settings_db", {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
page.checkLoading = true;
|
||||||
|
const res = await apiFetch("/admin/settings/settings_db/check", {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
page.checkLoading = false;
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(
|
||||||
|
t("components.admin.settings.settings_db.check_error") + res.error
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notifySuccess(t("components.admin.settings.settings_db.check_success"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.input-number {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.save-button {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,388 @@
|
||||||
|
<template>
|
||||||
|
<a-flex wrap justify="center" gap="large">
|
||||||
|
<a-card>
|
||||||
|
<a-statistic
|
||||||
|
:title="t('components.admin.settings.smtp_queue.manage.total')"
|
||||||
|
:value="page.total"
|
||||||
|
style="margin-right: 50px"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
<a-card>
|
||||||
|
<a-statistic
|
||||||
|
:title="t('components.admin.settings.smtp_queue.manage.new')"
|
||||||
|
:value="page.new"
|
||||||
|
style="margin-right: 50px"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card>
|
||||||
|
<a-statistic
|
||||||
|
:title="t('components.admin.settings.smtp_queue.manage.resend')"
|
||||||
|
:value="page.awaited"
|
||||||
|
style="margin-right: 50px"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
</a-flex>
|
||||||
|
<br />
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="processMsg(record.ID)">{{
|
||||||
|
t("components.admin.settings.smtp_queue.manage.process_now")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.delete') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.settings.smtp_queue.manage.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteMsg(record.ID)"
|
||||||
|
>
|
||||||
|
<a-button danger>
|
||||||
|
{{ t("components.admin.settings.smtp_queue.manage.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'WillResend'">
|
||||||
|
{{
|
||||||
|
record.WillResend === ""
|
||||||
|
? t(
|
||||||
|
"components.admin.settings.smtp_queue.manage.column_will_resend_now"
|
||||||
|
)
|
||||||
|
: record.WillResend
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="6">
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="18">
|
||||||
|
<a-space
|
||||||
|
v-if="page.withSearch"
|
||||||
|
wrap
|
||||||
|
style="display: flex; justify-content: end"
|
||||||
|
>
|
||||||
|
<a-button @click="processAwaiting">{{
|
||||||
|
$t("components.admin.settings.smtp_queue.manage.process_filtered")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t(
|
||||||
|
'components.admin.settings.smtp_queue.manage.clear_filtered'
|
||||||
|
) + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.settings.smtp_queue.manage.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.cancel')
|
||||||
|
"
|
||||||
|
@confirm="clearAll"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.settings.smtp_queue.manage.clear_filtered")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
<a-space v-else wrap style="display: flex; justify-content: end">
|
||||||
|
<a-button @click="processAwaiting">{{
|
||||||
|
$t(
|
||||||
|
"components.admin.settings.smtp_queue.manage.process_awaiting_resend"
|
||||||
|
)
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t(
|
||||||
|
'components.admin.settings.smtp_queue.manage.delete_awaiting_resend'
|
||||||
|
) + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.settings.smtp_queue.manage.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteAwaiting"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t(
|
||||||
|
"components.admin.settings.smtp_queue.manage.delete_awaiting_resend"
|
||||||
|
)
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.clear_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.settings.smtp_queue.manage.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.cancel')
|
||||||
|
"
|
||||||
|
@confirm="clearAll"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.settings.smtp_queue.manage.clear_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<!-- <a-space>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.clear_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.settings.smtp_queue.manage.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.cancel')
|
||||||
|
"
|
||||||
|
@confirm="clearAll"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.admin.settings.smtp_queue.manage.clear_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="
|
||||||
|
t(
|
||||||
|
'components.admin.settings.smtp_queue.manage.delete_awaiting_resend'
|
||||||
|
) + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.admin.settings.smtp_queue.manage.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.admin.settings.smtp_queue.manage.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteAwaiting"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t(
|
||||||
|
"components.admin.settings.smtp_queue.manage.delete_awaiting_resend"
|
||||||
|
)
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button @click="processAwaiting">{{
|
||||||
|
$t(
|
||||||
|
"components.admin.settings.smtp_queue.manage.process_awaiting_resend"
|
||||||
|
)
|
||||||
|
}}</a-button>
|
||||||
|
</a-space> -->
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { DeleteOutlined } from "@ant-design/icons-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.admin.settings.smtp_queue.manage.column_sender"),
|
||||||
|
dataIndex: "Sender",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.settings.smtp_queue.manage.column_receiver"),
|
||||||
|
dataIndex: "Receiver",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.settings.smtp_queue.manage.column_added"),
|
||||||
|
dataIndex: "Added",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.admin.settings.smtp_queue.manage.column_will_resend"),
|
||||||
|
dataIndex: "WillResend",
|
||||||
|
key: "WillResend",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
total: number;
|
||||||
|
new: number;
|
||||||
|
awaited: number;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
total: 0,
|
||||||
|
new: 0,
|
||||||
|
awaited: 0,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/settings/smtp_queue/messages?" + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.total = res.data.Total;
|
||||||
|
page.new = res.data.New;
|
||||||
|
page.awaited = res.data.Awaited;
|
||||||
|
page.data = res.data.Messages;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearAll() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/settings/smtp_queue/messages/delete?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function deleteMsg(id: string) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/settings/smtp_queue/messages/delete/${id}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processMsg(id: string) {
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/settings/smtp_queue/messages/retry/${id}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function deleteAwaiting() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/settings/smtp_queue/messages/delete_awaiting?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function processAwaiting() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
"/admin/settings/smtp_queue/messages/retry?" + params.toString(),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,304 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.admin.settings.smtp_queue.settings.Type')"
|
||||||
|
>
|
||||||
|
<a-select v-model:value="page.new.Type" @change="getNew">
|
||||||
|
<a-select-option value="pgsqlSmtpQueue">PostgreSQL</a-select-option>
|
||||||
|
<a-select-option value="sqliteFilesSmtpQueue"
|
||||||
|
>SQLite</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
page.new.Type === 'pgsqlSmtpQueue'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.ConnParams.Host'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.ConnParams.Host"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.ConnParams.Port'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
class="form-input-number"
|
||||||
|
v-model:value="page.new.ConnParams.Port"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t('components.admin.settings.smtp_queue.settings.ConnParams.Db')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.ConnParams.Db"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.ConnParams.User'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.ConnParams.User"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.ConnParams.Pass'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
autocomplete="off"
|
||||||
|
v-model:value="page.new.ConnParams.Pass"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.ConnParams.MaxConn'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
:min="1"
|
||||||
|
class="form-input-number"
|
||||||
|
v-model:value="page.new.ConnParams.MaxConn"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.ConnParams.MaxQueueItems'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
:min="20"
|
||||||
|
class="form-input-number"
|
||||||
|
v-model:value="page.new.ConnParams.MaxQueueItems"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.new.Type === 'sqliteFilesSmtpQueue'">
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
'components.admin.settings.smtp_queue.settings.PathParams.DatabaseDir'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.new.PathParams.SMTPQueueDir">
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-space style="display: flex; justify-content: center">
|
||||||
|
<a-button
|
||||||
|
:loading="page.checkLoading"
|
||||||
|
:disabled="!page.new.Type"
|
||||||
|
class="save-button"
|
||||||
|
size="large"
|
||||||
|
@click="check"
|
||||||
|
>
|
||||||
|
{{ $t("components.admin.settings.smtp_queue.settings.check") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.new.Type || !page.contentLoaded"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
style="width: fit-content"
|
||||||
|
@click="saveAndRestart(save)"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
page.old.Type != page.new.Type
|
||||||
|
? $t("components.admin.settings.smtp_queue.settings.change")
|
||||||
|
: $t("components.admin.settings.smtp_queue.settings.save")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { saveAndRestart } from "@/composables/restart";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
interface ConnParams {
|
||||||
|
Host: string;
|
||||||
|
Port: number;
|
||||||
|
Db: string;
|
||||||
|
User: string;
|
||||||
|
Pass: string;
|
||||||
|
MaxConn: number;
|
||||||
|
MaxQueueItems: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PathParams {
|
||||||
|
SMTPQueueDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
old: {
|
||||||
|
Type: string;
|
||||||
|
ConnParams: ConnParams;
|
||||||
|
PathParams: PathParams;
|
||||||
|
};
|
||||||
|
new: {
|
||||||
|
Type: string;
|
||||||
|
ConnParams: ConnParams;
|
||||||
|
PathParams: PathParams;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLoading: boolean;
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
new: {
|
||||||
|
Type: "",
|
||||||
|
ConnParams: {
|
||||||
|
Host: "",
|
||||||
|
Port: 0,
|
||||||
|
Db: "",
|
||||||
|
User: "",
|
||||||
|
Pass: "",
|
||||||
|
MaxConn: 0,
|
||||||
|
MaxQueueItems: 0,
|
||||||
|
},
|
||||||
|
PathParams: {
|
||||||
|
SMTPQueueDir: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checkLoading: false,
|
||||||
|
old: {
|
||||||
|
Type: "",
|
||||||
|
ConnParams: {
|
||||||
|
Host: "",
|
||||||
|
Port: 0,
|
||||||
|
Db: "",
|
||||||
|
User: "",
|
||||||
|
Pass: "",
|
||||||
|
MaxConn: 0,
|
||||||
|
MaxQueueItems: 0,
|
||||||
|
},
|
||||||
|
PathParams: {
|
||||||
|
SMTPQueueDir: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8 };
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/admin/settings/smtp_queue/settings");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.new = { ...res.data };
|
||||||
|
page.old = { ...res.data };
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNew() {
|
||||||
|
if (page.new.Type === page.old.Type) {
|
||||||
|
page.new = { ...page.old };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/admin/settings/smtp_queue/settings/new?type=${page.new.Type}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.new = res.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save(): Promise<boolean> {
|
||||||
|
const res = await apiFetch("/admin/settings/smtp_queue/settings", {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check() {
|
||||||
|
page.checkLoading = true;
|
||||||
|
const res = await apiFetch("/admin/settings/smtp_queue/settings/check", {
|
||||||
|
method: "POST",
|
||||||
|
body: page.new,
|
||||||
|
});
|
||||||
|
page.checkLoading = false;
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(
|
||||||
|
t("components.admin.settings.smtp_queue.settings.check_error") + res.error
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notifySuccess(
|
||||||
|
t("components.admin.settings.smtp_queue.settings.check_success")
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,815 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.common.shared_address_books.search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ t("components.common.shared_address_books.add_book") }}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_address_books.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_address_books.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.common.shared_address_books.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteBook(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_address_books.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_address_books.delete_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_address_books.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.common.shared_address_books.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteBook(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_address_books.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAccessModal(record.Id)">{{
|
||||||
|
t("components.common.shared_address_books.edit_access")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button @click="showPropModal(record.Id, record.Name)">{{
|
||||||
|
t("components.common.shared_address_books.properties")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.common.shared_address_books.delete') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_address_books.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_address_books.cancel')"
|
||||||
|
@confirm="deleteBook(record.Id)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.common.shared_address_books.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="t('components.common.shared_address_books.add_book')"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
style="margin-top: 8px"
|
||||||
|
:placeholder="$t('components.common.shared_address_books.add_name')"
|
||||||
|
v-model:value="page.add.Name"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button type="primary" :disabled="!page.add.Name" @click="addBook">
|
||||||
|
{{ t("components.common.shared_address_books.add_book") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.props.show"
|
||||||
|
:title="t('components.common.shared_address_books.properties')"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
:placeholder="$t('components.common.shared_address_books.add_name')"
|
||||||
|
v-model:value="page.props.Name"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button type="primary" :disabled="!page.props.Name" @click="updateBook">
|
||||||
|
{{ t("components.common.shared_address_books.update_book") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.access.show"
|
||||||
|
style="width: 50%; min-width: 720px"
|
||||||
|
@cancel="get"
|
||||||
|
>
|
||||||
|
<a-table
|
||||||
|
:columns="accessColumns"
|
||||||
|
:data-source="page.access.data"
|
||||||
|
:loading="page.access.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-radio-group
|
||||||
|
@change="getAccess()"
|
||||||
|
v-model:value="page.access.type"
|
||||||
|
>
|
||||||
|
<a-radio-button value="email">{{
|
||||||
|
t("components.common.shared_address_books.access_type_email")
|
||||||
|
}}</a-radio-button>
|
||||||
|
<a-radio-button value="group">{{
|
||||||
|
t("components.common.shared_address_books.access_type_group")
|
||||||
|
}}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.access.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.common.shared_address_books.access_search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="getAccess"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col
|
||||||
|
style="display: flex; align-items: flex-end; justify-content: end"
|
||||||
|
:span="6"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
v-if="page.access.type === 'email'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.access.showAdd = true"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"components.common.shared_address_books.access_type_email_add"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="page.access.type === 'group'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.access.showAdd = true"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"components.common.shared_address_books.access_type_group_add"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.access.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_address_books.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_address_books.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.common.shared_address_books.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_address_books.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_address_books.delete_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_address_books.ok')"
|
||||||
|
:cancel-text="
|
||||||
|
t('components.common.shared_address_books.cancel')
|
||||||
|
"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_address_books.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.common.shared_address_books.delete') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_address_books.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_address_books.cancel')"
|
||||||
|
@confirm="deleteAccess(record.Name)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.common.shared_address_books.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'perm'">
|
||||||
|
<a-select
|
||||||
|
v-model:value="record.Perm"
|
||||||
|
style="min-width: 150px"
|
||||||
|
@change="updateAccess(record.Name, record.Perm)"
|
||||||
|
>
|
||||||
|
<a-select-option value="read">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_address_books.access_perm_read")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="all">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_address_books.access_perm_all")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.access.pagination.current"
|
||||||
|
:defaultPageSize="page.access.pagination.size"
|
||||||
|
:total="page.access.pagination.total"
|
||||||
|
@change="accessPageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.access.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<template #footer> </template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.access.showAdd"
|
||||||
|
:title="
|
||||||
|
page.access.type === 'email'
|
||||||
|
? t('components.common.shared_address_books.access_type_email_add')
|
||||||
|
: t('components.common.shared_address_books.access_type_group_add')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
<a-form>
|
||||||
|
<a-form-item>
|
||||||
|
<template v-if="page.access.type === 'email'">
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.access.inputValue"
|
||||||
|
:placeholder="
|
||||||
|
t(
|
||||||
|
'components.common.shared_address_books.access_type_email_placeholder'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.access.type === 'group'">
|
||||||
|
<a-select
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="page.access.groups.length === 0"
|
||||||
|
v-model:value="page.access.selectValue"
|
||||||
|
:options="page.access.groups"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model:value="page.access.perm">
|
||||||
|
<a-select-option value="read">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_address_books.access_perm_read")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="all">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_address_books.access_perm_all")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
(page.access.type === 'email' && page.access.inputValue === '') ||
|
||||||
|
(page.access.type === 'group' && page.access.selectValue === '')
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
addAccess(
|
||||||
|
page.access.type === 'email'
|
||||||
|
? page.access.inputValue
|
||||||
|
: page.access.selectValue
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ t("components.common.shared_address_books.access_add") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_address_books.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const accessColumns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_address_books.access_col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_address_books.access_col_permissions"),
|
||||||
|
dataIndex: "Perm",
|
||||||
|
key: "perm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
domain?: string;
|
||||||
|
user: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
all_groups: string[];
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
Name: string;
|
||||||
|
};
|
||||||
|
props: {
|
||||||
|
show: boolean;
|
||||||
|
Name: string;
|
||||||
|
Id: number;
|
||||||
|
};
|
||||||
|
access: {
|
||||||
|
inputValue: string;
|
||||||
|
selectValue: string;
|
||||||
|
perm: string;
|
||||||
|
type: string;
|
||||||
|
data: any[];
|
||||||
|
loading: boolean;
|
||||||
|
show: boolean;
|
||||||
|
Id: number;
|
||||||
|
groups: any[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAdd: boolean;
|
||||||
|
withSearch: boolean;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
all_groups: [],
|
||||||
|
access: {
|
||||||
|
withSearch: false,
|
||||||
|
inputValue: "",
|
||||||
|
selectValue: "",
|
||||||
|
perm: "read",
|
||||||
|
type: "users",
|
||||||
|
data: [],
|
||||||
|
loading: false,
|
||||||
|
show: false,
|
||||||
|
Id: 0,
|
||||||
|
groups: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
showAdd: false,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
getGroups();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books/groups`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books/groups`;
|
||||||
|
}
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.all_groups = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString());
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
function accessPageChange(current: number) {
|
||||||
|
page.access.pagination.current = current;
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addBook() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Name: page.add.Name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateBook() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
Name: page.props.Name,
|
||||||
|
Id: page.props.Id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.props.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function showAccessModal(id: number) {
|
||||||
|
page.access.Id = id;
|
||||||
|
page.access.show = true;
|
||||||
|
page.access.type = "email";
|
||||||
|
page.access.inputValue = "";
|
||||||
|
getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showPropModal(id: number, name: string) {
|
||||||
|
page.props.Name = name;
|
||||||
|
page.props.show = true;
|
||||||
|
page.props.Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal() {
|
||||||
|
page.add.show = true;
|
||||||
|
page.add.Name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteBook(id: number | undefined) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAccess() {
|
||||||
|
page.access.withSearch = page.access.pagination.search !== "";
|
||||||
|
page.access.loading = true;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", page.access.Id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("page", String(page.access.pagination.current));
|
||||||
|
params.append("size", String(page.access.pagination.size));
|
||||||
|
params.append("search", page.access.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString());
|
||||||
|
|
||||||
|
page.access.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.data = [];
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
for (let index = 0; index < res.data.length; index++) {
|
||||||
|
const element = res.data[index];
|
||||||
|
|
||||||
|
page.access.data.push({
|
||||||
|
Name: element.AccessTo,
|
||||||
|
Perm: element.Permissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.pagination.total = res.total;
|
||||||
|
|
||||||
|
page.access.groups = [];
|
||||||
|
page.access.selectValue = "";
|
||||||
|
|
||||||
|
if (page.access.type === "group") {
|
||||||
|
for (let j = 0; j < page.all_groups.length; j++) {
|
||||||
|
const group = page.all_groups[j];
|
||||||
|
let skip = false;
|
||||||
|
for (let i = 0; i < page.access.data.length; i++) {
|
||||||
|
const alreadyHas = page.access.data[i];
|
||||||
|
if (alreadyHas.Name === group) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
page.access.groups.push({ value: group });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.access.groups.length) {
|
||||||
|
page.access.selectValue = page.access.groups[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", page.access.Id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("page", String(page.access.pagination.current));
|
||||||
|
params.append("size", String(page.access.pagination.size));
|
||||||
|
params.append("search", page.access.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAccess(access: string, perm: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", page.access.Id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
Perm: perm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", page.access.Id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_address_books/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/address_books/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
Perm: page.access.perm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.showAdd = false;
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,475 @@
|
||||||
|
<template>
|
||||||
|
<div >
|
||||||
|
<a-table class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.common.shared_calendars.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ t("components.common.shared_calendars.add_calendar") }}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_calendars.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_calendars.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_calendars.cancel')"
|
||||||
|
@confirm="deleteCalendar(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_calendars.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_calendars.delete_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_calendars.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_calendars.cancel')"
|
||||||
|
@confirm="deleteCalendar(undefined)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_calendars.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="showAccessModal(record.Id)">{{
|
||||||
|
t("components.common.shared_calendars.edit_access")
|
||||||
|
}}</a-button>
|
||||||
|
<a-button @click="showPropModal(record)">{{
|
||||||
|
t("components.common.shared_calendars.properties")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.common.shared_calendars.delete') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_calendars.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_calendars.cancel')"
|
||||||
|
@confirm="deleteCalendar(record.Id)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.common.shared_calendars.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 600px"
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="t('components.common.shared_calendars.add_calendar')"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item :label="$t('components.common.shared_calendars.add_name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.add.cal.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.common.shared_calendars.add_freebusy')"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.add.cal.AllowFreebusy"> </a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.common.shared_calendars.add_showfrom')"
|
||||||
|
>
|
||||||
|
<a-input-number :min="0" v-model:value="page.add.cal.ShowFromNWeeks">
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('components.common.shared_calendars.add_showto')">
|
||||||
|
<a-input-number :min="0" v-model:value="page.add.cal.ShowToNWeeks">
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('components.common.shared_calendars.add_color')">
|
||||||
|
<input type="color" :value="page.add.cal.Color" @input="(e: any) => (page.add.cal.Color = e.target.value)">
|
||||||
|
</input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.add.cal.Name"
|
||||||
|
@click="addCalendar"
|
||||||
|
>
|
||||||
|
{{ t("components.common.shared_calendars.add_calendar") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
style="width: 600px"
|
||||||
|
v-model:open="page.props.show"
|
||||||
|
:title="t('components.common.shared_calendars.properties')"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" labelWrap>
|
||||||
|
<a-form-item :label="$t('components.common.shared_calendars.add_name')"
|
||||||
|
:rules="[{ required: true }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="page.props.cal.Name"> </a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.common.shared_calendars.add_freebusy')"
|
||||||
|
>
|
||||||
|
<a-switch v-model:checked="page.props.cal.AllowFreebusy"> </a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="$t('components.common.shared_calendars.add_showfrom')"
|
||||||
|
>
|
||||||
|
<a-input-number :min="0" v-model:value="page.props.cal.ShowFromNWeeks">
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('components.common.shared_calendars.add_showto')">
|
||||||
|
<a-input-number :min="0" v-model:value="page.props.cal.ShowToNWeeks">
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$t('components.common.shared_calendars.add_color')">
|
||||||
|
<input type="color" :value="page.props.cal.Color" @input="(e: any) => (page.props.cal.Color = e.target.value)">
|
||||||
|
</input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!page.props.cal.Name"
|
||||||
|
@click="updateCalendar"
|
||||||
|
>
|
||||||
|
{{ t("components.common.shared_calendars.update_calendar") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<CalendarsAccess v-if="page.access.show"
|
||||||
|
v-model:open="page.access.show"
|
||||||
|
:type="page.access.type"
|
||||||
|
:id="page.access.Id"
|
||||||
|
:domain="props.domain"
|
||||||
|
:resources="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
</CalendarsAccess>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import CalendarsAccess from "./CalendarsAccess.vue";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8 };
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_calendars.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
domain?: string;
|
||||||
|
user: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
interface Calendar {
|
||||||
|
Id: number;
|
||||||
|
Name: string;
|
||||||
|
ShowFromNWeeks: string;
|
||||||
|
ShowToNWeeks: string;
|
||||||
|
AllowFreebusy: boolean;
|
||||||
|
Color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
all_groups: string[];
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
cal: Calendar;
|
||||||
|
};
|
||||||
|
props: {
|
||||||
|
show: boolean;
|
||||||
|
cal: Calendar;
|
||||||
|
};
|
||||||
|
access: {
|
||||||
|
perm: string;
|
||||||
|
type: string;
|
||||||
|
show: boolean;
|
||||||
|
Id: number;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
cal: {
|
||||||
|
Id: 0,
|
||||||
|
Name: "",
|
||||||
|
ShowFromNWeeks: "",
|
||||||
|
ShowToNWeeks: "",
|
||||||
|
AllowFreebusy: false,
|
||||||
|
Color: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
all_groups: [],
|
||||||
|
access: {
|
||||||
|
perm: "read",
|
||||||
|
type: "users",
|
||||||
|
show: false,
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
show: false,
|
||||||
|
cal: {
|
||||||
|
Id: 0,
|
||||||
|
Name: "",
|
||||||
|
ShowFromNWeeks: "",
|
||||||
|
ShowToNWeeks: "",
|
||||||
|
AllowFreebusy: false,
|
||||||
|
Color: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_calendars?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString());
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function addCalendar() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_calendars`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: page.add.cal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCalendar() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_calendars`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: page.props.cal,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.props.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function showAccessModal(id: number) {
|
||||||
|
page.access.Id = id;
|
||||||
|
page.access.show = true;
|
||||||
|
page.access.type = "email";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showPropModal(record: any) {
|
||||||
|
page.props.cal = {...record};
|
||||||
|
page.props.show = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showAddModal() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_calendars/default`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars/default`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = true;
|
||||||
|
page.add.cal.Name = "";
|
||||||
|
page.add.cal.ShowFromNWeeks = res.data.ShowFromNWeeks;
|
||||||
|
page.add.cal.ShowToNWeeks = res.data.ShowToNWeeks;
|
||||||
|
page.add.cal.AllowFreebusy = res.data.AllowFreebusy;
|
||||||
|
page.add.cal.Color = res.data.Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteCalendar(id: number | undefined) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_calendars?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,504 @@
|
||||||
|
<template>
|
||||||
|
<a-modal style="width: 50%; min-width: 720px" v-model:open="openModel">
|
||||||
|
<a-table
|
||||||
|
:columns="accessColumns"
|
||||||
|
:data-source="page.access.data"
|
||||||
|
:loading="page.access.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-radio-group
|
||||||
|
v-if="!props.resources"
|
||||||
|
@change="getAccess()"
|
||||||
|
v-model:value="page.access.type"
|
||||||
|
>
|
||||||
|
<a-radio-button value="email">{{
|
||||||
|
t("components.common.shared_calendars.access_type_email")
|
||||||
|
}}</a-radio-button>
|
||||||
|
<a-radio-button value="group">{{
|
||||||
|
t("components.common.shared_calendars.access_type_group")
|
||||||
|
}}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.access.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.common.shared_calendars.access_search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="getAccess"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col
|
||||||
|
style="display: flex; align-items: flex-end; justify-content: end"
|
||||||
|
:span="6"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
v-if="page.access.type === 'email'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.access.showAdd = true"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t("components.common.shared_calendars.access_type_email_add")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="page.access.type === 'group'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.access.showAdd = true"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t("components.common.shared_calendars.access_type_group_add")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.access.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_calendars.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_calendars.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_calendars.cancel')"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_calendars.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_calendars.delete_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_calendars.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_calendars.cancel')"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_calendars.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.common.shared_calendars.delete') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_calendars.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_calendars.cancel')"
|
||||||
|
@confirm="deleteAccess(record.Name)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.common.shared_calendars.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'perm'">
|
||||||
|
<a-select
|
||||||
|
v-if="!props.resources"
|
||||||
|
v-model:value="record.Perm"
|
||||||
|
style="min-width: 150px"
|
||||||
|
@change="updateAccess(record.Name, record.Perm)"
|
||||||
|
>
|
||||||
|
<a-select-option value="read">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_calendars.access_perm_read")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="all">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_calendars.access_perm_all")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
<div v-else>
|
||||||
|
{{
|
||||||
|
record.Perm === "read"
|
||||||
|
? t("components.common.shared_calendars.access_perm_read")
|
||||||
|
: t("components.common.shared_calendars.access_perm_all")
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.access.pagination.current"
|
||||||
|
:defaultPageSize="page.access.pagination.size"
|
||||||
|
:total="page.access.pagination.total"
|
||||||
|
@change="accessPageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.access.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<template #footer> </template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.access.showAdd"
|
||||||
|
:title="
|
||||||
|
page.access.type === 'email'
|
||||||
|
? t('components.common.shared_calendars.access_type_email_add')
|
||||||
|
: t('components.common.shared_calendars.access_type_group_add')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
<a-form>
|
||||||
|
<a-form-item>
|
||||||
|
<template v-if="page.access.type === 'email'">
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.access.inputValue"
|
||||||
|
:placeholder="
|
||||||
|
t(
|
||||||
|
'components.common.shared_calendars.access_type_email_placeholder'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.access.type === 'group'">
|
||||||
|
<a-select
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="page.access.groups.length === 0"
|
||||||
|
v-model:value="page.access.selectValue"
|
||||||
|
:options="page.access.groups"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="!props.resources">
|
||||||
|
<a-select v-model:value="page.access.perm">
|
||||||
|
<a-select-option value="read">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_calendars.access_perm_read")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="all">
|
||||||
|
{{
|
||||||
|
t("components.common.shared_calendars.access_perm_all")
|
||||||
|
}}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
(page.access.type === 'email' && page.access.inputValue === '') ||
|
||||||
|
(page.access.type === 'group' && page.access.selectValue === '')
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
addAccess(
|
||||||
|
page.access.type === 'email'
|
||||||
|
? page.access.inputValue
|
||||||
|
: page.access.selectValue
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ t("components.common.shared_calendars.access_add") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const openModel = defineModel<boolean>("open");
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
type: string;
|
||||||
|
id: number;
|
||||||
|
domain?: string;
|
||||||
|
resources: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const accessColumns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_calendars.access_col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_calendars.access_col_permissions"),
|
||||||
|
dataIndex: "Perm",
|
||||||
|
key: "perm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
all_groups: string[];
|
||||||
|
access: {
|
||||||
|
inputValue: string;
|
||||||
|
selectValue: string;
|
||||||
|
perm: string;
|
||||||
|
type: string;
|
||||||
|
data: any[];
|
||||||
|
loading: boolean;
|
||||||
|
show: boolean;
|
||||||
|
groups: any[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAdd: boolean;
|
||||||
|
withSearch: boolean;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
all_groups: [],
|
||||||
|
access: {
|
||||||
|
withSearch: false,
|
||||||
|
inputValue: "",
|
||||||
|
selectValue: "",
|
||||||
|
perm: "read",
|
||||||
|
type: "users",
|
||||||
|
data: [],
|
||||||
|
loading: false,
|
||||||
|
show: false,
|
||||||
|
groups: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
showAdd: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.access.inputValue = "";
|
||||||
|
page.access.type = props.type;
|
||||||
|
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
await getGroups();
|
||||||
|
getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/${
|
||||||
|
props.resources ? `resources/resources` : `shared_calendars`
|
||||||
|
}/groups`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars/groups`;
|
||||||
|
}
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.all_groups = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAccess() {
|
||||||
|
page.access.withSearch = page.access.pagination.search !== "";
|
||||||
|
page.access.loading = true;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", props.id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("page", String(page.access.pagination.current));
|
||||||
|
params.append("size", String(page.access.pagination.size));
|
||||||
|
params.append("search", page.access.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/${
|
||||||
|
props.resources ? `resources/resources` : `shared_calendars`
|
||||||
|
}/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString());
|
||||||
|
|
||||||
|
page.access.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.data = [];
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
for (let index = 0; index < res.data.length; index++) {
|
||||||
|
const element = res.data[index];
|
||||||
|
|
||||||
|
page.access.data.push({
|
||||||
|
Name: element.AccessTo,
|
||||||
|
Perm: element.Permissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.pagination.total = res.total;
|
||||||
|
|
||||||
|
page.access.groups = [];
|
||||||
|
page.access.selectValue = "";
|
||||||
|
|
||||||
|
if (page.access.type === "group") {
|
||||||
|
for (let j = 0; j < page.all_groups.length; j++) {
|
||||||
|
const group = page.all_groups[j];
|
||||||
|
let skip = false;
|
||||||
|
for (let i = 0; i < page.access.data.length; i++) {
|
||||||
|
const alreadyHas = page.access.data[i];
|
||||||
|
if (alreadyHas.Name === group) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
page.access.groups.push({ value: group });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.access.groups.length) {
|
||||||
|
page.access.selectValue = page.access.groups[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", props.id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("page", String(page.access.pagination.current));
|
||||||
|
params.append("size", String(page.access.pagination.size));
|
||||||
|
params.append("search", page.access.pagination.search);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/${
|
||||||
|
props.resources ? `resources/resources` : `shared_calendars`
|
||||||
|
}/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.common.shared_calendars.remove_success"));
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAccess(access: string, perm: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", props.id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/${
|
||||||
|
props.resources ? `resources/resources` : `shared_calendars`
|
||||||
|
}/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
Perm: perm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.common.shared_calendars.update_success"));
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("id", props.id.toString());
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/${
|
||||||
|
props.resources ? `resources/resources` : `shared_calendars`
|
||||||
|
}/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/calendars/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
Perm: page.access.perm,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.common.shared_calendars.add_success"));
|
||||||
|
|
||||||
|
page.access.showAdd = false;
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
function accessPageChange(current: number) {
|
||||||
|
page.access.pagination.current = current;
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,755 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel-content">
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
@click="
|
||||||
|
page.currBaseDir = rootBaseDir;
|
||||||
|
page.lastBaseDir = [];
|
||||||
|
get();
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<HomeOutlined />
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
:disabled="page.lastBaseDir.length === 0"
|
||||||
|
@click="
|
||||||
|
page.currBaseDir = page.lastBaseDir.pop() as BaseDir;
|
||||||
|
get();
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ArrowLeftOutlined />
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
style="width: 250px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.common.shared_folders.search') +
|
||||||
|
page.currBaseDir.CName
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showAddModal">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ t("components.common.shared_folders.add_folder") }}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_folders.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_folders.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_folders.cancel')"
|
||||||
|
@confirm="deleteFolder('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_folders.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="t('components.common.shared_folders.delete_all') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_folders.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_folders.cancel')"
|
||||||
|
@confirm="deleteFolder('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_folders.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
props.user !== 'admin' && props.user !== record.Creator
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="showAccessModal(record.Name)"
|
||||||
|
>{{ t("components.common.shared_folders.edit_access") }}</a-button
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.common.shared_folders.delete') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_folders.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_folders.cancel')"
|
||||||
|
@confirm="deleteFolder(record.Name)"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
props.user !== 'admin' && props.user !== record.Creator
|
||||||
|
"
|
||||||
|
danger
|
||||||
|
>{{ t("components.common.shared_folders.delete") }}</a-button
|
||||||
|
>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'cname'">
|
||||||
|
<a
|
||||||
|
@click="
|
||||||
|
page.lastBaseDir = [...page.lastBaseDir, page.currBaseDir];
|
||||||
|
page.currBaseDir = {
|
||||||
|
Name: record.Name,
|
||||||
|
CName: record.CName,
|
||||||
|
};
|
||||||
|
get();
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ record.CName }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'creator'">
|
||||||
|
<a-space>
|
||||||
|
<template v-if="record.CreatorStatus === 'archived'">
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.in_archive`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<LockOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-if="record.CreatorStatus === 'deleted'">
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.deleted`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
{{ record.Creator }}
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.add.show"
|
||||||
|
:title="t('components.common.shared_folders.add_folder')"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
style="margin-top: 8px"
|
||||||
|
:placeholder="$t('components.common.shared_folders.add_name')"
|
||||||
|
v-model:value="page.add.Name"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<template #footer>
|
||||||
|
<a-button type="primary" :disabled="!page.add.Name" @click="addFolder">
|
||||||
|
{{ t("components.common.shared_folders.add_folder") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
v-model:open="page.access.show"
|
||||||
|
style="width: 50%; min-width: 700px"
|
||||||
|
@cancel="get"
|
||||||
|
>
|
||||||
|
<a-table
|
||||||
|
:columns="accessColumns"
|
||||||
|
:data-source="page.access.data"
|
||||||
|
:loading="page.access.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-radio-group
|
||||||
|
@change="getAccess()"
|
||||||
|
v-model:value="page.access.type"
|
||||||
|
>
|
||||||
|
<a-radio-button value="email">{{
|
||||||
|
t("components.common.shared_folders.access_type_email")
|
||||||
|
}}</a-radio-button>
|
||||||
|
<a-radio-button value="group">{{
|
||||||
|
t("components.common.shared_folders.access_type_group")
|
||||||
|
}}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.access.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.common.shared_folders.access_search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="getAccess"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col
|
||||||
|
style="display: flex; align-items: flex-end; justify-content: end"
|
||||||
|
:span="6"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
v-if="page.access.type === 'email'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.access.showAdd = true"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t("components.common.shared_folders.access_type_email_add")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="page.access.type === 'group'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.access.showAdd = true"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t("components.common.shared_folders.access_type_group_add")
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.access.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.common.shared_folders.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.common.shared_folders.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_folders.cancel')"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_folders.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="t('components.common.shared_folders.delete_all') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_folders.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_folders.cancel')"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.shared_folders.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.common.shared_folders.delete') + '?'"
|
||||||
|
:ok-text="t('components.common.shared_folders.ok')"
|
||||||
|
:cancel-text="t('components.common.shared_folders.cancel')"
|
||||||
|
@confirm="deleteAccess(record.Name)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.common.shared_folders.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.access.pagination.current"
|
||||||
|
:defaultPageSize="page.access.pagination.size"
|
||||||
|
:total="page.access.pagination.total"
|
||||||
|
@change="accessPageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.access.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<template #footer> </template>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.access.showAdd"
|
||||||
|
:title="
|
||||||
|
page.access.type === 'email'
|
||||||
|
? t('components.common.shared_folders.access_type_email_add')
|
||||||
|
: t('components.common.shared_folders.access_type_group_add')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template v-if="page.access.type === 'email'">
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.access.inputValue"
|
||||||
|
:placeholder="
|
||||||
|
t('components.common.shared_folders.access_type_email_placeholder')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.access.type === 'group'">
|
||||||
|
<a-select
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="page.access.groups.length === 0"
|
||||||
|
v-model:value="page.access.selectValue"
|
||||||
|
:options="page.access.groups"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
(page.access.type === 'email' && page.access.inputValue === '') ||
|
||||||
|
(page.access.type === 'group' && page.access.selectValue === '')
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
addAccess(
|
||||||
|
page.access.type === 'email'
|
||||||
|
? page.access.inputValue
|
||||||
|
: page.access.selectValue
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ t("components.common.shared_folders.access_add") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_folders.col_folder"),
|
||||||
|
dataIndex: "CName",
|
||||||
|
key: "cname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_folders.col_owner"),
|
||||||
|
dataIndex: "Creator",
|
||||||
|
key: "creator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const accessColumns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.shared_folders.access_col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
domain?: string;
|
||||||
|
user: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
interface BaseDir {
|
||||||
|
Name: string;
|
||||||
|
CName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootBaseDir: BaseDir = {
|
||||||
|
Name: "",
|
||||||
|
CName: t("components.common.shared_folders.root_folder"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
all_groups: string[];
|
||||||
|
currBaseDir: BaseDir;
|
||||||
|
lastBaseDir: BaseDir[];
|
||||||
|
withSearch: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
add: {
|
||||||
|
show: boolean;
|
||||||
|
Name: string;
|
||||||
|
};
|
||||||
|
access: {
|
||||||
|
inputValue: string;
|
||||||
|
selectValue: string;
|
||||||
|
type: string;
|
||||||
|
data: any[];
|
||||||
|
loading: boolean;
|
||||||
|
show: boolean;
|
||||||
|
Name: string;
|
||||||
|
groups: any[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAdd: boolean;
|
||||||
|
withSearch: boolean;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
currBaseDir: rootBaseDir,
|
||||||
|
lastBaseDir: [],
|
||||||
|
withSearch: false,
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
add: {
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
all_groups: [],
|
||||||
|
access: {
|
||||||
|
withSearch: false,
|
||||||
|
inputValue: "",
|
||||||
|
selectValue: "",
|
||||||
|
type: "email",
|
||||||
|
data: [],
|
||||||
|
loading: false,
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
groups: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
showAdd: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
getGroups();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders/groups`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders/groups`;
|
||||||
|
}
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.all_groups = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("base_dir", page.currBaseDir.Name);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString());
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
function accessPageChange(current: number) {
|
||||||
|
page.access.pagination.current = current;
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFolder() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("base_dir", page.currBaseDir.Name);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Name: page.add.Name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.add.show = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
async function showAccessModal(name: string) {
|
||||||
|
page.access.Name = name;
|
||||||
|
page.access.show = true;
|
||||||
|
page.access.type = "email";
|
||||||
|
page.access.inputValue = "";
|
||||||
|
getAccess();
|
||||||
|
}
|
||||||
|
async function showAddModal() {
|
||||||
|
page.add.show = true;
|
||||||
|
page.add.Name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteFolder(name: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
params.append("base_dir", page.currBaseDir.Name);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAccess() {
|
||||||
|
page.access.withSearch = page.access.pagination.search !== "";
|
||||||
|
page.access.loading = true;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("name", page.access.Name);
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("page", String(page.access.pagination.current));
|
||||||
|
params.append("size", String(page.access.pagination.size));
|
||||||
|
params.append("search", page.access.pagination.search);
|
||||||
|
params.append("base_dir", page.currBaseDir.Name);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString());
|
||||||
|
|
||||||
|
page.access.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.data = [];
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
for (let index = 0; index < res.data.length; index++) {
|
||||||
|
const element = res.data[index];
|
||||||
|
page.access.data.push({ Name: element });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.pagination.total = res.total;
|
||||||
|
|
||||||
|
page.access.groups = [];
|
||||||
|
page.access.selectValue = "";
|
||||||
|
|
||||||
|
if (page.access.type === "group") {
|
||||||
|
for (let j = 0; j < page.all_groups.length; j++) {
|
||||||
|
const group = page.all_groups[j];
|
||||||
|
let skip = false;
|
||||||
|
for (let i = 0; i < page.access.data.length; i++) {
|
||||||
|
const alreadyHas = page.access.data[i];
|
||||||
|
if (alreadyHas.Name === group) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
page.access.groups.push({ value: group });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.access.groups.length) {
|
||||||
|
page.access.selectValue = page.access.groups[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("name", page.access.Name);
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("page", String(page.access.pagination.current));
|
||||||
|
params.append("size", String(page.access.pagination.size));
|
||||||
|
params.append("search", page.access.pagination.search);
|
||||||
|
params.append("base_dir", page.currBaseDir.Name);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("name", page.access.Name);
|
||||||
|
params.append("type", page.access.type);
|
||||||
|
params.append("base_dir", page.currBaseDir.Name);
|
||||||
|
|
||||||
|
let url = "";
|
||||||
|
if (props.domain) {
|
||||||
|
url = `/admin/domains/${props.domain}/shared_folders/access?`;
|
||||||
|
} else {
|
||||||
|
url = `/user/shared_folders/access?`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await apiFetch(url + params.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Access: access,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.access.showAdd = false;
|
||||||
|
|
||||||
|
return getAccess();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<a-select v-model="model">
|
||||||
|
<a-select-option v-for="tz in timezones" :value="tz"
|
||||||
|
>UTC{{ tz }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const model = defineModel();
|
||||||
|
|
||||||
|
const timezones = [
|
||||||
|
"-12",
|
||||||
|
"-11",
|
||||||
|
"-10",
|
||||||
|
"-09",
|
||||||
|
"-08",
|
||||||
|
"-07",
|
||||||
|
"-06",
|
||||||
|
"-05",
|
||||||
|
"-04",
|
||||||
|
"-03",
|
||||||
|
"-02",
|
||||||
|
"-01",
|
||||||
|
"+00",
|
||||||
|
"+01",
|
||||||
|
"+02",
|
||||||
|
"+03",
|
||||||
|
"+04",
|
||||||
|
"+05",
|
||||||
|
"+06",
|
||||||
|
"+07",
|
||||||
|
"+08",
|
||||||
|
"+09",
|
||||||
|
"+10",
|
||||||
|
"+11",
|
||||||
|
"+12",
|
||||||
|
"+13",
|
||||||
|
"+14",
|
||||||
|
];
|
||||||
|
</script>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<a-select
|
||||||
|
v-model="model"
|
||||||
|
mode="multiple"
|
||||||
|
style="width: 100%"
|
||||||
|
max-tag-count="responsive"
|
||||||
|
>
|
||||||
|
<a-select-option value="1">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.monday")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="2">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.tuesday")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="3">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.wednesday")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="4">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.thursday")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="5">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.friday")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="6">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.saturday")
|
||||||
|
}}</a-select-option>
|
||||||
|
<a-select-option value="0">{{
|
||||||
|
t("components.user.calendars.share_free_time.work_days_names.sunday")
|
||||||
|
}}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const model = defineModel();
|
||||||
|
</script>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<a-time-range-picker v-model="model" format="HH:mm" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
const model = defineModel();
|
||||||
|
</script>
|
|
@ -0,0 +1,118 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Card size="small" style="min-width: 100px; width: min-content">
|
||||||
|
<template #title>
|
||||||
|
{{ timeToDate(props.day) }}
|
||||||
|
<br />
|
||||||
|
{{ timeToDayName(new Date(props.day)) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-card-grid
|
||||||
|
v-for="interval in page.intervals"
|
||||||
|
style="width: 100%; text-align: center; padding: 1px"
|
||||||
|
>
|
||||||
|
<Interval
|
||||||
|
:interval="interval"
|
||||||
|
:checked="page.checked.get(interval)"
|
||||||
|
:add="add"
|
||||||
|
:remove="remove"
|
||||||
|
></Interval>
|
||||||
|
</a-card-grid>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { timeToDate } from "@/composables/misc";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import Interval from "@/components/common/approval/Interval.vue";
|
||||||
|
import Card from "ant-design-vue/es/card/Card";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
day: string;
|
||||||
|
add: Function;
|
||||||
|
remove: Function;
|
||||||
|
checked?: string[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
intervals: string[];
|
||||||
|
checked: Map<string, boolean>;
|
||||||
|
}>({
|
||||||
|
intervals: [],
|
||||||
|
checked: new Map<string, boolean>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.checked) {
|
||||||
|
props.checked.forEach((element) => {
|
||||||
|
page.checked.set(element, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
page.intervals = formIntervals();
|
||||||
|
});
|
||||||
|
|
||||||
|
function formIntervals(): string[] {
|
||||||
|
let res: string[] = [];
|
||||||
|
|
||||||
|
for (let hour = 6; hour < 22; hour++) {
|
||||||
|
let hourStr = String(hour);
|
||||||
|
if (hour < 10) {
|
||||||
|
hourStr = "0" + hourStr;
|
||||||
|
}
|
||||||
|
res.push(hourStr + ":00" + " - " + hourStr + ":30");
|
||||||
|
|
||||||
|
let nextHour = hour + 1;
|
||||||
|
let nextHourStr = String(nextHour);
|
||||||
|
if (nextHour < 10) {
|
||||||
|
nextHourStr = "0" + nextHourStr;
|
||||||
|
}
|
||||||
|
res.push(hourStr + ":30" + " - " + nextHourStr + ":00");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add(interval: string) {
|
||||||
|
return props.add(props.day + "|" + interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(interval: string) {
|
||||||
|
return props.remove(props.day + "|" + interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeToDayName(date: Date): string {
|
||||||
|
switch (date.getDay()) {
|
||||||
|
case 1:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.monday"
|
||||||
|
);
|
||||||
|
case 2:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.tuesday"
|
||||||
|
);
|
||||||
|
case 3:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.wednesday"
|
||||||
|
);
|
||||||
|
case 4:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.thursday"
|
||||||
|
);
|
||||||
|
case 5:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.friday"
|
||||||
|
);
|
||||||
|
case 6:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.saturday"
|
||||||
|
);
|
||||||
|
case 0:
|
||||||
|
return t(
|
||||||
|
"components.user.calendars.share_free_time.work_days_names.sunday"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="page.checked ? 'checked' : 'unchecked'"
|
||||||
|
@pointerdown="onHover"
|
||||||
|
@pointerenter="onHover"
|
||||||
|
@click="click"
|
||||||
|
>
|
||||||
|
{{ props.interval }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
interval: string;
|
||||||
|
add: Function;
|
||||||
|
remove: Function;
|
||||||
|
checked?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
checked: boolean;
|
||||||
|
}>({
|
||||||
|
checked: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.checked) {
|
||||||
|
change();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function onHover(payload: PointerEvent) {
|
||||||
|
if (payload.buttons && payload.pointerType !== "touch") {
|
||||||
|
change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function click(payload: MouseEvent) {
|
||||||
|
const pointerPayload = payload as PointerEvent;
|
||||||
|
if (pointerPayload.pointerType === "mouse") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
change();
|
||||||
|
}
|
||||||
|
|
||||||
|
function change() {
|
||||||
|
page.checked = !page.checked;
|
||||||
|
if (page.checked) {
|
||||||
|
props.add(props.interval);
|
||||||
|
} else {
|
||||||
|
props.remove(props.interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.checked {
|
||||||
|
background-color: #bae7ff;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.unchecked {
|
||||||
|
background-color: white;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<a-flex gap="middle">
|
||||||
|
<a-select :value="page.currentAct.Label" @change="changeAct">
|
||||||
|
<a-select-option v-for="(act, i) in actions" :value="act.Label">
|
||||||
|
{{ act.Label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<slot
|
||||||
|
v-if="
|
||||||
|
page.currentAct.Args.length > 1 &&
|
||||||
|
!page.currentAct.NoVal &&
|
||||||
|
page.renderValue
|
||||||
|
"
|
||||||
|
name="action-value"
|
||||||
|
:change="(v: string) => {page.currentAct.Args[1] = v; update()}"
|
||||||
|
:type="page.currentAct.Value"
|
||||||
|
:value="page.currentAct.Args[1]"
|
||||||
|
>
|
||||||
|
</slot>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
actions: Action[];
|
||||||
|
value: string[];
|
||||||
|
update: Function;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
renderValue: boolean;
|
||||||
|
currentAct: Action;
|
||||||
|
}>({
|
||||||
|
renderValue: false,
|
||||||
|
currentAct: {
|
||||||
|
...(props.actions.find(
|
||||||
|
(c: Action) => c.Value === props.value[0]
|
||||||
|
) as Action),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.currentAct.Args = [...props.value];
|
||||||
|
page.renderValue = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function changeAct(newLabel: string) {
|
||||||
|
page.currentAct = {
|
||||||
|
...(props.actions.find((c: Action) => c.Label === newLabel) as Action),
|
||||||
|
};
|
||||||
|
page.currentAct.Args = [...page.currentAct.Args];
|
||||||
|
update();
|
||||||
|
page.renderValue = false;
|
||||||
|
nextTick(() => {
|
||||||
|
page.renderValue = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
return props.update(page.currentAct.Args);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export interface Action {
|
||||||
|
Value: string;
|
||||||
|
Label: string;
|
||||||
|
NextAllowedActions: string[];
|
||||||
|
Args: string[];
|
||||||
|
NoVal: boolean;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<a-flex gap="middle">
|
||||||
|
<a-select :value="page.currentCond.Label" @change="changeCond">
|
||||||
|
<a-select-option v-for="(cond, i) in conditions" :value="cond.Label">
|
||||||
|
{{ cond.Label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select
|
||||||
|
v-if="page.currentCond.OpIdx !== -1"
|
||||||
|
v-model:value="page.currentCond.Args[page.currentCond.OpIdx]"
|
||||||
|
@change="update()"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="(op, i) in page.currentCond.OpList"
|
||||||
|
:value="op.Val"
|
||||||
|
>
|
||||||
|
{{ op.Cname }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<slot
|
||||||
|
v-if="page.currentCond.ValIdx !== -1 && page.renderValue"
|
||||||
|
name="condition-value"
|
||||||
|
:change="(v: string) => {page.currentCond.Args[page.currentCond.ValIdx] = String(v); update()}"
|
||||||
|
:type="page.currentCond.Value"
|
||||||
|
:value="page.currentCond.Args[page.currentCond.ValIdx]"
|
||||||
|
>
|
||||||
|
</slot>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
conditions: Condition[];
|
||||||
|
value: string[];
|
||||||
|
update: Function;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
renderValue: boolean;
|
||||||
|
currentCond: Condition;
|
||||||
|
}>({
|
||||||
|
renderValue: false,
|
||||||
|
currentCond: {
|
||||||
|
...(props.conditions.find(
|
||||||
|
(c: Condition) => c.Args[c.KeyIdx] === props.value[c.KeyIdx]
|
||||||
|
) as Condition),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.currentCond.Args = [...props.value];
|
||||||
|
page.renderValue = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function changeCond(newLabel: string) {
|
||||||
|
page.currentCond = {
|
||||||
|
...(props.conditions.find(
|
||||||
|
(c: Condition) => c.Label === newLabel
|
||||||
|
) as Condition),
|
||||||
|
};
|
||||||
|
page.currentCond.Args = [...page.currentCond.Args];
|
||||||
|
update();
|
||||||
|
page.renderValue = false;
|
||||||
|
nextTick(() => {
|
||||||
|
page.renderValue = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
return props.update(page.currentCond.Args);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export interface OpList {
|
||||||
|
Val: string;
|
||||||
|
Cname: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Condition {
|
||||||
|
Value: string;
|
||||||
|
Label: string;
|
||||||
|
KeyIdx: number;
|
||||||
|
OpIdx: number;
|
||||||
|
ValIdx: number;
|
||||||
|
OpList: OpList[];
|
||||||
|
Args: string[];
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,588 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel-content">
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="showRule(newRule())">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{ t("components.common.rules.add_rule") }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'enabled'">
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="record.Enabled"
|
||||||
|
@change="enable(record.Idx, record.Enabled)"
|
||||||
|
></a-switch>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<a @click="showRule(record)">{{ record.Name }}</a>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
@click="move(record.Idx, 'up')"
|
||||||
|
:disabled="record.Idx === 0"
|
||||||
|
>
|
||||||
|
<UpOutlined></UpOutlined>
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
@click="move(record.Idx, 'down')"
|
||||||
|
:disabled="record.Idx === page.data.length - 1"
|
||||||
|
>
|
||||||
|
<DownOutlined></DownOutlined>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 90%"
|
||||||
|
v-model:open="page.edit.show"
|
||||||
|
:title="
|
||||||
|
page.edit.rule.Idx === -1
|
||||||
|
? t('components.common.rules.add_rule')
|
||||||
|
: t('components.common.rules.edit_rule')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||||
|
<a-form-item :label="t('components.common.rules.edit_name')">
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-input style="width: 200px" v-model:value="page.edit.rule.Name">
|
||||||
|
</a-input>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px"> </a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
|
||||||
|
<a-form-item :label="t('components.common.rules.edit_conditions')">
|
||||||
|
<a-flex gap="middle" vertical>
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-radio-group v-model:value="page.edit.rule.Condition.Op">
|
||||||
|
<a-radio-button value="and">{{
|
||||||
|
t("components.common.rules.edit_conditions_all")
|
||||||
|
}}</a-radio-button>
|
||||||
|
<a-radio-button value="or">{{
|
||||||
|
t("components.common.rules.edit_conditions_any")
|
||||||
|
}}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px"> </a-col>
|
||||||
|
</a-row>
|
||||||
|
<template v-if="page.renderConditions && page.edit.show">
|
||||||
|
<div v-for="(condition, i) in page.edit.rule.Condition.Conditions">
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<Conditions
|
||||||
|
:value="condition"
|
||||||
|
:update="(newVal: string[]) => updateCondition(i, newVal)"
|
||||||
|
:conditions="props.conditions"
|
||||||
|
>
|
||||||
|
<template #condition-value="valueProps">
|
||||||
|
<slot
|
||||||
|
name="condition-value"
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</Conditions>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px">
|
||||||
|
<a-button
|
||||||
|
:disabled="i === 0"
|
||||||
|
danger
|
||||||
|
@click="deleteCondition(i)"
|
||||||
|
>
|
||||||
|
{{ t("components.common.rules.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-button
|
||||||
|
style="width: fit-content; margin: auto"
|
||||||
|
@click="addCondition"
|
||||||
|
>
|
||||||
|
<PlusOutlined> </PlusOutlined>
|
||||||
|
{{ t("components.common.rules.add_condition") }}
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px"> </a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-flex>
|
||||||
|
</a-form-item>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
|
||||||
|
<a-form-item :label="t('components.common.rules.edit_actions')">
|
||||||
|
<a-flex gap="middle" vertical>
|
||||||
|
<template v-if="page.renderActions && page.edit.show">
|
||||||
|
<div v-for="(action, i) in page.edit.rule.Action.Actions">
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<Actions
|
||||||
|
:value="action"
|
||||||
|
:update="(newVal: string[]) => updateAction(i,newVal)"
|
||||||
|
:actions="page.allowedActions[i]"
|
||||||
|
>
|
||||||
|
<template #action-value="valueProps">
|
||||||
|
<slot
|
||||||
|
name="action-value"
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</Actions>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px">
|
||||||
|
<a-button :disabled="i === 0" danger @click="deleteAction(i)">
|
||||||
|
{{ t("components.common.rules.delete") }}
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-button
|
||||||
|
:disabled="page.newActionOptions.length === 0"
|
||||||
|
style="width: fit-content; margin: auto"
|
||||||
|
@click="addAction"
|
||||||
|
>
|
||||||
|
<PlusOutlined> </PlusOutlined>
|
||||||
|
{{ t("components.common.rules.add_action") }}
|
||||||
|
</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col flex="100px"> </a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-flex>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<template #footer>
|
||||||
|
<a-space>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.edit.rule.Idx !== -1"
|
||||||
|
:title="t('components.common.rules.delete_rule') + '?'"
|
||||||
|
:ok-text="t('components.common.rules.ok')"
|
||||||
|
:cancel-text="t('components.common.rules.cancel')"
|
||||||
|
@confirm="deleteRule(page.edit.rule.Idx)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.common.rules.delete_rule")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.edit.rule.Name || !page.edit.valid"
|
||||||
|
@click="saveRule(page.edit.rule)"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{{ t("components.common.rules.save_rule") }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
UpOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import type { Condition } from "./Conditions.vue";
|
||||||
|
import Conditions from "./Conditions.vue";
|
||||||
|
import type { Action } from "./Actions.vue";
|
||||||
|
import { nextTick } from "vue";
|
||||||
|
import Actions from "./Actions.vue";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.common.rules.col_enabled"),
|
||||||
|
key: "enabled",
|
||||||
|
align: "center",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.common.rules.col_name"),
|
||||||
|
key: "name",
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.common.rules.col_action"),
|
||||||
|
key: "action",
|
||||||
|
align: "center",
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const labelCol = { span: 3, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 21, style: { "text-align": "center" } };
|
||||||
|
|
||||||
|
interface ConditionStruct {
|
||||||
|
Op: string;
|
||||||
|
Conditions: string[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionStruct {
|
||||||
|
Actions: string[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Rule {
|
||||||
|
Idx: number;
|
||||||
|
Name: string;
|
||||||
|
Condition: ConditionStruct;
|
||||||
|
Action: ActionStruct;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newRule(): Rule {
|
||||||
|
return {
|
||||||
|
Idx: -1,
|
||||||
|
Name: "",
|
||||||
|
Condition: {
|
||||||
|
Op: "and",
|
||||||
|
Conditions: [[...props.conditions[0].Args]],
|
||||||
|
},
|
||||||
|
Action: {
|
||||||
|
Actions: [[...props.actions[0].Args]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function copyRule(old: Rule): Rule {
|
||||||
|
return {
|
||||||
|
Idx: old.Idx,
|
||||||
|
Name: old.Name,
|
||||||
|
Condition: {
|
||||||
|
Op: old.Condition.Op,
|
||||||
|
Conditions: [...old.Condition.Conditions],
|
||||||
|
},
|
||||||
|
Action: {
|
||||||
|
Actions: [...old.Action.Actions],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
getGetUrl: Function;
|
||||||
|
getMoveUrl: Function;
|
||||||
|
getEnableUrl: Function;
|
||||||
|
getDeleteUrl: Function;
|
||||||
|
getSaveUrl: Function;
|
||||||
|
conditions: Condition[];
|
||||||
|
actions: Action[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
edit: {
|
||||||
|
show: boolean;
|
||||||
|
rule: Rule;
|
||||||
|
valid: boolean;
|
||||||
|
};
|
||||||
|
renderConditions: boolean;
|
||||||
|
renderActions: boolean;
|
||||||
|
allowedActions: Action[][];
|
||||||
|
newActionOptions: Action[];
|
||||||
|
}>({
|
||||||
|
loading: false,
|
||||||
|
data: undefined,
|
||||||
|
edit: {
|
||||||
|
show: false,
|
||||||
|
rule: newRule(),
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
renderConditions: true,
|
||||||
|
renderActions: true,
|
||||||
|
allowedActions: [],
|
||||||
|
newActionOptions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
function computeAllowedActions() {
|
||||||
|
page.allowedActions = [];
|
||||||
|
page.newActionOptions = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < page.edit.rule.Action.Actions.length + 1; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
page.allowedActions.push(props.actions);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let action = props.actions.find(
|
||||||
|
(a: Action) => a.Value === page.edit.rule.Action.Actions[i - 1][0]
|
||||||
|
) as Action;
|
||||||
|
|
||||||
|
let newAllowedActionsValue = [...action.NextAllowedActions];
|
||||||
|
|
||||||
|
for (let j = 0; j < page.allowedActions.length; j++) {
|
||||||
|
const prevAllowedActions = page.allowedActions[j];
|
||||||
|
newAllowedActionsValue = newAllowedActionsValue.filter((act: string) =>
|
||||||
|
prevAllowedActions.map((a: Action) => a.Value).includes(act)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.allowedActions.push(
|
||||||
|
props.actions.filter((a: Action) =>
|
||||||
|
newAllowedActionsValue.includes(a.Value)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.newActionOptions = page.allowedActions.pop() as Action[];
|
||||||
|
|
||||||
|
for (let i = page.edit.rule.Action.Actions.length - 1; i >= 0; i--) {
|
||||||
|
if (
|
||||||
|
!page.allowedActions[i]
|
||||||
|
.map((a: Action) => a.Value)
|
||||||
|
.includes(page.edit.rule.Action.Actions[i][0])
|
||||||
|
) {
|
||||||
|
page.edit.rule.Action.Actions.splice(i, 1);
|
||||||
|
page.allowedActions.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAction(i: number, newVal: string[]) {
|
||||||
|
page.edit.rule.Action.Actions[i] = newVal;
|
||||||
|
computeAllowedActions();
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCondition(i: number, newVal: string[]) {
|
||||||
|
page.edit.rule.Condition.Conditions[i] = newVal;
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
let ok = true;
|
||||||
|
|
||||||
|
page.edit.rule.Action.Actions.forEach((args) => {
|
||||||
|
let action = props.actions.find(
|
||||||
|
(a: Action) => a.Value === args[0]
|
||||||
|
) as Action;
|
||||||
|
if (!action.NoVal && args[1] === "") {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.edit.rule.Condition.Conditions.forEach((args) => {
|
||||||
|
let cond = props.conditions.find(
|
||||||
|
(a: Condition) => a.Value === args[0]
|
||||||
|
) as Condition;
|
||||||
|
if (cond.ValIdx != -1 && args[cond.ValIdx] === "") {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.edit.valid = ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCondition() {
|
||||||
|
page.edit.rule.Condition.Conditions.push(props.conditions[0].Args);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCondition(i: number) {
|
||||||
|
page.edit.rule.Condition.Conditions.splice(i, 1);
|
||||||
|
page.renderConditions = false;
|
||||||
|
validate();
|
||||||
|
nextTick(() => {
|
||||||
|
page.renderConditions = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAction() {
|
||||||
|
if (page.newActionOptions.length > 0) {
|
||||||
|
page.edit.rule.Action.Actions.push(page.newActionOptions[0].Args);
|
||||||
|
}
|
||||||
|
computeAllowedActions();
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAction(i: number) {
|
||||||
|
page.edit.rule.Action.Actions.splice(i, 1);
|
||||||
|
computeAllowedActions();
|
||||||
|
validate();
|
||||||
|
page.renderActions = false;
|
||||||
|
nextTick(() => {
|
||||||
|
page.renderActions = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(page.edit.rule.Action.Actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showRule(rule: any) {
|
||||||
|
page.edit.rule = copyRule(rule);
|
||||||
|
page.edit.show = true;
|
||||||
|
computeAllowedActions();
|
||||||
|
// for (let i = 0; i < page.edit.rule.Action.Actions.length; i++) {
|
||||||
|
// const action = page.edit.rule.Action.Actions[i];
|
||||||
|
// page.allowedActions.push(getAllowedActions(i));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// function getAllowedActions(to: number): Action[] {
|
||||||
|
// if (page.edit.rule.Action.Actions.length === 1) {
|
||||||
|
// return props.actions;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let avaliableActions = props.actions.map((a: Action) => a.Value);
|
||||||
|
// for (let i = 0; i < to; i++) {
|
||||||
|
// const element = page.edit.rule.Action.Actions[i];
|
||||||
|
// let action = props.actions.find(
|
||||||
|
// (a: Action) => a.Value === element[0]
|
||||||
|
// ) as Action;
|
||||||
|
// avaliableActions = avaliableActions.filter((act: string) =>
|
||||||
|
// action.NextAllowedActions.includes(act)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let res = props.actions.filter((a: Action) =>
|
||||||
|
// avaliableActions.includes(a.Value)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(res);
|
||||||
|
// return res;
|
||||||
|
|
||||||
|
// return nil;
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
let url = props.getGetUrl();
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
for (let i = 0; i < page.data.length; i++) {
|
||||||
|
const element = page.data[i];
|
||||||
|
element.Enabled = !element.Disabled;
|
||||||
|
element.Idx = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function move(idx: number, direction: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("idx", String(idx));
|
||||||
|
params.append("direction", direction);
|
||||||
|
let url = props.getMoveUrl();
|
||||||
|
const res = await apiFetch(url + "?" + params.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enable(idx: number, enabled: boolean) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("idx", String(idx));
|
||||||
|
params.append("enabled", String(enabled));
|
||||||
|
let url = props.getEnableUrl();
|
||||||
|
const res = await apiFetch(url + "?" + params.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRule(idx: number) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("idx", String(idx));
|
||||||
|
let url = props.getDeleteUrl();
|
||||||
|
const res = await apiFetch(url + "?" + params.toString(), {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.edit.show = false;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveRule(rule: any) {
|
||||||
|
let url = props.getSaveUrl();
|
||||||
|
const res = await apiFetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: rule,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.edit.show = false;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 500px;
|
||||||
|
max-width: 50%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<a-input
|
||||||
|
v-if="
|
||||||
|
type === ActKwTransformFromAddress || type === ActKwTransformToAddress
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
change: Function;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
value: string;
|
||||||
|
}>({
|
||||||
|
value: props.value,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import type { Action } from "../Actions.vue";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
const ActKwTransformFromAddress = `transform_from_address`;
|
||||||
|
const ActKwTransformToAddress = `transform_to_address`;
|
||||||
|
|
||||||
|
export const actionsList: Action[] = [
|
||||||
|
{
|
||||||
|
Value: ActKwTransformFromAddress,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.address.actions.transform_from_address"
|
||||||
|
),
|
||||||
|
NextAllowedActions: [ActKwTransformFromAddress, ActKwTransformToAddress],
|
||||||
|
Args: [ActKwTransformFromAddress, "*@(old_host.ru) => new_host.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwTransformToAddress,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.address.actions.transform_to_address"
|
||||||
|
),
|
||||||
|
NextAllowedActions: [ActKwTransformFromAddress, ActKwTransformToAddress],
|
||||||
|
Args: [ActKwTransformToAddress, "*@(old_host.ru) => new_host.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
|
@ -0,0 +1,192 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<a-input
|
||||||
|
v-if="
|
||||||
|
props.type === CondKwFromSMTP ||
|
||||||
|
props.type === CondKwToSMTP ||
|
||||||
|
props.type === CondKwFromGroup ||
|
||||||
|
props.type === CondKwToGroup
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
const props = defineProps<{
|
||||||
|
change: Function;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
value: string;
|
||||||
|
}>({
|
||||||
|
value: props.value,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import type { Condition } from "../Conditions.vue";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
const CondKwFromSMTP = `from`;
|
||||||
|
const CondKwToSMTP = `to`;
|
||||||
|
const CondKwFromGroup = `from_group`;
|
||||||
|
const CondKwToGroup = `to_group`;
|
||||||
|
const CondKwAll = `all`;
|
||||||
|
|
||||||
|
export const conditionsList: Condition[] = [
|
||||||
|
{
|
||||||
|
Value: CondKwFromSMTP,
|
||||||
|
Label: i18n.global.t("components.common.rules.address.conditions.from"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwFromSMTP, "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwToSMTP,
|
||||||
|
Label: i18n.global.t("components.common.rules.address.conditions.to"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwToSMTP, "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwFromGroup,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.from_group"
|
||||||
|
),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwFromGroup, "==", "group"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwToGroup,
|
||||||
|
Label: i18n.global.t("components.common.rules.address.conditions.to_group"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.address.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwToGroup, "==", "group"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwAll,
|
||||||
|
Label: i18n.global.t("components.common.rules.address.conditions.all"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: -1,
|
||||||
|
ValIdx: -1,
|
||||||
|
OpList: [],
|
||||||
|
Args: [CondKwAll],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
|
@ -0,0 +1,210 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<a-select
|
||||||
|
v-if="
|
||||||
|
(type === ActKwToFolder || type === ActKwCopyToFolder) &&
|
||||||
|
page.folders.length !== 0
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="(newVal: string) => props.change(newVal)"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="foldersOpt in page.folders"
|
||||||
|
:value="foldersOpt[1]"
|
||||||
|
>
|
||||||
|
{{ foldersOpt[0] }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-input
|
||||||
|
v-if="type === ActKwRedirectTo || type === ActKwCopyTo"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<a-textarea
|
||||||
|
v-if="type === ActKwReplyMsg"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
change: Function;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
getFoldersUrl: Function;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
value: string;
|
||||||
|
folders: string[][];
|
||||||
|
}>({
|
||||||
|
value: props.value,
|
||||||
|
folders: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.type === ActKwToFolder || props.type === ActKwCopyToFolder) {
|
||||||
|
getFolders();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getFolders() {
|
||||||
|
let url = props.getFoldersUrl();
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.folders = [];
|
||||||
|
page.folders = res.data;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import type { Action } from "../Actions.vue";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
const ActKwToFolder = `to_folder`;
|
||||||
|
const ActKwCopyToFolder = `copy_to_folder`;
|
||||||
|
const ActKwMarkAsSeen = `mark_as_seen`;
|
||||||
|
const ActKwMarkAsFlagged = `mark_as_flagged`;
|
||||||
|
const ActKwRedirectTo = `redirect_to`;
|
||||||
|
const ActKwCopyTo = `copy_to`;
|
||||||
|
const ActKwReplyMsg = `reply_msg`;
|
||||||
|
const ActKwReject = `reject`;
|
||||||
|
const ActKwStop = `stop`;
|
||||||
|
|
||||||
|
export const actionsList: Action[] = [
|
||||||
|
{
|
||||||
|
Value: ActKwToFolder,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.actions.to_folder"),
|
||||||
|
NextAllowedActions: [
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwReplyMsg,
|
||||||
|
ActKwStop,
|
||||||
|
ActKwCopyToFolder,
|
||||||
|
ActKwMarkAsSeen,
|
||||||
|
ActKwMarkAsFlagged,
|
||||||
|
],
|
||||||
|
Args: [ActKwToFolder, "INBOX"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwCopyToFolder,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.actions.copy_to_folder"
|
||||||
|
),
|
||||||
|
NextAllowedActions: [
|
||||||
|
ActKwToFolder,
|
||||||
|
ActKwCopyToFolder,
|
||||||
|
ActKwMarkAsSeen,
|
||||||
|
ActKwMarkAsFlagged,
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwReplyMsg,
|
||||||
|
ActKwStop,
|
||||||
|
],
|
||||||
|
Args: [ActKwCopyToFolder, "INBOX"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwMarkAsSeen,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.actions.mark_as_seen"
|
||||||
|
),
|
||||||
|
NextAllowedActions: [
|
||||||
|
ActKwToFolder,
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwReplyMsg,
|
||||||
|
ActKwStop,
|
||||||
|
ActKwCopyToFolder,
|
||||||
|
ActKwMarkAsFlagged,
|
||||||
|
],
|
||||||
|
Args: [ActKwMarkAsSeen, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwMarkAsFlagged,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.actions.mark_as_flagged"
|
||||||
|
),
|
||||||
|
NextAllowedActions: [
|
||||||
|
ActKwToFolder,
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwReplyMsg,
|
||||||
|
ActKwStop,
|
||||||
|
ActKwCopyToFolder,
|
||||||
|
ActKwMarkAsSeen,
|
||||||
|
],
|
||||||
|
Args: [ActKwMarkAsFlagged, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwRedirectTo,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.actions.redirect_to"
|
||||||
|
),
|
||||||
|
NextAllowedActions: [ActKwCopyTo, ActKwReplyMsg],
|
||||||
|
Args: [ActKwRedirectTo, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwCopyTo,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.actions.copy_to"),
|
||||||
|
NextAllowedActions: [
|
||||||
|
ActKwRedirectTo,
|
||||||
|
ActKwToFolder,
|
||||||
|
ActKwCopyToFolder,
|
||||||
|
ActKwMarkAsSeen,
|
||||||
|
ActKwMarkAsFlagged,
|
||||||
|
ActKwReplyMsg,
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwStop,
|
||||||
|
],
|
||||||
|
Args: [ActKwCopyTo, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwReplyMsg,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.actions.reply_msg"),
|
||||||
|
NextAllowedActions: [
|
||||||
|
ActKwRedirectTo,
|
||||||
|
ActKwToFolder,
|
||||||
|
ActKwCopyToFolder,
|
||||||
|
ActKwMarkAsSeen,
|
||||||
|
ActKwMarkAsFlagged,
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwReplyMsg,
|
||||||
|
ActKwReject,
|
||||||
|
ActKwStop,
|
||||||
|
],
|
||||||
|
Args: [ActKwReplyMsg, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwReject,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.actions.reject"),
|
||||||
|
NextAllowedActions: [],
|
||||||
|
Args: [ActKwReject, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwStop,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.actions.stop"),
|
||||||
|
NextAllowedActions: [],
|
||||||
|
Args: [ActKwStop, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
|
@ -0,0 +1,503 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<a-input
|
||||||
|
v-if="
|
||||||
|
props.type === CondKwToSMTP ||
|
||||||
|
props.type === CondKwFromSMTP ||
|
||||||
|
props.type === CondKwHeader ||
|
||||||
|
props.type === CondKwBody
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<a-input-number
|
||||||
|
v-if="props.type === CondKwSpamScore"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="!page.value ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
<a-tooltip title="1048576, 1024K or 1M">
|
||||||
|
<a-input
|
||||||
|
v-if="props.type === CondKwSize"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-date-picker
|
||||||
|
v-if="props.type === CondKwDate"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="page.dateValue"
|
||||||
|
@change="props.change(page.dateValue.format('YYYY-MM-DD'))"
|
||||||
|
:allowClear="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
const props = defineProps<{
|
||||||
|
change: Function;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
value: string;
|
||||||
|
dateValue: any;
|
||||||
|
}>({
|
||||||
|
value: props.value,
|
||||||
|
dateValue: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.type === "date") {
|
||||||
|
page.dateValue = dayjs(Date.parse(page.value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import type { Condition, OpList } from "../Conditions.vue";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
const CondKwDate = `date`;
|
||||||
|
const CondKwHeader = `header`;
|
||||||
|
const CondKwSize = `size`;
|
||||||
|
const CondKwSpamScore = `spam_score`;
|
||||||
|
const CondKwBody = `body`;
|
||||||
|
const CondKwAll = `all`;
|
||||||
|
const CondKwFromSMTP = `from`;
|
||||||
|
const CondKwToSMTP = `to`;
|
||||||
|
|
||||||
|
export const conditionsList: Condition[] = [
|
||||||
|
{
|
||||||
|
Value: CondKwDate,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.date"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `>=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `<=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwDate, "=", new Date().toISOString().split("T")[0]],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwToSMTP,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.to"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwToSMTP, "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwFromSMTP,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.from"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwFromSMTP, "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwHeader,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.header_to"
|
||||||
|
),
|
||||||
|
KeyIdx: 1,
|
||||||
|
OpIdx: 2,
|
||||||
|
ValIdx: 3,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwHeader, "To", "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwHeader,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.header_from"
|
||||||
|
),
|
||||||
|
KeyIdx: 1,
|
||||||
|
OpIdx: 2,
|
||||||
|
ValIdx: 3,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwHeader, "From", "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwHeader,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.subject"),
|
||||||
|
KeyIdx: 1,
|
||||||
|
OpIdx: 2,
|
||||||
|
ValIdx: 3,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwHeader, "Subject", "=", "some subject"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwHeader,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.copy_recipient"
|
||||||
|
),
|
||||||
|
KeyIdx: 1,
|
||||||
|
OpIdx: 2,
|
||||||
|
ValIdx: 3,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwHeader, "Cc", "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwSize,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.size"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `<`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_less"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `>`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_greater"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwSize, "=", "100K"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwSpamScore,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.spam_score"
|
||||||
|
),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `<=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_less_or_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `>=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_greater_or_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwSpamScore, "=", "5"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwBody,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.body"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.incoming.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwBody, "=", "hello"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwAll,
|
||||||
|
Label: i18n.global.t("components.common.rules.incoming.conditions.all"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: -1,
|
||||||
|
ValIdx: -1,
|
||||||
|
OpList: [],
|
||||||
|
Args: [CondKwAll],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function filterCondsForMailDeletion(list: Condition[]): Condition[] {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const element = list[i];
|
||||||
|
if (element.Value === CondKwHeader) {
|
||||||
|
element.OpList = element.OpList.filter((c: OpList) => {
|
||||||
|
return c.Val === "=" || c.Val === "!";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list.filter((c: Condition) => {
|
||||||
|
return (
|
||||||
|
c.Value === CondKwDate ||
|
||||||
|
c.Value === CondKwHeader ||
|
||||||
|
c.Value === CondKwSize ||
|
||||||
|
c.Value === CondKwAll ||
|
||||||
|
c.Value === CondKwBody ||
|
||||||
|
c.Value === CondKwSpamScore
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const conditionsListForMailDeletion: Condition[] =
|
||||||
|
filterCondsForMailDeletion(conditionsList);
|
||||||
|
</script>
|
|
@ -0,0 +1,381 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<a-input
|
||||||
|
v-if="
|
||||||
|
type === ActKwTransformFromAddress ||
|
||||||
|
type === ActKwTransformToAddress ||
|
||||||
|
type === ActKwTransformFromAddressAndHeader ||
|
||||||
|
type === ActKwTransformToAddressAndHeader ||
|
||||||
|
type === ActKwCopyTo ||
|
||||||
|
type === ActKwPremoderation ||
|
||||||
|
type === ActKwSendViaSmartHost
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
<a-input-number
|
||||||
|
v-if="
|
||||||
|
type === ActKwRejectExternalCount || type === ActKwRejectInternalCount
|
||||||
|
"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="!page.value ? 'error' : ''"
|
||||||
|
:min="0"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
<a-select
|
||||||
|
v-if="
|
||||||
|
(type === ActKwRejectGroups || type === ActKwAllowGroups) &&
|
||||||
|
page.gEmails.length !== 0
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="(newVal: string) => props.change(newVal)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
<a-select-option v-for="v in page.gEmails" :value="v">
|
||||||
|
{{ v }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select
|
||||||
|
v-if="
|
||||||
|
(type === ActKwRejectInGroup || type === ActKwAllowInGroup) &&
|
||||||
|
page.groups.length !== 0
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="(newVal: string) => props.change(newVal)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
<a-select-option v-for="v in page.groups" :value="v">
|
||||||
|
{{ v }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-textarea
|
||||||
|
v-if="type === ActKwSendResponse"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
<a-textarea
|
||||||
|
v-if="type === ActKwAddTextToStart || type === ActKwAddTextToEnd"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-textarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
change: Function;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
getGroupsUrl: Function;
|
||||||
|
getGroupEmailsUrl: Function;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
value: string;
|
||||||
|
gEmails: [];
|
||||||
|
groups: [];
|
||||||
|
}>({
|
||||||
|
value: props.value,
|
||||||
|
gEmails: [],
|
||||||
|
groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.type === ActKwAllowInGroup || props.type === ActKwRejectInGroup) {
|
||||||
|
getGroups();
|
||||||
|
}
|
||||||
|
if (props.type === ActKwAllowGroups || props.type === ActKwRejectGroups) {
|
||||||
|
getGroupEmails();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
let url = props.getGroupsUrl();
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.groups = [];
|
||||||
|
page.groups = res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGroupEmails() {
|
||||||
|
let url = props.getGroupEmailsUrl();
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.gEmails = [];
|
||||||
|
page.gEmails = res.data;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import type { Action } from "../Actions.vue";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
const ActKwTransformFromAddress = `transform_from_address`;
|
||||||
|
const ActKwTransformToAddress = `transform_to_address`;
|
||||||
|
const ActKwTransformFromAddressAndHeader = `transform_from_address_and_header`;
|
||||||
|
const ActKwTransformToAddressAndHeader = `transform_to_address_and_header`;
|
||||||
|
const ActKwCopyTo = `copy_to`;
|
||||||
|
const ActKwPremoderation = `premoderation`;
|
||||||
|
const ActKwSendViaSmartHost = `send_via_smart-host`;
|
||||||
|
|
||||||
|
const ActKwRejectExternal = `reject_external`;
|
||||||
|
const ActKwRejectExternalCount = `reject_external_count`;
|
||||||
|
const ActKwRejectInternalCount = `reject_internal_count`;
|
||||||
|
const ActKwRejectGroups = `reject_groups`;
|
||||||
|
const ActKwRejectInGroup = `reject_in_group`;
|
||||||
|
const ActRejectAny = `reject_any`;
|
||||||
|
|
||||||
|
const ActKwAllowExternal = `allow_external`;
|
||||||
|
const ActKwAllowGroups = `allow_groups`;
|
||||||
|
const ActKwAllowInGroup = `allow_in_group`;
|
||||||
|
const ActKwAllowAny = `allow_any`;
|
||||||
|
|
||||||
|
const ActKwSendResponse = `send_response`;
|
||||||
|
const ActKwAddTextToStart = `add_text_to_start`;
|
||||||
|
const ActKwAddTextToEnd = `add_text_to_end`;
|
||||||
|
|
||||||
|
const allActions = [
|
||||||
|
ActKwTransformFromAddress,
|
||||||
|
ActKwTransformToAddress,
|
||||||
|
ActKwTransformFromAddressAndHeader,
|
||||||
|
ActKwTransformToAddressAndHeader,
|
||||||
|
ActKwCopyTo,
|
||||||
|
ActKwPremoderation,
|
||||||
|
ActKwSendViaSmartHost,
|
||||||
|
ActKwRejectExternal,
|
||||||
|
ActKwRejectExternalCount,
|
||||||
|
ActKwRejectInternalCount,
|
||||||
|
ActKwRejectGroups,
|
||||||
|
ActKwRejectInGroup,
|
||||||
|
ActRejectAny,
|
||||||
|
ActKwAllowExternal,
|
||||||
|
ActKwAllowGroups,
|
||||||
|
ActKwAllowInGroup,
|
||||||
|
ActKwAllowAny,
|
||||||
|
ActKwSendResponse,
|
||||||
|
];
|
||||||
|
|
||||||
|
const onlyAllowActions = [
|
||||||
|
ActKwAllowExternal,
|
||||||
|
ActKwAllowGroups,
|
||||||
|
ActKwAllowInGroup,
|
||||||
|
ActKwAllowAny,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const actionsList: Action[] = [
|
||||||
|
{
|
||||||
|
Value: ActKwTransformFromAddress,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.transform_from_address"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwTransformFromAddress, "*@(old_host.ru) => new_host.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwTransformToAddress,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.transform_to_address"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwTransformToAddress, "*@(old_host.ru) => new_host.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwTransformFromAddressAndHeader,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.transform_from_address_and_header"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [
|
||||||
|
ActKwTransformFromAddressAndHeader,
|
||||||
|
"*@(old_host.ru) => new_host.ru",
|
||||||
|
],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwTransformToAddressAndHeader,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.transform_to_address_and_header"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwTransformToAddressAndHeader, "*@(old_host.ru) => new_host.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwCopyTo,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.actions.copy_to"),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwCopyTo, "copy_to@example.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwPremoderation,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.premoderation"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwPremoderation, "moderator@example.ru"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwSendViaSmartHost,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.send_via_smart_host"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwSendViaSmartHost, "[user:pass@]host[:port]"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwRejectExternal,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.reject_external"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwRejectExternal, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwRejectExternalCount,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.reject_external_count"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwRejectExternalCount, "10"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwRejectInternalCount,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.reject_internal_count"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwRejectInternalCount, "10"],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwRejectGroups,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.reject_groups"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwRejectGroups, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwRejectInGroup,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.reject_in_group"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActKwRejectInGroup, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActRejectAny,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.actions.reject_any"),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [ActRejectAny, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Value: ActKwAllowExternal,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.allow_external"
|
||||||
|
),
|
||||||
|
NextAllowedActions: onlyAllowActions,
|
||||||
|
Args: [ActKwAllowExternal, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwAllowGroups,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.allow_groups"
|
||||||
|
),
|
||||||
|
NextAllowedActions: onlyAllowActions,
|
||||||
|
Args: [ActKwAllowGroups, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwAllowInGroup,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.allow_in_group"
|
||||||
|
),
|
||||||
|
NextAllowedActions: onlyAllowActions,
|
||||||
|
Args: [ActKwAllowInGroup, ""],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwAllowAny,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.actions.allow_any"),
|
||||||
|
NextAllowedActions: onlyAllowActions,
|
||||||
|
Args: [ActKwAllowAny, ""],
|
||||||
|
NoVal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwSendResponse,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.send_response"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [
|
||||||
|
ActKwSendResponse,
|
||||||
|
i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.send_response_default"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwAddTextToStart,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.add_text_to_start"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [
|
||||||
|
ActKwAddTextToStart,
|
||||||
|
i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.add_text_default"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: ActKwAddTextToEnd,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.add_text_to_end"
|
||||||
|
),
|
||||||
|
NextAllowedActions: allActions,
|
||||||
|
Args: [
|
||||||
|
ActKwAddTextToEnd,
|
||||||
|
i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.actions.add_text_default"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
NoVal: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
|
@ -0,0 +1,300 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<a-select
|
||||||
|
v-if="
|
||||||
|
(type === CondKwFromGroup || type === CondKwToGroup) &&
|
||||||
|
page.groups.length !== 0
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="(newVal: string) => props.change(newVal)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
<a-select-option v-for="g in page.groups" :value="g">
|
||||||
|
{{ g }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-input
|
||||||
|
v-if="
|
||||||
|
props.type === CondKwToSMTP ||
|
||||||
|
props.type === CondKwFromSMTP ||
|
||||||
|
props.type === CondKwHeader ||
|
||||||
|
props.type === CondKwBody
|
||||||
|
"
|
||||||
|
v-model:value="page.value"
|
||||||
|
@change="props.change(page.value)"
|
||||||
|
:status="page.value === '' ? 'error' : ''"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
const props = defineProps<{
|
||||||
|
change: Function;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
getGroupsUrl: Function;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
value: string;
|
||||||
|
groups: string[];
|
||||||
|
}>({
|
||||||
|
value: props.value,
|
||||||
|
groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.type === CondKwFromGroup || props.type === CondKwToGroup) {
|
||||||
|
getGroups();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
let url = props.getGroupsUrl();
|
||||||
|
const res = await apiFetch(url);
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.groups = [];
|
||||||
|
page.groups = res.data;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import type { Condition } from "../Conditions.vue";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
|
||||||
|
const CondKwToSMTP = `to`;
|
||||||
|
const CondKwFromSMTP = `from`;
|
||||||
|
const CondKwFromGroup = `from_group`;
|
||||||
|
const CondKwToGroup = `to_group`;
|
||||||
|
const CondKwHeader = `header`;
|
||||||
|
const CondKwBody = `body`;
|
||||||
|
const CondKwAll = `all`;
|
||||||
|
|
||||||
|
export const conditionsList: Condition[] = [
|
||||||
|
{
|
||||||
|
Value: CondKwToSMTP,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.conditions.to"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwToSMTP, "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwFromSMTP,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.conditions.from"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwFromSMTP, "=", "test@example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwHeader,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.conditions.subject"),
|
||||||
|
KeyIdx: 1,
|
||||||
|
OpIdx: 2,
|
||||||
|
ValIdx: 3,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `^`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_starts"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `$`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_ends"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwHeader, "Subject", "=", "some subject"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwBody,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.conditions.body"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_contains"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwBody, "=", "hello"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwFromGroup,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.from_group"
|
||||||
|
),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwFromGroup, "==", ""],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwToGroup,
|
||||||
|
Label: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.to_group"
|
||||||
|
),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: 1,
|
||||||
|
ValIdx: 2,
|
||||||
|
OpList: [
|
||||||
|
{
|
||||||
|
Val: `==`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Val: `!=`,
|
||||||
|
Cname: i18n.global.t(
|
||||||
|
"components.common.rules.outgoing.conditions.op_not_equal"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Args: [CondKwToGroup, "==", ""],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Value: CondKwAll,
|
||||||
|
Label: i18n.global.t("components.common.rules.outgoing.conditions.all"),
|
||||||
|
KeyIdx: 0,
|
||||||
|
OpIdx: -1,
|
||||||
|
ValIdx: -1,
|
||||||
|
OpList: [],
|
||||||
|
Args: [CondKwAll],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
|
@ -0,0 +1,176 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-input-search
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.user.address_books.with_access.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'perm'">
|
||||||
|
<div v-if="record.Permissions === 'all'">
|
||||||
|
{{ t("components.common.shared_address_books.access_perm_all") }}
|
||||||
|
</div>
|
||||||
|
<div v-if="record.Permissions === 'read'">
|
||||||
|
{{ t("components.common.shared_address_books.access_perm_read") }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'owner'">
|
||||||
|
<a-space>
|
||||||
|
<template v-if="record.OwnerStatus === 'archived'">
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.in_archive`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<LockOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-if="record.OwnerStatus === 'deleted'">
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.deleted`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
{{ record.Owner }}
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.user.address_books.with_access.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.user.address_books.with_access.col_owner"),
|
||||||
|
dataIndex: "Owner",
|
||||||
|
key: "owner",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.user.address_books.with_access.col_permission"),
|
||||||
|
dataIndex: "Permissions",
|
||||||
|
key: "perm",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/user/address_books/with_access?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<AddressBooks v-if="page.loaded" :user="page.user"></AddressBooks>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AddressBooks from "@/components/common/AddressBooks.vue";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const page = reactive<{
|
||||||
|
user: string;
|
||||||
|
loaded: boolean;
|
||||||
|
}>({
|
||||||
|
user: "",
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.user = useAuthStore().username;
|
||||||
|
page.loaded = true;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,176 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-input-search
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.user.calendars.with_access.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'perm'">
|
||||||
|
<div v-if="record.Permissions === 'all'">
|
||||||
|
{{ t("components.common.shared_calendars.access_perm_all") }}
|
||||||
|
</div>
|
||||||
|
<div v-if="record.Permissions === 'read'">
|
||||||
|
{{ t("components.common.shared_calendars.access_perm_read") }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'owner'">
|
||||||
|
<a-space>
|
||||||
|
<template v-if="record.OwnerStatus === 'archived'">
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.in_archive`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<LockOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-if="record.OwnerStatus === 'deleted'">
|
||||||
|
<a-tooltip
|
||||||
|
:title="
|
||||||
|
t(`components.admin.domains.mailstorage.mailboxes.deleted`)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
{{ record.Owner }}
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.user.calendars.with_access.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.user.calendars.with_access.col_owner"),
|
||||||
|
dataIndex: "Owner",
|
||||||
|
key: "owner",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.user.calendars.with_access.col_permission"),
|
||||||
|
dataIndex: "Permissions",
|
||||||
|
key: "perm",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
loading: boolean;
|
||||||
|
data: any;
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/user/calendars/with_access?` + params.toString()
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = res.data;
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,360 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-table
|
||||||
|
class="panel-content"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-input-search
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="
|
||||||
|
t('components.user.calendars.events_planner.my_events.search')
|
||||||
|
"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
<a-range-picker
|
||||||
|
style="width: 230px"
|
||||||
|
@change="get"
|
||||||
|
v-model:value="page.range"
|
||||||
|
>
|
||||||
|
</a-range-picker>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-space style="display: flex; justify-content: end">
|
||||||
|
<a-button type="primary" @click="newEvent">
|
||||||
|
<PlusOutlined />
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"components.user.calendars.events_planner.my_events.new_event"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'start'">
|
||||||
|
<template v-if="record.Start">
|
||||||
|
{{ record.Start }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-space>
|
||||||
|
{{
|
||||||
|
record.ApprovalCreatedAt
|
||||||
|
? t(
|
||||||
|
"components.user.calendars.events_planner.my_events.status_under_approval"
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"components.user.calendars.events_planner.my_events.status_approval_done"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<a-button shape="circle" @click="showApprovalStatus(record.Id)">
|
||||||
|
<EyeOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="editEvent(record.Id)">{{
|
||||||
|
t("common.misc.edit")
|
||||||
|
}}</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('common.misc.delete') + '?'"
|
||||||
|
:ok-text="t('common.misc.ok')"
|
||||||
|
:cancel-text="t('common.misc.cancel')"
|
||||||
|
@confirm="deleteEvent(record.Id)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{ t("common.misc.delete") }}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal v-model:open="page.approvalStatus.show">
|
||||||
|
<template v-if="page.approvalStatus.approved.length !== 0">
|
||||||
|
<a-divider orientation="left">{{
|
||||||
|
$t(
|
||||||
|
"components.user.calendars.events_planner.my_events.approval_with_ft"
|
||||||
|
)
|
||||||
|
}}</a-divider>
|
||||||
|
<a-list size="small" bordered :data-source="page.approvalStatus.approved">
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>{{ item }}</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.approvalStatus.waiting.length !== 0">
|
||||||
|
<a-divider orientation="left">{{
|
||||||
|
$t(
|
||||||
|
"components.user.calendars.events_planner.my_events.approval_without_ft"
|
||||||
|
)
|
||||||
|
}}</a-divider>
|
||||||
|
<a-list size="small" bordered :data-source="page.approvalStatus.waiting">
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>{{ item }}</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer> </template>
|
||||||
|
</a-modal>
|
||||||
|
<ScriptErrorNotify
|
||||||
|
v-model:open="page.scriptErrorNotify.show"
|
||||||
|
:data="page.scriptErrorNotify.data"
|
||||||
|
></ScriptErrorNotify>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import router from "@/router";
|
||||||
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
import Export from "@/components/admin/domains/mailstorage/Export.vue";
|
||||||
|
import {
|
||||||
|
DomainPlaceholder,
|
||||||
|
EventIdPlaceholder,
|
||||||
|
MailboxPlaceholder,
|
||||||
|
RouteAdminDomainsDomainMailStorageMailboxesExport,
|
||||||
|
RouteUserCalendarsEventsPlannerEdit,
|
||||||
|
RouteUserCalendarsEventsPlannerNew,
|
||||||
|
} from "@/router/consts";
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
HddOutlined,
|
||||||
|
HomeOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
QuestionOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
import { createVNode, onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { timeToDateTime } from "@/composables/misc";
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.user.calendars.events_planner.my_events.col_name"),
|
||||||
|
dataIndex: "Subject",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.user.calendars.events_planner.my_events.col_start"),
|
||||||
|
dataIndex: "Start",
|
||||||
|
key: "start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("components.user.calendars.events_planner.my_events.col_finish"),
|
||||||
|
dataIndex: "Finish",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t(
|
||||||
|
"components.user.calendars.events_planner.my_events.col_participants"
|
||||||
|
),
|
||||||
|
dataIndex: "Participants",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: t("components.user.calendars.events_planner.my_events.col_status"),
|
||||||
|
// dataIndex: "Status",
|
||||||
|
// key: "status",
|
||||||
|
// },
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
key: "action",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultFrom = new Date();
|
||||||
|
defaultFrom.setHours(0, 0, 0, 0);
|
||||||
|
const defaultTo = new Date().setDate(defaultFrom.getDate() + 7);
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
loading: boolean;
|
||||||
|
data: any[];
|
||||||
|
range: [any, any];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
approvalStatus: {
|
||||||
|
show: boolean;
|
||||||
|
waiting: string[];
|
||||||
|
approved: string[];
|
||||||
|
};
|
||||||
|
scriptErrorNotify: {
|
||||||
|
show: boolean;
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
range: [dayjs(defaultFrom), dayjs(defaultTo)],
|
||||||
|
approvalStatus: {
|
||||||
|
show: false,
|
||||||
|
waiting: [],
|
||||||
|
approved: [],
|
||||||
|
},
|
||||||
|
scriptErrorNotify: {
|
||||||
|
show: false,
|
||||||
|
data: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.loading = true;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("from", (page.range[0] as Dayjs).toISOString());
|
||||||
|
params.append("to", (page.range[1] as Dayjs).toISOString());
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(`/user/calendars/events?` + params.toString());
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = [];
|
||||||
|
if (res.data) {
|
||||||
|
(res.data as Array<any>).forEach((el: any) => {
|
||||||
|
el.Participants =
|
||||||
|
(el.MandatoryParticipants as Array<any>).length +
|
||||||
|
(el.OptionalParticipants as Array<any>).length;
|
||||||
|
if (el.IncludeCreator) {
|
||||||
|
el.Participants += 1;
|
||||||
|
}
|
||||||
|
if (el.Start) {
|
||||||
|
el.Finish = timeToDateTime(
|
||||||
|
addMinutes(new Date(el.Start), el.DurationMins)
|
||||||
|
);
|
||||||
|
el.Start = timeToDateTime(el.Start);
|
||||||
|
}
|
||||||
|
page.data.push(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMinutes(date: Date, minutes: number): Date {
|
||||||
|
return new Date(date.getTime() + minutes * 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteEvent(id: number) {
|
||||||
|
const res = await apiFetch(`/user/calendars/events/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
page.scriptErrorNotify.data = res.data;
|
||||||
|
page.scriptErrorNotify.show = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editEvent(id: number) {
|
||||||
|
router.push({
|
||||||
|
path: RouteUserCalendarsEventsPlannerEdit.replace(
|
||||||
|
EventIdPlaceholder,
|
||||||
|
id.toString()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newEvent() {
|
||||||
|
router.push({
|
||||||
|
path: RouteUserCalendarsEventsPlannerNew,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showApprovalStatus(eventId: number) {
|
||||||
|
page.approvalStatus.approved = [];
|
||||||
|
page.approvalStatus.waiting = [];
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/user/calendars/events/${eventId}/approval_status`
|
||||||
|
);
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.approvalStatus.approved = res.data.approved;
|
||||||
|
page.approvalStatus.waiting = res.data.waiting;
|
||||||
|
page.approvalStatus.show = true;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<a-modal v-model:open="open" width="800px">
|
||||||
|
<a-descriptions
|
||||||
|
:column="1"
|
||||||
|
:labelStyle="{ fontWeight: '600' }"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-space>
|
||||||
|
<ExclamationCircleTwoTone
|
||||||
|
two-tone-color="orangered"
|
||||||
|
style="font-size: x-large"
|
||||||
|
/>
|
||||||
|
<div style="font-size: large">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
"components.user.calendars.events_planner.script_error_notify.title"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
`components.user.calendars.events_planner.script_error_notify.resource_name`
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>{{ props.data.ResourceName }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
`components.user.calendars.events_planner.script_error_notify.error`
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div style="color: orangered">{{ props.data.Error }}</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
`components.user.calendars.events_planner.script_error_notify.script`
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="props.data.Script" :rows="13"></a-textarea>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="
|
||||||
|
$t(
|
||||||
|
`components.user.calendars.events_planner.script_error_notify.output`
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-textarea v-model:value="props.data.Output" :rows="3"></a-textarea>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
<template #footer></template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import {
|
||||||
|
CloseCircleFilled,
|
||||||
|
ExclamationCircleFilled,
|
||||||
|
ExclamationCircleTwoTone,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { onMounted, onUnmounted, reactive } from "vue";
|
||||||
|
|
||||||
|
interface ScriptErrorMeta {
|
||||||
|
ResourceName: string;
|
||||||
|
Error: string;
|
||||||
|
Script: string;
|
||||||
|
Output: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = defineModel("open");
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
data: ScriptErrorMeta;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
meta?: ScriptErrorMeta;
|
||||||
|
}>({
|
||||||
|
meta: undefined,
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<Calendars v-if="page.loaded" :user="page.user"></Calendars>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Calendars from "@/components/common/Calendars.vue";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const page = reactive<{
|
||||||
|
user: string;
|
||||||
|
loaded: boolean;
|
||||||
|
}>({
|
||||||
|
user: "",
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.user = useAuthStore().username;
|
||||||
|
page.loaded = true;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,255 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<div v-if="page.contentLoaded">
|
||||||
|
<a-form
|
||||||
|
v-if="page.contentLoaded"
|
||||||
|
:label-col="labelCol"
|
||||||
|
:wrapper-col="wrapperCol"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.user.calendars.share_free_time.timezone')"
|
||||||
|
>
|
||||||
|
<TimezoneSelect
|
||||||
|
v-model:value="page.settings.Timezone"
|
||||||
|
@change="updateSettings"
|
||||||
|
>
|
||||||
|
</TimezoneSelect>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.user.calendars.share_free_time.work_days')"
|
||||||
|
>
|
||||||
|
<WorkDaysSelect
|
||||||
|
@change="updateSettings"
|
||||||
|
v-model:value="page.settings.WorkDays"
|
||||||
|
>
|
||||||
|
</WorkDaysSelect>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('components.user.calendars.share_free_time.work_hours')"
|
||||||
|
>
|
||||||
|
<WorkHoursRangePicker
|
||||||
|
@change="updateSettings"
|
||||||
|
v-model:value="page.settings.workTimeValues"
|
||||||
|
>
|
||||||
|
</WorkHoursRangePicker>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
t('components.user.calendars.share_free_time.time_between_events')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
@change="updateSettings"
|
||||||
|
v-model:value="page.settings.timeBetweenEventsMinsValue"
|
||||||
|
>
|
||||||
|
<a-select-option value="0"
|
||||||
|
>0 {{ t("common.suffixes.min") }}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="5"
|
||||||
|
>5 {{ t("common.suffixes.min") }}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="10"
|
||||||
|
>10 {{ t("common.suffixes.min") }}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="15"
|
||||||
|
>15 {{ t("common.suffixes.min") }}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="30"
|
||||||
|
>30 {{ t("common.suffixes.min") }}</a-select-option
|
||||||
|
>
|
||||||
|
<a-select-option value="60"
|
||||||
|
>60 {{ t("common.suffixes.min") }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
t(
|
||||||
|
'components.user.calendars.share_free_time.two_month_restriction'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
@change="updateSettings"
|
||||||
|
v-model:checked="page.settings.TwoMonthRestriction"
|
||||||
|
>
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<a-divider></a-divider>
|
||||||
|
<div>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col flex="auto">
|
||||||
|
<a-input-search :disabled="!page.link" v-model:value="page.link">
|
||||||
|
<template #enterButton>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.link"
|
||||||
|
@click="copyToClipboard(page.link)"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</template> </a-input-search
|
||||||
|
></a-col>
|
||||||
|
<a-col flex="100px">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="getToken"
|
||||||
|
:loading="page.linkLoading"
|
||||||
|
>
|
||||||
|
{{ $t("components.user.calendars.share_free_time.gen_link") }}
|
||||||
|
</a-button></a-col
|
||||||
|
>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-spin
|
||||||
|
v-else
|
||||||
|
size="large"
|
||||||
|
style="display: flex; justify-content: center"
|
||||||
|
></a-spin>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { copyToClipboard } from "@/composables/misc";
|
||||||
|
import {
|
||||||
|
CopyOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
RetweetOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import { RouteSharedFreeTime, TokenPlaceholder } from "@/router/consts";
|
||||||
|
import TimezoneSelect from "../../common/TimezoneSelect.vue";
|
||||||
|
import WorkDaysSelect from "../../common/WorkDaysSelect.vue";
|
||||||
|
import WorkHoursRangePicker from "../../common/WorkHoursRangePicker.vue";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 14, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 10, style: {} };
|
||||||
|
|
||||||
|
interface Settings {
|
||||||
|
Timezone: string;
|
||||||
|
WorkDays: string[];
|
||||||
|
WorkTime: string[];
|
||||||
|
workTimeValues: any[];
|
||||||
|
TimeBetweenEventsMins: number;
|
||||||
|
timeBetweenEventsMinsValue: string;
|
||||||
|
TwoMonthRestriction: boolean;
|
||||||
|
Token: string;
|
||||||
|
Hostname: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
settings: Settings;
|
||||||
|
link: string;
|
||||||
|
linkLoading: boolean;
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
settings: {
|
||||||
|
TwoMonthRestriction: true,
|
||||||
|
Timezone: "+03",
|
||||||
|
WorkTime: [],
|
||||||
|
workTimeValues: [
|
||||||
|
dayjs(Date.parse("1970-01-01 10:00")),
|
||||||
|
dayjs(Date.parse("1970-01-01 19:00")),
|
||||||
|
],
|
||||||
|
WorkDays: ["1", "2", "3", "4", "5"],
|
||||||
|
TimeBetweenEventsMins: 0,
|
||||||
|
timeBetweenEventsMinsValue: "0",
|
||||||
|
Token: "",
|
||||||
|
Hostname: "",
|
||||||
|
},
|
||||||
|
link: "",
|
||||||
|
linkLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getSettings() {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("timezone", String(new Date().getTimezoneOffset()));
|
||||||
|
|
||||||
|
const res = await apiFetch("/user/share_free_time?" + params.toString());
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
page.settings = res.data;
|
||||||
|
page.settings.timeBetweenEventsMinsValue = String(
|
||||||
|
page.settings.TimeBetweenEventsMins
|
||||||
|
);
|
||||||
|
page.settings.workTimeValues = [
|
||||||
|
dayjs(Date.parse("1970-01-01 " + page.settings.WorkTime[0])),
|
||||||
|
dayjs(Date.parse("1970-01-01 " + page.settings.WorkTime[1])),
|
||||||
|
];
|
||||||
|
page.link = generateLink(page.settings.Hostname, page.settings.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSettings() {
|
||||||
|
page.settings.TimeBetweenEventsMins = Number(
|
||||||
|
page.settings.timeBetweenEventsMinsValue
|
||||||
|
);
|
||||||
|
page.settings.WorkTime[0] = page.settings.workTimeValues[0].format("HH:mm");
|
||||||
|
page.settings.WorkTime[1] = page.settings.workTimeValues[1].format("HH:mm");
|
||||||
|
|
||||||
|
const res = await apiFetch("/user/share_free_time", {
|
||||||
|
method: "POST",
|
||||||
|
body: page.settings,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getToken() {
|
||||||
|
page.linkLoading = true;
|
||||||
|
const res = await apiFetch("/user/share_free_time/refresh", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
page.linkLoading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.link = generateLink(res.data.Hostname, res.data.Token);
|
||||||
|
|
||||||
|
notifySuccess(
|
||||||
|
t("components.user.calendars.share_free_time.gen_link_success")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateLink(hostname: string, token: string): string {
|
||||||
|
// let schema = location.href.substring(0, location.href.indexOf(location.host));
|
||||||
|
|
||||||
|
return (
|
||||||
|
// schema +
|
||||||
|
// location.host +
|
||||||
|
hostname + RouteSharedFreeTime.replace(TokenPlaceholder, token)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 700px;
|
||||||
|
max-width: 25%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Rules
|
||||||
|
:get-get-url="getUrl"
|
||||||
|
:get-delete-url="getUrl"
|
||||||
|
:get-save-url="getUrl"
|
||||||
|
:get-move-url="moveUrl"
|
||||||
|
:get-enable-url="enableUrl"
|
||||||
|
:conditions="conditionsList"
|
||||||
|
:actions="actionsList"
|
||||||
|
>
|
||||||
|
<template #condition-value="valueProps">
|
||||||
|
<ConditionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
>
|
||||||
|
</ConditionValue>
|
||||||
|
</template>
|
||||||
|
<template #action-value="valueProps">
|
||||||
|
<ActionValue
|
||||||
|
:change="valueProps.change"
|
||||||
|
:value="valueProps.value"
|
||||||
|
:type="valueProps.type"
|
||||||
|
:get-folders-url="foldersUrl"
|
||||||
|
>
|
||||||
|
</ActionValue>
|
||||||
|
</template>
|
||||||
|
</Rules>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ConditionValue from "@/components/common/rules/incoming/Conditions.vue";
|
||||||
|
import ActionValue from "@/components/common/rules/incoming/Actions.vue";
|
||||||
|
import { conditionsList } from "@/components/common/rules/incoming/Conditions.vue";
|
||||||
|
import { actionsList } from "@/components/common/rules/incoming/Actions.vue";
|
||||||
|
import Rules from "@/components/common/rules/Rules.vue";
|
||||||
|
|
||||||
|
function getUrl(): string {
|
||||||
|
return `/user/incoming_rules`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUrl(): string {
|
||||||
|
return `/user/incoming_rules/move`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function foldersUrl(): string {
|
||||||
|
return `/user/incoming_rules/folders`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableUrl(): string {
|
||||||
|
return `/user/incoming_rules/enable`;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,345 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel-content">
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="page.data"
|
||||||
|
:loading="page.loading"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<a-row>
|
||||||
|
<a-col style="display: flex; align-items: flex-end" :span="18">
|
||||||
|
<a-space>
|
||||||
|
<a-radio-group @change="get()" v-model:value="page.type">
|
||||||
|
<a-radio-button value="email">{{
|
||||||
|
t("components.user.mailbox_shared_access.type_email")
|
||||||
|
}}</a-radio-button>
|
||||||
|
<a-radio-button value="group">{{
|
||||||
|
t("components.user.mailbox_shared_access.type_group")
|
||||||
|
}}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
|
||||||
|
<a-input-search
|
||||||
|
v-model:value="page.pagination.search"
|
||||||
|
:placeholder="t('components.user.mailbox_shared_access.search')"
|
||||||
|
enter-button
|
||||||
|
allow-clear
|
||||||
|
@search="get"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col
|
||||||
|
style="display: flex; align-items: flex-end; justify-content: end"
|
||||||
|
:span="6"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
<a-button
|
||||||
|
v-if="page.type === 'email'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.showAdd = true"
|
||||||
|
>
|
||||||
|
{{ t("components.user.mailbox_shared_access.type_email_add") }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="page.type === 'group'"
|
||||||
|
type="primary"
|
||||||
|
@click="page.showAdd = true"
|
||||||
|
>
|
||||||
|
{{ t("components.user.mailbox_shared_access.type_group_add") }}
|
||||||
|
</a-button>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="page.withSearch"
|
||||||
|
:title="
|
||||||
|
t('components.user.mailbox_shared_access.delete_found') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.user.mailbox_shared_access.ok')"
|
||||||
|
:cancel-text="t('components.user.mailbox_shared_access.cancel')"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.user.mailbox_shared_access.delete_found")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-popconfirm
|
||||||
|
v-else
|
||||||
|
:title="
|
||||||
|
t('components.user.mailbox_shared_access.delete_all') + '?'
|
||||||
|
"
|
||||||
|
:ok-text="t('components.user.mailbox_shared_access.ok')"
|
||||||
|
:cancel-text="t('components.user.mailbox_shared_access.cancel')"
|
||||||
|
@confirm="deleteAccess('')"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
$t("components.user.mailbox_shared_access.delete_all")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-popconfirm
|
||||||
|
:title="t('components.user.mailbox_shared_access.delete') + '?'"
|
||||||
|
:ok-text="t('components.user.mailbox_shared_access.ok')"
|
||||||
|
:cancel-text="t('components.user.mailbox_shared_access.cancel')"
|
||||||
|
@confirm="deleteAccess(record.Name)"
|
||||||
|
>
|
||||||
|
<a-button danger>{{
|
||||||
|
t("components.user.mailbox_shared_access.delete")
|
||||||
|
}}</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-flex justify="flex-end">
|
||||||
|
<a-pagination
|
||||||
|
v-model:value="page.pagination.current"
|
||||||
|
:defaultPageSize="page.pagination.size"
|
||||||
|
:total="page.pagination.total"
|
||||||
|
@change="pageChange"
|
||||||
|
:pageSizeOptions="['10', '20', '50', '100']"
|
||||||
|
v-model:pageSize="page.pagination.size"
|
||||||
|
:showSizeChanger="true"
|
||||||
|
/>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<a-modal
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:open="page.showAdd"
|
||||||
|
:title="
|
||||||
|
page.type === 'email'
|
||||||
|
? t('components.user.mailbox_shared_access.type_email_add')
|
||||||
|
: t('components.user.mailbox_shared_access.type_group_add')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div style="margin-top: 16px">
|
||||||
|
<template v-if="page.type === 'email'">
|
||||||
|
<a-input
|
||||||
|
v-model:value="page.inputValue"
|
||||||
|
:placeholder="
|
||||||
|
t('components.user.mailbox_shared_access.type_email_placeholder')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
<template v-if="page.type === 'group'">
|
||||||
|
<a-select
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="page.groups.length === 0"
|
||||||
|
v-model:value="page.selectValue"
|
||||||
|
:options="page.groups"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<a-button
|
||||||
|
:disabled="
|
||||||
|
(page.type === 'email' && page.inputValue === '') ||
|
||||||
|
(page.type === 'group' && page.selectValue === '')
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="add(page.type === 'email' ? page.inputValue : page.selectValue)"
|
||||||
|
>
|
||||||
|
{{ t("components.user.mailbox_shared_access.add") }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { notifyError } from "@/composables/alert";
|
||||||
|
import type { ColumnType } from "ant-design-vue/es/table";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const columns: ColumnType<any>[] = [
|
||||||
|
{
|
||||||
|
title: t("components.user.mailbox_shared_access.col_name"),
|
||||||
|
dataIndex: "Name",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: "action",
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
inputValue: string;
|
||||||
|
selectValue: string;
|
||||||
|
type: string;
|
||||||
|
data: any[];
|
||||||
|
loading: boolean;
|
||||||
|
show: boolean;
|
||||||
|
Name: string;
|
||||||
|
groups: any[];
|
||||||
|
pagination: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
|
showAdd: boolean;
|
||||||
|
withSearch: boolean;
|
||||||
|
all_groups: string[];
|
||||||
|
}>({
|
||||||
|
inputValue: "",
|
||||||
|
selectValue: "",
|
||||||
|
type: "email",
|
||||||
|
data: [],
|
||||||
|
loading: false,
|
||||||
|
show: false,
|
||||||
|
Name: "",
|
||||||
|
groups: [],
|
||||||
|
pagination: {
|
||||||
|
current: 1,
|
||||||
|
total: 0,
|
||||||
|
size: 50,
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
showAdd: false,
|
||||||
|
withSearch: false,
|
||||||
|
all_groups: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getGroups();
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
const res = await apiFetch(`/user/mailbox_shared_access/groups`);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.all_groups = res.data;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageChange(current: number) {
|
||||||
|
page.pagination.current = current;
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
page.withSearch = page.pagination.search !== "";
|
||||||
|
page.loading = true;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("type", page.type);
|
||||||
|
params.append("page", String(page.pagination.current));
|
||||||
|
params.append("size", String(page.pagination.size));
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/user/mailbox_shared_access?` + params.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
page.loading = false;
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.data = [];
|
||||||
|
|
||||||
|
if (res.data) {
|
||||||
|
for (let index = 0; index < res.data.length; index++) {
|
||||||
|
const element = res.data[index];
|
||||||
|
page.data.push({ Name: element });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page.pagination.total = res.total;
|
||||||
|
|
||||||
|
page.groups = [];
|
||||||
|
page.selectValue = "";
|
||||||
|
|
||||||
|
if (page.type === "group") {
|
||||||
|
for (let j = 0; j < page.all_groups.length; j++) {
|
||||||
|
const group = page.all_groups[j];
|
||||||
|
let skip = false;
|
||||||
|
for (let i = 0; i < page.data.length; i++) {
|
||||||
|
const alreadyHas = page.data[i];
|
||||||
|
if (alreadyHas.Name === group) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
page.groups.push({ value: group });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.groups.length) {
|
||||||
|
page.selectValue = page.groups[0].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAccess(access: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("search", page.pagination.search);
|
||||||
|
|
||||||
|
const res = await apiFetch(
|
||||||
|
`/user/mailbox_shared_access?` + params.toString(),
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
AccessTo: access,
|
||||||
|
Type: page.type,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function add(access: string) {
|
||||||
|
const res = await apiFetch(`/user/mailbox_shared_access`, {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
AccessTo: access,
|
||||||
|
Type: page.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.showAdd = false;
|
||||||
|
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 800px;
|
||||||
|
max-width: 60%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,277 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-row :gutter="[16, 16]" justify="space-around">
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title>
|
||||||
|
{{ t("components.user.profile.mailbox.title") }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.mailbox.user')"
|
||||||
|
>{{ page.mailboxBlock.User }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.mailbox.size')"
|
||||||
|
>{{ page.mailboxBlock.Size }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.mailbox.count')"
|
||||||
|
>{{ page.mailboxBlock.Count }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title>
|
||||||
|
{{ t("components.user.profile.dav.title") }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.dav.books')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.davBlock.BooksUrl }}
|
||||||
|
<a-button
|
||||||
|
v-if="page.davBlock.BooksUrl.startsWith('http')"
|
||||||
|
@click="copyToClipboard(page.davBlock.BooksUrl)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.dav.calendars')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.davBlock.CalsUrl }}
|
||||||
|
<a-button
|
||||||
|
v-if="page.davBlock.CalsUrl.startsWith('http')"
|
||||||
|
@click="copyToClipboard(page.davBlock.CalsUrl)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.dav.login')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.davBlock.Login }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.davBlock.Login)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<br />
|
||||||
|
<a-row :gutter="[16, 16]" justify="space-around">
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title>
|
||||||
|
{{ t("components.user.profile.imap.title") }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.imap.server')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.imapBlock.Server }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.imapBlock.Server)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.imap.port')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.imapBlock.Port }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.imapBlock.Port.toString())"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.imap.encryption')"
|
||||||
|
>{{ page.imapBlock.SSL }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.imap.auth')"
|
||||||
|
>{{
|
||||||
|
t("components.user.profile.imap.auth_text")
|
||||||
|
}}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.imap.auth_login')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.imapBlock.Login }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.imapBlock.Login)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
|
||||||
|
<a-card hoverable style="height: 100%">
|
||||||
|
<template #title>
|
||||||
|
{{ t("components.user.profile.smtp.title") }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-descriptions :column="1" :labelStyle="{ fontWeight: '600' }">
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.smtp.server')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.smtpBlock.Server }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.smtpBlock.Server)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button> </a-space
|
||||||
|
></a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.smtp.port')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.smtpBlock.Port }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.smtpBlock.Port.toString())"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button> </a-space
|
||||||
|
></a-descriptions-item>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.smtp.encryption')"
|
||||||
|
>{{ page.smtpBlock.SSL }}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.smtp.auth')"
|
||||||
|
>{{
|
||||||
|
t("components.user.profile.smtp.auth_text")
|
||||||
|
}}</a-descriptions-item
|
||||||
|
>
|
||||||
|
<a-descriptions-item
|
||||||
|
:label="t('components.user.profile.smtp.auth_login')"
|
||||||
|
>
|
||||||
|
<a-space>
|
||||||
|
{{ page.smtpBlock.Login }}
|
||||||
|
<a-button
|
||||||
|
@click="copyToClipboard(page.smtpBlock.Login)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<CopyOutlined />
|
||||||
|
</a-button> </a-space
|
||||||
|
></a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { copyToClipboard } from "@/composables/misc";
|
||||||
|
import { CopyOutlined } from "@ant-design/icons-vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
mailboxBlock: {
|
||||||
|
Count: number;
|
||||||
|
Size: string;
|
||||||
|
User: string;
|
||||||
|
};
|
||||||
|
davBlock: {
|
||||||
|
BooksUrl: string;
|
||||||
|
CalsUrl: string;
|
||||||
|
Login: string;
|
||||||
|
};
|
||||||
|
imapBlock: {
|
||||||
|
Server: string;
|
||||||
|
Port: number;
|
||||||
|
SSL: string;
|
||||||
|
Login: string;
|
||||||
|
};
|
||||||
|
smtpBlock: {
|
||||||
|
Server: string;
|
||||||
|
Port: number;
|
||||||
|
SSL: string;
|
||||||
|
Login: string;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
mailboxBlock: {
|
||||||
|
Count: 0,
|
||||||
|
Size: "",
|
||||||
|
User: "",
|
||||||
|
},
|
||||||
|
davBlock: {
|
||||||
|
BooksUrl: "",
|
||||||
|
CalsUrl: "",
|
||||||
|
Login: "",
|
||||||
|
},
|
||||||
|
imapBlock: {
|
||||||
|
Server: "",
|
||||||
|
Port: 0,
|
||||||
|
SSL: "",
|
||||||
|
Login: "",
|
||||||
|
},
|
||||||
|
smtpBlock: {
|
||||||
|
Server: "",
|
||||||
|
Port: 0,
|
||||||
|
SSL: "",
|
||||||
|
Login: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/user/profile");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
|
||||||
|
page.mailboxBlock = res.data.MailboxBlock;
|
||||||
|
page.davBlock = res.data.DavBlock;
|
||||||
|
page.smtpBlock = res.data.SmtpBlock;
|
||||||
|
page.imapBlock = res.data.ImapBlock;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,84 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-card class="panel-content">
|
||||||
|
<a-form :label-col="labelCol" :wrapper-col="wrapperCol" :labelWrap="true">
|
||||||
|
<a-form-item :label="t('components.user.recovery.show')">
|
||||||
|
<a-switch v-model:checked="page.showFolder"> </a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-button
|
||||||
|
:disabled="!page.contentLoaded"
|
||||||
|
class="save-button"
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="update"
|
||||||
|
>
|
||||||
|
{{ $t("components.user.recovery.save") }}
|
||||||
|
</a-button>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { notifyError, notifySuccess } from "@/composables/alert";
|
||||||
|
import { apiFetch } from "@/composables/apiFetch";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const labelCol = { span: 16, style: { "text-align": "left" } };
|
||||||
|
const wrapperCol = { span: 8, style: { "text-align": "right" } };
|
||||||
|
|
||||||
|
const page = reactive<{
|
||||||
|
contentLoaded: boolean;
|
||||||
|
showFolder: boolean;
|
||||||
|
}>({
|
||||||
|
contentLoaded: false,
|
||||||
|
showFolder: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
get();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function get() {
|
||||||
|
const res = await apiFetch("/user/recovery_folder");
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.contentLoaded = true;
|
||||||
|
page.showFolder = res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
const res = await apiFetch("/user/recovery_folder", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
Show: page.showFolder,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifySuccess(t("components.user.recovery.success"));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.panel-content {
|
||||||
|
min-width: 500px;
|
||||||
|
max-width: 25%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.save-button {
|
||||||
|
width: 30%;
|
||||||
|
min-width: 200px;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<SharedFolders v-if="page.loaded" :user="page.user"></SharedFolders>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SharedFolders from "@/components/common/SharedFolders.vue";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const page = reactive<{
|
||||||
|
user: string;
|
||||||
|
loaded: boolean;
|
||||||
|
}>({
|
||||||
|
user: "",
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
page.user = useAuthStore().username;
|
||||||
|
page.loaded = true;
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { notification } from "ant-design-vue";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
notification.config({
|
||||||
|
maxCount: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function notifyError(msg: string) {
|
||||||
|
notification["error"]({
|
||||||
|
message: i18n.global.t("common.notify.error"),
|
||||||
|
description: msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notifySuccess(msg: string) {
|
||||||
|
notification["success"]({
|
||||||
|
message: i18n.global.t("common.notify.success"),
|
||||||
|
description: msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notifyWarning(msg: string) {
|
||||||
|
notification["warning"]({
|
||||||
|
message: i18n.global.t("common.notify.warning"),
|
||||||
|
description: msg,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import router from "@/router";
|
||||||
|
import { RouteLogin } from "@/router/consts";
|
||||||
|
import { useReturnToPageStore } from "@/stores/returnToPage";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
export const apiFetch = async (request: any, opts?: any): Promise<Response> => {
|
||||||
|
if (opts && opts.body && !opts.isFormData) {
|
||||||
|
opts.body = JSON.stringify(opts.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = new Headers();
|
||||||
|
//headers.set("Authorization", "Bearer " + useAuthStore().token);
|
||||||
|
|
||||||
|
if (opts && !opts.isFormData) {
|
||||||
|
headers.set("Content-Type", "application/json");
|
||||||
|
}
|
||||||
|
const resp = await fetch(
|
||||||
|
import.meta.env.VITE_API_URL + "/backend" + request,
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
credentials: "include",
|
||||||
|
...opts,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { result, total, error } = await resp.json();
|
||||||
|
|
||||||
|
if (resp.status != 200) {
|
||||||
|
if (resp.status == 401) {
|
||||||
|
useAuthStore().resetAuth();
|
||||||
|
useReturnToPageStore().setPath(
|
||||||
|
router.currentRoute.value.path,
|
||||||
|
useRoute().query
|
||||||
|
);
|
||||||
|
router.push(RouteLogin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: result, total, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
data: any;
|
||||||
|
total: number;
|
||||||
|
error: string;
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
|
||||||
|
export const timeToDateTime = (time: any) => {
|
||||||
|
if (time == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var d = new Date(time);
|
||||||
|
return (
|
||||||
|
("0" + d.getHours()).slice(-2) +
|
||||||
|
":" +
|
||||||
|
("0" + d.getMinutes()).slice(-2) +
|
||||||
|
" " +
|
||||||
|
("0" + d.getDate()).slice(-2) +
|
||||||
|
"/" +
|
||||||
|
("0" + (d.getMonth() + 1)).slice(-2) +
|
||||||
|
"/" +
|
||||||
|
d.getFullYear()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timeToDate = (time: any) => {
|
||||||
|
if (time == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var d = new Date(time);
|
||||||
|
return (
|
||||||
|
("0" + d.getDate()).slice(-2) + "/" + ("0" + (d.getMonth() + 1)).slice(-2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timeToFullDate = (time: any) => {
|
||||||
|
if (time == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var d = new Date(time);
|
||||||
|
return (
|
||||||
|
("0" + d.getDate()).slice(-2) +
|
||||||
|
"." +
|
||||||
|
("0" + (d.getMonth() + 1)).slice(-2) +
|
||||||
|
"." +
|
||||||
|
d.getFullYear()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyToClipboard = (text: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
message.info(i18n.global.t("common.misc.copy"));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const genRandomString = (length: number) => {
|
||||||
|
let result = "";
|
||||||
|
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
let counter = 0;
|
||||||
|
while (counter < length) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTimezoneString = () => {
|
||||||
|
let offset = new Date().getTimezoneOffset();
|
||||||
|
offset = offset / -60;
|
||||||
|
let resStr = "UTC";
|
||||||
|
if (offset < 0) {
|
||||||
|
resStr += "-";
|
||||||
|
offset *= -1;
|
||||||
|
} else {
|
||||||
|
resStr += "+";
|
||||||
|
}
|
||||||
|
if (offset < 10) {
|
||||||
|
resStr += "0";
|
||||||
|
}
|
||||||
|
resStr += offset;
|
||||||
|
resStr += ":00";
|
||||||
|
return resStr;
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
import { createVNode } from "vue";
|
||||||
|
import router from "@/router";
|
||||||
|
import { RouteLogin } from "@/router/consts";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
import { apiFetch } from "./apiFetch";
|
||||||
|
import { notifyError } from "./alert";
|
||||||
|
|
||||||
|
export function saveAndRestart(callback: () => Promise<boolean>) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: i18n.global.t("common.save_and_restart.confirm_title"),
|
||||||
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
|
content: i18n.global.t("common.save_and_restart.confirm_content"),
|
||||||
|
okText: i18n.global.t("common.save_and_restart.ok"),
|
||||||
|
cancelText: i18n.global.t("common.save_and_restart.cancel"),
|
||||||
|
async onOk() {
|
||||||
|
if (await callback()) {
|
||||||
|
const res = await apiFetch("/admin/restart", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
notifyError(res.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push(RouteLogin);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { createI18n } from "vue-i18n";
|
||||||
|
import { translations } from "./translations";
|
||||||
|
|
||||||
|
const defaultLocale = "eng";
|
||||||
|
|
||||||
|
export function getLocale() {
|
||||||
|
const locale = localStorage.getItem("locale");
|
||||||
|
if (!locale) {
|
||||||
|
return defaultLocale;
|
||||||
|
}
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocale(locale: any) {
|
||||||
|
localStorage.setItem("locale", locale);
|
||||||
|
i18n.global.locale.value = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
var locale = localStorage.getItem("locale");
|
||||||
|
if (!locale) {
|
||||||
|
locale = defaultLocale;
|
||||||
|
localStorage.setItem("locale", locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: locale,
|
||||||
|
fallbackLocale: defaultLocale,
|
||||||
|
messages: translations,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,49 @@
|
||||||
import {createApp} from 'vue'
|
import "./assets/main.css";
|
||||||
import App from './App.vue'
|
|
||||||
import './style.css';
|
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
import { createApp, nextTick } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
import piniaPluginPersistedState from "pinia-plugin-persistedstate";
|
||||||
|
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
const pinia = createPinia();
|
||||||
|
pinia.use(piniaPluginPersistedState);
|
||||||
|
|
||||||
|
app.use(pinia);
|
||||||
|
app.use(i18n);
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.use({
|
||||||
|
install(Vue) {
|
||||||
|
Vue.mixin({
|
||||||
|
mounted() {
|
||||||
|
this.disableAutoComplete();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
disableAutoComplete() {
|
||||||
|
let elements = document.querySelectorAll('[autocomplete="off"]');
|
||||||
|
|
||||||
|
if (!elements) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.forEach((element) => {
|
||||||
|
element.setAttribute("readonly", "readonly");
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.removeAttribute("readonly");
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
export const RouteLogin = "/";
|
||||||
|
|
||||||
|
export const RouteShared = "/sh";
|
||||||
|
export const TokenPlaceholder = ":token";
|
||||||
|
export const RouteSharedFreeTime = `/sh/ft/${TokenPlaceholder}`;
|
||||||
|
export const RouteEventsExternalApproval = `/sh/ev/ap/${TokenPlaceholder}`;
|
||||||
|
|
||||||
|
export const RouteAdmin = "/admin";
|
||||||
|
export const RouteAdminDashboard = "/admin/dashboard";
|
||||||
|
|
||||||
|
export const RouteAdminSettings = "/admin/settings";
|
||||||
|
export const RouteAdminSettingsMain = "/admin/settings/main";
|
||||||
|
export const RouteAdminSettingsSMTPQueue = "/admin/settings/smtp_queue";
|
||||||
|
export const RouteAdminSettingsSMTPQueueSettings =
|
||||||
|
"/admin/settings/smtp_queue/settings";
|
||||||
|
export const RouteAdminSettingsSMTPQueueManage =
|
||||||
|
"/admin/settings/smtp_queue/manage";
|
||||||
|
export const RouteAdminSettingsSettingsDB = "/admin/settings/settings_db";
|
||||||
|
export const RouteAdminSettingsAddressChange = "/admin/settings/address_change";
|
||||||
|
export const RouteAdminSettingsCalendars = "/admin/settings/calendars";
|
||||||
|
export const RouteAdminSettingsLicense = "/admin/settings/license";
|
||||||
|
|
||||||
|
export const RouteAdminSecurity = "/admin/security";
|
||||||
|
export const RouteAdminSecurityBlockedIps = "/admin/security/blocked_ips";
|
||||||
|
export const RouteAdminSecurityWhiteList = "/admin/security/white_list";
|
||||||
|
export const RouteAdminSecurityWhiteListIp = "/admin/security/white_list/ip";
|
||||||
|
export const RouteAdminSecurityWhiteListEmail =
|
||||||
|
"/admin/security/white_list/email";
|
||||||
|
export const RouteAdminSecurityBlackList = "/admin/security/black_list";
|
||||||
|
export const RouteAdminSecurityBlackListIp = "/admin/security/black_list/ip";
|
||||||
|
export const RouteAdminSecurityBlackListEmail =
|
||||||
|
"/admin/security/black_list/email";
|
||||||
|
|
||||||
|
export const RouteAdminDomains = "/admin/domains";
|
||||||
|
export const DomainPlaceholder = ":domain";
|
||||||
|
export const UserdbPlaceholder = ":sid";
|
||||||
|
export const RouteAdminDomainsAdd = `/admin/domains/add`;
|
||||||
|
export const RouteAdminDomainsDomain = `/admin/domains/${DomainPlaceholder}`;
|
||||||
|
export const RouteAdminDomainsDomainMailStorage = `/admin/domains/${DomainPlaceholder}/mailstorage`;
|
||||||
|
export const RouteAdminDomainsDomainMailStorageMailboxes = `/admin/domains/${DomainPlaceholder}/mailstorage/mailboxes`;
|
||||||
|
export const MailboxPlaceholder = ":mailbox";
|
||||||
|
export const RouteAdminDomainsDomainMailStorageMailboxesExport = `/admin/domains/${DomainPlaceholder}/mailstorage/mailboxes/${MailboxPlaceholder}/export`;
|
||||||
|
export const RouteAdminDomainsDomainMailStorageSettings = `/admin/domains/${DomainPlaceholder}/mailstorage/settings`;
|
||||||
|
export const RouteAdminDomainsDomainUserDB = `/admin/domains/${DomainPlaceholder}/userdb`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBAdd = `/admin/domains/${DomainPlaceholder}/userdb/add`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBProvider = `/admin/domains/${DomainPlaceholder}/userdb/${UserdbPlaceholder}`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBProviderUsers = `/admin/domains/${DomainPlaceholder}/userdb/${UserdbPlaceholder}/users`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBProviderGroups = `/admin/domains/${DomainPlaceholder}/userdb/${UserdbPlaceholder}/groups`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBProviderRedirects = `/admin/domains/${DomainPlaceholder}/userdb/${UserdbPlaceholder}/redirects`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBProviderSettings = `/admin/domains/${DomainPlaceholder}/userdb/${UserdbPlaceholder}/settings`;
|
||||||
|
export const RouteAdminDomainsDomainUserDBProviderOther = `/admin/domains/${DomainPlaceholder}/userdb/${UserdbPlaceholder}/other`;
|
||||||
|
export const RouteAdminDomainsDomainSharedFolders = `/admin/domains/${DomainPlaceholder}/shared_folders`;
|
||||||
|
export const RouteAdminDomainsDomainAddressBooks = `/admin/domains/${DomainPlaceholder}/address_books`;
|
||||||
|
export const RouteAdminDomainsDomainCalendars = `/admin/domains/${DomainPlaceholder}/calendars`;
|
||||||
|
|
||||||
|
export const RouteAdminDomainsDomainResources = `/admin/domains/${DomainPlaceholder}/resources`;
|
||||||
|
export const RouteAdminDomainsDomainResourcesList = `/admin/domains/${DomainPlaceholder}/resources/list`;
|
||||||
|
export const RouteAdminDomainsDomainResourcesOffices = `/admin/domains/${DomainPlaceholder}/resources/offices`;
|
||||||
|
export const RouteAdminDomainsDomainResourcesCategories = `/admin/domains/${DomainPlaceholder}/resources/categories`;
|
||||||
|
|
||||||
|
export const RouteAdminDomainsDomainIncRules = `/admin/domains/${DomainPlaceholder}/incoming_rules`;
|
||||||
|
export const RouteAdminDomainsDomainOutRules = `/admin/domains/${DomainPlaceholder}/outgoing_rules`;
|
||||||
|
export const RouteAdminDomainsDomainDKIM = `/admin/domains/${DomainPlaceholder}/dkim`;
|
||||||
|
export const RouteAdminDomainsDomainMigration = `/admin/domains/${DomainPlaceholder}/migration`;
|
||||||
|
export const RouteAdminDomainsDomainCIDRAccess = `/admin/domains/${DomainPlaceholder}/cidr_access`;
|
||||||
|
export const RouteAdminDomainsDomainCIDRAccessPools = `/admin/domains/${DomainPlaceholder}/cidr_access/pools`;
|
||||||
|
export const RouteAdminDomainsDomainCIDRAccessPolicy = `/admin/domains/${DomainPlaceholder}/cidr_access/policy`;
|
||||||
|
export const RouteAdminDomainsDomainOther = `/admin/domains/${DomainPlaceholder}/other`;
|
||||||
|
export const RouteAdminDomainsDomainOtherMailsPermDeletion = `/admin/domains/${DomainPlaceholder}/other/mails_perm_deletion`;
|
||||||
|
|
||||||
|
export const RouteUser = "/user";
|
||||||
|
export const RouteUserProfile = "/user/profile";
|
||||||
|
export const RouteUserRules = "/user/rules";
|
||||||
|
export const RouteUserRecovery = "/user/recovery";
|
||||||
|
export const RouteUserMailboxSharedAccess = "/user/mailbox_shared_access";
|
||||||
|
export const RouteUserSharedFolders = "/user/shared_folders";
|
||||||
|
|
||||||
|
export const RouteUserAddressBooks = "/user/address_books";
|
||||||
|
export const RouteUserAddressBooksMy = "/user/address_books/my_books";
|
||||||
|
export const RouteUserAddressBooksAvaliableToMe =
|
||||||
|
"/user/address_books/avaliable_to_me";
|
||||||
|
|
||||||
|
export const RouteUserCalendars = "/user/calendars";
|
||||||
|
export const RouteUserCalendarsMy = "/user/calendars/my_calendars";
|
||||||
|
export const RouteUserCalendarsAvaliableToMe =
|
||||||
|
"/user/calendars/avaliable_to_me";
|
||||||
|
export const RouteUserCalendarsShareFreeTime =
|
||||||
|
"/user/calendars/share_free_time";
|
||||||
|
|
||||||
|
export const RouteUserCalendarsEventsPlanner = "/user/calendars/events_planner";
|
||||||
|
export const RouteUserCalendarsEventsPlannerList =
|
||||||
|
"/user/calendars/events_planner/my_events";
|
||||||
|
export const RouteUserCalendarsEventsPlannerNew =
|
||||||
|
"/user/calendars/events_planner/new";
|
||||||
|
|
||||||
|
export const EventIdPlaceholder = ":event_id";
|
||||||
|
|
||||||
|
export const RouteUserCalendarsEventsPlannerEdit = `/user/calendars/events_planner/${EventIdPlaceholder}/edit`;
|
|
@ -0,0 +1,105 @@
|
||||||
|
import {
|
||||||
|
createRouter,
|
||||||
|
createWebHistory,
|
||||||
|
type RouteRecordRaw,
|
||||||
|
} from "vue-router";
|
||||||
|
import { pipeline } from "@/router/middleware/pipeline";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import {
|
||||||
|
RouteAdmin,
|
||||||
|
RouteAdminDashboard,
|
||||||
|
RouteLogin,
|
||||||
|
RouteShared,
|
||||||
|
RouteUser,
|
||||||
|
RouteUserProfile,
|
||||||
|
} from "./consts";
|
||||||
|
import routes from "@/router/routes";
|
||||||
|
import { nextTick } from "vue";
|
||||||
|
import i18n from "@/locale";
|
||||||
|
import { useReturnToPageStore } from "@/stores/returnToPage";
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: routes as RouteRecordRaw[],
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
let path = to.path;
|
||||||
|
if (!to) {
|
||||||
|
path = location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.startsWith(RouteShared)) {
|
||||||
|
if (to.name !== RouteLogin) {
|
||||||
|
if (!useAuthStore().authed) {
|
||||||
|
useReturnToPageStore().setPath(path, to.query);
|
||||||
|
return next({ name: RouteLogin });
|
||||||
|
}
|
||||||
|
if (useAuthStore().isAdmin && !to.path.startsWith(RouteAdmin)) {
|
||||||
|
return next({ name: RouteAdminDashboard });
|
||||||
|
}
|
||||||
|
if (!useAuthStore().isAdmin && !to.path.startsWith(RouteUser)) {
|
||||||
|
return next({ name: RouteUserProfile });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to.meta.middleware) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const middleware = Array.isArray(to.meta.middleware)
|
||||||
|
? to.meta.middleware
|
||||||
|
: [to.meta.middleware];
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
next,
|
||||||
|
};
|
||||||
|
|
||||||
|
return middleware[0](pipeline(context, middleware, 1), context);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.afterEach((to, from) => {
|
||||||
|
// Use next tick to handle router history correctly
|
||||||
|
// see: https://github.com/vuejs/vue-router/issues/914#issuecomment-384477609
|
||||||
|
nextTick(() => {
|
||||||
|
let title = to.meta.title as string;
|
||||||
|
if (!title) {
|
||||||
|
if (to.path.startsWith("/admin")) {
|
||||||
|
title = i18n.global.t("admin.title");
|
||||||
|
} else if (to.path.startsWith("/user")) {
|
||||||
|
if (useAuthStore().username) {
|
||||||
|
title = useAuthStore().username;
|
||||||
|
} else {
|
||||||
|
title = i18n.global.t("user.title");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
title = title + " - " + i18n.global.t("tegu");
|
||||||
|
}
|
||||||
|
document.title = title || "Tegu";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (to.hash === "#refresh") {
|
||||||
|
router.replace({ hash: "" }).then(() => {
|
||||||
|
router.go(0);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.query.redirect) {
|
||||||
|
let redirect = to.query.redirect as string;
|
||||||
|
let parts = redirect.split("?");
|
||||||
|
router.push(parts[0]);
|
||||||
|
|
||||||
|
useReturnToPageStore().setPath(
|
||||||
|
parts[0],
|
||||||
|
Object.fromEntries(new URLSearchParams(parts[1]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue