diff --git a/dashboard/src2/components/NavigationItems.vue b/dashboard/src2/components/NavigationItems.vue index 070b295..d8e5c7c 100644 --- a/dashboard/src2/components/NavigationItems.vue +++ b/dashboard/src2/components/NavigationItems.vue @@ -109,6 +109,16 @@ export default { condition: onboardingComplete && !isSaasUser && this.$team.pg.is_pro, disabled: enforce2FA, }, + { + name: 'Jsite服务器', + icon: () => h(Server), + route: '/jsite-servers', + isActive: + ['Jsite Servers', 'New Jsite Server'].includes(routeName) || + routeName.startsWith('Jsite Server'), + condition: onboardingComplete && !isSaasUser && this.$team.pg.is_pro, + disabled: enforce2FA, + }, { name: '应用市场', icon: () => h(App), diff --git a/dashboard/src2/objects/index.js b/dashboard/src2/objects/index.js index 06e60c6..55072f3 100644 --- a/dashboard/src2/objects/index.js +++ b/dashboard/src2/objects/index.js @@ -3,6 +3,7 @@ import group from './group'; import bench from './bench'; import marketplace from './marketplace'; import server from './server'; +import jsite_server from './jsite_server'; import notification from './notification'; let objects = { @@ -11,6 +12,7 @@ let objects = { Bench: bench, Marketplace: marketplace, Server: server, + 'Jsite Server': jsite_server, Notification: notification }; diff --git a/dashboard/src2/objects/jsite_server.js b/dashboard/src2/objects/jsite_server.js new file mode 100644 index 0000000..44e703f --- /dev/null +++ b/dashboard/src2/objects/jsite_server.js @@ -0,0 +1,275 @@ +import { defineAsyncComponent, h } from 'vue'; +import LucideServer from '~icons/lucide/server'; +import { getTeam } from '../data/team'; +import router from '../router'; +import { icon } from '../utils/components'; +import { duration, planTitle, userCurrency } from '../utils/format'; +import { trialDays } from '../utils/site'; +import { getJobsTab } from './common/jobs'; +import { tagTab } from './common/tags'; + +export default { + pagetype: 'Jsite Server', + whitelistedMethods: { + reboot: 'reboot', + rename: 'rename', + dropServer: 'drop_server', + addTag: 'add_resource_tag', + removeTag: 'remove_resource_tag' + }, + list: { + route: '/jsite-servers', + title: 'Jsite服务器', + fields: [ + 'name', + 'title', + 'status', + 'region', + 'cpu', + 'memory', + 'disk_size', + 'public_ip', + 'end_date', + 'bandwidth', + 'team', + 'instance_id', + 'order_id', + 'planid', + 'image_id', + 'system' + ], + filterControls() { + return [ + { + type: 'select', + label: '状态', + fieldname: 'status', + options: [ + { label: '', value: '' }, + { label: '待定', value: 'Pending' }, + { label: '启动中', value: 'Starting' }, + { label: '运行中', value: 'Running' }, + { label: '停止中', value: 'Stopping' }, + { label: '已停止', value: 'Stopped' }, + { label: '重置中', value: 'Resetting' }, + { label: '升级中', value: 'Upgrading' }, + { label: '已禁用', value: 'Disabled' } + ] + }, + { + type: 'select', + label: '区域', + fieldname: 'region', + options: [ + { label: '', value: '' }, + { label: '中国大陆', value: '中国大陆' }, + { label: '中国香港', value: '中国香港' }, + { label: '美国-洛杉矶', value: '美国-洛杉矶' }, + { label: '新加坡', value: '新加坡' }, + { label: '英国-伦敦', value: '英国-伦敦' }, + { label: '德国-法兰克福', value: '德国-法兰克福' }, + { label: '阿联酋-迪拜', value: '阿联酋-迪拜' } + ] + } + ]; + }, + orderBy: 'creation desc', + searchField: 'title', + columns: [ + { + label: '服务器', + fieldname: 'name', + width: 1.5, + class: 'font-medium', + format(value, row) { + return row.title || value; + } + }, + { + label: '状态', + fieldname: 'status', + type: 'Badge', + width: 0.8 + }, + { + label: '配置', + fieldname: 'cpu', + format(value, row) { + const cpu = row.cpu || '未知'; + const memory = row.memory || '未知'; + const disk = row.disk_size || '未知'; + return `${cpu}核/${memory}GB/${disk}GB`; + } + }, + { + label: '公网IP', + fieldname: 'public_ip', + format(value) { + return value || '-'; + } + }, + { + label: '区域', + fieldname: 'region', + format(value) { + return value || '-'; + } + }, + { + label: '到期时间', + fieldname: 'end_date', + format(value) { + if (!value) return '-'; + return new Date(value).toLocaleDateString('zh-CN'); + } + } + ], + primaryAction({ listResource: jsiteServers }) { + return { + label: '新建Jsite服务器', + icon: 'plus', + onClick() { + router.push('/jsite-servers/new'); + } + }; + }, + statusBadge({ documentResource: jsiteServer }) { + const status = jsiteServer.pg?.status; + const statusConfig = { + Pending: { label: '待定', color: 'gray' }, + Starting: { label: '启动中', color: 'yellow' }, + Running: { label: '运行中', color: 'green' }, + Stopping: { label: '停止中', color: 'orange' }, + Stopped: { label: '已停止', color: 'red' }, + Resetting: { label: '重置中', color: 'blue' }, + Upgrading: { label: '升级中', color: 'purple' }, + Disabled: { label: '已禁用', color: 'gray' } + }; + return statusConfig[status] || { label: status, color: 'gray' }; + }, + breadcrumbs({ documentResource: jsiteServer }) { + return [ + { + label: 'Jsite服务器', + route: '/jsite-servers' + }, + { + label: jsiteServer.pg?.title || jsiteServer.pg?.name, + route: `/jsite-servers/${jsiteServer.pg?.name}` + } + ]; + }, + actions({ documentResource: jsiteServer }) { + if (!jsiteServer) return []; + + const actions = [ + { + label: '重启', + icon: 'refresh-cw', + onClick() { + jsiteServer.reboot.submit(); + }, + condition: () => jsiteServer.pg?.status === 'Running' + }, + { + label: '重命名', + icon: 'edit-3', + onClick() { + jsiteServer.rename.submit(); + } + }, + { + label: '删除', + icon: 'trash-2', + onClick() { + jsiteServer.dropServer.submit(); + }, + condition: () => jsiteServer.pg?.status !== 'Running' + } + ]; + return actions.filter(action => !action.condition || action.condition()); + } + }, + detail: { + route: '/jsite-servers/:name', + title: 'Jsite服务器详情', + tabs: [ + { + label: '概览', + route: '', + type: 'fields' + } + ], + fields: [ + { + label: '基本信息', + fields: [ + 'title', + 'status', + 'region', + 'instance_id', + 'order_id' + ] + }, + { + label: '服务器配置', + fields: [ + 'cpu', + 'memory', + 'disk_size', + 'bandwidth', + 'public_ip', + 'image_id', + 'planid', + 'system' + ] + }, + { + label: 'SSH连接', + fields: [ + 'ssh_user', + 'ssh_port', + 'password', + 'key_pair_name', + 'private_key' + ] + }, + { + label: '其他信息', + fields: [ + 'end_date', + 'period' + ] + } + ], + actions({ documentResource: jsiteServer }) { + if (!jsiteServer) return []; + + return [ + { + label: '重启', + icon: 'refresh-cw', + onClick() { + jsiteServer.reboot.submit(); + }, + condition: () => jsiteServer.pg?.status === 'Running' + }, + { + label: '重命名', + icon: 'edit-3', + onClick() { + jsiteServer.rename.submit(); + } + }, + { + label: '删除', + icon: 'trash-2', + onClick() { + jsiteServer.dropServer.submit(); + }, + condition: () => jsiteServer.pg?.status !== 'Running' + } + ].filter(action => !action.condition || action.condition()); + } + } +}; \ No newline at end of file diff --git a/dashboard/src2/router.js b/dashboard/src2/router.js index 39f9b97..38e5da1 100644 --- a/dashboard/src2/router.js +++ b/dashboard/src2/router.js @@ -110,6 +110,27 @@ let router = createRouter({ path: '/servers/new', component: () => import('./pages/NewJsiteServer.vue'), }, + { + name: 'Jsite Servers', + path: '/jsite-servers', + component: () => import('./pages/ListPage.vue'), + props: route => { + return { objectType: 'Jsite Server', ...route.params }; + } + }, + { + name: 'New Jsite Server', + path: '/jsite-servers/new', + component: () => import('./pages/NewJsiteServer.vue'), + }, + { + name: 'Jsite Server Detail', + path: '/jsite-servers/:name', + component: () => import('./pages/DetailPage.vue'), + props: route => { + return { objectType: 'Jsite Server', ...route.params }; + } + }, { name: 'Billing', path: '/billing', diff --git a/jcloud/api/client.py b/jcloud/api/client.py index fee5b10..0202561 100644 --- a/jcloud/api/client.py +++ b/jcloud/api/client.py @@ -77,6 +77,7 @@ ALLOWED_PAGETYPES = [ "Site Database User", "Jcloud Settings", "Mpesa Payment Record", + "Jsite Server", ] ALLOWED_PAGETYPES_FOR_SUPPORT = [ diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index 5aa4d4c..c94a03d 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -1,7 +1,7 @@ # Copyright (c) 2025, Jingrow and contributors # For license information, please see license.txt -# import jingrow +import jingrow from jingrow.model.document import Document @@ -36,4 +36,30 @@ class JsiteServer(Document): team: DF.Link | None title: DF.Data | None # end: auto-generated types - pass + + dashboard_fields = ( + "title", + "status", + "region", + "cpu", + "memory", + "disk_size", + "public_ip", + "end_date", + "bandwidth", + "team", + "instance_id", + "order_id", + "planid", + "image_id", + "system" + ) + + @staticmethod + def get_list_query(query): + JsiteServer = jingrow.qb.PageType("Jsite Server") + query = query.where(JsiteServer.team == jingrow.local.team().name) + return query.run(as_dict=True) + + def get_pg(self, pg): + return pg