From 76d7a5e62140bf4400d7cee2812a17f5721ad40e Mon Sep 17 00:00:00 2001 From: jingrow Date: Fri, 25 Jul 2025 22:22:59 +0800 Subject: [PATCH 001/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Jsite=20Server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jcloud/pagetype/jsite_server/__init__.py | 0 .../pagetype/jsite_server/jsite_server.js | 8 + .../pagetype/jsite_server/jsite_server.json | 191 ++++++++++++++++++ .../pagetype/jsite_server/jsite_server.py | 33 +++ .../jsite_server/test_jsite_server.py | 9 + 5 files changed, 241 insertions(+) create mode 100644 jcloud/jcloud/pagetype/jsite_server/__init__.py create mode 100644 jcloud/jcloud/pagetype/jsite_server/jsite_server.js create mode 100644 jcloud/jcloud/pagetype/jsite_server/jsite_server.json create mode 100644 jcloud/jcloud/pagetype/jsite_server/jsite_server.py create mode 100644 jcloud/jcloud/pagetype/jsite_server/test_jsite_server.py diff --git a/jcloud/jcloud/pagetype/jsite_server/__init__.py b/jcloud/jcloud/pagetype/jsite_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js new file mode 100644 index 0000000..1a8274d --- /dev/null +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Jingrow and contributors +// For license information, please see license.txt + +// jingrow.ui.form.on("Jsite Server", { +// refresh(frm) { + +// }, +// }); diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json new file mode 100644 index 0000000..3324471 --- /dev/null +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -0,0 +1,191 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-27 10:00:00", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "team", + "column_break_4", + "status", + "plan", + "server_section", + "instance_id", + "cpu", + "disk_size", + "region", + "column_break_aliyun", + "end_date", + "memory", + "bandwidth", + "ip", + "ssh_section", + "ssh_user", + "ssh_port", + "column_break_20", + "root_public_key" + ], + "fields": [ + { + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "状态", + "options": "Pending\nInstalling\nActive\nBroken\nArchived", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "标题", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "team", + "fieldtype": "Link", + "label": "团队", + "options": "Team" + }, + { + "fieldname": "plan", + "fieldtype": "Link", + "label": "计划", + "options": "Server Plan" + }, + { + "fetch_from": "virtual_machine.public_ip_address", + "fieldname": "ip", + "fieldtype": "Data", + "in_list_view": 1, + "label": "公网IP", + "set_only_once": 1 + }, + { + "collapsible": 1, + "fieldname": "ssh_section", + "fieldtype": "Section Break", + "label": "SSH" + }, + { + "default": "root", + "fieldname": "ssh_user", + "fieldtype": "Data", + "label": "SSH用户" + }, + { + "default": "22", + "fieldname": "ssh_port", + "fieldtype": "Int", + "label": "SSH端口" + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, + { + "fieldname": "root_public_key", + "fieldtype": "Code", + "label": "Root公钥" + }, + { + "fieldname": "column_break_aliyun", + "fieldtype": "Column Break" + }, + { + "fieldname": "instance_id", + "fieldtype": "Data", + "label": "实例ID", + "read_only": 1 + }, + { + "fieldname": "end_date", + "fieldtype": "Date", + "label": "到期时间" + }, + { + "fieldname": "region", + "fieldtype": "Data", + "label": "地域", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "server_section", + "fieldtype": "Section Break", + "label": "服务器信息" + }, + { + "fieldname": "bandwidth", + "fieldtype": "Data", + "label": "带宽(Mbps)", + "read_only": 1 + }, + { + "fieldname": "cpu", + "fieldtype": "Data", + "label": "CPU", + "read_only": 1 + }, + { + "fieldname": "memory", + "fieldtype": "Data", + "label": "内存", + "read_only": 1 + }, + { + "fieldname": "disk_size", + "fieldtype": "Data", + "label": "硬盘容量", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-07-25 22:37:43.914945", + "modified_by": "Administrator", + "module": "Jcloud", + "name": "Jsite Server", + "naming_rule": "Set by user", + "owner": "Administrator", + "pagetype": "PageType", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "read": 1, + "role": "Jcloud Admin", + "write": 1 + }, + { + "create": 1, + "read": 1, + "role": "Jcloud Member", + "write": 1 + } + ], + "quick_entry": 1, + "row_format": "Dynamic", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "title_field": "title" +} \ No newline at end of file diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py new file mode 100644 index 0000000..0f5013c --- /dev/null +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -0,0 +1,33 @@ +# Copyright (c) 2025, Jingrow and contributors +# For license information, please see license.txt + +# import jingrow +from jingrow.model.document import Document + + +class JsiteServer(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from jingrow.types import DF + + bandwidth: DF.Data | None + cpu: DF.Data | None + disk_size: DF.Data | None + end_date: DF.Date | None + instance_id: DF.Data | None + ip: DF.Data | None + memory: DF.Data | None + plan: DF.Link | None + region: DF.Data | None + root_public_key: DF.Code | None + ssh_port: DF.Int + ssh_user: DF.Data | None + status: DF.Literal["Pending", "Installing", "Active", "Broken", "Archived"] + team: DF.Link | None + title: DF.Data + # end: auto-generated types + pass diff --git a/jcloud/jcloud/pagetype/jsite_server/test_jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/test_jsite_server.py new file mode 100644 index 0000000..fba309e --- /dev/null +++ b/jcloud/jcloud/pagetype/jsite_server/test_jsite_server.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Jingrow and Contributors +# See license.txt + +# import jingrow +from jingrow.tests.utils import JingrowTestCase + + +class TestJsiteServer(JingrowTestCase): + pass -- 2.48.1 From b920a74e35cdbc18aa20265c4ac142c31c1a695c Mon Sep 17 00:00:00 2001 From: jingrow Date: Sat, 26 Jul 2025 13:08:39 +0800 Subject: [PATCH 002/250] =?UTF-8?q?Server=20Plan=E7=9A=84=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E7=B1=BB=E5=9E=8B=E5=A2=9E=E5=8A=A0Jsite=20S?= =?UTF-8?q?erver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/jcloud/pagetype/server_plan/server_plan.json | 7 ++++--- jcloud/jcloud/pagetype/server_plan/server_plan.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/jcloud/jcloud/pagetype/server_plan/server_plan.json b/jcloud/jcloud/pagetype/server_plan/server_plan.json index 52ccfc1..abcccc0 100644 --- a/jcloud/jcloud/pagetype/server_plan/server_plan.json +++ b/jcloud/jcloud/pagetype/server_plan/server_plan.json @@ -3,7 +3,6 @@ "allow_rename": 1, "autoname": "prompt", "creation": "2024-02-05 22:21:47.560972", - "pagetype": "PageType", "engine": "InnoDB", "field_order": [ "enabled", @@ -36,7 +35,7 @@ "fieldname": "server_type", "fieldtype": "Select", "label": "Server Type", - "options": "Server\nDatabase Server\nProxy Server\nSelf Hosted Server" + "options": "Server\nDatabase Server\nProxy Server\nSelf Hosted Server\nJsite Server" }, { "fieldname": "section_break_nifk", @@ -131,12 +130,13 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-21 13:49:02.682602", + "modified": "2025-07-26 13:07:36.008926", "modified_by": "Administrator", "module": "Jcloud", "name": "Server Plan", "naming_rule": "Set by user", "owner": "Administrator", + "pagetype": "PageType", "permissions": [ { "create": 1, @@ -151,6 +151,7 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] diff --git a/jcloud/jcloud/pagetype/server_plan/server_plan.py b/jcloud/jcloud/pagetype/server_plan/server_plan.py index aec2345..7646ffb 100644 --- a/jcloud/jcloud/pagetype/server_plan/server_plan.py +++ b/jcloud/jcloud/pagetype/server_plan/server_plan.py @@ -25,7 +25,7 @@ class ServerPlan(Plan): price_cny: DF.Currency price_usd: DF.Currency roles: DF.Table[HasRole] - server_type: DF.Literal["Server", "Database Server", "Proxy Server", "Self Hosted Server"] + server_type: DF.Literal["Server", "Database Server", "Proxy Server", "Self Hosted Server", "Jsite Server"] title: DF.Data | None vcpu: DF.Int # end: auto-generated types -- 2.48.1 From a85024c01fc0a468378aca2bdecb2a3afc27ac4a Mon Sep 17 00:00:00 2001 From: jingrow Date: Sat, 26 Jul 2025 13:15:19 +0800 Subject: [PATCH 003/250] =?UTF-8?q?=E6=9B=B4=E6=96=B0jsite=20server?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetype/jsite_server/jsite_server.json | 20 ++++++++++++------- .../pagetype/jsite_server/jsite_server.py | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 3324471..59c8236 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -35,14 +35,13 @@ "in_standard_filter": 1, "label": "状态", "options": "Pending\nInstalling\nActive\nBroken\nArchived", - "read_only": 1, - "reqd": 1 + "read_only": 1 }, { "fieldname": "title", "fieldtype": "Data", - "label": "标题", - "reqd": 1 + "in_list_view": 1, + "label": "标题" }, { "fieldname": "column_break_4", @@ -66,7 +65,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "公网IP", - "set_only_once": 1 + "read_only": 1 }, { "collapsible": 1, @@ -108,11 +107,14 @@ { "fieldname": "end_date", "fieldtype": "Date", - "label": "到期时间" + "in_list_view": 1, + "label": "到期时间", + "read_only": 1 }, { "fieldname": "region", "fieldtype": "Data", + "in_list_view": 1, "label": "地域", "read_only": 1 }, @@ -125,31 +127,35 @@ { "fieldname": "bandwidth", "fieldtype": "Data", + "in_list_view": 1, "label": "带宽(Mbps)", "read_only": 1 }, { "fieldname": "cpu", "fieldtype": "Data", + "in_list_view": 1, "label": "CPU", "read_only": 1 }, { "fieldname": "memory", "fieldtype": "Data", + "in_list_view": 1, "label": "内存", "read_only": 1 }, { "fieldname": "disk_size", "fieldtype": "Data", + "in_list_view": 1, "label": "硬盘容量", "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-25 22:37:43.914945", + "modified": "2025-07-26 13:13:06.510636", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index 0f5013c..f1038c8 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -28,6 +28,6 @@ class JsiteServer(Document): ssh_user: DF.Data | None status: DF.Literal["Pending", "Installing", "Active", "Broken", "Archived"] team: DF.Link | None - title: DF.Data + title: DF.Data | None # end: auto-generated types pass -- 2.48.1 From ebc9eb036c0f80a761a59eefff4df8580cda7f5d Mon Sep 17 00:00:00 2001 From: jingrow Date: Sat, 26 Jul 2025 14:04:20 +0800 Subject: [PATCH 004/250] =?UTF-8?q?=E6=9B=B4=E6=96=B0requirements.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetype/jsite_server/jsite_server.json | 17 ++++++++++++++++- .../pagetype/jsite_server/jsite_server.py | 2 ++ requirements.txt | 6 +++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 59c8236..c560709 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -15,11 +15,13 @@ "cpu", "disk_size", "region", + "system_image", "column_break_aliyun", "end_date", "memory", "bandwidth", "ip", + "running_status", "ssh_section", "ssh_user", "ssh_port", @@ -151,11 +153,24 @@ "in_list_view": 1, "label": "硬盘容量", "read_only": 1 + }, + { + "fieldname": "system_image", + "fieldtype": "Data", + "label": "系统镜像" + }, + { + "fetch_from": "virtual_machine.public_ip_address", + "fieldname": "running_status", + "fieldtype": "Data", + "in_list_view": 1, + "label": "运行状态", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-26 13:13:06.510636", + "modified": "2025-07-26 13:27:00.795541", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index f1038c8..7a96902 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -24,9 +24,11 @@ class JsiteServer(Document): plan: DF.Link | None region: DF.Data | None root_public_key: DF.Code | None + running_status: DF.Data | None ssh_port: DF.Int ssh_user: DF.Data | None status: DF.Literal["Pending", "Installing", "Active", "Broken", "Archived"] + system_image: DF.Data | None team: DF.Link | None title: DF.Data | None # end: auto-generated types diff --git a/requirements.txt b/requirements.txt index 03c8957..78e60ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ posthog==3.0.1 prometheus-client==0.19.0 PyGithub==1.44.1 pyOpenSSL~=23.2.0 -python-telegram-bot==13.15 +python-telegram-bot==22.3 razorpay~=1.2.0 requests<2.32 responses==0.23.1 @@ -29,3 +29,7 @@ wrapt~=1.15.0 elasticsearch-dsl>=8.0.0,<9.0.0 hcloud==2.2.1 playwright==1.49.1 +segno~=1.6.6 +wechatpayv3~=1.3.10 +alipay-sdk-python~=3.7.479 +alibabacloud_swas_open20200601==4.0.0 -- 2.48.1 From 403ae1fe4981f3e47041cc44b55f912c49617518 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sat, 26 Jul 2025 23:04:02 +0800 Subject: [PATCH 005/250] =?UTF-8?q?=E6=96=B0=E5=A2=9ENewJsiteServer.vue?= =?UTF-8?q?=E5=8F=8Aaliyun=5Fserver=5Flight.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/pages/NewJsiteServer.vue | 693 ++++++++++++++++++++++++ jcloud/api/aliyun_server_light.py | 376 +++++++++++++ 2 files changed, 1069 insertions(+) create mode 100644 dashboard/src2/pages/NewJsiteServer.vue create mode 100644 jcloud/api/aliyun_server_light.py diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue new file mode 100644 index 0000000..a5882dd --- /dev/null +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -0,0 +1,693 @@ + + + + + diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py new file mode 100644 index 0000000..bbf3a0b --- /dev/null +++ b/jcloud/api/aliyun_server_light.py @@ -0,0 +1,376 @@ +import jingrow +import os +import sys +from typing import Dict, Any + +from alibabacloud_swas_open20200601.client import Client as SWAS_OPEN20200601Client +from alibabacloud_credentials.client import Client as CredentialClient +from alibabacloud_tea_openapi import models as open_api_models +from alibabacloud_swas_open20200601 import models as swas__open20200601_models +from alibabacloud_tea_util import models as util_models +from alibabacloud_tea_util.client import Client as UtilClient + + +class AliyunLightServerManager: + """阿里云轻量应用服务器管理器""" + _instance = None + _clients = {} # 缓存不同地域的客户端 + + @classmethod + def get_instance(cls): + """获取单例实例""" + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def __init__(self): + """初始化管理器""" + if AliyunLightServerManager._instance is not None: + raise Exception("请使用 AliyunLightServerManager.get_instance() 获取实例") + + # 初始化配置 + self._initialize_credentials() + + def _initialize_credentials(self): + """初始化阿里云凭据""" + try: + settings = jingrow.get_single("Jcloud Settings") + if not settings: + jingrow.log_error("阿里云凭据初始化失败", "无法获取Jcloud Settings配置") + raise Exception("Jcloud Settings配置不存在") + + self.access_key_id = settings.get("aliyun_access_key_id") + self.access_secret = settings.get_password("aliyun_access_secret") if settings.get("aliyun_access_secret") else None + + if not self.access_key_id or not self.access_secret: + jingrow.log_error("阿里云凭据初始化失败", "阿里云凭据未配置,请在Jcloud Settings中配置aliyun_access_key_id和aliyun_access_secret") + raise Exception("阿里云凭据未配置") + + except Exception as e: + jingrow.log_error(f"阿里云凭据初始化失败: {str(e)}") + self.access_key_id = None + self.access_secret = None + raise + + def _get_client(self, region_id: str = 'cn-shanghai') -> SWAS_OPEN20200601Client: + """获取指定地域的客户端,使用缓存机制""" + if region_id not in self._clients: + try: + # 直接使用凭据创建配置 + config = open_api_models.Config( + access_key_id=self.access_key_id, + access_key_secret=self.access_secret + ) + config.endpoint = f'swas.{region_id}.aliyuncs.com' + self._clients[region_id] = SWAS_OPEN20200601Client(config) + except Exception as e: + jingrow.log_error("阿里云客户端创建失败", f"创建客户端时发生错误: {str(e)}") + raise + + return self._clients[region_id] + + def _convert_response_to_dict(self, response_body): + """将阿里云SDK响应对象转换为可序列化的字典""" + if response_body is None: + return None + + result = {} + # 获取所有公共属性 + for attr_name in dir(response_body): + if not attr_name.startswith('_') and not callable(getattr(response_body, attr_name)): + attr_value = getattr(response_body, attr_name) + if attr_value is not None: + if hasattr(attr_value, '__dict__') and not isinstance(attr_value, (str, int, float, bool)): + # 如果是复杂对象,递归转换 + result[attr_name] = self._convert_response_to_dict(attr_value) + elif isinstance(attr_value, list): + # 如果是列表,转换列表中的每个元素 + converted_list = [] + for item in attr_value: + if hasattr(item, '__dict__') and not isinstance(item, (str, int, float, bool)): + converted_list.append(self._convert_response_to_dict(item)) + else: + converted_list.append(item) + result[attr_name] = converted_list + else: + # 基本类型直接赋值 + result[attr_name] = attr_value + + return result + + def create_instance(self, plan_id, image_id, period=1, region_id='cn-shanghai'): + """创建轻量应用服务器实例""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.CreateInstancesRequest( + region_id=region_id, + plan_id=plan_id, + image_id=image_id, + period=period + ) + runtime = util_models.RuntimeOptions() + response = client.create_instances_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例创建成功'} + except Exception as e: + jingrow.log_error("创建实例失败", f"创建实例时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例创建失败'} + + def start_instance(self, instance_id, region_id='cn-shanghai'): + """启动实例""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.StartInstanceRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.start_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例启动成功'} + except Exception as e: + jingrow.log_error("启动实例失败", f"启动实例 {instance_id} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例启动失败'} + + def stop_instance(self, instance_id, region_id='cn-shanghai'): + """停止实例""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.StopInstanceRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.stop_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例停止成功'} + except Exception as e: + jingrow.log_error("停止实例失败", f"停止实例 {instance_id} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例停止失败'} + + def upgrade_instance(self, instance_id, plan_id, region_id='cn-shanghai'): + """升级实例配置""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.UpgradeInstanceRequest( + region_id=region_id, + instance_id=instance_id, + plan_id=plan_id + ) + runtime = util_models.RuntimeOptions() + response = client.upgrade_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例升级成功'} + except Exception as e: + jingrow.log_error("升级实例失败", f"升级实例 {instance_id} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例升级失败'} + + def renew_instance(self, instance_id, period=1, period_unit='Month', region_id='cn-shanghai'): + """续费实例""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.RenewInstanceRequest( + region_id=region_id, + instance_id=instance_id, + period=period, + period_unit=period_unit + ) + runtime = util_models.RuntimeOptions() + response = client.renew_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例续费成功'} + except Exception as e: + jingrow.log_error("续费实例失败", f"续费实例 {instance_id} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例续费失败'} + + def reset_system(self, instance_id, image_id, password=None, region_id='cn-shanghai'): + """重置系统""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.ResetSystemRequest( + region_id=region_id, + instance_id=instance_id, + image_id=image_id + ) + if password: + request.password = password + runtime = util_models.RuntimeOptions() + response = client.reset_system_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '系统重置成功'} + except Exception as e: + jingrow.log_error("重置系统失败", f"重置实例 {instance_id} 系统时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '系统重置失败'} + + def get_instance_info(self, instance_id, region_id='cn-shanghai'): + """获取实例信息""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.GetInstanceRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.get_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取实例信息成功'} + except Exception as e: + jingrow.log_error("获取实例信息失败", f"获取实例 {instance_id} 信息时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取实例信息失败'} + + def list_instances(self, page_number=1, page_size=20, region_id='cn-shanghai'): + """获取实例列表""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.ListInstancesRequest( + region_id=region_id, + page_number=page_number, + page_size=page_size + ) + runtime = util_models.RuntimeOptions() + response = client.list_instances_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取实例列表成功'} + except Exception as e: + jingrow.log_error("获取实例列表失败", f"获取实例列表时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取实例列表失败'} + + def delete_instance(self, instance_id, region_id='cn-shanghai'): + """删除实例""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.DeleteInstanceRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.delete_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例删除成功'} + except Exception as e: + jingrow.log_error("删除实例失败", f"删除实例 {instance_id} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例删除失败'} + + def get_plans(self, region_id='cn-shanghai'): + """获取可用套餐列表""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.ListPlansRequest( + region_id=region_id + ) + runtime = util_models.RuntimeOptions() + response = client.list_plans_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取套餐列表成功'} + except Exception as e: + jingrow.log_error("获取套餐列表失败", f"获取套餐列表时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取套餐列表失败'} + + def get_images(self, image_type='system', region_id='cn-shanghai'): + """获取可用镜像列表""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.ListImagesRequest( + region_id=region_id, + image_type=image_type + ) + runtime = util_models.RuntimeOptions() + response = client.list_images_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取镜像列表成功'} + except Exception as e: + jingrow.log_error("获取镜像列表失败", f"获取镜像列表时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取镜像列表失败'} + + def get_regions(self): + """获取可用地域列表""" + try: + # 直接使用凭据创建配置 + config = open_api_models.Config( + access_key_id=self.access_key_id, + access_key_secret=self.access_secret + ) + # 默认用上海endpoint,获取所有地域 + config.endpoint = 'swas.cn-shanghai.aliyuncs.com' + client = SWAS_OPEN20200601Client(config) + request = swas__open20200601_models.ListRegionsRequest(accept_language='zh-CN') + runtime = util_models.RuntimeOptions() + response = client.list_regions_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取地域列表成功'} + except Exception as e: + jingrow.log_error("获取地域列表失败", f"获取地域列表时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取地域列表失败'} + + +# 全局管理器实例 +_aliyun_manager = None + +def _get_manager(): + """获取阿里云管理器实例""" + global _aliyun_manager + if _aliyun_manager is None: + _aliyun_manager = AliyunLightServerManager.get_instance() + return _aliyun_manager + + +# API函数 - 使用@jingrow.whitelist()装饰器 +@jingrow.whitelist() +def create_aliyun_instance(plan_id, image_id, period=1, region_id='cn-shanghai'): + """创建轻量应用服务器实例""" + manager = _get_manager() + return manager.create_instance(plan_id, image_id, period, region_id) + +@jingrow.whitelist() +def start_aliyun_instance(instance_id, region_id='cn-shanghai'): + """启动实例""" + manager = _get_manager() + return manager.start_instance(instance_id, region_id) + +@jingrow.whitelist() +def stop_aliyun_instance(instance_id, region_id='cn-shanghai'): + """停止实例""" + manager = _get_manager() + return manager.stop_instance(instance_id, region_id) + +@jingrow.whitelist() +def upgrade_aliyun_instance(instance_id, plan_id, region_id='cn-shanghai'): + """升级实例配置""" + manager = _get_manager() + return manager.upgrade_instance(instance_id, plan_id, region_id) + +@jingrow.whitelist() +def renew_aliyun_instance(instance_id, period=1, period_unit='Month', region_id='cn-shanghai'): + """续费实例""" + manager = _get_manager() + return manager.renew_instance(instance_id, period, period_unit, region_id) + +@jingrow.whitelist() +def reset_aliyun_instance_system(instance_id, image_id, password=None, region_id='cn-shanghai'): + """重置系统""" + manager = _get_manager() + return manager.reset_system(instance_id, image_id, password, region_id) + +@jingrow.whitelist() +def get_aliyun_instance_info(instance_id, region_id='cn-shanghai'): + """获取实例信息""" + manager = _get_manager() + return manager.get_instance_info(instance_id, region_id) + +@jingrow.whitelist() +def list_aliyun_instances(page_number=1, page_size=20, region_id='cn-shanghai'): + """获取实例列表""" + manager = _get_manager() + return manager.list_instances(page_number, page_size, region_id) + +@jingrow.whitelist() +def delete_aliyun_instance(instance_id, region_id='cn-shanghai'): + """删除实例""" + manager = _get_manager() + return manager.delete_instance(instance_id, region_id) + +@jingrow.whitelist() +def get_aliyun_plans(region_id='cn-shanghai'): + """获取可用套餐列表""" + manager = _get_manager() + return manager.get_plans(region_id) + +@jingrow.whitelist() +def get_aliyun_images(image_type='system', region_id='cn-shanghai'): + """获取可用镜像列表""" + manager = _get_manager() + return manager.get_images(image_type, region_id) + +@jingrow.whitelist() +def get_aliyun_regions(): + """通过阿里云SDK实时获取可用地域列表""" + manager = _get_manager() + return manager.get_regions() \ No newline at end of file -- 2.48.1 From 48d03160cbf30d1630eb5fc39b99d8649ae967db Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 14:24:24 +0800 Subject: [PATCH 006/250] =?UTF-8?q?=E6=96=B0=E5=BB=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=8C=87=E5=90=91NewJsiteServer.vue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/src2/router.js b/dashboard/src2/router.js index f1f0053..39f9b97 100644 --- a/dashboard/src2/router.js +++ b/dashboard/src2/router.js @@ -108,7 +108,7 @@ let router = createRouter({ { name: 'New Server', path: '/servers/new', - component: () => import('./pages/NewServer.vue'), + component: () => import('./pages/NewJsiteServer.vue'), }, { name: 'Billing', -- 2.48.1 From 94118baf15c2138d02c85aad4113d1fd0d6aed79 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 17:35:00 +0800 Subject: [PATCH 007/250] =?UTF-8?q?Order=E8=AE=A2=E5=8D=95=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=A2=9E=E5=8A=A0=E6=96=B0=E5=BB=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E5=92=8C=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=BB=AD=E8=B4=B9?= =?UTF-8?q?=EF=BC=8C=E5=89=8D=E7=AB=AF=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E7=82=B9=E5=87=BB=E6=96=B0=E5=BB=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=97=B6=E5=BC=B9=E5=87=BA=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/pages/NewJsiteServer.vue | 69 +++++++------ jcloud/api/aliyun_server_light.py | 127 +++++++++++++++++++++++- jcloud/api/billing.py | 121 ++++++++++++++++++++++ jcloud/jcloud/pagetype/order/order.json | 11 +- jcloud/jcloud/pagetype/order/order.py | 2 +- 5 files changed, 294 insertions(+), 36 deletions(-) diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index a5882dd..7f03ae4 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -136,8 +136,13 @@ -

服务器创建成功!

-

您的 Jsite 服务器已成功开通。

+

支付成功!

+

+ 您的订单已支付成功,服务器正在创建中。创建并启动需要 3-5 分钟,请耐心等待。 +

+

+ 创建完成后,您将收到通知,也可以在服务器列表中查看状态。 +

@@ -437,9 +442,6 @@ export default { this.isProcessingPayment = false; this.paymentSuccess = true; - console.log('余额支付成功,更新状态:', { - paymentSuccess: this.paymentSuccess - }); }, onError(error) { this.error = error.message || '余额支付处理失败'; @@ -462,6 +464,7 @@ export default { toast.success('支付页面已在新窗口打开'); + // 开始轮询支付状态 this.startPaymentCheck(); }, onError(error) { @@ -483,6 +486,7 @@ export default { this.paymentQrCode = response.payment_url; this.paymentQrCodeImage = response.qr_code_image || null; + // 开始轮询支付状态 this.startPaymentCheck(); }, onError(error) { @@ -495,13 +499,17 @@ export default { checkPaymentStatus() { return { url: 'jcloud.api.billing.check_server_order_payment_status', + params: { + order_id: this.order?.order_id + }, validate() { if (!this.order || !this.order.order_id) { throw new DashboardError('缺少订单信息'); } }, onSuccess(data) { - if (data && data.status === '交易成功') { + if (data && data.status === '已支付') { + // 支付成功,停止轮询 this.stopPaymentCheck(); const orderData = { ...this.order, ...(data.order || {}) }; @@ -511,7 +519,11 @@ export default { } } }; - } + }, + + }, + beforeUnmount() { + this.stopPaymentCheck(); }, mounted() { console.log('组件mounted,开始获取地域列表'); @@ -558,6 +570,24 @@ export default { this.isLoading = false; }, + startPaymentCheck() { + this.isProcessingPayment = false; + + this.checkInterval = setInterval(() => { + this.$resources.checkPaymentStatus.submit(); + }, 3000); + + // 15分钟后停止检查 + setTimeout(() => { + this.stopPaymentCheck(); + }, 900000); + }, + stopPaymentCheck() { + if (this.checkInterval) { + clearInterval(this.checkInterval); + this.checkInterval = null; + } + }, processPayment() { this.isProcessingPayment = true; this.error = null; @@ -588,26 +618,9 @@ export default { }); } }, - startPaymentCheck() { - this.isProcessingPayment = false; - - this.checkInterval = setInterval(() => { - this.$resources.checkPaymentStatus.submit(); - }, 3000); - - // 15分钟后停止检查 - setTimeout(() => { - this.stopPaymentCheck(); - }, 900000); - }, - stopPaymentCheck() { - if (this.checkInterval) { - clearInterval(this.checkInterval); - this.checkInterval = null; - } - }, + + close() { - this.stopPaymentCheck(); this.show = false; this.$emit('success'); }, @@ -656,9 +669,7 @@ export default { return name; } }, - beforeUnmount() { - this.stopPaymentCheck(); - } + }; diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index bbf3a0b..2ba3d14 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -373,4 +373,129 @@ def get_aliyun_images(image_type='system', region_id='cn-shanghai'): def get_aliyun_regions(): """通过阿里云SDK实时获取可用地域列表""" manager = _get_manager() - return manager.get_regions() \ No newline at end of file + return manager.get_regions() + +# 服务器订单和创建相关 + +@jingrow.whitelist() +def create_server_order(**kwargs): + """创建服务器订单""" + try: + plan_id = kwargs.get('plan_id') + image_id = kwargs.get('image_id') + period = kwargs.get('period', 1) + region_id = kwargs.get('region_id', 'cn-shanghai') + + if not plan_id or not image_id: + jingrow.throw("缺少必要参数") + + from jcloud.utils import get_current_team + team = get_current_team(True) + + # 获取套餐价格 + plans_response = get_aliyun_plans(region_id) + selected_plan = None + if plans_response and plans_response.get('success'): + plans = plans_response['data'].get('plans', []) + selected_plan = next((p for p in plans if p.get('plan_id') == plan_id), None) + + if not selected_plan: + jingrow.throw("套餐不存在") + + # 计算价格 + monthly_price = float(selected_plan.get('origin_price', 0)) + total_amount = monthly_price * period + + # 生成订单号 + from datetime import datetime + import random + order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6)) + + # 创建订单 + order = jingrow.get_pg({ + "pagetype": "Order", + "order_id": order_id, + "order_type": "新建服务器", + "team": team.name, + "status": "待支付", + "total_amount": total_amount, + "title": f"新建服务器 - {region_id}", + "description": f"{selected_plan.get('core')}核/{selected_plan.get('memory')}GB/{selected_plan.get('disk_size')}GB, {period}个月", + "server_config": { + "plan_id": plan_id, + "image_id": image_id, + "period": period, + "region_id": region_id, + "monthly_price": monthly_price + } + }) + + order.insert(ignore_permissions=True) + jingrow.db.commit() + + return {"success": True, "order": order.as_dict()} + + except Exception as e: + jingrow.log_error("创建服务器订单失败", str(e)) + return {"success": False, "message": str(e)} + +def create_server_async(order_name): + """异步创建服务器""" + try: + order = jingrow.get_pg("Order", order_name) + if not order: + raise Exception("订单不存在") + + # 获取配置 + config = order.server_config or {} + plan_id = config.get('plan_id') + image_id = config.get('image_id') + period = config.get('period', 1) + region_id = config.get('region_id', 'cn-shanghai') + + # 调用阿里云API创建实例 + result = create_aliyun_instance(plan_id, image_id, period, region_id) + + if not result or not result.get('success'): + raise Exception(f"阿里云创建失败: {result.get('message', '未知错误')}") + + instance_id = result['data']['instance_id'] + + # 创建服务器记录 + server = jingrow.get_pg({ + "pagetype": "Jsite Server", + "instance_id": instance_id, + "team": order.team, + "order": order.name, + "status": "Running", + "region_id": region_id, + "plan_id": plan_id, + "image_id": image_id, + "period": period, + "monthly_price": config.get('monthly_price', 0), + "total_amount": order.total_amount, + "payment_method": order.payment_method, + "created_at": jingrow.utils.now(), + "expires_at": jingrow.utils.add_months(jingrow.utils.nowdate(), period) + }) + + server.insert(ignore_permissions=True) + + # 更新订单状态 + order.status = "交易成功" + order.instance_id = instance_id + order.server_record = server.name + order.save(ignore_permissions=True) + + jingrow.db.commit() + + return True + + except Exception as e: + jingrow.log_error("服务器创建失败", f"订单 {order_name}: {str(e)}") + # 更新订单状态为失败 + try: + jingrow.db.set_value("Order", order_name, "status", "创建失败") + except: + pass + raise e \ No newline at end of file diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py index 0adbc82..1bfc9f0 100644 --- a/jcloud/api/billing.py +++ b/jcloud/api/billing.py @@ -1088,6 +1088,9 @@ def handle_order_payment_complete(order_id): process_balance_recharge(order) elif order.order_type == "网站续费": process_site_renew(order_id) + elif order.order_type == "Jsite Server": + # 异步创建服务器 + jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) return True except Exception as e: @@ -1869,3 +1872,121 @@ def get_balance_transactions(page=1, page_size=20, search=None): "total": 0, "error": str(e) } + +# Jsite Server 相关功能 + +@jingrow.whitelist() +def process_balance_payment_for_server_order(order_id): + """使用余额支付服务器订单""" + try: + team = get_current_team(True) + order = jingrow.get_pg("Order", {"order_id": order_id}) + + if not order or order.team != team.name: + jingrow.throw("订单不存在或无权限") + + if order.status != "待支付": + return {"success": False, "message": "订单已处理"} + + balance = team.get_balance() + if balance < order.total_amount: + return {"success": False, "message": "余额不足"} + + # 扣款 + balance_transaction = jingrow.get_pg({ + "pagetype": "Balance Transaction", + "team": team.name, + "type": "Adjustment", + "source": "Prepaid Credits", + "amount": -order.total_amount, + "description": f"服务器购买: {order.title}" + }) + balance_transaction.insert(ignore_permissions=True) + balance_transaction.submit() + + # 更新订单状态 + order.status = "已支付" + order.payment_method = "余额支付" + order.save(ignore_permissions=True) + + # 异步创建服务器 + jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) + + return {"success": True, "message": "支付成功,服务器创建中"} + + except Exception as e: + jingrow.log_error("服务器支付失败", str(e)) + return {"success": False, "message": str(e)} + +@jingrow.whitelist() +def process_alipay_server_order(order_id): + """支付宝支付服务器订单""" + team = get_current_team(True) + order = jingrow.get_pg("Order", {"order_id": order_id}) + + if not order or order.team != team.name: + jingrow.throw("订单不存在或无权限") + + if order.status != "待支付": + jingrow.throw("订单已处理") + + from jcloud.api.payment.alipay import AlipayAPI + api = AlipayAPI() + + payment_url = api.generate_payment_url( + order_id=order_id, + amount=order.total_amount, + subject=order.title, + team_name=team.name + ) + + order.payment_method = "支付宝" + order.save(ignore_permissions=True) + + return {"payment_url": payment_url, "order_id": order_id} + +@jingrow.whitelist() +def process_wechatpay_server_order(order_id): + """微信支付服务器订单""" + team = get_current_team(True) + order = jingrow.get_pg("Order", {"order_id": order_id}) + + if not order or order.team != team.name: + jingrow.throw("订单不存在或无权限") + + if order.status != "待支付": + jingrow.throw("订单已处理") + + wechat_pay = WeChatPayAPI() + qr_code_url = wechat_pay.generate_payment_url( + order_id=order_id, + amount=order.total_amount, + subject=order.title, + team_name=team.name + ) + + order.payment_method = "微信支付" + order.save(ignore_permissions=True) + + return {"qr_code_url": qr_code_url, "order_id": order_id} + +@jingrow.whitelist() +def check_server_order_payment_status(order_id): + """检查服务器订单支付状态""" + try: + order = jingrow.get_pg("Order", {"order_id": order_id}) + if not order: + jingrow.throw(f"找不到订单: {order_id}") + + return { + "success": True, + "status": order.status, + "order": order.as_dict() + } + + except Exception as e: + jingrow.log_error("服务器订单错误", f"检查服务器订单状态失败: {str(e)}") + return { + "success": False, + "message": f"检查订单状态失败: {str(e)}" + } diff --git a/jcloud/jcloud/pagetype/order/order.json b/jcloud/jcloud/pagetype/order/order.json index 333313a..9508344 100644 --- a/jcloud/jcloud/pagetype/order/order.json +++ b/jcloud/jcloud/pagetype/order/order.json @@ -2,7 +2,6 @@ "actions": [], "allow_rename": 1, "creation": "2025-03-23 21:29:54.329381", - "pagetype": "PageType", "engine": "InnoDB", "field_order": [ "title", @@ -37,7 +36,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Status", - "options": "\u5f85\u652f\u4ed8\n\u5df2\u652f\u4ed8\n\u4ea4\u6613\u6210\u529f\n\u5df2\u53d6\u6d88\n\u5df2\u9000\u6b3e", + "options": "待支付\n已支付\n交易成功\n已取消\n已退款", "read_only": 1 }, { @@ -45,7 +44,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Payment Method", - "options": "\n\u652f\u4ed8\u5b9d\n\u5fae\u4fe1\u652f\u4ed8\n\u4f59\u989d\u652f\u4ed8\n\u94f6\u884c\u8f6c\u8d26\n\u5176\u4ed6", + "options": "\n支付宝\n微信支付\n余额支付\n银行转账\n其他", "read_only": 1 }, { @@ -78,18 +77,19 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Order Type", - "options": "\n\u4f59\u989d\u5145\u503c\n\u65b0\u5efa\u7f51\u7ad9\n\u7f51\u7ad9\u7eed\u8d39\n\u57df\u540d\u7eed\u8d39", + "options": "\n余额充值\n新建网站\n网站续费\n域名续费\n新建服务器\n服务器续费", "read_only": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-03-26 03:34:52.624889", + "modified": "2025-07-27 17:29:16.088188", "modified_by": "Administrator", "module": "Jcloud", "name": "Order", "owner": "Administrator", + "pagetype": "PageType", "permissions": [ { "create": 1, @@ -104,6 +104,7 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", "states": [] diff --git a/jcloud/jcloud/pagetype/order/order.py b/jcloud/jcloud/pagetype/order/order.py index 816f515..d7d807d 100644 --- a/jcloud/jcloud/pagetype/order/order.py +++ b/jcloud/jcloud/pagetype/order/order.py @@ -16,7 +16,7 @@ class Order(Document): description: DF.Data | None order_id: DF.Data | None - order_type: DF.Literal["", "\u4f59\u989d\u5145\u503c", "\u65b0\u5efa\u7f51\u7ad9", "\u7f51\u7ad9\u7eed\u8d39", "\u57df\u540d\u7eed\u8d39"] + order_type: DF.Literal["", "\u4f59\u989d\u5145\u503c", "\u65b0\u5efa\u7f51\u7ad9", "\u7f51\u7ad9\u7eed\u8d39", "\u57df\u540d\u7eed\u8d39", "\u65b0\u5efa\u670d\u52a1\u5668", "\u670d\u52a1\u5668\u7eed\u8d39"] payment_method: DF.Literal["", "\u652f\u4ed8\u5b9d", "\u5fae\u4fe1\u652f\u4ed8", "\u4f59\u989d\u652f\u4ed8", "\u94f6\u884c\u8f6c\u8d26", "\u5176\u4ed6"] status: DF.Literal["\u5f85\u652f\u4ed8", "\u5df2\u652f\u4ed8", "\u4ea4\u6613\u6210\u529f", "\u5df2\u53d6\u6d88", "\u5df2\u9000\u6b3e"] team: DF.Link | None -- 2.48.1 From 49ec83539c61d9565c5d027ab28111ddb17f2f15 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 19:21:55 +0800 Subject: [PATCH 008/250] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E8=AE=A2=E5=8D=95=E6=97=B6=E7=9A=84=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E5=92=8C=E4=BD=99=E9=A2=9D=E6=94=AF=E4=BB=98=E7=9A=84?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/pages/NewJsiteServer.vue | 210 ++++++++++++------------ jcloud/api/aliyun_server_light.py | 2 +- jcloud/api/billing.py | 121 +------------- 3 files changed, 111 insertions(+), 222 deletions(-) diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index 7f03ae4..335edcd 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -8,111 +8,109 @@ 请选择服务器配置并填写相关信息,点击"创建"后将自动为您开通。

-
-
- - - -
- 正在加载地域列表... -
-
- 已加载 {{ regions.length }} 个地域 -
+
+ + + +
+ 正在加载地域列表...
-
- - -
-
- - -
-
- - +
+ 已加载 {{ regions.length }} 个地域
+
+
+ + +
+
+ + +
+
+ + +
- -
-
-
月度费用
-
- ¥ {{ getSelectedPlanPrice() }} - (月付) + +
+
+
月度费用
+
+ ¥ {{ getSelectedPlanPrice() }} + (月付) +
+
+
+
购买时长
+
{{ period }} 个月
+
+
+
总计
+
¥ {{ getTotalAmount() }}
+
+
+ + +
+ +
+ + +
-
-
购买时长
-
{{ period }} 个月
-
-
-
总计
-
¥ {{ getTotalAmount() }}
-
-
- - -
- -
- - - + + - - -
+
+
+
-
- {{ error }} -
- +
+ {{ error }} +
@@ -419,7 +417,7 @@ export default { // 处理余额支付 processBalancePayment() { return { - url: 'jcloud.api.billing.process_balance_payment_for_server_order', + url: 'jcloud.api.billing.process_balance_payment_for_order', validate() { if (!this.order || !this.order.order_id) { throw new DashboardError('缺少订单信息'); @@ -452,7 +450,7 @@ export default { // 处理支付宝支付 processAlipayPayment() { return { - url: 'jcloud.api.billing.process_alipay_server_order', + url: 'jcloud.api.billing.process_alipay_order', validate() { if (!this.order || !this.order.order_id) { throw new DashboardError('缺少订单信息'); @@ -476,7 +474,7 @@ export default { // 处理微信支付 processWechatPayment() { return { - url: 'jcloud.api.billing.process_wechatpay_server_order', + url: 'jcloud.api.billing.process_wechatpay_order', validate() { if (!this.order || !this.order.order_id) { throw new DashboardError('缺少订单信息'); @@ -498,7 +496,7 @@ export default { // 检查支付状态 checkPaymentStatus() { return { - url: 'jcloud.api.billing.check_server_order_payment_status', + url: 'jcloud.api.billing.check_site_order_payment_status', params: { order_id: this.order?.order_id }, @@ -667,6 +665,14 @@ export default { return `${name} (${platform})`; } return name; + }, + getPaymentMethodName(method) { + const methodNames = { + 'balance': '余额支付', + 'alipay': '支付宝', + 'wechatpay': '微信支付' + }; + return methodNames[method] || '未知方式'; } }, diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 2ba3d14..0c2b1e1 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -419,7 +419,7 @@ def create_server_order(**kwargs): "team": team.name, "status": "待支付", "total_amount": total_amount, - "title": f"新建服务器 - {region_id}", + "title": f"{region_id}", "description": f"{selected_plan.get('core')}核/{selected_plan.get('memory')}GB/{selected_plan.get('disk_size')}GB, {period}个月", "server_config": { "plan_id": plan_id, diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py index 1bfc9f0..0c1e05a 100644 --- a/jcloud/api/billing.py +++ b/jcloud/api/billing.py @@ -43,6 +43,7 @@ from jcloud.utils.billing import ( ) from jcloud.utils.mpesa_utils import create_mpesa_request_log from jcloud.api.payment.wechatpay import WeChatPayAPI +from jcloud.api.payment.alipay import AlipayAPI @jingrow.whitelist() def get_publishable_key_and_setup_intent(): @@ -1223,7 +1224,6 @@ def create_alipay_order_for_recharge(amount): jingrow.db.commit() # 直接使用AlipayAPI类生成支付链接 - from jcloud.api.payment.alipay import AlipayAPI api = AlipayAPI() try: @@ -1451,7 +1451,7 @@ def process_balance_payment_for_order(order_id): "type": "Adjustment", "source": "Prepaid Credits", "amount": -1 * float(order.total_amount), # 使用负数表示扣减 - "description": f"新建网站: {order.title}", + "description": f"{order.order_type}-{order.title}", "paid_via_local_pg": 1 }) balance_transaction.flags.ignore_permissions = True @@ -1590,7 +1590,6 @@ def process_alipay_order(order_id): amount = round(float(order.total_amount), 2) # 直接使用AlipayAPI类生成支付链接 - from jcloud.api.payment.alipay import AlipayAPI api = AlipayAPI() try: @@ -1874,119 +1873,3 @@ def get_balance_transactions(page=1, page_size=20, search=None): } # Jsite Server 相关功能 - -@jingrow.whitelist() -def process_balance_payment_for_server_order(order_id): - """使用余额支付服务器订单""" - try: - team = get_current_team(True) - order = jingrow.get_pg("Order", {"order_id": order_id}) - - if not order or order.team != team.name: - jingrow.throw("订单不存在或无权限") - - if order.status != "待支付": - return {"success": False, "message": "订单已处理"} - - balance = team.get_balance() - if balance < order.total_amount: - return {"success": False, "message": "余额不足"} - - # 扣款 - balance_transaction = jingrow.get_pg({ - "pagetype": "Balance Transaction", - "team": team.name, - "type": "Adjustment", - "source": "Prepaid Credits", - "amount": -order.total_amount, - "description": f"服务器购买: {order.title}" - }) - balance_transaction.insert(ignore_permissions=True) - balance_transaction.submit() - - # 更新订单状态 - order.status = "已支付" - order.payment_method = "余额支付" - order.save(ignore_permissions=True) - - # 异步创建服务器 - jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) - - return {"success": True, "message": "支付成功,服务器创建中"} - - except Exception as e: - jingrow.log_error("服务器支付失败", str(e)) - return {"success": False, "message": str(e)} - -@jingrow.whitelist() -def process_alipay_server_order(order_id): - """支付宝支付服务器订单""" - team = get_current_team(True) - order = jingrow.get_pg("Order", {"order_id": order_id}) - - if not order or order.team != team.name: - jingrow.throw("订单不存在或无权限") - - if order.status != "待支付": - jingrow.throw("订单已处理") - - from jcloud.api.payment.alipay import AlipayAPI - api = AlipayAPI() - - payment_url = api.generate_payment_url( - order_id=order_id, - amount=order.total_amount, - subject=order.title, - team_name=team.name - ) - - order.payment_method = "支付宝" - order.save(ignore_permissions=True) - - return {"payment_url": payment_url, "order_id": order_id} - -@jingrow.whitelist() -def process_wechatpay_server_order(order_id): - """微信支付服务器订单""" - team = get_current_team(True) - order = jingrow.get_pg("Order", {"order_id": order_id}) - - if not order or order.team != team.name: - jingrow.throw("订单不存在或无权限") - - if order.status != "待支付": - jingrow.throw("订单已处理") - - wechat_pay = WeChatPayAPI() - qr_code_url = wechat_pay.generate_payment_url( - order_id=order_id, - amount=order.total_amount, - subject=order.title, - team_name=team.name - ) - - order.payment_method = "微信支付" - order.save(ignore_permissions=True) - - return {"qr_code_url": qr_code_url, "order_id": order_id} - -@jingrow.whitelist() -def check_server_order_payment_status(order_id): - """检查服务器订单支付状态""" - try: - order = jingrow.get_pg("Order", {"order_id": order_id}) - if not order: - jingrow.throw(f"找不到订单: {order_id}") - - return { - "success": True, - "status": order.status, - "order": order.as_dict() - } - - except Exception as e: - jingrow.log_error("服务器订单错误", f"检查服务器订单状态失败: {str(e)}") - return { - "success": False, - "message": f"检查订单状态失败: {str(e)}" - } -- 2.48.1 From 8cbe43237f1777665f13085d531e4a610b754316 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 19:28:24 +0800 Subject: [PATCH 009/250] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8Dcheck=5Fsite?= =?UTF-8?q?=5Forder=5Fpayment=5Fstatus=E4=B8=BAcheck=5Forder=5Fpayment=5Fs?= =?UTF-8?q?tatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/components/OrderCheckout.vue | 2 +- dashboard/src2/components/SiteRenewalDialog.vue | 2 +- dashboard/src2/pages/NewJsiteServer.vue | 2 +- jcloud/api/billing.py | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dashboard/src2/components/OrderCheckout.vue b/dashboard/src2/components/OrderCheckout.vue index d7f5a3b..4ddeef6 100644 --- a/dashboard/src2/components/OrderCheckout.vue +++ b/dashboard/src2/components/OrderCheckout.vue @@ -265,7 +265,7 @@ export default { }, checkPaymentStatus() { return { - url: 'jcloud.api.billing.check_site_order_payment_status', + url: 'jcloud.api.billing.check_order_payment_status', params: { order_id: this.order.order_id || this.order.name }, diff --git a/dashboard/src2/components/SiteRenewalDialog.vue b/dashboard/src2/components/SiteRenewalDialog.vue index e5663f3..273c82d 100644 --- a/dashboard/src2/components/SiteRenewalDialog.vue +++ b/dashboard/src2/components/SiteRenewalDialog.vue @@ -431,7 +431,7 @@ export default { }, checkPaymentStatus() { return { - url: 'jcloud.api.billing.check_site_order_payment_status', + url: 'jcloud.api.billing.check_order_payment_status', params: { order_id: this.order?.order_id }, diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index 335edcd..f903dcb 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -496,7 +496,7 @@ export default { // 检查支付状态 checkPaymentStatus() { return { - url: 'jcloud.api.billing.check_site_order_payment_status', + url: 'jcloud.api.billing.check_order_payment_status', params: { order_id: this.order?.order_id }, diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py index 0c1e05a..1b6468e 100644 --- a/jcloud/api/billing.py +++ b/jcloud/api/billing.py @@ -1677,7 +1677,7 @@ def process_wechatpay_order(order_id): jingrow.throw(f"创建微信支付订单失败") @jingrow.whitelist() -def check_site_order_payment_status(order_id): +def check_order_payment_status(order_id): """检查订单支付状态""" try: # 获取订单信息 @@ -1872,4 +1872,3 @@ def get_balance_transactions(page=1, page_size=20, search=None): "error": str(e) } -# Jsite Server 相关功能 -- 2.48.1 From 207190822f3243133d964c24e526d555f8aed8f6 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 20:45:51 +0800 Subject: [PATCH 010/250] =?UTF-8?q?=E6=96=B0=E5=BB=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=97=B6=E4=BD=99=E9=A2=9D=E6=94=AF=E4=BB=98=E6=94=B9?= =?UTF-8?q?=E6=88=90=E4=BD=BF=E7=94=A8process=5Fbalance=5Fpayment=5Ffor=5F?= =?UTF-8?q?server=5Forder=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/pages/NewJsiteServer.vue | 2 +- jcloud/api/billing.py | 72 ++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index f903dcb..402ec48 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -417,7 +417,7 @@ export default { // 处理余额支付 processBalancePayment() { return { - url: 'jcloud.api.billing.process_balance_payment_for_order', + url: 'jcloud.api.billing.process_balance_payment_for_server_order', validate() { if (!this.order || !this.order.order_id) { throw new DashboardError('缺少订单信息'); diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py index 1b6468e..5a8c97d 100644 --- a/jcloud/api/billing.py +++ b/jcloud/api/billing.py @@ -1089,7 +1089,7 @@ def handle_order_payment_complete(order_id): process_balance_recharge(order) elif order.order_type == "网站续费": process_site_renew(order_id) - elif order.order_type == "Jsite Server": + elif order.order_type == "新建服务器": # 异步创建服务器 jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) @@ -1568,6 +1568,76 @@ def process_balance_payment_for_renew_order(order_id): "message": _(f"余额支付失败: {str(e)}") } +@jingrow.whitelist() +def process_balance_payment_for_server_order(order_id): + """使用账户余额支付订单""" + try: + # 获取当前用户团队 + team = get_current_team(True) + + # 获取订单信息 + order = jingrow.get_pg("Order", {"order_id": order_id}) + if not order: + jingrow.throw(f"找不到订单: {order_id}") + + # 验证订单是否属于当前团队 + if order.team != team.name: + jingrow.throw("您没有权限支付此订单") + + # 检查订单状态 + if order.status != "待支付": + return { + "success": False, + "message": "该订单已支付或已取消" + } + + # 使用 Team 类的 get_balance 方法获取余额 + balance = team.get_balance() + + # 检查余额是否足够 + if balance < order.total_amount: + return { + "success": False, + "message": "余额不足" + } + + # 创建余额交易记录(扣款) + balance_transaction = jingrow.get_pg({ + "pagetype": "Balance Transaction", + "team": team.name, + "type": "Adjustment", + "source": "Prepaid Credits", + "amount": -1 * float(order.total_amount), # 使用负数表示扣减 + "description": f"{order.order_type}-{order.title}", + "paid_via_local_pg": 1 + }) + balance_transaction.flags.ignore_permissions = True + balance_transaction.insert() + balance_transaction.submit() + + # 更新订单状态 + order.status = "已支付" + order.payment_method = "余额支付" + order.save(ignore_permissions=True) + jingrow.db.commit() + + # 异步执行服务器创建 + jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) + jingrow.log_error("异步执行创建服务器", f"订单ID: {order_id}") + return { + "status": "Success", + "message": "支付成功", + "order": order.as_dict() + } + + except Exception as e: + jingrow.log_error("支付错误", f"余额支付失败: {str(e)}") + return { + "status": "Error", + "message": f"余额支付失败: {str(e)}" + } + + @jingrow.whitelist() def process_alipay_order(order_id): """创建支付宝订单支付链接""" -- 2.48.1 From e9f327af932e189fa7b2c8a4f3d2cb706c261020 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 22:29:04 +0800 Subject: [PATCH 011/250] =?UTF-8?q?=E6=9B=B4=E6=96=B0Jsite=20Server?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=96=B0=E5=BB=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=97=B6=E7=82=B9=E5=87=BB=E7=A1=AE=E8=AE=A4=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=90=8E=E5=90=8C=E6=97=B6=E5=88=9B=E5=BB=BA=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E8=AE=A2=E5=8D=95=E5=92=8CJsite=20Server=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/pages/NewJsiteServer.vue | 23 +++++- jcloud/api/aliyun_server_light.py | 76 ++++++++----------- .../pagetype/jsite_server/jsite_server.json | 32 ++++++-- .../pagetype/jsite_server/jsite_server.py | 4 +- 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index 402ec48..bd4a6ee 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -136,10 +136,10 @@

支付成功!

- 您的订单已支付成功,服务器正在创建中。创建并启动需要 3-5 分钟,请耐心等待。 + 您的订单已支付成功,服务器记录已创建。服务器正在后台创建中,创建并启动需要 3-5 分钟,请耐心等待。

- 创建完成后,您将收到通知,也可以在服务器列表中查看状态。 + 您可以在服务器列表中查看服务器状态,创建完成后状态将更新为"Running"。

@@ -267,6 +267,7 @@ export default { success: false, // 支付相关状态 order: null, + server: null, // 服务器记录信息 showPaymentProcessing: false, paymentSuccess: false, isProcessingPayment: false, @@ -400,9 +401,11 @@ export default { console.log('订单创建成功,完整响应:', data); console.log('订单ID:', data.order?.order_id); + console.log('服务器记录:', data.server); // 显示订单支付界面 this.order = data.order; + this.server = data.server; // 保存服务器记录信息 this.showPaymentProcessing = true; // 立即处理支付 @@ -436,7 +439,13 @@ export default { ...(response.order || {}), status: '已支付' }; - this.$emit('success', orderData); + + // 同时发送服务器记录信息 + const serverData = { + ...this.server + }; + + this.$emit('success', { order: orderData, server: serverData }); this.isProcessingPayment = false; this.paymentSuccess = true; @@ -511,7 +520,13 @@ export default { this.stopPaymentCheck(); const orderData = { ...this.order, ...(data.order || {}) }; - this.$emit('success', orderData); + + // 同时发送服务器记录信息 + const serverData = { + ...this.server + }; + + this.$emit('success', { order: orderData, server: serverData }); this.paymentSuccess = true; } diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 0c2b1e1..1e99f2a 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -411,7 +411,7 @@ def create_server_order(**kwargs): import random order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6)) - # 创建订单 + # 创建订单记录 order = jingrow.get_pg({ "pagetype": "Order", "order_id": order_id, @@ -420,20 +420,28 @@ def create_server_order(**kwargs): "status": "待支付", "total_amount": total_amount, "title": f"{region_id}", - "description": f"{selected_plan.get('core')}核/{selected_plan.get('memory')}GB/{selected_plan.get('disk_size')}GB, {period}个月", - "server_config": { - "plan_id": plan_id, - "image_id": image_id, - "period": period, - "region_id": region_id, - "monthly_price": monthly_price - } + "description": f"{selected_plan.get('core')}核/{selected_plan.get('memory')}GB/{selected_plan.get('disk_size')}GB, {period}个月" }) - order.insert(ignore_permissions=True) + + # 创建服务器记录 + server = jingrow.get_pg({ + "pagetype": "Jsite Server", + "team": team.name, + "status": "Pending", + "region": region_id, + "system_image": image_id, + "end_date": jingrow.utils.add_months(jingrow.utils.nowdate(), period), + "title": f"{region_id} - {selected_plan.get('core')}核/{selected_plan.get('memory')}GB", + "planid": plan_id, + "period": period, + "order_id": order_id + }) + server.insert(ignore_permissions=True) + jingrow.db.commit() - return {"success": True, "order": order.as_dict()} + return {"success": True, "order": order.as_dict(), "server": server.as_dict()} except Exception as e: jingrow.log_error("创建服务器订单失败", str(e)) @@ -446,12 +454,16 @@ def create_server_async(order_name): if not order: raise Exception("订单不存在") - # 获取配置 - config = order.server_config or {} - plan_id = config.get('plan_id') - image_id = config.get('image_id') - period = config.get('period', 1) - region_id = config.get('region_id', 'cn-shanghai') + # 查找对应的服务器记录(通过订单ID) + server = jingrow.get_pg("Jsite Server", {"order_id": order.order_id}) + if not server: + raise Exception("找不到对应的服务器记录") + + # 从服务器记录中获取配置信息 + region_id = server.region or 'cn-shanghai' + image_id = server.system_image or 'e9363571cf2444aba422b17470285465' + plan_id = server.planid or 'swas.s.c2m1s30b1.linux' + period = server.period or 1 # 调用阿里云API创建实例 result = create_aliyun_instance(plan_id, image_id, period, region_id) @@ -461,30 +473,13 @@ def create_server_async(order_name): instance_id = result['data']['instance_id'] - # 创建服务器记录 - server = jingrow.get_pg({ - "pagetype": "Jsite Server", - "instance_id": instance_id, - "team": order.team, - "order": order.name, - "status": "Running", - "region_id": region_id, - "plan_id": plan_id, - "image_id": image_id, - "period": period, - "monthly_price": config.get('monthly_price', 0), - "total_amount": order.total_amount, - "payment_method": order.payment_method, - "created_at": jingrow.utils.now(), - "expires_at": jingrow.utils.add_months(jingrow.utils.nowdate(), period) - }) - - server.insert(ignore_permissions=True) + # 更新服务器记录状态 + server.status = "Active" + server.instance_id = instance_id + server.save(ignore_permissions=True) # 更新订单状态 order.status = "交易成功" - order.instance_id = instance_id - order.server_record = server.name order.save(ignore_permissions=True) jingrow.db.commit() @@ -493,9 +488,4 @@ def create_server_async(order_name): except Exception as e: jingrow.log_error("服务器创建失败", f"订单 {order_name}: {str(e)}") - # 更新订单状态为失败 - try: - jingrow.db.set_value("Order", order_name, "status", "创建失败") - except: - pass raise e \ No newline at end of file diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index c560709..f100f10 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -9,19 +9,21 @@ "team", "column_break_4", "status", - "plan", + "order_id", "server_section", "instance_id", "cpu", "disk_size", "region", "system_image", + "planid", "column_break_aliyun", "end_date", "memory", "bandwidth", "ip", "running_status", + "period", "ssh_section", "ssh_user", "ssh_port", @@ -55,12 +57,6 @@ "label": "团队", "options": "Team" }, - { - "fieldname": "plan", - "fieldtype": "Link", - "label": "计划", - "options": "Server Plan" - }, { "fetch_from": "virtual_machine.public_ip_address", "fieldname": "ip", @@ -166,11 +162,31 @@ "in_list_view": 1, "label": "运行状态", "read_only": 1 + }, + { + "fieldname": "order_id", + "fieldtype": "Data", + "label": "订单ID", + "read_only": 1 + }, + { + "fieldname": "planid", + "fieldtype": "Data", + "hidden": 1, + "label": "套餐ID", + "read_only": 1 + }, + { + "fieldname": "period", + "fieldtype": "Int", + "hidden": 1, + "label": "购买时长(月)", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-26 13:27:00.795541", + "modified": "2025-07-27 22:04:39.820405", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index 7a96902..0c0a705 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -21,7 +21,9 @@ class JsiteServer(Document): instance_id: DF.Data | None ip: DF.Data | None memory: DF.Data | None - plan: DF.Link | None + order_id: DF.Data | None + period: DF.Int + planid: DF.Data | None region: DF.Data | None root_public_key: DF.Code | None running_status: DF.Data | None -- 2.48.1 From 27060bfe67f05b4e4122ec4ee22a0e87b83a1d30 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 27 Jul 2025 23:30:51 +0800 Subject: [PATCH 012/250] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=98=E6=AC=BE?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E5=90=8E=E5=88=9B=E5=BB=BA=E4=BA=91=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 4 +++- jcloud/jcloud/pagetype/jsite_server/jsite_server.json | 8 +++----- jcloud/jcloud/pagetype/jsite_server/jsite_server.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 1e99f2a..ad707f8 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -471,10 +471,12 @@ def create_server_async(order_name): if not result or not result.get('success'): raise Exception(f"阿里云创建失败: {result.get('message', '未知错误')}") - instance_id = result['data']['instance_id'] + # 阿里云API返回的是instance_ids数组,取第一个实例ID + instance_id = result['data']['instance_ids'][0] # 更新服务器记录状态 server.status = "Active" + server.running_status = "运行中" server.instance_id = instance_id server.save(ignore_permissions=True) diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index f100f10..7106632 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -117,7 +117,6 @@ "read_only": 1 }, { - "collapsible": 1, "fieldname": "server_section", "fieldtype": "Section Break", "label": "服务器信息" @@ -158,9 +157,10 @@ { "fetch_from": "virtual_machine.public_ip_address", "fieldname": "running_status", - "fieldtype": "Data", + "fieldtype": "Select", "in_list_view": 1, "label": "运行状态", + "options": "\n运行中\n已停机", "read_only": 1 }, { @@ -172,21 +172,19 @@ { "fieldname": "planid", "fieldtype": "Data", - "hidden": 1, "label": "套餐ID", "read_only": 1 }, { "fieldname": "period", "fieldtype": "Int", - "hidden": 1, "label": "购买时长(月)", "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-27 22:04:39.820405", + "modified": "2025-07-27 23:19:16.648233", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index 0c0a705..b505a1a 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -26,7 +26,7 @@ class JsiteServer(Document): planid: DF.Data | None region: DF.Data | None root_public_key: DF.Code | None - running_status: DF.Data | None + running_status: DF.Literal["", "\u8fd0\u884c\u4e2d", "\u5df2\u505c\u673a"] ssh_port: DF.Int ssh_user: DF.Data | None status: DF.Literal["Pending", "Installing", "Active", "Broken", "Archived"] -- 2.48.1 From c86761e977c2e2bf2f5c3f7ba525a33de4db0be9 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 00:05:13 +0800 Subject: [PATCH 013/250] =?UTF-8?q?=E5=88=9B=E5=BB=BAJsite=20Server?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=97=B6=E5=90=8C=E6=97=B6=E5=86=99=E5=85=A5?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E9=85=8D=E7=BD=AE=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 9 ++++++++- jcloud/jcloud/pagetype/jsite_server/jsite_server.json | 9 +++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index ad707f8..2aac8f0 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -428,6 +428,7 @@ def create_server_order(**kwargs): server = jingrow.get_pg({ "pagetype": "Jsite Server", "team": team.name, + "order_id": order_id, "status": "Pending", "region": region_id, "system_image": image_id, @@ -435,7 +436,10 @@ def create_server_order(**kwargs): "title": f"{region_id} - {selected_plan.get('core')}核/{selected_plan.get('memory')}GB", "planid": plan_id, "period": period, - "order_id": order_id + "cpu": selected_plan.get('core'), + "memory": selected_plan.get('memory'), + "disk_size": selected_plan.get('disk_size'), + "bandwidth": selected_plan.get('bandwidth') }) server.insert(ignore_permissions=True) @@ -468,6 +472,9 @@ def create_server_async(order_name): # 调用阿里云API创建实例 result = create_aliyun_instance(plan_id, image_id, period, region_id) + # 打印result到后台日志 + jingrow.log_error("阿里云创建实例结果", f"订单 {order_name} 的创建结果: {result}") + if not result or not result.get('success'): raise Exception(f"阿里云创建失败: {result.get('message', '未知错误')}") diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 7106632..280913f 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -132,21 +132,21 @@ "fieldname": "cpu", "fieldtype": "Data", "in_list_view": 1, - "label": "CPU", + "label": "CPU(核)", "read_only": 1 }, { "fieldname": "memory", "fieldtype": "Data", "in_list_view": 1, - "label": "内存", + "label": "内存(GB)", "read_only": 1 }, { "fieldname": "disk_size", "fieldtype": "Data", "in_list_view": 1, - "label": "硬盘容量", + "label": "硬盘容量(GB)", "read_only": 1 }, { @@ -178,13 +178,14 @@ { "fieldname": "period", "fieldtype": "Int", + "hidden": 1, "label": "购买时长(月)", "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-27 23:19:16.648233", + "modified": "2025-07-27 23:51:17.953839", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", -- 2.48.1 From f738913dca8f8d9e93d2a5594885cedb6f864300 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 00:57:56 +0800 Subject: [PATCH 014/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E5=AF=B9api=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 98 +++++++++++++++++++ .../pagetype/jsite_server/jsite_server.json | 14 +-- .../pagetype/jsite_server/jsite_server.py | 2 +- 3 files changed, 106 insertions(+), 8 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 2aac8f0..efa9cae 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -290,6 +290,53 @@ class AliyunLightServerManager: jingrow.log_error("获取地域列表失败", f"获取地域列表时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '获取地域列表失败'} + def create_instance_key_pair(self, instance_id, key_pair_name, region_id='cn-shanghai'): + """为实例创建密钥对""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.CreateInstanceKeyPairRequest( + region_id=region_id, + instance_id=instance_id, + key_pair_name=key_pair_name + ) + runtime = util_models.RuntimeOptions() + response = client.create_instance_key_pair_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '密钥对创建成功'} + except Exception as e: + jingrow.log_error("创建密钥对失败", f"为实例 {instance_id} 创建密钥对 {key_pair_name} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '密钥对创建失败'} + + def list_instance_key_pairs(self, instance_id, region_id='cn-shanghai'): + """获取实例的密钥对列表""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.ListInstanceKeyPairsRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.list_instance_key_pairs_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取密钥对列表成功'} + except Exception as e: + jingrow.log_error("获取密钥对列表失败", f"获取实例 {instance_id} 密钥对列表时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取密钥对列表失败'} + + def delete_instance_key_pair(self, instance_id, key_pair_name, region_id='cn-shanghai'): + """删除实例的密钥对""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.DeleteInstanceKeyPairRequest( + region_id=region_id, + instance_id=instance_id, + key_pair_name=key_pair_name + ) + runtime = util_models.RuntimeOptions() + response = client.delete_instance_key_pair_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '密钥对删除成功'} + except Exception as e: + jingrow.log_error("删除密钥对失败", f"删除实例 {instance_id} 密钥对 {key_pair_name} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '密钥对删除失败'} + # 全局管理器实例 _aliyun_manager = None @@ -375,6 +422,26 @@ def get_aliyun_regions(): manager = _get_manager() return manager.get_regions() +@jingrow.whitelist() +def create_aliyun_instance_key_pair(instance_id, key_pair_name, region_id='cn-shanghai'): + """为实例创建密钥对""" + manager = _get_manager() + return manager.create_instance_key_pair(instance_id, key_pair_name, region_id) + +@jingrow.whitelist() +def list_aliyun_instance_key_pairs(instance_id, region_id='cn-shanghai'): + """获取实例的密钥对列表""" + manager = _get_manager() + return manager.list_instance_key_pairs(instance_id, region_id) + +@jingrow.whitelist() +def delete_aliyun_instance_key_pair(instance_id, key_pair_name, region_id='cn-shanghai'): + """删除实例的密钥对""" + manager = _get_manager() + return manager.delete_instance_key_pair(instance_id, key_pair_name, region_id) + + + # 服务器订单和创建相关 @jingrow.whitelist() @@ -487,6 +554,37 @@ def create_server_async(order_name): server.instance_id = instance_id server.save(ignore_permissions=True) + # 自动创建密钥对 + try: + key_pair_name = f"{region_id}-{instance_id[:8]}" + key_pair_result = create_aliyun_instance_key_pair(instance_id, key_pair_name, region_id) + + # 打印密钥对创建结果到后台日志 + jingrow.log_error("密钥对创建结果", f"实例 {instance_id} 的密钥对创建结果: {key_pair_result}") + + if key_pair_result and key_pair_result.get('success'): + jingrow.log_error("密钥对创建成功", f"实例 {instance_id} 自动创建密钥对 {key_pair_name} 成功") + + # 获取并保存密钥对信息 + key_pair_data = key_pair_result.get('data', {}) + private_key = key_pair_data.get('private_key') + + if private_key: + # 保存私钥到服务器记录中(阿里云不返回公钥,会自动配置到实例中) + server.key_pair_name = key_pair_name + server.private_key = private_key + # 公钥字段留空,因为阿里云会自动处理 + server.public_key = "" + jingrow.log_error("私钥已保存", f"实例 {instance_id} 的私钥已保存到服务器记录中,公钥由阿里云自动配置") + else: + jingrow.log_error("私钥获取失败", f"实例 {instance_id} 创建密钥对成功但未获取到私钥") + + server.save(ignore_permissions=True) + else: + jingrow.log_error("密钥对创建失败", f"实例 {instance_id} 自动创建密钥对失败: {key_pair_result.get('message', '未知错误')}") + except Exception as key_error: + jingrow.log_error("密钥对创建异常", f"实例 {instance_id} 自动创建密钥对时发生异常: {str(key_error)}") + # 更新订单状态 order.status = "交易成功" order.save(ignore_permissions=True) diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 280913f..5703db0 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -28,7 +28,7 @@ "ssh_user", "ssh_port", "column_break_20", - "root_public_key" + "private_key" ], "fields": [ { @@ -87,11 +87,6 @@ "fieldname": "column_break_20", "fieldtype": "Column Break" }, - { - "fieldname": "root_public_key", - "fieldtype": "Code", - "label": "Root公钥" - }, { "fieldname": "column_break_aliyun", "fieldtype": "Column Break" @@ -181,11 +176,16 @@ "hidden": 1, "label": "购买时长(月)", "read_only": 1 + }, + { + "fieldname": "private_key", + "fieldtype": "Text", + "label": "Private Key(私钥)" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-27 23:51:17.953839", + "modified": "2025-07-28 00:57:19.288916", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index b505a1a..c559280 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -24,8 +24,8 @@ class JsiteServer(Document): order_id: DF.Data | None period: DF.Int planid: DF.Data | None + private_key: DF.Text | None region: DF.Data | None - root_public_key: DF.Code | None running_status: DF.Literal["", "\u8fd0\u884c\u4e2d", "\u5df2\u505c\u673a"] ssh_port: DF.Int ssh_user: DF.Data | None -- 2.48.1 From e996d7af823845bcf4ce1f7046b371ec0d6fbc3f Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 12:53:32 +0800 Subject: [PATCH 015/250] =?UTF-8?q?create=5Fserver=5Fasync=E6=9B=BF?= =?UTF-8?q?=E6=8D=A2=E4=B8=BAcreate=5Faliyun=5Fserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 2 +- jcloud/api/billing.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index efa9cae..27468f8 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -518,7 +518,7 @@ def create_server_order(**kwargs): jingrow.log_error("创建服务器订单失败", str(e)) return {"success": False, "message": str(e)} -def create_server_async(order_name): +def create_aliyun_server(order_name): """异步创建服务器""" try: order = jingrow.get_pg("Order", order_name) diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py index 5a8c97d..958b102 100644 --- a/jcloud/api/billing.py +++ b/jcloud/api/billing.py @@ -1091,7 +1091,7 @@ def handle_order_payment_complete(order_id): process_site_renew(order_id) elif order.order_type == "新建服务器": # 异步创建服务器 - jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) + jingrow.enqueue('jcloud.api.aliyun_server_light.create_aliyun_server', order_name=order.name) return True except Exception as e: @@ -1622,7 +1622,7 @@ def process_balance_payment_for_server_order(order_id): jingrow.db.commit() # 异步执行服务器创建 - jingrow.enqueue('jcloud.api.aliyun_server_light.create_server_async', order_name=order.name) + jingrow.enqueue('jcloud.api.aliyun_server_light.create_aliyun_server', order_name=order.name) jingrow.log_error("异步执行创建服务器", f"订单ID: {order_id}") return { "status": "Success", -- 2.48.1 From d26bd67dd63879be1e229472b6922aee465c61b4 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 14:04:37 +0800 Subject: [PATCH 016/250] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E5=AF=B9=E5=88=9B=E5=BB=BA=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 76 +++++++++++-------- .../pagetype/jsite_server/jsite_server.json | 23 +++--- .../pagetype/jsite_server/jsite_server.py | 2 +- 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 27468f8..50951d0 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -553,38 +553,7 @@ def create_aliyun_server(order_name): server.running_status = "运行中" server.instance_id = instance_id server.save(ignore_permissions=True) - - # 自动创建密钥对 - try: - key_pair_name = f"{region_id}-{instance_id[:8]}" - key_pair_result = create_aliyun_instance_key_pair(instance_id, key_pair_name, region_id) - - # 打印密钥对创建结果到后台日志 - jingrow.log_error("密钥对创建结果", f"实例 {instance_id} 的密钥对创建结果: {key_pair_result}") - - if key_pair_result and key_pair_result.get('success'): - jingrow.log_error("密钥对创建成功", f"实例 {instance_id} 自动创建密钥对 {key_pair_name} 成功") - - # 获取并保存密钥对信息 - key_pair_data = key_pair_result.get('data', {}) - private_key = key_pair_data.get('private_key') - - if private_key: - # 保存私钥到服务器记录中(阿里云不返回公钥,会自动配置到实例中) - server.key_pair_name = key_pair_name - server.private_key = private_key - # 公钥字段留空,因为阿里云会自动处理 - server.public_key = "" - jingrow.log_error("私钥已保存", f"实例 {instance_id} 的私钥已保存到服务器记录中,公钥由阿里云自动配置") - else: - jingrow.log_error("私钥获取失败", f"实例 {instance_id} 创建密钥对成功但未获取到私钥") - - server.save(ignore_permissions=True) - else: - jingrow.log_error("密钥对创建失败", f"实例 {instance_id} 自动创建密钥对失败: {key_pair_result.get('message', '未知错误')}") - except Exception as key_error: - jingrow.log_error("密钥对创建异常", f"实例 {instance_id} 自动创建密钥对时发生异常: {str(key_error)}") - + # 更新订单状态 order.status = "交易成功" order.save(ignore_permissions=True) @@ -595,4 +564,45 @@ def create_aliyun_server(order_name): except Exception as e: jingrow.log_error("服务器创建失败", f"订单 {order_name}: {str(e)}") - raise e \ No newline at end of file + raise e + +@jingrow.whitelist() +def create_server_key_pair(instance_id): + """为实例创建密钥对并保存私钥""" + try: + # 查找对应的服务器记录获取region_id + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + jingrow.log_error("找不到对应的服务器记录") + + region_id = server.region + key_pair_name = f"{region_id}-{instance_id[:8]}" + + # 调用阿里云API创建密钥对 + key_pair_result = create_aliyun_instance_key_pair(instance_id, key_pair_name, region_id) + + if not key_pair_result or not key_pair_result.get('success'): + jingrow.log_error(f"密钥对创建失败: {key_pair_result.get('message', '未知错误')}") + + # 获取私钥 + key_pair_data = key_pair_result.get('data', {}) + private_key = key_pair_data.get('private_key') + + if private_key: + # 保存私钥到服务器记录 + server.key_pair_name = key_pair_name + server.private_key = private_key + server.save(ignore_permissions=True) + + return { + "success": True, + "message": "密钥对创建成功", + "key_pair_name": key_pair_name, + "private_key": private_key + } + else: + jingrow.log_error("密钥对创建成功但未获取到私钥") + + except Exception as e: + jingrow.log_error("创建密钥对失败", f"实例 {instance_id}: {str(e)}") + return {"success": False, "message": str(e)} \ No newline at end of file diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 5703db0..63d6aef 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -21,7 +21,7 @@ "end_date", "memory", "bandwidth", - "ip", + "public_ip", "running_status", "period", "ssh_section", @@ -57,14 +57,6 @@ "label": "团队", "options": "Team" }, - { - "fetch_from": "virtual_machine.public_ip_address", - "fieldname": "ip", - "fieldtype": "Data", - "in_list_view": 1, - "label": "公网IP", - "read_only": 1 - }, { "collapsible": 1, "fieldname": "ssh_section", @@ -95,7 +87,8 @@ "fieldname": "instance_id", "fieldtype": "Data", "label": "实例ID", - "read_only": 1 + "read_only": 1, + "unique": 1 }, { "fieldname": "end_date", @@ -181,11 +174,19 @@ "fieldname": "private_key", "fieldtype": "Text", "label": "Private Key(私钥)" + }, + { + "fetch_from": "virtual_machine.public_ip_address", + "fieldname": "public_ip", + "fieldtype": "Data", + "in_list_view": 1, + "label": "公网IP", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-28 00:57:19.288916", + "modified": "2025-07-28 13:48:36.022662", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index c559280..aa4a995 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -19,12 +19,12 @@ class JsiteServer(Document): disk_size: DF.Data | None end_date: DF.Date | None instance_id: DF.Data | None - ip: DF.Data | None memory: DF.Data | None order_id: DF.Data | None period: DF.Int planid: DF.Data | None private_key: DF.Text | None + public_ip: DF.Data | None region: DF.Data | None running_status: DF.Literal["", "\u8fd0\u884c\u4e2d", "\u5df2\u505c\u673a"] ssh_port: DF.Int -- 2.48.1 From 27b713250303629a96579d514f479892a05a7cd6 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 15:39:13 +0800 Subject: [PATCH 017/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0update=5Fserver=5Frec?= =?UTF-8?q?ord=E7=AB=AF=E7=82=B9=E7=94=A8=E4=BA=8E=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=91=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=9B=B8=E5=85=B3=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 127 +++++++++++++++++- .../pagetype/jsite_server/jsite_server.json | 36 +++-- .../pagetype/jsite_server/jsite_server.py | 8 +- 3 files changed, 141 insertions(+), 30 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 50951d0..3bb839f 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -1,6 +1,9 @@ import jingrow import os import sys +import json +import random +from datetime import datetime from typing import Dict, Any from alibabacloud_swas_open20200601.client import Client as SWAS_OPEN20200601Client @@ -10,6 +13,8 @@ from alibabacloud_swas_open20200601 import models as swas__open20200601_models from alibabacloud_tea_util import models as util_models from alibabacloud_tea_util.client import Client as UtilClient +from jcloud.utils import get_current_team + class AliyunLightServerManager: """阿里云轻量应用服务器管理器""" @@ -337,6 +342,27 @@ class AliyunLightServerManager: jingrow.log_error("删除密钥对失败", f"删除实例 {instance_id} 密钥对 {key_pair_name} 时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '密钥对删除失败'} + def get_instance_details(self, instance_ids, region_id='cn-shanghai'): + """获取实例详细信息(支持批量查询)""" + client = self._get_client(region_id) + try: + # 如果instance_ids是列表,转换为JSON字符串 + if isinstance(instance_ids, list): + instance_ids_str = json.dumps(instance_ids) + else: + instance_ids_str = instance_ids + + request = swas__open20200601_models.ListInstancesRequest( + region_id=region_id, + instance_ids=instance_ids_str + ) + runtime = util_models.RuntimeOptions() + response = client.list_instances_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取实例详细信息成功'} + except Exception as e: + jingrow.log_error("获取实例详细信息失败", f"获取实例详细信息时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取实例详细信息失败'} + # 全局管理器实例 _aliyun_manager = None @@ -440,6 +466,11 @@ def delete_aliyun_instance_key_pair(instance_id, key_pair_name, region_id='cn-sh manager = _get_manager() return manager.delete_instance_key_pair(instance_id, key_pair_name, region_id) +@jingrow.whitelist() +def get_aliyun_instance_details(instance_ids, region_id='cn-shanghai'): + """获取实例详细信息(支持批量查询)""" + manager = _get_manager() + return manager.get_instance_details(instance_ids, region_id) # 服务器订单和创建相关 @@ -456,7 +487,6 @@ def create_server_order(**kwargs): if not plan_id or not image_id: jingrow.throw("缺少必要参数") - from jcloud.utils import get_current_team team = get_current_team(True) # 获取套餐价格 @@ -474,8 +504,6 @@ def create_server_order(**kwargs): total_amount = monthly_price * period # 生成订单号 - from datetime import datetime - import random order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6)) # 创建订单记录 @@ -498,7 +526,7 @@ def create_server_order(**kwargs): "order_id": order_id, "status": "Pending", "region": region_id, - "system_image": image_id, + "image_id": image_id, "end_date": jingrow.utils.add_months(jingrow.utils.nowdate(), period), "title": f"{region_id} - {selected_plan.get('core')}核/{selected_plan.get('memory')}GB", "planid": plan_id, @@ -532,7 +560,7 @@ def create_aliyun_server(order_name): # 从服务器记录中获取配置信息 region_id = server.region or 'cn-shanghai' - image_id = server.system_image or 'e9363571cf2444aba422b17470285465' + image_id = server.image_id or 'e9363571cf2444aba422b17470285465' plan_id = server.planid or 'swas.s.c2m1s30b1.linux' period = server.period or 1 @@ -549,8 +577,7 @@ def create_aliyun_server(order_name): instance_id = result['data']['instance_ids'][0] # 更新服务器记录状态 - server.status = "Active" - server.running_status = "运行中" + server.status = "Running" server.instance_id = instance_id server.save(ignore_permissions=True) @@ -605,4 +632,90 @@ def create_server_key_pair(instance_id): except Exception as e: jingrow.log_error("创建密钥对失败", f"实例 {instance_id}: {str(e)}") + return {"success": False, "message": str(e)} + +@jingrow.whitelist() +def update_server_record(instance_ids): + """根据阿里云实例信息更新Jsite Server记录""" + try: + # 解析instance_ids(JSON字符串格式) + if isinstance(instance_ids, str): + instance_ids = json.loads(instance_ids) + + # 获取第一个实例ID + instance_id = instance_ids[0] + + # 查找对应的Jsite Server记录获取region_id + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + jingrow.log_error("更新服务器信息失败", f"找不到实例ID为 {instance_id} 的Jsite Server记录") + return {"success": False, "message": f"找不到实例ID为 {instance_id} 的Jsite Server记录"} + + region_id = server.region or 'cn-shanghai' + + # 获取阿里云实例详细信息 + instance_details = get_aliyun_instance_details(instance_ids, region_id) + + if not instance_details or not instance_details.get('success'): + error_msg = f"获取阿里云实例信息失败: {instance_details.get('message', '未知错误')}" + jingrow.log_error("更新服务器信息失败", error_msg) + return {"success": False, "message": error_msg} + + # 解析实例信息 + instances = instance_details.get('data', {}).get('instances', []) + if not instances: + jingrow.log_error("更新服务器信息失败", "未找到实例信息") + return {"success": False, "message": "未找到实例信息"} + + instance_info = instances[0] # 取第一个实例 + + # 更新public_ip + public_ip = instance_info.get('public_ip_address') + if public_ip: + server.public_ip = public_ip + + # 更新end_date(从expired_time转换) + expired_time = instance_info.get('expired_time') + if expired_time: + try: + expired_datetime = datetime.fromisoformat(expired_time.replace('Z', '+00:00')) + server.end_date = expired_datetime.strftime('%Y-%m-%d %H:%M:%S') + except Exception as e: + jingrow.log_error("时间格式转换失败", f"转换expired_time {expired_time} 时发生错误: {str(e)}") + + # 更新其他相关信息 + server.status = instance_info.get('status', '') + + # 更新系统信息 + image_info = instance_info.get('image', {}) + if image_info: + image_name = image_info.get('image_name', '') + image_version = image_info.get('image_version', '') + server.system = f"{image_name} {image_version}".strip() + + # 更新资源规格信息 + resource_spec = instance_info.get('resource_spec', {}) + if resource_spec: + server.cpu = resource_spec.get('cpu') + server.memory = resource_spec.get('memory') + server.disk_size = resource_spec.get('disk_size') + server.bandwidth = resource_spec.get('bandwidth') + + # 保存更新 + server.save(ignore_permissions=True) + jingrow.db.commit() + + return { + "success": True, + "message": "服务器信息更新成功", + "updated_fields": { + "public_ip": public_ip, + "end_date": server.end_date, + "status": server.status, + "system": server.system + } + } + + except Exception as e: + jingrow.log_error("更新服务器信息失败", f"更新服务器信息时发生错误: {str(e)}") return {"success": False, "message": str(e)} \ No newline at end of file diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 63d6aef..fd99951 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -15,14 +15,14 @@ "cpu", "disk_size", "region", - "system_image", + "image_id", "planid", "column_break_aliyun", "end_date", "memory", "bandwidth", "public_ip", - "running_status", + "system", "period", "ssh_section", "ssh_user", @@ -38,7 +38,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "状态", - "options": "Pending\nInstalling\nActive\nBroken\nArchived", + "options": "Pending\nStarting\nRunning\nStopping\nStopped\nResetting\nUpgrading\nDisabled", "read_only": 1 }, { @@ -92,7 +92,7 @@ }, { "fieldname": "end_date", - "fieldtype": "Date", + "fieldtype": "Datetime", "in_list_view": 1, "label": "到期时间", "read_only": 1 @@ -137,20 +137,6 @@ "label": "硬盘容量(GB)", "read_only": 1 }, - { - "fieldname": "system_image", - "fieldtype": "Data", - "label": "系统镜像" - }, - { - "fetch_from": "virtual_machine.public_ip_address", - "fieldname": "running_status", - "fieldtype": "Select", - "in_list_view": 1, - "label": "运行状态", - "options": "\n运行中\n已停机", - "read_only": 1 - }, { "fieldname": "order_id", "fieldtype": "Data", @@ -182,11 +168,23 @@ "in_list_view": 1, "label": "公网IP", "read_only": 1 + }, + { + "fieldname": "image_id", + "fieldtype": "Data", + "label": "镜像ID", + "read_only": 1 + }, + { + "fieldname": "system", + "fieldtype": "Data", + "label": "系统", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-28 13:48:36.022662", + "modified": "2025-07-28 15:36:38.598173", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index aa4a995..f2d3bf2 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -17,7 +17,8 @@ class JsiteServer(Document): bandwidth: DF.Data | None cpu: DF.Data | None disk_size: DF.Data | None - end_date: DF.Date | None + end_date: DF.Datetime | None + image_id: DF.Data | None instance_id: DF.Data | None memory: DF.Data | None order_id: DF.Data | None @@ -26,11 +27,10 @@ class JsiteServer(Document): private_key: DF.Text | None public_ip: DF.Data | None region: DF.Data | None - running_status: DF.Literal["", "\u8fd0\u884c\u4e2d", "\u5df2\u505c\u673a"] ssh_port: DF.Int ssh_user: DF.Data | None - status: DF.Literal["Pending", "Installing", "Active", "Broken", "Archived"] - system_image: DF.Data | None + status: DF.Literal["Pending", "Starting", "Running", "Stopping", "Stopped", "Resetting", "Upgrading", "Disabled"] + system: DF.Data | None team: DF.Link | None title: DF.Data | None # end: auto-generated types -- 2.48.1 From 57fe26e442057728f9e2294c17b4442532ef04ec Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 16:02:50 +0800 Subject: [PATCH 018/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E5=AE=9E=E4=BE=8BAPI=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 3bb839f..7526e28 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -150,6 +150,21 @@ class AliyunLightServerManager: jingrow.log_error("停止实例失败", f"停止实例 {instance_id} 时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '实例停止失败'} + def reboot_instance(self, instance_id, region_id='cn-shanghai'): + """重启实例""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.RebootInstanceRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.reboot_instance_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '实例重启成功'} + except Exception as e: + jingrow.log_error("重启实例失败", f"重启实例 {instance_id} 时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '实例重启失败'} + def upgrade_instance(self, instance_id, plan_id, region_id='cn-shanghai'): """升级实例配置""" client = self._get_client(region_id) @@ -394,6 +409,12 @@ def stop_aliyun_instance(instance_id, region_id='cn-shanghai'): manager = _get_manager() return manager.stop_instance(instance_id, region_id) +@jingrow.whitelist() +def reboot_aliyun_instance(instance_id, region_id='cn-shanghai'): + """重启实例""" + manager = _get_manager() + return manager.reboot_instance(instance_id, region_id) + @jingrow.whitelist() def upgrade_aliyun_instance(instance_id, plan_id, region_id='cn-shanghai'): """升级实例配置""" -- 2.48.1 From 9afe7e78e72ebd51853b0202ba63dcf94c6e0bfe Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 17:42:04 +0800 Subject: [PATCH 019/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 115 ++++++++++++++---- .../pagetype/jsite_server/jsite_server.js | 53 +++++++- .../pagetype/jsite_server/jsite_server.json | 2 +- 3 files changed, 143 insertions(+), 27 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 7526e28..5902c4c 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -3,6 +3,7 @@ import os import sys import json import random +import time from datetime import datetime from typing import Dict, Any @@ -216,20 +217,7 @@ class AliyunLightServerManager: jingrow.log_error("重置系统失败", f"重置实例 {instance_id} 系统时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '系统重置失败'} - def get_instance_info(self, instance_id, region_id='cn-shanghai'): - """获取实例信息""" - client = self._get_client(region_id) - try: - request = swas__open20200601_models.GetInstanceRequest( - region_id=region_id, - instance_id=instance_id - ) - runtime = util_models.RuntimeOptions() - response = client.get_instance_with_options(request, runtime) - return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取实例信息成功'} - except Exception as e: - jingrow.log_error("获取实例信息失败", f"获取实例 {instance_id} 信息时发生错误: {str(e)}") - return {'success': False, 'error': str(e), 'message': '获取实例信息失败'} + def list_instances(self, page_number=1, page_size=20, region_id='cn-shanghai'): """获取实例列表""" @@ -411,9 +399,96 @@ def stop_aliyun_instance(instance_id, region_id='cn-shanghai'): @jingrow.whitelist() def reboot_aliyun_instance(instance_id, region_id='cn-shanghai'): - """重启实例""" - manager = _get_manager() - return manager.reboot_instance(instance_id, region_id) + """重启实例并更新状态""" + try: + # 1. 查找对应的Jsite Server记录 + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + return {"success": False, "message": "找不到对应的服务器记录"} + + # 2. 立即更新状态为"重启中" + server.status = "Starting" + server.save(ignore_permissions=True) + jingrow.db.commit() + + # 3. 调用阿里云API重启实例 + manager = _get_manager() + result = manager.reboot_instance(instance_id, region_id) + + # 4. 启动后台任务监控重启状态 + jingrow.enqueue( + "jcloud.api.aliyun_server_light.monitor_reboot_status", + instance_id=instance_id, + region_id=region_id, + queue="long", + timeout=600 + ) + + return { + "success": True, + "message": "重启命令已发送,正在监控重启状态...", + "server_name": server.name + } + + except Exception as e: + jingrow.log_error("重启实例失败", f"重启实例 {instance_id} 时发生错误: {str(e)}") + return {"success": False, "error": str(e), "message": "重启实例失败"} + + +def monitor_reboot_status(instance_id, region_id): + """简单的重启状态监控 - 直接循环查询""" + try: + # 通过instance_id查找服务器记录 + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + jingrow.log_error("找不到服务器记录", f"无法找到实例ID为 {instance_id} 的服务器记录") + return + + manager = _get_manager() + max_retries = 30 # 最多查询30次 + + for retry in range(max_retries): + # 查询实例状态 + instance_info = manager.get_instance_details([instance_id], region_id) + + if not instance_info or not instance_info.get('success'): + jingrow.log_error("获取实例状态失败", f"无法获取实例 {instance_id} 的状态信息") + time.sleep(10) + continue + + # 解析实例状态 + instances = instance_info.get('data', {}).get('instances', []) + if not instances: + jingrow.log_error("获取实例状态失败", f"实例 {instance_id} 的状态信息为空") + time.sleep(10) + continue + + instance = instances[0] # 取第一个实例 + status = instance.get('status', '') + + # 更新服务器状态 + if status == 'Running': + server.status = 'Running' + server.save(ignore_permissions=True) + jingrow.db.commit() + jingrow.log_error("重启完成", f"服务器 {server.name} 重启完成") + return + elif status in ['Starting', 'Stopping']: + # 继续等待 + time.sleep(10) + else: + # 其他状态,记录日志并停止 + server.status = status + server.save(ignore_permissions=True) + jingrow.db.commit() + jingrow.log_error("重启状态异常", f"服务器 {server.name} 状态为: {status}") + return + + # 超时处理 + jingrow.log_error("监控超时", f"服务器 {server.name} 重启监控超时,已查询 {max_retries} 次") + + except Exception as e: + jingrow.log_error("监控重启状态失败", f"监控实例 {instance_id} 重启状态时发生错误: {str(e)}") @jingrow.whitelist() def upgrade_aliyun_instance(instance_id, plan_id, region_id='cn-shanghai'): @@ -433,11 +508,7 @@ def reset_aliyun_instance_system(instance_id, image_id, password=None, region_id manager = _get_manager() return manager.reset_system(instance_id, image_id, password, region_id) -@jingrow.whitelist() -def get_aliyun_instance_info(instance_id, region_id='cn-shanghai'): - """获取实例信息""" - manager = _get_manager() - return manager.get_instance_info(instance_id, region_id) + @jingrow.whitelist() def list_aliyun_instances(page_number=1, page_size=20, region_id='cn-shanghai'): diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js index 1a8274d..4ec50d9 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -1,8 +1,53 @@ // Copyright (c) 2025, Jingrow and contributors // For license information, please see license.txt -// jingrow.ui.form.on("Jsite Server", { -// refresh(frm) { +jingrow.ui.form.on("Jsite Server", { + refresh(frm) { + if (frm.pg.instance_id) { + frm.add_custom_button(__('重启'), function() { + // 弹出确认对话框 + jingrow.confirm( + __('确定要重启服务器吗?重启过程中服务器将暂时不可用。'), + function() { + restart_server(frm); + } + ); + }); + } + } +}); -// }, -// }); +function restart_server(frm) { + jingrow.call({ + method: 'jcloud.api.aliyun_server_light.reboot_aliyun_instance', + args: { + instance_id: frm.pg.instance_id, + region_id: frm.pg.region || 'cn-shanghai' + }, + callback: function(r) { + if (r.success) { + jingrow.msgprint({ + title: __('成功'), + message: __('重启命令已发送,请稍后刷新页面查看状态'), + indicator: 'green' + }); + setTimeout(function() { + frm.reload_pg(); + }, 3000); + } else { + jingrow.msgprint({ + title: __('重启失败'), + message: r.message || r.error || __('未知错误'), + indicator: 'red' + }); + } + }, + error: function(err) { + jingrow.msgprint({ + title: __('重启失败'), + message: err.message, + indicator: 'red' + }); + } + }); +} diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index fd99951..b8ccb00 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -184,7 +184,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-28 15:36:38.598173", + "modified": "2025-07-28 16:21:14.934079", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", -- 2.48.1 From ff9873d0f7ce57f82e1ff996771a78c7f26cf54a Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 18:39:41 +0800 Subject: [PATCH 020/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E6=8C=89=E9=92=AE=E5=8F=8AAPI=E7=AB=AF?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 58 +++++++++++++++++ .../pagetype/jsite_server/jsite_server.js | 62 +++++++++++++++++++ .../pagetype/jsite_server/jsite_server.json | 9 ++- .../pagetype/jsite_server/jsite_server.py | 1 + 4 files changed, 129 insertions(+), 1 deletion(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 5902c4c..07a6b0b 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -217,6 +217,22 @@ class AliyunLightServerManager: jingrow.log_error("重置系统失败", f"重置实例 {instance_id} 系统时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '系统重置失败'} + def update_instance_password(self, instance_id, password, region_id='cn-shanghai'): + """更新实例密码""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.UpdateInstanceAttributeRequest( + region_id=region_id, + instance_id=instance_id, + password=password + ) + runtime = util_models.RuntimeOptions() + response = client.update_instance_attribute_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '密码更新成功'} + except Exception as e: + jingrow.log_error("更新实例密码失败", f"更新实例 {instance_id} 密码时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '密码更新失败'} + def list_instances(self, page_number=1, page_size=20, region_id='cn-shanghai'): @@ -508,6 +524,48 @@ def reset_aliyun_instance_system(instance_id, image_id, password=None, region_id manager = _get_manager() return manager.reset_system(instance_id, image_id, password, region_id) +@jingrow.whitelist() +def update_aliyun_instance_password(instance_id, password, region_id='cn-shanghai'): + """更新实例密码""" + manager = _get_manager() + return manager.update_instance_password(instance_id, password, region_id) + +@jingrow.whitelist() +def reset_aliyun_instance_password(instance_id, password, region_id='cn-shanghai'): + """重置实例密码并更新Jsite Server记录""" + try: + # 1. 查找对应的Jsite Server记录 + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + return {"success": False, "message": "找不到对应的服务器记录"} + + # 2. 调用阿里云API更新密码 + manager = _get_manager() + result = manager.update_instance_password(instance_id, password, region_id) + + if not result or not result.get('success'): + error_msg = f"阿里云密码更新失败: {result.get('message', '未知错误')}" + jingrow.log_error("重置密码失败", error_msg) + return {"success": False, "message": error_msg} + + # 3. 更新Jsite Server记录中的password字段 + server.password = password + server.save(ignore_permissions=True) + jingrow.db.commit() + + # 4. 自动重启实例 + reboot_aliyun_instance(instance_id, region_id) + + return { + "success": True, + "message": "密码重置成功", + "server_name": server.name, + "instance_id": instance_id + } + + except Exception as e: + jingrow.log_error("重置密码失败", f"重置实例 {instance_id} 密码时发生错误: {str(e)}") + return {"success": False, "error": str(e), "message": "密码重置失败"} @jingrow.whitelist() diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js index 4ec50d9..1113c7c 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -13,6 +13,31 @@ jingrow.ui.form.on("Jsite Server", { } ); }); + + frm.add_custom_button(__('重置密码'), function() { + // 弹出密码输入对话框 + jingrow.prompt( + { + fieldtype: 'Password', + label: __('服务器密码'), + fieldname: 'new_password', + reqd: 1, + description: __('长度为 8 至 30 个字符,必须同时包含大小写英文字母、数字和特殊符号。') + }, + function(values) { + if (values.new_password) { + reset_password(frm, values.new_password); + } else { + jingrow.msgprint({ + title: __('错误'), + message: __('请输入新密码'), + indicator: 'red' + }); + } + }, + __('重置服务器密码') + ); + }); } } }); @@ -51,3 +76,40 @@ function restart_server(frm) { } }); } + +function reset_password(frm, new_password) { + jingrow.call({ + method: 'jcloud.api.aliyun_server_light.reset_aliyun_instance_password', + args: { + instance_id: frm.pg.instance_id, + password: new_password, + region_id: frm.pg.region || 'cn-shanghai' + }, + callback: function(r) { + if (r.success) { + jingrow.msgprint({ + title: __('成功'), + message: __('密码重置成功,新密码已保存到服务器记录中'), + indicator: 'green' + }); + // 刷新页面以显示新密码 + setTimeout(function() { + frm.reload_pg(); + }, 2000); + } else { + jingrow.msgprint({ + title: __('重置失败'), + message: r.message || r.error || __('未知错误'), + indicator: 'red' + }); + } + }, + error: function(err) { + jingrow.msgprint({ + title: __('重置失败'), + message: err.message, + indicator: 'red' + }); + } + }); +} diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index b8ccb00..618a6db 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -27,6 +27,7 @@ "ssh_section", "ssh_user", "ssh_port", + "password", "column_break_20", "private_key" ], @@ -180,11 +181,17 @@ "fieldtype": "Data", "label": "系统", "read_only": 1 + }, + { + "default": "22", + "fieldname": "password", + "fieldtype": "Password", + "label": "服务器密码" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-28 16:21:14.934079", + "modified": "2025-07-28 17:53:25.476573", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index f2d3bf2..bcae718 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -22,6 +22,7 @@ class JsiteServer(Document): instance_id: DF.Data | None memory: DF.Data | None order_id: DF.Data | None + password: DF.Password | None period: DF.Int planid: DF.Data | None private_key: DF.Text | None -- 2.48.1 From a59ab5f8a38e91ba6de1fc1ec298487413ad73b8 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 18:57:00 +0800 Subject: [PATCH 021/250] =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=AD=97=E6=AE=B5=E5=8F=B3=E8=BE=B9=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=98=8E=E6=96=87=E6=98=BE=E7=A4=BA=E5=AF=86=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E5=8F=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetype/jsite_server/jsite_server.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js index 1113c7c..a185715 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -39,6 +39,46 @@ jingrow.ui.form.on("Jsite Server", { ); }); } + + // 为password字段添加眼睛图标 + if (frm.get_field('password')) { + let password_field = frm.get_field('password'); + + // 确保toggle-password按钮可见 + password_field.toggle_password.removeClass('hidden'); + + // 重写toggle_password的点击事件 + password_field.toggle_password.off('click').on('click', function() { + if (password_field.$input.attr('type') === 'password') { + // 使用jingrow.call获取解密后的密码 + jingrow.call({ + method: 'jingrow.client.get_password', + args: { + pagetype: 'Jsite Server', + name: frm.pg.name, + fieldname: 'password' + }, + callback: function(r) { + if (r.message) { + password_field.$input.attr('type', 'text'); + password_field.$input.val(r.message); + password_field.toggle_password.html(jingrow.utils.icon('hide', 'sm')); + } else { + jingrow.msgprint({ + title: __('提示'), + message: __('当前没有保存的密码'), + indicator: 'yellow' + }); + } + } + }); + } else { + password_field.$input.attr('type', 'password'); + password_field.$input.val('••••••••'); + password_field.toggle_password.html(jingrow.utils.icon('unhide', 'sm')); + } + }); + } } }); -- 2.48.1 From caefb4a40855ad5654dab42757baaa0ad8dcaf69 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 20:59:36 +0800 Subject: [PATCH 022/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E5=AF=B9api=E7=AB=AF=E7=82=B9=E5=8F=8A?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 128 +++++++++++++----- .../pagetype/jsite_server/jsite_server.json | 8 +- .../pagetype/jsite_server/jsite_server.py | 1 + 3 files changed, 103 insertions(+), 34 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 07a6b0b..fdee79c 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -4,6 +4,7 @@ import sys import json import random import time +import uuid from datetime import datetime from typing import Dict, Any @@ -330,37 +331,53 @@ class AliyunLightServerManager: jingrow.log_error("创建密钥对失败", f"为实例 {instance_id} 创建密钥对 {key_pair_name} 时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '密钥对创建失败'} - def list_instance_key_pairs(self, instance_id, region_id='cn-shanghai'): - """获取实例的密钥对列表""" - client = self._get_client(region_id) - try: - request = swas__open20200601_models.ListInstanceKeyPairsRequest( - region_id=region_id, - instance_id=instance_id - ) - runtime = util_models.RuntimeOptions() - response = client.list_instance_key_pairs_with_options(request, runtime) - return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取密钥对列表成功'} - except Exception as e: - jingrow.log_error("获取密钥对列表失败", f"获取实例 {instance_id} 密钥对列表时发生错误: {str(e)}") - return {'success': False, 'error': str(e), 'message': '获取密钥对列表失败'} - def delete_instance_key_pair(self, instance_id, key_pair_name, region_id='cn-shanghai'): - """删除实例的密钥对""" + + def delete_instance_key_pair(self, instance_id, region_id='cn-shanghai'): + """解绑实例的密钥对""" client = self._get_client(region_id) try: request = swas__open20200601_models.DeleteInstanceKeyPairRequest( region_id=region_id, - instance_id=instance_id, - key_pair_name=key_pair_name + instance_id=instance_id ) runtime = util_models.RuntimeOptions() response = client.delete_instance_key_pair_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '密钥对解绑成功'} + except Exception as e: + jingrow.log_error("解绑密钥对失败", f"解绑实例 {instance_id} 密钥对时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '密钥对解绑失败'} + + def delete_key_pairs(self, key_pair_names, region_id='cn-shanghai'): + """删除密钥对""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.DeleteKeyPairsRequest( + region_id=region_id, + key_pair_names=key_pair_names + ) + runtime = util_models.RuntimeOptions() + response = client.delete_key_pairs_with_options(request, runtime) return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '密钥对删除成功'} except Exception as e: - jingrow.log_error("删除密钥对失败", f"删除实例 {instance_id} 密钥对 {key_pair_name} 时发生错误: {str(e)}") + jingrow.log_error("删除密钥对失败", f"删除密钥对 {key_pair_names} 时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '密钥对删除失败'} + def get_instance_key_pair(self, instance_id, region_id='cn-shanghai'): + """获取实例的密钥对详细信息""" + client = self._get_client(region_id) + try: + request = swas__open20200601_models.DescribeInstanceKeyPairRequest( + region_id=region_id, + instance_id=instance_id + ) + runtime = util_models.RuntimeOptions() + response = client.describe_instance_key_pair_with_options(request, runtime) + return {'success': True, 'data': self._convert_response_to_dict(response.body), 'message': '获取密钥对详细信息成功'} + except Exception as e: + jingrow.log_error("获取密钥对详细信息失败", f"获取实例 {instance_id} 密钥对详细信息时发生错误: {str(e)}") + return {'success': False, 'error': str(e), 'message': '获取密钥对详细信息失败'} + def get_instance_details(self, instance_ids, region_id='cn-shanghai'): """获取实例详细信息(支持批量查询)""" client = self._get_client(region_id) @@ -604,17 +621,53 @@ def create_aliyun_instance_key_pair(instance_id, key_pair_name, region_id='cn-sh manager = _get_manager() return manager.create_instance_key_pair(instance_id, key_pair_name, region_id) -@jingrow.whitelist() -def list_aliyun_instance_key_pairs(instance_id, region_id='cn-shanghai'): - """获取实例的密钥对列表""" - manager = _get_manager() - return manager.list_instance_key_pairs(instance_id, region_id) + @jingrow.whitelist() -def delete_aliyun_instance_key_pair(instance_id, key_pair_name, region_id='cn-shanghai'): - """删除实例的密钥对""" +def delete_aliyun_instance_key_pair(instance_id): + """解绑并删除实例的密钥对""" + try: + # 1. 查找对应的Jsite Server记录获取key_pair_name和region_id + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + return {"success": False, "message": "找不到对应的服务器记录"} + + key_pair_name = server.key_pair_name + region_id = server.region + + if not key_pair_name: + return {"success": True, "message": "实例没有关联的密钥对"} + + manager = _get_manager() + + # 2. 解绑实例的密钥对 + unbind_result = manager.delete_instance_key_pair(instance_id, region_id) + if not unbind_result or not unbind_result.get('success'): + return {"success": False, "message": f"解绑密钥对失败: {unbind_result.get('message', '未知错误')}"} + + # 3. 删除密钥对 + delete_result = manager.delete_key_pairs([key_pair_name], region_id) + if not delete_result or not delete_result.get('success'): + return {"success": False, "message": f"删除密钥对失败: {delete_result.get('message', '未知错误')}"} + + # 4. 清除服务器记录中的密钥对信息 + server.key_pair_name = None + server.private_key = None + server.save(ignore_permissions=True) + jingrow.db.commit() + + return {"success": True, "message": "密钥对解绑并删除成功"} + + except Exception as e: + jingrow.log_error("删除实例密钥对失败", f"删除实例 {instance_id} 密钥对时发生错误: {str(e)}") + return {"success": False, "message": str(e)} + + +@jingrow.whitelist() +def get_aliyun_instance_key_pair(instance_id, region_id='cn-shanghai'): + """获取实例的密钥对详细信息""" manager = _get_manager() - return manager.delete_instance_key_pair(instance_id, key_pair_name, region_id) + return manager.get_instance_key_pair(instance_id, region_id) @jingrow.whitelist() def get_aliyun_instance_details(instance_ids, region_id='cn-shanghai'): @@ -753,15 +806,22 @@ def create_server_key_pair(instance_id): jingrow.log_error("找不到对应的服务器记录") region_id = server.region - key_pair_name = f"{region_id}-{instance_id[:8]}" + uuid_suffix = str(uuid.uuid4())[:4] + key_pair_name = f"{region_id}-{instance_id[:8]}-{uuid_suffix}" - # 调用阿里云API创建密钥对 - key_pair_result = create_aliyun_instance_key_pair(instance_id, key_pair_name, region_id) + # 直接调用管理器方法创建密钥对 + manager = _get_manager() + key_pair_result = manager.create_instance_key_pair(instance_id, key_pair_name, region_id) + + # 添加调试日志 + jingrow.log_error("密钥对创建调试", f"完整的key_pair_result: {key_pair_result}") if not key_pair_result or not key_pair_result.get('success'): - jingrow.log_error(f"密钥对创建失败: {key_pair_result.get('message', '未知错误')}") + error_msg = key_pair_result.get('message', '未知错误') if key_pair_result else '返回结果为空' + jingrow.log_error(f"密钥对创建失败: {error_msg}") + return {"success": False, "message": f"密钥对创建失败: {error_msg}"} - # 获取私钥 + # 获取私钥 - 直接访问data字段 key_pair_data = key_pair_result.get('data', {}) private_key = key_pair_data.get('private_key') @@ -770,6 +830,7 @@ def create_server_key_pair(instance_id): server.key_pair_name = key_pair_name server.private_key = private_key server.save(ignore_permissions=True) + jingrow.db.commit() return { "success": True, @@ -778,7 +839,8 @@ def create_server_key_pair(instance_id): "private_key": private_key } else: - jingrow.log_error("密钥对创建成功但未获取到私钥") + jingrow.log_error("密钥对创建成功但未获取到私钥", f"完整的key_pair_result: {key_pair_result}") + return {"success": False, "message": "密钥对创建成功但未获取到私钥"} except Exception as e: jingrow.log_error("创建密钥对失败", f"实例 {instance_id}: {str(e)}") diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json index 618a6db..d2aba15 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.json +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json @@ -29,6 +29,7 @@ "ssh_port", "password", "column_break_20", + "key_pair_name", "private_key" ], "fields": [ @@ -187,11 +188,16 @@ "fieldname": "password", "fieldtype": "Password", "label": "服务器密码" + }, + { + "fieldname": "key_pair_name", + "fieldtype": "Data", + "label": "密钥对名称" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-28 17:53:25.476573", + "modified": "2025-07-28 20:45:29.493153", "modified_by": "Administrator", "module": "Jcloud", "name": "Jsite Server", diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py index bcae718..5aa4d4c 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.py +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py @@ -20,6 +20,7 @@ class JsiteServer(Document): end_date: DF.Datetime | None image_id: DF.Data | None instance_id: DF.Data | None + key_pair_name: DF.Data | None memory: DF.Data | None order_id: DF.Data | None password: DF.Password | None -- 2.48.1 From 273c144271f2bdf6e53487778586cfa2a0417e36 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 21:27:25 +0800 Subject: [PATCH 023/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E5=AF=B9=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 20 +++++++- .../pagetype/jsite_server/jsite_server.js | 49 ++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index fdee79c..7a82fd2 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -762,7 +762,7 @@ def create_aliyun_server(order_name): raise Exception("找不到对应的服务器记录") # 从服务器记录中获取配置信息 - region_id = server.region or 'cn-shanghai' + region_id = server.region image_id = server.image_id or 'e9363571cf2444aba422b17470285465' plan_id = server.planid or 'swas.s.c2m1s30b1.linux' period = server.period or 1 @@ -863,7 +863,7 @@ def update_server_record(instance_ids): jingrow.log_error("更新服务器信息失败", f"找不到实例ID为 {instance_id} 的Jsite Server记录") return {"success": False, "message": f"找不到实例ID为 {instance_id} 的Jsite Server记录"} - region_id = server.region or 'cn-shanghai' + region_id = server.region # 获取阿里云实例详细信息 instance_details = get_aliyun_instance_details(instance_ids, region_id) @@ -930,4 +930,20 @@ def update_server_record(instance_ids): except Exception as e: jingrow.log_error("更新服务器信息失败", f"更新服务器信息时发生错误: {str(e)}") + return {"success": False, "message": str(e)} + +@jingrow.whitelist() +def reset_server_key_pair(instance_id): + """重置服务器密钥对:先删除旧的,再创建新的""" + try: + # 第一步:删除旧的密钥对(如果存在) + delete_aliyun_instance_key_pair(instance_id) + + # 第二步:创建新的密钥对 + result = create_server_key_pair(instance_id) + + return result + + except Exception as e: + jingrow.log_error("重置密钥对失败", f"重置实例 {instance_id} 密钥对时发生错误: {str(e)}") return {"success": False, "message": str(e)} \ No newline at end of file diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js index a185715..ff17885 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -38,6 +38,16 @@ jingrow.ui.form.on("Jsite Server", { __('重置服务器密码') ); }); + + frm.add_custom_button(__('重置密钥对'), function() { + // 弹出确认对话框 + jingrow.confirm( + __('确定要重置密钥对吗?这将删除旧的密钥对并创建新的密钥对。重置后需要使用新的私钥才能连接服务器。'), + function() { + reset_key_pair(frm); + } + ); + }); } // 为password字段添加眼睛图标 @@ -87,7 +97,7 @@ function restart_server(frm) { method: 'jcloud.api.aliyun_server_light.reboot_aliyun_instance', args: { instance_id: frm.pg.instance_id, - region_id: frm.pg.region || 'cn-shanghai' + region_id: frm.pg.region }, callback: function(r) { if (r.success) { @@ -123,7 +133,7 @@ function reset_password(frm, new_password) { args: { instance_id: frm.pg.instance_id, password: new_password, - region_id: frm.pg.region || 'cn-shanghai' + region_id: frm.pg.region }, callback: function(r) { if (r.success) { @@ -153,3 +163,38 @@ function reset_password(frm, new_password) { } }); } + +function reset_key_pair(frm) { + jingrow.call({ + method: 'jcloud.api.aliyun_server_light.reset_server_key_pair', + args: { + instance_id: frm.pg.instance_id + }, + callback: function(r) { + if (r.success) { + jingrow.msgprint({ + title: __('成功'), + message: __('密钥对重置成功,新的私钥已保存到服务器记录中'), + indicator: 'green' + }); + // 刷新页面以显示新的密钥对信息 + setTimeout(function() { + frm.reload_pg(); + }, 2000); + } else { + jingrow.msgprint({ + title: __('重置失败'), + message: r.message || r.error || __('未知错误'), + indicator: 'red' + }); + } + }, + error: function(err) { + jingrow.msgprint({ + title: __('重置失败'), + message: err.message, + indicator: 'red' + }); + } + }); +} -- 2.48.1 From 5d8e22f579083c7649f0a3ccf6db89c4cb19d317 Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 22:55:39 +0800 Subject: [PATCH 024/250] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=8C=89=E9=92=AE=E5=8F=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 72 +++++++++++++------ .../pagetype/jsite_server/jsite_server.js | 46 ++++++++++++ 2 files changed, 96 insertions(+), 22 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index 7a82fd2..d1ccae0 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -200,15 +200,16 @@ class AliyunLightServerManager: jingrow.log_error("续费实例失败", f"续费实例 {instance_id} 时发生错误: {str(e)}") return {'success': False, 'error': str(e), 'message': '实例续费失败'} - def reset_system(self, instance_id, image_id, password=None, region_id='cn-shanghai'): + def reset_system(self, instance_id, image_id=None, password=None, region_id='cn-shanghai'): """重置系统""" client = self._get_client(region_id) try: request = swas__open20200601_models.ResetSystemRequest( region_id=region_id, - instance_id=instance_id, - image_id=image_id + instance_id=instance_id ) + if image_id: + request.image_id = image_id if password: request.password = password runtime = util_models.RuntimeOptions() @@ -448,12 +449,12 @@ def reboot_aliyun_instance(instance_id, region_id='cn-shanghai'): manager = _get_manager() result = manager.reboot_instance(instance_id, region_id) - # 4. 启动后台任务监控重启状态 + # 4. 启动后台任务监控重启状态(延迟10秒开始) + time.sleep(10) jingrow.enqueue( - "jcloud.api.aliyun_server_light.monitor_reboot_status", + "jcloud.api.aliyun_server_light.monitor_server_status", instance_id=instance_id, region_id=region_id, - queue="long", timeout=600 ) @@ -468,8 +469,8 @@ def reboot_aliyun_instance(instance_id, region_id='cn-shanghai'): return {"success": False, "error": str(e), "message": "重启实例失败"} -def monitor_reboot_status(instance_id, region_id): - """简单的重启状态监控 - 直接循环查询""" +def monitor_server_status(instance_id, region_id): + """通用的服务器状态监控 - 监控服务器状态直到Running""" try: # 通过instance_id查找服务器记录 server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) @@ -478,7 +479,9 @@ def monitor_reboot_status(instance_id, region_id): return manager = _get_manager() - max_retries = 30 # 最多查询30次 + max_retries = 60 # 最多查询60次(10分钟) + # 阿里云轻量应用服务器可能的状态值 + waiting_states = ['Starting', 'Stopping', 'Resetting', 'Stopped', 'Pending'] for retry in range(max_retries): # 查询实例状态 @@ -498,30 +501,24 @@ def monitor_reboot_status(instance_id, region_id): instance = instances[0] # 取第一个实例 status = instance.get('status', '') - + # 更新服务器状态 if status == 'Running': server.status = 'Running' server.save(ignore_permissions=True) jingrow.db.commit() - jingrow.log_error("重启完成", f"服务器 {server.name} 重启完成") return - elif status in ['Starting', 'Stopping']: + elif status in waiting_states: # 继续等待 time.sleep(10) else: - # 其他状态,记录日志并停止 server.status = status server.save(ignore_permissions=True) jingrow.db.commit() - jingrow.log_error("重启状态异常", f"服务器 {server.name} 状态为: {status}") return - # 超时处理 - jingrow.log_error("监控超时", f"服务器 {server.name} 重启监控超时,已查询 {max_retries} 次") - except Exception as e: - jingrow.log_error("监控重启状态失败", f"监控实例 {instance_id} 重启状态时发生错误: {str(e)}") + jingrow.log_error("监控状态失败", f"监控实例 {instance_id} 状态时发生错误: {str(e)}") @jingrow.whitelist() def upgrade_aliyun_instance(instance_id, plan_id, region_id='cn-shanghai'): @@ -536,10 +533,41 @@ def renew_aliyun_instance(instance_id, period=1, period_unit='Month', region_id= return manager.renew_instance(instance_id, period, period_unit, region_id) @jingrow.whitelist() -def reset_aliyun_instance_system(instance_id, image_id, password=None, region_id='cn-shanghai'): - """重置系统""" - manager = _get_manager() - return manager.reset_system(instance_id, image_id, password, region_id) +def reset_aliyun_instance_system(instance_id, image_id=None, password=None, region_id='cn-shanghai'): + """重置系统并更新状态""" + try: + # 1. 查找对应的Jsite Server记录 + server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id}) + if not server: + return {"success": False, "message": "找不到对应的服务器记录"} + + # 2. 立即更新状态为"重置中" + server.status = "Resetting" + server.save(ignore_permissions=True) + jingrow.db.commit() + + # 3. 调用阿里云API重置系统 + manager = _get_manager() + result = manager.reset_system(instance_id, image_id, password, region_id) + + # 4. 启动后台任务监控重置状态(延迟10秒开始) + time.sleep(10) + jingrow.enqueue( + "jcloud.api.aliyun_server_light.monitor_server_status", + instance_id=instance_id, + region_id=region_id, + timeout=600 + ) + + return { + "success": True, + "message": "重置命令已发送,正在监控重置状态...", + "server_name": server.name + } + + except Exception as e: + jingrow.log_error("重置系统失败", f"重置实例 {instance_id} 系统时发生错误: {str(e)}") + return {"success": False, "error": str(e), "message": "重置系统失败"} @jingrow.whitelist() def update_aliyun_instance_password(instance_id, password, region_id='cn-shanghai'): diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js index ff17885..9ae9cbf 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -48,6 +48,16 @@ jingrow.ui.form.on("Jsite Server", { } ); }); + + frm.add_custom_button(__('重置系统'), function() { + // 弹出确认对话框 + jingrow.confirm( + __('确定要重置系统吗?这将清除所有数据并重新安装系统,操作不可逆!'), + function() { + reset_system(frm); + } + ); + }); } // 为password字段添加眼睛图标 @@ -198,3 +208,39 @@ function reset_key_pair(frm) { } }); } + +function reset_system(frm) { + jingrow.call({ + method: 'jcloud.api.aliyun_server_light.reset_aliyun_instance_system', + args: { + instance_id: frm.pg.instance_id, + region_id: frm.pg.region + }, + callback: function(r) { + if (r.success) { + jingrow.msgprint({ + title: __('成功'), + message: __('系统重置命令已发送,请稍后刷新页面查看状态'), + indicator: 'green' + }); + // 刷新页面以显示最新状态 + setTimeout(function() { + frm.reload_pg(); + }, 3000); + } else { + jingrow.msgprint({ + title: __('重置失败'), + message: r.message || r.error || __('未知错误'), + indicator: 'red' + }); + } + }, + error: function(err) { + jingrow.msgprint({ + title: __('重置失败'), + message: err.message, + indicator: 'red' + }); + } + }); +} -- 2.48.1 From ea646f4106fe95c34ee54249abe016a0a573242e Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 23:18:29 +0800 Subject: [PATCH 025/250] =?UTF-8?q?=E4=BC=98=E5=8C=96jsite=5Fserver.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetype/jsite_server/jsite_server.js | 103 ------------------ 1 file changed, 103 deletions(-) diff --git a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js index 9ae9cbf..f4564f9 100644 --- a/jcloud/jcloud/pagetype/jsite_server/jsite_server.js +++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js @@ -108,31 +108,6 @@ function restart_server(frm) { args: { instance_id: frm.pg.instance_id, region_id: frm.pg.region - }, - callback: function(r) { - if (r.success) { - jingrow.msgprint({ - title: __('成功'), - message: __('重启命令已发送,请稍后刷新页面查看状态'), - indicator: 'green' - }); - setTimeout(function() { - frm.reload_pg(); - }, 3000); - } else { - jingrow.msgprint({ - title: __('重启失败'), - message: r.message || r.error || __('未知错误'), - indicator: 'red' - }); - } - }, - error: function(err) { - jingrow.msgprint({ - title: __('重启失败'), - message: err.message, - indicator: 'red' - }); } }); } @@ -144,32 +119,6 @@ function reset_password(frm, new_password) { instance_id: frm.pg.instance_id, password: new_password, region_id: frm.pg.region - }, - callback: function(r) { - if (r.success) { - jingrow.msgprint({ - title: __('成功'), - message: __('密码重置成功,新密码已保存到服务器记录中'), - indicator: 'green' - }); - // 刷新页面以显示新密码 - setTimeout(function() { - frm.reload_pg(); - }, 2000); - } else { - jingrow.msgprint({ - title: __('重置失败'), - message: r.message || r.error || __('未知错误'), - indicator: 'red' - }); - } - }, - error: function(err) { - jingrow.msgprint({ - title: __('重置失败'), - message: err.message, - indicator: 'red' - }); } }); } @@ -179,32 +128,6 @@ function reset_key_pair(frm) { method: 'jcloud.api.aliyun_server_light.reset_server_key_pair', args: { instance_id: frm.pg.instance_id - }, - callback: function(r) { - if (r.success) { - jingrow.msgprint({ - title: __('成功'), - message: __('密钥对重置成功,新的私钥已保存到服务器记录中'), - indicator: 'green' - }); - // 刷新页面以显示新的密钥对信息 - setTimeout(function() { - frm.reload_pg(); - }, 2000); - } else { - jingrow.msgprint({ - title: __('重置失败'), - message: r.message || r.error || __('未知错误'), - indicator: 'red' - }); - } - }, - error: function(err) { - jingrow.msgprint({ - title: __('重置失败'), - message: err.message, - indicator: 'red' - }); } }); } @@ -215,32 +138,6 @@ function reset_system(frm) { args: { instance_id: frm.pg.instance_id, region_id: frm.pg.region - }, - callback: function(r) { - if (r.success) { - jingrow.msgprint({ - title: __('成功'), - message: __('系统重置命令已发送,请稍后刷新页面查看状态'), - indicator: 'green' - }); - // 刷新页面以显示最新状态 - setTimeout(function() { - frm.reload_pg(); - }, 3000); - } else { - jingrow.msgprint({ - title: __('重置失败'), - message: r.message || r.error || __('未知错误'), - indicator: 'red' - }); - } - }, - error: function(err) { - jingrow.msgprint({ - title: __('重置失败'), - message: err.message, - indicator: 'red' - }); } }); } -- 2.48.1 From 2087285c0038111eb8ec24531aced562b6e58eff Mon Sep 17 00:00:00 2001 From: jingrow Date: Mon, 28 Jul 2025 23:24:23 +0800 Subject: [PATCH 026/250] =?UTF-8?q?=E5=88=A0=E9=99=A4aliyun=5Fserver=5Flig?= =?UTF-8?q?ht.py=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jcloud/api/aliyun_server_light.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py index d1ccae0..6330091 100644 --- a/jcloud/api/aliyun_server_light.py +++ b/jcloud/api/aliyun_server_light.py @@ -840,10 +840,7 @@ def create_server_key_pair(instance_id): # 直接调用管理器方法创建密钥对 manager = _get_manager() key_pair_result = manager.create_instance_key_pair(instance_id, key_pair_name, region_id) - - # 添加调试日志 - jingrow.log_error("密钥对创建调试", f"完整的key_pair_result: {key_pair_result}") - + if not key_pair_result or not key_pair_result.get('success'): error_msg = key_pair_result.get('message', '未知错误') if key_pair_result else '返回结果为空' jingrow.log_error(f"密钥对创建失败: {error_msg}") -- 2.48.1 From ad2929e2fac673c398ff9917bac239e2f12ea7af Mon Sep 17 00:00:00 2001 From: jingrow Date: Tue, 29 Jul 2025 00:20:55 +0800 Subject: [PATCH 027/250] =?UTF-8?q?dashboard=E5=A2=9E=E5=8A=A0Jsite?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E8=8F=9C=E5=8D=95=E5=B9=B6=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=88=97=E8=A1=A8=E9=A1=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/components/NavigationItems.vue | 10 + dashboard/src2/objects/index.js | 2 + dashboard/src2/objects/jsite_server.js | 275 ++++++++++++++++++ dashboard/src2/router.js | 21 ++ jcloud/api/client.py | 1 + .../pagetype/jsite_server/jsite_server.py | 30 +- 6 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 dashboard/src2/objects/jsite_server.js 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 -- 2.48.1 From 71b62d9bdfa04353c1030e31d7ee9007f0cabd44 Mon Sep 17 00:00:00 2001 From: jingrow Date: Tue, 29 Jul 2025 00:45:26 +0800 Subject: [PATCH 028/250] =?UTF-8?q?dashbaord=E5=A2=9E=E5=8A=A0Jsite?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=E8=AF=A6=E6=83=85=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src2/components/JsiteServerOverview.vue | 238 ++++++++++++++++++ dashboard/src2/objects/jsite_server.js | 6 +- 2 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 dashboard/src2/components/JsiteServerOverview.vue diff --git a/dashboard/src2/components/JsiteServerOverview.vue b/dashboard/src2/components/JsiteServerOverview.vue new file mode 100644 index 0000000..f322746 --- /dev/null +++ b/dashboard/src2/components/JsiteServerOverview.vue @@ -0,0 +1,238 @@ + + + \ No newline at end of file diff --git a/dashboard/src2/objects/jsite_server.js b/dashboard/src2/objects/jsite_server.js index 44e703f..32035c6 100644 --- a/dashboard/src2/objects/jsite_server.js +++ b/dashboard/src2/objects/jsite_server.js @@ -197,7 +197,11 @@ export default { { label: '概览', route: '', - type: 'fields' + type: 'Component', + component: defineAsyncComponent(() => import('../components/JsiteServerOverview.vue')), + props: jsiteServer => { + return { server: jsiteServer.pg?.name }; + } } ], fields: [ -- 2.48.1 From 77bc63da84800129522f590c9a068ec93baadd33 Mon Sep 17 00:00:00 2001 From: jingrow Date: Tue, 29 Jul 2025 13:19:25 +0800 Subject: [PATCH 029/250] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E9=A2=9C=E8=89=B2=E6=98=A0=E5=B0=84=E4=B8=BA?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=AD=E6=96=87=E7=8A=B6=E6=80=81=E5=80=BC?= =?UTF-8?q?=EF=BC=8CJsite=E6=9C=8D=E5=8A=A1=E5=99=A8=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=92=8C=E5=8C=BA=E5=9F=9F=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=B8=BA=E4=B8=AD=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/components/global/Badge.vue | 75 ++++++++------- dashboard/src2/objects/jsite_server.js | 109 +++++++++++++++++----- 2 files changed, 127 insertions(+), 57 deletions(-) diff --git a/dashboard/src/components/global/Badge.vue b/dashboard/src/components/global/Badge.vue index a4bc03b..895ae13 100644 --- a/dashboard/src/components/global/Badge.vue +++ b/dashboard/src/components/global/Badge.vue @@ -22,42 +22,45 @@ export default { computed: { _color() { if (this.theme) return this.theme; - return { - Approved: 'green', - Recovering: 'orange', - Recovered: 'blue', - Broken: 'red', - Installing: 'orange', - Running: 'blue', - Pending: 'orange', - Failure: 'red', - Fatal: 'red', - Failed: 'red', - 'Update Available': 'blue', - Enabled: 'blue', - 'Awaiting Approval': 'orange', - 'Awaiting Deploy': 'orange', - Success: 'green', - Completed: 'green', - Deployed: 'green', - Expired: 'red', - Paid: 'green', - Unpaid: 'orange', - 'Invoice Created': 'blue', - Rejected: 'red', - 'In Review': 'orange', - 'Attention Required': 'red', - Active: 'green', - Trial: 'orange', - Published: 'green', - Owner: 'blue', - Primary: 'green', - 'Latest Deployed': 'orange', - 'Not Deployed': 'orange', - 'Action Required': 'red', - 'First Deploy': 'green', - 'Will be Uninstalled': 'red', - }[this.label]; + + // 状态配置数组 - 每个配置包含英文、中文、颜色三个属性 + const statusConfigs = [ + // 服务器状态 + { en: 'Pending', zh: '待处理', color: 'orange' }, + { en: 'Starting', zh: '启动中', color: 'orange' }, + { en: 'Running', zh: '运行中', color: 'green' }, + { en: 'Stopping', zh: '停止中', color: 'orange' }, + { en: 'Stopped', zh: '已停止', color: 'red' }, + { en: 'Resetting', zh: '重置中', color: 'blue' }, + { en: 'Upgrading', zh: '升级中', color: 'purple' }, + { en: 'Disabled', zh: '已禁用', color: 'gray' }, + + // 其他状态 + { en: 'Approved', zh: '已批准', color: 'green' }, + { en: 'Recovering', zh: '恢复中', color: 'orange' }, + { en: 'Recovered', zh: '已恢复', color: 'blue' }, + { en: 'Broken', zh: '损坏', color: 'red' }, + { en: 'Installing', zh: '安装中', color: 'orange' }, + { en: 'Failure', zh: '失败', color: 'red' }, + { en: 'Fatal', zh: '致命错误', color: 'red' }, + { en: 'Failed', zh: '失败', color: 'red' }, + { en: 'Success', zh: '成功', color: 'green' }, + { en: 'Completed', zh: '已完成', color: 'green' }, + { en: 'Deployed', zh: '已部署', color: 'green' }, + { en: 'Expired', zh: '已过期', color: 'red' }, + { en: 'Paid', zh: '已支付', color: 'green' }, + { en: 'Unpaid', zh: '未支付', color: 'orange' }, + { en: 'Rejected', zh: '已拒绝', color: 'red' }, + { en: 'Active', zh: '激活', color: 'green' }, + { en: 'Trial', zh: '试用', color: 'orange' }, + { en: 'Published', zh: '已发布', color: 'green' }, + { en: 'Owner', zh: '所有者', color: 'blue' }, + { en: 'Primary', zh: '主要', color: 'green' }, + ]; + + // 查找匹配的配置 + const config = statusConfigs.find(item => item.en === this.label || item.zh === this.label); + return config ? config.color : 'gray'; }, }, }; diff --git a/dashboard/src2/objects/jsite_server.js b/dashboard/src2/objects/jsite_server.js index 32035c6..9c0f326 100644 --- a/dashboard/src2/objects/jsite_server.js +++ b/dashboard/src2/objects/jsite_server.js @@ -46,7 +46,7 @@ export default { fieldname: 'status', options: [ { label: '', value: '' }, - { label: '待定', value: 'Pending' }, + { label: '待处理', value: 'Pending' }, { label: '启动中', value: 'Starting' }, { label: '运行中', value: 'Running' }, { label: '停止中', value: 'Stopping' }, @@ -62,13 +62,32 @@ export default { fieldname: 'region', options: [ { label: '', value: '' }, - { label: '中国大陆', value: '中国大陆' }, - { label: '中国香港', value: '中国香港' }, - { label: '美国-洛杉矶', value: '美国-洛杉矶' }, - { label: '新加坡', value: '新加坡' }, - { label: '英国-伦敦', value: '英国-伦敦' }, - { label: '德国-法兰克福', value: '德国-法兰克福' }, - { label: '阿联酋-迪拜', value: '阿联酋-迪拜' } + { label: '华北1(青岛)', value: 'cn-qingdao' }, + { label: '华北2(北京)', value: 'cn-beijing' }, + { label: '华北3(张家口)', value: 'cn-zhangjiakou' }, + { label: '华北5(呼和浩特)', value: 'cn-huhehaote' }, + { label: '华东1(杭州)', value: 'cn-hangzhou' }, + { label: '华东2(上海)', value: 'cn-shanghai' }, + { label: '华南1(深圳)', value: 'cn-shenzhen' }, + { label: '华南2(河源)', value: 'cn-heyuan' }, + { label: '西南1(成都)', value: 'cn-chengdu' }, + { label: '华南3(广州)', value: 'cn-guangzhou' }, + { label: '华北6(乌兰察布)', value: 'cn-wulanchabu' }, + { label: '华东5(南京)', value: 'cn-nanjing' }, + { label: '华东6(福州)', value: 'cn-fuzhou' }, + { label: '华中1(武汉)', value: 'cn-wuhan-lr' }, + { label: '中国香港', value: 'cn-hongkong' }, + { label: '新加坡', value: 'ap-southeast-1' }, + { label: '马来西亚(吉隆坡)', value: 'ap-southeast-3' }, + { label: '印度尼西亚(雅加达)', value: 'ap-southeast-5' }, + { label: '日本(东京)', value: 'ap-northeast-1' }, + { label: '美国(硅谷)', value: 'us-west-1' }, + { label: '美国(弗吉尼亚)', value: 'us-east-1' }, + { label: '德国(法兰克福)', value: 'eu-central-1' }, + { label: '英国(伦敦)', value: 'eu-west-1' }, + { label: '菲律宾(马尼拉)', value: 'ap-southeast-6' }, + { label: '泰国(曼谷)', value: 'ap-southeast-7' }, + { label: '韩国(首尔)', value: 'ap-northeast-2' } ] } ]; @@ -89,7 +108,20 @@ export default { label: '状态', fieldname: 'status', type: 'Badge', - width: 0.8 + width: 0.8, + format(value) { + const statusMap = { + 'Pending': '待处理', + 'Starting': '启动中', + 'Running': '运行中', + 'Stopping': '停止中', + 'Stopped': '已停止', + 'Resetting': '重置中', + 'Upgrading': '升级中', + 'Disabled': '已禁用' + }; + return statusMap[value] || value; + } }, { label: '配置', @@ -112,7 +144,40 @@ export default { label: '区域', fieldname: 'region', format(value) { - return value || '-'; + if (!value) return '-'; + + // 区域ID到中文名称的映射表 + const regionMap = { + 'cn-qingdao': '华北1(青岛)', + 'cn-beijing': '华北2(北京)', + 'cn-zhangjiakou': '华北3(张家口)', + 'cn-huhehaote': '华北5(呼和浩特)', + 'cn-hangzhou': '华东1(杭州)', + 'cn-shanghai': '华东2(上海)', + 'cn-shenzhen': '华南1(深圳)', + 'cn-heyuan': '华南2(河源)', + 'cn-chengdu': '西南1(成都)', + 'cn-guangzhou': '华南3(广州)', + 'cn-wulanchabu': '华北6(乌兰察布)', + 'cn-nanjing': '华东5(南京)', + 'cn-fuzhou': '华东6(福州)', + 'cn-wuhan-lr': '华中1(武汉)', + 'cn-hongkong': '中国香港', + 'ap-southeast-1': '新加坡', + 'ap-southeast-3': '马来西亚(吉隆坡)', + 'ap-southeast-5': '印度尼西亚(雅加达)', + 'ap-northeast-1': '日本(东京)', + 'us-west-1': '美国(硅谷)', + 'us-east-1': '美国(弗吉尼亚)', + 'eu-central-1': '德国(法兰克福)', + 'eu-west-1': '英国(伦敦)', + 'ap-southeast-6': '菲律宾(马尼拉)', + 'ap-southeast-7': '泰国(曼谷)', + 'ap-northeast-2': '韩国(首尔)' + }; + + // 如果是RegionId,返回对应的中文名称;如果已经是中文名称,直接返回 + return regionMap[value] || value; } }, { @@ -120,7 +185,7 @@ export default { fieldname: 'end_date', format(value) { if (!value) return '-'; - return new Date(value).toLocaleDateString('zh-CN'); + return value; } } ], @@ -135,17 +200,19 @@ export default { }, 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' } + const statusMap = { + 'Pending': '待处理', + 'Starting': '启动中', + 'Running': '运行中', + 'Stopping': '停止中', + 'Stopped': '已停止', + 'Resetting': '重置中', + 'Upgrading': '升级中', + 'Disabled': '已禁用' + }; + return { + label: statusMap[status] || status }; - return statusConfig[status] || { label: status, color: 'gray' }; }, breadcrumbs({ documentResource: jsiteServer }) { return [ -- 2.48.1 From 5ea7f12a47e8bcd9aa1d214cd62661969ec3cb5a Mon Sep 17 00:00:00 2001 From: jingrow Date: Tue, 29 Jul 2025 13:43:51 +0800 Subject: [PATCH 030/250] =?UTF-8?q?=E5=88=A0=E9=99=A4NewJsiteServer.vue?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97=E4=BC=98=E5=8C=96jsite=5Fs?= =?UTF-8?q?erver.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src2/objects/jsite_server.js | 9 +++-- dashboard/src2/pages/NewJsiteServer.vue | 49 +++++-------------------- 2 files changed, 15 insertions(+), 43 deletions(-) diff --git a/dashboard/src2/objects/jsite_server.js b/dashboard/src2/objects/jsite_server.js index 9c0f326..d43393e 100644 --- a/dashboard/src2/objects/jsite_server.js +++ b/dashboard/src2/objects/jsite_server.js @@ -191,8 +191,11 @@ export default { ], primaryAction({ listResource: jsiteServers }) { return { - label: '新建Jsite服务器', - icon: 'plus', + label: '新建服务器', + variant: 'solid', + slots: { + prefix: icon('plus') + }, onClick() { router.push('/jsite-servers/new'); } @@ -217,7 +220,7 @@ export default { breadcrumbs({ documentResource: jsiteServer }) { return [ { - label: 'Jsite服务器', + label: '服务器', route: '/jsite-servers' }, { diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue index bd4a6ee..2ae0bcc 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src2/pages/NewJsiteServer.vue @@ -1,27 +1,27 @@