mirror of
https://github.com/XShengTech/MEGREZ.git
synced 2026-05-03 13:02:38 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fc2c3ec09 | |||
| 011827f5c7 | |||
| 69554b5e39 | |||
| a24fb8e8ad | |||
| 42de9caf52 | |||
| 5780f84714 | |||
| de8c601be9 | |||
| e283a626a1 | |||
| 37ba1baf71 | |||
| 81b52e80b5 | |||
| 13bd350274 | |||
| 35a55a9e03 | |||
| 801335ab5d | |||
| d0e95f9521 | |||
| 417397f282 | |||
| e80f3f02aa | |||
| 2e3cfbcecb | |||
| 52bdfa17eb | |||
| cbc426c180 | |||
| 1a3a126388 | |||
| 1133650632 | |||
| 9d561cb63a | |||
| 1f55ecb649 | |||
| 87995f0572 | |||
| bb7b60352e | |||
| ef93c40361 | |||
| 716764a86d | |||
| 44c319698c | |||
| 8104773f40 | |||
| 249f99e5d9 |
@@ -38,7 +38,7 @@
|
||||
|
||||
✅ 资源性能监控看板
|
||||
|
||||
✅ Jupter Notebook 支持
|
||||
✅ Jupyter Notebook 支持
|
||||
|
||||
|
||||
## 📝 安装 & 使用教程
|
||||
@@ -46,12 +46,15 @@
|
||||
> [!WARNING]
|
||||
> 部署仓库: [XShengTech/MEGREZ-Deploy](https://github.com/XShengTech/MEGREZ-Deploy)
|
||||
>
|
||||
> 查看文档 [**>>> 🚧 正在施工中 <<<**]()
|
||||
|
||||
> 查看文档 [**>>> MEGREZ 文档 <<<**](http://docs.megrez.xsheng-ai.com/)
|
||||
|
||||
|
||||
## 📌 效果展示
|
||||
|
||||
### 视频演示
|
||||
|
||||
[Bilibili - MEGREZ——你的新一代开源GPU管理系统](https://www.bilibili.com/video/BV1EnfWY9ECC/)
|
||||
|
||||
### 登录注册
|
||||
|
||||
| 登录 | 注册 |
|
||||
@@ -76,9 +79,9 @@
|
||||
|
||||
### 内置功能
|
||||
|
||||
| VSCode 网页版 | Jupter Notebook | Grafana 资源监控 |
|
||||
| VSCode 网页版 | Jupyter Notebook | Grafana 资源监控 |
|
||||
| ---------------------------------------------------- | ----------------------------------------------- | -------------------------------- |
|
||||
|  |  |  |
|
||||
|  |  |  |
|
||||
|
||||
### 系统管理
|
||||
|
||||
|
||||
@@ -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 })
|
||||
@@ -90,6 +108,9 @@ export default {
|
||||
AdminInstancesDelete(id) {
|
||||
return ajax(`admin/instances/${id}`, 'delete', {})
|
||||
},
|
||||
AdminInstancesForceDelete(id) {
|
||||
return ajax(`admin/instances/${id}/force`, 'delete', {})
|
||||
},
|
||||
|
||||
AdminUserList(params) {
|
||||
return ajax('admin/users', 'get', { params })
|
||||
|
||||
@@ -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'
|
||||
// },
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -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,11 @@
|
||||
import AppLayout from '@/layout/AppLayout.vue';
|
||||
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';
|
||||
@@ -32,6 +35,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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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,12 +70,11 @@
|
||||
: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 + '/lab'" target="_blank" v-tooltip.top="'Jupter Lab'" />
|
||||
<Button v-else severity="info" icon="pi pi-inbox" aria-label="Filter" v-tooltip.top="'Jupter Lab'"
|
||||
:href="'http://' + data.jupyter_address" 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"
|
||||
:href="'http://' + data.grafana_address + '/public-dashboards/2c510f203876465ba76617510ce3e219'"
|
||||
target="_blank" v-tooltip.top="'监控'" />
|
||||
:href="'http://' + data.grafana_address" target="_blank" v-tooltip.top="'监控'" />
|
||||
<Button v-else severity="contrast" icon="pi pi-chart-bar" v-tooltip.top="'监控'" disabled />
|
||||
<Button v-if="!isAdmin" icon="pi pi-ellipsis-h" severity="secondary" aria-label="Bookmark"
|
||||
@click="showMenu($event, data)" />
|
||||
@@ -288,6 +287,7 @@ const instanceMenuItemsTemplate = ref([
|
||||
{ label: '重启实例', icon: 'pi pi-refresh !text-sky-500', command: () => { instanceRestart(instanceDetail.value.id) } },
|
||||
{ label: '调整配置', icon: 'pi pi-sliders-h !text-indigo-500', command: () => { openInstanceModify() } },
|
||||
{ label: '删除实例', icon: 'pi pi-trash !text-red-500', command: () => { openInstanceDelete() } },
|
||||
{ label: '强制删除', icon: 'pi pi-exclamation-triangle !text-red-500', command: () => { openInstanceForceDelete() } }
|
||||
])
|
||||
const instanceMenuItems = ref([])
|
||||
|
||||
@@ -376,37 +376,42 @@ const showMenu = (event, instance) => {
|
||||
let newItem = { ...item }
|
||||
switch (item.label) {
|
||||
case '无卡模式开机':
|
||||
if (instanceDetail.value.cpu_only === true || instanceDetail.value.status !== statusStoped.value) {
|
||||
if (instanceDetail.value.cpu_only === true || instanceDetail.value.status !== statusStoped.value || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '开机':
|
||||
if (instanceDetail.value.status === statusRunning.value || statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (instanceDetail.value.status === statusRunning.value || statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '关机':
|
||||
if (instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '暂停':
|
||||
if (instanceDetail.value.status === statusPaused.value || instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (instanceDetail.value.status === statusPaused.value || instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '重启实例':
|
||||
if (statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '调整配置':
|
||||
if (instanceDetail.value.status !== statusStoped.value) {
|
||||
if (instanceDetail.value.status !== statusStoped.value || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '删除实例':
|
||||
if (instanceDetail.value.status === statusDeleting.value) {
|
||||
if (instanceDetail.value.status === statusDeleting.value || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '强制删除':
|
||||
if (instanceDetail.value.status !== statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
@@ -493,6 +498,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
|
||||
@@ -517,6 +525,20 @@ const instanceDelete = async (id) => {
|
||||
})
|
||||
}
|
||||
|
||||
const instanceForceDelete = async (id) => {
|
||||
toast.add({ severity: 'info', summary: '强制释放实例', detail: '正在强制释放实例', life: 3000 });
|
||||
setTimeout(() => {
|
||||
getInstances()
|
||||
}, 100);
|
||||
await api.AdminInstancesForceDelete(id, { force: true }).then(async (res) => {
|
||||
toast.add({ severity: 'success', summary: '强制释放实例', detail: '实例已强制释放', life: 3000 });
|
||||
await getInstances()
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
toast.add({ severity: 'error', summary: '强制释放实例失败', detail: err.response.data.msg, life: 3000 });
|
||||
})
|
||||
}
|
||||
|
||||
const openInstanceModify = async () => {
|
||||
instanceConfiguration.value.gpu_count = instanceDetail.value.gpu_count
|
||||
instanceConfiguration.value.volume_size = instanceDetail.value.volume_size
|
||||
@@ -595,6 +617,27 @@ const openInstanceDelete = () => {
|
||||
});
|
||||
}
|
||||
|
||||
const openInstanceForceDelete = () => {
|
||||
confirm.require({
|
||||
header: '确认强制删除 实例ID: ' + instanceDetail.value.id,
|
||||
message: '强制实例删除后,数据将无法恢复,请确认删除',
|
||||
icon: 'pi pi-info-circle',
|
||||
rejectProps: {
|
||||
label: '取消',
|
||||
severity: 'secondary',
|
||||
outlined: true
|
||||
},
|
||||
acceptProps: {
|
||||
label: '强制删除',
|
||||
severity: 'danger'
|
||||
},
|
||||
accept: async () => {
|
||||
await instanceForceDelete(instanceDetail.value.id)
|
||||
},
|
||||
reject: () => { }
|
||||
});
|
||||
}
|
||||
|
||||
const instanceModifyLabel = (id, label) => {
|
||||
api.AdminInstancesModifyLabel(id, { label: label }).then(async (res) => {
|
||||
toast.add({ severity: 'success', summary: '修改备注成功', detail: '已保存备注', life: 3000 });
|
||||
|
||||
@@ -74,12 +74,11 @@
|
||||
: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 + '/lab'" target="_blank" v-tooltip.top="'Jupter Lab'" />
|
||||
<Button v-else severity="info" icon="pi pi-inbox" aria-label="Filter" v-tooltip.top="'Jupter Lab'"
|
||||
:href="'http://' + data.jupyter_address" 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"
|
||||
:href="'http://' + data.grafana_address + '/public-dashboards/2c510f203876465ba76617510ce3e219'"
|
||||
target="_blank" v-tooltip.top="'监控'" />
|
||||
:href="'http://' + data.grafana_address" target="_blank" v-tooltip.top="'监控'" />
|
||||
<Button v-else severity="contrast" icon="pi pi-chart-bar" v-tooltip.top="'监控'" disabled />
|
||||
<Button icon="pi pi-ellipsis-h" severity="secondary" aria-label="Bookmark"
|
||||
@click="showMenu($event, data)" />
|
||||
@@ -126,13 +125,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>
|
||||
@@ -386,37 +385,37 @@ const showMenu = (event, instance) => {
|
||||
let newItem = { ...item }
|
||||
switch (item.label) {
|
||||
case '无卡模式开机':
|
||||
if (instanceDetail.value.cpu_only === true || instanceDetail.value.status !== statusStoped.value) {
|
||||
if (instanceDetail.value.cpu_only === true || instanceDetail.value.status !== statusStoped.value || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '开机':
|
||||
if (instanceDetail.value.status === statusRunning.value || statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (instanceDetail.value.status === statusRunning.value || statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '关机':
|
||||
if (instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '暂停':
|
||||
if (instanceDetail.value.status === statusPaused.value || instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (instanceDetail.value.status === statusPaused.value || instanceDetail.value.status === statusStoped.value || statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '重启实例':
|
||||
if (statusIng.indexOf(instanceDetail.value.status) !== -1) {
|
||||
if (statusIng.indexOf(instanceDetail.value.status) !== -1 || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '调整配置':
|
||||
if (instanceDetail.value.status !== statusStoped.value) {
|
||||
if (instanceDetail.value.status !== statusStoped.value || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
case '删除实例':
|
||||
if (instanceDetail.value.status === statusDeleting.value) {
|
||||
if (instanceDetail.value.status === statusDeleting.value || instanceDetail.value.status === statusFail.value) {
|
||||
newItem.disabled = true
|
||||
}
|
||||
break
|
||||
@@ -502,6 +501,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>
|
||||
个人设置
|
||||
<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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+24
-12
@@ -8,23 +8,34 @@ import (
|
||||
)
|
||||
|
||||
type Status int
|
||||
type Action int
|
||||
|
||||
const (
|
||||
InstanceFail Status = -1
|
||||
InstanceRunning Status = 0
|
||||
InstancePaused Status = 1
|
||||
InstanceStopped Status = 2
|
||||
InstanceStatusFail Status = -1
|
||||
InstanceStatusRunning Status = 0
|
||||
InstanceStatusPaused Status = 1
|
||||
InstanceStatusStopped Status = 2
|
||||
|
||||
InstanceReady Status = 3
|
||||
InstanceStarting Status = 4
|
||||
InstanceStopping Status = 5
|
||||
InstancePausing Status = 6
|
||||
InstanceRestarting Status = 7
|
||||
InstanceModifying Status = 8
|
||||
InstanceDeleting Status = 9
|
||||
InstanceStatusReady Status = 3
|
||||
InstanceStatusStarting Status = 4
|
||||
InstanceStatusStopping Status = 5
|
||||
InstanceStatusPausing Status = 6
|
||||
InstanceStatusRestarting Status = 7
|
||||
InstanceStatusModifying Status = 8
|
||||
InstanceStatusDeleting Status = 9
|
||||
)
|
||||
|
||||
var instanceIngStatus = []Status{InstanceReady, InstanceStarting, InstanceStopping, InstancePausing, InstanceRestarting, InstanceModifying, InstanceDeleting}
|
||||
const (
|
||||
InstanceActionCreate Action = 1
|
||||
InstanceActionStart Action = 2
|
||||
InstanceActionPause Action = 3
|
||||
InstanceActionStop Action = 4
|
||||
InstanceActionRestart Action = 5
|
||||
InstanceActionModify Action = 6
|
||||
InstanceActionDelete Action = 7
|
||||
)
|
||||
|
||||
var instanceIngStatus = []Status{InstanceStatusReady, InstanceStatusStarting, InstanceStatusStopping, InstanceStatusPausing, InstanceStatusRestarting, InstanceStatusModifying, InstanceStatusDeleting}
|
||||
|
||||
type Instances struct {
|
||||
ID uint `json:"id" gorm:"primary_key;autoIncrement;index"`
|
||||
@@ -46,6 +57,7 @@ type Instances struct {
|
||||
GrafanaAddress string `json:"grafana_address" gorm:"type:varchar(255)"`
|
||||
CodeServerAddress string `json:"code_server_address" gorm:"type:varchar(255)"`
|
||||
Status Status `json:"status" gorm:"not null"` // Detail in Constants
|
||||
FromAction Action `json:"from_action"`
|
||||
|
||||
Label string `json:"label" gorm:"type:varchar(255)"`
|
||||
|
||||
|
||||
+2
-1
@@ -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"`
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func addHandler(ctx iris.Context) {
|
||||
|
||||
SshPasswd: crypto.Hex(16),
|
||||
|
||||
Status: models.InstanceReady,
|
||||
Status: models.InstanceStatusReady,
|
||||
}
|
||||
result = database.DB.Create(&instance)
|
||||
if result.Error != nil {
|
||||
|
||||
@@ -45,17 +45,17 @@ func controlHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Action == instanceController.ActionStop && instance.Status != models.InstanceRunning && instance.Status != models.InstancePaused {
|
||||
if req.Action == instanceController.ActionStop && instance.Status != models.InstanceStatusRunning && instance.Status != models.InstanceStatusPaused {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Action == instanceController.ActionPause && instance.Status != models.InstanceRunning {
|
||||
if req.Action == instanceController.ActionPause && instance.Status != models.InstanceStatusRunning {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Action == instanceController.ActionStart && instance.Status != models.InstanceStopped && instance.Status != models.InstancePaused {
|
||||
if req.Action == instanceController.ActionStart && instance.Status != models.InstanceStatusStopped && instance.Status != models.InstanceStatusPaused {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func controlHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
status := instance.Status
|
||||
if status == models.InstanceStopped && (req.Action == instanceController.ActionStart || req.Action == instanceController.ActionRestart) {
|
||||
if status == models.InstanceStatusStopped && (req.Action == instanceController.ActionStart || req.Action == instanceController.ActionRestart) {
|
||||
remainGpu, err := redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(server.ID)), int64(-instance.GpuCount)).Result()
|
||||
if err != nil {
|
||||
l.Error("incrby gpu num error: %v", err)
|
||||
@@ -88,7 +88,7 @@ func controlHandler(ctx iris.Context) {
|
||||
|
||||
switch req.Action {
|
||||
case instanceController.ActionStart:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStarting)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusStarting)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceStartError, iris.StatusInternalServerError)
|
||||
@@ -96,7 +96,7 @@ func controlHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
case instanceController.ActionPause:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstancePausing)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusPausing)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstancePauseError, iris.StatusInternalServerError)
|
||||
@@ -104,7 +104,7 @@ func controlHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
case instanceController.ActionStop:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStopping)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusStopping)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceStopError, iris.StatusInternalServerError)
|
||||
@@ -113,7 +113,7 @@ func controlHandler(ctx iris.Context) {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(server.ID)), int64(instance.GpuCount))
|
||||
|
||||
case instanceController.ActionRestart:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceRestarting)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusRestarting)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceRestartError, iris.StatusInternalServerError)
|
||||
|
||||
@@ -36,14 +36,14 @@ func deleteHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceDeleting)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusDeleting)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeServerSaveError, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if status == models.InstanceRunning || status == models.InstancePaused {
|
||||
if status == models.InstanceStatusRunning || status == models.InstanceStatusPaused {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.GpuCount))
|
||||
}
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.VolumeSize+30))
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"megrez/models"
|
||||
"megrez/routers/api/v1/middleware"
|
||||
"megrez/services/database"
|
||||
"megrez/services/dispatcher"
|
||||
"megrez/services/redis"
|
||||
"strconv"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
func forceDeleteHandler(ctx iris.Context) {
|
||||
l.SetFunction("forceDeleteHandler")
|
||||
|
||||
id, err := ctx.Params().GetUint("id")
|
||||
if err != nil {
|
||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
instance := models.Instances{
|
||||
ID: id,
|
||||
}
|
||||
result := database.DB.First(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("get instance error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceDeleteError, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
status := instance.Status
|
||||
if status != models.InstanceStatusFail {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if instance.FromAction == models.InstanceActionStop || instance.FromAction == models.InstanceActionPause || instance.FromAction == models.InstanceActionRestart {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.GpuCount))
|
||||
}
|
||||
if instance.FromAction != models.InstanceActionCreate {
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.VolumeSize+30))
|
||||
}
|
||||
|
||||
dispatcherData := dispatcher.Data{
|
||||
Type: dispatcher.Delete,
|
||||
Status: status,
|
||||
InstanceID: instance.ID,
|
||||
Force: true,
|
||||
}
|
||||
dispatcher.Push(instance.ServerID, dispatcherData)
|
||||
|
||||
middleware.Success(ctx)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -57,12 +61,22 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if instance.Status != models.InstanceStopped {
|
||||
if instance.Status != models.InstanceStatusStopped {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
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
|
||||
}
|
||||
@@ -106,7 +120,7 @@ func modifyHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
status := instance.Status
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceModifying)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusModifying)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeServerSaveError, iris.StatusInternalServerError)
|
||||
|
||||
@@ -34,4 +34,5 @@ func InitInstances(party router.Party) {
|
||||
party.Post("/{id:uint}", middleware.SuperAdminCheck, modifyHandler)
|
||||
party.Post("/{id:uint}/label", middleware.SuperAdminCheck, labelHandler)
|
||||
party.Delete("/{id:uint}", middleware.SuperAdminCheck, deleteHandler)
|
||||
party.Delete("/{id:uint}/force", middleware.SuperAdminCheck, forceDeleteHandler)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func addHandler(ctx iris.Context) {
|
||||
|
||||
SshPasswd: crypto.Hex(16),
|
||||
|
||||
Status: models.InstanceReady,
|
||||
Status: models.InstanceStatusReady,
|
||||
}
|
||||
result = database.DB.Create(&instance)
|
||||
if result.Error != nil {
|
||||
|
||||
@@ -51,17 +51,17 @@ func controlHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Action == instanceController.ActionStop && instance.Status != models.InstanceRunning && instance.Status != models.InstancePaused {
|
||||
if req.Action == instanceController.ActionStop && instance.Status != models.InstanceStatusRunning && instance.Status != models.InstanceStatusPaused {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Action == instanceController.ActionPause && instance.Status != models.InstanceRunning {
|
||||
if req.Action == instanceController.ActionPause && instance.Status != models.InstanceStatusRunning {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Action == instanceController.ActionStart && instance.Status != models.InstanceStopped && instance.Status != models.InstancePaused {
|
||||
if req.Action == instanceController.ActionStart && instance.Status != models.InstanceStatusStopped && instance.Status != models.InstanceStatusPaused {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func controlHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
status := instance.Status
|
||||
if status == models.InstanceStopped && (req.Action == instanceController.ActionStart || req.Action == instanceController.ActionRestart) {
|
||||
if status == models.InstanceStatusStopped && (req.Action == instanceController.ActionStart || req.Action == instanceController.ActionRestart) {
|
||||
remainGpu, err := redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(server.ID)), int64(-instance.GpuCount)).Result()
|
||||
if err != nil {
|
||||
l.Error("incrby gpu num error: %v", err)
|
||||
@@ -94,7 +94,7 @@ func controlHandler(ctx iris.Context) {
|
||||
|
||||
switch req.Action {
|
||||
case instanceController.ActionStart:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStarting)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusStarting)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceStartError, iris.StatusInternalServerError)
|
||||
@@ -102,7 +102,7 @@ func controlHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
case instanceController.ActionPause:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstancePausing)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusPausing)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstancePauseError, iris.StatusInternalServerError)
|
||||
@@ -110,7 +110,7 @@ func controlHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
case instanceController.ActionStop:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStopping)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusStopping)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceStopError, iris.StatusInternalServerError)
|
||||
@@ -119,7 +119,7 @@ func controlHandler(ctx iris.Context) {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(server.ID)), int64(instance.GpuCount))
|
||||
|
||||
case instanceController.ActionRestart:
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceRestarting)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusRestarting)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeInstanceRestartError, iris.StatusInternalServerError)
|
||||
|
||||
@@ -40,14 +40,14 @@ func deleteHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceDeleting)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusDeleting)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeServerSaveError, iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if status == models.InstanceRunning || status == models.InstancePaused {
|
||||
if status == models.InstanceStatusRunning || status == models.InstanceStatusPaused {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.GpuCount))
|
||||
}
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(instance.ServerID)), int64(instance.VolumeSize+30))
|
||||
|
||||
@@ -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)
|
||||
@@ -63,12 +67,22 @@ func modifyHandler(ctx iris.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if instance.Status != models.InstanceStopped {
|
||||
if instance.Status != models.InstanceStatusStopped {
|
||||
middleware.Error(ctx, middleware.CodeInstanceStatusError, iris.StatusBadRequest)
|
||||
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
|
||||
}
|
||||
@@ -112,7 +126,7 @@ func modifyHandler(ctx iris.Context) {
|
||||
}
|
||||
|
||||
status := instance.Status
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceModifying)
|
||||
result = database.DB.Model(&instance).Update("status", models.InstanceStatusModifying)
|
||||
if result.Error != nil {
|
||||
l.Error("update instance status error: %v", result.Error)
|
||||
middleware.Error(ctx, middleware.CodeServerSaveError, iris.StatusInternalServerError)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package index
|
||||
|
||||
import "github.com/kataras/iris/v12"
|
||||
|
||||
func cors(ctx iris.Context) {
|
||||
ctx.Header("Access-Control-Allow-Origin", "*")
|
||||
ctx.Header("Access-Control-Allow-Methods", "GET")
|
||||
ctx.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
ctx.Header("Access-Control-Max-Age", "86400")
|
||||
ctx.Header("Access-Control-Allow-Credentials", "true")
|
||||
ctx.Next()
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
)
|
||||
|
||||
func InitIndex(app *iris.Application) {
|
||||
app.Use(cors)
|
||||
app.HandleDir("/", GetWebFS(), iris.DirOptions{
|
||||
IndexName: "/index.html",
|
||||
Compress: true,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func add(serverID uint, data Data) (err error) {
|
||||
ctx := context.Background()
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(instance.GpuCount))
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(serverID)), int64(instance.VolumeSize+30))
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionCreate)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -37,35 +37,37 @@ func control(serverID uint, data Data) (err error) {
|
||||
return errors.New("instance status error")
|
||||
}
|
||||
|
||||
if data.Action == instanceController.ActionPause && data.Status != models.InstanceRunning {
|
||||
if data.Action == instanceController.ActionPause && data.Status != models.InstanceStatusRunning {
|
||||
lc.Error("instance status error")
|
||||
return errors.New("instance status error")
|
||||
}
|
||||
|
||||
if data.Action == instanceController.ActionStart && data.Status != models.InstanceStopped && data.Status != models.InstancePaused {
|
||||
if data.Action == instanceController.ActionStart && data.Status != models.InstanceStatusStopped && data.Status != models.InstanceStatusPaused {
|
||||
lc.Error("instance status error")
|
||||
return errors.New("instance status error")
|
||||
}
|
||||
|
||||
switch data.Action {
|
||||
case instanceController.ActionStart:
|
||||
if data.Status == models.InstancePaused {
|
||||
if data.Status == models.InstanceStatusPaused {
|
||||
err = instanceController.Continue(&instance)
|
||||
if err != nil {
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionStart)
|
||||
lc.Error("instance continue error: %v", err)
|
||||
return
|
||||
}
|
||||
} else if data.Status == models.InstanceStopped {
|
||||
} else if data.Status == models.InstanceStatusStopped {
|
||||
err = instanceController.Restart(&instance)
|
||||
if err != nil {
|
||||
lc.Error("instance restart error: %v", err)
|
||||
ctx := context.Background()
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(instance.GpuCount))
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionStart)
|
||||
lc.Error("instance restart error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if data.Status == models.InstanceStopped && instance.Status == models.InstanceRunning {
|
||||
if data.Status == models.InstanceStatusStopped && instance.Status == models.InstanceStatusRunning {
|
||||
server.GpuUsed += instance.GpuCount
|
||||
result = database.DB.Save(&server)
|
||||
if result.Error != nil {
|
||||
@@ -79,6 +81,7 @@ func control(serverID uint, data Data) (err error) {
|
||||
case instanceController.ActionPause:
|
||||
err = instanceController.Pause(&instance)
|
||||
if err != nil {
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionPause)
|
||||
lc.Error("instance pause error: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -88,14 +91,16 @@ func control(serverID uint, data Data) (err error) {
|
||||
case instanceController.ActionStop:
|
||||
err = instanceController.Stop(&instance)
|
||||
if err != nil {
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionStop)
|
||||
lc.Error("instance stop error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if (data.Status == models.InstanceRunning || data.Status == models.InstancePaused) && instance.Status == models.InstanceStopped {
|
||||
if (data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused) && instance.Status == models.InstanceStatusStopped {
|
||||
server.GpuUsed -= instance.GpuCount
|
||||
result = database.DB.Save(&server)
|
||||
if result.Error != nil {
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail)
|
||||
lc.Error("save server error: %v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
@@ -106,14 +111,16 @@ func control(serverID uint, data Data) (err error) {
|
||||
case instanceController.ActionRestart:
|
||||
err = instanceController.Restart(&instance)
|
||||
if err != nil {
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionRestart)
|
||||
lc.Error("instance restart error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if data.Status == models.InstanceStopped && instance.Status == models.InstanceRunning {
|
||||
if data.Status == models.InstanceStatusStopped && instance.Status == models.InstanceStatusRunning {
|
||||
server.GpuUsed += instance.GpuCount
|
||||
result = database.DB.Save(&server)
|
||||
if result.Error != nil {
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail)
|
||||
lc.Error("save server error: %v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
@@ -34,25 +34,47 @@ func delete(serverID uint, data Data) (err error) {
|
||||
err = instanceController.Delete(&instance)
|
||||
if err != nil {
|
||||
lc.Error("delete instance error: %v", err)
|
||||
ctx := context.Background()
|
||||
if data.Status == models.InstanceRunning || data.Status == models.InstancePaused {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(-instance.GpuCount))
|
||||
if !data.Force {
|
||||
ctx := context.Background()
|
||||
if data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused {
|
||||
redis.RawDB.IncrBy(ctx, "remain_gpu:server:"+strconv.Itoa(int(serverID)), int64(-instance.GpuCount))
|
||||
}
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(serverID)), int64(-instance.VolumeSize-30))
|
||||
database.DB.Model(&instance).Update("status", models.InstanceStatusFail).Update("from_action", models.InstanceActionDelete)
|
||||
return
|
||||
}
|
||||
redis.RawDB.IncrBy(ctx, "remain_volume:server:"+strconv.Itoa(int(serverID)), int64(-instance.VolumeSize-30))
|
||||
}
|
||||
|
||||
if !data.Force {
|
||||
server.VolumeUsed -= instance.VolumeSize + 30
|
||||
if data.Status == models.InstanceStatusRunning || data.Status == models.InstanceStatusPaused {
|
||||
server.GpuUsed -= instance.GpuCount
|
||||
}
|
||||
result = database.DB.Save(&server)
|
||||
if result.Error != nil {
|
||||
lc.Error("save server error: %v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
lc.Info("delete instance success: %v", instance.ID)
|
||||
} else {
|
||||
if instance.FromAction != models.InstanceActionCreate {
|
||||
server.VolumeUsed -= instance.VolumeSize + 30
|
||||
}
|
||||
if instance.FromAction == models.InstanceActionStop || instance.FromAction == models.InstanceActionPause || instance.FromAction == models.InstanceActionRestart {
|
||||
server.GpuUsed -= instance.GpuCount
|
||||
}
|
||||
result = database.DB.Save(&server)
|
||||
if result.Error != nil {
|
||||
lc.Error("save server error: %v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
result = database.DB.Delete(&instance)
|
||||
if result.Error != nil {
|
||||
lc.Error("force delete instance error: %v", result.Error)
|
||||
}
|
||||
lc.Info("force delete instance success: %v", instance.ID)
|
||||
return
|
||||
}
|
||||
|
||||
server.VolumeUsed -= instance.VolumeSize + 30
|
||||
if data.Status == models.InstanceRunning || data.Status == models.InstancePaused {
|
||||
server.GpuUsed -= instance.GpuCount
|
||||
}
|
||||
result = database.DB.Save(&server)
|
||||
if result.Error != nil {
|
||||
lc.Error("save server error: %v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
lc.Info("delete instance success: %v", instance.ID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type Data struct {
|
||||
VolumeSize *int `json:"volume_size,omitempty"`
|
||||
|
||||
Action instanceController.Action `json:"action,omitempty"`
|
||||
Force bool `json:"force,omitempty"`
|
||||
}
|
||||
|
||||
type Type int
|
||||
|
||||
@@ -38,14 +38,24 @@ func modify(serverID uint, data Data) (err error) {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if data.Status != models.InstanceStopped {
|
||||
if data.Status != models.InstanceStatusStopped {
|
||||
lc.Error("instance status 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
|
||||
@@ -60,14 +70,14 @@ func modify(serverID uint, data Data) (err error) {
|
||||
|
||||
err = instanceController.Patch(&instance, gpuCount, volumeSize, data.CpuOnly)
|
||||
if err != nil {
|
||||
lc.Error("patch instance error: %v", err)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
if data.Status == models.InstanceStopped {
|
||||
if data.Status == models.InstanceStatusStopped {
|
||||
server.GpuUsed += instance.GpuCount
|
||||
}
|
||||
server.VolumeUsed += instance.VolumeSize - oldVolumeSize
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func Continue(instance *models.Instances) (err error) {
|
||||
l.SetFunction("Start")
|
||||
|
||||
instance.Status = models.InstanceStarting
|
||||
instance.Status = models.InstanceStatusStarting
|
||||
result := database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
@@ -34,7 +34,7 @@ func Continue(instance *models.Instances) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
instance.Status = models.InstanceRunning
|
||||
instance.Status = models.InstanceStatusRunning
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
|
||||
@@ -48,7 +48,7 @@ func Create(instance *models.Instances) (containerName, volumeName string, err e
|
||||
}
|
||||
|
||||
go func() {
|
||||
SetJupterPassword(server.IP, server.Port, server.Apikey, containerName, instance.SshPasswd)
|
||||
SetJupyterPassword(server.IP, server.Port, server.Apikey, containerName, instance.SshPasswd)
|
||||
SetCodeServerPassword(server.IP, server.Port, server.Apikey, containerName, instance.SshPasswd)
|
||||
}()
|
||||
|
||||
@@ -64,9 +64,9 @@ func Create(instance *models.Instances) (containerName, volumeName string, err e
|
||||
|
||||
instance.SshAddress = server.IP + ":" + portBindings["22"]
|
||||
instance.TensorBoardAddress = server.IP + ":" + portBindings["6007"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["8888"]
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["3000"]
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["8080"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["80"] + "/jupyter"
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["80"] + "/monitor/public-dashboards/2c510f203876465ba76617510ce3e219"
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["80"] + "/code-server/"
|
||||
|
||||
instance.Status = 0
|
||||
result = database.DB.Save(&instance)
|
||||
|
||||
@@ -40,14 +40,13 @@ func createInstance(ip string, port int, apikey string,
|
||||
Memory: strconv.Itoa(memorySize) + "GB",
|
||||
ContainerPorts: []string{
|
||||
"22", // SSH
|
||||
"80", // Nginx
|
||||
"6007", // TensorBoard
|
||||
"8888", // Jupyter Notebook
|
||||
"3000", // Grafana
|
||||
"8080", // Code-Server
|
||||
"34567", // Custom Port
|
||||
},
|
||||
Env: []string{
|
||||
"NVIDIA_DRIVER_CAPABILITIES=video,compute,utility",
|
||||
"NVIDIA_VISIBLE_DEVICES=none",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -63,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",
|
||||
Dest: "/root/megrez-mnt",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
func Delete(instance *models.Instances) (err error) {
|
||||
l.SetFunction("Delete")
|
||||
|
||||
instance.Status = models.InstanceDeleting
|
||||
instance.Status = models.InstanceStatusDeleting
|
||||
result := database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
|
||||
@@ -57,9 +57,9 @@ func SetRootPassword(ip string, port int, apikey string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetJupterPassword(ip string, port int, apikey string,
|
||||
func SetJupyterPassword(ip string, port int, apikey string,
|
||||
containerName, password string) (err error) {
|
||||
l.SetFunction("SetJupterPassword")
|
||||
l.SetFunction("SetJupyterPassword")
|
||||
|
||||
// Set Jupyter Password
|
||||
data := executeReq{
|
||||
@@ -85,8 +85,8 @@ func SetJupterPassword(ip string, port int, apikey string,
|
||||
c.Do()
|
||||
|
||||
if c.GetStatusCode() != 200 {
|
||||
l.Error("set jupter password error: %d", c.GetStatusCode())
|
||||
return errors.New("set jupter password request error")
|
||||
l.Error("set jupyter password error: %d", c.GetStatusCode())
|
||||
return errors.New("set jupyter password request error")
|
||||
}
|
||||
|
||||
var res resStruct
|
||||
@@ -97,7 +97,7 @@ func SetJupterPassword(ip string, port int, apikey string,
|
||||
}
|
||||
|
||||
if res.Code != 200 {
|
||||
l.Error("set jupter password code: %d, error: %s", res.Code, res.Msg)
|
||||
l.Error("set jupyter password code: %d, error: %s", res.Code, res.Msg)
|
||||
return errors.New(res.Msg)
|
||||
}
|
||||
|
||||
@@ -124,8 +124,8 @@ func SetJupterPassword(ip string, port int, apikey string,
|
||||
c.Do()
|
||||
|
||||
if c.GetStatusCode() != 200 {
|
||||
l.Error("restart jupter error: %d", c.GetStatusCode())
|
||||
return errors.New("restart jupter request error")
|
||||
l.Error("restart jupyter error: %d", c.GetStatusCode())
|
||||
return errors.New("restart jupyter request error")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(c.GetBody(), &res)
|
||||
@@ -135,7 +135,7 @@ func SetJupterPassword(ip string, port int, apikey string,
|
||||
}
|
||||
|
||||
if res.Code != 200 {
|
||||
l.Error("restart jupter code: %d, error: %s", res.Code, res.Msg)
|
||||
l.Error("restart jupyter code: %d, error: %s", res.Code, res.Msg)
|
||||
return errors.New(res.Msg)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ type volumePatchStruct struct {
|
||||
func Patch(instance *models.Instances, gpuCount, volumeSize int, cpuOnly bool) (err error) {
|
||||
l.SetFunction("Patch")
|
||||
|
||||
instance.Status = models.InstanceModifying
|
||||
instance.Status = models.InstanceStatusModifying
|
||||
result := database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
@@ -42,7 +42,7 @@ func Patch(instance *models.Instances, gpuCount, volumeSize int, cpuOnly bool) (
|
||||
}
|
||||
|
||||
if gpuCount == instance.GpuCount && volumeSize == instance.VolumeSize && cpuOnly == instance.CpuOnly {
|
||||
instance.Status = models.InstanceStopped
|
||||
instance.Status = models.InstanceStatusStopped
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
@@ -87,16 +87,12 @@ func Patch(instance *models.Instances, gpuCount, volumeSize int, cpuOnly bool) (
|
||||
|
||||
err = SetRootPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
if err != nil {
|
||||
deleteInstance(server.IP, server.Port, server.Apikey, instance.ContainerName)
|
||||
if instance.VolumeName != "" {
|
||||
deleteVolume(server.IP, server.Port, server.Apikey, instance.VolumeName, false)
|
||||
}
|
||||
l.Error("set root password error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
SetJupterPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
SetJupyterPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
SetCodeServerPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
}()
|
||||
|
||||
@@ -108,12 +104,13 @@ func Patch(instance *models.Instances, gpuCount, volumeSize int, cpuOnly bool) (
|
||||
|
||||
instance.SshAddress = server.IP + ":" + portBindings["22"]
|
||||
instance.TensorBoardAddress = server.IP + ":" + portBindings["6007"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["8888"]
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["3000"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["80"] + "/jupyter"
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["80"] + "/monitor/public-dashboards/2c510f203876465ba76617510ce3e219"
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["80"] + "/code-server/"
|
||||
|
||||
instance.CpuOnly = true
|
||||
instance.GpuCount = 0
|
||||
instance.Status = models.InstanceRunning
|
||||
instance.Status = models.InstanceStatusRunning
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
@@ -135,16 +132,12 @@ func Patch(instance *models.Instances, gpuCount, volumeSize int, cpuOnly bool) (
|
||||
|
||||
err = SetRootPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
if err != nil {
|
||||
deleteInstance(server.IP, server.Port, server.Apikey, instance.ContainerName)
|
||||
if instance.VolumeName != "" {
|
||||
deleteVolume(server.IP, server.Port, server.Apikey, instance.VolumeName, false)
|
||||
}
|
||||
l.Error("set root password error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
SetJupterPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
SetJupyterPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
SetCodeServerPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
}()
|
||||
|
||||
@@ -156,13 +149,13 @@ func Patch(instance *models.Instances, gpuCount, volumeSize int, cpuOnly bool) (
|
||||
|
||||
instance.SshAddress = server.IP + ":" + portBindings["22"]
|
||||
instance.TensorBoardAddress = server.IP + ":" + portBindings["6007"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["8888"]
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["3000"]
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["8080"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["80"] + "/jupyter"
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["80"] + "/monitor/public-dashboards/2c510f203876465ba76617510ce3e219"
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["80"] + "/code-server/"
|
||||
|
||||
instance.CpuOnly = false
|
||||
instance.GpuCount = gpuCount
|
||||
instance.Status = models.InstanceRunning
|
||||
instance.Status = models.InstanceStatusRunning
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func Pause(instance *models.Instances) (err error) {
|
||||
l.SetFunction("Pause")
|
||||
|
||||
instance.Status = models.InstancePausing
|
||||
instance.Status = models.InstanceStatusPausing
|
||||
result := database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
@@ -34,7 +34,7 @@ func Pause(instance *models.Instances) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
instance.Status = models.InstancePaused
|
||||
instance.Status = models.InstanceStatusPaused
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
func Restart(instance *models.Instances) (err error) {
|
||||
l.SetFunction("Restart")
|
||||
|
||||
if instance.Status == models.InstanceStopped || instance.Status == models.InstancePaused {
|
||||
instance.Status = models.InstanceStarting
|
||||
if instance.Status == models.InstanceStatusStopped || instance.Status == models.InstanceStatusPaused {
|
||||
instance.Status = models.InstanceStatusStarting
|
||||
} else {
|
||||
instance.Status = models.InstanceRestarting
|
||||
instance.Status = models.InstanceStatusRestarting
|
||||
}
|
||||
result := database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
@@ -40,16 +40,12 @@ func Restart(instance *models.Instances) (err error) {
|
||||
|
||||
err = SetRootPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
if err != nil {
|
||||
deleteInstance(server.IP, server.Port, server.Apikey, instance.ContainerName)
|
||||
if instance.VolumeName != "" {
|
||||
deleteVolume(server.IP, server.Port, server.Apikey, instance.VolumeName, false)
|
||||
}
|
||||
l.Error("set root password error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
SetJupterPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
SetJupyterPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
SetCodeServerPassword(server.IP, server.Port, server.Apikey, instance.ContainerName, instance.SshPasswd)
|
||||
}()
|
||||
|
||||
@@ -61,11 +57,11 @@ func Restart(instance *models.Instances) (err error) {
|
||||
|
||||
instance.SshAddress = server.IP + ":" + portBindings["22"]
|
||||
instance.TensorBoardAddress = server.IP + ":" + portBindings["6007"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["8888"]
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["3000"]
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["8080"]
|
||||
instance.JupyterAddress = server.IP + ":" + portBindings["80"] + "/jupyter"
|
||||
instance.GrafanaAddress = server.IP + ":" + portBindings["80"] + "/monitor/public-dashboards/2c510f203876465ba76617510ce3e219"
|
||||
instance.CodeServerAddress = server.IP + ":" + portBindings["80"] + "/code-server/"
|
||||
|
||||
instance.Status = models.InstanceRunning
|
||||
instance.Status = models.InstanceStatusRunning
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func Stop(instance *models.Instances) (err error) {
|
||||
l.SetFunction("Stop")
|
||||
|
||||
instance.Status = models.InstanceStopping
|
||||
instance.Status = models.InstanceStatusStopping
|
||||
result := database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
@@ -34,7 +34,7 @@ func Stop(instance *models.Instances) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
instance.Status = models.InstanceStopped
|
||||
instance.Status = models.InstanceStatusStopped
|
||||
result = database.DB.Save(&instance)
|
||||
if result.Error != nil {
|
||||
l.Error("save instance error: %v", result.Error)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"megrez/services/database"
|
||||
)
|
||||
|
||||
const imagesKey = "images"
|
||||
|
||||
func systemInit() (err error) {
|
||||
l.SetFunction("systemInit")
|
||||
|
||||
@@ -22,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 {
|
||||
@@ -35,6 +38,16 @@ func systemInit() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
st := models.System{
|
||||
Key: imagesKey,
|
||||
Value: "{}",
|
||||
}
|
||||
result = database.DB.Create(&st)
|
||||
if result.Error != nil {
|
||||
l.Error("Create system failed, Error: %v", result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
l.Info("System init success")
|
||||
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user