16 Commits

Author SHA1 Message Date
Harry-zklcdc 2bf6a58e7f [Fix] 🐛 Docker Image TimeZone Error 2025-07-14 01:47:19 +08:00
Harry-zklcdc 69f91dba02 [Fix] 🐛 HTTP Get Remote IP Error 2025-07-10 15:53:30 +08:00
Harry-zklcdc 2a18684162 [Feat] Independent Admin Management Page 2025-07-06 16:10:03 +08:00
Harry-zklcdc e6f81e19dc [Fix] 🐛 Page Router Permission 2025-07-06 15:32:39 +08:00
Harry-zklcdc 05502938ba [Fix] 📌 Dialog Draggable 2025-06-08 01:56:29 +08:00
Harry-zklcdc 7fc2c3ec09 [Fix] 🐛 Send Instance Current Config to Controler #23 2025-05-05 21:26:47 +08:00
Harry-zklcdc 011827f5c7 [Fix] 🐛 PreCheck the Config if Instance has Changed #21 2025-03-30 01:25:21 +08:00
Harry-zklcdc 69554b5e39 [Fix] 🐛 Ignore GpuCount Value at Instance Modify to CpuOnly #21 2025-03-30 01:24:12 +08:00
Harry-zklcdc a24fb8e8ad [Feat] Add Auto Dark Mode 2025-03-17 13:39:20 +08:00
Harry-zklcdc 42de9caf52 [Feat] Add Docs Link 2025-03-06 00:26:00 +08:00
Harry-zklcdc 5780f84714 [Feat] Set logging model to "main" in main function 2025-03-05 18:59:42 +08:00
Harry-zklcdc de8c601be9 [Fix] 🐛 Instance Mount Path 2025-03-04 18:21:57 +08:00
Harry-zklcdc e283a626a1 [Fix] 🐛 Photo Show Error 2025-02-28 21:26:33 +08:00
Harry-zklcdc 37ba1baf71 [Fix] 😅 Typo #15 2025-02-25 00:02:57 +08:00
Harry-zklcdc 81b52e80b5 [Fix] 🐛 Fix Regexp Pattern to Support subdomain Email #15 2025-02-24 23:38:04 +08:00
Harry-zklcdc 13bd350274 [Fix] 🐛 Add Sendding Status #15 2025-02-24 22:12:25 +08:00
23 changed files with 381 additions and 74 deletions
+4 -1
View File
@@ -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"]
+2 -2
View File
@@ -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 资源监控 |
| ---------------------------------------------------- | ----------------------------------------------- | -------------------------------- | | ---------------------------------------------------- | ----------------------------------------------- | -------------------------------- |
| ![image-20250116010708107](./assets/code-server/.png) | ![Jupyter NoteBook](./assets/juper-notebook.png) | ![Grafana](./assets/grafana.png) | | ![image-20250116010708107](./assets/code-server.png) | ![Jupyter NoteBook](./assets/juper-notebook.png) | ![Grafana](./assets/grafana.png) |
### 系统管理 ### 系统管理
+87
View File
@@ -0,0 +1,87 @@
<script setup>
import { useLayout } from '@/layout/composables/layout';
import { computed, onMounted, ref, watch } from 'vue';
import AppAdminSidebar from './AppAdminSidebar.vue';
import AppFooter from './AppFooter.vue';
import AppTopbar from './AppTopbar.vue';
import api from '@/api';
import { useProfileStore } from '@/stores/profile';
import { useToast } from 'primevue/usetoast';
import { useRouter } from 'vue-router';
const router = useRouter()
const profileStore = useProfileStore()
const toast = useToast()
const { layoutConfig, layoutState, isSidebarActive, resetMenu } = useLayout();
const outsideClickListener = ref(null);
watch(isSidebarActive, (newVal) => {
if (newVal) {
bindOutsideClickListener();
} else {
unbindOutsideClickListener();
}
});
const containerClass = computed(() => {
return {
'layout-overlay': layoutConfig.menuMode === 'overlay',
'layout-static': layoutConfig.menuMode === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-mobile-active': layoutState.staticMenuMobileActive
};
});
function bindOutsideClickListener() {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
resetMenu();
}
};
document.addEventListener('click', outsideClickListener.value);
}
}
function unbindOutsideClickListener() {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener.value = null;
}
}
function isOutsideClicked(event) {
const sidebarEl = document.querySelector('.layout-sidebar');
const topbarEl = document.querySelector('.layout-menu-button');
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
}
onMounted(() => {
api.GetUserProfile().then(res => {
profileStore.setUserProfile(res.data.data.result)
}).catch(_ => {
toast.add({ severity: 'error', summary: '登录过期,请重新登录', life: 3000 })
profileStore.clearUserProfile()
router.push('/login')
})
})
</script>
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<app-admin-sidebar></app-admin-sidebar>
<div class="layout-main-container">
<div class="layout-main">
<router-view></router-view>
</div>
<app-footer></app-footer>
</div>
<div class="layout-mask animate-fadein"></div>
</div>
</template>
+88
View File
@@ -0,0 +1,88 @@
<script setup>
import { useProfileStore } from '@/stores/profile';
import { onMounted, ref } from 'vue';
import AppMenuItem from './AppMenuItem.vue';
const profileStore = useProfileStore();
const model = ref([]);
const defaultModel = ref({
label: '用户中心',
items: [
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/'
},
]
})
const adminModel = ref({
label: '系统设置',
items: [
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/admin/instances'
},
{
label: '用户管理',
icon: 'pi pi-fw pi-users text-indigo-500',
to: '/admin/users'
},
]
})
const superAdminModel = ref({
label: '系统设置',
items: [
{
label: '节点管理',
icon: 'pi pi-fw pi-server text-yellow-400',
to: '/admin/servers'
},
{
label: '实例管理',
icon: 'pi pi-fw pi-desktop text-lime-500',
to: '/admin/instances'
},
{
label: '用户管理',
icon: 'pi pi-fw pi-users text-indigo-500',
to: '/admin/users'
},
{
label: '镜像管理',
icon: 'pi pi-fw pi-images text-teal-500',
to: '/admin/images'
},
]
})
onMounted(() => {
const superAdmin = profileStore.isSuperAdmin
const admin = profileStore.isAdmin
if (superAdmin) {
model.value.push(superAdminModel.value)
} else if (admin) {
model.value.push(adminModel.value)
}
model.value.push(defaultModel.value);
});
</script>
<template>
<ul class="layout-menu">
<template v-for="(item, i) in model" :key="item">
<app-menu-item v-if="!item.separator" :item="item" :index="i"></app-menu-item>
<li v-if="item.separator" class="menu-separator"></li>
</template>
</ul>
</template>
<style lang="scss" scoped></style>
+11
View File
@@ -0,0 +1,11 @@
<script setup>
import AppAdminMenu from './AppAdminMenu.vue';
</script>
<template>
<div class="layout-sidebar">
<app-admin-menu></app-admin-menu>
</div>
</template>
<style lang="scss" scoped></style>
+5 -31
View File
@@ -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'
},
] ]
}) })
+45 -3
View File
@@ -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>
+27 -16
View File
@@ -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;
+28 -4
View File
@@ -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/images', path: '/admin/',
name: 'admin-dashboard',
component: AppAdminLayout,
children: [
{
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;
+6 -1
View File
@@ -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
}) })
} }
+1 -1
View File
@@ -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>
+4 -1
View File
@@ -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
+3 -3
View File
@@ -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>
+3 -2
View File
@@ -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>
+4 -1
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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)
+15 -1
View File
@@ -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
} }
+15 -1
View File
@@ -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
} }
+13 -3
View File
@@ -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
+7
View File
@@ -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",
}) })
} }
+9
View File
@@ -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)