mirror of
https://github.com/XShengTech/MEGREZ.git
synced 2026-01-14 00:57:17 +08:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2147f3876b | ||
|
|
9b389674cf | ||
|
|
46cdf276e6 | ||
|
|
e54fa18283 | ||
|
|
f3543e2657 | ||
|
|
f82d04e7d3 | ||
|
|
f8b84a96f0 | ||
|
|
38bfb0c828 | ||
|
|
2bf6a58e7f | ||
|
|
69f91dba02 | ||
|
|
2a18684162 | ||
|
|
e6f81e19dc | ||
|
|
05502938ba | ||
|
|
7fc2c3ec09 | ||
|
|
011827f5c7 | ||
|
|
69554b5e39 | ||
|
|
a24fb8e8ad | ||
|
|
42de9caf52 | ||
|
|
5780f84714 | ||
|
|
de8c601be9 | ||
|
|
e283a626a1 | ||
|
|
37ba1baf71 | ||
|
|
81b52e80b5 | ||
|
|
13bd350274 | ||
|
|
35a55a9e03 | ||
|
|
801335ab5d | ||
|
|
d0e95f9521 | ||
|
|
417397f282 | ||
|
|
e80f3f02aa | ||
|
|
2e3cfbcecb | ||
|
|
52bdfa17eb |
32
.github/workflows/CLA.yml
vendored
Normal file
32
.github/workflows/CLA.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: "CLA Assistant"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened,closed,synchronize]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
CLAAssistant:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||
uses: contributor-assistant/github-action@v2.6.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PERSONAL_ACCESS_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: 'signatures/v1/cla.json'
|
||||
path-to-document: 'https://github.com/XShengTech/.github/blob/main/CLA.md'
|
||||
branch: 'CLA'
|
||||
allowlist: dependabot[bot]
|
||||
|
||||
remote-organization-name: XShengTech
|
||||
remote-repository-name: .github
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM golang:1.23.6-alpine AS builder
|
||||
FROM golang:1.25-alpine AS builder
|
||||
LABEL stage=gobuilder \
|
||||
mainatiner=https://github.com/XShengTech/MEGREZ
|
||||
|
||||
@ -24,11 +24,15 @@ RUN BRANCH=$(git rev-parse --abbrev-ref HEAD) && \
|
||||
|
||||
FROM alpine
|
||||
|
||||
VOLUME /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
COPY --from=builder /build/megrez /app/megrez
|
||||
|
||||
COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip /opt/zoneinfo.zip
|
||||
ENV ZONEINFO=/opt/zoneinfo.zip
|
||||
|
||||
EXPOSE 34567
|
||||
|
||||
ENTRYPOINT ["./megrez"]
|
||||
17
README.md
17
README.md
@ -38,7 +38,7 @@
|
||||
|
||||
✅ 资源性能监控看板
|
||||
|
||||
✅ Jupter Notebook 支持
|
||||
✅ Jupyter Notebook 支持
|
||||
|
||||
|
||||
## 📝 安装 & 使用教程
|
||||
@ -46,7 +46,7 @@
|
||||
> [!WARNING]
|
||||
> 部署仓库: [XShengTech/MEGREZ-Deploy](https://github.com/XShengTech/MEGREZ-Deploy)
|
||||
>
|
||||
> 查看文档 [**>>> 🚧 正在施工中 <<<**]()
|
||||
> 查看文档 [**>>> MEGREZ 文档 <<<**](http://docs.megrez.xsheng-ai.com/)
|
||||
|
||||
|
||||
## 📌 效果展示
|
||||
@ -79,9 +79,9 @@
|
||||
|
||||
### 内置功能
|
||||
|
||||
| VSCode 网页版 | Jupter Notebook | Grafana 资源监控 |
|
||||
| VSCode 网页版 | Jupyter Notebook | Grafana 资源监控 |
|
||||
| ---------------------------------------------------- | ----------------------------------------------- | -------------------------------- |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|
||||
### 系统管理
|
||||
|
||||
@ -135,6 +135,15 @@
|
||||
若您对源码做出修改/扩展,同样需要以 AGPL-3.0-or-later 开源,您可以以 `Powered by 晓声智能科技, modified by xxx` 格式在页脚注明。
|
||||
|
||||
|
||||
## 📌 贡献者许可协议 (CLA)
|
||||
|
||||
> [!NOTE]
|
||||
> 本项目要求贡献者签署我们的贡献者许可协议(CLA)。
|
||||
>
|
||||
> This project requires contributors to sign our Contributor License Agreement (CLA).
|
||||
>
|
||||
> 协议: https://github.com/XShengTech/.github/blob/main/CLA.md
|
||||
|
||||
|
||||
## 🙏 特别鸣谢
|
||||
|
||||
|
||||
@ -10,6 +10,13 @@ database:
|
||||
redis:
|
||||
host: ms-redis
|
||||
port: 6379
|
||||
smtp:
|
||||
host:
|
||||
port:
|
||||
user:
|
||||
password:
|
||||
ssl: false
|
||||
system:
|
||||
base_url:
|
||||
verify: false
|
||||
mount_dir: /path/to/mount
|
||||
|
||||
@ -35,6 +35,24 @@ export default {
|
||||
UserRegister(data) {
|
||||
return ajax('user/register', 'post', { data })
|
||||
},
|
||||
UserForgetRequest(data) {
|
||||
return ajax('user/forget', 'post', { data })
|
||||
},
|
||||
UserForgerPassword(data) {
|
||||
return ajax('user/password', 'put', { data })
|
||||
},
|
||||
UserResetPassword(data) {
|
||||
return ajax('user/password', 'post', { data })
|
||||
},
|
||||
UserResetEmail(data) {
|
||||
return ajax('user/email', 'post', { data })
|
||||
},
|
||||
UserVerifyRequest() {
|
||||
return ajax(`user/verify`, 'post', {})
|
||||
},
|
||||
UserVerify(code) {
|
||||
return ajax(`user/verify/${code}`, 'get', {})
|
||||
},
|
||||
|
||||
UserInstancesList(params) {
|
||||
return ajax('user/instances', 'get', { params })
|
||||
|
||||
87
frontend/src/layout/AppAdminLayout.vue
Normal file
87
frontend/src/layout/AppAdminLayout.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import { useLayout } from '@/layout/composables/layout';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import AppAdminSidebar from './AppAdminSidebar.vue';
|
||||
import AppFooter from './AppFooter.vue';
|
||||
import AppTopbar from './AppTopbar.vue';
|
||||
|
||||
import api from '@/api';
|
||||
import { useProfileStore } from '@/stores/profile';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter()
|
||||
const profileStore = useProfileStore()
|
||||
const toast = useToast()
|
||||
|
||||
const { layoutConfig, layoutState, isSidebarActive, resetMenu } = useLayout();
|
||||
|
||||
const outsideClickListener = ref(null);
|
||||
|
||||
watch(isSidebarActive, (newVal) => {
|
||||
if (newVal) {
|
||||
bindOutsideClickListener();
|
||||
} else {
|
||||
unbindOutsideClickListener();
|
||||
}
|
||||
});
|
||||
|
||||
const containerClass = computed(() => {
|
||||
return {
|
||||
'layout-overlay': layoutConfig.menuMode === 'overlay',
|
||||
'layout-static': layoutConfig.menuMode === 'static',
|
||||
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
|
||||
'layout-overlay-active': layoutState.overlayMenuActive,
|
||||
'layout-mobile-active': layoutState.staticMenuMobileActive
|
||||
};
|
||||
});
|
||||
|
||||
function bindOutsideClickListener() {
|
||||
if (!outsideClickListener.value) {
|
||||
outsideClickListener.value = (event) => {
|
||||
if (isOutsideClicked(event)) {
|
||||
resetMenu();
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', outsideClickListener.value);
|
||||
}
|
||||
}
|
||||
|
||||
function unbindOutsideClickListener() {
|
||||
if (outsideClickListener.value) {
|
||||
document.removeEventListener('click', outsideClickListener);
|
||||
outsideClickListener.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function isOutsideClicked(event) {
|
||||
const sidebarEl = document.querySelector('.layout-sidebar');
|
||||
const topbarEl = document.querySelector('.layout-menu-button');
|
||||
|
||||
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
api.GetUserProfile().then(res => {
|
||||
profileStore.setUserProfile(res.data.data.result)
|
||||
}).catch(_ => {
|
||||
toast.add({ severity: 'error', summary: '登录过期,请重新登录', life: 3000 })
|
||||
profileStore.clearUserProfile()
|
||||
router.push('/login')
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-wrapper" :class="containerClass">
|
||||
<app-topbar></app-topbar>
|
||||
<app-admin-sidebar></app-admin-sidebar>
|
||||
<div class="layout-main-container">
|
||||
<div class="layout-main">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
<div class="layout-mask animate-fadein"></div>
|
||||
</div>
|
||||
</template>
|
||||
88
frontend/src/layout/AppAdminMenu.vue
Normal file
88
frontend/src/layout/AppAdminMenu.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<script setup>
|
||||
import { useProfileStore } from '@/stores/profile';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import AppMenuItem from './AppMenuItem.vue';
|
||||
|
||||
const profileStore = useProfileStore();
|
||||
|
||||
|
||||
const model = ref([]);
|
||||
|
||||
const defaultModel = ref({
|
||||
label: '用户中心',
|
||||
items: [
|
||||
{
|
||||
label: '实例管理',
|
||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||
to: '/'
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const adminModel = ref({
|
||||
label: '系统设置',
|
||||
items: [
|
||||
{
|
||||
label: '实例管理',
|
||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||
to: '/admin/instances'
|
||||
},
|
||||
{
|
||||
label: '用户管理',
|
||||
icon: 'pi pi-fw pi-users text-indigo-500',
|
||||
to: '/admin/users'
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const superAdminModel = ref({
|
||||
label: '系统设置',
|
||||
items: [
|
||||
{
|
||||
label: '节点管理',
|
||||
icon: 'pi pi-fw pi-server text-yellow-400',
|
||||
to: '/admin/servers'
|
||||
},
|
||||
{
|
||||
label: '实例管理',
|
||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||
to: '/admin/instances'
|
||||
},
|
||||
{
|
||||
label: '用户管理',
|
||||
icon: 'pi pi-fw pi-users text-indigo-500',
|
||||
to: '/admin/users'
|
||||
},
|
||||
{
|
||||
label: '镜像管理',
|
||||
icon: 'pi pi-fw pi-images text-teal-500',
|
||||
to: '/admin/images'
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const superAdmin = profileStore.isSuperAdmin
|
||||
const admin = profileStore.isAdmin
|
||||
|
||||
if (superAdmin) {
|
||||
model.value.push(superAdminModel.value)
|
||||
} else if (admin) {
|
||||
model.value.push(adminModel.value)
|
||||
}
|
||||
|
||||
model.value.push(defaultModel.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="layout-menu">
|
||||
<template v-for="(item, i) in model" :key="item">
|
||||
<app-menu-item v-if="!item.separator" :item="item" :index="i"></app-menu-item>
|
||||
<li v-if="item.separator" class="menu-separator"></li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
11
frontend/src/layout/AppAdminSidebar.vue
Normal file
11
frontend/src/layout/AppAdminSidebar.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import AppAdminMenu from './AppAdminMenu.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-sidebar">
|
||||
<app-admin-menu></app-admin-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@ -56,15 +56,9 @@ const model = ref([
|
||||
{
|
||||
label: '使用文档',
|
||||
icon: 'pi pi-fw pi-book text-amber-500',
|
||||
url: '#',
|
||||
url: 'http://docs.megrez.xsheng-ai.com/guide/usage/',
|
||||
target: '_blank'
|
||||
},
|
||||
// {
|
||||
// label: '开源信息',
|
||||
// icon: 'pi pi-fw pi-cog',
|
||||
// url: 'https://github.com/primefaces/sakai-vue',
|
||||
// target: '_blank'
|
||||
// },
|
||||
]
|
||||
}
|
||||
]);
|
||||
@ -73,15 +67,10 @@ const adminModel = ref({
|
||||
label: '系统设置',
|
||||
items: [
|
||||
{
|
||||
label: '实例管理',
|
||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||
label: '管理后台',
|
||||
icon: 'pi pi-fw pi-sliders-h text-yellow-500',
|
||||
to: '/admin/instances'
|
||||
},
|
||||
{
|
||||
label: '用户管理',
|
||||
icon: 'pi pi-fw pi-users text-indigo-500',
|
||||
to: '/admin/users'
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
@ -89,25 +78,10 @@ const superAdminModel = ref({
|
||||
label: '系统设置',
|
||||
items: [
|
||||
{
|
||||
label: '节点管理',
|
||||
icon: 'pi pi-fw pi-server text-yellow-400',
|
||||
label: '管理后台',
|
||||
icon: 'pi pi-fw pi-sliders-h text-yellow-400',
|
||||
to: '/admin/servers'
|
||||
},
|
||||
{
|
||||
label: '实例管理',
|
||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||
to: '/admin/instances'
|
||||
},
|
||||
{
|
||||
label: '镜像管理',
|
||||
icon: 'pi pi-fw pi-images text-teal-500',
|
||||
to: '/admin/images'
|
||||
},
|
||||
{
|
||||
label: '用户管理',
|
||||
icon: 'pi pi-fw pi-users text-indigo-500',
|
||||
to: '/admin/users'
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Menu ref="profileMenu" :model="instanceMenuItems" :popup="true" @blur="profileMenuActive = false" />
|
||||
<Menu ref="profileMenu" :model="profileMenuItems" :popup="true" @blur="profileMenuActive = false" />
|
||||
</template>
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@ const username = ref('');
|
||||
|
||||
const profileMenu = ref(null);
|
||||
const profileMenuActive = ref(false);
|
||||
const instanceMenuItems = [
|
||||
const profileMenuItems = ref([
|
||||
{
|
||||
label: '个人信息',
|
||||
icon: 'pi pi-user !text-cyan-500',
|
||||
@ -78,6 +78,12 @@ const instanceMenuItems = [
|
||||
router.push('/settings');
|
||||
}
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
]);
|
||||
|
||||
const profileExitItems = ref([
|
||||
{
|
||||
label: '退出登录',
|
||||
icon: 'pi pi-sign-out',
|
||||
@ -86,7 +92,35 @@ const instanceMenuItems = [
|
||||
logout();
|
||||
}
|
||||
}
|
||||
];
|
||||
]);
|
||||
|
||||
const profileAdminItems = ref([
|
||||
{
|
||||
label: '管理后台',
|
||||
icon: 'pi pi-sliders-h !text-yellow-400',
|
||||
command: () => {
|
||||
profileMenu.value.hide();
|
||||
router.push('/admin/instances');
|
||||
}
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
])
|
||||
|
||||
const profileSuperadminItems = ref([
|
||||
{
|
||||
label: '管理后台',
|
||||
icon: 'pi pi-sliders-h !text-yellow-400',
|
||||
command: () => {
|
||||
profileMenu.value.hide();
|
||||
router.push('/admin/servers');
|
||||
}
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
])
|
||||
|
||||
const showMenu = (event) => {
|
||||
profileMenu.value.show(event);
|
||||
@ -105,6 +139,14 @@ const logout = () => {
|
||||
|
||||
onMounted(() => {
|
||||
username.value = profileStore.username;
|
||||
|
||||
if (profileStore.isSuperAdmin) {
|
||||
profileMenuItems.value.push(...profileSuperadminItems.value);
|
||||
} else if (profileStore.isAdmin) {
|
||||
profileMenuItems.value.push(...profileAdminItems.value);
|
||||
}
|
||||
|
||||
profileMenuItems.value.push(...profileExitItems.value);
|
||||
});
|
||||
|
||||
</script>
|
||||
@ -4,10 +4,36 @@ const layoutConfig = reactive({
|
||||
preset: 'Aura',
|
||||
primary: 'blue',
|
||||
surface: null,
|
||||
darkTheme: false,
|
||||
darkTheme: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches,
|
||||
menuMode: 'static'
|
||||
});
|
||||
|
||||
// Listen for changes to the prefers-color-scheme media query
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||
layoutConfig.darkTheme = e.matches;
|
||||
toggleDarkMode();
|
||||
});
|
||||
}
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
if (!document.startViewTransition) {
|
||||
executeDarkModeToggle();
|
||||
return;
|
||||
}
|
||||
|
||||
document.startViewTransition(() => executeDarkModeToggle(event));
|
||||
};
|
||||
|
||||
if (layoutConfig.darkTheme) {
|
||||
toggleDarkMode()
|
||||
}
|
||||
|
||||
const executeDarkModeToggle = () => {
|
||||
layoutConfig.darkTheme = !layoutConfig.darkTheme;
|
||||
document.documentElement.classList.toggle('app-dark');
|
||||
};
|
||||
|
||||
const layoutState = reactive({
|
||||
staticMenuDesktopInactive: false,
|
||||
overlayMenuActive: false,
|
||||
@ -39,21 +65,6 @@ export function useLayout() {
|
||||
layoutConfig.menuMode = mode;
|
||||
};
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
if (!document.startViewTransition) {
|
||||
executeDarkModeToggle();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
document.startViewTransition(() => executeDarkModeToggle(event));
|
||||
};
|
||||
|
||||
const executeDarkModeToggle = () => {
|
||||
layoutConfig.darkTheme = !layoutConfig.darkTheme;
|
||||
document.documentElement.classList.toggle('app-dark');
|
||||
};
|
||||
|
||||
const onMenuToggle = () => {
|
||||
if (layoutConfig.menuMode === 'overlay') {
|
||||
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import AppLayout from '@/layout/AppLayout.vue';
|
||||
import { useProfileStore } from '@/stores/profile';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
import Forget from '@/views/Forget.vue';
|
||||
import Login from '@/views/Login.vue';
|
||||
import Register from '@/views/Register.vue';
|
||||
import Reset from '@/views/Reset.vue';
|
||||
import Verify from '@/views/Verify.vue';
|
||||
|
||||
import InstanceCreate from '@/views/users/InstanceCreate.vue';
|
||||
import InstanceList from '@/views/users/InstanceList.vue';
|
||||
@ -13,6 +17,7 @@ import Instances from '@/views/admin/Instances.vue';
|
||||
import Servers from '@/views/admin/Servers.vue';
|
||||
import Users from '@/views/admin/Users.vue';
|
||||
|
||||
import AppAdminLayout from '@/layout/AppAdminLayout.vue';
|
||||
import NotFound from '@/views/NotFound.vue';
|
||||
|
||||
const router = createRouter({
|
||||
@ -32,6 +37,21 @@ const router = createRouter({
|
||||
name: 'register',
|
||||
component: Register
|
||||
},
|
||||
{
|
||||
path: '/forget',
|
||||
name: 'forget',
|
||||
component: Forget
|
||||
},
|
||||
{
|
||||
path: '/reset/:code',
|
||||
name: 'forget-reset',
|
||||
component: Reset
|
||||
},
|
||||
{
|
||||
path: '/verify/:code',
|
||||
name: 'verify',
|
||||
component: Verify
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'dashboard',
|
||||
@ -51,24 +71,31 @@ const router = createRouter({
|
||||
path: 'settings',
|
||||
name: 'settings',
|
||||
component: Settings
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/admin/',
|
||||
name: 'admin-dashboard',
|
||||
component: AppAdminLayout,
|
||||
children: [
|
||||
{
|
||||
path: 'admin/images',
|
||||
path: 'images',
|
||||
name: 'admin-images',
|
||||
component: Images
|
||||
},
|
||||
{
|
||||
path: 'admin/instances',
|
||||
path: 'instances',
|
||||
name: 'admin-instances',
|
||||
component: Instances
|
||||
},
|
||||
{
|
||||
path: 'admin/servers',
|
||||
path: 'servers',
|
||||
name: 'admin-servers',
|
||||
component: Servers
|
||||
},
|
||||
{
|
||||
path: 'admin/users',
|
||||
path: 'users',
|
||||
name: 'admin-users',
|
||||
component: Users
|
||||
}
|
||||
@ -82,4 +109,19 @@ const router = createRouter({
|
||||
]
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const profileStore = useProfileStore();
|
||||
if (to.path.startsWith('/admin')) {
|
||||
if (!profileStore.isAdmin && !profileStore.isSuperAdmin) {
|
||||
next({ name: 'not-found' });
|
||||
return;
|
||||
}
|
||||
if ((to.path.endsWith('servers') || to.path.endsWith('settings')) && !profileStore.isSuperAdmin) {
|
||||
next({ name: 'not-found' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
76
frontend/src/views/Forget.vue
Normal file
76
frontend/src/views/Forget.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<FloatingConfigurator />
|
||||
<div
|
||||
class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div
|
||||
style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
|
||||
<div class="text-center items-center flex flex-col mb-8">
|
||||
<img :src="logo" style="width: 16rem;" />
|
||||
<div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mt-6 mb-4">天权 算能聚联计算平台</div>
|
||||
<span class="text-muted-color font-medium">忘记密码</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-surface-900 dark:text-surface-0 text-xl font-medium mb-2">邮箱</label>
|
||||
<InputText id="email" type="text" placeholder="邮箱" class="w-full md:w-[30rem] mb-8" v-model="form.email" />
|
||||
|
||||
<div class="flex items-center justify-between mt-2 mb-8 gap-8">
|
||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">记起密码?<span
|
||||
class="text-primary" @click="handleLogin">立即登入</span></span>
|
||||
</div>
|
||||
<Button v-if="!requesting" label="发送邮件" class="w-full" @click="handleSubmit"></Button>
|
||||
<Button v-else label="发送中" icon="pi pi-spin pi-spinner" disabled class="w-full"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import logo from '@/assets/logo-text.webp';
|
||||
import FloatingConfigurator from '@/components/FloatingConfigurator.vue';
|
||||
|
||||
import api from '@/api';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
|
||||
const requesting = ref(false)
|
||||
const form = ref({
|
||||
email: ''
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
requesting.value = true
|
||||
api.UserForgetRequest(form.value).then(res => {
|
||||
toast.add({ severity: 'success', summary: '发送成功', detail: '请查看邮箱', life: 3000 })
|
||||
requesting.value = false
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
toast.add({ severity: 'error', summary: '发送失败', detail: '请检查后重新尝试', life: 3000 })
|
||||
requesting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pi-eye {
|
||||
transform: scale(1.6);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.pi-eye-slash {
|
||||
transform: scale(1.6);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
</style>
|
||||
@ -25,7 +25,8 @@
|
||||
<div class="flex items-center justify-between mt-2 mb-8 gap-8">
|
||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">没有账号?<span
|
||||
class="text-primary" @click="handleRegister">立即注册</span></span>
|
||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-primary">忘记密码</span>
|
||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-primary"
|
||||
@click="handleForget">忘记密码</span>
|
||||
</div>
|
||||
<Button label="登入" class="w-full" @click="handleSubmit"></Button>
|
||||
</div>
|
||||
@ -69,6 +70,10 @@ const handleSubmit = () => {
|
||||
const handleRegister = () => {
|
||||
router.push('/register')
|
||||
}
|
||||
|
||||
const handleForget = () => {
|
||||
router.push('/forget')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
102
frontend/src/views/Reset.vue
Normal file
102
frontend/src/views/Reset.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<FloatingConfigurator />
|
||||
<div
|
||||
class="bg-surface-50 dark:bg-surface-950 flex items-center justify-center min-h-screen min-w-[100vw] overflow-hidden">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div
|
||||
style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
|
||||
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20" style="border-radius: 53px">
|
||||
<div class="text-center items-center flex flex-col mb-8">
|
||||
<img :src="logo" style="width: 16rem;" />
|
||||
<div class="text-surface-900 dark:text-surface-0 text-3xl font-medium mt-6 mb-4">天权 算能聚联计算平台</div>
|
||||
<span class="text-muted-color font-medium">重置密码</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password"
|
||||
class="block text-surface-900 dark:text-surface-0 font-medium text-xl mb-2">新密码</label>
|
||||
<Password id="password" v-model="form.password" placeholder="新密码" :toggleMask="true"
|
||||
class="w-full md:w-[30rem] mb-4" fluid :feedback="false" />
|
||||
|
||||
<label for="repassword"
|
||||
class="block text-surface-900 dark:text-surface-0 font-medium text-xl mb-2">重复密码</label>
|
||||
<Password id="repassword" v-model="form.repassword" placeholder="重复密码" :toggleMask="true" class="mb-4" fluid
|
||||
:feedback="false" @keydown.enter="handleSubmit" />
|
||||
|
||||
<div class="flex items-center justify-between mt-2 mb-8 gap-8">
|
||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">记起密码?<span
|
||||
class="text-primary" @click="handleLogin">立即登入</span></span>
|
||||
</div>
|
||||
<Button label="重置密码" class="w-full" @click="handleSubmit"></Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import logo from '@/assets/logo-text.webp';
|
||||
import FloatingConfigurator from '@/components/FloatingConfigurator.vue';
|
||||
|
||||
import api from '@/api';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
||||
const form = ref({
|
||||
code: route.params.code,
|
||||
password: '',
|
||||
repassword: ''
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!form.value.code) {
|
||||
toast.add({ severity: 'error', summary: '验证码错误', detail: '请检查后重新尝试', life: 3000 })
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.value.password) {
|
||||
toast.add({ severity: 'error', summary: '密码不能为空', detail: '请检查后重新尝试', life: 3000 })
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.value.repassword) {
|
||||
toast.add({ severity: 'error', summary: '重复密码不能为空', detail: '请检查后重新尝试', life: 3000 })
|
||||
return
|
||||
}
|
||||
|
||||
if (form.value.password !== form.value.repassword) {
|
||||
toast.add({ severity: 'error', summary: '两次密码不一致', detail: '请检查后重新尝试', life: 3000 })
|
||||
return
|
||||
}
|
||||
|
||||
api.UserForgerPassword(form.value).then(res => {
|
||||
toast.add({ severity: 'success', summary: '重置密码成功', detail: '请登录', life: 3000 })
|
||||
router.push('/login')
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
toast.add({ severity: 'error', summary: '重置密码失败', detail: '请检查后重新尝试', life: 3000 })
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pi-eye {
|
||||
transform: scale(1.6);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.pi-eye-slash {
|
||||
transform: scale(1.6);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
</style>
|
||||
91
frontend/src/views/Verify.vue
Normal file
91
frontend/src/views/Verify.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<FloatingConfigurator />
|
||||
<div class="flex items-center justify-center min-h-screen overflow-hidden">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img :src="logo" width="64" height="32" class="-mt-6 mb-4" />
|
||||
<div
|
||||
style="border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, color-mix(in srgb, var(--primary-color), transparent 60%) 10%, var(--surface-ground) 30%)">
|
||||
<div class="w-full bg-surface-0 dark:bg-surface-900 py-20 px-8 sm:px-20 flex flex-col items-center"
|
||||
style="border-radius: 53px">
|
||||
<h1 class="text-surface-900 dark:text-surface-0 font-bold text-3xl lg:text-5xl mb-2">邮箱验证</h1>
|
||||
<div class="text-surface-600 dark:text-surface-200 mb-8">验证邮箱地址</div>
|
||||
|
||||
<router-link v-if="status == 0"
|
||||
class="w-full flex items-center mb-8 py-8 border-surface-300 dark:border-surface-500 border-b">
|
||||
<span class="flex justify-center items-center border-2 border-primary text-primary rounded-border"
|
||||
style="height: 3.5rem; width: 3.5rem">
|
||||
<i class="pi pi-fw pi-spin pi-spinner !text-2xl"></i>
|
||||
</span>
|
||||
<span class="ml-6 flex flex-col">
|
||||
<span class="text-surface-900 dark:text-surface-0 lg:text-xl font-medium mb-0">
|
||||
验证中
|
||||
</span>
|
||||
<span class="text-surface-600 dark:text-surface-200 lg:text-xl">
|
||||
请稍后
|
||||
</span>
|
||||
</span>
|
||||
</router-link>
|
||||
|
||||
<router-link v-else-if="status == 1" to="/"
|
||||
class="w-full flex items-center mb-8 py-8 border-surface-300 dark:border-surface-500 border-b">
|
||||
<span class="flex justify-center items-center border-2 border-emerald-500 text-emerald-500 rounded-border"
|
||||
style="height: 3.5rem; width: 3.5rem">
|
||||
<i class="pi pi-fw pi-check !text-2xl"></i>
|
||||
</span>
|
||||
<span class="ml-6 flex flex-col">
|
||||
<span class="text-surface-900 dark:text-surface-0 lg:text-xl font-medium mb-0">
|
||||
验证成功
|
||||
</span>
|
||||
<span class="text-surface-600 dark:text-surface-200 lg:text-xl">
|
||||
你可以开始使用了
|
||||
</span>
|
||||
</span>
|
||||
</router-link>
|
||||
|
||||
<router-link v-else-if="status == 2" to="/"
|
||||
class="w-full flex items-center mb-8 py-8 border-surface-300 dark:border-surface-500 border-b">
|
||||
<span class="flex justify-center items-center border-2 border-red-500 text-red-500 rounded-border"
|
||||
style="height: 3.5rem; width: 3.5rem">
|
||||
<i class="pi pi-fw pi-times !text-2xl"></i>
|
||||
</span>
|
||||
<span class="ml-6 flex flex-col">
|
||||
<span class="text-surface-900 dark:text-surface-0 lg:text-xl font-medium mb-0">
|
||||
验证失败
|
||||
</span>
|
||||
<span class="text-surface-600 dark:text-surface-200 lg:text-xl">
|
||||
请重新验证
|
||||
</span>
|
||||
</span>
|
||||
</router-link>
|
||||
<Button as="router-link" label="回到仪表盘" to="/" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import logo from '@/assets/logo.svg';
|
||||
import FloatingConfigurator from '@/components/FloatingConfigurator.vue';
|
||||
|
||||
import api from '@/api';
|
||||
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
||||
const status = ref(0) // 0: verifing, 1: success, 2: fail
|
||||
const code = route.params.code
|
||||
|
||||
api.UserVerify(code).then(res => {
|
||||
status.value = 1
|
||||
toast.add({ severity: 'success', summary: '验证成功', detail: '你可以开始使用了', life: 3000 })
|
||||
}).catch(err => {
|
||||
status.value = 2
|
||||
toast.add({ severity: 'error', summary: '验证失败', detail: err.response.data.msg, life: 3000 })
|
||||
console.error(err)
|
||||
})
|
||||
</script>
|
||||
@ -70,7 +70,8 @@
|
||||
:href="'http://' + data.code_server_address" target="_blank" v-tooltip.top="'VSCode Web'" />
|
||||
<Button v-else icon="pi pi-code" aria-label="Filter" v-tooltip.top="'VSCode Web'" disabled />
|
||||
<Button v-if="data.status == statusRunning" severity="info" icon="pi pi-inbox" aria-label="Filter" as="a"
|
||||
:href="'http://' + data.jupyter_address" target="_blank" v-tooltip.top="'Jupyter Lab'" />
|
||||
:href="'http://' + data.jupyter_address + '?token=' + data.ssh_passwd" target="_blank"
|
||||
v-tooltip.top="'Jupyter Lab'" />
|
||||
<Button v-else severity="info" icon="pi pi-inbox" aria-label="Filter" v-tooltip.top="'Jupyter Lab'"
|
||||
disabled />
|
||||
<Button v-if="data.status == statusRunning" severity="contrast" icon="pi pi-chart-bar" as="a"
|
||||
@ -121,13 +122,13 @@
|
||||
</Fieldset>
|
||||
<Fieldset legend="GPU">
|
||||
<span v-if="instanceDetail.gpu_count !== 0">{{ instanceDetail.gpu_type }} * {{ instanceDetail.gpu_count
|
||||
}}</span>
|
||||
}}</span>
|
||||
<span v-else>无卡模式</span>
|
||||
</Fieldset>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<Fieldset class="flex flex-wrap gap-2 w-full" legend="CPU">
|
||||
<span v-if="instanceDetail.gpu_count !== 0">{{ instanceDetail.cpu_count_per_gpu * instanceDetail.gpu_count
|
||||
}}
|
||||
}}
|
||||
核</span>
|
||||
<span v-else>1 核</span>
|
||||
</Fieldset>
|
||||
@ -225,7 +226,7 @@
|
||||
</template>
|
||||
</Drawer>
|
||||
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -498,6 +499,9 @@ const instanceModify = async () => {
|
||||
setTimeout(() => {
|
||||
getInstances()
|
||||
}, 100);
|
||||
if (instanceConfiguration.value.cpu_only) {
|
||||
delete instanceConfiguration.value.gpu_count
|
||||
}
|
||||
await api.AdminInstancesModify(instanceDetail.value.id, instanceConfiguration.value).then(async (res) => {
|
||||
toast.add({ severity: 'success', summary: '调整配置', detail: '已调整配置', life: 3000 });
|
||||
instanceModifyVisible.value = false
|
||||
|
||||
@ -96,7 +96,7 @@
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="serverModifyVisible" modal :header="'编辑节点 - ' + serverDetail.name"
|
||||
<Dialog v-model:visible="serverModifyVisible" modal :header="'编辑节点 - ' + serverDetail.name" :draggable="false"
|
||||
:style="{ width: '42rem' }">
|
||||
<span class="text-surface-500 dark:text-surface-400 block mb-6">编辑节点配置</span>
|
||||
<div class="flex items-center gap-0 mb-4">
|
||||
@ -183,7 +183,7 @@
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="serverAddVisible" modal :header="'添加节点'" :style="{ width: '42rem' }">
|
||||
<Dialog v-model:visible="serverAddVisible" modal :header="'添加节点'" :draggable="false" :style="{ width: '42rem' }">
|
||||
<span class="text-surface-500 dark:text-surface-400 block mb-6">添加节点配置</span>
|
||||
<div class="flex items-center gap-0 mb-4">
|
||||
<label class="font-semibold w-20">名称:</label>
|
||||
@ -269,7 +269,7 @@
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@ -39,7 +39,8 @@
|
||||
</Paginator>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="userModifyVisible" modal :header="'编辑用户 - ' + userData.username" :style="{ width: '25rem' }">
|
||||
<Dialog v-model:visible="userModifyVisible" modal :header="'编辑用户 - ' + userData.username" :draggable="false"
|
||||
:style="{ width: '25rem' }">
|
||||
<span class="text-surface-500 dark:text-surface-400 block mb-6">编辑用户信息</span>
|
||||
<div class="flex items-center gap-0 mb-4">
|
||||
<label class="font-semibold w-20">邮箱:</label>
|
||||
@ -61,7 +62,7 @@
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@ -74,7 +74,8 @@
|
||||
:href="'http://' + data.code_server_address" target="_blank" v-tooltip.top="'VSCode Web'" />
|
||||
<Button v-else icon="pi pi-code" aria-label="Filter" v-tooltip.top="'VSCode Web'" disabled />
|
||||
<Button v-if="data.status == statusRunning" severity="info" icon="pi pi-inbox" aria-label="Filter" as="a"
|
||||
:href="'http://' + data.jupyter_address" target="_blank" v-tooltip.top="'Jupyter Lab'" />
|
||||
:href="'http://' + data.jupyter_address + '?token=' + data.ssh_passwd" target="_blank"
|
||||
v-tooltip.top="'Jupyter Lab'" />
|
||||
<Button v-else severity="info" icon="pi pi-inbox" aria-label="Filter" v-tooltip.top="'Jupyter Lab'"
|
||||
disabled />
|
||||
<Button v-if="data.status == statusRunning" severity="contrast" icon="pi pi-chart-bar" as="a"
|
||||
@ -125,13 +126,13 @@
|
||||
</Fieldset>
|
||||
<Fieldset legend="GPU">
|
||||
<span v-if="instanceDetail.gpu_count !== 0">{{ instanceDetail.gpu_type }} * {{ instanceDetail.gpu_count
|
||||
}}</span>
|
||||
}}</span>
|
||||
<span v-else>无卡模式</span>
|
||||
</Fieldset>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<Fieldset class="flex flex-wrap gap-2 w-full" legend="CPU">
|
||||
<span v-if="instanceDetail.gpu_count !== 0">{{ instanceDetail.cpu_count_per_gpu * instanceDetail.gpu_count
|
||||
}}
|
||||
}}
|
||||
核</span>
|
||||
<span v-else>1 核</span>
|
||||
</Fieldset>
|
||||
@ -229,7 +230,7 @@
|
||||
</template>
|
||||
</Drawer>
|
||||
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -501,6 +502,9 @@ const instanceModify = async () => {
|
||||
setTimeout(() => {
|
||||
getInstances()
|
||||
}, 100);
|
||||
if (instanceConfiguration.value.cpu_only) {
|
||||
delete instanceConfiguration.value.gpu_count
|
||||
}
|
||||
await api.UserInstancesModify(instanceDetail.value.id, instanceConfiguration.value).then(async (res) => {
|
||||
toast.add({ severity: 'success', summary: '调整配置', detail: '已调整配置', life: 3000 });
|
||||
instanceModifyVisible.value = false
|
||||
|
||||
@ -1,3 +1,176 @@
|
||||
<template>
|
||||
个人设置
|
||||
</template>
|
||||
<SectionBanner label="安全设置" icon="pi pi-cog text-emerald-500"></SectionBanner>
|
||||
<Fluid>
|
||||
<div class="flex flex-col md:flex-row gap-8">
|
||||
<div class="md:w-1/2">
|
||||
<div class="card rounded-2xl flex flex-col gap-4">
|
||||
<div class="font-semibold text-xl">邮箱验证</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="email">邮箱</label>
|
||||
<InputText v-model="userProfile.email" type="text" disabled />
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Button v-if="!userProfile.verify && !verifyRequesting" label="验证" severity="warn"
|
||||
style="width: 5.6rem; float: right;" @click="emailVerify" />
|
||||
<Button v-else-if="verifyRequesting" label="发送中" severity="warn" icon="pi pi-spin pi-spinner" disabled
|
||||
style="width: 7.2rem; float: right;" />
|
||||
<Button v-else label="已验证" severity="secondary" disabled style="width: 5.6rem; float: right;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card rounded-2xl flex flex-col gap-4">
|
||||
<div class="font-semibold text-xl">修改邮箱</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="email">邮箱</label>
|
||||
<InputText v-if="!resetEmailModifyStatus" v-model="userProfile.email" type="text" disabled />
|
||||
<InputText v-else v-model="resetEmailData.email" type="text" />
|
||||
</div>
|
||||
<div v-if="!resetEmailModifyStatus" class="mt-2">
|
||||
<Button label="修改" style="width: 5.6rem; float: right;" @click="resetEmailModifyStatus = true" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<Button v-if="!resetEmailRequesting" label="保存" style="width: 5.6rem; float: right;" @click="resetEmail" />
|
||||
<Button v-else label="保存中" icon="pi pi-spin pi-spinner" disabled style="width: 7.2rem; float: right;" />
|
||||
<Button class="mr-2" label="取消" severity="secondary" style="width: 5.6rem; float: right;"
|
||||
@click="resetEmailModifyStatus = false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:w-1/2">
|
||||
<div class="card rounded-2xl flex flex-col gap-4">
|
||||
<div class="font-semibold text-xl">修改密码</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="old_password">原密码</label>
|
||||
<Password v-model="resetPasswordData.old_password" :toggleMask="true" :feedback="false" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="new_password">新密码</label>
|
||||
<Password v-model="resetPasswordData.new_password" :toggleMask="true" :feedback="false" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="re_password">重复密码</label>
|
||||
<Password v-model="resetPasswordData.re_password" :toggleMask="true" :feedback="false" />
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Button v-if="!resetPasswordRequesting" label="保存" style="width: 5.6rem; float: right;"
|
||||
@click="resetPassword" />
|
||||
<Button v-else label="保存中" icon="pi pi-spin pi-spinner" disabled style="width: 7.2rem; float: right;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fluid>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import api from '@/api';
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const userProfile = ref({})
|
||||
|
||||
const verifyRequesting = ref(false)
|
||||
|
||||
const resetEmailModifyStatus = ref(false)
|
||||
const resetEmailRequesting = ref(false)
|
||||
const resetEmailData = ref({
|
||||
email: ''
|
||||
})
|
||||
|
||||
const resetPasswordRequesting = ref(false)
|
||||
const resetPasswordData = ref({
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
re_password: ''
|
||||
})
|
||||
|
||||
const getProfile = () => {
|
||||
api.GetUserProfile().then((res) => {
|
||||
userProfile.value = res.data.data.result
|
||||
resetEmailData.value.email = userProfile.value.email
|
||||
console.log(userProfile.value)
|
||||
}).catch((err) => {
|
||||
toast.add({ severity: 'error', summary: '获取用户信息失败', detail: err.response.data.msg, life: 3000 })
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
const emailVerify = () => {
|
||||
verifyRequesting.value = true
|
||||
api.UserVerifyRequest().then((res) => {
|
||||
toast.add({ severity: 'success', summary: '验证邮件已发送', detail: '请前往邮箱查看', life: 3000 })
|
||||
verifyRequesting.value = false
|
||||
}).catch((err) => {
|
||||
toast.add({ severity: 'error', summary: '发送失败', detail: err.response.data.msg, life: 3000 })
|
||||
verifyRequesting.value = false
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
const resetPassword = () => {
|
||||
resetPasswordRequesting.value = true
|
||||
|
||||
if (resetPasswordData.value.old_password == "") {
|
||||
toast.add({ severity: 'error', summary: '原密码不能为空', detail: '请重新输入', life: 3000 })
|
||||
resetPasswordRequesting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (resetPasswordData.value.new_password == "") {
|
||||
toast.add({ severity: 'error', summary: '新密码不能为空', detail: '请重新输入', life: 3000 })
|
||||
resetPasswordRequesting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (resetPasswordData.value.re_password == "") {
|
||||
toast.add({ severity: 'error', summary: '重复密码不能为空', detail: '请重新输入', life: 3000 })
|
||||
resetPasswordRequesting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (resetPasswordData.value.new_password != resetPasswordData.value.re_password) {
|
||||
toast.add({ severity: 'error', summary: '两次密码不一致', detail: '请重新输入', life: 3000 })
|
||||
resetPasswordRequesting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
api.UserResetPassword(resetPasswordData.value).then((res) => {
|
||||
toast.add({ severity: 'success', summary: '修改成功', detail: '请重新登录', life: 3000 })
|
||||
resetPasswordRequesting.value = false
|
||||
}).catch((err) => {
|
||||
toast.add({ severity: 'error', summary: '修改失败', detail: err.response.data.msg, life: 3000 })
|
||||
resetPasswordRequesting.value = false
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
const resetEmail = () => {
|
||||
resetEmailRequesting.value = true
|
||||
|
||||
if (resetEmailData.value.email == '') {
|
||||
toast.add({ severity: 'error', summary: '邮箱不能为空', detail: '请重新输入', life: 3000 })
|
||||
resetEmailRequesting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
api.UserResetEmail(resetEmailData.value).then((res) => {
|
||||
toast.add({ severity: 'success', summary: '修改成功', detail: '请重新登录', life: 3000 })
|
||||
resetEmailRequesting.value = false
|
||||
resetEmailModifyStatus.value = false
|
||||
getProfile()
|
||||
}).catch((err) => {
|
||||
toast.add({ severity: 'error', summary: '修改失败', detail: err.response.data.msg, life: 3000 })
|
||||
resetEmailRequesting.value = false
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getProfile()
|
||||
})
|
||||
|
||||
</script>
|
||||
62
go.mod
62
go.mod
@ -1,65 +1,67 @@
|
||||
module megrez
|
||||
|
||||
go 1.23.1
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kataras/iris/v12 v12.2.11
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.9
|
||||
gorm.io/gorm v1.25.12
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
|
||||
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
|
||||
github.com/CloudyKit/jet/v6 v6.3.1 // indirect
|
||||
github.com/Joker/jade v1.1.3 // indirect
|
||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/Shopify/goreferrer v0.0.0-20250617153402-88c1d9a79b05 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/flosch/pongo2/v4 v4.0.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/iris-contrib/schema v0.0.6 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kataras/blocks v0.0.8 // indirect
|
||||
github.com/kataras/golog v0.1.11 // indirect
|
||||
github.com/kataras/pio v0.0.13 // indirect
|
||||
github.com/kataras/blocks v0.0.11 // indirect
|
||||
github.com/kataras/golog v0.1.13 // indirect
|
||||
github.com/kataras/pio v0.0.14 // indirect
|
||||
github.com/kataras/sitemap v0.0.6 // indirect
|
||||
github.com/kataras/tunnel v0.0.4 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/mailgun/raymond/v2 v2.0.48 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||
github.com/redis/go-redis/v9 v9.5.1 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.20.19 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.12 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.23.10 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.8.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/yosssi/ace v0.0.5 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
127
go.sum
127
go.sum
@ -1,27 +1,27 @@
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
|
||||
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
|
||||
github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw=
|
||||
github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
|
||||
github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
|
||||
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
|
||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
|
||||
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
|
||||
github.com/Shopify/goreferrer v0.0.0-20250617153402-88c1d9a79b05 h1:dG7/gLroJGht/jSQtHiLvT48Hxn+crbmvyItZC8cWOs=
|
||||
github.com/Shopify/goreferrer v0.0.0-20250617153402-88c1d9a79b05/go.mod h1:NYezi6wtnJtBm5btoprXc5SvAdqH0XTXWnUup0MptAI=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -35,18 +35,18 @@ github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0H
|
||||
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0 h1:4gjrh/PN2MuWCCElk8/I4OCKRKWCCo2zEct3VKCbibU=
|
||||
github.com/gomarkdown/markdown v0.0.0-20240328165702-4d01890c35c0/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
|
||||
@ -59,8 +59,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@ -69,20 +69,20 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
|
||||
github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
|
||||
github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20=
|
||||
github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A=
|
||||
github.com/kataras/blocks v0.0.11 h1:JJdYW0AUaJKLx5kEWs/oRVCvKVXo+6CAAeaVAiJf7wE=
|
||||
github.com/kataras/blocks v0.0.11/go.mod h1:b4UySrJySEOq6drKH9U3bOpMI+dRH148mayYfS3RFb8=
|
||||
github.com/kataras/golog v0.1.13 h1:bGbPglTdCutekqwOUf8L1jq3tZ5ADG9gfPBd5p5SzKA=
|
||||
github.com/kataras/golog v0.1.13/go.mod h1:oQmzBTCv/35TetBosjJl/k+LPdlJEblaTupkNwJlwj8=
|
||||
github.com/kataras/iris/v12 v12.2.11 h1:sGgo43rMPfzDft8rjVhPs6L3qDJy3TbBrMD/zGL1pzk=
|
||||
github.com/kataras/iris/v12 v12.2.11/go.mod h1:uMAeX8OqG9vqdhyrIPv8Lajo/wXTtAF43wchP9WHt2w=
|
||||
github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
|
||||
github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
|
||||
github.com/kataras/pio v0.0.14 h1:VGBHOmhwrMMrZeuRqoSfOrFwG+v1JxQge8N50DhmRYQ=
|
||||
github.com/kataras/pio v0.0.14/go.mod h1:ZIlcw5+5Zyb/kOlU7X4uosZ8dbnXmA4GcGKt1XyyTY0=
|
||||
github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
|
||||
github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
|
||||
github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
|
||||
github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@ -90,21 +90,21 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
|
||||
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
@ -115,21 +115,21 @@ github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiy
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tdewolff/minify/v2 v2.23.10 h1:puzRCH00Im+KDf+PxuuSmJykMTVd8Pp1HzTCxVutNmI=
|
||||
github.com/tdewolff/minify/v2 v2.23.10/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno=
|
||||
github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po=
|
||||
github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo=
|
||||
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
|
||||
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
@ -142,6 +142,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
|
||||
@ -153,45 +155,46 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@ -203,9 +206,9 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
|
||||
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
|
||||
|
||||
14
libs/utils/email.go
Normal file
14
libs/utils/email.go
Normal file
@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func EmailFormat(email string) bool {
|
||||
pattern := `\w[-\w.+]*@([-A-Za-z0-9]+\.)+[A-Za-z]{2,14}`
|
||||
match, err := regexp.MatchString(pattern, email)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return match
|
||||
}
|
||||
1
main.go
1
main.go
@ -27,6 +27,7 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
l.SetModel("main")
|
||||
l.Info("Branch: %s", BRANCH)
|
||||
l.Info("Version: %s", VERSION)
|
||||
l.Info("Commit: %s", COMMIT)
|
||||
|
||||
@ -16,7 +16,8 @@ type Users struct {
|
||||
Password string `json:"password,omitempty" gorm:"type:varchar(255);not null"`
|
||||
Role int `json:"role" gorm:"not null,default:0"`
|
||||
|
||||
Email string `json:"email" gorm:"type:varchar(255);uniqueIndex;unique;not null"`
|
||||
Email string `json:"email" gorm:"type:varchar(255);uniqueIndex;unique;not null"`
|
||||
Verify bool `json:"verify" gorm:"not null,default:false"`
|
||||
|
||||
Balance float64 `json:"balance" gorm:"not null"`
|
||||
|
||||
|
||||
@ -33,6 +33,10 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.CpuOnly {
|
||||
req.GpuCount = nil
|
||||
}
|
||||
|
||||
if req.GpuCount != nil {
|
||||
if *req.GpuCount < 0 {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
@ -62,7 +66,17 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.CpuOnly == instance.CpuOnly && req.CpuOnly {
|
||||
hasChanges := false
|
||||
if req.CpuOnly != instance.CpuOnly {
|
||||
hasChanges = true
|
||||
}
|
||||
if req.GpuCount != nil && *req.GpuCount != instance.GpuCount {
|
||||
hasChanges = true
|
||||
}
|
||||
if req.VolumeSize != nil && *req.VolumeSize != instance.VolumeSize {
|
||||
hasChanges = true
|
||||
}
|
||||
if !hasChanges {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ func detailHandler(ctx iris.Context) {
|
||||
user := models.Users{
|
||||
ID: id,
|
||||
}
|
||||
result := database.DB.Select("id", "username", "email", "role", "balance", "created_at").First(&user)
|
||||
result := database.DB.Select("id", "username", "email", "role", "verify", "balance", "created_at").First(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("detail user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeAdminUserDetailError, iris.StatusInternalServerError)
|
||||
|
||||
@ -42,7 +42,7 @@ func listHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
result := database.DB.Limit(limit).Offset(offset).Select("id", "username", "email", "role", "balance", "created_at").Order("id").Find(&users)
|
||||
result := database.DB.Limit(limit).Offset(offset).Select("id", "username", "email", "role", "verify", "balance", "created_at").Order("id").Find(&users)
|
||||
if result.Error != nil {
|
||||
l.Error("list users error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeAdminUserListError, iris.StatusInternalServerError)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"megrez/libs/utils"
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/database"
|
||||
@ -9,8 +10,10 @@ import (
|
||||
)
|
||||
|
||||
type modifyReqStruct struct {
|
||||
Email *string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
Role *int `json:"role"`
|
||||
Verify *bool `json:"verify"`
|
||||
}
|
||||
|
||||
func modifyHandler(ctx iris.Context) {
|
||||
@ -45,6 +48,12 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
if *req.Email != "" && !utils.EmailFormat(*req.Email) {
|
||||
user.Email = *req.Email
|
||||
}
|
||||
}
|
||||
|
||||
if req.Password != nil {
|
||||
if *req.Password != "" {
|
||||
user.Password = user.PasswordHash(*req.Password)
|
||||
@ -55,6 +64,10 @@ func modifyHandler(ctx iris.Context) {
|
||||
user.Role = *req.Role
|
||||
}
|
||||
|
||||
if req.Verify != nil {
|
||||
user.Verify = *req.Verify
|
||||
}
|
||||
|
||||
result = database.DB.Save(&user)
|
||||
if result.Error != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
|
||||
@ -14,6 +14,12 @@ const (
|
||||
CodeUserNotExist ResCode = 1002
|
||||
CodeRegisterRequestError ResCode = 1003
|
||||
CodeRegisterError ResCode = 1004
|
||||
CodeEmailFormatError ResCode = 1005
|
||||
CodeUserAlreadyVerified ResCode = 1006
|
||||
CodeUserVerifyInvalid ResCode = 1007
|
||||
CodePasswordNotMatch ResCode = 1008
|
||||
CodeEmailSameError ResCode = 1009
|
||||
|
||||
CodeInternalCreateError ResCode = 1010
|
||||
CodeInstanceDeleteError ResCode = 1011
|
||||
CodeInstanceQueryError ResCode = 1012
|
||||
@ -56,6 +62,12 @@ var codeMsgMap = map[ResCode]string{
|
||||
CodeUserNotExist: "user not exist",
|
||||
CodeRegisterRequestError: "register request error",
|
||||
CodeRegisterError: "username or email exist",
|
||||
CodeEmailFormatError: "email format error",
|
||||
CodeUserAlreadyVerified: "user already verified",
|
||||
CodeUserVerifyInvalid: "email verify error",
|
||||
CodePasswordNotMatch: "password not match",
|
||||
CodeEmailSameError: "email same error",
|
||||
|
||||
CodeInternalCreateError: "create error",
|
||||
CodeInstanceDeleteError: "delete instance error",
|
||||
CodeInstanceStatusError: "instance status error",
|
||||
|
||||
78
routers/api/v1/user/forgerPassword.go
Normal file
78
routers/api/v1/user/forgerPassword.go
Normal file
@ -0,0 +1,78 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/database"
|
||||
"megrez/services/redis"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
type forgerPasswordStruct struct {
|
||||
Code string `json:"code"`
|
||||
Password string `json:"password"`
|
||||
RePassword string `json:"repassword"`
|
||||
}
|
||||
|
||||
func forgetPasswordHandler(ctx iris.Context) {
|
||||
l.SetFunction("forgetPasswordHandler")
|
||||
|
||||
var req forgerPasswordStruct
|
||||
if err := ctx.ReadJSON(&req); err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Code == "" || req.Password == "" || req.RePassword == "" {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Password != req.RePassword {
|
||||
middleware.Error(ctx, middleware.CodePasswordNotMatch, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rdb := redis.RawDB
|
||||
v := rdb.Get(ctx, forgetPasswordRedisKeyPrefix+req.Code)
|
||||
|
||||
if v.Err() != nil {
|
||||
middleware.Error(ctx, middleware.CodeUserVerifyInvalid, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
r := rdb.Del(ctx, forgetPasswordRedisKeyPrefix+req.Code)
|
||||
if r.Err() != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("delete redis verify code error: %v", r.Err())
|
||||
return
|
||||
}
|
||||
|
||||
id, err := v.Int()
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeUserVerifyInvalid, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := models.Users{
|
||||
ID: uint(id),
|
||||
}
|
||||
result := database.DB.First(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("get user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeUserVerifyInvalid, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = user.PasswordHash(req.Password)
|
||||
|
||||
result = database.DB.Model(&user).Update("password", user.Password)
|
||||
if result.Error != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("update user password Error: %v", result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.Success(ctx)
|
||||
}
|
||||
97
routers/api/v1/user/forgetSend.go
Normal file
97
routers/api/v1/user/forgetSend.go
Normal file
@ -0,0 +1,97 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"megrez/libs/crypto"
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/config"
|
||||
"megrez/services/database"
|
||||
"megrez/services/redis"
|
||||
"megrez/services/smtp"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
type forgetSendStruct struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
const forgetPasswordRedisKeyPrefix = "forget:user:"
|
||||
const forgetPasswordUrlPrefix = "/reset/"
|
||||
const forgetPasswordTitle = "重置密码"
|
||||
const forgetPasswordHTMLFormat = `
|
||||
<div>
|
||||
<table cellpadding="0" align="center" style="overflow:hidden;background:#fff;margin:0 auto;text-align:left;position:relative;font-size:14px; font-family:'lucida Grande',Verdana;line-height:1.5;box-shadow:0 0 3px #ccc;border:1px solid #ccc;border-radius:5px;border-collapse:collapse;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th valign="middle" style="height:38px;color:#fff; font-size:14px;line-height:38px; font-weight:bold;text-align:left;padding:10px 24px 6px; border-bottom:1px solid #467ec3;background:#518bcb;border-radius:5px 5px 0 0;">
|
||||
MEGREZ 天权算能聚联计算平台</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="padding:20px 35px 40px;">
|
||||
<h2 style="font-weight:bold;margin-bottom:5px;font-size:14px;">Hello, %s:</h2>
|
||||
<p style="margin-top:20px">
|
||||
请在15分钟内点击链接: <a href="%s">%s</a> 进行密码重置操作,十五分钟后该链接将会失效.
|
||||
</p>
|
||||
<p style="margin-top:20px">
|
||||
为了保护你的账户,请不要使用单一的密码来进行重置。
|
||||
</p>
|
||||
<p style="margin-top:20px">
|
||||
如果您有任何问题,请联系系统管理员以获得更多信息与支持。
|
||||
</p>
|
||||
<p style="margin-left:2em;"></p>
|
||||
<p style="text-indent:0;text-align:right;">MEGREZ 天权算能聚联计算平台</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
|
||||
func forgetSendHandler(ctx iris.Context) {
|
||||
var req forgetSendStruct
|
||||
if err := ctx.ReadJSON(&req); err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := models.Users{
|
||||
Email: req.Email,
|
||||
}
|
||||
result := database.DB.Where(&user).First(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("get user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeUserNotExist, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rdb := redis.RawDB
|
||||
|
||||
forgetUrl := crypto.Hex(32)
|
||||
err := rdb.Set(ctx, forgetPasswordRedisKeyPrefix+forgetUrl, user.ID, 15*time.Minute).Err()
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("Set Redis error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
forgetUrl = config.GetSystemBaseUrl() + forgetPasswordUrlPrefix + forgetUrl
|
||||
err = smtp.Send(user.Email, forgetPasswordTitle, fmt.Sprintf(forgetPasswordHTMLFormat, user.Username, forgetUrl, forgetUrl))
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("Send SMTP Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.Success(ctx)
|
||||
}
|
||||
@ -39,6 +39,10 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.CpuOnly {
|
||||
req.GpuCount = nil
|
||||
}
|
||||
|
||||
if req.GpuCount != nil {
|
||||
if *req.GpuCount < 0 {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
@ -68,7 +72,17 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.CpuOnly == instance.CpuOnly && req.CpuOnly {
|
||||
hasChanges := false
|
||||
if req.CpuOnly != instance.CpuOnly {
|
||||
hasChanges = true
|
||||
}
|
||||
if req.GpuCount != nil && *req.GpuCount != instance.GpuCount {
|
||||
hasChanges = true
|
||||
}
|
||||
if req.VolumeSize != nil && *req.VolumeSize != instance.VolumeSize {
|
||||
hasChanges = true
|
||||
}
|
||||
if !hasChanges {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@ func loginHandler(ctx iris.Context) {
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
Balance: user.Balance,
|
||||
Verify: user.Verify,
|
||||
}
|
||||
|
||||
middleware.Result(ctx, profile)
|
||||
|
||||
@ -14,6 +14,7 @@ type profile struct {
|
||||
Email string `json:"email"`
|
||||
Role int `json:"role"`
|
||||
Balance float64 `json:"balance"`
|
||||
Verify bool `json:"verify"`
|
||||
}
|
||||
|
||||
func profileHandler(ctx iris.Context) {
|
||||
@ -40,6 +41,7 @@ func profileHandler(ctx iris.Context) {
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
Balance: user.Balance,
|
||||
Verify: user.Verify,
|
||||
}
|
||||
|
||||
middleware.Result(ctx, profile)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"megrez/libs/utils"
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/config"
|
||||
@ -29,6 +30,11 @@ func registerHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.EmailFormat(userReq.Email) {
|
||||
middleware.Error(ctx, middleware.CodeEmailFormatError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := models.Users{
|
||||
Username: userReq.Username,
|
||||
Email: userReq.Email,
|
||||
|
||||
64
routers/api/v1/user/resetEmail.go
Normal file
64
routers/api/v1/user/resetEmail.go
Normal file
@ -0,0 +1,64 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"megrez/libs/utils"
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/database"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
type resetEmailStruct struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
func resetEmailHandler(ctx iris.Context) {
|
||||
l.SetFunction("resetEmailHandler")
|
||||
|
||||
userId, err := ctx.Values().GetInt("userId")
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req resetEmailStruct
|
||||
if err := ctx.ReadJSON(&req); err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.EmailFormat(req.Email) {
|
||||
middleware.Error(ctx, middleware.CodeEmailFormatError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := models.Users{
|
||||
ID: uint(userId),
|
||||
}
|
||||
result := database.DB.First(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("get user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeUserNotExist, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == user.Email {
|
||||
middleware.Error(ctx, middleware.CodeEmailSameError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
result = database.DB.Model(&user).Update("email", req.Email).Update("verify", false)
|
||||
if result.Error != nil {
|
||||
l.Error("save user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInternalPatchError, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.Success(ctx)
|
||||
}
|
||||
@ -11,6 +11,7 @@ import (
|
||||
type resetPasswordStruct struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
RePassword string `json:"re_password"`
|
||||
}
|
||||
|
||||
func resetPasswordHandler(ctx iris.Context) {
|
||||
@ -22,12 +23,17 @@ func resetPasswordHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var resetPassword resetPasswordStruct
|
||||
if err := ctx.ReadJSON(&resetPassword); err != nil {
|
||||
var req resetPasswordStruct
|
||||
if err := ctx.ReadJSON(&req); err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.NewPassword != req.RePassword {
|
||||
middleware.Error(ctx, middleware.CodePasswordNotMatch, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := models.Users{
|
||||
ID: uint(userId),
|
||||
}
|
||||
@ -38,12 +44,12 @@ func resetPasswordHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !user.CheckPassword(resetPassword.OldPassword) {
|
||||
if !user.CheckPassword(req.OldPassword) {
|
||||
middleware.Error(ctx, middleware.CodePasswordError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = user.PasswordHash(resetPassword.NewPassword)
|
||||
user.Password = user.PasswordHash(req.NewPassword)
|
||||
result = database.DB.Save(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("save user error: %v", result.Error)
|
||||
|
||||
@ -24,7 +24,12 @@ func InitUser(party router.Party) {
|
||||
party.Get("/logout", middleware.AuthCheck, logoutHandler)
|
||||
party.Post("/register", registerHandler)
|
||||
party.Get("/profile", middleware.AuthCheck, profileHandler)
|
||||
party.Post("/resetPassword", middleware.AuthCheck, resetPasswordHandler)
|
||||
party.Post("/password", middleware.AuthCheck, resetPasswordHandler)
|
||||
party.Post("/forget", forgetSendHandler)
|
||||
party.Put("/password", forgetPasswordHandler)
|
||||
party.Post("/email", middleware.AuthCheck, resetEmailHandler)
|
||||
party.Get("/verify/{code:string}", verifyHandler)
|
||||
party.Post("/verify", middleware.AuthCheck, verifySendHandler)
|
||||
|
||||
servers.InitServers(party.Party("/servers"))
|
||||
instances.InitInstances(party.Party("/instances"))
|
||||
|
||||
51
routers/api/v1/user/verify.go
Normal file
51
routers/api/v1/user/verify.go
Normal file
@ -0,0 +1,51 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/database"
|
||||
"megrez/services/redis"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
func verifyHandler(ctx iris.Context) {
|
||||
l.SetFunction("verifyHandler")
|
||||
|
||||
code := ctx.Params().GetString("code")
|
||||
|
||||
rdb := redis.RawDB
|
||||
v := rdb.Get(ctx, verifyRedisKeyPrefix+code)
|
||||
|
||||
if v.Err() != nil {
|
||||
middleware.Error(ctx, middleware.CodeUserVerifyInvalid, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
r := rdb.Del(ctx, verifyRedisKeyPrefix+code)
|
||||
if r.Err() != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("delete redis verify code error: %v", r.Err())
|
||||
return
|
||||
}
|
||||
|
||||
email := v.Val()
|
||||
user := models.Users{
|
||||
Email: email,
|
||||
}
|
||||
result := database.DB.Where(&user).First(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("get user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeUserVerifyInvalid, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
result = database.DB.Model(&user).Update("verify", true)
|
||||
if result.Error != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("update user verify status Error: %v", result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.Success(ctx)
|
||||
}
|
||||
92
routers/api/v1/user/verifySend.go
Normal file
92
routers/api/v1/user/verifySend.go
Normal file
@ -0,0 +1,92 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"megrez/libs/crypto"
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/config"
|
||||
"megrez/services/database"
|
||||
"megrez/services/redis"
|
||||
"megrez/services/smtp"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
const verifyRedisKeyPrefix = "verify:user:"
|
||||
const verifyUrlPrefix = "/verify/"
|
||||
const verifyTitle = "邮箱验证"
|
||||
const verifyHTMLFormat = `
|
||||
<div>
|
||||
<table cellpadding="0" align="center" style="overflow:hidden;background:#fff;margin:0 auto;text-align:left;position:relative;font-size:14px; font-family:'lucida Grande',Verdana;line-height:1.5;box-shadow:0 0 3px #ccc;border:1px solid #ccc;border-radius:5px;border-collapse:collapse;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th valign="middle" style="height:38px;color:#fff; font-size:14px;line-height:38px; font-weight:bold;text-align:left;padding:10px 24px 6px; border-bottom:1px solid #467ec3;background:#518bcb;border-radius:5px 5px 0 0;">
|
||||
MEGREZ 天权算能聚联计算平台</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="padding:20px 35px 40px;">
|
||||
<h2 style="font-weight:bold;margin-bottom:5px;font-size:14px;">Hello, %s:</h2>
|
||||
<p style="margin-top:20px">
|
||||
请在15分钟内点击链接: <a href="%s">%s</a> 进行邮箱验证操作,十五分钟后该链接将会失效.
|
||||
</p>
|
||||
<p style="margin-top:20px">
|
||||
如果您有任何问题,请联系系统管理员以获得更多信息与支持。
|
||||
</p>
|
||||
<p style="margin-left:2em;"></p>
|
||||
<p style="text-indent:0;text-align:right;">MEGREZ 天权算能聚联计算平台</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
|
||||
func verifySendHandler(ctx iris.Context) {
|
||||
l.SetFunction("verifySendHandler")
|
||||
|
||||
userId, err := ctx.Values().GetInt("userId")
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := models.Users{
|
||||
ID: uint(userId),
|
||||
}
|
||||
result := database.DB.First(&user)
|
||||
if result.Error != nil {
|
||||
l.Error("get user error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeUserNotExist, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if user.Verify {
|
||||
middleware.Error(ctx, middleware.CodeUserAlreadyVerified, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rdb := redis.RawDB
|
||||
|
||||
verifyUrl := crypto.Hex(32)
|
||||
err = rdb.Set(ctx, verifyRedisKeyPrefix+verifyUrl, user.Email, 15*time.Minute).Err()
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("Set Redis Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
verifyUrl = config.GetSystemBaseUrl() + verifyUrlPrefix + verifyUrl
|
||||
err = smtp.Send(user.Email, verifyTitle, fmt.Sprintf(verifyHTMLFormat, user.Username, verifyUrl, verifyUrl))
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeServeBusy, iris.StatusInternalServerError)
|
||||
l.Error("Send SMTP Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.Success(ctx)
|
||||
}
|
||||
@ -9,6 +9,7 @@ type configStruct struct {
|
||||
Http httpStruct `yaml:"http,omitempty"`
|
||||
Database databaseStruct `yaml:"database,omitempty"`
|
||||
Redis redisStruct `yaml:"redis,omitempty"`
|
||||
Smtp smtpStruct `yaml:"smtp,omitempty"`
|
||||
Log logStruct `yaml:"log,omitempty"`
|
||||
System systemStruct `yaml:"system,omitempty"`
|
||||
}
|
||||
@ -35,12 +36,21 @@ type redisStruct struct {
|
||||
SentinelPassword string `yaml:"sentinel_password,omitempty"`
|
||||
}
|
||||
|
||||
type smtpStruct struct {
|
||||
Host string `yaml:"host,omitempty"`
|
||||
Port int `yaml:"port,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
User string `yaml:"user,omitempty"`
|
||||
SSL bool `yaml:"ssl,omitempty"`
|
||||
}
|
||||
|
||||
type logStruct struct {
|
||||
Level string `yaml:"level,omitempty"`
|
||||
File string `yaml:"file,omitempty"`
|
||||
}
|
||||
|
||||
type systemStruct struct {
|
||||
BaseUrl string `yaml:"base_url,omitempty"`
|
||||
Salt string `yaml:"salt,omitempty"`
|
||||
Verify bool `yaml:"verify,omitempty"`
|
||||
MountDir string `yaml:"mount_dir,omitempty"`
|
||||
@ -64,6 +74,7 @@ var config = configStruct{
|
||||
Password: "GpuManager",
|
||||
Database: 0,
|
||||
},
|
||||
Smtp: smtpStruct{},
|
||||
Log: logStruct{
|
||||
Level: "DEBUG",
|
||||
File: "data/logs/backend.log",
|
||||
|
||||
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@ -19,6 +20,10 @@ func InitConfig(path string) {
|
||||
l.Fatal("Failed to unmarshal config file", err)
|
||||
}
|
||||
l.SetLevel(config.GetLogLevel())
|
||||
|
||||
if config.System.BaseUrl == "" {
|
||||
config.System.BaseUrl = "http://" + config.Http.Host + ":" + strconv.Itoa(config.Http.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func GetDatabase() databaseStruct {
|
||||
@ -33,6 +38,10 @@ func GetRedis() redisStruct {
|
||||
return config.GetRedis()
|
||||
}
|
||||
|
||||
func GetSmtp() smtpStruct {
|
||||
return config.GetSmtp()
|
||||
}
|
||||
|
||||
func GetLogLevel() string {
|
||||
return config.GetLogLevel()
|
||||
}
|
||||
@ -41,6 +50,10 @@ func GetLogFile() string {
|
||||
return config.GetLogFile()
|
||||
}
|
||||
|
||||
func GetSystemBaseUrl() string {
|
||||
return config.GetSystemBaseUrl()
|
||||
}
|
||||
|
||||
func GetSystemSalt() string {
|
||||
return config.GetSystemSalt()
|
||||
}
|
||||
|
||||
@ -20,6 +20,10 @@ func (c *configStruct) GetRedis() redisStruct {
|
||||
return c.Redis
|
||||
}
|
||||
|
||||
func (c *configStruct) GetSmtp() smtpStruct {
|
||||
return c.Smtp
|
||||
}
|
||||
|
||||
func (c *configStruct) GetLogLevel() string {
|
||||
return c.Log.Level
|
||||
}
|
||||
@ -28,6 +32,10 @@ func (c *configStruct) GetLogFile() string {
|
||||
return c.Log.File
|
||||
}
|
||||
|
||||
func (c *configStruct) GetSystemBaseUrl() string {
|
||||
return c.System.BaseUrl
|
||||
}
|
||||
|
||||
func (c *configStruct) GetSystemSalt() string {
|
||||
return c.System.Salt
|
||||
}
|
||||
|
||||
@ -10,12 +10,6 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type modifyDataStruct struct {
|
||||
CpuOnly bool `json:"cpu_only"`
|
||||
GpuCount *int `json:"gpu_count"`
|
||||
VolumeSize *int `json:"volume_size"`
|
||||
}
|
||||
|
||||
func modify(serverID uint, data Data) (err error) {
|
||||
lc := l.Clone()
|
||||
lc.SetFunction("modify")
|
||||
@ -43,9 +37,19 @@ func modify(serverID uint, data Data) (err error) {
|
||||
return errors.New("instance status error")
|
||||
}
|
||||
|
||||
if data.CpuOnly == instance.CpuOnly && data.CpuOnly {
|
||||
lc.Error("instance already cpu_only mode")
|
||||
return errors.New("instance already cpu_only mode")
|
||||
hasChanges := false
|
||||
if data.CpuOnly != instance.CpuOnly {
|
||||
hasChanges = true
|
||||
}
|
||||
if data.GpuCount != nil && *data.GpuCount != instance.GpuCount {
|
||||
hasChanges = true
|
||||
}
|
||||
if data.VolumeSize != nil && *data.VolumeSize != instance.VolumeSize {
|
||||
hasChanges = true
|
||||
}
|
||||
if !hasChanges {
|
||||
lc.Error("no changes")
|
||||
return errors.New("no changes")
|
||||
}
|
||||
|
||||
oldVolumeSize := instance.VolumeSize
|
||||
@ -61,6 +65,7 @@ func modify(serverID uint, data Data) (err error) {
|
||||
err = instanceController.Patch(&instance, gpuCount, volumeSize, data.CpuOnly)
|
||||
if err != nil {
|
||||
ctx := context.Background()
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(gpuCount))
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(serverID)), int64(volumeSize-oldVolumeSize))
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionModify)
|
||||
lc.Error("patch instance error: %v", err)
|
||||
|
||||
@ -51,6 +51,13 @@ func Start() {
|
||||
LogLevel: "disable",
|
||||
Charset: "UTF-8",
|
||||
EnableOptimizations: true,
|
||||
RemoteAddrHeaders: []string{
|
||||
"X-Real-Ip",
|
||||
"X-Forwarded-For",
|
||||
"CF-Connecting-IP",
|
||||
"True-Client-Ip",
|
||||
"X-Appengine-Remote-Addr",
|
||||
},
|
||||
}),
|
||||
iris.WithoutServerError(iris.ErrServerClosed),
|
||||
)
|
||||
|
||||
@ -62,7 +62,7 @@ func createInstance(ip string, port int, apikey string,
|
||||
if config.GetSystemMountDir() != "" {
|
||||
data.Binds = append(data.Binds, bindStruct{
|
||||
Src: config.GetSystemMountDir(),
|
||||
Dest: "/root/megrez-pub",
|
||||
Dest: "/root/megrez-mnt",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,15 @@ func patchGpu(ip string, port int, apikey string,
|
||||
Dest: "/root/megrez-tmp",
|
||||
},
|
||||
}
|
||||
data.GpuPatch = &gpuPatchStruct{
|
||||
GpuCount: gpuCount,
|
||||
}
|
||||
data.CpuPatch = &cpuPatchStruct{
|
||||
CpuCount: cpuCountPerGpu * gpuCount,
|
||||
}
|
||||
data.MemoryPatch = &MemoryPatchStruct{
|
||||
Memory: strconv.Itoa(memoryPerGpu*gpuCount) + "GB",
|
||||
}
|
||||
}
|
||||
|
||||
reqBytes, err := json.Marshal(data)
|
||||
|
||||
90
services/smtp/smtp.go
Normal file
90
services/smtp/smtp.go
Normal file
@ -0,0 +1,90 @@
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"megrez/services/config"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const emailTitle = "MEGREZ 天权算能聚联计算平台"
|
||||
|
||||
func Send(toAddr, title string, html string) error {
|
||||
smtpConf := config.GetSmtp()
|
||||
|
||||
if smtpConf.Host == "" || smtpConf.Port == 0 || smtpConf.User == "" || smtpConf.Password == "" {
|
||||
return errors.New("SMTP smtpConfiguration is not set")
|
||||
}
|
||||
|
||||
if !smtpConf.SSL {
|
||||
auth := smtp.PlainAuth("", smtpConf.User, smtpConf.Password, smtpConf.Host)
|
||||
msg := append([]byte("Subject: "+title+" - "+emailTitle+" \r\n"+
|
||||
"MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\r\n\r\n"),
|
||||
html...)
|
||||
err := smtp.SendMail(smtpConf.Host+":"+strconv.Itoa(smtpConf.Port), auth, smtpConf.User, []string{toAddr}, msg)
|
||||
if err != nil {
|
||||
return errors.New("fail to send email, Error:" + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
from := mail.Address{Name: "MEGREZ", Address: smtpConf.User}
|
||||
to := mail.Address{Name: "", Address: toAddr}
|
||||
auth := smtp.PlainAuth("", smtpConf.User, smtpConf.Password, smtpConf.Host)
|
||||
// Setup headers
|
||||
headers := make(map[string]string)
|
||||
headers["From"] = from.String()
|
||||
headers["To"] = to.String()
|
||||
headers["Subject"] = title + " - " + emailTitle
|
||||
headers["Content-Type"] = "text/html;charset=UTF-8"
|
||||
msg := ""
|
||||
for k, v := range headers {
|
||||
msg += fmt.Sprintf("%s: %s\r\n", k, v)
|
||||
}
|
||||
msg += "\r\n" + html
|
||||
tlssmtpConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: smtpConf.Host,
|
||||
}
|
||||
conn, err := tls.Dial("tcp", smtpConf.Host+":"+strconv.Itoa(smtpConf.Port), tlssmtpConfig)
|
||||
if err != nil {
|
||||
return errors.New("fail to connect to the server, Error:" + err.Error())
|
||||
}
|
||||
c, err := smtp.NewClient(conn, smtpConf.Host)
|
||||
if err != nil {
|
||||
return errors.New("fail to create smtp client, Error:" + err.Error())
|
||||
}
|
||||
if err = c.Auth(auth); err != nil {
|
||||
return errors.New("fail to auth, Error:" + err.Error())
|
||||
}
|
||||
|
||||
// To && From
|
||||
if err = c.Mail(from.Address); err != nil {
|
||||
return errors.New("fail to set from address, Error:" + err.Error())
|
||||
}
|
||||
|
||||
if err = c.Rcpt(to.Address); err != nil {
|
||||
return errors.New("fail to set to address, Error:" + err.Error())
|
||||
}
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return errors.New("fail to get smtp data writer, Error:" + err.Error())
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(msg))
|
||||
if err != nil {
|
||||
return errors.New("fail to write data, Error:" + err.Error())
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return errors.New("fail to close smtp data writer, Error:" + err.Error())
|
||||
}
|
||||
|
||||
c.Quit()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -24,6 +24,7 @@ func systemInit() (err error) {
|
||||
Username: "admin",
|
||||
Email: "admin@gpuManager.com",
|
||||
Role: 3,
|
||||
Verify: true,
|
||||
}
|
||||
result := database.DB.Create(&user)
|
||||
if result.Error != nil {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user