mirror of
https://github.com/XShengTech/MEGREZ.git
synced 2026-05-03 13:02:38 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bf6a58e7f | |||
| 69f91dba02 | |||
| 2a18684162 | |||
| e6f81e19dc | |||
| 05502938ba | |||
| 7fc2c3ec09 | |||
| 011827f5c7 | |||
| 69554b5e39 | |||
| a24fb8e8ad | |||
| 42de9caf52 | |||
| 5780f84714 | |||
| de8c601be9 | |||
| e283a626a1 | |||
| 37ba1baf71 | |||
| 81b52e80b5 | |||
| 13bd350274 |
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.23.6-alpine AS builder
|
FROM golang:1.24-alpine AS builder
|
||||||
LABEL stage=gobuilder \
|
LABEL stage=gobuilder \
|
||||||
mainatiner=https://github.com/XShengTech/MEGREZ
|
mainatiner=https://github.com/XShengTech/MEGREZ
|
||||||
|
|
||||||
@@ -29,6 +29,9 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY --from=builder /build/megrez /app/megrez
|
COPY --from=builder /build/megrez /app/megrez
|
||||||
|
|
||||||
|
COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip /opt/zoneinfo.zip
|
||||||
|
ENV ZONEINFO=/opt/zoneinfo.zip
|
||||||
|
|
||||||
EXPOSE 34567
|
EXPOSE 34567
|
||||||
|
|
||||||
ENTRYPOINT ["./megrez"]
|
ENTRYPOINT ["./megrez"]
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 部署仓库: [XShengTech/MEGREZ-Deploy](https://github.com/XShengTech/MEGREZ-Deploy)
|
> 部署仓库: [XShengTech/MEGREZ-Deploy](https://github.com/XShengTech/MEGREZ-Deploy)
|
||||||
>
|
>
|
||||||
> 查看文档 [**>>> 🚧 正在施工中 <<<**]()
|
> 查看文档 [**>>> MEGREZ 文档 <<<**](http://docs.megrez.xsheng-ai.com/)
|
||||||
|
|
||||||
|
|
||||||
## 📌 效果展示
|
## 📌 效果展示
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
| VSCode 网页版 | Jupyter Notebook | Grafana 资源监控 |
|
| VSCode 网页版 | Jupyter Notebook | Grafana 资源监控 |
|
||||||
| ---------------------------------------------------- | ----------------------------------------------- | -------------------------------- |
|
| ---------------------------------------------------- | ----------------------------------------------- | -------------------------------- |
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
### 系统管理
|
### 系统管理
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useLayout } from '@/layout/composables/layout';
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
import AppAdminSidebar from './AppAdminSidebar.vue';
|
||||||
|
import AppFooter from './AppFooter.vue';
|
||||||
|
import AppTopbar from './AppTopbar.vue';
|
||||||
|
|
||||||
|
import api from '@/api';
|
||||||
|
import { useProfileStore } from '@/stores/profile';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const profileStore = useProfileStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const { layoutConfig, layoutState, isSidebarActive, resetMenu } = useLayout();
|
||||||
|
|
||||||
|
const outsideClickListener = ref(null);
|
||||||
|
|
||||||
|
watch(isSidebarActive, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
bindOutsideClickListener();
|
||||||
|
} else {
|
||||||
|
unbindOutsideClickListener();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerClass = computed(() => {
|
||||||
|
return {
|
||||||
|
'layout-overlay': layoutConfig.menuMode === 'overlay',
|
||||||
|
'layout-static': layoutConfig.menuMode === 'static',
|
||||||
|
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
|
||||||
|
'layout-overlay-active': layoutState.overlayMenuActive,
|
||||||
|
'layout-mobile-active': layoutState.staticMenuMobileActive
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function bindOutsideClickListener() {
|
||||||
|
if (!outsideClickListener.value) {
|
||||||
|
outsideClickListener.value = (event) => {
|
||||||
|
if (isOutsideClicked(event)) {
|
||||||
|
resetMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', outsideClickListener.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unbindOutsideClickListener() {
|
||||||
|
if (outsideClickListener.value) {
|
||||||
|
document.removeEventListener('click', outsideClickListener);
|
||||||
|
outsideClickListener.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOutsideClicked(event) {
|
||||||
|
const sidebarEl = document.querySelector('.layout-sidebar');
|
||||||
|
const topbarEl = document.querySelector('.layout-menu-button');
|
||||||
|
|
||||||
|
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
api.GetUserProfile().then(res => {
|
||||||
|
profileStore.setUserProfile(res.data.data.result)
|
||||||
|
}).catch(_ => {
|
||||||
|
toast.add({ severity: 'error', summary: '登录过期,请重新登录', life: 3000 })
|
||||||
|
profileStore.clearUserProfile()
|
||||||
|
router.push('/login')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-wrapper" :class="containerClass">
|
||||||
|
<app-topbar></app-topbar>
|
||||||
|
<app-admin-sidebar></app-admin-sidebar>
|
||||||
|
<div class="layout-main-container">
|
||||||
|
<div class="layout-main">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
<app-footer></app-footer>
|
||||||
|
</div>
|
||||||
|
<div class="layout-mask animate-fadein"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useProfileStore } from '@/stores/profile';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import AppMenuItem from './AppMenuItem.vue';
|
||||||
|
|
||||||
|
const profileStore = useProfileStore();
|
||||||
|
|
||||||
|
|
||||||
|
const model = ref([]);
|
||||||
|
|
||||||
|
const defaultModel = ref({
|
||||||
|
label: '用户中心',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: '实例管理',
|
||||||
|
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||||
|
to: '/'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const adminModel = ref({
|
||||||
|
label: '系统设置',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: '实例管理',
|
||||||
|
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||||
|
to: '/admin/instances'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户管理',
|
||||||
|
icon: 'pi pi-fw pi-users text-indigo-500',
|
||||||
|
to: '/admin/users'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const superAdminModel = ref({
|
||||||
|
label: '系统设置',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: '节点管理',
|
||||||
|
icon: 'pi pi-fw pi-server text-yellow-400',
|
||||||
|
to: '/admin/servers'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '实例管理',
|
||||||
|
icon: 'pi pi-fw pi-desktop text-lime-500',
|
||||||
|
to: '/admin/instances'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户管理',
|
||||||
|
icon: 'pi pi-fw pi-users text-indigo-500',
|
||||||
|
to: '/admin/users'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '镜像管理',
|
||||||
|
icon: 'pi pi-fw pi-images text-teal-500',
|
||||||
|
to: '/admin/images'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const superAdmin = profileStore.isSuperAdmin
|
||||||
|
const admin = profileStore.isAdmin
|
||||||
|
|
||||||
|
if (superAdmin) {
|
||||||
|
model.value.push(superAdminModel.value)
|
||||||
|
} else if (admin) {
|
||||||
|
model.value.push(adminModel.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
model.value.push(defaultModel.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul class="layout-menu">
|
||||||
|
<template v-for="(item, i) in model" :key="item">
|
||||||
|
<app-menu-item v-if="!item.separator" :item="item" :index="i"></app-menu-item>
|
||||||
|
<li v-if="item.separator" class="menu-separator"></li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
import AppAdminMenu from './AppAdminMenu.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-sidebar">
|
||||||
|
<app-admin-menu></app-admin-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -56,15 +56,9 @@ const model = ref([
|
|||||||
{
|
{
|
||||||
label: '使用文档',
|
label: '使用文档',
|
||||||
icon: 'pi pi-fw pi-book text-amber-500',
|
icon: 'pi pi-fw pi-book text-amber-500',
|
||||||
url: '#',
|
url: 'http://docs.megrez.xsheng-ai.com/guide/usage/',
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// label: '开源信息',
|
|
||||||
// icon: 'pi pi-fw pi-cog',
|
|
||||||
// url: 'https://github.com/primefaces/sakai-vue',
|
|
||||||
// target: '_blank'
|
|
||||||
// },
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -73,15 +67,10 @@ const adminModel = ref({
|
|||||||
label: '系统设置',
|
label: '系统设置',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: '实例管理',
|
label: '管理后台',
|
||||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
icon: 'pi pi-fw pi-sliders-h text-yellow-500',
|
||||||
to: '/admin/instances'
|
to: '/admin/instances'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '用户管理',
|
|
||||||
icon: 'pi pi-fw pi-users text-indigo-500',
|
|
||||||
to: '/admin/users'
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -89,25 +78,10 @@ const superAdminModel = ref({
|
|||||||
label: '系统设置',
|
label: '系统设置',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: '节点管理',
|
label: '管理后台',
|
||||||
icon: 'pi pi-fw pi-server text-yellow-400',
|
icon: 'pi pi-fw pi-sliders-h text-yellow-400',
|
||||||
to: '/admin/servers'
|
to: '/admin/servers'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '实例管理',
|
|
||||||
icon: 'pi pi-fw pi-desktop text-lime-500',
|
|
||||||
to: '/admin/instances'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '镜像管理',
|
|
||||||
icon: 'pi pi-fw pi-images text-teal-500',
|
|
||||||
to: '/admin/images'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '用户管理',
|
|
||||||
icon: 'pi pi-fw pi-users text-indigo-500',
|
|
||||||
to: '/admin/users'
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Menu ref="profileMenu" :model="instanceMenuItems" :popup="true" @blur="profileMenuActive = false" />
|
<Menu ref="profileMenu" :model="profileMenuItems" :popup="true" @blur="profileMenuActive = false" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ const username = ref('');
|
|||||||
|
|
||||||
const profileMenu = ref(null);
|
const profileMenu = ref(null);
|
||||||
const profileMenuActive = ref(false);
|
const profileMenuActive = ref(false);
|
||||||
const instanceMenuItems = [
|
const profileMenuItems = ref([
|
||||||
{
|
{
|
||||||
label: '个人信息',
|
label: '个人信息',
|
||||||
icon: 'pi pi-user !text-cyan-500',
|
icon: 'pi pi-user !text-cyan-500',
|
||||||
@@ -78,6 +78,12 @@ const instanceMenuItems = [
|
|||||||
router.push('/settings');
|
router.push('/settings');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const profileExitItems = ref([
|
||||||
{
|
{
|
||||||
label: '退出登录',
|
label: '退出登录',
|
||||||
icon: 'pi pi-sign-out',
|
icon: 'pi pi-sign-out',
|
||||||
@@ -86,7 +92,35 @@ const instanceMenuItems = [
|
|||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
|
||||||
|
const profileAdminItems = ref([
|
||||||
|
{
|
||||||
|
label: '管理后台',
|
||||||
|
icon: 'pi pi-sliders-h !text-yellow-400',
|
||||||
|
command: () => {
|
||||||
|
profileMenu.value.hide();
|
||||||
|
router.push('/admin/instances');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const profileSuperadminItems = ref([
|
||||||
|
{
|
||||||
|
label: '管理后台',
|
||||||
|
icon: 'pi pi-sliders-h !text-yellow-400',
|
||||||
|
command: () => {
|
||||||
|
profileMenu.value.hide();
|
||||||
|
router.push('/admin/servers');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
const showMenu = (event) => {
|
const showMenu = (event) => {
|
||||||
profileMenu.value.show(event);
|
profileMenu.value.show(event);
|
||||||
@@ -105,6 +139,14 @@ const logout = () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
username.value = profileStore.username;
|
username.value = profileStore.username;
|
||||||
|
|
||||||
|
if (profileStore.isSuperAdmin) {
|
||||||
|
profileMenuItems.value.push(...profileSuperadminItems.value);
|
||||||
|
} else if (profileStore.isAdmin) {
|
||||||
|
profileMenuItems.value.push(...profileAdminItems.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
profileMenuItems.value.push(...profileExitItems.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -4,10 +4,36 @@ const layoutConfig = reactive({
|
|||||||
preset: 'Aura',
|
preset: 'Aura',
|
||||||
primary: 'blue',
|
primary: 'blue',
|
||||||
surface: null,
|
surface: null,
|
||||||
darkTheme: false,
|
darkTheme: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches,
|
||||||
menuMode: 'static'
|
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({
|
const layoutState = reactive({
|
||||||
staticMenuDesktopInactive: false,
|
staticMenuDesktopInactive: false,
|
||||||
overlayMenuActive: false,
|
overlayMenuActive: false,
|
||||||
@@ -39,21 +65,6 @@ export function useLayout() {
|
|||||||
layoutConfig.menuMode = mode;
|
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 = () => {
|
const onMenuToggle = () => {
|
||||||
if (layoutConfig.menuMode === 'overlay') {
|
if (layoutConfig.menuMode === 'overlay') {
|
||||||
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
|
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AppLayout from '@/layout/AppLayout.vue';
|
import AppLayout from '@/layout/AppLayout.vue';
|
||||||
|
import { useProfileStore } from '@/stores/profile';
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
import Forget from '@/views/Forget.vue';
|
import Forget from '@/views/Forget.vue';
|
||||||
@@ -16,6 +17,7 @@ import Instances from '@/views/admin/Instances.vue';
|
|||||||
import Servers from '@/views/admin/Servers.vue';
|
import Servers from '@/views/admin/Servers.vue';
|
||||||
import Users from '@/views/admin/Users.vue';
|
import Users from '@/views/admin/Users.vue';
|
||||||
|
|
||||||
|
import AppAdminLayout from '@/layout/AppAdminLayout.vue';
|
||||||
import NotFound from '@/views/NotFound.vue';
|
import NotFound from '@/views/NotFound.vue';
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -69,24 +71,31 @@ const router = createRouter({
|
|||||||
path: 'settings',
|
path: 'settings',
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
component: Settings
|
component: Settings
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/',
|
||||||
|
name: 'admin-dashboard',
|
||||||
|
component: AppAdminLayout,
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
path: 'admin/images',
|
path: 'images',
|
||||||
name: 'admin-images',
|
name: 'admin-images',
|
||||||
component: Images
|
component: Images
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'admin/instances',
|
path: 'instances',
|
||||||
name: 'admin-instances',
|
name: 'admin-instances',
|
||||||
component: Instances
|
component: Instances
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'admin/servers',
|
path: 'servers',
|
||||||
name: 'admin-servers',
|
name: 'admin-servers',
|
||||||
component: Servers
|
component: Servers
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'admin/users',
|
path: 'users',
|
||||||
name: 'admin-users',
|
name: 'admin-users',
|
||||||
component: Users
|
component: Users
|
||||||
}
|
}
|
||||||
@@ -100,4 +109,19 @@ const router = createRouter({
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const profileStore = useProfileStore();
|
||||||
|
if (to.path.startsWith('/admin')) {
|
||||||
|
if (!profileStore.isAdmin && !profileStore.isSuperAdmin) {
|
||||||
|
next({ name: 'not-found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((to.path.endsWith('servers') || to.path.endsWith('settings')) && !profileStore.isSuperAdmin) {
|
||||||
|
next({ name: 'not-found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">记起密码?<span
|
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">记起密码?<span
|
||||||
class="text-primary" @click="handleLogin">立即登入</span></span>
|
class="text-primary" @click="handleLogin">立即登入</span></span>
|
||||||
</div>
|
</div>
|
||||||
<Button label="发送邮件" class="w-full" @click="handleSubmit"></Button>
|
<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>
|
</div>
|
||||||
@@ -40,16 +41,20 @@ import { useRouter } from 'vue-router';
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const requesting = ref(false)
|
||||||
const form = ref({
|
const form = ref({
|
||||||
email: ''
|
email: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
|
requesting.value = true
|
||||||
api.UserForgetRequest(form.value).then(res => {
|
api.UserForgetRequest(form.value).then(res => {
|
||||||
toast.add({ severity: 'success', summary: '发送成功', detail: '请查看邮箱', life: 3000 })
|
toast.add({ severity: 'success', summary: '发送成功', detail: '请查看邮箱', life: 3000 })
|
||||||
|
requesting.value = false
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
toast.add({ severity: 'error', summary: '发送失败', detail: '请检查后重新尝试', life: 3000 })
|
toast.add({ severity: 'error', summary: '发送失败', detail: '请检查后重新尝试', life: 3000 })
|
||||||
|
requesting.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">记起密码?<span
|
<span class="font-medium no-underline ml-2 text-right cursor-pointer text-slate-600">记起密码?<span
|
||||||
class="text-primary" @click="handleLogin">立即登入</span></span>
|
class="text-primary" @click="handleLogin">立即登入</span></span>
|
||||||
</div>
|
</div>
|
||||||
<Button label="注册" class="w-full" @click="handleSubmit"></Button>
|
<Button label="重置密码" class="w-full" @click="handleSubmit"></Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -225,7 +225,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<ConfirmDialog></ConfirmDialog>
|
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -498,6 +498,9 @@ const instanceModify = async () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getInstances()
|
getInstances()
|
||||||
}, 100);
|
}, 100);
|
||||||
|
if (instanceConfiguration.value.cpu_only) {
|
||||||
|
delete instanceConfiguration.value.gpu_count
|
||||||
|
}
|
||||||
await api.AdminInstancesModify(instanceDetail.value.id, instanceConfiguration.value).then(async (res) => {
|
await api.AdminInstancesModify(instanceDetail.value.id, instanceConfiguration.value).then(async (res) => {
|
||||||
toast.add({ severity: 'success', summary: '调整配置', detail: '已调整配置', life: 3000 });
|
toast.add({ severity: 'success', summary: '调整配置', detail: '已调整配置', life: 3000 });
|
||||||
instanceModifyVisible.value = false
|
instanceModifyVisible.value = false
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog v-model:visible="serverModifyVisible" modal :header="'编辑节点 - ' + serverDetail.name"
|
<Dialog v-model:visible="serverModifyVisible" modal :header="'编辑节点 - ' + serverDetail.name" :draggable="false"
|
||||||
:style="{ width: '42rem' }">
|
:style="{ width: '42rem' }">
|
||||||
<span class="text-surface-500 dark:text-surface-400 block mb-6">编辑节点配置</span>
|
<span class="text-surface-500 dark:text-surface-400 block mb-6">编辑节点配置</span>
|
||||||
<div class="flex items-center gap-0 mb-4">
|
<div class="flex items-center gap-0 mb-4">
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog v-model:visible="serverAddVisible" modal :header="'添加节点'" :style="{ width: '42rem' }">
|
<Dialog v-model:visible="serverAddVisible" modal :header="'添加节点'" :draggable="false" :style="{ width: '42rem' }">
|
||||||
<span class="text-surface-500 dark:text-surface-400 block mb-6">添加节点配置</span>
|
<span class="text-surface-500 dark:text-surface-400 block mb-6">添加节点配置</span>
|
||||||
<div class="flex items-center gap-0 mb-4">
|
<div class="flex items-center gap-0 mb-4">
|
||||||
<label class="font-semibold w-20">名称:</label>
|
<label class="font-semibold w-20">名称:</label>
|
||||||
@@ -269,7 +269,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<ConfirmDialog></ConfirmDialog>
|
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -39,7 +39,8 @@
|
|||||||
</Paginator>
|
</Paginator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog v-model:visible="userModifyVisible" modal :header="'编辑用户 - ' + userData.username" :style="{ width: '25rem' }">
|
<Dialog v-model:visible="userModifyVisible" modal :header="'编辑用户 - ' + userData.username" :draggable="false"
|
||||||
|
:style="{ width: '25rem' }">
|
||||||
<span class="text-surface-500 dark:text-surface-400 block mb-6">编辑用户信息</span>
|
<span class="text-surface-500 dark:text-surface-400 block mb-6">编辑用户信息</span>
|
||||||
<div class="flex items-center gap-0 mb-4">
|
<div class="flex items-center gap-0 mb-4">
|
||||||
<label class="font-semibold w-20">邮箱:</label>
|
<label class="font-semibold w-20">邮箱:</label>
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<ConfirmDialog></ConfirmDialog>
|
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -229,7 +229,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<ConfirmDialog></ConfirmDialog>
|
<ConfirmDialog :draggable="false"></ConfirmDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -501,6 +501,9 @@ const instanceModify = async () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getInstances()
|
getInstances()
|
||||||
}, 100);
|
}, 100);
|
||||||
|
if (instanceConfiguration.value.cpu_only) {
|
||||||
|
delete instanceConfiguration.value.gpu_count
|
||||||
|
}
|
||||||
await api.UserInstancesModify(instanceDetail.value.id, instanceConfiguration.value).then(async (res) => {
|
await api.UserInstancesModify(instanceDetail.value.id, instanceConfiguration.value).then(async (res) => {
|
||||||
toast.add({ severity: 'success', summary: '调整配置', detail: '已调整配置', life: 3000 });
|
toast.add({ severity: 'success', summary: '调整配置', detail: '已调整配置', life: 3000 });
|
||||||
instanceModifyVisible.value = false
|
instanceModifyVisible.value = false
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func EmailFormat(email string) bool {
|
func EmailFormat(email string) bool {
|
||||||
pattern := `^\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*$`
|
pattern := `\w[-\w.+]*@([-A-Za-z0-9]+\.)+[A-Za-z]{2,14}`
|
||||||
match, err := regexp.MatchString(pattern, email)
|
match, err := regexp.MatchString(pattern, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
l.SetModel("main")
|
||||||
l.Info("Branch: %s", BRANCH)
|
l.Info("Branch: %s", BRANCH)
|
||||||
l.Info("Version: %s", VERSION)
|
l.Info("Version: %s", VERSION)
|
||||||
l.Info("Commit: %s", COMMIT)
|
l.Info("Commit: %s", COMMIT)
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func modifyHandler(ctx iris.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.CpuOnly {
|
||||||
|
req.GpuCount = nil
|
||||||
|
}
|
||||||
|
|
||||||
if req.GpuCount != nil {
|
if req.GpuCount != nil {
|
||||||
if *req.GpuCount < 0 {
|
if *req.GpuCount < 0 {
|
||||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||||
@@ -62,7 +66,17 @@ func modifyHandler(ctx iris.Context) {
|
|||||||
return
|
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)
|
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ func modifyHandler(ctx iris.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.CpuOnly {
|
||||||
|
req.GpuCount = nil
|
||||||
|
}
|
||||||
|
|
||||||
if req.GpuCount != nil {
|
if req.GpuCount != nil {
|
||||||
if *req.GpuCount < 0 {
|
if *req.GpuCount < 0 {
|
||||||
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||||
@@ -68,7 +72,17 @@ func modifyHandler(ctx iris.Context) {
|
|||||||
return
|
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)
|
middleware.Error(ctx, middleware.CodeBadRequest, iris.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,9 +43,19 @@ func modify(serverID uint, data Data) (err error) {
|
|||||||
return errors.New("instance status error")
|
return errors.New("instance status error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.CpuOnly == instance.CpuOnly && data.CpuOnly {
|
hasChanges := false
|
||||||
lc.Error("instance already cpu_only mode")
|
if data.CpuOnly != instance.CpuOnly {
|
||||||
return errors.New("instance already cpu_only mode")
|
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
|
oldVolumeSize := instance.VolumeSize
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ func Start() {
|
|||||||
LogLevel: "disable",
|
LogLevel: "disable",
|
||||||
Charset: "UTF-8",
|
Charset: "UTF-8",
|
||||||
EnableOptimizations: true,
|
EnableOptimizations: true,
|
||||||
|
RemoteAddrHeaders: []string{
|
||||||
|
"X-Real-Ip",
|
||||||
|
"X-Forwarded-For",
|
||||||
|
"CF-Connecting-IP",
|
||||||
|
"True-Client-Ip",
|
||||||
|
"X-Appengine-Remote-Addr",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
iris.WithoutServerError(iris.ErrServerClosed),
|
iris.WithoutServerError(iris.ErrServerClosed),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func createInstance(ip string, port int, apikey string,
|
|||||||
if config.GetSystemMountDir() != "" {
|
if config.GetSystemMountDir() != "" {
|
||||||
data.Binds = append(data.Binds, bindStruct{
|
data.Binds = append(data.Binds, bindStruct{
|
||||||
Src: config.GetSystemMountDir(),
|
Src: config.GetSystemMountDir(),
|
||||||
Dest: "/root/megrez-pub",
|
Dest: "/root/megrez-mnt",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,15 @@ func patchGpu(ip string, port int, apikey string,
|
|||||||
Dest: "/root/megrez-tmp",
|
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)
|
reqBytes, err := json.Marshal(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user