diff --git a/.gitignore b/.gitignore
index 81cc2c2..e4354d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,7 +91,7 @@ ipython_config.py
# install all needed dependencies.
#Pipfile.lock
-# PEP 582; used by e.g. git.jingrow.com:3000/David-OConnor/pyflow
+# PEP 582; used by e.g. git.jingrow.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
diff --git a/README.md b/README.md
index f056ab9..ff47383 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
**Full Service Cloud Hosting For The Jingrow Stack - Powers Jingrow**
[](https://codecov.io/gh/jingrow/jcloud)
-[](http://git.jingrow.com:3000/jingrow/jcloud/actions/workflows/main.yaml)
+[](http://git.jingrow.com/jingrow/jcloud/actions/workflows/main.yaml)
@@ -54,11 +54,11 @@ Additionally, customers lacked full control over their servers—no SSH access,
### Under the Hood
-- [**Jingrow Framework**](http://git.jingrow.com:3000/jingrow/jingrow): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
+- [**Jingrow Framework**](http://git.jingrow.com/jingrow/jingrow): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
-- [**Jingrow UI**](http://git.jingrow.com:3000/jingrow/jingrow-ui): A Vue-based UI library, to provide a modern user interface. The Jingrow UI library provides a variety of components that can be used to build single-page applications on top of the Jingrow Framework.
+- [**Jingrow UI**](http://git.jingrow.com/jingrow/jingrow-ui): A Vue-based UI library, to provide a modern user interface. The Jingrow UI library provides a variety of components that can be used to build single-page applications on top of the Jingrow Framework.
-- [**Agent**](http://git.jingrow.com:3000/jingrow/agent): A flask app designed to work along with Jcloud. It provides a CLI interface for Jcloud to communicate with the sites and benches.
+- [**Agent**](http://git.jingrow.com/jingrow/agent): A flask app designed to work along with Jcloud. It provides a CLI interface for Jcloud to communicate with the sites and benches.
- [**Docker**](https://www.docker.com): An open-source platform that enables developers to build, package, and deploy applications in lightweight, portable containers.
diff --git a/backbone/setup_mac.py b/backbone/setup_mac.py
index 80bd730..c4a2213 100644
--- a/backbone/setup_mac.py
+++ b/backbone/setup_mac.py
@@ -49,8 +49,8 @@ def setup_libvirt():
def setup_packer():
shell.execute("brew tap hashicorp/tap")
brew_install("hashicorp/tap/packer")
- shell.execute("packer plugins install git.jingrow.com:3000/hashicorp/qemu")
- shell.execute("packer plugins install git.jingrow.com:3000/hashicorp/vagrant")
+ shell.execute("packer plugins install git.jingrow.com/hashicorp/qemu")
+ shell.execute("packer plugins install git.jingrow.com/hashicorp/vagrant")
if __name__ == "__main__":
diff --git a/dashboard/src/components/global/Badge.vue b/dashboard/src/components/global/Badge.vue
index a4bc03b..cb48411 100644
--- a/dashboard/src/components/global/Badge.vue
+++ b/dashboard/src/components/global/Badge.vue
@@ -22,42 +22,53 @@ 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: 'ok', zh: '正常', color: 'green' },
+ { en: 'clienthold', zh: '锁定', color: 'red' },
+ { en: 'clientupdateprohibited', zh: '更新锁定', color: 'red' },
+ { en: 'clienttransferprohibited', zh: '转移锁定', color: 'red' },
+ { en: 'clientdeleteprohibited', zh: '删除锁定', color: 'red' },
+ { en: 'clientrenewprohibited', zh: '续费锁定', color: 'red' },
+
+ // 服务器状态
+ { 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/src/views/site/SiteAppsAndSubscriptions.vue b/dashboard/src/views/site/SiteAppsAndSubscriptions.vue
index 39c0a93..4f0c398 100644
--- a/dashboard/src/views/site/SiteAppsAndSubscriptions.vue
+++ b/dashboard/src/views/site/SiteAppsAndSubscriptions.vue
@@ -431,7 +431,7 @@ export default {
this.$confirm({
title: 'Remove App',
message: `Are you sure you want to uninstall app ${app.title} from site ?
- All doctypes and modules pertaining to this app will be removed. `,
+ All pagetypes and modules pertaining to this app will be removed. `,
actionLabel: 'Remove App',
actionColor: 'red',
action: closeDialog => {
diff --git a/dashboard/src2/components/ChurnFeedbackDialog.vue b/dashboard/src2/components/ChurnFeedbackDialog.vue
index f02c761..78fbecb 100644
--- a/dashboard/src2/components/ChurnFeedbackDialog.vue
+++ b/dashboard/src2/components/ChurnFeedbackDialog.vue
@@ -95,9 +95,9 @@ export default {
if (
[
- 'Payment issues',
- 'Features were missing',
- 'My reason is not listed here'
+ '支付问题',
+ '缺少功能',
+ '我的原因不在此列表中'
].includes(this.feedback) &&
!this.note
) {
@@ -118,15 +118,15 @@ export default {
computed: {
options() {
return [
- 'I am moving to a different product e.g ZOHO, Quickbooks, etc.',
- 'I was just exploring the product',
- 'I prefer self-hosting my instance',
- 'Moved site to another 今果 Jingrow account',
- 'I did not like the 今果 Jingrow experience',
- '今果 Jingrow is too expensive for me',
- 'Payment issues',
- 'Features were missing',
- 'My reason is not listed here'
+ '我要迁移到其他产品',
+ '我只是在探索这个产品',
+ '我更喜欢自己托管实例',
+ '已将站点迁移到另一个Jingrow账户',
+ '我不喜欢Jingrow的体验',
+ 'Jingrow对我来说太贵了',
+ '支付问题',
+ '缺少功能',
+ '我的原因不在此列表中'
];
}
}
diff --git a/dashboard/src2/components/DomainOwner.vue b/dashboard/src2/components/DomainOwner.vue
new file mode 100644
index 0000000..337ad53
--- /dev/null
+++ b/dashboard/src2/components/DomainOwner.vue
@@ -0,0 +1,935 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 全部状态
+ 已实名认证
+ 未实名认证
+
+
+
+
+
+
+
+
+
+
+
+
+
所有者名称
+
实名状态
+
联系信息
+
操作
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
暂无所有者模板
+
点击上方按钮创建第一个模板
+
+
+
+
+
+
+
+
{{ getDisplayName(owner) }}
+
+
+
+
+ {{ getRealNameStatusText(owner.r_status) }}
+
+
+
+
+
{{ owner.c_ph || '-' }}
+
{{ owner.c_em || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 显示第 {{ (pagination.pageno - 1) * pagination.limit + 1 }} -
+ {{ Math.min(pagination.pageno * pagination.limit, pagination.total) }} 条,
+ 共 {{ pagination.total }} 条记录
+
+
+
+ ←
+
+
+
+ {{ page }}
+
+
+
+ →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基本信息
+
+
+
所有者类型
+
{{ getOwnerTypeText(selectedOwner.c_regtype) }}
+
+
+
实名认证状态
+
{{ getRealNameStatusText(selectedOwner.r_status) }}
+
+
+
单位名称
+
{{ selectedOwner.c_org_m || '-' }}
+
+
+
姓名
+
{{ getDisplayName(selectedOwner) }}
+
+
+
+
+
+
+
联系信息
+
+
+
电子邮箱
+
{{ selectedOwner.c_em || '-' }}
+
+
+
手机号码
+
{{ selectedOwner.c_ph || '-' }}
+
+
+
省份
+
{{ selectedOwner.c_st_m || '-' }}
+
+
+
城市
+
{{ selectedOwner.c_ct_m || '-' }}
+
+
+
通讯地址
+
{{ selectedOwner.c_adr_m || '-' }}
+
+
+
+
+
+
+
证件信息
+
+
+
证件类型
+
{{ getCertificateTypeName(selectedOwner.c_idtype_gswl) }}
+
+
+
证件号码
+
{{ selectedOwner.c_idnum_gswl || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/DomainOwnerDialog.vue b/dashboard/src2/components/DomainOwnerDialog.vue
new file mode 100644
index 0000000..3df7d0b
--- /dev/null
+++ b/dashboard/src2/components/DomainOwnerDialog.vue
@@ -0,0 +1,617 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 域名所有者名称代表域名的所有权,必须与证件及备案主体一致,实名认证后不可更改。所有信息须真实完整,手机号码必须归属本人,禁止代持,变更后90天内不可再改,虚假信息可能导致域名被注销。
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ {{ isLoading ? '创建中...' : '保存' }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainAddDNSRecordDialog.vue b/dashboard/src2/components/JsiteDomainAddDNSRecordDialog.vue
new file mode 100644
index 0000000..070c5f7
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainAddDNSRecordDialog.vue
@@ -0,0 +1,246 @@
+
+
+
+
+ 添加DNS记录
+
+ 为域名 {{ domainDoc?.domain }} 添加新的DNS解析记录
+
+
+
+
+
+
+
+ 取消
+
+
+ 添加记录
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainDNSRecords.vue b/dashboard/src2/components/JsiteDomainDNSRecords.vue
new file mode 100644
index 0000000..0089b14
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainDNSRecords.vue
@@ -0,0 +1,828 @@
+
+
+
+
+
+
+
+
+
+ 刷新
+
+
+
+ 删除选中 ({{ selectedRecords.length }})
+
+
+
+ 添加记录
+
+
+
+
+
+
+
+
+
+
+
+
+
加载失败
+
{{ $resources.dnsRecords.error }}
+
+
+
+
+
+
+
+
+
+
+
+ 显示第 {{ (pagination.pageno - 1) * pagination.limit + 1 }} -
+ {{ Math.min(pagination.pageno * pagination.limit, pagination.total) }} 条,
+ 共 {{ pagination.total }} 条记录
+
+
+
+ ←
+
+
+
+ {{ page }}
+
+
+
+ →
+
+
+
+
+
+
+
+
+
暂无DNS记录
+
开始添加您的第一个DNS解析记录
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainEditDNSRecordDialog.vue b/dashboard/src2/components/JsiteDomainEditDNSRecordDialog.vue
new file mode 100644
index 0000000..3d21515
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainEditDNSRecordDialog.vue
@@ -0,0 +1,259 @@
+
+
+
+
+ 编辑DNS记录
+
+ 编辑域名 {{ domainDoc?.domain }} 的DNS解析记录
+
+
+
+
+
+
+
+ 取消
+
+
+ 保存修改
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainModifyDNSServerDialog.vue b/dashboard/src2/components/JsiteDomainModifyDNSServerDialog.vue
new file mode 100644
index 0000000..b5d75f8
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainModifyDNSServerDialog.vue
@@ -0,0 +1,299 @@
+
+
+
+
+
+
+
+
+ DNS1(主DNS服务器)*
+
+
+
{{ errors.dns1 }}
+
+
+
+
+
+ DNS2(辅DNS服务器)*
+
+
+
{{ errors.dns2 }}
+
+
+
+
+
+ DNS3(可选)
+
+
+
{{ errors.dns3 }}
+
+
+
+
+
+ DNS4(可选)
+
+
+
{{ errors.dns4 }}
+
+
+
+
+
+ DNS5(可选)
+
+
+
{{ errors.dns5 }}
+
+
+
+
+
+ DNS6(可选)
+
+
+
{{ errors.dns6 }}
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+ 取消
+
+
+ {{ isLoading ? '修改中...' : '确定' }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainOverview.vue b/dashboard/src2/components/JsiteDomainOverview.vue
new file mode 100644
index 0000000..01f97f7
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainOverview.vue
@@ -0,0 +1,505 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 到期时间:{{ $format.date($domain.pg.end_date) }}
+
+
+
+
+ 续费
+
+
+
+
+
+
+
+
+
+
域名信息
+
+
+
+
{{ info.label }}
+
+
+
+
+
+
+
+
+
+ {{ info.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
DNS服务器
+
+
+
+
+ 主DNS:
+ {{ $domain.pg.dns_host1 }}
+
+
+ 辅DNS:
+ {{ $domain.pg.dns_host2 }}
+
+
+ DNS3:
+ {{ $domain.pg.dns_host3 }}
+
+
+ DNS4:
+ {{ $domain.pg.dns_host4 }}
+
+
+ DNS5:
+ {{ $domain.pg.dns_host5 }}
+
+
+ DNS6:
+ {{ $domain.pg.dns_host6 }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainRealNameInfoDialog.vue b/dashboard/src2/components/JsiteDomainRealNameInfoDialog.vue
new file mode 100644
index 0000000..09e5396
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainRealNameInfoDialog.vue
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+
+
+
+
+
域名基本信息
+
+
+
+ 域名
+ {{ realNameData.domain }}
+
+
+ 注册时间
+ {{ formatDateTime(realNameData.regdate) }}
+
+
+ 到期时间
+ {{ formatDateTime(realNameData.rexpiredate) }}
+
+
+ 域名状态
+ {{ getDomainStatusText(realNameData.status) }}
+
+
+ 实名状态
+ {{ getRealNameStatusText(realNameData.real_name_status?.r_status) }}
+
+
+ 域名所有者
+ {{ realNameData.owner?.dom_org_m || '未填写' }}
+
+
+
+
+
+
+
+
实名证件信息
+
+
+
+ 实名证件类型
+ {{ getDocumentTypeText(realNameData.orgfile?.f_type) }}
+
+
+
实名证件号码
+
+
{{ realNameData.orgfile?.f_code || '未填写' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
联系信息
+
+
+
+ 联系电话
+ {{ realNameData.owner?.dom_ph || '未填写' }}
+
+
+ 所有者邮箱
+ {{ realNameData.owner?.dom_em || '未填写' }}
+
+
+ 所属区域
+ {{ getFullAddress() }}
+
+
+ 通讯地址
+ {{ realNameData.owner?.dom_adr_m || '未填写' }}
+
+
+ 邮编
+ {{ realNameData.owner?.dom_pc || '未填写' }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainRenewalDialog.vue b/dashboard/src2/components/JsiteDomainRenewalDialog.vue
new file mode 100644
index 0000000..c78bb45
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainRenewalDialog.vue
@@ -0,0 +1,537 @@
+
+
+
+
+
+
+
+ 域名: {{ domainDoc?.domain }}
+
+
+
+
+
+ 续费时长
+
+
+
+ {{ period.name }}
+ -{{ period.discount }}%
+
+
+
+
+
+
+
续费时长
+
{{ renewalPeriod }} 年
+
+
+
折扣
+
-{{ discountPercentage }}%
+
+
+
总计
+
¥ {{ totalAmount }}
+
+
+
+
+
+
+ 选择支付方式
+
+
+
+ 余额支付
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
续费成功!
+
+ 您的域名已成功续费 {{ renewalPeriod }} 年
+
+
+
+
+
+
+
+
+
扫一扫付款(元)
+
{{ order?.total_amount }} 元
+
+
+
+
+
+
+
+
+
+
+ 请使用微信扫描二维码完成支付
+
+
二维码有效期 15 分钟
+
+
+
+
+
+
+
+
请在新页面完成支付宝支付
+
+ 如果没有自动跳转,请点击下方按钮打开支付页面
+
+
+
+ 打开支付页面
+
+
+ 支付完成后,请稍等片刻,系统会自动刷新页面
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+ {{ isLoading ? '处理中...' : (isPriceLoading ? '获取价格中...' : '确认续费') }}
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainTransferDialog.vue b/dashboard/src2/components/JsiteDomainTransferDialog.vue
new file mode 100644
index 0000000..6a519ce
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainTransferDialog.vue
@@ -0,0 +1,204 @@
+
+
+
+
域名转入
+
+ 将域名转入到我们的平台进行管理
+
+
+
+
+
+ 域名
+
+
+
+
+
转移授权码
+
+
+ 转移授权码需要从原域名注册商处获取
+
+
+
+
+
+
转入费用
+
+ ¥ {{ transferPrice }}
+ (年付)
+
+
+
+
+
总计
+
¥ {{ transferPrice }}
+
+
+
+
+
+ 选择支付方式
+
+
+
+ 余额支付
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+ 取消
+
+
+ {{ isLoading ? '处理中...' : '确认转入' }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteDomainUploadRealNameDialog.vue b/dashboard/src2/components/JsiteDomainUploadRealNameDialog.vue
new file mode 100644
index 0000000..2a78132
--- /dev/null
+++ b/dashboard/src2/components/JsiteDomainUploadRealNameDialog.vue
@@ -0,0 +1,397 @@
+
+
+
+
+
+
+
+
域名:
+
{{ domainDoc?.domain || domain }}
+
+
+
+
资料状态
+
+
+ 未传图片
+
+
+ 未实名
+
+
+
+
+
+
+
域名所有者
+
+ {{ ownerType ? `(${ownerType === 'I' ? '个人' : '企业'})` : '' }} {{ domainDoc?.owner_name || '未知' }}
+
+
+
+
+
+
所有者证件号码
+
+
+
+ {{ option.label }}
+
+
+
+
+
+
+
+
+
所有者证件材料
+
+
+
+
+
+
+
+ 请保证图片中证件四角完整、证件上文字信息完整清晰,请使用原件拍摄或彩色扫描件,不要用复印件,否则无法通过审核。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ file.name }}
+
{{ formatFileSize(file.size) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 重置
+
+
+ {{ loading ? '提交中...' : '提交' }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteServerFirewallRules.vue b/dashboard/src2/components/JsiteServerFirewallRules.vue
new file mode 100644
index 0000000..f0f79a0
--- /dev/null
+++ b/dashboard/src2/components/JsiteServerFirewallRules.vue
@@ -0,0 +1,609 @@
+
+
+
+
+
+
+
+
+
+ 刷新
+
+
+
+ 删除选中 ({{ selectedRules.length }})
+
+
+
+ 添加规则
+
+
+
+
+
+
+
+
+
+
+
+
+
加载失败
+
{{ $resources.firewallRules.error }}
+
+
+
+
+
+
+
+
+
+
+
+ 显示第 {{ (pagination.pageno - 1) * pagination.limit + 1 }} -
+ {{ Math.min(pagination.pageno * pagination.limit, pagination.total) }} 条,
+ 共 {{ pagination.total }} 条记录
+
+
+
+ ←
+
+
+
+ {{ page }}
+
+
+
+ →
+
+
+
+
+
+
+
+
+
暂无防火墙规则
+
开始添加您的第一个防火墙规则
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteServerOverview.vue b/dashboard/src2/components/JsiteServerOverview.vue
new file mode 100644
index 0000000..039a1a5
--- /dev/null
+++ b/dashboard/src2/components/JsiteServerOverview.vue
@@ -0,0 +1,772 @@
+
+
+
+
+
+
+
+
当前套餐
+
+
+
+
+
+ ¥{{ $jsiteServer.pg.plan_price }}/月
+
+
+
+
+ 到期时间:{{ $format.date($jsiteServer.pg.end_date) }}
+
+
+
+
+ 续费
+
+
+ 升级
+
+
+
+
+
+
+
+
+
+
服务器配置
+
+
+
+
+ CPU:
+ {{ $jsiteServer.pg.cpu || '未知' }}核
+
+
+ 内存:
+ {{ $jsiteServer.pg.memory || '未知' }}GB
+
+
+ 系统盘:
+ {{ $jsiteServer.pg.disk_size || '未知' }}GB
+
+
+ 带宽:
+ {{ $jsiteServer.pg.bandwidth || '未知' }}Mbps
+
+
+
+
+
+
+
+
+
服务器信息
+
+
+
+
{{ info.label }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ info.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ info.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
操作
+
+
+
+
+ 重启
+
+
+ 强制重启
+
+
+ 重置密码
+
+
+ 重置密钥对
+
+
+ 删除密钥对
+
+
+ 重置系统
+
+
+
+
+
+
+
+
+
SSH连接
+
+
+
+
+ SSH端口:
+ {{ $jsiteServer.pg.ssh_port || '22' }}
+
+
+ SSH用户:
+ {{ $jsiteServer.pg.ssh_user || 'root' }}
+
+
+
服务器密码:
+
+
+ {{ showPassword ? decryptedPassword : ($jsiteServer.pg.password || '未设置') }}
+
+
+
+
+
+
+
+
+ 密钥对名称:
+ {{ $jsiteServer.pg.key_pair_name || '未设置' }}
+
+
+
私钥:
+
+
+
+
+
{{ $jsiteServer.pg.private_key }}
+
+
未设置
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteServerRenewalDialog.vue b/dashboard/src2/components/JsiteServerRenewalDialog.vue
new file mode 100644
index 0000000..a61a876
--- /dev/null
+++ b/dashboard/src2/components/JsiteServerRenewalDialog.vue
@@ -0,0 +1,534 @@
+
+
+
+
+
+
+
+ 选择续费时长,确保您的服务器持续可用
+
+
+
+
+
+ 续费周期
+
+
+
+ {{ period.name }}
+ -{{ period.discount }}%
+
+
+
+
+
+
+
月度费用
+
+ ¥ {{ serverInfo.plan_price }}
+ (月付)
+
+
+
+
续费时长
+
{{ selectedPeriod }} 个月
+
+
+
折扣
+
-{{ discountPercentage }}%
+
+
+
总计
+
¥ {{ totalAmount }}
+
+
+
+
+
+ 选择支付方式
+
+
+
+ 余额支付
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
续费成功!
+
+ 您的服务器已成功续费 {{ selectedPeriod }} 个月
+
+
+
+
+
+
+
+
+
扫一扫付款(元)
+
{{ order?.total_amount }} 元
+
+
+
+
+
+
+
+
+
+
+ 请使用微信扫描二维码完成支付
+
+
二维码有效期 15 分钟
+
+
+
+
+
+
+
+
请在新页面完成支付宝支付
+
+ 如果没有自动跳转,请点击下方按钮打开支付页面
+
+
+
+ 打开支付页面
+
+
+ 支付完成后,请稍等片刻,系统会自动刷新页面
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+ {{ isLoading ? '处理中...' : '确认续费' }}
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/JsiteServerUpgradeDialog.vue b/dashboard/src2/components/JsiteServerUpgradeDialog.vue
new file mode 100644
index 0000000..e9d47f9
--- /dev/null
+++ b/dashboard/src2/components/JsiteServerUpgradeDialog.vue
@@ -0,0 +1,629 @@
+
+
+
+
+
+
+
+ 选择新的套餐配置,升级您的服务器性能
+
+
+
+
+
+
选择升级套餐
+
+
+
+
+ 请选择升级套餐
+
+ {{ getPlanDisplayName(plan) }}
+
+
+
+
+
+
+
+
费用明细
+
+
+ 当前套餐月费
+ ¥{{ upgradePriceInfo.current_plan_price }}
+
+
+ 新套餐月费
+ ¥{{ upgradePriceInfo.new_plan_price }}
+
+
+ 剩余天数
+ {{ upgradePriceInfo.remaining_days }}天
+
+
+ 每日差价
+ ¥{{ upgradePriceInfo.daily_price_diff.toFixed(2) }}
+
+
+
+ 升级费用
+ ¥{{ upgradePriceInfo.upgrade_amount.toFixed(2) }}
+
+
+
+
+
+
+
+
+ 选择支付方式
+
+
+
+ 余额支付
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支付成功!
+
+ 您的服务器升级已提交,请3至5分钟后刷新页面查看结果...
+
+
+
+
+
+
+
+
+
扫一扫付款(元)
+
{{ order?.total_amount }} 元
+
+
+
+
+
+
+
+
+
+
+ 请使用微信扫描二维码完成支付
+
+
二维码有效期 15 分钟
+
+
+
+
+
+
+
+
请在新页面完成支付宝支付
+
+ 如果没有自动跳转,请点击下方按钮打开支付页面
+
+
+
+ 打开支付页面
+
+
+ 支付完成后,请稍等片刻,系统会自动刷新页面
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+ {{ isLoading ? '处理中...' : '确认升级' }}
+
+
+
+
+ 关闭
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/components/NavigationItems.vue b/dashboard/src2/components/NavigationItems.vue
index 070b295..5fe6b57 100644
--- a/dashboard/src2/components/NavigationItems.vue
+++ b/dashboard/src2/components/NavigationItems.vue
@@ -100,7 +100,7 @@ export default {
disabled: enforce2FA,
},
{
- name: '服务器',
+ name: 'Jingrow服务器',
icon: () => h(Server),
route: '/servers',
isActive:
@@ -109,6 +109,24 @@ export default {
condition: onboardingComplete && !isSaasUser && this.$team.pg.is_pro,
disabled: enforce2FA,
},
+ {
+ name: '域名',
+ icon: () => h(Globe),
+ route: '/domains',
+ isActive:
+ ['Jsite Domain List', 'New Jsite Domain'].includes(routeName) ||
+ routeName.startsWith('Jsite Domain'),
+ disabled: enforce2FA,
+ },
+ {
+ name: '服务器',
+ icon: () => h(Server),
+ route: '/jsite-servers',
+ isActive:
+ ['Jsite Servers', 'New Jsite Server'].includes(routeName) ||
+ routeName.startsWith('Jsite Server'),
+ disabled: enforce2FA,
+ },
{
name: '应用市场',
icon: () => h(App),
@@ -123,7 +141,7 @@ export default {
name: '开发工具',
icon: () => h(Code),
route: '/devtools',
- condition: onboardingComplete && !isSaasUser && this.$team.pg.is_developer && this.$team.pg.is_pro,
+ condition: onboardingComplete && !isSaasUser && this.$team.pg.is_developer && this.$team.pg.is_pro,
disabled: enforce2FA,
children: [
{
diff --git a/dashboard/src2/components/ObjectList.vue b/dashboard/src2/components/ObjectList.vue
index 6bc7978..8273b33 100644
--- a/dashboard/src2/components/ObjectList.vue
+++ b/dashboard/src2/components/ObjectList.vue
@@ -306,7 +306,7 @@ this.$socket.emit('pagetype_unsubscribe', pagetype);
columns.push({
label: '',
key: '__actions',
- type: '操作',
+ type: 'Actions',
width: '100px',
align: 'right',
actions: (row) => this.options.rowActions({ ...this.context, row }),
diff --git a/dashboard/src2/components/Onboarding.vue b/dashboard/src2/components/Onboarding.vue
index 9bec67e..b26e601 100644
--- a/dashboard/src2/components/Onboarding.vue
+++ b/dashboard/src2/components/Onboarding.vue
@@ -3,11 +3,11 @@
欢迎来到 今果 Jingrow
- 今果 Jingrow让您能够轻松管理站点和应用程序,通过一个易于使用的仪表板,提供强大的功能,如自动备份、自定义域名、SSL 证书、自定义应用程序、自动更新等。
+ Jingrow是一站式通用数字化平台,以"一切皆页面"的革新理念,帮助您快速构建从个人工作到团队协作的一站式数字化解决方案。通过AI Agent智能体、可视化工作流编排、零代码可视化数据建模、自动化任务、团队协作、角色与权限管理、文件库、知识库、笔记,会议,待办事项,通知、智能报表等模块,引领数字化协作新趋势。
- 您距离创建第一个站点仅一步之遥。
+ 从这里开始,让一切数字化、系统化、智能化、自动化。
@@ -155,7 +155,7 @@
3
-
创建您的第一个站点
+
创建您的第一个数字化平台
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/SiteActionCell.vue b/dashboard/src2/components/SiteActionCell.vue
index 86a0c52..68a835f 100644
--- a/dashboard/src2/components/SiteActionCell.vue
+++ b/dashboard/src2/components/SiteActionCell.vue
@@ -45,7 +45,7 @@ function getSiteActionHandler(action) {
'使用文件恢复': defineAsyncComponent(() =>
import('./SiteDatabaseRestoreDialog.vue')
),
- '从现有站点恢复': defineAsyncComponent(() =>
+ '从指定站点恢复': defineAsyncComponent(() =>
import('./site/SiteDatabaseRestoreFromURLDialog.vue')
),
'管理数据库用户': defineAsyncComponent(() =>
@@ -101,7 +101,9 @@ function onDeactivateSite() {
variant: 'solid',
theme: 'red',
onClick({ hide }) {
- return site.deactivate.submit().then(hide);
+ toast.success('停用请求已提交');
+ hide();
+ site.deactivate.submit();
}
}
});
@@ -119,7 +121,9 @@ function onActivateSite() {
label: '激活',
variant: 'solid',
onClick({ hide }) {
- return site.activate.submit().then(hide);
+ toast.success('激活请求已提交');
+ hide();
+ site.activate.submit();
}
}
});
@@ -159,22 +163,23 @@ function onDropSite() {
import('./ChurnFeedbackDialog.vue')
);
- return site.archive.submit({ force: values.force }).then(() => {
- hide();
+ toast.success('删除请求已提交');
+ hide();
+ site.archive.submit({ force: values.force });
+ setTimeout(() => {
if (val) {
renderDialog(
h(FeedbackDialog, {
team: site.pg.team,
onUpdated() {
router.replace({ name: 'Site List' });
- toast.success('站点删除成功');
}
})
);
} else {
router.replace({ name: 'Site List' });
}
- });
+ }, 1000);
}
}
});
@@ -204,9 +209,9 @@ function onMigrateSite() {
if (values.confirmSiteName !== site.pg.name) {
throw new Error('站点名称不匹配');
}
- return site.migrate
- .submit({ skip_failing_patches: values.skipFailingPatches })
- .then(hide);
+ toast.success('更新请求已提交');
+ hide();
+ site.migrate.submit({ skip_failing_patches: values.skipFailingPatches });
}
}
});
@@ -232,7 +237,9 @@ function onSiteReset() {
if (values.confirmSiteName !== site.pg.name) {
throw new Error('站点名称不匹配。');
}
- return site.reinstall.submit().then(hide);
+ toast.success('重置请求已提交');
+ hide();
+ site.reinstall.submit();
}
}
});
@@ -244,23 +251,15 @@ function onScheduleBackup() {
message:
'您确定要计划备份吗?这将创建一个现场备份。',
onSuccess({ hide }) {
- toast.promise(
- site.backup.submit({
- with_files: true
- }),
- {
- loading: '正在计划备份...',
- success: () => {
- hide();
- router.push({
- name: 'Site Jobs',
- params: { name: site.name }
- });
- return '备份计划成功';
- },
- error: e => getToastErrorMessage(e)
- }
- );
+ toast.success('备份请求已提交');
+ hide();
+ site.backup.submit({ with_files: true });
+ setTimeout(() => {
+ router.push({
+ name: 'Site Jobs',
+ params: { name: site.name }
+ });
+ }, 1000);
}
});
}
@@ -283,14 +282,9 @@ function onTransferSite() {
label: '转移',
variant: 'solid',
onClick: ({ hide, values }) => {
- return site.sendTransferRequest
- .submit({ team_mail_id: values.email, reason: values.reason || '' })
- .then(() => {
- hide();
- toast.success(
- `转移请求已成功发送至 ${values.email}。`
- );
- });
+ toast.success(`转移请求已提交至 ${values.email}`);
+ hide();
+ site.sendTransferRequest.submit({ team_mail_id: values.email, reason: values.reason || '' });
}
}
});
@@ -304,7 +298,9 @@ function onClearCache() {
label: '清除缓存',
variant: 'solid',
onClick: ({ hide }) => {
- return site.clearSiteCache.submit().then(hide);
+ toast.success('清除缓存请求已提交');
+ hide();
+ site.clearSiteCache.submit();
}
}
});
diff --git a/dashboard/src2/components/SiteRenewalDialog.vue b/dashboard/src2/components/SiteRenewalDialog.vue
index e5663f3..480e8e0 100644
--- a/dashboard/src2/components/SiteRenewalDialog.vue
+++ b/dashboard/src2/components/SiteRenewalDialog.vue
@@ -319,9 +319,6 @@ export default {
return;
}
- console.log('订单创建成功,完整响应:', data);
- console.log('订单ID:', data.order?.order_id);
-
// 显示订单支付界面
this.order = data.order;
this.showPaymentProcessing = true;
@@ -340,7 +337,6 @@ export default {
url: 'jcloud.api.billing.process_balance_payment_for_renew_order',
params: {},
validate() {
- console.log('验证订单信息:', this.order);
if (!this.order || !this.order.order_id) {
throw new DashboardError('缺少订单信息');
}
@@ -366,9 +362,6 @@ export default {
// 更新UI状态,显示成功信息(不再显示toast)
this.isProcessingPayment = false;
this.paymentSuccess = true;
- console.log('余额支付成功,更新状态:', {
- paymentSuccess: this.paymentSuccess
- });
},
onError(error) {
this.error = error.message || '余额支付处理失败';
@@ -431,7 +424,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
},
@@ -488,11 +481,6 @@ export default {
return;
}
- console.log('准备处理支付,订单信息:', {
- id: this.order.order_id,
- method: this.selectedPaymentMethod
- });
-
if (this.selectedPaymentMethod === 'balance') {
// 处理余额支付
this.$resources.processBalancePayment.submit({
diff --git a/dashboard/src2/components/marketplace/NewMarketplaceAppDialog.vue b/dashboard/src2/components/marketplace/NewMarketplaceAppDialog.vue
index 6ee8c36..5d0bd99 100644
--- a/dashboard/src2/components/marketplace/NewMarketplaceAppDialog.vue
+++ b/dashboard/src2/components/marketplace/NewMarketplaceAppDialog.vue
@@ -168,7 +168,7 @@ this.selectedBranch = {
async checkRepoVisibility(owner, repo) {
try {
const response = await fetch(
- `http://git.jingrow.com:3000/api/v1/repos/${owner}/${repo}`
+ `http://git.jingrow.com/api/v1/repos/${owner}/${repo}`
);
if (!response.ok) {
throw new Error('仓库未找到或为私有');
diff --git a/dashboard/src2/components/settings/InviteTeamMemberDialog.vue b/dashboard/src2/components/settings/InviteTeamMemberDialog.vue
index 5e6f3a0..33250c1 100644
--- a/dashboard/src2/components/settings/InviteTeamMemberDialog.vue
+++ b/dashboard/src2/components/settings/InviteTeamMemberDialog.vue
@@ -14,7 +14,7 @@
>
-
+
import { toast } from 'vue-sonner';
-import { DashboardError } from '../../utils/error';
-import { getToastErrorMessage } from '../../utils/toast';
export default {
data() {
return {
- email: '',
+ username: '',
show: true,
selectedRoles: [],
selectedRole: null,
@@ -110,29 +108,25 @@ export default {
);
},
inviteMember() {
- toast.promise(
- this.$team.inviteTeamMember.submit(
- {
- email: this.email,
- roles: this.selectedRoles.map((role) => role.value),
- },
- {
- validate: () => {
- if (!this.email) {
- throw new DashboardError('邮箱为必填项');
- }
- },
- },
- ),
- {
- loading: '正在发送邀请...',
- success: () => {
- this.show = false;
- return '邀请已发送!';
- },
- error: (e) => getToastErrorMessage(e),
- },
- );
+ if (!this.username) {
+ toast.error('用户名为必填项');
+ return;
+ }
+ if (this.$team.inviteTeamMember.loading) return;
+
+ toast.success('已添加成员到团队', { duration: 2000 });
+ this.show = false;
+ this.$team.inviteTeamMember.submit({
+ username: this.username,
+ roles: this.selectedRoles.map((role) => role.value),
+ });
+ this.username = '';
+ this.selectedRoles = [];
+ setTimeout(() => {
+ if (this.$team?.getTeamMembers) {
+ this.$team.getTeamMembers.submit();
+ }
+ }, 500);
},
},
};
diff --git a/dashboard/src2/components/settings/RoleConfigureDialog.vue b/dashboard/src2/components/settings/RoleConfigureDialog.vue
index 334a960..2944477 100644
--- a/dashboard/src2/components/settings/RoleConfigureDialog.vue
+++ b/dashboard/src2/components/settings/RoleConfigureDialog.vue
@@ -133,7 +133,6 @@
import { Switch, Tabs, TabList, TabPanel } from 'jingrow-ui';
import { toast } from 'vue-sonner';
import UserWithAvatarCell from '../UserWithAvatarCell.vue';
-import { getToastErrorMessage } from '../../utils/toast';
export default {
props: {
@@ -288,21 +287,31 @@ export default {
},
methods: {
addUser(user) {
- return toast.promise(this.$resources.role.addUser.submit({ user }), {
- loading: `正在将 ${user} 添加到 ${this.role.title}`,
- success: () => {
- this.member = {};
- return `${user} 已添加到 ${this.role.title}`;
- },
- error: (e) => getToastErrorMessage(e),
- });
+ if (!user) return;
+ if (this.$resources.role.addUser.loading) return;
+
+ toast.success(`已添加 ${user} 到 ${this.role.title}`, { duration: 2000 });
+ this.member = {};
+ this.$resources.role.addUser.submit({ user });
+ // 刷新角色数据
+ setTimeout(() => {
+ if (this.$resources.role) {
+ this.$resources.role.reload();
+ }
+ }, 500);
},
removeUser(user) {
- return toast.promise(this.$resources.role.removeUser.submit({ user }), {
- loading: `正在将 ${user} 从 ${this.role.title} 中移除`,
- success: () => `${user} 已从 ${this.role.title} 中移除`,
- error: (e) => getToastErrorMessage(e),
- });
+ if (!user) return;
+ if (this.$resources.role.removeUser.loading) return;
+
+ toast.success(`已从 ${this.role.title} 中移除 ${user}`, { duration: 2000 });
+ this.$resources.role.removeUser.submit({ user });
+ // 刷新角色数据
+ setTimeout(() => {
+ if (this.$resources.role) {
+ this.$resources.role.reload();
+ }
+ }, 500);
},
},
};
diff --git a/dashboard/src2/components/settings/TeamSettings.vue b/dashboard/src2/components/settings/TeamSettings.vue
index 0ef1d69..29b2de4 100644
--- a/dashboard/src2/components/settings/TeamSettings.vue
+++ b/dashboard/src2/components/settings/TeamSettings.vue
@@ -11,7 +11,6 @@ import { getTeam } from '../../data/team';
import { confirmDialog, renderDialog } from '../../utils/components';
import ObjectList from '../ObjectList.vue';
import UserWithAvatarCell from '../UserWithAvatarCell.vue';
-import { getToastErrorMessage } from '../../utils/toast';
const team = getTeam();
team.getTeamMembers.submit();
@@ -47,19 +46,12 @@ const teamMembersListOptions = ref({
title: '移除成员',
message: `确定要将
${row.full_name} 从团队中移除吗?`,
onSuccess({ hide }) {
- if (team.removeTeamMember.loading) return;
- toast.promise(
- team.removeTeamMember.submit({ member: row.name }),
- {
- loading: '正在移除成员...',
- success: () => {
- team.getTeamMembers.submit();
- hide();
- return '成员已移除';
- },
- error: e => getToastErrorMessage(e)
- }
- );
+ toast.success('成员已被删除', { duration: 2000 });
+ hide();
+ team.removeTeamMember.submit({ member: row.name });
+ setTimeout(() => {
+ team.getTeamMembers.submit();
+ }, 500);
}
});
}
diff --git a/dashboard/src2/components/settings/profile/AccountProfile.vue b/dashboard/src2/components/settings/profile/AccountProfile.vue
index d1759c9..5b40115 100644
--- a/dashboard/src2/components/settings/profile/AccountProfile.vue
+++ b/dashboard/src2/components/settings/profile/AccountProfile.vue
@@ -66,6 +66,16 @@
+
+
+
+ 重置密码
+
+
+
+
@@ -198,6 +212,7 @@ import { defineAsyncComponent, h } from 'vue';
import FileUploader from '@/components/FileUploader.vue';
import { confirmDialog, renderDialog } from '../../../utils/components';
import TFADialog from './TFADialog.vue';
+import ResetPasswordDialog from './ResetPasswordDialog.vue';
import router from '../../../router';
import AddPrepaidCreditsDialog from '../../billing/AddPrepaidCreditsDialog.vue';
@@ -205,12 +220,14 @@ export default {
name: 'AccountProfile',
components: {
TFADialog,
+ ResetPasswordDialog,
FileUploader,
AddPrepaidCreditsDialog,
},
data() {
return {
show2FADialog: false,
+ showResetPasswordDialog: false,
disableAccount2FACode: '',
showProfileEditDialog: false,
showEnableAccountDialog: false,
diff --git a/dashboard/src2/components/settings/profile/ResetPasswordDialog.vue b/dashboard/src2/components/settings/profile/ResetPasswordDialog.vue
new file mode 100644
index 0000000..c95d700
--- /dev/null
+++ b/dashboard/src2/components/settings/profile/ResetPasswordDialog.vue
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+
+ {{ passwordStrengthMessage }}
+
+
+
+ {{ passwordMismatchMessage }}
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确认
+
+
+
+
+
+
+
diff --git a/dashboard/src2/components/site/InstallAppDialog.vue b/dashboard/src2/components/site/InstallAppDialog.vue
index 4a1bf8d..f2fc902 100644
--- a/dashboard/src2/components/site/InstallAppDialog.vue
+++ b/dashboard/src2/components/site/InstallAppDialog.vue
@@ -94,10 +94,10 @@ export default {
// 如果应用不可安装,显示对应提示
if (!checkResult.installable) {
const subscriptionTypeMap = {
- 2: 'Pro',
- 3: 'Business',
- 4: 'Enterprise',
- 5: 'Ultimate'
+ 2: '299元/月',
+ 3: '399元/月',
+ 4: '499元/月',
+ 5: '599元/月'
};
const requiredPlan = subscriptionTypeMap[checkResult.required_plan_level] ||
diff --git a/dashboard/src2/components/site/SiteDatabaseRestoreFromURLDialog.vue b/dashboard/src2/components/site/SiteDatabaseRestoreFromURLDialog.vue
index ac7948b..2a74e40 100644
--- a/dashboard/src2/components/site/SiteDatabaseRestoreFromURLDialog.vue
+++ b/dashboard/src2/components/site/SiteDatabaseRestoreFromURLDialog.vue
@@ -1,156 +1,188 @@
-
-
-
-
-
-
- 此操作将用备份中的数据 和应用 替换您站点中的当前内容
-
-
-
-
-
-
-
-
- 找到来自 {{ fetchedBackupFileTimestamp }} 的最新备份
-
-
- 获取备份
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/dashboard/src2/dialogs/PasswordDialog.vue b/dashboard/src2/dialogs/PasswordDialog.vue
new file mode 100644
index 0000000..2de3b00
--- /dev/null
+++ b/dashboard/src2/dialogs/PasswordDialog.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
{{ description }}
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确定
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/objects/common/jobs.ts b/dashboard/src2/objects/common/jobs.ts
index f7860b8..46025f4 100644
--- a/dashboard/src2/objects/common/jobs.ts
+++ b/dashboard/src2/objects/common/jobs.ts
@@ -5,7 +5,7 @@ import { isMobile } from '../../utils/device';
import { duration } from '../../utils/format';
import { ColumnField, Tab } from './types';
-type JobDocTypes = 'Site' | 'Bench' | 'Server' | 'Release Group';
+type JobPageTypes = 'Site' | 'Bench' | 'Server' | 'Release Group';
// 英文 job_type 到中文的映射
const jobTypeI18nMap: Record
= {
@@ -35,7 +35,7 @@ function statusI18n(status: string) {
return statusI18nMap[status] || status;
}
-export function getJobsTab(pagetype: JobDocTypes) {
+export function getJobsTab(pagetype: JobPageTypes) {
const jobRoute = getJobRoute(pagetype);
return {
@@ -95,7 +95,7 @@ export function getJobsTab(pagetype: JobDocTypes) {
} satisfies Tab as Tab;
}
-function getJobRoute(pagetype: JobDocTypes) {
+function getJobRoute(pagetype: JobPageTypes) {
if (pagetype === 'Site') return 'Site Job';
else if (pagetype === 'Bench') return 'Bench Job';
else if (pagetype === 'Server') return 'Server Job';
@@ -103,7 +103,7 @@ function getJobRoute(pagetype: JobDocTypes) {
throw unreachable;
}
-function getJobTabColumns(pagetype: JobDocTypes) {
+function getJobTabColumns(pagetype: JobPageTypes) {
const columns: ColumnField[] = [
{
label: '任务类型',
diff --git a/dashboard/src2/objects/domain.js b/dashboard/src2/objects/domain.js
new file mode 100644
index 0000000..0f669fa
--- /dev/null
+++ b/dashboard/src2/objects/domain.js
@@ -0,0 +1,238 @@
+import { defineAsyncComponent, h } from 'vue';
+import LucideGlobe from '~icons/lucide/globe';
+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 Domain',
+ whitelistedMethods: {
+ renew: 'renew',
+ rename: 'rename',
+ dropDomain: 'drop_domain',
+ addTag: 'add_resource_tag',
+ removeTag: 'remove_resource_tag'
+ },
+ list: {
+ route: '/domains',
+ title: '域名',
+ fields: [
+ 'name',
+ 'domain',
+ 'status',
+ 'domain_owner',
+ 'domain_registrar',
+ 'registration_date',
+ 'end_date',
+ 'price',
+ 'period',
+ 'auto_renew',
+ 'team',
+ 'order_id',
+ 'description'
+ ],
+ filterControls() {
+ return [
+ {
+ type: 'select',
+ label: '状态',
+ fieldname: 'status',
+ options: [
+ { label: '', value: '' },
+ { label: '正常', value: 'ok' },
+ { label: '锁定', value: 'clienthold' }
+ ]
+ }
+ ];
+ },
+ orderBy: 'creation desc',
+ searchField: 'domain',
+ columns: [
+ {
+ label: '域名',
+ fieldname: 'domain',
+ width: 2,
+ class: 'font-medium',
+ format(value) {
+ return value;
+ }
+ },
+ {
+ label: '状态',
+ fieldname: 'status',
+ type: 'Badge',
+ width: 0.8,
+ format(value) {
+ const statusMap = {
+ 'ok': '正常',
+ 'clienthold': '锁定'
+ };
+ return statusMap[value] || value;
+ }
+ },
+ {
+ label: '注册时间',
+ fieldname: 'registration_date',
+ format(value) {
+ if (!value) return '-';
+ return value;
+ }
+ },
+ {
+ label: '到期时间',
+ fieldname: 'end_date',
+ format(value) {
+ if (!value) return '-';
+ return value;
+ }
+ }
+ ],
+ primaryAction({ listResource: domains }) {
+ return {
+ label: '新建域名',
+ variant: 'solid',
+ slots: {
+ prefix: icon('plus')
+ },
+ onClick() {
+ router.push('/domains/new');
+ }
+ };
+ },
+ statusBadge({ documentResource: domain }) {
+ const status = domain.pg?.status;
+ const statusMap = {
+ 'Pending': '待处理',
+ 'Active': '正常',
+ 'Expired': '已过期',
+ 'Suspended': '已暂停',
+ 'Cancelled': '已取消'
+ };
+ return {
+ label: statusMap[status] || status
+ };
+ },
+ breadcrumbs({ documentResource: domain }) {
+ return [
+ {
+ label: '域名',
+ route: '/domains'
+ },
+ {
+ label: domain.pg?.domain || domain.pg?.name,
+ route: `/domains/${domain.pg?.name}`
+ }
+ ];
+ },
+ actions({ documentResource: domain }) {
+ if (!domain) return [];
+
+ const actions = [
+ {
+ label: '续费',
+ icon: 'refresh-cw',
+ onClick() {
+ domain.renew.submit();
+ },
+ condition: () => domain.pg?.status === 'Active'
+ },
+ {
+ label: '重命名',
+ icon: 'edit-3',
+ onClick() {
+ domain.rename.submit();
+ }
+ },
+ {
+ label: '删除',
+ icon: 'trash-2',
+ onClick() {
+ domain.dropDomain.submit();
+ },
+ condition: () => domain.pg?.status !== 'Active'
+ }
+ ];
+ return actions.filter(action => !action.condition || action.condition());
+ }
+ },
+ detail: {
+ route: '/domains/:name',
+ title: '域名详细信息',
+ tabs: [
+ {
+ label: '概览',
+ route: '',
+ type: 'Component',
+ component: defineAsyncComponent(() => import('../components/JsiteDomainOverview.vue')),
+ props: domain => {
+ return { domain: domain.pg?.name };
+ }
+ },
+ {
+ label: '域名解析',
+ route: 'dns',
+ type: 'Component',
+ component: defineAsyncComponent(() => import('../components/JsiteDomainDNSRecords.vue')),
+ props: domain => {
+ return { domain: domain.pg?.name };
+ }
+ },
+ {
+ label: '所有者模板',
+ route: 'owners',
+ type: 'Component',
+ component: defineAsyncComponent(() => import('../components/DomainOwner.vue')),
+ props: domain => {
+ return { domain: domain.pg?.name };
+ }
+ }
+ ],
+ fields: [
+ {
+ label: '基本信息',
+ fields: [
+ 'domain',
+ 'status',
+ 'domain_owner',
+ 'domain_registrar',
+ 'registration_date',
+ 'end_date'
+ ]
+ },
+ {
+ label: '价格信息',
+ fields: [
+ 'price',
+ 'period',
+ 'auto_renew',
+ 'order_id'
+ ]
+ },
+ {
+ label: 'DNS设置',
+ fields: [
+ 'dns_host1',
+ 'dns_host2',
+ 'dns_host3',
+ 'dns_host4',
+ 'dns_host5',
+ 'dns_host6'
+ ]
+ },
+ {
+ label: '其他信息',
+ fields: [
+ 'description',
+ 'whois_protection'
+ ]
+ }
+ ],
+ actions({ documentResource: domain }) {
+ return [];
+ }
+ }
+};
\ No newline at end of file
diff --git a/dashboard/src2/objects/index.js b/dashboard/src2/objects/index.js
index 06e60c6..589ec91 100644
--- a/dashboard/src2/objects/index.js
+++ b/dashboard/src2/objects/index.js
@@ -3,7 +3,9 @@ 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';
+import domain from './domain';
let objects = {
Site: site,
@@ -11,6 +13,8 @@ let objects = {
Bench: bench,
Marketplace: marketplace,
Server: server,
+ 'Jsite Server': jsite_server,
+ 'Jsite Domain': domain,
Notification: notification
};
diff --git a/dashboard/src2/objects/jsite_server.js b/dashboard/src2/objects/jsite_server.js
new file mode 100644
index 0000000..395e411
--- /dev/null
+++ b/dashboard/src2/objects/jsite_server.js
@@ -0,0 +1,333 @@
+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: '服务器',
+ fields: [
+ 'name',
+ 'title',
+ 'status',
+ 'region',
+ 'cpu',
+ 'memory',
+ 'disk_size',
+ 'public_ip',
+ 'private_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: '华北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' }
+ ]
+ }
+ ];
+ },
+ 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,
+ format(value) {
+ const statusMap = {
+ 'Pending': '准备中',
+ 'Starting': '启动中',
+ 'Running': '运行中',
+ 'Stopping': '停止中',
+ 'Stopped': '已停止',
+ 'Resetting': '重置中',
+ 'Upgrading': '升级中',
+ 'Disabled': '已禁用'
+ };
+ return statusMap[value] || value;
+ }
+ },
+ {
+ 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) {
+ 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;
+ }
+ },
+ {
+ label: '到期时间',
+ fieldname: 'end_date',
+ format(value) {
+ if (!value) return '-';
+ return value;
+ }
+ }
+ ],
+ primaryAction({ listResource: jsiteServers }) {
+ return {
+ label: '新建服务器',
+ variant: 'solid',
+ slots: {
+ prefix: icon('plus')
+ },
+ onClick() {
+ router.push('/jsite-servers/new');
+ }
+ };
+ },
+ statusBadge({ documentResource: jsiteServer }) {
+ const status = jsiteServer.pg?.status;
+ const statusMap = {
+ 'Pending': '准备中',
+ 'Starting': '启动中',
+ 'Running': '运行中',
+ 'Stopping': '停止中',
+ 'Stopped': '已停止',
+ 'Resetting': '重置中',
+ 'Upgrading': '升级中',
+ 'Disabled': '已禁用'
+ };
+ return {
+ label: statusMap[status] || status
+ };
+ },
+ breadcrumbs({ documentResource: jsiteServer }) {
+ return [
+ {
+ label: '服务器',
+ 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: '服务器详细信息',
+ tabs: [
+ {
+ label: '概览',
+ route: '',
+ type: 'Component',
+ component: defineAsyncComponent(() => import('../components/JsiteServerOverview.vue')),
+ props: jsiteServer => {
+ return { server: jsiteServer.pg?.name };
+ }
+ },
+ {
+ label: '防火墙',
+ route: 'firewall',
+ type: 'Component',
+ component: defineAsyncComponent(() => import('../components/JsiteServerFirewallRules.vue')),
+ props: jsiteServer => {
+ return { server: jsiteServer.pg?.name };
+ }
+ }
+ ],
+ 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 }) {
+ return [];
+ }
+ }
+};
\ No newline at end of file
diff --git a/dashboard/src2/objects/server.js b/dashboard/src2/objects/server.js
index 17c36d2..2d08383 100644
--- a/dashboard/src2/objects/server.js
+++ b/dashboard/src2/objects/server.js
@@ -241,7 +241,7 @@ export default {
orderBy: 'creation desc',
searchField: 'host_name',
route(row) {
- return { name: '站点详情', params: { name: row.name } };
+ return { name: 'Site Detail', params: { name: row.name } };
},
filterControls() {
return [
diff --git a/dashboard/src2/pages/BillingOrders.vue b/dashboard/src2/pages/BillingOrders.vue
index c73b45d..344008f 100644
--- a/dashboard/src2/pages/BillingOrders.vue
+++ b/dashboard/src2/pages/BillingOrders.vue
@@ -142,7 +142,6 @@ export default {
const ordersResource = createResource({
url: 'jcloud.api.billing.get_orders',
transform(response) {
- console.log('API响应:', response); // 调试日志
return {
orders: response.orders || [],
total: response.total || 0
@@ -163,15 +162,8 @@ export default {
// 更新加载状态
initialLoading.value = false;
loadingMore.value = false;
-
- console.log('获取到订单数据:', {
- orders: orders.value,
- total: totalCount.value,
- hasMore: hasMoreToLoad.value
- });
},
onError(error) {
- console.error('获取订单数据失败:', error);
initialLoading.value = false;
loadingMore.value = false;
}
@@ -229,7 +221,7 @@ export default {
URL.revokeObjectURL(link.href);
},
onError(error) {
- console.error('导出数据失败:', error);
+ // 导出失败处理
}
});
diff --git a/dashboard/src2/pages/DetailPage.vue b/dashboard/src2/pages/DetailPage.vue
index e846cbe..901a356 100644
--- a/dashboard/src2/pages/DetailPage.vue
+++ b/dashboard/src2/pages/DetailPage.vue
@@ -164,7 +164,7 @@ documentResource: this.$resources.document
{
label: this.title,
route: {
- name: `${this.object.pagetype} 详情`,
+ name: `${this.object.pagetype} Detail`,
params: { name: this.name }
}
}
diff --git a/dashboard/src2/pages/LoginSignup.vue b/dashboard/src2/pages/LoginSignup.vue
index 896aecf..0a6e52a 100644
--- a/dashboard/src2/pages/LoginSignup.vue
+++ b/dashboard/src2/pages/LoginSignup.vue
@@ -202,11 +202,16 @@
+
+ 请输入正确的手机号码格式
+
注册
@@ -435,6 +440,7 @@ export default {
passwordStrengthClass: '',
passwordStrengthText: '',
passwordStrengthTextClass: '',
+ phoneNumberFormatError: false,
};
},
mounted() {
@@ -602,36 +608,39 @@ export default {
},
};
},
- signupWithUsername() {
- return {
- url: 'jcloud.api.account.signup_with_username',
- params: {
- username: this.username,
- email: this.email || null,
- phone_number: this.phoneNumber || null,
- password: this.signupPassword,
- referrer: this.getReferrerIfAny(),
- product: this.$route.query.product,
- },
- onSuccess(res) {
- // 保存用户信息
- localStorage.setItem('login_email', this.username || this.email);
-
- if (res && res.dashboard_route) {
- // 使用后端提供的路由
- window.location.href = res.dashboard_route;
- } else {
- // 简化URL,仅确保前缀正确
- window.location.href = '/dashboard/welcome';
- }
- },
- onError(err) {
- toast.error(
- getToastErrorMessage(err, '注册失败,请检查您的信息'),
- );
- },
- };
- },
+ signupWithUsername() {
+ return {
+ url: 'jcloud.api.account.signup_with_username',
+ params: {
+ username: this.username,
+ email: this.email || null,
+ phone_number: this.phoneNumber || null,
+ password: this.signupPassword,
+ referrer: this.getReferrerIfAny(),
+ product: this.$route.query.product,
+ },
+ onSuccess(res) {
+ if (res && res.success === false) {
+ toast.error(res.message || '注册失败,请检查您的信息');
+ return;
+ }
+
+ localStorage.setItem('login_email', this.username || this.email);
+
+ if (res && res.dashboard_route) {
+ window.location.href = res.dashboard_route;
+ } else {
+ window.location.href = '/dashboard/welcome';
+ }
+ },
+ onError(err) {
+ const errorMessage = err?.messages?.length
+ ? err.messages.join('\n')
+ : (err?.message || '注册失败,请检查您的信息');
+ toast.error(errorMessage);
+ },
+ };
+ },
},
methods: {
resetSignupState() {
@@ -648,14 +657,10 @@ export default {
provider: this.socialLoginKey,
});
} else if (this.useEmail && this.otpSent) {
- // 如果是邮箱验证码登录且已发送验证码,则通过verifyOTPAndLogin处理
- // 这部分由专门的按钮处理,不在表单提交中处理
return;
} else if (!this.useEmail && this.email && this.password) {
- // 密码登录
this.checkTwoFactorAndLogin();
} else if (this.useEmail && !this.otpSent) {
- // 请求发送验证码
this.$resources.sendOTP.submit();
}
} else if (this.hasForgotPassword) {
@@ -665,6 +670,14 @@ export default {
toast.error('用户名不能为空');
return;
}
+ if (!this.phoneNumber) {
+ toast.error('手机号不能为空');
+ return;
+ }
+ if (!this.isPhoneNumberValid) {
+ toast.error('请输入正确的手机号码格式');
+ return;
+ }
if (!this.signupPassword) {
toast.error('密码不能为空');
return;
@@ -805,6 +818,14 @@ export default {
}
});
},
+ validatePhoneNumber() {
+ // 失焦时验证手机号格式
+ if (this.phoneNumber && this.phoneNumber.length > 0) {
+ this.phoneNumberFormatError = !this.isPhoneNumberValid;
+ } else {
+ this.phoneNumberFormatError = false;
+ }
+ },
},
computed: {
error() {
@@ -928,6 +949,11 @@ export default {
/[A-Z]/.test(this.signupPassword) &&
/[0-9]/.test(this.signupPassword);
},
+ isPhoneNumberValid() {
+ // 中国大陆手机号验证:11位数字,以1开头,第二位为3-9
+ const phoneRegex = /^1[3-9]\d{9}$/;
+ return phoneRegex.test(this.phoneNumber);
+ },
},
};
\ No newline at end of file
diff --git a/dashboard/src2/pages/NewJsiteDomain.vue b/dashboard/src2/pages/NewJsiteDomain.vue
new file mode 100644
index 0000000..a0aba01
--- /dev/null
+++ b/dashboard/src2/pages/NewJsiteDomain.vue
@@ -0,0 +1,1345 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedSuffix }}
+
+
+
+
+
+
+
+ {{ isChecking ? '查询中...' : '查询域名' }}
+
+
+
+
+
+
+
+
选择域名后缀
+
+
+
+
+
+ 默认排序
+ 热门优先
+ 字母排序
+
+
+
+
+
+
+
+ {{ category.label }}
+
+
+
+
+
+
+
+
+
+ {{ suffix.value }}
+
+ HOT
+
+
+
+
+
+
+
+
+
+
+
+
{{ fullDomain }} 可以注册
+
+ ¥{{ domainPrice }}/年
+
+
+
+
+
{{ fullDomain }} 已被注册
+
+
+
+
+
+
+
域名所有者 *
+
+
+ 请选择域名所有者
+
+ {{ getOwnerDisplayName(owner) }}
+
+
+
+
+
+
+ 新建域名所有者
+
+
+
+
+
购买时长
+
+ {{ p.label }}
+
+
+
+
+
+
+
购买时长
+
{{ period }} 年
+
+
+
总计
+
¥ {{ totalPrice.toFixed(2) }}
+
+
+
+
+
+
+ 选择支付方式
+
+
+
+ 余额支付
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+ {{ isLoading ? '处理中...' : '注册域名' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支付成功!
+
+ 您的订单已支付成功,域名正在注册中,注册需要1分钟左右,请耐心等待。
+
+
+ 您可以在域名列表中查看域名状态。
+
+
+
+ 返回域名列表
+
+
+
+
+
+
+
+
+
域名注册 - {{ fullDomain }}
+
+
+
扫一扫付款(元)
+
{{ order?.total_amount }} 元
+
+
+
+
+
+
+
+
+
+
+ 请使用微信扫描二维码完成支付
+
+
二维码有效期 15 分钟
+
+
+
+
+
+
+
+
请在新页面完成支付宝支付
+
+ 如果没有自动跳转,请点击下方按钮打开支付页面
+
+
+
+ 打开支付页面
+
+
+ 支付完成后,请稍等片刻,系统会自动刷新页面
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src2/pages/NewJsiteServer.vue
new file mode 100644
index 0000000..8f7ff43
--- /dev/null
+++ b/dashboard/src2/pages/NewJsiteServer.vue
@@ -0,0 +1,782 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
新建服务器
+
+ 请选择服务器配置和支付方式,点击"创建"后将自动为您开通。
+
+
+
+
+
+ 区域选择
+
+ 请选择区域
+
+ {{ getRegionName(region) }}
+
+
+
+
+
+ 镜像选择(Jsite服务器推荐选择Ubuntu-22.04)
+
+ 请选择镜像
+
+ {{ getImageDisplayName(image) }}
+
+
+
+
+
+ 套餐选择
+
+ 请选择套餐
+
+ {{ getPlanDisplayName(plan) }}
+
+
+
+
+
+ 购买时长
+
+ {{ p.label }}
+
+
+
+
+
+
+
月度费用
+
+ ¥ {{ getSelectedPlanPrice() }}
+ (月付)
+
+
+
+
购买时长
+
{{ period }} 个月
+
+
+
总计
+
¥ {{ getTotalAmount() }}
+
+
+
+
+
+
+ 选择支付方式
+
+
+
+ 余额支付
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+ {{ isLoading ? '处理中...' : '创建' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
支付成功!
+
+ 您的订单已支付成功,服务器记录已创建。服务器正在后台创建中,创建并启动需要 3-5 分钟,请耐心等待。
+
+
+ 您可以在服务器列表中查看服务器状态,创建完成后状态将更新为"运行中"。
+
+
+
+ 返回服务器列表
+
+
+
+
+
+
+
+
+
+
扫一扫付款(元)
+
{{ order?.total_amount }} 元
+
+
+
+
+
+
+
+
+
+
+ 请使用微信扫描二维码完成支付
+
+
二维码有效期 15 分钟
+
+
+
+
+
+
+
+
请在新页面完成支付宝支付
+
+ 如果没有自动跳转,请点击下方按钮打开支付页面
+
+
+
+ 打开支付页面
+
+
+ 支付完成后,请稍等片刻,系统会自动刷新页面
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src2/router.js b/dashboard/src2/router.js
index f1f0053..03fce2d 100644
--- a/dashboard/src2/router.js
+++ b/dashboard/src2/router.js
@@ -110,6 +110,48 @@ let router = createRouter({
path: '/servers/new',
component: () => import('./pages/NewServer.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: 'Jsite Domain List',
+ path: '/domains',
+ component: () => import('./pages/ListPage.vue'),
+ props: route => {
+ return { objectType: 'Jsite Domain', ...route.params };
+ }
+ },
+ {
+ name: 'New Jsite Domain',
+ path: '/domains/new',
+ component: () => import('./pages/NewJsiteDomain.vue'),
+ },
+ {
+ name: 'Jsite Domain Detail',
+ path: '/domains/:name',
+ component: () => import('./pages/DetailPage.vue'),
+ props: route => {
+ return { objectType: 'Jsite Domain', ...route.params };
+ }
+ },
{
name: 'Billing',
path: '/billing',
diff --git a/dashboard/src2/utils/regions.ts b/dashboard/src2/utils/regions.ts
new file mode 100644
index 0000000..693533c
--- /dev/null
+++ b/dashboard/src2/utils/regions.ts
@@ -0,0 +1,6199 @@
+// 完整的地区数据工具文件
+// 包含国家、省份、城市、区县的完整数据结构
+
+// 国家数据(复用country.ts中的数据)
+const countries = {
+ AD: '安道尔',
+ AE: '阿拉伯联合酋长国',
+ AF: '阿富汗',
+ AG: '安提瓜和巴布达',
+ AI: '安圭拉',
+ AL: '阿尔巴尼亚',
+ AM: '亚美尼亚',
+ AO: '安哥拉',
+ AQ: '南极洲',
+ AR: '阿根廷',
+ AS: '美属萨摩亚',
+ AT: '奥地利',
+ AU: '澳大利亚',
+ AW: '阿鲁巴',
+ AX: '奥兰群岛',
+ AZ: '阿塞拜疆',
+ BA: '波斯尼亚和黑塞哥维那',
+ BB: '巴巴多斯',
+ BD: '孟加拉国',
+ BE: '比利时',
+ BF: '布基纳法索',
+ BG: '保加利亚',
+ BH: '巴林',
+ BI: '布隆迪',
+ BJ: '贝宁',
+ BL: '圣巴泰勒米',
+ BM: '百慕大',
+ BN: '文莱',
+ BO: '玻利维亚',
+ BQ: '荷兰加勒比区',
+ BR: '巴西',
+ BS: '巴哈马',
+ BT: '不丹',
+ BV: '布韦岛',
+ BW: '博茨瓦纳',
+ BY: '白俄罗斯',
+ BZ: '伯利兹',
+ CA: '加拿大',
+ CC: '科科斯群岛',
+ CD: '刚果民主共和国',
+ CF: '中非共和国',
+ CG: '刚果共和国',
+ CH: '瑞士',
+ CI: '科特迪瓦',
+ CK: '库克群岛',
+ CL: '智利',
+ CM: '喀麦隆',
+ CN: '中国',
+ CO: '哥伦比亚',
+ CR: '哥斯达黎加',
+ CU: '古巴',
+ CV: '佛得角',
+ CW: '库拉索',
+ CX: '圣诞岛',
+ CY: '塞浦路斯',
+ CZ: '捷克',
+ DE: '德国',
+ DJ: '吉布提',
+ DK: '丹麦',
+ DM: '多米尼克',
+ DO: '多米尼加共和国',
+ DZ: '阿尔及利亚',
+ EC: '厄瓜多尔',
+ EE: '爱沙尼亚',
+ EG: '埃及',
+ EH: '西撒哈拉',
+ ER: '厄立特里亚',
+ ES: '西班牙',
+ ET: '埃塞俄比亚',
+ FI: '芬兰',
+ FJ: '斐济',
+ FK: '福克兰群岛',
+ FM: '密克罗尼西亚',
+ FO: '法罗群岛',
+ FR: '法国',
+ GA: '加蓬',
+ GB: '英国',
+ GD: '格林纳达',
+ GE: '格鲁吉亚',
+ GF: '法属圭亚那',
+ GG: '根西岛',
+ GH: '加纳',
+ GI: '直布罗陀',
+ GL: '格陵兰',
+ GM: '冈比亚',
+ GN: '几内亚',
+ GP: '瓜德罗普',
+ GQ: '赤道几内亚',
+ GR: '希腊',
+ GS: '南乔治亚和南桑威奇群岛',
+ GT: '危地马拉',
+ GU: '关岛',
+ GW: '几内亚比绍',
+ GY: '圭亚那',
+ HK: '香港',
+ HM: '赫德岛和麦克唐纳群岛',
+ HN: '洪都拉斯',
+ HR: '克罗地亚',
+ HT: '海地',
+ HU: '匈牙利',
+ ID: '印度尼西亚',
+ IE: '爱尔兰',
+ IL: '以色列',
+ IM: '马恩岛',
+ IN: '印度',
+ IO: '英属印度洋领地',
+ IQ: '伊拉克',
+ IR: '伊朗',
+ IS: '冰岛',
+ IT: '意大利',
+ JE: '泽西岛',
+ JM: '牙买加',
+ JO: '约旦',
+ JP: '日本',
+ KE: '肯尼亚',
+ KG: '吉尔吉斯斯坦',
+ KH: '柬埔寨',
+ KI: '基里巴斯',
+ KM: '科摩罗',
+ KN: '圣基茨和尼维斯',
+ KP: '朝鲜',
+ KR: '韩国',
+ KW: '科威特',
+ KY: '开曼群岛',
+ KZ: '哈萨克斯坦',
+ LA: '老挝',
+ LB: '黎巴嫩',
+ LC: '圣卢西亚',
+ LI: '列支敦士登',
+ LK: '斯里兰卡',
+ LR: '利比里亚',
+ LS: '莱索托',
+ LT: '立陶宛',
+ LU: '卢森堡',
+ LV: '拉脱维亚',
+ LY: '利比亚',
+ MA: '摩洛哥',
+ MC: '摩纳哥',
+ MD: '摩尔多瓦',
+ ME: '黑山',
+ MF: '法属圣马丁',
+ MG: '马达加斯加',
+ MH: '马绍尔群岛',
+ MK: '北马其顿',
+ ML: '马里',
+ MM: '缅甸',
+ MN: '蒙古',
+ MO: '澳门',
+ MP: '北马里亚纳群岛',
+ MQ: '马提尼克',
+ MR: '毛里塔尼亚',
+ MS: '蒙特塞拉特',
+ MT: '马耳他',
+ MU: '毛里求斯',
+ MV: '马尔代夫',
+ MW: '马拉维',
+ MX: '墨西哥',
+ MY: '马来西亚',
+ MZ: '莫桑比克',
+ NA: '纳米比亚',
+ NC: '新喀里多尼亚',
+ NE: '尼日尔',
+ NF: '诺福克岛',
+ NG: '尼日利亚',
+ NI: '尼加拉瓜',
+ NL: '荷兰',
+ NO: '挪威',
+ NP: '尼泊尔',
+ NR: '瑙鲁',
+ NU: '纽埃',
+ NZ: '新西兰',
+ OM: '阿曼',
+ PA: '巴拿马',
+ PE: '秘鲁',
+ PF: '法属波利尼西亚',
+ PG: '巴布亚新几内亚',
+ PH: '菲律宾',
+ PK: '巴基斯坦',
+ PL: '波兰',
+ PM: '圣皮埃尔和密克隆',
+ PN: '皮特凯恩群岛',
+ PR: '波多黎各',
+ PS: '巴勒斯坦',
+ PT: '葡萄牙',
+ PW: '帕劳',
+ PY: '巴拉圭',
+ QA: '卡塔尔',
+ RE: '留尼汪',
+ RO: '罗马尼亚',
+ RS: '塞尔维亚',
+ RU: '俄罗斯',
+ RW: '卢旺达',
+ SA: '沙特阿拉伯',
+ SB: '所罗门群岛',
+ SC: '塞舌尔',
+ SD: '苏丹',
+ SE: '瑞典',
+ SG: '新加坡',
+ SH: '圣赫勒拿、阿森松和特里斯坦-达库尼亚',
+ SI: '斯洛文尼亚',
+ SJ: '斯瓦尔巴和扬马延',
+ SK: '斯洛伐克',
+ SL: '塞拉利昂',
+ SM: '圣马力诺',
+ SN: '塞内加尔',
+ SO: '索马里',
+ SR: '苏里南',
+ SS: '南苏丹',
+ ST: '圣多美和普林西比',
+ SV: '萨尔瓦多',
+ SX: '荷属圣马丁',
+ SY: '叙利亚',
+ SZ: '斯威士兰',
+ TC: '特克斯和凯科斯群岛',
+ TD: '乍得',
+ TF: '法属南部领地',
+ TG: '多哥',
+ TH: '泰国',
+ TJ: '塔吉克斯坦',
+ TK: '托克劳',
+ TL: '东帝汶',
+ TM: '土库曼斯坦',
+ TN: '突尼斯',
+ TO: '汤加',
+ TR: '土耳其',
+ TT: '特立尼达和多巴哥',
+ TV: '图瓦卢',
+ TW: '台湾',
+ TZ: '坦桑尼亚',
+ UA: '乌克兰',
+ UG: '乌干达',
+ UM: '美国本土外小岛屿',
+ US: '美国',
+ UY: '乌拉圭',
+ UZ: '乌兹别克斯坦',
+ VA: '梵蒂冈',
+ VC: '圣文森特和格林纳丁斯',
+ VE: '委内瑞拉',
+ VG: '英属维尔京群岛',
+ VI: '美属维尔京群岛',
+ VN: '越南',
+ VU: '瓦努阿图',
+ WF: '瓦利斯和富图纳',
+ WS: '萨摩亚',
+ YE: '也门',
+ YT: '马约特',
+ ZA: '南非',
+ ZM: '赞比亚',
+ ZW: '津巴布韦'
+};
+
+// 中国地区数据结构
+interface District {
+ code: string;
+ name: string;
+}
+
+interface City {
+ code: string;
+ name: string;
+ districts: District[];
+}
+
+interface Province {
+ code: string;
+ name: string;
+ cities: City[];
+}
+
+// 中国省份、城市、区县数据 - 完整数据集
+const chinaRegions: Province[] = [
+ {
+ code: '11',
+ name: '北京市',
+ cities: [
+ {
+ code: '1101',
+ name: '市辖区',
+ districts: [
+ { code: '110101', name: '东城区' },
+ { code: '110102', name: '西城区' },
+ { code: '110105', name: '朝阳区' },
+ { code: '110106', name: '丰台区' },
+ { code: '110107', name: '石景山区' },
+ { code: '110108', name: '海淀区' },
+ { code: '110109', name: '门头沟区' },
+ { code: '110111', name: '房山区' },
+ { code: '110112', name: '通州区' },
+ { code: '110113', name: '顺义区' },
+ { code: '110114', name: '昌平区' },
+ { code: '110115', name: '大兴区' },
+ { code: '110116', name: '怀柔区' },
+ { code: '110117', name: '平谷区' },
+ { code: '110118', name: '密云区' },
+ { code: '110119', name: '延庆区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '12',
+ name: '天津市',
+ cities: [
+ {
+ code: '1201',
+ name: '市辖区',
+ districts: [
+ { code: '120101', name: '和平区' },
+ { code: '120102', name: '河东区' },
+ { code: '120103', name: '河西区' },
+ { code: '120104', name: '南开区' },
+ { code: '120105', name: '河北区' },
+ { code: '120106', name: '红桥区' },
+ { code: '120110', name: '东丽区' },
+ { code: '120111', name: '西青区' },
+ { code: '120112', name: '津南区' },
+ { code: '120113', name: '北辰区' },
+ { code: '120114', name: '武清区' },
+ { code: '120115', name: '宝坻区' },
+ { code: '120116', name: '滨海新区' },
+ { code: '120117', name: '宁河区' },
+ { code: '120118', name: '静海区' },
+ { code: '120119', name: '蓟州区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '13',
+ name: '河北省',
+ cities: [
+ {
+ code: '1301',
+ name: '石家庄市',
+ districts: [
+ { code: '130102', name: '长安区' },
+ { code: '130104', name: '桥西区' },
+ { code: '130105', name: '新华区' },
+ { code: '130107', name: '井陉矿区' },
+ { code: '130108', name: '裕华区' },
+ { code: '130109', name: '藁城区' },
+ { code: '130110', name: '鹿泉区' },
+ { code: '130111', name: '栾城区' },
+ { code: '130121', name: '井陉县' },
+ { code: '130123', name: '正定县' },
+ { code: '130125', name: '行唐县' },
+ { code: '130126', name: '灵寿县' },
+ { code: '130127', name: '高邑县' },
+ { code: '130128', name: '深泽县' },
+ { code: '130129', name: '赞皇县' },
+ { code: '130130', name: '无极县' },
+ { code: '130131', name: '平山县' },
+ { code: '130132', name: '元氏县' },
+ { code: '130133', name: '赵县' },
+ { code: '130171', name: '石家庄高新技术产业开发区' },
+ { code: '130172', name: '石家庄循环化工园区' },
+ { code: '130181', name: '辛集市' },
+ { code: '130183', name: '晋州市' },
+ { code: '130184', name: '新乐市' }
+ ]
+ },
+ {
+ code: '1302',
+ name: '唐山市',
+ districts: [
+ { code: '130202', name: '路南区' },
+ { code: '130203', name: '路北区' },
+ { code: '130204', name: '古冶区' },
+ { code: '130205', name: '开平区' },
+ { code: '130207', name: '丰南区' },
+ { code: '130208', name: '丰润区' },
+ { code: '130209', name: '曹妃甸区' },
+ { code: '130224', name: '滦南县' },
+ { code: '130225', name: '乐亭县' },
+ { code: '130227', name: '迁西县' },
+ { code: '130229', name: '玉田县' },
+ { code: '130271', name: '河北唐山芦台经济开发区' },
+ { code: '130272', name: '唐山市汉沽管理区' },
+ { code: '130273', name: '唐山高新技术产业开发区' },
+ { code: '130274', name: '河北唐山海港经济开发区' },
+ { code: '130281', name: '遵化市' },
+ { code: '130283', name: '迁安市' },
+ { code: '130284', name: '滦州市' }
+ ]
+ },
+ {
+ code: '1303',
+ name: '秦皇岛市',
+ districts: [
+ { code: '130302', name: '海港区' },
+ { code: '130303', name: '山海关区' },
+ { code: '130304', name: '北戴河区' },
+ { code: '130306', name: '抚宁区' },
+ { code: '130321', name: '青龙满族自治县' },
+ { code: '130322', name: '昌黎县' },
+ { code: '130324', name: '卢龙县' },
+ { code: '130371', name: '秦皇岛市经济技术开发区' },
+ { code: '130372', name: '北戴河新区' }
+ ]
+ },
+ {
+ code: '1304',
+ name: '邯郸市',
+ districts: [
+ { code: '130402', name: '邯山区' },
+ { code: '130403', name: '丛台区' },
+ { code: '130404', name: '复兴区' },
+ { code: '130406', name: '峰峰矿区' },
+ { code: '130407', name: '肥乡区' },
+ { code: '130408', name: '永年区' },
+ { code: '130423', name: '临漳县' },
+ { code: '130424', name: '成安县' },
+ { code: '130425', name: '大名县' },
+ { code: '130426', name: '涉县' },
+ { code: '130427', name: '磁县' },
+ { code: '130430', name: '邱县' },
+ { code: '130431', name: '鸡泽县' },
+ { code: '130432', name: '广平县' },
+ { code: '130433', name: '馆陶县' },
+ { code: '130434', name: '魏县' },
+ { code: '130435', name: '曲周县' },
+ { code: '130471', name: '邯郸经济技术开发区' },
+ { code: '130473', name: '邯郸冀南新区' },
+ { code: '130481', name: '武安市' }
+ ]
+ },
+ {
+ code: '1305',
+ name: '邢台市',
+ districts: [
+ { code: '130502', name: '襄都区' },
+ { code: '130503', name: '信都区' },
+ { code: '130505', name: '任泽区' },
+ { code: '130506', name: '南和区' },
+ { code: '130522', name: '临城县' },
+ { code: '130523', name: '内丘县' },
+ { code: '130524', name: '柏乡县' },
+ { code: '130525', name: '隆尧县' },
+ { code: '130528', name: '宁晋县' },
+ { code: '130529', name: '巨鹿县' },
+ { code: '130530', name: '新河县' },
+ { code: '130531', name: '广宗县' },
+ { code: '130532', name: '平乡县' },
+ { code: '130533', name: '威县' },
+ { code: '130534', name: '清河县' },
+ { code: '130535', name: '临西县' },
+ { code: '130571', name: '河北邢台经济开发区' },
+ { code: '130581', name: '南宫市' },
+ { code: '130582', name: '沙河市' }
+ ]
+ },
+ {
+ code: '1306',
+ name: '保定市',
+ districts: [
+ { code: '130602', name: '竞秀区' },
+ { code: '130606', name: '莲池区' },
+ { code: '130607', name: '满城区' },
+ { code: '130608', name: '清苑区' },
+ { code: '130609', name: '徐水区' },
+ { code: '130623', name: '涞水县' },
+ { code: '130624', name: '阜平县' },
+ { code: '130626', name: '定兴县' },
+ { code: '130627', name: '唐县' },
+ { code: '130628', name: '高阳县' },
+ { code: '130629', name: '容城县' },
+ { code: '130630', name: '涞源县' },
+ { code: '130631', name: '望都县' },
+ { code: '130632', name: '安新县' },
+ { code: '130633', name: '易县' },
+ { code: '130634', name: '曲阳县' },
+ { code: '130635', name: '蠡县' },
+ { code: '130636', name: '顺平县' },
+ { code: '130637', name: '博野县' },
+ { code: '130638', name: '雄县' },
+ { code: '130671', name: '保定高新技术产业开发区' },
+ { code: '130672', name: '保定白沟新城' },
+ { code: '130681', name: '涿州市' },
+ { code: '130682', name: '定州市' },
+ { code: '130683', name: '安国市' },
+ { code: '130684', name: '高碑店市' }
+ ]
+ },
+ {
+ code: '1307',
+ name: '张家口市',
+ districts: [
+ { code: '130702', name: '桥东区' },
+ { code: '130703', name: '桥西区' },
+ { code: '130705', name: '宣化区' },
+ { code: '130706', name: '下花园区' },
+ { code: '130708', name: '万全区' },
+ { code: '130709', name: '崇礼区' },
+ { code: '130722', name: '张北县' },
+ { code: '130723', name: '康保县' },
+ { code: '130724', name: '沽源县' },
+ { code: '130725', name: '尚义县' },
+ { code: '130726', name: '蔚县' },
+ { code: '130727', name: '阳原县' },
+ { code: '130728', name: '怀安县' },
+ { code: '130730', name: '怀来县' },
+ { code: '130731', name: '涿鹿县' },
+ { code: '130732', name: '赤城县' },
+ { code: '130771', name: '张家口经济开发区' },
+ { code: '130772', name: '张家口市察北管理区' },
+ { code: '130773', name: '张家口市塞北管理区' }
+ ]
+ },
+ {
+ code: '1308',
+ name: '承德市',
+ districts: [
+ { code: '130802', name: '双桥区' },
+ { code: '130803', name: '双滦区' },
+ { code: '130804', name: '鹰手营子矿区' },
+ { code: '130821', name: '承德县' },
+ { code: '130822', name: '兴隆县' },
+ { code: '130824', name: '滦平县' },
+ { code: '130825', name: '隆化县' },
+ { code: '130826', name: '丰宁满族自治县' },
+ { code: '130827', name: '宽城满族自治县' },
+ { code: '130828', name: '围场满族蒙古族自治县' },
+ { code: '130871', name: '承德高新技术产业开发区' },
+ { code: '130881', name: '平泉市' }
+ ]
+ },
+ {
+ code: '1309',
+ name: '沧州市',
+ districts: [
+ { code: '130902', name: '新华区' },
+ { code: '130903', name: '运河区' },
+ { code: '130921', name: '沧县' },
+ { code: '130922', name: '青县' },
+ { code: '130923', name: '东光县' },
+ { code: '130924', name: '海兴县' },
+ { code: '130925', name: '盐山县' },
+ { code: '130926', name: '肃宁县' },
+ { code: '130927', name: '南皮县' },
+ { code: '130928', name: '吴桥县' },
+ { code: '130929', name: '献县' },
+ { code: '130930', name: '孟村回族自治县' },
+ { code: '130971', name: '河北沧州经济开发区' },
+ { code: '130972', name: '沧州高新技术产业开发区' },
+ { code: '130973', name: '沧州渤海新区' },
+ { code: '130981', name: '泊头市' },
+ { code: '130982', name: '任丘市' },
+ { code: '130983', name: '黄骅市' },
+ { code: '130984', name: '河间市' }
+ ]
+ },
+ {
+ code: '1310',
+ name: '廊坊市',
+ districts: [
+ { code: '131002', name: '安次区' },
+ { code: '131003', name: '广阳区' },
+ { code: '131022', name: '固安县' },
+ { code: '131023', name: '永清县' },
+ { code: '131024', name: '香河县' },
+ { code: '131025', name: '大城县' },
+ { code: '131026', name: '文安县' },
+ { code: '131028', name: '大厂回族自治县' },
+ { code: '131071', name: '廊坊经济技术开发区' },
+ { code: '131081', name: '霸州市' },
+ { code: '131082', name: '三河市' }
+ ]
+ },
+ {
+ code: '1311',
+ name: '衡水市',
+ districts: [
+ { code: '131102', name: '桃城区' },
+ { code: '131103', name: '冀州区' },
+ { code: '131121', name: '枣强县' },
+ { code: '131122', name: '武邑县' },
+ { code: '131123', name: '武强县' },
+ { code: '131124', name: '饶阳县' },
+ { code: '131125', name: '安平县' },
+ { code: '131126', name: '故城县' },
+ { code: '131127', name: '景县' },
+ { code: '131128', name: '阜城县' },
+ { code: '131171', name: '河北衡水高新技术产业开发区' },
+ { code: '131172', name: '衡水滨湖新区' },
+ { code: '131182', name: '深州市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '14',
+ name: '山西省',
+ cities: [
+ {
+ code: '1401',
+ name: '太原市',
+ districts: [
+ { code: '140105', name: '小店区' },
+ { code: '140106', name: '迎泽区' },
+ { code: '140107', name: '杏花岭区' },
+ { code: '140108', name: '尖草坪区' },
+ { code: '140109', name: '万柏林区' },
+ { code: '140110', name: '晋源区' },
+ { code: '140121', name: '清徐县' },
+ { code: '140122', name: '阳曲县' },
+ { code: '140123', name: '娄烦县' },
+ { code: '140171', name: '山西转型综合改革示范区' },
+ { code: '140181', name: '古交市' }
+ ]
+ },
+ {
+ code: '1402',
+ name: '大同市',
+ districts: [
+ { code: '140212', name: '新荣区' },
+ { code: '140213', name: '平城区' },
+ { code: '140214', name: '云冈区' },
+ { code: '140215', name: '云州区' },
+ { code: '140221', name: '阳高县' },
+ { code: '140222', name: '天镇县' },
+ { code: '140223', name: '广灵县' },
+ { code: '140224', name: '灵丘县' },
+ { code: '140225', name: '浑源县' },
+ { code: '140226', name: '左云县' },
+ { code: '140271', name: '山西大同经济开发区' }
+ ]
+ },
+ {
+ code: '1403',
+ name: '阳泉市',
+ districts: [
+ { code: '140302', name: '城区' },
+ { code: '140303', name: '矿区' },
+ { code: '140311', name: '郊区' },
+ { code: '140321', name: '平定县' },
+ { code: '140322', name: '盂县' }
+ ]
+ },
+ {
+ code: '1404',
+ name: '长治市',
+ districts: [
+ { code: '140403', name: '潞州区' },
+ { code: '140404', name: '上党区' },
+ { code: '140405', name: '屯留区' },
+ { code: '140406', name: '潞城区' },
+ { code: '140423', name: '襄垣县' },
+ { code: '140425', name: '平顺县' },
+ { code: '140426', name: '黎城县' },
+ { code: '140427', name: '壶关县' },
+ { code: '140428', name: '长子县' },
+ { code: '140429', name: '武乡县' },
+ { code: '140430', name: '沁县' },
+ { code: '140431', name: '沁源县' }
+ ]
+ },
+ {
+ code: '1405',
+ name: '晋城市',
+ districts: [
+ { code: '140502', name: '城区' },
+ { code: '140521', name: '沁水县' },
+ { code: '140522', name: '阳城县' },
+ { code: '140524', name: '陵川县' },
+ { code: '140525', name: '泽州县' },
+ { code: '140581', name: '高平市' }
+ ]
+ },
+ {
+ code: '1406',
+ name: '朔州市',
+ districts: [
+ { code: '140602', name: '朔城区' },
+ { code: '140603', name: '平鲁区' },
+ { code: '140621', name: '山阴县' },
+ { code: '140622', name: '应县' },
+ { code: '140623', name: '右玉县' },
+ { code: '140671', name: '山西朔州经济开发区' },
+ { code: '140681', name: '怀仁市' }
+ ]
+ },
+ {
+ code: '1407',
+ name: '晋中市',
+ districts: [
+ { code: '140702', name: '榆次区' },
+ { code: '140703', name: '太谷区' },
+ { code: '140721', name: '榆社县' },
+ { code: '140722', name: '左权县' },
+ { code: '140723', name: '和顺县' },
+ { code: '140724', name: '昔阳县' },
+ { code: '140725', name: '寿阳县' },
+ { code: '140727', name: '祁县' },
+ { code: '140728', name: '平遥县' },
+ { code: '140729', name: '灵石县' },
+ { code: '140781', name: '介休市' }
+ ]
+ },
+ {
+ code: '1408',
+ name: '运城市',
+ districts: [
+ { code: '140802', name: '盐湖区' },
+ { code: '140821', name: '临猗县' },
+ { code: '140822', name: '万荣县' },
+ { code: '140823', name: '闻喜县' },
+ { code: '140824', name: '稷山县' },
+ { code: '140825', name: '新绛县' },
+ { code: '140826', name: '绛县' },
+ { code: '140827', name: '垣曲县' },
+ { code: '140828', name: '夏县' },
+ { code: '140829', name: '平陆县' },
+ { code: '140830', name: '芮城县' },
+ { code: '140881', name: '永济市' },
+ { code: '140882', name: '河津市' }
+ ]
+ },
+ {
+ code: '1409',
+ name: '忻州市',
+ districts: [
+ { code: '140902', name: '忻府区' },
+ { code: '140921', name: '定襄县' },
+ { code: '140922', name: '五台县' },
+ { code: '140923', name: '代县' },
+ { code: '140924', name: '繁峙县' },
+ { code: '140925', name: '宁武县' },
+ { code: '140926', name: '静乐县' },
+ { code: '140927', name: '神池县' },
+ { code: '140928', name: '五寨县' },
+ { code: '140929', name: '岢岚县' },
+ { code: '140930', name: '河曲县' },
+ { code: '140931', name: '保德县' },
+ { code: '140932', name: '偏关县' },
+ { code: '140971', name: '五台山风景名胜区' },
+ { code: '140981', name: '原平市' }
+ ]
+ },
+ {
+ code: '1410',
+ name: '临汾市',
+ districts: [
+ { code: '141002', name: '尧都区' },
+ { code: '141021', name: '曲沃县' },
+ { code: '141022', name: '翼城县' },
+ { code: '141023', name: '襄汾县' },
+ { code: '141024', name: '洪洞县' },
+ { code: '141025', name: '古县' },
+ { code: '141026', name: '安泽县' },
+ { code: '141027', name: '浮山县' },
+ { code: '141028', name: '吉县' },
+ { code: '141029', name: '乡宁县' },
+ { code: '141030', name: '大宁县' },
+ { code: '141031', name: '隰县' },
+ { code: '141032', name: '永和县' },
+ { code: '141033', name: '蒲县' },
+ { code: '141034', name: '汾西县' },
+ { code: '141081', name: '侯马市' },
+ { code: '141082', name: '霍州市' }
+ ]
+ },
+ {
+ code: '1411',
+ name: '吕梁市',
+ districts: [
+ { code: '141102', name: '离石区' },
+ { code: '141121', name: '文水县' },
+ { code: '141122', name: '交城县' },
+ { code: '141123', name: '兴县' },
+ { code: '141124', name: '临县' },
+ { code: '141125', name: '柳林县' },
+ { code: '141126', name: '石楼县' },
+ { code: '141127', name: '岚县' },
+ { code: '141128', name: '方山县' },
+ { code: '141129', name: '中阳县' },
+ { code: '141130', name: '交口县' },
+ { code: '141181', name: '孝义市' },
+ { code: '141182', name: '汾阳市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '15',
+ name: '内蒙古自治区',
+ cities: [
+ {
+ code: '1501',
+ name: '呼和浩特市',
+ districts: [
+ { code: '150102', name: '新城区' },
+ { code: '150103', name: '回民区' },
+ { code: '150104', name: '玉泉区' },
+ { code: '150105', name: '赛罕区' },
+ { code: '150121', name: '土默特左旗' },
+ { code: '150122', name: '托克托县' },
+ { code: '150123', name: '和林格尔县' },
+ { code: '150124', name: '清水河县' },
+ { code: '150125', name: '武川县' },
+ { code: '150172', name: '呼和浩特经济技术开发区' }
+ ]
+ },
+ {
+ code: '1502',
+ name: '包头市',
+ districts: [
+ { code: '150202', name: '东河区' },
+ { code: '150203', name: '昆都仑区' },
+ { code: '150204', name: '青山区' },
+ { code: '150205', name: '石拐区' },
+ { code: '150206', name: '白云鄂博矿区' },
+ { code: '150207', name: '九原区' },
+ { code: '150221', name: '土默特右旗' },
+ { code: '150222', name: '固阳县' },
+ { code: '150223', name: '达尔罕茂明安联合旗' },
+ { code: '150271', name: '包头稀土高新技术产业开发区' }
+ ]
+ },
+ {
+ code: '1503',
+ name: '乌海市',
+ districts: [
+ { code: '150302', name: '海勃湾区' },
+ { code: '150303', name: '海南区' },
+ { code: '150304', name: '乌达区' }
+ ]
+ },
+ {
+ code: '1504',
+ name: '赤峰市',
+ districts: [
+ { code: '150402', name: '红山区' },
+ { code: '150403', name: '元宝山区' },
+ { code: '150404', name: '松山区' },
+ { code: '150421', name: '阿鲁科尔沁旗' },
+ { code: '150422', name: '巴林左旗' },
+ { code: '150423', name: '巴林右旗' },
+ { code: '150424', name: '林西县' },
+ { code: '150425', name: '克什克腾旗' },
+ { code: '150426', name: '翁牛特旗' },
+ { code: '150428', name: '喀喇沁旗' },
+ { code: '150429', name: '宁城县' },
+ { code: '150430', name: '敖汉旗' }
+ ]
+ },
+ {
+ code: '1505',
+ name: '通辽市',
+ districts: [
+ { code: '150502', name: '科尔沁区' },
+ { code: '150521', name: '科尔沁左翼中旗' },
+ { code: '150522', name: '科尔沁左翼后旗' },
+ { code: '150523', name: '开鲁县' },
+ { code: '150524', name: '库伦旗' },
+ { code: '150525', name: '奈曼旗' },
+ { code: '150526', name: '扎鲁特旗' },
+ { code: '150571', name: '通辽经济技术开发区' },
+ { code: '150581', name: '霍林郭勒市' }
+ ]
+ },
+ {
+ code: '1506',
+ name: '鄂尔多斯市',
+ districts: [
+ { code: '150602', name: '东胜区' },
+ { code: '150603', name: '康巴什区' },
+ { code: '150621', name: '达拉特旗' },
+ { code: '150622', name: '准格尔旗' },
+ { code: '150623', name: '鄂托克前旗' },
+ { code: '150624', name: '鄂托克旗' },
+ { code: '150625', name: '杭锦旗' },
+ { code: '150626', name: '乌审旗' },
+ { code: '150627', name: '伊金霍洛旗' }
+ ]
+ },
+ {
+ code: '1507',
+ name: '呼伦贝尔市',
+ districts: [
+ { code: '150702', name: '海拉尔区' },
+ { code: '150703', name: '扎赉诺尔区' },
+ { code: '150721', name: '阿荣旗' },
+ { code: '150722', name: '莫力达瓦达斡尔族自治旗' },
+ { code: '150723', name: '鄂伦春自治旗' },
+ { code: '150724', name: '鄂温克族自治旗' },
+ { code: '150725', name: '陈巴尔虎旗' },
+ { code: '150726', name: '新巴尔虎左旗' },
+ { code: '150727', name: '新巴尔虎右旗' },
+ { code: '150781', name: '满洲里市' },
+ { code: '150782', name: '牙克石市' },
+ { code: '150783', name: '扎兰屯市' },
+ { code: '150784', name: '额尔古纳市' },
+ { code: '150785', name: '根河市' }
+ ]
+ },
+ {
+ code: '1508',
+ name: '巴彦淖尔市',
+ districts: [
+ { code: '150802', name: '临河区' },
+ { code: '150821', name: '五原县' },
+ { code: '150822', name: '磴口县' },
+ { code: '150823', name: '乌拉特前旗' },
+ { code: '150824', name: '乌拉特中旗' },
+ { code: '150825', name: '乌拉特后旗' },
+ { code: '150826', name: '杭锦后旗' }
+ ]
+ },
+ {
+ code: '1509',
+ name: '乌兰察布市',
+ districts: [
+ { code: '150902', name: '集宁区' },
+ { code: '150921', name: '卓资县' },
+ { code: '150922', name: '化德县' },
+ { code: '150923', name: '商都县' },
+ { code: '150924', name: '兴和县' },
+ { code: '150925', name: '凉城县' },
+ { code: '150926', name: '察哈尔右翼前旗' },
+ { code: '150927', name: '察哈尔右翼中旗' },
+ { code: '150928', name: '察哈尔右翼后旗' },
+ { code: '150929', name: '四子王旗' },
+ { code: '150981', name: '丰镇市' }
+ ]
+ },
+ {
+ code: '1522',
+ name: '兴安盟',
+ districts: [
+ { code: '152201', name: '乌兰浩特市' },
+ { code: '152202', name: '阿尔山市' },
+ { code: '152221', name: '科尔沁右翼前旗' },
+ { code: '152222', name: '科尔沁右翼中旗' },
+ { code: '152223', name: '扎赉特旗' },
+ { code: '152224', name: '突泉县' }
+ ]
+ },
+ {
+ code: '1525',
+ name: '锡林郭勒盟',
+ districts: [
+ { code: '152501', name: '二连浩特市' },
+ { code: '152502', name: '锡林浩特市' },
+ { code: '152522', name: '阿巴嘎旗' },
+ { code: '152523', name: '苏尼特左旗' },
+ { code: '152524', name: '苏尼特右旗' },
+ { code: '152525', name: '东乌珠穆沁旗' },
+ { code: '152526', name: '西乌珠穆沁旗' },
+ { code: '152527', name: '太仆寺旗' },
+ { code: '152528', name: '镶黄旗' },
+ { code: '152529', name: '正镶白旗' },
+ { code: '152530', name: '正蓝旗' },
+ { code: '152531', name: '多伦县' },
+ { code: '152571', name: '乌拉盖管理区管委会' }
+ ]
+ },
+ {
+ code: '1529',
+ name: '阿拉善盟',
+ districts: [
+ { code: '152921', name: '阿拉善左旗' },
+ { code: '152922', name: '阿拉善右旗' },
+ { code: '152923', name: '额济纳旗' },
+ { code: '152971', name: '内蒙古阿拉善高新技术产业开发区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '21',
+ name: '辽宁省',
+ cities: [
+ {
+ code: '2101',
+ name: '沈阳市',
+ districts: [
+ { code: '210102', name: '和平区' },
+ { code: '210103', name: '沈河区' },
+ { code: '210104', name: '大东区' },
+ { code: '210105', name: '皇姑区' },
+ { code: '210106', name: '铁西区' },
+ { code: '210111', name: '苏家屯区' },
+ { code: '210112', name: '浑南区' },
+ { code: '210113', name: '沈北新区' },
+ { code: '210114', name: '于洪区' },
+ { code: '210115', name: '辽中区' },
+ { code: '210123', name: '康平县' },
+ { code: '210124', name: '法库县' },
+ { code: '210181', name: '新民市' }
+ ]
+ },
+ {
+ code: '2102',
+ name: '大连市',
+ districts: [
+ { code: '210202', name: '中山区' },
+ { code: '210203', name: '西岗区' },
+ { code: '210204', name: '沙河口区' },
+ { code: '210211', name: '甘井子区' },
+ { code: '210212', name: '旅顺口区' },
+ { code: '210213', name: '金州区' },
+ { code: '210214', name: '普兰店区' },
+ { code: '210224', name: '长海县' },
+ { code: '210281', name: '瓦房店市' },
+ { code: '210283', name: '庄河市' }
+ ]
+ },
+ {
+ code: '2103',
+ name: '鞍山市',
+ districts: [
+ { code: '210302', name: '铁东区' },
+ { code: '210303', name: '铁西区' },
+ { code: '210304', name: '立山区' },
+ { code: '210311', name: '千山区' },
+ { code: '210321', name: '台安县' },
+ { code: '210323', name: '岫岩满族自治县' },
+ { code: '210381', name: '海城市' }
+ ]
+ },
+ {
+ code: '2104',
+ name: '抚顺市',
+ districts: [
+ { code: '210402', name: '新抚区' },
+ { code: '210403', name: '东洲区' },
+ { code: '210404', name: '望花区' },
+ { code: '210411', name: '顺城区' },
+ { code: '210421', name: '抚顺县' },
+ { code: '210422', name: '新宾满族自治县' },
+ { code: '210423', name: '清原满族自治县' }
+ ]
+ },
+ {
+ code: '2105',
+ name: '本溪市',
+ districts: [
+ { code: '210502', name: '平山区' },
+ { code: '210503', name: '溪湖区' },
+ { code: '210504', name: '明山区' },
+ { code: '210505', name: '南芬区' },
+ { code: '210521', name: '本溪满族自治县' },
+ { code: '210522', name: '桓仁满族自治县' }
+ ]
+ },
+ {
+ code: '2106',
+ name: '丹东市',
+ districts: [
+ { code: '210602', name: '元宝区' },
+ { code: '210603', name: '振兴区' },
+ { code: '210604', name: '振安区' },
+ { code: '210624', name: '宽甸满族自治县' },
+ { code: '210681', name: '东港市' },
+ { code: '210682', name: '凤城市' }
+ ]
+ },
+ {
+ code: '2107',
+ name: '锦州市',
+ districts: [
+ { code: '210702', name: '古塔区' },
+ { code: '210703', name: '凌河区' },
+ { code: '210711', name: '太和区' },
+ { code: '210726', name: '黑山县' },
+ { code: '210727', name: '义县' },
+ { code: '210781', name: '凌海市' },
+ { code: '210782', name: '北镇市' }
+ ]
+ },
+ {
+ code: '2108',
+ name: '营口市',
+ districts: [
+ { code: '210802', name: '站前区' },
+ { code: '210803', name: '西市区' },
+ { code: '210804', name: '鲅鱼圈区' },
+ { code: '210811', name: '老边区' },
+ { code: '210881', name: '盖州市' },
+ { code: '210882', name: '大石桥市' }
+ ]
+ },
+ {
+ code: '2109',
+ name: '阜新市',
+ districts: [
+ { code: '210902', name: '海州区' },
+ { code: '210903', name: '新邱区' },
+ { code: '210904', name: '太平区' },
+ { code: '210905', name: '清河门区' },
+ { code: '210911', name: '细河区' },
+ { code: '210921', name: '阜新蒙古族自治县' },
+ { code: '210922', name: '彰武县' }
+ ]
+ },
+ {
+ code: '2110',
+ name: '辽阳市',
+ districts: [
+ { code: '211002', name: '白塔区' },
+ { code: '211003', name: '文圣区' },
+ { code: '211004', name: '宏伟区' },
+ { code: '211005', name: '弓长岭区' },
+ { code: '211011', name: '太子河区' },
+ { code: '211021', name: '辽阳县' },
+ { code: '211081', name: '灯塔市' }
+ ]
+ },
+ {
+ code: '2111',
+ name: '盘锦市',
+ districts: [
+ { code: '211102', name: '双台子区' },
+ { code: '211103', name: '兴隆台区' },
+ { code: '211104', name: '大洼区' },
+ { code: '211122', name: '盘山县' }
+ ]
+ },
+ {
+ code: '2112',
+ name: '铁岭市',
+ districts: [
+ { code: '211202', name: '银州区' },
+ { code: '211204', name: '清河区' },
+ { code: '211221', name: '铁岭县' },
+ { code: '211223', name: '西丰县' },
+ { code: '211224', name: '昌图县' },
+ { code: '211281', name: '调兵山市' },
+ { code: '211282', name: '开原市' }
+ ]
+ },
+ {
+ code: '2113',
+ name: '朝阳市',
+ districts: [
+ { code: '211302', name: '双塔区' },
+ { code: '211303', name: '龙城区' },
+ { code: '211321', name: '朝阳县' },
+ { code: '211322', name: '建平县' },
+ { code: '211324', name: '喀喇沁左翼蒙古族自治县' },
+ { code: '211381', name: '北票市' },
+ { code: '211382', name: '凌源市' }
+ ]
+ },
+ {
+ code: '2114',
+ name: '葫芦岛市',
+ districts: [
+ { code: '211402', name: '连山区' },
+ { code: '211403', name: '龙港区' },
+ { code: '211404', name: '南票区' },
+ { code: '211421', name: '绥中县' },
+ { code: '211422', name: '建昌县' },
+ { code: '211481', name: '兴城市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '22',
+ name: '吉林省',
+ cities: [
+ {
+ code: '2201',
+ name: '长春市',
+ districts: [
+ { code: '220102', name: '南关区' },
+ { code: '220103', name: '宽城区' },
+ { code: '220104', name: '朝阳区' },
+ { code: '220105', name: '二道区' },
+ { code: '220106', name: '绿园区' },
+ { code: '220112', name: '双阳区' },
+ { code: '220113', name: '九台区' },
+ { code: '220122', name: '农安县' },
+ { code: '220171', name: '长春经济技术开发区' },
+ { code: '220172', name: '长春净月高新技术产业开发区' },
+ { code: '220173', name: '长春高新技术产业开发区' },
+ { code: '220174', name: '长春汽车经济技术开发区' },
+ { code: '220182', name: '榆树市' },
+ { code: '220183', name: '德惠市' },
+ { code: '220184', name: '公主岭市' }
+ ]
+ },
+ {
+ code: '2202',
+ name: '吉林市',
+ districts: [
+ { code: '220202', name: '昌邑区' },
+ { code: '220203', name: '龙潭区' },
+ { code: '220204', name: '船营区' },
+ { code: '220211', name: '丰满区' },
+ { code: '220221', name: '永吉县' },
+ { code: '220271', name: '吉林经济开发区' },
+ { code: '220272', name: '吉林高新技术产业开发区' },
+ { code: '220273', name: '吉林中国新加坡食品区' },
+ { code: '220281', name: '蛟河市' },
+ { code: '220282', name: '桦甸市' },
+ { code: '220283', name: '舒兰市' },
+ { code: '220284', name: '磐石市' }
+ ]
+ },
+ {
+ code: '2203',
+ name: '四平市',
+ districts: [
+ { code: '220302', name: '铁西区' },
+ { code: '220303', name: '铁东区' },
+ { code: '220322', name: '梨树县' },
+ { code: '220323', name: '伊通满族自治县' },
+ { code: '220382', name: '双辽市' }
+ ]
+ },
+ {
+ code: '2204',
+ name: '辽源市',
+ districts: [
+ { code: '220402', name: '龙山区' },
+ { code: '220403', name: '西安区' },
+ { code: '220421', name: '东丰县' },
+ { code: '220422', name: '东辽县' }
+ ]
+ },
+ {
+ code: '2205',
+ name: '通化市',
+ districts: [
+ { code: '220502', name: '东昌区' },
+ { code: '220503', name: '二道江区' },
+ { code: '220521', name: '通化县' },
+ { code: '220523', name: '辉南县' },
+ { code: '220524', name: '柳河县' },
+ { code: '220581', name: '梅河口市' },
+ { code: '220582', name: '集安市' }
+ ]
+ },
+ {
+ code: '2206',
+ name: '白山市',
+ districts: [
+ { code: '220602', name: '浑江区' },
+ { code: '220605', name: '江源区' },
+ { code: '220621', name: '抚松县' },
+ { code: '220622', name: '靖宇县' },
+ { code: '220623', name: '长白朝鲜族自治县' },
+ { code: '220681', name: '临江市' }
+ ]
+ },
+ {
+ code: '2207',
+ name: '松原市',
+ districts: [
+ { code: '220702', name: '宁江区' },
+ { code: '220721', name: '前郭尔罗斯蒙古族自治县' },
+ { code: '220722', name: '长岭县' },
+ { code: '220723', name: '乾安县' },
+ { code: '220771', name: '吉林松原经济开发区' },
+ { code: '220781', name: '扶余市' }
+ ]
+ },
+ {
+ code: '2208',
+ name: '白城市',
+ districts: [
+ { code: '220802', name: '洮北区' },
+ { code: '220821', name: '镇赉县' },
+ { code: '220822', name: '通榆县' },
+ { code: '220871', name: '吉林白城经济开发区' },
+ { code: '220881', name: '洮南市' },
+ { code: '220882', name: '大安市' }
+ ]
+ },
+ {
+ code: '2224',
+ name: '延边朝鲜族自治州',
+ districts: [
+ { code: '222401', name: '延吉市' },
+ { code: '222402', name: '图们市' },
+ { code: '222403', name: '敦化市' },
+ { code: '222404', name: '珲春市' },
+ { code: '222405', name: '龙井市' },
+ { code: '222406', name: '和龙市' },
+ { code: '222424', name: '汪清县' },
+ { code: '222426', name: '安图县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '23',
+ name: '黑龙江省',
+ cities: [
+ {
+ code: '2301',
+ name: '哈尔滨市',
+ districts: [
+ { code: '230102', name: '道里区' },
+ { code: '230103', name: '南岗区' },
+ { code: '230104', name: '道外区' },
+ { code: '230108', name: '平房区' },
+ { code: '230109', name: '松北区' },
+ { code: '230110', name: '香坊区' },
+ { code: '230111', name: '呼兰区' },
+ { code: '230112', name: '阿城区' },
+ { code: '230113', name: '双城区' },
+ { code: '230123', name: '依兰县' },
+ { code: '230124', name: '方正县' },
+ { code: '230125', name: '宾县' },
+ { code: '230126', name: '巴彦县' },
+ { code: '230127', name: '木兰县' },
+ { code: '230128', name: '通河县' },
+ { code: '230129', name: '延寿县' },
+ { code: '230183', name: '尚志市' },
+ { code: '230184', name: '五常市' }
+ ]
+ },
+ {
+ code: '2302',
+ name: '齐齐哈尔市',
+ districts: [
+ { code: '230202', name: '龙沙区' },
+ { code: '230203', name: '建华区' },
+ { code: '230204', name: '铁锋区' },
+ { code: '230205', name: '昂昂溪区' },
+ { code: '230206', name: '富拉尔基区' },
+ { code: '230207', name: '碾子山区' },
+ { code: '230208', name: '梅里斯达斡尔族区' },
+ { code: '230221', name: '龙江县' },
+ { code: '230223', name: '依安县' },
+ { code: '230224', name: '泰来县' },
+ { code: '230225', name: '甘南县' },
+ { code: '230227', name: '富裕县' },
+ { code: '230229', name: '克山县' },
+ { code: '230230', name: '克东县' },
+ { code: '230231', name: '拜泉县' },
+ { code: '230281', name: '讷河市' }
+ ]
+ },
+ {
+ code: '2303',
+ name: '鸡西市',
+ districts: [
+ { code: '230302', name: '鸡冠区' },
+ { code: '230303', name: '恒山区' },
+ { code: '230304', name: '滴道区' },
+ { code: '230305', name: '梨树区' },
+ { code: '230306', name: '城子河区' },
+ { code: '230307', name: '麻山区' },
+ { code: '230321', name: '鸡东县' },
+ { code: '230381', name: '虎林市' },
+ { code: '230382', name: '密山市' }
+ ]
+ },
+ {
+ code: '2304',
+ name: '鹤岗市',
+ districts: [
+ { code: '230402', name: '向阳区' },
+ { code: '230403', name: '工农区' },
+ { code: '230404', name: '南山区' },
+ { code: '230405', name: '兴安区' },
+ { code: '230406', name: '东山区' },
+ { code: '230407', name: '兴山区' },
+ { code: '230421', name: '萝北县' },
+ { code: '230422', name: '绥滨县' }
+ ]
+ },
+ {
+ code: '2305',
+ name: '双鸭山市',
+ districts: [
+ { code: '230502', name: '尖山区' },
+ { code: '230503', name: '岭东区' },
+ { code: '230505', name: '四方台区' },
+ { code: '230506', name: '宝山区' },
+ { code: '230521', name: '集贤县' },
+ { code: '230522', name: '友谊县' },
+ { code: '230523', name: '宝清县' },
+ { code: '230524', name: '饶河县' }
+ ]
+ },
+ {
+ code: '2306',
+ name: '大庆市',
+ districts: [
+ { code: '230602', name: '萨尔图区' },
+ { code: '230603', name: '龙凤区' },
+ { code: '230604', name: '让胡路区' },
+ { code: '230605', name: '红岗区' },
+ { code: '230606', name: '大同区' },
+ { code: '230621', name: '肇州县' },
+ { code: '230622', name: '肇源县' },
+ { code: '230623', name: '林甸县' },
+ { code: '230624', name: '杜尔伯特蒙古族自治县' },
+ { code: '230671', name: '大庆高新技术产业开发区' }
+ ]
+ },
+ {
+ code: '2307',
+ name: '伊春市',
+ districts: [
+ { code: '230717', name: '伊美区' },
+ { code: '230718', name: '乌翠区' },
+ { code: '230719', name: '友好区' },
+ { code: '230722', name: '嘉荫县' },
+ { code: '230723', name: '汤旺县' },
+ { code: '230724', name: '丰林县' },
+ { code: '230725', name: '大箐山县' },
+ { code: '230726', name: '南岔县' },
+ { code: '230751', name: '金林区' },
+ { code: '230781', name: '铁力市' }
+ ]
+ },
+ {
+ code: '2308',
+ name: '佳木斯市',
+ districts: [
+ { code: '230803', name: '向阳区' },
+ { code: '230804', name: '前进区' },
+ { code: '230805', name: '东风区' },
+ { code: '230811', name: '郊区' },
+ { code: '230822', name: '桦南县' },
+ { code: '230826', name: '桦川县' },
+ { code: '230828', name: '汤原县' },
+ { code: '230881', name: '同江市' },
+ { code: '230882', name: '富锦市' },
+ { code: '230883', name: '抚远市' }
+ ]
+ },
+ {
+ code: '2309',
+ name: '七台河市',
+ districts: [
+ { code: '230902', name: '新兴区' },
+ { code: '230903', name: '桃山区' },
+ { code: '230904', name: '茄子河区' },
+ { code: '230921', name: '勃利县' }
+ ]
+ },
+ {
+ code: '2310',
+ name: '牡丹江市',
+ districts: [
+ { code: '231002', name: '东安区' },
+ { code: '231003', name: '阳明区' },
+ { code: '231004', name: '爱民区' },
+ { code: '231005', name: '西安区' },
+ { code: '231025', name: '林口县' },
+ { code: '231081', name: '绥芬河市' },
+ { code: '231083', name: '海林市' },
+ { code: '231084', name: '宁安市' },
+ { code: '231085', name: '穆棱市' },
+ { code: '231086', name: '东宁市' }
+ ]
+ },
+ {
+ code: '2311',
+ name: '黑河市',
+ districts: [
+ { code: '231102', name: '爱辉区' },
+ { code: '231123', name: '逊克县' },
+ { code: '231124', name: '孙吴县' },
+ { code: '231181', name: '北安市' },
+ { code: '231182', name: '五大连池市' },
+ { code: '231183', name: '嫩江市' }
+ ]
+ },
+ {
+ code: '2312',
+ name: '绥化市',
+ districts: [
+ { code: '231202', name: '北林区' },
+ { code: '231221', name: '望奎县' },
+ { code: '231222', name: '兰西县' },
+ { code: '231223', name: '青冈县' },
+ { code: '231224', name: '庆安县' },
+ { code: '231225', name: '明水县' },
+ { code: '231226', name: '绥棱县' },
+ { code: '231281', name: '安达市' },
+ { code: '231282', name: '肇东市' },
+ { code: '231283', name: '海伦市' }
+ ]
+ },
+ {
+ code: '2327',
+ name: '大兴安岭地区',
+ districts: [
+ { code: '232701', name: '漠河市' },
+ { code: '232721', name: '呼玛县' },
+ { code: '232722', name: '塔河县' },
+ { code: '232761', name: '加格达奇区' },
+ { code: '232762', name: '松岭区' },
+ { code: '232763', name: '新林区' },
+ { code: '232764', name: '呼中区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '31',
+ name: '上海市',
+ cities: [
+ {
+ code: '3101',
+ name: '市辖区',
+ districts: [
+ { code: '310101', name: '黄浦区' },
+ { code: '310104', name: '徐汇区' },
+ { code: '310105', name: '长宁区' },
+ { code: '310106', name: '静安区' },
+ { code: '310107', name: '普陀区' },
+ { code: '310109', name: '虹口区' },
+ { code: '310110', name: '杨浦区' },
+ { code: '310112', name: '闵行区' },
+ { code: '310113', name: '宝山区' },
+ { code: '310114', name: '嘉定区' },
+ { code: '310115', name: '浦东新区' },
+ { code: '310116', name: '金山区' },
+ { code: '310117', name: '松江区' },
+ { code: '310118', name: '青浦区' },
+ { code: '310120', name: '奉贤区' },
+ { code: '310151', name: '崇明区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '32',
+ name: '江苏省',
+ cities: [
+ {
+ code: '3201',
+ name: '南京市',
+ districts: [
+ { code: '320102', name: '玄武区' },
+ { code: '320104', name: '秦淮区' },
+ { code: '320105', name: '建邺区' },
+ { code: '320106', name: '鼓楼区' },
+ { code: '320111', name: '浦口区' },
+ { code: '320113', name: '栖霞区' },
+ { code: '320114', name: '雨花台区' },
+ { code: '320115', name: '江宁区' },
+ { code: '320116', name: '六合区' },
+ { code: '320117', name: '溧水区' },
+ { code: '320118', name: '高淳区' }
+ ]
+ },
+ {
+ code: '3202',
+ name: '无锡市',
+ districts: [
+ { code: '320205', name: '锡山区' },
+ { code: '320206', name: '惠山区' },
+ { code: '320211', name: '滨湖区' },
+ { code: '320213', name: '梁溪区' },
+ { code: '320214', name: '新吴区' },
+ { code: '320281', name: '江阴市' },
+ { code: '320282', name: '宜兴市' }
+ ]
+ },
+ {
+ code: '3203',
+ name: '徐州市',
+ districts: [
+ { code: '320302', name: '鼓楼区' },
+ { code: '320303', name: '云龙区' },
+ { code: '320305', name: '贾汪区' },
+ { code: '320311', name: '泉山区' },
+ { code: '320312', name: '铜山区' },
+ { code: '320321', name: '丰县' },
+ { code: '320322', name: '沛县' },
+ { code: '320324', name: '睢宁县' },
+ { code: '320371', name: '徐州经济技术开发区' },
+ { code: '320381', name: '新沂市' },
+ { code: '320382', name: '邳州市' }
+ ]
+ },
+ {
+ code: '3204',
+ name: '常州市',
+ districts: [
+ { code: '320402', name: '天宁区' },
+ { code: '320404', name: '钟楼区' },
+ { code: '320411', name: '新北区' },
+ { code: '320412', name: '武进区' },
+ { code: '320413', name: '金坛区' },
+ { code: '320481', name: '溧阳市' }
+ ]
+ },
+ {
+ code: '3205',
+ name: '苏州市',
+ districts: [
+ { code: '320505', name: '虎丘区' },
+ { code: '320506', name: '吴中区' },
+ { code: '320507', name: '相城区' },
+ { code: '320508', name: '姑苏区' },
+ { code: '320509', name: '吴江区' },
+ { code: '320576', name: '苏州工业园区' },
+ { code: '320581', name: '常熟市' },
+ { code: '320582', name: '张家港市' },
+ { code: '320583', name: '昆山市' },
+ { code: '320585', name: '太仓市' }
+ ]
+ },
+ {
+ code: '3206',
+ name: '南通市',
+ districts: [
+ { code: '320612', name: '通州区' },
+ { code: '320613', name: '崇川区' },
+ { code: '320614', name: '海门区' },
+ { code: '320623', name: '如东县' },
+ { code: '320671', name: '南通经济技术开发区' },
+ { code: '320681', name: '启东市' },
+ { code: '320682', name: '如皋市' },
+ { code: '320685', name: '海安市' }
+ ]
+ },
+ {
+ code: '3207',
+ name: '连云港市',
+ districts: [
+ { code: '320703', name: '连云区' },
+ { code: '320706', name: '海州区' },
+ { code: '320707', name: '赣榆区' },
+ { code: '320722', name: '东海县' },
+ { code: '320723', name: '灌云县' },
+ { code: '320724', name: '灌南县' },
+ { code: '320771', name: '连云港经济技术开发区' }
+ ]
+ },
+ {
+ code: '3208',
+ name: '淮安市',
+ districts: [
+ { code: '320803', name: '淮安区' },
+ { code: '320804', name: '淮阴区' },
+ { code: '320812', name: '清江浦区' },
+ { code: '320813', name: '洪泽区' },
+ { code: '320826', name: '涟水县' },
+ { code: '320830', name: '盱眙县' },
+ { code: '320831', name: '金湖县' },
+ { code: '320871', name: '淮安经济技术开发区' }
+ ]
+ },
+ {
+ code: '3209',
+ name: '盐城市',
+ districts: [
+ { code: '320902', name: '亭湖区' },
+ { code: '320903', name: '盐都区' },
+ { code: '320904', name: '大丰区' },
+ { code: '320921', name: '响水县' },
+ { code: '320922', name: '滨海县' },
+ { code: '320923', name: '阜宁县' },
+ { code: '320924', name: '射阳县' },
+ { code: '320925', name: '建湖县' },
+ { code: '320971', name: '盐城经济技术开发区' },
+ { code: '320981', name: '东台市' }
+ ]
+ },
+ {
+ code: '3210',
+ name: '扬州市',
+ districts: [
+ { code: '321002', name: '广陵区' },
+ { code: '321003', name: '邗江区' },
+ { code: '321012', name: '江都区' },
+ { code: '321023', name: '宝应县' },
+ { code: '321071', name: '扬州经济技术开发区' },
+ { code: '321081', name: '仪征市' },
+ { code: '321084', name: '高邮市' }
+ ]
+ },
+ {
+ code: '3211',
+ name: '镇江市',
+ districts: [
+ { code: '321102', name: '京口区' },
+ { code: '321111', name: '润州区' },
+ { code: '321112', name: '丹徒区' },
+ { code: '321171', name: '镇江新区' },
+ { code: '321181', name: '丹阳市' },
+ { code: '321182', name: '扬中市' },
+ { code: '321183', name: '句容市' }
+ ]
+ },
+ {
+ code: '3212',
+ name: '泰州市',
+ districts: [
+ { code: '321202', name: '海陵区' },
+ { code: '321203', name: '高港区' },
+ { code: '321204', name: '姜堰区' },
+ { code: '321281', name: '兴化市' },
+ { code: '321282', name: '靖江市' },
+ { code: '321283', name: '泰兴市' }
+ ]
+ },
+ {
+ code: '3213',
+ name: '宿迁市',
+ districts: [
+ { code: '321302', name: '宿城区' },
+ { code: '321311', name: '宿豫区' },
+ { code: '321322', name: '沭阳县' },
+ { code: '321323', name: '泗阳县' },
+ { code: '321324', name: '泗洪县' },
+ { code: '321371', name: '宿迁经济技术开发区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '33',
+ name: '浙江省',
+ cities: [
+ {
+ code: '3301',
+ name: '杭州市',
+ districts: [
+ { code: '330102', name: '上城区' },
+ { code: '330105', name: '拱墅区' },
+ { code: '330106', name: '西湖区' },
+ { code: '330108', name: '滨江区' },
+ { code: '330109', name: '萧山区' },
+ { code: '330110', name: '余杭区' },
+ { code: '330111', name: '富阳区' },
+ { code: '330112', name: '临安区' },
+ { code: '330113', name: '临平区' },
+ { code: '330114', name: '钱塘区' },
+ { code: '330122', name: '桐庐县' },
+ { code: '330127', name: '淳安县' },
+ { code: '330182', name: '建德市' }
+ ]
+ },
+ {
+ code: '3302',
+ name: '宁波市',
+ districts: [
+ { code: '330203', name: '海曙区' },
+ { code: '330205', name: '江北区' },
+ { code: '330206', name: '北仑区' },
+ { code: '330211', name: '镇海区' },
+ { code: '330212', name: '鄞州区' },
+ { code: '330213', name: '奉化区' },
+ { code: '330225', name: '象山县' },
+ { code: '330226', name: '宁海县' },
+ { code: '330281', name: '余姚市' },
+ { code: '330282', name: '慈溪市' }
+ ]
+ },
+ {
+ code: '3303',
+ name: '温州市',
+ districts: [
+ { code: '330302', name: '鹿城区' },
+ { code: '330303', name: '龙湾区' },
+ { code: '330304', name: '瓯海区' },
+ { code: '330305', name: '洞头区' },
+ { code: '330324', name: '永嘉县' },
+ { code: '330326', name: '平阳县' },
+ { code: '330327', name: '苍南县' },
+ { code: '330328', name: '文成县' },
+ { code: '330329', name: '泰顺县' },
+ { code: '330381', name: '瑞安市' },
+ { code: '330382', name: '乐清市' },
+ { code: '330383', name: '龙港市' }
+ ]
+ },
+ {
+ code: '3304',
+ name: '嘉兴市',
+ districts: [
+ { code: '330402', name: '南湖区' },
+ { code: '330411', name: '秀洲区' },
+ { code: '330421', name: '嘉善县' },
+ { code: '330424', name: '海盐县' },
+ { code: '330481', name: '海宁市' },
+ { code: '330482', name: '平湖市' },
+ { code: '330483', name: '桐乡市' }
+ ]
+ },
+ {
+ code: '3305',
+ name: '湖州市',
+ districts: [
+ { code: '330502', name: '吴兴区' },
+ { code: '330503', name: '南浔区' },
+ { code: '330521', name: '德清县' },
+ { code: '330522', name: '长兴县' },
+ { code: '330523', name: '安吉县' }
+ ]
+ },
+ {
+ code: '3306',
+ name: '绍兴市',
+ districts: [
+ { code: '330602', name: '越城区' },
+ { code: '330603', name: '柯桥区' },
+ { code: '330604', name: '上虞区' },
+ { code: '330624', name: '新昌县' },
+ { code: '330681', name: '诸暨市' },
+ { code: '330683', name: '嵊州市' }
+ ]
+ },
+ {
+ code: '3307',
+ name: '金华市',
+ districts: [
+ { code: '330702', name: '婺城区' },
+ { code: '330703', name: '金东区' },
+ { code: '330723', name: '武义县' },
+ { code: '330726', name: '浦江县' },
+ { code: '330727', name: '磐安县' },
+ { code: '330781', name: '兰溪市' },
+ { code: '330782', name: '义乌市' },
+ { code: '330783', name: '东阳市' },
+ { code: '330784', name: '永康市' }
+ ]
+ },
+ {
+ code: '3308',
+ name: '衢州市',
+ districts: [
+ { code: '330802', name: '柯城区' },
+ { code: '330803', name: '衢江区' },
+ { code: '330822', name: '常山县' },
+ { code: '330824', name: '开化县' },
+ { code: '330825', name: '龙游县' },
+ { code: '330881', name: '江山市' }
+ ]
+ },
+ {
+ code: '3309',
+ name: '舟山市',
+ districts: [
+ { code: '330902', name: '定海区' },
+ { code: '330903', name: '普陀区' },
+ { code: '330921', name: '岱山县' },
+ { code: '330922', name: '嵊泗县' }
+ ]
+ },
+ {
+ code: '3310',
+ name: '台州市',
+ districts: [
+ { code: '331002', name: '椒江区' },
+ { code: '331003', name: '黄岩区' },
+ { code: '331004', name: '路桥区' },
+ { code: '331022', name: '三门县' },
+ { code: '331023', name: '天台县' },
+ { code: '331024', name: '仙居县' },
+ { code: '331081', name: '温岭市' },
+ { code: '331082', name: '临海市' },
+ { code: '331083', name: '玉环市' }
+ ]
+ },
+ {
+ code: '3311',
+ name: '丽水市',
+ districts: [
+ { code: '331102', name: '莲都区' },
+ { code: '331121', name: '青田县' },
+ { code: '331122', name: '缙云县' },
+ { code: '331123', name: '遂昌县' },
+ { code: '331124', name: '松阳县' },
+ { code: '331125', name: '云和县' },
+ { code: '331126', name: '庆元县' },
+ { code: '331127', name: '景宁畲族自治县' },
+ { code: '331181', name: '龙泉市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '34',
+ name: '安徽省',
+ cities: [
+ {
+ code: '3401',
+ name: '合肥市',
+ districts: [
+ { code: '340102', name: '瑶海区' },
+ { code: '340103', name: '庐阳区' },
+ { code: '340104', name: '蜀山区' },
+ { code: '340111', name: '包河区' },
+ { code: '340121', name: '长丰县' },
+ { code: '340122', name: '肥东县' },
+ { code: '340123', name: '肥西县' },
+ { code: '340124', name: '庐江县' },
+ { code: '340176', name: '合肥高新技术产业开发区' },
+ { code: '340177', name: '合肥经济技术开发区' },
+ { code: '340178', name: '合肥新站高新技术产业开发区' },
+ { code: '340181', name: '巢湖市' }
+ ]
+ },
+ {
+ code: '3402',
+ name: '芜湖市',
+ districts: [
+ { code: '340202', name: '镜湖区' },
+ { code: '340207', name: '鸠江区' },
+ { code: '340209', name: '弋江区' },
+ { code: '340210', name: '湾沚区' },
+ { code: '340212', name: '繁昌区' },
+ { code: '340223', name: '南陵县' },
+ { code: '340271', name: '芜湖经济技术开发区' },
+ { code: '340272', name: '安徽芜湖三山经济开发区' },
+ { code: '340281', name: '无为市' }
+ ]
+ },
+ {
+ code: '3403',
+ name: '蚌埠市',
+ districts: [
+ { code: '340302', name: '龙子湖区' },
+ { code: '340303', name: '蚌山区' },
+ { code: '340304', name: '禹会区' },
+ { code: '340311', name: '淮上区' },
+ { code: '340321', name: '怀远县' },
+ { code: '340322', name: '五河县' },
+ { code: '340323', name: '固镇县' },
+ { code: '340371', name: '蚌埠市高新技术开发区' },
+ { code: '340372', name: '蚌埠市经济开发区' }
+ ]
+ },
+ {
+ code: '3404',
+ name: '淮南市',
+ districts: [
+ { code: '340402', name: '大通区' },
+ { code: '340403', name: '田家庵区' },
+ { code: '340404', name: '谢家集区' },
+ { code: '340405', name: '八公山区' },
+ { code: '340406', name: '潘集区' },
+ { code: '340421', name: '凤台县' },
+ { code: '340422', name: '寿县' }
+ ]
+ },
+ {
+ code: '3405',
+ name: '马鞍山市',
+ districts: [
+ { code: '340503', name: '花山区' },
+ { code: '340504', name: '雨山区' },
+ { code: '340506', name: '博望区' },
+ { code: '340521', name: '当涂县' },
+ { code: '340522', name: '含山县' },
+ { code: '340523', name: '和县' }
+ ]
+ },
+ {
+ code: '3406',
+ name: '淮北市',
+ districts: [
+ { code: '340602', name: '杜集区' },
+ { code: '340603', name: '相山区' },
+ { code: '340604', name: '烈山区' },
+ { code: '340621', name: '濉溪县' }
+ ]
+ },
+ {
+ code: '3407',
+ name: '铜陵市',
+ districts: [
+ { code: '340705', name: '铜官区' },
+ { code: '340706', name: '义安区' },
+ { code: '340711', name: '郊区' },
+ { code: '340722', name: '枞阳县' }
+ ]
+ },
+ {
+ code: '3408',
+ name: '安庆市',
+ districts: [
+ { code: '340802', name: '迎江区' },
+ { code: '340803', name: '大观区' },
+ { code: '340811', name: '宜秀区' },
+ { code: '340822', name: '怀宁县' },
+ { code: '340825', name: '太湖县' },
+ { code: '340826', name: '宿松县' },
+ { code: '340827', name: '望江县' },
+ { code: '340828', name: '岳西县' },
+ { code: '340871', name: '安徽安庆经济开发区' },
+ { code: '340881', name: '桐城市' },
+ { code: '340882', name: '潜山市' }
+ ]
+ },
+ {
+ code: '3410',
+ name: '黄山市',
+ districts: [
+ { code: '341002', name: '屯溪区' },
+ { code: '341003', name: '黄山区' },
+ { code: '341004', name: '徽州区' },
+ { code: '341021', name: '歙县' },
+ { code: '341022', name: '休宁县' },
+ { code: '341023', name: '黟县' },
+ { code: '341024', name: '祁门县' }
+ ]
+ },
+ {
+ code: '3411',
+ name: '滁州市',
+ districts: [
+ { code: '341102', name: '琅琊区' },
+ { code: '341103', name: '南谯区' },
+ { code: '341122', name: '来安县' },
+ { code: '341124', name: '全椒县' },
+ { code: '341125', name: '定远县' },
+ { code: '341126', name: '凤阳县' },
+ { code: '341171', name: '中新苏滁高新技术产业开发区' },
+ { code: '341172', name: '滁州经济技术开发区' },
+ { code: '341181', name: '天长市' },
+ { code: '341182', name: '明光市' }
+ ]
+ },
+ {
+ code: '3412',
+ name: '阜阳市',
+ districts: [
+ { code: '341202', name: '颍州区' },
+ { code: '341203', name: '颍东区' },
+ { code: '341204', name: '颍泉区' },
+ { code: '341221', name: '临泉县' },
+ { code: '341222', name: '太和县' },
+ { code: '341225', name: '阜南县' },
+ { code: '341226', name: '颍上县' },
+ { code: '341271', name: '阜阳合肥现代产业园区' },
+ { code: '341272', name: '阜阳经济技术开发区' },
+ { code: '341282', name: '界首市' }
+ ]
+ },
+ {
+ code: '3413',
+ name: '宿州市',
+ districts: [
+ { code: '341302', name: '埇桥区' },
+ { code: '341321', name: '砀山县' },
+ { code: '341322', name: '萧县' },
+ { code: '341323', name: '灵璧县' },
+ { code: '341324', name: '泗县' },
+ { code: '341371', name: '宿州马鞍山现代产业园区' },
+ { code: '341372', name: '宿州经济技术开发区' }
+ ]
+ },
+ {
+ code: '3415',
+ name: '六安市',
+ districts: [
+ { code: '341502', name: '金安区' },
+ { code: '341503', name: '裕安区' },
+ { code: '341504', name: '叶集区' },
+ { code: '341522', name: '霍邱县' },
+ { code: '341523', name: '舒城县' },
+ { code: '341524', name: '金寨县' },
+ { code: '341525', name: '霍山县' }
+ ]
+ },
+ {
+ code: '3416',
+ name: '亳州市',
+ districts: [
+ { code: '341602', name: '谯城区' },
+ { code: '341621', name: '涡阳县' },
+ { code: '341622', name: '蒙城县' },
+ { code: '341623', name: '利辛县' }
+ ]
+ },
+ {
+ code: '3417',
+ name: '池州市',
+ districts: [
+ { code: '341702', name: '贵池区' },
+ { code: '341721', name: '东至县' },
+ { code: '341722', name: '石台县' },
+ { code: '341723', name: '青阳县' }
+ ]
+ },
+ {
+ code: '3418',
+ name: '宣城市',
+ districts: [
+ { code: '341802', name: '宣州区' },
+ { code: '341821', name: '郎溪县' },
+ { code: '341823', name: '泾县' },
+ { code: '341824', name: '绩溪县' },
+ { code: '341825', name: '旌德县' },
+ { code: '341871', name: '宣城市经济开发区' },
+ { code: '341881', name: '宁国市' },
+ { code: '341882', name: '广德市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '35',
+ name: '福建省',
+ cities: [
+ {
+ code: '3501',
+ name: '福州市',
+ districts: [
+ { code: '350102', name: '鼓楼区' },
+ { code: '350103', name: '台江区' },
+ { code: '350104', name: '仓山区' },
+ { code: '350105', name: '马尾区' },
+ { code: '350111', name: '晋安区' },
+ { code: '350112', name: '长乐区' },
+ { code: '350121', name: '闽侯县' },
+ { code: '350122', name: '连江县' },
+ { code: '350123', name: '罗源县' },
+ { code: '350124', name: '闽清县' },
+ { code: '350125', name: '永泰县' },
+ { code: '350128', name: '平潭县' },
+ { code: '350181', name: '福清市' }
+ ]
+ },
+ {
+ code: '3502',
+ name: '厦门市',
+ districts: [
+ { code: '350203', name: '思明区' },
+ { code: '350205', name: '海沧区' },
+ { code: '350206', name: '湖里区' },
+ { code: '350211', name: '集美区' },
+ { code: '350212', name: '同安区' },
+ { code: '350213', name: '翔安区' }
+ ]
+ },
+ {
+ code: '3503',
+ name: '莆田市',
+ districts: [
+ { code: '350302', name: '城厢区' },
+ { code: '350303', name: '涵江区' },
+ { code: '350304', name: '荔城区' },
+ { code: '350305', name: '秀屿区' },
+ { code: '350322', name: '仙游县' }
+ ]
+ },
+ {
+ code: '3504',
+ name: '三明市',
+ districts: [
+ { code: '350404', name: '三元区' },
+ { code: '350405', name: '沙县区' },
+ { code: '350421', name: '明溪县' },
+ { code: '350423', name: '清流县' },
+ { code: '350424', name: '宁化县' },
+ { code: '350425', name: '大田县' },
+ { code: '350426', name: '尤溪县' },
+ { code: '350428', name: '将乐县' },
+ { code: '350429', name: '泰宁县' },
+ { code: '350430', name: '建宁县' },
+ { code: '350481', name: '永安市' }
+ ]
+ },
+ {
+ code: '3505',
+ name: '泉州市',
+ districts: [
+ { code: '350502', name: '鲤城区' },
+ { code: '350503', name: '丰泽区' },
+ { code: '350504', name: '洛江区' },
+ { code: '350505', name: '泉港区' },
+ { code: '350521', name: '惠安县' },
+ { code: '350524', name: '安溪县' },
+ { code: '350525', name: '永春县' },
+ { code: '350526', name: '德化县' },
+ { code: '350527', name: '金门县' },
+ { code: '350581', name: '石狮市' },
+ { code: '350582', name: '晋江市' },
+ { code: '350583', name: '南安市' }
+ ]
+ },
+ {
+ code: '3506',
+ name: '漳州市',
+ districts: [
+ { code: '350602', name: '芗城区' },
+ { code: '350603', name: '龙文区' },
+ { code: '350604', name: '龙海区' },
+ { code: '350605', name: '长泰区' },
+ { code: '350622', name: '云霄县' },
+ { code: '350623', name: '漳浦县' },
+ { code: '350624', name: '诏安县' },
+ { code: '350626', name: '东山县' },
+ { code: '350627', name: '南靖县' },
+ { code: '350628', name: '平和县' },
+ { code: '350629', name: '华安县' }
+ ]
+ },
+ {
+ code: '3507',
+ name: '南平市',
+ districts: [
+ { code: '350702', name: '延平区' },
+ { code: '350703', name: '建阳区' },
+ { code: '350721', name: '顺昌县' },
+ { code: '350722', name: '浦城县' },
+ { code: '350723', name: '光泽县' },
+ { code: '350724', name: '松溪县' },
+ { code: '350725', name: '政和县' },
+ { code: '350781', name: '邵武市' },
+ { code: '350782', name: '武夷山市' },
+ { code: '350783', name: '建瓯市' }
+ ]
+ },
+ {
+ code: '3508',
+ name: '龙岩市',
+ districts: [
+ { code: '350802', name: '新罗区' },
+ { code: '350803', name: '永定区' },
+ { code: '350821', name: '长汀县' },
+ { code: '350823', name: '上杭县' },
+ { code: '350824', name: '武平县' },
+ { code: '350825', name: '连城县' },
+ { code: '350881', name: '漳平市' }
+ ]
+ },
+ {
+ code: '3509',
+ name: '宁德市',
+ districts: [
+ { code: '350902', name: '蕉城区' },
+ { code: '350921', name: '霞浦县' },
+ { code: '350922', name: '古田县' },
+ { code: '350923', name: '屏南县' },
+ { code: '350924', name: '寿宁县' },
+ { code: '350925', name: '周宁县' },
+ { code: '350926', name: '柘荣县' },
+ { code: '350981', name: '福安市' },
+ { code: '350982', name: '福鼎市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '36',
+ name: '江西省',
+ cities: [
+ {
+ code: '3601',
+ name: '南昌市',
+ districts: [
+ { code: '360102', name: '东湖区' },
+ { code: '360103', name: '西湖区' },
+ { code: '360104', name: '青云谱区' },
+ { code: '360111', name: '青山湖区' },
+ { code: '360112', name: '新建区' },
+ { code: '360113', name: '红谷滩区' },
+ { code: '360121', name: '南昌县' },
+ { code: '360123', name: '安义县' },
+ { code: '360124', name: '进贤县' }
+ ]
+ },
+ {
+ code: '3602',
+ name: '景德镇市',
+ districts: [
+ { code: '360202', name: '昌江区' },
+ { code: '360203', name: '珠山区' },
+ { code: '360222', name: '浮梁县' },
+ { code: '360281', name: '乐平市' }
+ ]
+ },
+ {
+ code: '3603',
+ name: '萍乡市',
+ districts: [
+ { code: '360302', name: '安源区' },
+ { code: '360313', name: '湘东区' },
+ { code: '360321', name: '莲花县' },
+ { code: '360322', name: '上栗县' },
+ { code: '360323', name: '芦溪县' }
+ ]
+ },
+ {
+ code: '3604',
+ name: '九江市',
+ districts: [
+ { code: '360402', name: '濂溪区' },
+ { code: '360403', name: '浔阳区' },
+ { code: '360404', name: '柴桑区' },
+ { code: '360423', name: '武宁县' },
+ { code: '360424', name: '修水县' },
+ { code: '360425', name: '永修县' },
+ { code: '360426', name: '德安县' },
+ { code: '360428', name: '都昌县' },
+ { code: '360429', name: '湖口县' },
+ { code: '360430', name: '彭泽县' },
+ { code: '360481', name: '瑞昌市' },
+ { code: '360482', name: '共青城市' },
+ { code: '360483', name: '庐山市' }
+ ]
+ },
+ {
+ code: '3605',
+ name: '新余市',
+ districts: [
+ { code: '360502', name: '渝水区' },
+ { code: '360521', name: '分宜县' }
+ ]
+ },
+ {
+ code: '3606',
+ name: '鹰潭市',
+ districts: [
+ { code: '360602', name: '月湖区' },
+ { code: '360603', name: '余江区' },
+ { code: '360681', name: '贵溪市' }
+ ]
+ },
+ {
+ code: '3607',
+ name: '赣州市',
+ districts: [
+ { code: '360702', name: '章贡区' },
+ { code: '360703', name: '南康区' },
+ { code: '360704', name: '赣县区' },
+ { code: '360722', name: '信丰县' },
+ { code: '360723', name: '大余县' },
+ { code: '360724', name: '上犹县' },
+ { code: '360725', name: '崇义县' },
+ { code: '360726', name: '安远县' },
+ { code: '360728', name: '定南县' },
+ { code: '360729', name: '全南县' },
+ { code: '360730', name: '宁都县' },
+ { code: '360731', name: '于都县' },
+ { code: '360732', name: '兴国县' },
+ { code: '360733', name: '会昌县' },
+ { code: '360734', name: '寻乌县' },
+ { code: '360735', name: '石城县' },
+ { code: '360781', name: '瑞金市' },
+ { code: '360783', name: '龙南市' }
+ ]
+ },
+ {
+ code: '3608',
+ name: '吉安市',
+ districts: [
+ { code: '360802', name: '吉州区' },
+ { code: '360803', name: '青原区' },
+ { code: '360821', name: '吉安县' },
+ { code: '360822', name: '吉水县' },
+ { code: '360823', name: '峡江县' },
+ { code: '360824', name: '新干县' },
+ { code: '360825', name: '永丰县' },
+ { code: '360826', name: '泰和县' },
+ { code: '360827', name: '遂川县' },
+ { code: '360828', name: '万安县' },
+ { code: '360829', name: '安福县' },
+ { code: '360830', name: '永新县' },
+ { code: '360881', name: '井冈山市' }
+ ]
+ },
+ {
+ code: '3609',
+ name: '宜春市',
+ districts: [
+ { code: '360902', name: '袁州区' },
+ { code: '360921', name: '奉新县' },
+ { code: '360922', name: '万载县' },
+ { code: '360923', name: '上高县' },
+ { code: '360924', name: '宜丰县' },
+ { code: '360925', name: '靖安县' },
+ { code: '360926', name: '铜鼓县' },
+ { code: '360981', name: '丰城市' },
+ { code: '360982', name: '樟树市' },
+ { code: '360983', name: '高安市' }
+ ]
+ },
+ {
+ code: '3610',
+ name: '抚州市',
+ districts: [
+ { code: '361002', name: '临川区' },
+ { code: '361003', name: '东乡区' },
+ { code: '361021', name: '南城县' },
+ { code: '361022', name: '黎川县' },
+ { code: '361023', name: '南丰县' },
+ { code: '361024', name: '崇仁县' },
+ { code: '361025', name: '乐安县' },
+ { code: '361026', name: '宜黄县' },
+ { code: '361027', name: '金溪县' },
+ { code: '361028', name: '资溪县' },
+ { code: '361030', name: '广昌县' }
+ ]
+ },
+ {
+ code: '3611',
+ name: '上饶市',
+ districts: [
+ { code: '361102', name: '信州区' },
+ { code: '361103', name: '广丰区' },
+ { code: '361104', name: '广信区' },
+ { code: '361123', name: '玉山县' },
+ { code: '361124', name: '铅山县' },
+ { code: '361125', name: '横峰县' },
+ { code: '361126', name: '弋阳县' },
+ { code: '361127', name: '余干县' },
+ { code: '361128', name: '鄱阳县' },
+ { code: '361129', name: '万年县' },
+ { code: '361130', name: '婺源县' },
+ { code: '361181', name: '德兴市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '37',
+ name: '山东省',
+ cities: [
+ {
+ code: '3701',
+ name: '济南市',
+ districts: [
+ { code: '370102', name: '历下区' },
+ { code: '370103', name: '市中区' },
+ { code: '370104', name: '槐荫区' },
+ { code: '370105', name: '天桥区' },
+ { code: '370112', name: '历城区' },
+ { code: '370113', name: '长清区' },
+ { code: '370114', name: '章丘区' },
+ { code: '370115', name: '济阳区' },
+ { code: '370116', name: '莱芜区' },
+ { code: '370117', name: '钢城区' },
+ { code: '370124', name: '平阴县' },
+ { code: '370126', name: '商河县' },
+ { code: '370176', name: '济南高新技术产业开发区' }
+ ]
+ },
+ {
+ code: '3702',
+ name: '青岛市',
+ districts: [
+ { code: '370202', name: '市南区' },
+ { code: '370203', name: '市北区' },
+ { code: '370211', name: '黄岛区' },
+ { code: '370212', name: '崂山区' },
+ { code: '370213', name: '李沧区' },
+ { code: '370214', name: '城阳区' },
+ { code: '370215', name: '即墨区' },
+ { code: '370281', name: '胶州市' },
+ { code: '370283', name: '平度市' },
+ { code: '370285', name: '莱西市' }
+ ]
+ },
+ {
+ code: '3703',
+ name: '淄博市',
+ districts: [
+ { code: '370302', name: '淄川区' },
+ { code: '370303', name: '张店区' },
+ { code: '370304', name: '博山区' },
+ { code: '370305', name: '临淄区' },
+ { code: '370306', name: '周村区' },
+ { code: '370321', name: '桓台县' },
+ { code: '370322', name: '高青县' },
+ { code: '370323', name: '沂源县' }
+ ]
+ },
+ {
+ code: '3704',
+ name: '枣庄市',
+ districts: [
+ { code: '370402', name: '市中区' },
+ { code: '370403', name: '薛城区' },
+ { code: '370404', name: '峄城区' },
+ { code: '370405', name: '台儿庄区' },
+ { code: '370406', name: '山亭区' },
+ { code: '370481', name: '滕州市' }
+ ]
+ },
+ {
+ code: '3705',
+ name: '东营市',
+ districts: [
+ { code: '370502', name: '东营区' },
+ { code: '370503', name: '河口区' },
+ { code: '370505', name: '垦利区' },
+ { code: '370522', name: '利津县' },
+ { code: '370523', name: '广饶县' },
+ { code: '370571', name: '东营经济技术开发区' },
+ { code: '370572', name: '东营港经济开发区' }
+ ]
+ },
+ {
+ code: '3706',
+ name: '烟台市',
+ districts: [
+ { code: '370602', name: '芝罘区' },
+ { code: '370611', name: '福山区' },
+ { code: '370612', name: '牟平区' },
+ { code: '370613', name: '莱山区' },
+ { code: '370614', name: '蓬莱区' },
+ { code: '370671', name: '烟台高新技术产业开发区' },
+ { code: '370676', name: '烟台经济技术开发区' },
+ { code: '370681', name: '龙口市' },
+ { code: '370682', name: '莱阳市' },
+ { code: '370683', name: '莱州市' },
+ { code: '370685', name: '招远市' },
+ { code: '370686', name: '栖霞市' },
+ { code: '370687', name: '海阳市' }
+ ]
+ },
+ {
+ code: '3707',
+ name: '潍坊市',
+ districts: [
+ { code: '370702', name: '潍城区' },
+ { code: '370703', name: '寒亭区' },
+ { code: '370704', name: '坊子区' },
+ { code: '370705', name: '奎文区' },
+ { code: '370724', name: '临朐县' },
+ { code: '370725', name: '昌乐县' },
+ { code: '370772', name: '潍坊滨海经济技术开发区' },
+ { code: '370781', name: '青州市' },
+ { code: '370782', name: '诸城市' },
+ { code: '370783', name: '寿光市' },
+ { code: '370784', name: '安丘市' },
+ { code: '370785', name: '高密市' },
+ { code: '370786', name: '昌邑市' }
+ ]
+ },
+ {
+ code: '3708',
+ name: '济宁市',
+ districts: [
+ { code: '370811', name: '任城区' },
+ { code: '370812', name: '兖州区' },
+ { code: '370826', name: '微山县' },
+ { code: '370827', name: '鱼台县' },
+ { code: '370828', name: '金乡县' },
+ { code: '370829', name: '嘉祥县' },
+ { code: '370830', name: '汶上县' },
+ { code: '370831', name: '泗水县' },
+ { code: '370832', name: '梁山县' },
+ { code: '370871', name: '济宁高新技术产业开发区' },
+ { code: '370881', name: '曲阜市' },
+ { code: '370883', name: '邹城市' }
+ ]
+ },
+ {
+ code: '3709',
+ name: '泰安市',
+ districts: [
+ { code: '370902', name: '泰山区' },
+ { code: '370911', name: '岱岳区' },
+ { code: '370921', name: '宁阳县' },
+ { code: '370923', name: '东平县' },
+ { code: '370982', name: '新泰市' },
+ { code: '370983', name: '肥城市' }
+ ]
+ },
+ {
+ code: '3710',
+ name: '威海市',
+ districts: [
+ { code: '371002', name: '环翠区' },
+ { code: '371003', name: '文登区' },
+ { code: '371071', name: '威海火炬高技术产业开发区' },
+ { code: '371072', name: '威海经济技术开发区' },
+ { code: '371073', name: '威海临港经济技术开发区' },
+ { code: '371082', name: '荣成市' },
+ { code: '371083', name: '乳山市' }
+ ]
+ },
+ {
+ code: '3711',
+ name: '日照市',
+ districts: [
+ { code: '371102', name: '东港区' },
+ { code: '371103', name: '岚山区' },
+ { code: '371121', name: '五莲县' },
+ { code: '371122', name: '莒县' },
+ { code: '371171', name: '日照经济技术开发区' }
+ ]
+ },
+ {
+ code: '3713',
+ name: '临沂市',
+ districts: [
+ { code: '371302', name: '兰山区' },
+ { code: '371311', name: '罗庄区' },
+ { code: '371312', name: '河东区' },
+ { code: '371321', name: '沂南县' },
+ { code: '371322', name: '郯城县' },
+ { code: '371323', name: '沂水县' },
+ { code: '371324', name: '兰陵县' },
+ { code: '371325', name: '费县' },
+ { code: '371326', name: '平邑县' },
+ { code: '371327', name: '莒南县' },
+ { code: '371328', name: '蒙阴县' },
+ { code: '371329', name: '临沭县' },
+ { code: '371371', name: '临沂高新技术产业开发区' }
+ ]
+ },
+ {
+ code: '3714',
+ name: '德州市',
+ districts: [
+ { code: '371402', name: '德城区' },
+ { code: '371403', name: '陵城区' },
+ { code: '371422', name: '宁津县' },
+ { code: '371423', name: '庆云县' },
+ { code: '371424', name: '临邑县' },
+ { code: '371425', name: '齐河县' },
+ { code: '371426', name: '平原县' },
+ { code: '371427', name: '夏津县' },
+ { code: '371428', name: '武城县' },
+ { code: '371471', name: '德州天衢新区' },
+ { code: '371481', name: '乐陵市' },
+ { code: '371482', name: '禹城市' }
+ ]
+ },
+ {
+ code: '3715',
+ name: '聊城市',
+ districts: [
+ { code: '371502', name: '东昌府区' },
+ { code: '371503', name: '茌平区' },
+ { code: '371521', name: '阳谷县' },
+ { code: '371522', name: '莘县' },
+ { code: '371524', name: '东阿县' },
+ { code: '371525', name: '冠县' },
+ { code: '371526', name: '高唐县' },
+ { code: '371581', name: '临清市' }
+ ]
+ },
+ {
+ code: '3716',
+ name: '滨州市',
+ districts: [
+ { code: '371602', name: '滨城区' },
+ { code: '371603', name: '沾化区' },
+ { code: '371621', name: '惠民县' },
+ { code: '371622', name: '阳信县' },
+ { code: '371623', name: '无棣县' },
+ { code: '371625', name: '博兴县' },
+ { code: '371681', name: '邹平市' }
+ ]
+ },
+ {
+ code: '3717',
+ name: '菏泽市',
+ districts: [
+ { code: '371702', name: '牡丹区' },
+ { code: '371703', name: '定陶区' },
+ { code: '371721', name: '曹县' },
+ { code: '371722', name: '单县' },
+ { code: '371723', name: '成武县' },
+ { code: '371724', name: '巨野县' },
+ { code: '371725', name: '郓城县' },
+ { code: '371726', name: '鄄城县' },
+ { code: '371728', name: '东明县' },
+ { code: '371771', name: '菏泽经济技术开发区' },
+ { code: '371772', name: '菏泽高新技术开发区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '41',
+ name: '河南省',
+ cities: [
+ {
+ code: '4101',
+ name: '郑州市',
+ districts: [
+ { code: '410102', name: '中原区' },
+ { code: '410103', name: '二七区' },
+ { code: '410104', name: '管城回族区' },
+ { code: '410105', name: '金水区' },
+ { code: '410106', name: '上街区' },
+ { code: '410108', name: '惠济区' },
+ { code: '410122', name: '中牟县' },
+ { code: '410171', name: '郑州经济技术开发区' },
+ { code: '410172', name: '郑州高新技术产业开发区' },
+ { code: '410173', name: '郑州航空港经济综合实验区' },
+ { code: '410181', name: '巩义市' },
+ { code: '410182', name: '荥阳市' },
+ { code: '410183', name: '新密市' },
+ { code: '410184', name: '新郑市' },
+ { code: '410185', name: '登封市' }
+ ]
+ },
+ {
+ code: '4102',
+ name: '开封市',
+ districts: [
+ { code: '410202', name: '龙亭区' },
+ { code: '410203', name: '顺河回族区' },
+ { code: '410204', name: '鼓楼区' },
+ { code: '410205', name: '禹王台区' },
+ { code: '410212', name: '祥符区' },
+ { code: '410221', name: '杞县' },
+ { code: '410222', name: '通许县' },
+ { code: '410223', name: '尉氏县' },
+ { code: '410225', name: '兰考县' }
+ ]
+ },
+ {
+ code: '4103',
+ name: '洛阳市',
+ districts: [
+ { code: '410302', name: '老城区' },
+ { code: '410303', name: '西工区' },
+ { code: '410304', name: '瀍河回族区' },
+ { code: '410305', name: '涧西区' },
+ { code: '410307', name: '偃师区' },
+ { code: '410308', name: '孟津区' },
+ { code: '410311', name: '洛龙区' },
+ { code: '410323', name: '新安县' },
+ { code: '410324', name: '栾川县' },
+ { code: '410325', name: '嵩县' },
+ { code: '410326', name: '汝阳县' },
+ { code: '410327', name: '宜阳县' },
+ { code: '410328', name: '洛宁县' },
+ { code: '410329', name: '伊川县' },
+ { code: '410371', name: '洛阳高新技术产业开发区' }
+ ]
+ },
+ {
+ code: '4104',
+ name: '平顶山市',
+ districts: [
+ { code: '410402', name: '新华区' },
+ { code: '410403', name: '卫东区' },
+ { code: '410404', name: '石龙区' },
+ { code: '410411', name: '湛河区' },
+ { code: '410421', name: '宝丰县' },
+ { code: '410422', name: '叶县' },
+ { code: '410423', name: '鲁山县' },
+ { code: '410425', name: '郏县' },
+ { code: '410471', name: '平顶山高新技术产业开发区' },
+ { code: '410472', name: '平顶山市城乡一体化示范区' },
+ { code: '410481', name: '舞钢市' },
+ { code: '410482', name: '汝州市' }
+ ]
+ },
+ {
+ code: '4105',
+ name: '安阳市',
+ districts: [
+ { code: '410502', name: '文峰区' },
+ { code: '410503', name: '北关区' },
+ { code: '410505', name: '殷都区' },
+ { code: '410506', name: '龙安区' },
+ { code: '410522', name: '安阳县' },
+ { code: '410523', name: '汤阴县' },
+ { code: '410526', name: '滑县' },
+ { code: '410527', name: '内黄县' },
+ { code: '410571', name: '安阳高新技术产业开发区' },
+ { code: '410581', name: '林州市' }
+ ]
+ },
+ {
+ code: '4106',
+ name: '鹤壁市',
+ districts: [
+ { code: '410602', name: '鹤山区' },
+ { code: '410603', name: '山城区' },
+ { code: '410611', name: '淇滨区' },
+ { code: '410621', name: '浚县' },
+ { code: '410622', name: '淇县' },
+ { code: '410671', name: '鹤壁经济技术开发区' }
+ ]
+ },
+ {
+ code: '4107',
+ name: '新乡市',
+ districts: [
+ { code: '410702', name: '红旗区' },
+ { code: '410703', name: '卫滨区' },
+ { code: '410704', name: '凤泉区' },
+ { code: '410711', name: '牧野区' },
+ { code: '410721', name: '新乡县' },
+ { code: '410724', name: '获嘉县' },
+ { code: '410725', name: '原阳县' },
+ { code: '410726', name: '延津县' },
+ { code: '410727', name: '封丘县' },
+ { code: '410771', name: '新乡高新技术产业开发区' },
+ { code: '410772', name: '新乡经济技术开发区' },
+ { code: '410773', name: '新乡市平原城乡一体化示范区' },
+ { code: '410781', name: '卫辉市' },
+ { code: '410782', name: '辉县市' },
+ { code: '410783', name: '长垣市' }
+ ]
+ },
+ {
+ code: '4108',
+ name: '焦作市',
+ districts: [
+ { code: '410802', name: '解放区' },
+ { code: '410803', name: '中站区' },
+ { code: '410804', name: '马村区' },
+ { code: '410811', name: '山阳区' },
+ { code: '410821', name: '修武县' },
+ { code: '410822', name: '博爱县' },
+ { code: '410823', name: '武陟县' },
+ { code: '410825', name: '温县' },
+ { code: '410871', name: '焦作城乡一体化示范区' },
+ { code: '410882', name: '沁阳市' },
+ { code: '410883', name: '孟州市' }
+ ]
+ },
+ {
+ code: '4109',
+ name: '濮阳市',
+ districts: [
+ { code: '410902', name: '华龙区' },
+ { code: '410922', name: '清丰县' },
+ { code: '410923', name: '南乐县' },
+ { code: '410926', name: '范县' },
+ { code: '410927', name: '台前县' },
+ { code: '410928', name: '濮阳县' },
+ { code: '410971', name: '河南濮阳工业园区' },
+ { code: '410972', name: '濮阳经济技术开发区' }
+ ]
+ },
+ {
+ code: '4110',
+ name: '许昌市',
+ districts: [
+ { code: '411002', name: '魏都区' },
+ { code: '411003', name: '建安区' },
+ { code: '411024', name: '鄢陵县' },
+ { code: '411025', name: '襄城县' },
+ { code: '411071', name: '许昌经济技术开发区' },
+ { code: '411081', name: '禹州市' },
+ { code: '411082', name: '长葛市' }
+ ]
+ },
+ {
+ code: '4111',
+ name: '漯河市',
+ districts: [
+ { code: '411102', name: '源汇区' },
+ { code: '411103', name: '郾城区' },
+ { code: '411104', name: '召陵区' },
+ { code: '411121', name: '舞阳县' },
+ { code: '411122', name: '临颍县' },
+ { code: '411171', name: '漯河经济技术开发区' }
+ ]
+ },
+ {
+ code: '4112',
+ name: '三门峡市',
+ districts: [
+ { code: '411202', name: '湖滨区' },
+ { code: '411203', name: '陕州区' },
+ { code: '411221', name: '渑池县' },
+ { code: '411224', name: '卢氏县' },
+ { code: '411271', name: '河南三门峡经济开发区' },
+ { code: '411281', name: '义马市' },
+ { code: '411282', name: '灵宝市' }
+ ]
+ },
+ {
+ code: '4113',
+ name: '南阳市',
+ districts: [
+ { code: '411302', name: '宛城区' },
+ { code: '411303', name: '卧龙区' },
+ { code: '411321', name: '南召县' },
+ { code: '411322', name: '方城县' },
+ { code: '411323', name: '西峡县' },
+ { code: '411324', name: '镇平县' },
+ { code: '411325', name: '内乡县' },
+ { code: '411326', name: '淅川县' },
+ { code: '411327', name: '社旗县' },
+ { code: '411328', name: '唐河县' },
+ { code: '411329', name: '新野县' },
+ { code: '411330', name: '桐柏县' },
+ { code: '411371', name: '南阳高新技术产业开发区' },
+ { code: '411372', name: '南阳市城乡一体化示范区' },
+ { code: '411381', name: '邓州市' }
+ ]
+ },
+ {
+ code: '4114',
+ name: '商丘市',
+ districts: [
+ { code: '411402', name: '梁园区' },
+ { code: '411403', name: '睢阳区' },
+ { code: '411421', name: '民权县' },
+ { code: '411422', name: '睢县' },
+ { code: '411423', name: '宁陵县' },
+ { code: '411424', name: '柘城县' },
+ { code: '411425', name: '虞城县' },
+ { code: '411426', name: '夏邑县' },
+ { code: '411471', name: '豫东综合物流产业聚集区' },
+ { code: '411472', name: '河南商丘经济开发区' },
+ { code: '411481', name: '永城市' }
+ ]
+ },
+ {
+ code: '4115',
+ name: '信阳市',
+ districts: [
+ { code: '411502', name: '浉河区' },
+ { code: '411503', name: '平桥区' },
+ { code: '411521', name: '罗山县' },
+ { code: '411522', name: '光山县' },
+ { code: '411523', name: '新县' },
+ { code: '411524', name: '商城县' },
+ { code: '411525', name: '固始县' },
+ { code: '411526', name: '潢川县' },
+ { code: '411527', name: '淮滨县' },
+ { code: '411528', name: '息县' },
+ { code: '411571', name: '信阳高新技术产业开发区' }
+ ]
+ },
+ {
+ code: '4116',
+ name: '周口市',
+ districts: [
+ { code: '411602', name: '川汇区' },
+ { code: '411603', name: '淮阳区' },
+ { code: '411621', name: '扶沟县' },
+ { code: '411622', name: '西华县' },
+ { code: '411623', name: '商水县' },
+ { code: '411624', name: '沈丘县' },
+ { code: '411625', name: '郸城县' },
+ { code: '411627', name: '太康县' },
+ { code: '411628', name: '鹿邑县' },
+ { code: '411671', name: '周口临港开发区' },
+ { code: '411681', name: '项城市' }
+ ]
+ },
+ {
+ code: '4117',
+ name: '驻马店市',
+ districts: [
+ { code: '411702', name: '驿城区' },
+ { code: '411721', name: '西平县' },
+ { code: '411722', name: '上蔡县' },
+ { code: '411723', name: '平舆县' },
+ { code: '411724', name: '正阳县' },
+ { code: '411725', name: '确山县' },
+ { code: '411726', name: '泌阳县' },
+ { code: '411727', name: '汝南县' },
+ { code: '411728', name: '遂平县' },
+ { code: '411729', name: '新蔡县' },
+ { code: '411771', name: '河南驻马店经济开发区' }
+ ]
+ },
+ {
+ code: '4190',
+ name: '省直辖县级行政区划',
+ districts: [
+ { code: '419001', name: '济源市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '42',
+ name: '湖北省',
+ cities: [
+ {
+ code: '4201',
+ name: '武汉市',
+ districts: [
+ { code: '420102', name: '江岸区' },
+ { code: '420103', name: '江汉区' },
+ { code: '420104', name: '硚口区' },
+ { code: '420105', name: '汉阳区' },
+ { code: '420106', name: '武昌区' },
+ { code: '420107', name: '青山区' },
+ { code: '420111', name: '洪山区' },
+ { code: '420112', name: '东西湖区' },
+ { code: '420113', name: '汉南区' },
+ { code: '420114', name: '蔡甸区' },
+ { code: '420115', name: '江夏区' },
+ { code: '420116', name: '黄陂区' },
+ { code: '420117', name: '新洲区' }
+ ]
+ },
+ {
+ code: '4202',
+ name: '黄石市',
+ districts: [
+ { code: '420202', name: '黄石港区' },
+ { code: '420203', name: '西塞山区' },
+ { code: '420204', name: '下陆区' },
+ { code: '420205', name: '铁山区' },
+ { code: '420222', name: '阳新县' },
+ { code: '420281', name: '大冶市' }
+ ]
+ },
+ {
+ code: '4203',
+ name: '十堰市',
+ districts: [
+ { code: '420302', name: '茅箭区' },
+ { code: '420303', name: '张湾区' },
+ { code: '420304', name: '郧阳区' },
+ { code: '420322', name: '郧西县' },
+ { code: '420323', name: '竹山县' },
+ { code: '420324', name: '竹溪县' },
+ { code: '420325', name: '房县' },
+ { code: '420381', name: '丹江口市' }
+ ]
+ },
+ {
+ code: '4205',
+ name: '宜昌市',
+ districts: [
+ { code: '420502', name: '西陵区' },
+ { code: '420503', name: '伍家岗区' },
+ { code: '420504', name: '点军区' },
+ { code: '420505', name: '猇亭区' },
+ { code: '420506', name: '夷陵区' },
+ { code: '420525', name: '远安县' },
+ { code: '420526', name: '兴山县' },
+ { code: '420527', name: '秭归县' },
+ { code: '420528', name: '长阳土家族自治县' },
+ { code: '420529', name: '五峰土家族自治县' },
+ { code: '420581', name: '宜都市' },
+ { code: '420582', name: '当阳市' },
+ { code: '420583', name: '枝江市' }
+ ]
+ },
+ {
+ code: '4206',
+ name: '襄阳市',
+ districts: [
+ { code: '420602', name: '襄城区' },
+ { code: '420606', name: '樊城区' },
+ { code: '420607', name: '襄州区' },
+ { code: '420624', name: '南漳县' },
+ { code: '420625', name: '谷城县' },
+ { code: '420626', name: '保康县' },
+ { code: '420682', name: '老河口市' },
+ { code: '420683', name: '枣阳市' },
+ { code: '420684', name: '宜城市' }
+ ]
+ },
+ {
+ code: '4207',
+ name: '鄂州市',
+ districts: [
+ { code: '420702', name: '梁子湖区' },
+ { code: '420703', name: '华容区' },
+ { code: '420704', name: '鄂城区' }
+ ]
+ },
+ {
+ code: '4208',
+ name: '荆门市',
+ districts: [
+ { code: '420802', name: '东宝区' },
+ { code: '420804', name: '掇刀区' },
+ { code: '420822', name: '沙洋县' },
+ { code: '420881', name: '钟祥市' },
+ { code: '420882', name: '京山市' }
+ ]
+ },
+ {
+ code: '4209',
+ name: '孝感市',
+ districts: [
+ { code: '420902', name: '孝南区' },
+ { code: '420921', name: '孝昌县' },
+ { code: '420922', name: '大悟县' },
+ { code: '420923', name: '云梦县' },
+ { code: '420981', name: '应城市' },
+ { code: '420982', name: '安陆市' },
+ { code: '420984', name: '汉川市' }
+ ]
+ },
+ {
+ code: '4210',
+ name: '荆州市',
+ districts: [
+ { code: '421002', name: '沙市区' },
+ { code: '421003', name: '荆州区' },
+ { code: '421022', name: '公安县' },
+ { code: '421023', name: '监利县' },
+ { code: '421024', name: '江陵县' },
+ { code: '421081', name: '石首市' },
+ { code: '421083', name: '洪湖市' },
+ { code: '421087', name: '松滋市' }
+ ]
+ },
+ {
+ code: '4211',
+ name: '黄冈市',
+ districts: [
+ { code: '421102', name: '黄州区' },
+ { code: '421121', name: '团风县' },
+ { code: '421122', name: '红安县' },
+ { code: '421123', name: '罗田县' },
+ { code: '421124', name: '英山县' },
+ { code: '421125', name: '浠水县' },
+ { code: '421126', name: '蕲春县' },
+ { code: '421127', name: '黄梅县' },
+ { code: '421181', name: '麻城市' },
+ { code: '421182', name: '武穴市' }
+ ]
+ },
+ {
+ code: '4212',
+ name: '咸宁市',
+ districts: [
+ { code: '421202', name: '咸安区' },
+ { code: '421221', name: '嘉鱼县' },
+ { code: '421222', name: '通城县' },
+ { code: '421223', name: '崇阳县' },
+ { code: '421224', name: '通山县' },
+ { code: '421281', name: '赤壁市' }
+ ]
+ },
+ {
+ code: '4213',
+ name: '随州市',
+ districts: [
+ { code: '421303', name: '曾都区' },
+ { code: '421321', name: '随县' },
+ { code: '421381', name: '广水市' }
+ ]
+ },
+ {
+ code: '4228',
+ name: '恩施土家族苗族自治州',
+ districts: [
+ { code: '422801', name: '恩施市' },
+ { code: '422802', name: '利川市' },
+ { code: '422822', name: '建始县' },
+ { code: '422823', name: '巴东县' },
+ { code: '422825', name: '宣恩县' },
+ { code: '422826', name: '咸丰县' },
+ { code: '422827', name: '来凤县' },
+ { code: '422828', name: '鹤峰县' }
+ ]
+ },
+ {
+ code: '4290',
+ name: '省直辖县级行政区划',
+ districts: [
+ { code: '429004', name: '仙桃市' },
+ { code: '429005', name: '潜江市' },
+ { code: '429006', name: '天门市' },
+ { code: '429021', name: '神农架林区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '43',
+ name: '湖南省',
+ cities: [
+ {
+ code: '4301',
+ name: '长沙市',
+ districts: [
+ { code: '430102', name: '芙蓉区' },
+ { code: '430103', name: '天心区' },
+ { code: '430104', name: '岳麓区' },
+ { code: '430105', name: '开福区' },
+ { code: '430111', name: '雨花区' },
+ { code: '430112', name: '望城区' },
+ { code: '430121', name: '长沙县' },
+ { code: '430181', name: '浏阳市' },
+ { code: '430182', name: '宁乡市' }
+ ]
+ },
+ {
+ code: '4302',
+ name: '株洲市',
+ districts: [
+ { code: '430202', name: '荷塘区' },
+ { code: '430203', name: '芦淞区' },
+ { code: '430204', name: '石峰区' },
+ { code: '430211', name: '天元区' },
+ { code: '430212', name: '渌口区' },
+ { code: '430223', name: '攸县' },
+ { code: '430224', name: '茶陵县' },
+ { code: '430225', name: '炎陵县' },
+ { code: '430281', name: '醴陵市' }
+ ]
+ },
+ {
+ code: '4303',
+ name: '湘潭市',
+ districts: [
+ { code: '430302', name: '雨湖区' },
+ { code: '430304', name: '岳塘区' },
+ { code: '430321', name: '湘潭县' },
+ { code: '430381', name: '湘乡市' },
+ { code: '430382', name: '韶山市' }
+ ]
+ },
+ {
+ code: '4304',
+ name: '衡阳市',
+ districts: [
+ { code: '430405', name: '珠晖区' },
+ { code: '430406', name: '雁峰区' },
+ { code: '430407', name: '石鼓区' },
+ { code: '430408', name: '蒸湘区' },
+ { code: '430412', name: '南岳区' },
+ { code: '430421', name: '衡阳县' },
+ { code: '430422', name: '衡南县' },
+ { code: '430423', name: '衡山县' },
+ { code: '430424', name: '衡东县' },
+ { code: '430426', name: '祁东县' },
+ { code: '430481', name: '耒阳市' },
+ { code: '430482', name: '常宁市' }
+ ]
+ },
+ {
+ code: '4305',
+ name: '邵阳市',
+ districts: [
+ { code: '430502', name: '双清区' },
+ { code: '430503', name: '大祥区' },
+ { code: '430511', name: '北塔区' },
+ { code: '430522', name: '新邵县' },
+ { code: '430523', name: '邵阳县' },
+ { code: '430524', name: '隆回县' },
+ { code: '430525', name: '洞口县' },
+ { code: '430527', name: '绥宁县' },
+ { code: '430528', name: '新宁县' },
+ { code: '430529', name: '城步苗族自治县' },
+ { code: '430581', name: '武冈市' },
+ { code: '430582', name: '邵东市' }
+ ]
+ },
+ {
+ code: '4306',
+ name: '岳阳市',
+ districts: [
+ { code: '430602', name: '岳阳楼区' },
+ { code: '430603', name: '云溪区' },
+ { code: '430611', name: '君山区' },
+ { code: '430621', name: '岳阳县' },
+ { code: '430623', name: '华容县' },
+ { code: '430624', name: '湘阴县' },
+ { code: '430626', name: '平江县' },
+ { code: '430681', name: '汨罗市' },
+ { code: '430682', name: '临湘市' }
+ ]
+ },
+ {
+ code: '4307',
+ name: '常德市',
+ districts: [
+ { code: '430702', name: '武陵区' },
+ { code: '430703', name: '鼎城区' },
+ { code: '430721', name: '安乡县' },
+ { code: '430722', name: '汉寿县' },
+ { code: '430723', name: '澧县' },
+ { code: '430724', name: '临澧县' },
+ { code: '430725', name: '桃源县' },
+ { code: '430726', name: '石门县' },
+ { code: '430781', name: '津市市' }
+ ]
+ },
+ {
+ code: '4308',
+ name: '张家界市',
+ districts: [
+ { code: '430802', name: '永定区' },
+ { code: '430811', name: '武陵源区' },
+ { code: '430821', name: '慈利县' },
+ { code: '430822', name: '桑植县' }
+ ]
+ },
+ {
+ code: '4309',
+ name: '益阳市',
+ districts: [
+ { code: '430902', name: '资阳区' },
+ { code: '430903', name: '赫山区' },
+ { code: '430921', name: '南县' },
+ { code: '430922', name: '桃江县' },
+ { code: '430923', name: '安化县' },
+ { code: '430981', name: '沅江市' }
+ ]
+ },
+ {
+ code: '4310',
+ name: '郴州市',
+ districts: [
+ { code: '431002', name: '北湖区' },
+ { code: '431003', name: '苏仙区' },
+ { code: '431021', name: '桂阳县' },
+ { code: '431022', name: '宜章县' },
+ { code: '431023', name: '永兴县' },
+ { code: '431024', name: '嘉禾县' },
+ { code: '431025', name: '临武县' },
+ { code: '431026', name: '汝城县' },
+ { code: '431027', name: '桂东县' },
+ { code: '431028', name: '安仁县' },
+ { code: '431081', name: '资兴市' }
+ ]
+ },
+ {
+ code: '4311',
+ name: '永州市',
+ districts: [
+ { code: '431102', name: '零陵区' },
+ { code: '431103', name: '冷水滩区' },
+ { code: '431121', name: '祁阳县' },
+ { code: '431122', name: '东安县' },
+ { code: '431123', name: '双牌县' },
+ { code: '431124', name: '道县' },
+ { code: '431125', name: '江永县' },
+ { code: '431126', name: '宁远县' },
+ { code: '431127', name: '蓝山县' },
+ { code: '431128', name: '新田县' },
+ { code: '431129', name: '江华瑶族自治县' }
+ ]
+ },
+ {
+ code: '4312',
+ name: '怀化市',
+ districts: [
+ { code: '431202', name: '鹤城区' },
+ { code: '431221', name: '中方县' },
+ { code: '431222', name: '沅陵县' },
+ { code: '431223', name: '辰溪县' },
+ { code: '431224', name: '溆浦县' },
+ { code: '431225', name: '会同县' },
+ { code: '431226', name: '麻阳苗族自治县' },
+ { code: '431227', name: '新晃侗族自治县' },
+ { code: '431228', name: '芷江侗族自治县' },
+ { code: '431229', name: '靖州苗族侗族自治县' },
+ { code: '431230', name: '通道侗族自治县' },
+ { code: '431281', name: '洪江市' }
+ ]
+ },
+ {
+ code: '4313',
+ name: '娄底市',
+ districts: [
+ { code: '431302', name: '娄星区' },
+ { code: '431321', name: '双峰县' },
+ { code: '431322', name: '新化县' },
+ { code: '431381', name: '冷水江市' },
+ { code: '431382', name: '涟源市' }
+ ]
+ },
+ {
+ code: '4331',
+ name: '湘西土家族苗族自治州',
+ districts: [
+ { code: '433101', name: '吉首市' },
+ { code: '433122', name: '泸溪县' },
+ { code: '433123', name: '凤凰县' },
+ { code: '433124', name: '花垣县' },
+ { code: '433125', name: '保靖县' },
+ { code: '433126', name: '古丈县' },
+ { code: '433127', name: '永顺县' },
+ { code: '433130', name: '龙山县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '44',
+ name: '广东省',
+ cities: [
+ {
+ code: '4401',
+ name: '广州市',
+ districts: [
+ { code: '440103', name: '荔湾区' },
+ { code: '440104', name: '越秀区' },
+ { code: '440105', name: '海珠区' },
+ { code: '440106', name: '天河区' },
+ { code: '440111', name: '白云区' },
+ { code: '440112', name: '黄埔区' },
+ { code: '440113', name: '番禺区' },
+ { code: '440114', name: '花都区' },
+ { code: '440115', name: '南沙区' },
+ { code: '440117', name: '从化区' },
+ { code: '440118', name: '增城区' }
+ ]
+ },
+ {
+ code: '4402',
+ name: '韶关市',
+ districts: [
+ { code: '440203', name: '武江区' },
+ { code: '440204', name: '浈江区' },
+ { code: '440205', name: '曲江区' },
+ { code: '440222', name: '始兴县' },
+ { code: '440224', name: '仁化县' },
+ { code: '440229', name: '翁源县' },
+ { code: '440232', name: '乳源瑶族自治县' },
+ { code: '440233', name: '新丰县' },
+ { code: '440281', name: '乐昌市' },
+ { code: '440282', name: '南雄市' }
+ ]
+ },
+ {
+ code: '4403',
+ name: '深圳市',
+ districts: [
+ { code: '440303', name: '罗湖区' },
+ { code: '440304', name: '福田区' },
+ { code: '440305', name: '南山区' },
+ { code: '440306', name: '宝安区' },
+ { code: '440307', name: '龙岗区' },
+ { code: '440308', name: '盐田区' },
+ { code: '440309', name: '龙华区' },
+ { code: '440310', name: '坪山区' },
+ { code: '440311', name: '光明区' }
+ ]
+ },
+ {
+ code: '4404',
+ name: '珠海市',
+ districts: [
+ { code: '440402', name: '香洲区' },
+ { code: '440403', name: '斗门区' },
+ { code: '440404', name: '金湾区' }
+ ]
+ },
+ {
+ code: '4405',
+ name: '汕头市',
+ districts: [
+ { code: '440507', name: '龙湖区' },
+ { code: '440511', name: '金平区' },
+ { code: '440512', name: '濠江区' },
+ { code: '440513', name: '潮阳区' },
+ { code: '440514', name: '潮南区' },
+ { code: '440515', name: '澄海区' },
+ { code: '440523', name: '南澳县' }
+ ]
+ },
+ {
+ code: '4406',
+ name: '佛山市',
+ districts: [
+ { code: '440604', name: '禅城区' },
+ { code: '440605', name: '南海区' },
+ { code: '440606', name: '顺德区' },
+ { code: '440607', name: '三水区' },
+ { code: '440608', name: '高明区' }
+ ]
+ },
+ {
+ code: '4407',
+ name: '江门市',
+ districts: [
+ { code: '440703', name: '蓬江区' },
+ { code: '440704', name: '江海区' },
+ { code: '440705', name: '新会区' },
+ { code: '440781', name: '台山市' },
+ { code: '440783', name: '开平市' },
+ { code: '440784', name: '鹤山市' },
+ { code: '440785', name: '恩平市' }
+ ]
+ },
+ {
+ code: '4408',
+ name: '湛江市',
+ districts: [
+ { code: '440802', name: '赤坎区' },
+ { code: '440803', name: '霞山区' },
+ { code: '440804', name: '坡头区' },
+ { code: '440811', name: '麻章区' },
+ { code: '440823', name: '遂溪县' },
+ { code: '440825', name: '徐闻县' },
+ { code: '440881', name: '廉江市' },
+ { code: '440882', name: '雷州市' },
+ { code: '440883', name: '吴川市' }
+ ]
+ },
+ {
+ code: '4409',
+ name: '茂名市',
+ districts: [
+ { code: '440902', name: '茂南区' },
+ { code: '440904', name: '电白区' },
+ { code: '440981', name: '高州市' },
+ { code: '440982', name: '化州市' },
+ { code: '440983', name: '信宜市' }
+ ]
+ },
+ {
+ code: '4412',
+ name: '肇庆市',
+ districts: [
+ { code: '441202', name: '端州区' },
+ { code: '441203', name: '鼎湖区' },
+ { code: '441204', name: '高要区' },
+ { code: '441223', name: '广宁县' },
+ { code: '441224', name: '怀集县' },
+ { code: '441225', name: '封开县' },
+ { code: '441226', name: '德庆县' },
+ { code: '441284', name: '四会市' }
+ ]
+ },
+ {
+ code: '4413',
+ name: '惠州市',
+ districts: [
+ { code: '441302', name: '惠城区' },
+ { code: '441303', name: '惠阳区' },
+ { code: '441322', name: '博罗县' },
+ { code: '441323', name: '惠东县' },
+ { code: '441324', name: '龙门县' }
+ ]
+ },
+ {
+ code: '4414',
+ name: '梅州市',
+ districts: [
+ { code: '441402', name: '梅江区' },
+ { code: '441403', name: '梅县区' },
+ { code: '441422', name: '大埔县' },
+ { code: '441423', name: '丰顺县' },
+ { code: '441424', name: '五华县' },
+ { code: '441426', name: '平远县' },
+ { code: '441427', name: '蕉岭县' },
+ { code: '441481', name: '兴宁市' }
+ ]
+ },
+ {
+ code: '4415',
+ name: '汕尾市',
+ districts: [
+ { code: '441502', name: '城区' },
+ { code: '441521', name: '海丰县' },
+ { code: '441523', name: '陆河县' },
+ { code: '441581', name: '陆丰市' }
+ ]
+ },
+ {
+ code: '4416',
+ name: '河源市',
+ districts: [
+ { code: '441602', name: '源城区' },
+ { code: '441621', name: '紫金县' },
+ { code: '441622', name: '龙川县' },
+ { code: '441623', name: '连平县' },
+ { code: '441624', name: '和平县' },
+ { code: '441625', name: '东源县' }
+ ]
+ },
+ {
+ code: '4417',
+ name: '阳江市',
+ districts: [
+ { code: '441702', name: '江城区' },
+ { code: '441704', name: '阳东区' },
+ { code: '441721', name: '阳西县' },
+ { code: '441781', name: '阳春市' }
+ ]
+ },
+ {
+ code: '4418',
+ name: '清远市',
+ districts: [
+ { code: '441802', name: '清城区' },
+ { code: '441803', name: '清新区' },
+ { code: '441821', name: '佛冈县' },
+ { code: '441823', name: '阳山县' },
+ { code: '441825', name: '连山壮族瑶族自治县' },
+ { code: '441826', name: '连南瑶族自治县' },
+ { code: '441881', name: '英德市' },
+ { code: '441882', name: '连州市' }
+ ]
+ },
+ {
+ code: '4419',
+ name: '东莞市',
+ districts: [
+ { code: '441900003', name: '东城街道' },
+ { code: '441900004', name: '南城街道' },
+ { code: '441900005', name: '万江街道' },
+ { code: '441900006', name: '莞城街道' },
+ { code: '441900101', name: '石碣镇' },
+ { code: '441900102', name: '石龙镇' },
+ { code: '441900103', name: '茶山镇' },
+ { code: '441900104', name: '石排镇' },
+ { code: '441900105', name: '企石镇' },
+ { code: '441900106', name: '横沥镇' },
+ { code: '441900107', name: '桥头镇' },
+ { code: '441900108', name: '谢岗镇' },
+ { code: '441900109', name: '东坑镇' },
+ { code: '441900110', name: '常平镇' },
+ { code: '441900111', name: '寮步镇' },
+ { code: '441900112', name: '樟木头镇' },
+ { code: '441900113', name: '大朗镇' },
+ { code: '441900114', name: '黄江镇' },
+ { code: '441900115', name: '清溪镇' },
+ { code: '441900116', name: '塘厦镇' },
+ { code: '441900117', name: '凤岗镇' },
+ { code: '441900118', name: '大岭山镇' },
+ { code: '441900119', name: '长安镇' },
+ { code: '441900121', name: '虎门镇' },
+ { code: '441900122', name: '厚街镇' },
+ { code: '441900123', name: '沙田镇' },
+ { code: '441900124', name: '道滘镇' },
+ { code: '441900125', name: '洪梅镇' },
+ { code: '441900126', name: '麻涌镇' },
+ { code: '441900127', name: '望牛墩镇' },
+ { code: '441900128', name: '中堂镇' },
+ { code: '441900129', name: '高埗镇' }
+ ]
+ },
+ {
+ code: '4420',
+ name: '中山市',
+ districts: [
+ { code: '442000001', name: '石岐街道' },
+ { code: '442000002', name: '东区街道' },
+ { code: '442000003', name: '火炬开发区街道' },
+ { code: '442000004', name: '西区街道' },
+ { code: '442000005', name: '南区街道' },
+ { code: '442000006', name: '五桂山街道' },
+ { code: '442000100', name: '小榄镇' },
+ { code: '442000101', name: '黄圃镇' },
+ { code: '442000102', name: '民众镇' },
+ { code: '442000103', name: '东凤镇' },
+ { code: '442000104', name: '东升镇' },
+ { code: '442000105', name: '古镇镇' },
+ { code: '442000106', name: '沙溪镇' },
+ { code: '442000107', name: '坦洲镇' },
+ { code: '442000108', name: '港口镇' },
+ { code: '442000109', name: '三角镇' },
+ { code: '442000110', name: '横栏镇' },
+ { code: '442000111', name: '南头镇' },
+ { code: '442000112', name: '阜沙镇' },
+ { code: '442000113', name: '南朗镇' },
+ { code: '442000114', name: '三乡镇' },
+ { code: '442000115', name: '板芙镇' },
+ { code: '442000116', name: '大涌镇' },
+ { code: '442000117', name: '神湾镇' }
+ ]
+ },
+ {
+ code: '4451',
+ name: '潮州市',
+ districts: [
+ { code: '445102', name: '湘桥区' },
+ { code: '445103', name: '潮安区' },
+ { code: '445122', name: '饶平县' }
+ ]
+ },
+ {
+ code: '4452',
+ name: '揭阳市',
+ districts: [
+ { code: '445202', name: '榕城区' },
+ { code: '445203', name: '揭东区' },
+ { code: '445222', name: '揭西县' },
+ { code: '445224', name: '惠来县' },
+ { code: '445281', name: '普宁市' }
+ ]
+ },
+ {
+ code: '4453',
+ name: '云浮市',
+ districts: [
+ { code: '445302', name: '云城区' },
+ { code: '445303', name: '云安区' },
+ { code: '445321', name: '新兴县' },
+ { code: '445322', name: '郁南县' },
+ { code: '445381', name: '罗定市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '45',
+ name: '广西壮族自治区',
+ cities: [
+ {
+ code: '4501',
+ name: '南宁市',
+ districts: [
+ { code: '450102', name: '兴宁区' },
+ { code: '450103', name: '青秀区' },
+ { code: '450105', name: '江南区' },
+ { code: '450107', name: '西乡塘区' },
+ { code: '450108', name: '良庆区' },
+ { code: '450109', name: '邕宁区' },
+ { code: '450110', name: '武鸣区' },
+ { code: '450123', name: '隆安县' },
+ { code: '450124', name: '马山县' },
+ { code: '450125', name: '上林县' },
+ { code: '450126', name: '宾阳县' },
+ { code: '450181', name: '横州市' }
+ ]
+ },
+ {
+ code: '4502',
+ name: '柳州市',
+ districts: [
+ { code: '450202', name: '城中区' },
+ { code: '450203', name: '鱼峰区' },
+ { code: '450204', name: '柳南区' },
+ { code: '450205', name: '柳北区' },
+ { code: '450206', name: '柳江区' },
+ { code: '450222', name: '柳城县' },
+ { code: '450223', name: '鹿寨县' },
+ { code: '450224', name: '融安县' },
+ { code: '450225', name: '融水苗族自治县' },
+ { code: '450226', name: '三江侗族自治县' }
+ ]
+ },
+ {
+ code: '4503',
+ name: '桂林市',
+ districts: [
+ { code: '450302', name: '秀峰区' },
+ { code: '450303', name: '叠彩区' },
+ { code: '450304', name: '象山区' },
+ { code: '450305', name: '七星区' },
+ { code: '450311', name: '雁山区' },
+ { code: '450312', name: '临桂区' },
+ { code: '450321', name: '阳朔县' },
+ { code: '450323', name: '灵川县' },
+ { code: '450324', name: '全州县' },
+ { code: '450325', name: '兴安县' },
+ { code: '450326', name: '永福县' },
+ { code: '450327', name: '灌阳县' },
+ { code: '450328', name: '龙胜各族自治县' },
+ { code: '450329', name: '资源县' },
+ { code: '450330', name: '平乐县' },
+ { code: '450332', name: '恭城瑶族自治县' },
+ { code: '450381', name: '荔浦市' }
+ ]
+ },
+ {
+ code: '4504',
+ name: '梧州市',
+ districts: [
+ { code: '450403', name: '万秀区' },
+ { code: '450405', name: '长洲区' },
+ { code: '450406', name: '龙圩区' },
+ { code: '450421', name: '苍梧县' },
+ { code: '450422', name: '藤县' },
+ { code: '450423', name: '蒙山县' },
+ { code: '450481', name: '岑溪市' }
+ ]
+ },
+ {
+ code: '4505',
+ name: '北海市',
+ districts: [
+ { code: '450502', name: '海城区' },
+ { code: '450503', name: '银海区' },
+ { code: '450512', name: '铁山港区' },
+ { code: '450521', name: '合浦县' }
+ ]
+ },
+ {
+ code: '4506',
+ name: '防城港市',
+ districts: [
+ { code: '450602', name: '港口区' },
+ { code: '450603', name: '防城区' },
+ { code: '450621', name: '上思县' },
+ { code: '450681', name: '东兴市' }
+ ]
+ },
+ {
+ code: '4507',
+ name: '钦州市',
+ districts: [
+ { code: '450702', name: '钦南区' },
+ { code: '450703', name: '钦北区' },
+ { code: '450721', name: '灵山县' },
+ { code: '450722', name: '浦北县' }
+ ]
+ },
+ {
+ code: '4508',
+ name: '贵港市',
+ districts: [
+ { code: '450802', name: '港北区' },
+ { code: '450803', name: '港南区' },
+ { code: '450804', name: '覃塘区' },
+ { code: '450821', name: '平南县' },
+ { code: '450881', name: '桂平市' }
+ ]
+ },
+ {
+ code: '4509',
+ name: '玉林市',
+ districts: [
+ { code: '450902', name: '玉州区' },
+ { code: '450903', name: '福绵区' },
+ { code: '450921', name: '容县' },
+ { code: '450922', name: '陆川县' },
+ { code: '450923', name: '博白县' },
+ { code: '450924', name: '兴业县' },
+ { code: '450981', name: '北流市' }
+ ]
+ },
+ {
+ code: '4510',
+ name: '百色市',
+ districts: [
+ { code: '451002', name: '右江区' },
+ { code: '451003', name: '田阳区' },
+ { code: '451022', name: '田东县' },
+ { code: '451024', name: '德保县' },
+ { code: '451026', name: '那坡县' },
+ { code: '451027', name: '凌云县' },
+ { code: '451028', name: '乐业县' },
+ { code: '451029', name: '田林县' },
+ { code: '451030', name: '西林县' },
+ { code: '451031', name: '隆林各族自治县' },
+ { code: '451081', name: '靖西市' },
+ { code: '451082', name: '平果市' }
+ ]
+ },
+ {
+ code: '4511',
+ name: '贺州市',
+ districts: [
+ { code: '451102', name: '八步区' },
+ { code: '451103', name: '平桂区' },
+ { code: '451121', name: '昭平县' },
+ { code: '451122', name: '钟山县' },
+ { code: '451123', name: '富川瑶族自治县' }
+ ]
+ },
+ {
+ code: '4512',
+ name: '河池市',
+ districts: [
+ { code: '451202', name: '金城江区' },
+ { code: '451203', name: '宜州区' },
+ { code: '451221', name: '南丹县' },
+ { code: '451222', name: '天峨县' },
+ { code: '451223', name: '凤山县' },
+ { code: '451224', name: '东兰县' },
+ { code: '451225', name: '罗城仫佬族自治县' },
+ { code: '451226', name: '环江毛南族自治县' },
+ { code: '451227', name: '巴马瑶族自治县' },
+ { code: '451228', name: '都安瑶族自治县' },
+ { code: '451229', name: '大化瑶族自治县' }
+ ]
+ },
+ {
+ code: '4513',
+ name: '来宾市',
+ districts: [
+ { code: '451302', name: '兴宾区' },
+ { code: '451321', name: '忻城县' },
+ { code: '451322', name: '象州县' },
+ { code: '451323', name: '武宣县' },
+ { code: '451324', name: '金秀瑶族自治县' },
+ { code: '451381', name: '合山市' }
+ ]
+ },
+ {
+ code: '4514',
+ name: '崇左市',
+ districts: [
+ { code: '451402', name: '江州区' },
+ { code: '451421', name: '扶绥县' },
+ { code: '451422', name: '宁明县' },
+ { code: '451423', name: '龙州县' },
+ { code: '451424', name: '大新县' },
+ { code: '451425', name: '天等县' },
+ { code: '451481', name: '凭祥市' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '46',
+ name: '海南省',
+ cities: [
+ {
+ code: '4601',
+ name: '海口市',
+ districts: [
+ { code: '460105', name: '秀英区' },
+ { code: '460106', name: '龙华区' },
+ { code: '460107', name: '琼山区' },
+ { code: '460108', name: '美兰区' }
+ ]
+ },
+ {
+ code: '4602',
+ name: '三亚市',
+ districts: [
+ { code: '460202', name: '海棠区' },
+ { code: '460203', name: '吉阳区' },
+ { code: '460204', name: '天涯区' },
+ { code: '460205', name: '崖州区' }
+ ]
+ },
+ {
+ code: '4603',
+ name: '三沙市',
+ districts: [
+ { code: '460321', name: '西沙群岛' },
+ { code: '460322', name: '南沙群岛' },
+ { code: '460323', name: '中沙群岛的岛礁及其海域' }
+ ]
+ },
+ {
+ code: '4604',
+ name: '儋州市',
+ districts: [
+ { code: '460400100', name: '那大镇' },
+ { code: '460400101', name: '和庆镇' },
+ { code: '460400102', name: '南丰镇' },
+ { code: '460400103', name: '大成镇' },
+ { code: '460400104', name: '雅星镇' },
+ { code: '460400105', name: '兰洋镇' },
+ { code: '460400106', name: '光村镇' },
+ { code: '460400107', name: '木棠镇' },
+ { code: '460400108', name: '海头镇' },
+ { code: '460400109', name: '峨蔓镇' },
+ { code: '460400111', name: '王五镇' },
+ { code: '460400112', name: '白马井镇' },
+ { code: '460400113', name: '中和镇' },
+ { code: '460400114', name: '排浦镇' },
+ { code: '460400115', name: '东成镇' },
+ { code: '460400116', name: '新州镇' },
+ { code: '460400499', name: '洋浦经济开发区' },
+ { code: '460400500', name: '华南热作学院' }
+ ]
+ },
+ {
+ code: '4690',
+ name: '省直辖县级行政区划',
+ districts: [
+ { code: '469001', name: '五指山市' },
+ { code: '469002', name: '琼海市' },
+ { code: '469005', name: '文昌市' },
+ { code: '469006', name: '万宁市' },
+ { code: '469007', name: '东方市' },
+ { code: '469021', name: '定安县' },
+ { code: '469022', name: '屯昌县' },
+ { code: '469023', name: '澄迈县' },
+ { code: '469024', name: '临高县' },
+ { code: '469025', name: '白沙黎族自治县' },
+ { code: '469026', name: '昌江黎族自治县' },
+ { code: '469027', name: '乐东黎族自治县' },
+ { code: '469028', name: '陵水黎族自治县' },
+ { code: '469029', name: '保亭黎族苗族自治县' },
+ { code: '469030', name: '琼中黎族苗族自治县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '50',
+ name: '重庆市',
+ cities: [
+ {
+ code: '5001',
+ name: '市辖区',
+ districts: [
+ { code: '500101', name: '万州区' },
+ { code: '500102', name: '涪陵区' },
+ { code: '500103', name: '渝中区' },
+ { code: '500104', name: '大渡口区' },
+ { code: '500105', name: '江北区' },
+ { code: '500106', name: '沙坪坝区' },
+ { code: '500107', name: '九龙坡区' },
+ { code: '500108', name: '南岸区' },
+ { code: '500109', name: '北碚区' },
+ { code: '500110', name: '綦江区' },
+ { code: '500111', name: '大足区' },
+ { code: '500112', name: '渝北区' },
+ { code: '500113', name: '巴南区' },
+ { code: '500114', name: '黔江区' },
+ { code: '500115', name: '长寿区' },
+ { code: '500116', name: '江津区' },
+ { code: '500117', name: '合川区' },
+ { code: '500118', name: '永川区' },
+ { code: '500119', name: '南川区' },
+ { code: '500120', name: '璧山区' },
+ { code: '500151', name: '铜梁区' },
+ { code: '500152', name: '潼南区' },
+ { code: '500153', name: '荣昌区' },
+ { code: '500154', name: '开州区' },
+ { code: '500155', name: '梁平区' },
+ { code: '500156', name: '武隆区' }
+ ]
+ },
+ {
+ code: '5002',
+ name: '县',
+ districts: [
+ { code: '500229', name: '城口县' },
+ { code: '500230', name: '丰都县' },
+ { code: '500231', name: '垫江县' },
+ { code: '500233', name: '忠县' },
+ { code: '500235', name: '云阳县' },
+ { code: '500236', name: '奉节县' },
+ { code: '500237', name: '巫山县' },
+ { code: '500238', name: '巫溪县' },
+ { code: '500240', name: '石柱土家族自治县' },
+ { code: '500241', name: '秀山土家族苗族自治县' },
+ { code: '500242', name: '酉阳土家族苗族自治县' },
+ { code: '500243', name: '彭水苗族土家族自治县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '51',
+ name: '四川省',
+ cities: [
+ {
+ code: '5101',
+ name: '成都市',
+ districts: [
+ { code: '510104', name: '锦江区' },
+ { code: '510105', name: '青羊区' },
+ { code: '510106', name: '金牛区' },
+ { code: '510107', name: '武侯区' },
+ { code: '510108', name: '成华区' },
+ { code: '510112', name: '龙泉驿区' },
+ { code: '510113', name: '青白江区' },
+ { code: '510114', name: '新都区' },
+ { code: '510115', name: '温江区' },
+ { code: '510116', name: '双流区' },
+ { code: '510117', name: '郫都区' },
+ { code: '510118', name: '新津区' },
+ { code: '510121', name: '金堂县' },
+ { code: '510129', name: '大邑县' },
+ { code: '510131', name: '蒲江县' },
+ { code: '510181', name: '都江堰市' },
+ { code: '510182', name: '彭州市' },
+ { code: '510183', name: '邛崃市' },
+ { code: '510184', name: '崇州市' },
+ { code: '510185', name: '简阳市' }
+ ]
+ },
+ {
+ code: '5103',
+ name: '自贡市',
+ districts: [
+ { code: '510302', name: '自流井区' },
+ { code: '510303', name: '贡井区' },
+ { code: '510304', name: '大安区' },
+ { code: '510311', name: '沿滩区' },
+ { code: '510321', name: '荣县' },
+ { code: '510322', name: '富顺县' }
+ ]
+ },
+ {
+ code: '5104',
+ name: '攀枝花市',
+ districts: [
+ { code: '510402', name: '东区' },
+ { code: '510403', name: '西区' },
+ { code: '510411', name: '仁和区' },
+ { code: '510421', name: '米易县' },
+ { code: '510422', name: '盐边县' }
+ ]
+ },
+ {
+ code: '5105',
+ name: '泸州市',
+ districts: [
+ { code: '510502', name: '江阳区' },
+ { code: '510503', name: '纳溪区' },
+ { code: '510504', name: '龙马潭区' },
+ { code: '510521', name: '泸县' },
+ { code: '510522', name: '合江县' },
+ { code: '510524', name: '叙永县' },
+ { code: '510525', name: '古蔺县' }
+ ]
+ },
+ {
+ code: '5106',
+ name: '德阳市',
+ districts: [
+ { code: '510603', name: '旌阳区' },
+ { code: '510604', name: '罗江区' },
+ { code: '510623', name: '中江县' },
+ { code: '510681', name: '广汉市' },
+ { code: '510682', name: '什邡市' },
+ { code: '510683', name: '绵竹市' }
+ ]
+ },
+ {
+ code: '5107',
+ name: '绵阳市',
+ districts: [
+ { code: '510703', name: '涪城区' },
+ { code: '510704', name: '游仙区' },
+ { code: '510705', name: '安州区' },
+ { code: '510722', name: '三台县' },
+ { code: '510723', name: '盐亭县' },
+ { code: '510725', name: '梓潼县' },
+ { code: '510726', name: '北川羌族自治县' },
+ { code: '510727', name: '平武县' },
+ { code: '510781', name: '江油市' }
+ ]
+ },
+ {
+ code: '5108',
+ name: '广元市',
+ districts: [
+ { code: '510802', name: '利州区' },
+ { code: '510811', name: '昭化区' },
+ { code: '510812', name: '朝天区' },
+ { code: '510821', name: '旺苍县' },
+ { code: '510822', name: '青川县' },
+ { code: '510823', name: '剑阁县' },
+ { code: '510824', name: '苍溪县' }
+ ]
+ },
+ {
+ code: '5109',
+ name: '遂宁市',
+ districts: [
+ { code: '510903', name: '船山区' },
+ { code: '510904', name: '安居区' },
+ { code: '510921', name: '蓬溪县' },
+ { code: '510923', name: '大英县' },
+ { code: '510981', name: '射洪市' }
+ ]
+ },
+ {
+ code: '5110',
+ name: '内江市',
+ districts: [
+ { code: '511002', name: '市中区' },
+ { code: '511011', name: '东兴区' },
+ { code: '511024', name: '威远县' },
+ { code: '511025', name: '资中县' },
+ { code: '511083', name: '隆昌市' }
+ ]
+ },
+ {
+ code: '5111',
+ name: '乐山市',
+ districts: [
+ { code: '511102', name: '市中区' },
+ { code: '511111', name: '沙湾区' },
+ { code: '511112', name: '五通桥区' },
+ { code: '511113', name: '金口河区' },
+ { code: '511123', name: '犍为县' },
+ { code: '511124', name: '井研县' },
+ { code: '511126', name: '夹江县' },
+ { code: '511129', name: '沐川县' },
+ { code: '511132', name: '峨边彝族自治县' },
+ { code: '511133', name: '马边彝族自治县' },
+ { code: '511181', name: '峨眉山市' }
+ ]
+ },
+ {
+ code: '5113',
+ name: '南充市',
+ districts: [
+ { code: '511302', name: '顺庆区' },
+ { code: '511303', name: '高坪区' },
+ { code: '511304', name: '嘉陵区' },
+ { code: '511321', name: '南部县' },
+ { code: '511322', name: '营山县' },
+ { code: '511323', name: '蓬安县' },
+ { code: '511324', name: '仪陇县' },
+ { code: '511325', name: '西充县' },
+ { code: '511381', name: '阆中市' }
+ ]
+ },
+ {
+ code: '5114',
+ name: '眉山市',
+ districts: [
+ { code: '511402', name: '东坡区' },
+ { code: '511403', name: '彭山区' },
+ { code: '511421', name: '仁寿县' },
+ { code: '511423', name: '洪雅县' },
+ { code: '511424', name: '丹棱县' },
+ { code: '511425', name: '青神县' }
+ ]
+ },
+ {
+ code: '5115',
+ name: '宜宾市',
+ districts: [
+ { code: '511502', name: '翠屏区' },
+ { code: '511503', name: '南溪区' },
+ { code: '511504', name: '叙州区' },
+ { code: '511523', name: '江安县' },
+ { code: '511524', name: '长宁县' },
+ { code: '511525', name: '高县' },
+ { code: '511526', name: '珙县' },
+ { code: '511527', name: '筠连县' },
+ { code: '511528', name: '兴文县' },
+ { code: '511529', name: '屏山县' }
+ ]
+ },
+ {
+ code: '5116',
+ name: '广安市',
+ districts: [
+ { code: '511602', name: '广安区' },
+ { code: '511603', name: '前锋区' },
+ { code: '511621', name: '岳池县' },
+ { code: '511622', name: '武胜县' },
+ { code: '511623', name: '邻水县' },
+ { code: '511681', name: '华蓥市' }
+ ]
+ },
+ {
+ code: '5117',
+ name: '达州市',
+ districts: [
+ { code: '511702', name: '通川区' },
+ { code: '511703', name: '达川区' },
+ { code: '511722', name: '宣汉县' },
+ { code: '511723', name: '开江县' },
+ { code: '511724', name: '大竹县' },
+ { code: '511725', name: '渠县' },
+ { code: '511781', name: '万源市' }
+ ]
+ },
+ {
+ code: '5118',
+ name: '雅安市',
+ districts: [
+ { code: '511802', name: '雨城区' },
+ { code: '511803', name: '名山区' },
+ { code: '511822', name: '荥经县' },
+ { code: '511823', name: '汉源县' },
+ { code: '511824', name: '石棉县' },
+ { code: '511825', name: '天全县' },
+ { code: '511826', name: '芦山县' },
+ { code: '511827', name: '宝兴县' }
+ ]
+ },
+ {
+ code: '5119',
+ name: '巴中市',
+ districts: [
+ { code: '511902', name: '巴州区' },
+ { code: '511903', name: '恩阳区' },
+ { code: '511921', name: '通江县' },
+ { code: '511922', name: '南江县' },
+ { code: '511923', name: '平昌县' }
+ ]
+ },
+ {
+ code: '5120',
+ name: '资阳市',
+ districts: [
+ { code: '512002', name: '雁江区' },
+ { code: '512021', name: '安岳县' },
+ { code: '512022', name: '乐至县' }
+ ]
+ },
+ {
+ code: '5132',
+ name: '阿坝藏族羌族自治州',
+ districts: [
+ { code: '513201', name: '马尔康市' },
+ { code: '513221', name: '汶川县' },
+ { code: '513222', name: '理县' },
+ { code: '513223', name: '茂县' },
+ { code: '513224', name: '松潘县' },
+ { code: '513225', name: '九寨沟县' },
+ { code: '513226', name: '金川县' },
+ { code: '513227', name: '小金县' },
+ { code: '513228', name: '黑水县' },
+ { code: '513230', name: '壤塘县' },
+ { code: '513231', name: '阿坝县' },
+ { code: '513232', name: '若尔盖县' },
+ { code: '513233', name: '红原县' }
+ ]
+ },
+ {
+ code: '5133',
+ name: '甘孜藏族自治州',
+ districts: [
+ { code: '513301', name: '康定市' },
+ { code: '513322', name: '泸定县' },
+ { code: '513323', name: '丹巴县' },
+ { code: '513324', name: '九龙县' },
+ { code: '513325', name: '雅江县' },
+ { code: '513326', name: '道孚县' },
+ { code: '513327', name: '炉霍县' },
+ { code: '513328', name: '甘孜县' },
+ { code: '513329', name: '新龙县' },
+ { code: '513330', name: '德格县' },
+ { code: '513331', name: '白玉县' },
+ { code: '513332', name: '石渠县' },
+ { code: '513333', name: '色达县' },
+ { code: '513334', name: '理塘县' },
+ { code: '513335', name: '巴塘县' },
+ { code: '513336', name: '乡城县' },
+ { code: '513337', name: '稻城县' },
+ { code: '513338', name: '得荣县' }
+ ]
+ },
+ {
+ code: '5134',
+ name: '凉山彝族自治州',
+ districts: [
+ { code: '513401', name: '西昌市' },
+ { code: '513422', name: '木里藏族自治县' },
+ { code: '513423', name: '盐源县' },
+ { code: '513424', name: '德昌县' },
+ { code: '513425', name: '会理县' },
+ { code: '513426', name: '会东县' },
+ { code: '513427', name: '宁南县' },
+ { code: '513428', name: '普格县' },
+ { code: '513429', name: '布拖县' },
+ { code: '513430', name: '金阳县' },
+ { code: '513431', name: '昭觉县' },
+ { code: '513432', name: '喜德县' },
+ { code: '513433', name: '冕宁县' },
+ { code: '513434', name: '越西县' },
+ { code: '513435', name: '甘洛县' },
+ { code: '513436', name: '美姑县' },
+ { code: '513437', name: '雷波县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '52',
+ name: '贵州省',
+ cities: [
+ {
+ code: '5201',
+ name: '贵阳市',
+ districts: [
+ { code: '520102', name: '南明区' },
+ { code: '520103', name: '云岩区' },
+ { code: '520111', name: '花溪区' },
+ { code: '520112', name: '乌当区' },
+ { code: '520113', name: '白云区' },
+ { code: '520115', name: '观山湖区' },
+ { code: '520121', name: '开阳县' },
+ { code: '520122', name: '息烽县' },
+ { code: '520123', name: '修文县' },
+ { code: '520181', name: '清镇市' }
+ ]
+ },
+ {
+ code: '5202',
+ name: '六盘水市',
+ districts: [
+ { code: '520201', name: '钟山区' },
+ { code: '520203', name: '六枝特区' },
+ { code: '520221', name: '水城县' },
+ { code: '520281', name: '盘州市' }
+ ]
+ },
+ {
+ code: '5203',
+ name: '遵义市',
+ districts: [
+ { code: '520302', name: '红花岗区' },
+ { code: '520303', name: '汇川区' },
+ { code: '520304', name: '播州区' },
+ { code: '520322', name: '桐梓县' },
+ { code: '520323', name: '绥阳县' },
+ { code: '520324', name: '正安县' },
+ { code: '520325', name: '道真仡佬族苗族自治县' },
+ { code: '520326', name: '务川仡佬族苗族自治县' },
+ { code: '520327', name: '凤冈县' },
+ { code: '520328', name: '湄潭县' },
+ { code: '520329', name: '余庆县' },
+ { code: '520330', name: '习水县' },
+ { code: '520381', name: '赤水市' },
+ { code: '520382', name: '仁怀市' }
+ ]
+ },
+ {
+ code: '5204',
+ name: '安顺市',
+ districts: [
+ { code: '520402', name: '西秀区' },
+ { code: '520403', name: '平坝区' },
+ { code: '520422', name: '普定县' },
+ { code: '520423', name: '镇宁布依族苗族自治县' },
+ { code: '520424', name: '关岭布依族苗族自治县' },
+ { code: '520425', name: '紫云苗族布依族自治县' }
+ ]
+ },
+ {
+ code: '5205',
+ name: '毕节市',
+ districts: [
+ { code: '520502', name: '七星关区' },
+ { code: '520521', name: '大方县' },
+ { code: '520522', name: '黔西县' },
+ { code: '520523', name: '金沙县' },
+ { code: '520524', name: '织金县' },
+ { code: '520525', name: '纳雍县' },
+ { code: '520526', name: '威宁彝族回族苗族自治县' },
+ { code: '520527', name: '赫章县' }
+ ]
+ },
+ {
+ code: '5206',
+ name: '铜仁市',
+ districts: [
+ { code: '520602', name: '碧江区' },
+ { code: '520603', name: '万山区' },
+ { code: '520621', name: '江口县' },
+ { code: '520622', name: '玉屏侗族自治县' },
+ { code: '520623', name: '石阡县' },
+ { code: '520624', name: '思南县' },
+ { code: '520625', name: '印江土家族苗族自治县' },
+ { code: '520626', name: '德江县' },
+ { code: '520627', name: '沿河土家族自治县' },
+ { code: '520628', name: '松桃苗族自治县' }
+ ]
+ },
+ {
+ code: '5223',
+ name: '黔西南布依族苗族自治州',
+ districts: [
+ { code: '522301', name: '兴义市' },
+ { code: '522322', name: '兴仁县' },
+ { code: '522323', name: '普安县' },
+ { code: '522324', name: '晴隆县' },
+ { code: '522325', name: '贞丰县' },
+ { code: '522326', name: '望谟县' },
+ { code: '522327', name: '册亨县' },
+ { code: '522328', name: '安龙县' }
+ ]
+ },
+ {
+ code: '5226',
+ name: '黔东南苗族侗族自治州',
+ districts: [
+ { code: '522601', name: '凯里市' },
+ { code: '522622', name: '黄平县' },
+ { code: '522623', name: '施秉县' },
+ { code: '522624', name: '三穗县' },
+ { code: '522625', name: '镇远县' },
+ { code: '522626', name: '岑巩县' },
+ { code: '522627', name: '天柱县' },
+ { code: '522628', name: '锦屏县' },
+ { code: '522629', name: '剑河县' },
+ { code: '522630', name: '台江县' },
+ { code: '522631', name: '黎平县' },
+ { code: '522632', name: '榕江县' },
+ { code: '522633', name: '从江县' },
+ { code: '522634', name: '雷山县' },
+ { code: '522635', name: '麻江县' },
+ { code: '522636', name: '丹寨县' }
+ ]
+ },
+ {
+ code: '5227',
+ name: '黔南布依族苗族自治州',
+ districts: [
+ { code: '522701', name: '都匀市' },
+ { code: '522702', name: '福泉市' },
+ { code: '522722', name: '荔波县' },
+ { code: '522723', name: '贵定县' },
+ { code: '522725', name: '瓮安县' },
+ { code: '522726', name: '独山县' },
+ { code: '522727', name: '平塘县' },
+ { code: '522728', name: '罗甸县' },
+ { code: '522729', name: '长顺县' },
+ { code: '522730', name: '龙里县' },
+ { code: '522731', name: '惠水县' },
+ { code: '522732', name: '三都水族自治县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '53',
+ name: '云南省',
+ cities: [
+ {
+ code: '5301',
+ name: '昆明市',
+ districts: [
+ { code: '530102', name: '五华区' },
+ { code: '530103', name: '盘龙区' },
+ { code: '530111', name: '官渡区' },
+ { code: '530112', name: '西山区' },
+ { code: '530113', name: '东川区' },
+ { code: '530114', name: '呈贡区' },
+ { code: '530115', name: '晋宁区' },
+ { code: '530124', name: '富民县' },
+ { code: '530125', name: '宜良县' },
+ { code: '530126', name: '石林彝族自治县' },
+ { code: '530127', name: '嵩明县' },
+ { code: '530128', name: '禄劝彝族苗族自治县' },
+ { code: '530129', name: '寻甸回族彝族自治县' },
+ { code: '530181', name: '安宁市' }
+ ]
+ },
+ {
+ code: '5303',
+ name: '曲靖市',
+ districts: [
+ { code: '530302', name: '麒麟区' },
+ { code: '530303', name: '沾益区' },
+ { code: '530304', name: '马龙区' },
+ { code: '530322', name: '陆良县' },
+ { code: '530323', name: '师宗县' },
+ { code: '530324', name: '罗平县' },
+ { code: '530325', name: '富源县' },
+ { code: '530326', name: '会泽县' },
+ { code: '530381', name: '宣威市' }
+ ]
+ },
+ {
+ code: '5304',
+ name: '玉溪市',
+ districts: [
+ { code: '530402', name: '红塔区' },
+ { code: '530403', name: '江川区' },
+ { code: '530423', name: '通海县' },
+ { code: '530424', name: '华宁县' },
+ { code: '530425', name: '易门县' },
+ { code: '530426', name: '峨山彝族自治县' },
+ { code: '530427', name: '新平彝族傣族自治县' },
+ { code: '530428', name: '元江哈尼族彝族傣族自治县' },
+ { code: '530481', name: '澄江市' }
+ ]
+ },
+ {
+ code: '5305',
+ name: '保山市',
+ districts: [
+ { code: '530502', name: '隆阳区' },
+ { code: '530521', name: '施甸县' },
+ { code: '530523', name: '龙陵县' },
+ { code: '530524', name: '昌宁县' },
+ { code: '530581', name: '腾冲市' }
+ ]
+ },
+ {
+ code: '5306',
+ name: '昭通市',
+ districts: [
+ { code: '530602', name: '昭阳区' },
+ { code: '530621', name: '鲁甸县' },
+ { code: '530622', name: '巧家县' },
+ { code: '530623', name: '盐津县' },
+ { code: '530624', name: '大关县' },
+ { code: '530625', name: '永善县' },
+ { code: '530626', name: '绥江县' },
+ { code: '530627', name: '镇雄县' },
+ { code: '530628', name: '彝良县' },
+ { code: '530629', name: '威信县' },
+ { code: '530681', name: '水富市' }
+ ]
+ },
+ {
+ code: '5307',
+ name: '丽江市',
+ districts: [
+ { code: '530702', name: '古城区' },
+ { code: '530721', name: '玉龙纳西族自治县' },
+ { code: '530722', name: '永胜县' },
+ { code: '530723', name: '华坪县' },
+ { code: '530724', name: '宁蒗彝族自治县' }
+ ]
+ },
+ {
+ code: '5308',
+ name: '普洱市',
+ districts: [
+ { code: '530802', name: '思茅区' },
+ { code: '530821', name: '宁洱哈尼族彝族自治县' },
+ { code: '530822', name: '墨江哈尼族自治县' },
+ { code: '530823', name: '景东彝族自治县' },
+ { code: '530824', name: '景谷傣族彝族自治县' },
+ { code: '530825', name: '镇沅彝族哈尼族拉祜族自治县' },
+ { code: '530826', name: '江城哈尼族彝族自治县' },
+ { code: '530827', name: '孟连傣族拉祜族佤族自治县' },
+ { code: '530828', name: '澜沧拉祜族自治县' },
+ { code: '530829', name: '西盟佤族自治县' }
+ ]
+ },
+ {
+ code: '5309',
+ name: '临沧市',
+ districts: [
+ { code: '530902', name: '临翔区' },
+ { code: '530921', name: '凤庆县' },
+ { code: '530922', name: '云县' },
+ { code: '530923', name: '永德县' },
+ { code: '530924', name: '镇康县' },
+ { code: '530925', name: '双江拉祜族佤族布朗族傣族自治县' },
+ { code: '530926', name: '耿马傣族佤族自治县' },
+ { code: '530927', name: '沧源佤族自治县' }
+ ]
+ },
+ {
+ code: '5323',
+ name: '楚雄彝族自治州',
+ districts: [
+ { code: '532301', name: '楚雄市' },
+ { code: '532322', name: '双柏县' },
+ { code: '532323', name: '牟定县' },
+ { code: '532324', name: '南华县' },
+ { code: '532325', name: '姚安县' },
+ { code: '532326', name: '大姚县' },
+ { code: '532327', name: '永仁县' },
+ { code: '532328', name: '元谋县' },
+ { code: '532329', name: '武定县' },
+ { code: '532331', name: '禄丰县' }
+ ]
+ },
+ {
+ code: '5325',
+ name: '红河哈尼族彝族自治州',
+ districts: [
+ { code: '532501', name: '个旧市' },
+ { code: '532502', name: '开远市' },
+ { code: '532503', name: '蒙自市' },
+ { code: '532504', name: '弥勒市' },
+ { code: '532523', name: '屏边苗族自治县' },
+ { code: '532524', name: '建水县' },
+ { code: '532525', name: '石屏县' },
+ { code: '532527', name: '泸西县' },
+ { code: '532528', name: '元阳县' },
+ { code: '532529', name: '红河县' },
+ { code: '532530', name: '金平苗族瑶族傣族自治县' },
+ { code: '532531', name: '绿春县' },
+ { code: '532532', name: '河口瑶族自治县' }
+ ]
+ },
+ {
+ code: '5326',
+ name: '文山壮族苗族自治州',
+ districts: [
+ { code: '532601', name: '文山市' },
+ { code: '532622', name: '砚山县' },
+ { code: '532623', name: '西畴县' },
+ { code: '532624', name: '麻栗坡县' },
+ { code: '532625', name: '马关县' },
+ { code: '532626', name: '丘北县' },
+ { code: '532627', name: '广南县' },
+ { code: '532628', name: '富宁县' }
+ ]
+ },
+ {
+ code: '5328',
+ name: '西双版纳傣族自治州',
+ districts: [
+ { code: '532801', name: '景洪市' },
+ { code: '532822', name: '勐海县' },
+ { code: '532823', name: '勐腊县' }
+ ]
+ },
+ {
+ code: '5329',
+ name: '大理白族自治州',
+ districts: [
+ { code: '532901', name: '大理市' },
+ { code: '532922', name: '漾濞彝族自治县' },
+ { code: '532923', name: '祥云县' },
+ { code: '532924', name: '宾川县' },
+ { code: '532925', name: '弥渡县' },
+ { code: '532926', name: '南涧彝族自治县' },
+ { code: '532927', name: '巍山彝族回族自治县' },
+ { code: '532928', name: '永平县' },
+ { code: '532929', name: '云龙县' },
+ { code: '532930', name: '洱源县' },
+ { code: '532931', name: '剑川县' },
+ { code: '532932', name: '鹤庆县' }
+ ]
+ },
+ {
+ code: '5331',
+ name: '德宏傣族景颇族自治州',
+ districts: [
+ { code: '533102', name: '瑞丽市' },
+ { code: '533103', name: '芒市' },
+ { code: '533122', name: '梁河县' },
+ { code: '533123', name: '盈江县' },
+ { code: '533124', name: '陇川县' }
+ ]
+ },
+ {
+ code: '5333',
+ name: '怒江傈僳族自治州',
+ districts: [
+ { code: '533301', name: '泸水市' },
+ { code: '533323', name: '福贡县' },
+ { code: '533324', name: '贡山独龙族怒族自治县' },
+ { code: '533325', name: '兰坪白族普米族自治县' }
+ ]
+ },
+ {
+ code: '5334',
+ name: '迪庆藏族自治州',
+ districts: [
+ { code: '533401', name: '香格里拉市' },
+ { code: '533422', name: '德钦县' },
+ { code: '533423', name: '维西傈僳族自治县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '54',
+ name: '西藏自治区',
+ cities: [
+ {
+ code: '5401',
+ name: '拉萨市',
+ districts: [
+ { code: '540102', name: '城关区' },
+ { code: '540103', name: '堆龙德庆区' },
+ { code: '540104', name: '达孜区' },
+ { code: '540121', name: '林周县' },
+ { code: '540122', name: '当雄县' },
+ { code: '540123', name: '尼木县' },
+ { code: '540124', name: '曲水县' },
+ { code: '540127', name: '墨竹工卡县' }
+ ]
+ },
+ {
+ code: '5402',
+ name: '日喀则市',
+ districts: [
+ { code: '540202', name: '桑珠孜区' },
+ { code: '540221', name: '南木林县' },
+ { code: '540222', name: '江孜县' },
+ { code: '540223', name: '定日县' },
+ { code: '540224', name: '萨迦县' },
+ { code: '540225', name: '拉孜县' },
+ { code: '540226', name: '昂仁县' },
+ { code: '540227', name: '谢通门县' },
+ { code: '540228', name: '白朗县' },
+ { code: '540229', name: '仁布县' },
+ { code: '540230', name: '康马县' },
+ { code: '540231', name: '定结县' },
+ { code: '540232', name: '仲巴县' },
+ { code: '540233', name: '亚东县' },
+ { code: '540234', name: '吉隆县' },
+ { code: '540235', name: '聂拉木县' },
+ { code: '540236', name: '萨嘎县' },
+ { code: '540237', name: '岗巴县' }
+ ]
+ },
+ {
+ code: '5403',
+ name: '昌都市',
+ districts: [
+ { code: '540302', name: '卡若区' },
+ { code: '540321', name: '江达县' },
+ { code: '540322', name: '贡觉县' },
+ { code: '540323', name: '类乌齐县' },
+ { code: '540324', name: '丁青县' },
+ { code: '540325', name: '察雅县' },
+ { code: '540326', name: '八宿县' },
+ { code: '540327', name: '左贡县' },
+ { code: '540328', name: '芒康县' },
+ { code: '540329', name: '洛隆县' },
+ { code: '540330', name: '边坝县' }
+ ]
+ },
+ {
+ code: '5404',
+ name: '林芝市',
+ districts: [
+ { code: '540402', name: '巴宜区' },
+ { code: '540421', name: '工布江达县' },
+ { code: '540422', name: '米林县' },
+ { code: '540423', name: '墨脱县' },
+ { code: '540424', name: '波密县' },
+ { code: '540425', name: '察隅县' },
+ { code: '540426', name: '朗县' }
+ ]
+ },
+ {
+ code: '5405',
+ name: '山南市',
+ districts: [
+ { code: '540502', name: '乃东区' },
+ { code: '540521', name: '扎囊县' },
+ { code: '540522', name: '贡嘎县' },
+ { code: '540523', name: '桑日县' },
+ { code: '540524', name: '琼结县' },
+ { code: '540525', name: '曲松县' },
+ { code: '540526', name: '措美县' },
+ { code: '540527', name: '洛扎县' },
+ { code: '540528', name: '加查县' },
+ { code: '540529', name: '隆子县' },
+ { code: '540530', name: '错那县' },
+ { code: '540531', name: '浪卡子县' }
+ ]
+ },
+ {
+ code: '5406',
+ name: '那曲市',
+ districts: [
+ { code: '540602', name: '色尼区' },
+ { code: '540621', name: '嘉黎县' },
+ { code: '540622', name: '比如县' },
+ { code: '540623', name: '聂荣县' },
+ { code: '540624', name: '安多县' },
+ { code: '540625', name: '申扎县' },
+ { code: '540626', name: '索县' },
+ { code: '540627', name: '班戈县' },
+ { code: '540628', name: '巴青县' },
+ { code: '540629', name: '尼玛县' },
+ { code: '540630', name: '双湖县' }
+ ]
+ },
+ {
+ code: '5425',
+ name: '阿里地区',
+ districts: [
+ { code: '542521', name: '普兰县' },
+ { code: '542522', name: '札达县' },
+ { code: '542523', name: '噶尔县' },
+ { code: '542524', name: '日土县' },
+ { code: '542525', name: '革吉县' },
+ { code: '542526', name: '改则县' },
+ { code: '542527', name: '措勤县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '61',
+ name: '陕西省',
+ cities: [
+ {
+ code: '6101',
+ name: '西安市',
+ districts: [
+ { code: '610102', name: '新城区' },
+ { code: '610103', name: '碑林区' },
+ { code: '610104', name: '莲湖区' },
+ { code: '610111', name: '灞桥区' },
+ { code: '610112', name: '未央区' },
+ { code: '610113', name: '雁塔区' },
+ { code: '610114', name: '阎良区' },
+ { code: '610115', name: '临潼区' },
+ { code: '610116', name: '长安区' },
+ { code: '610117', name: '高陵区' },
+ { code: '610118', name: '鄠邑区' },
+ { code: '610122', name: '蓝田县' },
+ { code: '610124', name: '周至县' }
+ ]
+ },
+ {
+ code: '6102',
+ name: '铜川市',
+ districts: [
+ { code: '610202', name: '王益区' },
+ { code: '610203', name: '印台区' },
+ { code: '610204', name: '耀州区' },
+ { code: '610222', name: '宜君县' }
+ ]
+ },
+ {
+ code: '6103',
+ name: '宝鸡市',
+ districts: [
+ { code: '610302', name: '渭滨区' },
+ { code: '610303', name: '金台区' },
+ { code: '610304', name: '陈仓区' },
+ { code: '610322', name: '凤翔县' },
+ { code: '610323', name: '岐山县' },
+ { code: '610324', name: '扶风县' },
+ { code: '610326', name: '眉县' },
+ { code: '610327', name: '陇县' },
+ { code: '610328', name: '千阳县' },
+ { code: '610329', name: '麟游县' },
+ { code: '610330', name: '凤县' },
+ { code: '610331', name: '太白县' }
+ ]
+ },
+ {
+ code: '6104',
+ name: '咸阳市',
+ districts: [
+ { code: '610402', name: '秦都区' },
+ { code: '610403', name: '杨陵区' },
+ { code: '610404', name: '渭城区' },
+ { code: '610422', name: '三原县' },
+ { code: '610423', name: '泾阳县' },
+ { code: '610424', name: '乾县' },
+ { code: '610425', name: '礼泉县' },
+ { code: '610426', name: '永寿县' },
+ { code: '610428', name: '长武县' },
+ { code: '610429', name: '旬邑县' },
+ { code: '610430', name: '淳化县' },
+ { code: '610431', name: '武功县' },
+ { code: '610481', name: '兴平市' },
+ { code: '610482', name: '彬州市' }
+ ]
+ },
+ {
+ code: '6105',
+ name: '渭南市',
+ districts: [
+ { code: '610502', name: '临渭区' },
+ { code: '610503', name: '华州区' },
+ { code: '610522', name: '潼关县' },
+ { code: '610523', name: '大荔县' },
+ { code: '610524', name: '合阳县' },
+ { code: '610525', name: '澄城县' },
+ { code: '610526', name: '蒲城县' },
+ { code: '610527', name: '白水县' },
+ { code: '610528', name: '富平县' },
+ { code: '610581', name: '韩城市' },
+ { code: '610582', name: '华阴市' }
+ ]
+ },
+ {
+ code: '6106',
+ name: '延安市',
+ districts: [
+ { code: '610602', name: '宝塔区' },
+ { code: '610603', name: '安塞区' },
+ { code: '610621', name: '延长县' },
+ { code: '610622', name: '延川县' },
+ { code: '610623', name: '子长县' },
+ { code: '610625', name: '志丹县' },
+ { code: '610626', name: '吴起县' },
+ { code: '610627', name: '甘泉县' },
+ { code: '610628', name: '富县' },
+ { code: '610629', name: '洛川县' },
+ { code: '610630', name: '宜川县' },
+ { code: '610631', name: '黄龙县' },
+ { code: '610632', name: '黄陵县' }
+ ]
+ },
+ {
+ code: '6107',
+ name: '汉中市',
+ districts: [
+ { code: '610702', name: '汉台区' },
+ { code: '610703', name: '南郑区' },
+ { code: '610722', name: '城固县' },
+ { code: '610723', name: '洋县' },
+ { code: '610724', name: '西乡县' },
+ { code: '610725', name: '勉县' },
+ { code: '610726', name: '宁强县' },
+ { code: '610727', name: '略阳县' },
+ { code: '610728', name: '镇巴县' },
+ { code: '610729', name: '留坝县' },
+ { code: '610730', name: '佛坪县' }
+ ]
+ },
+ {
+ code: '6108',
+ name: '榆林市',
+ districts: [
+ { code: '610802', name: '榆阳区' },
+ { code: '610803', name: '横山区' },
+ { code: '610822', name: '府谷县' },
+ { code: '610824', name: '靖边县' },
+ { code: '610825', name: '定边县' },
+ { code: '610826', name: '绥德县' },
+ { code: '610827', name: '米脂县' },
+ { code: '610828', name: '佳县' },
+ { code: '610829', name: '吴堡县' },
+ { code: '610830', name: '清涧县' },
+ { code: '610831', name: '子洲县' },
+ { code: '610881', name: '神木市' }
+ ]
+ },
+ {
+ code: '6109',
+ name: '安康市',
+ districts: [
+ { code: '610902', name: '汉滨区' },
+ { code: '610921', name: '汉阴县' },
+ { code: '610922', name: '石泉县' },
+ { code: '610923', name: '宁陕县' },
+ { code: '610924', name: '紫阳县' },
+ { code: '610925', name: '岚皋县' },
+ { code: '610926', name: '平利县' },
+ { code: '610927', name: '镇坪县' },
+ { code: '610928', name: '旬阳县' },
+ { code: '610929', name: '白河县' }
+ ]
+ },
+ {
+ code: '6110',
+ name: '商洛市',
+ districts: [
+ { code: '611002', name: '商州区' },
+ { code: '611021', name: '洛南县' },
+ { code: '611022', name: '丹凤县' },
+ { code: '611023', name: '商南县' },
+ { code: '611024', name: '山阳县' },
+ { code: '611025', name: '镇安县' },
+ { code: '611026', name: '柞水县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '62',
+ name: '甘肃省',
+ cities: [
+ {
+ code: '6201',
+ name: '兰州市',
+ districts: [
+ { code: '620102', name: '城关区' },
+ { code: '620103', name: '七里河区' },
+ { code: '620104', name: '西固区' },
+ { code: '620105', name: '安宁区' },
+ { code: '620111', name: '红古区' },
+ { code: '620121', name: '永登县' },
+ { code: '620122', name: '皋兰县' },
+ { code: '620123', name: '榆中县' }
+ ]
+ },
+ {
+ code: '6202',
+ name: '嘉峪关市',
+ districts: [
+ { code: '620201001', name: '雄关街道' },
+ { code: '620201002', name: '钢城街道' },
+ { code: '620201100', name: '新城镇' }
+ ]
+ },
+ {
+ code: '6203',
+ name: '金昌市',
+ districts: [
+ { code: '620302', name: '金川区' },
+ { code: '620321', name: '永昌县' }
+ ]
+ },
+ {
+ code: '6204',
+ name: '白银市',
+ districts: [
+ { code: '620402', name: '白银区' },
+ { code: '620403', name: '平川区' },
+ { code: '620421', name: '靖远县' },
+ { code: '620422', name: '会宁县' },
+ { code: '620423', name: '景泰县' }
+ ]
+ },
+ {
+ code: '6205',
+ name: '天水市',
+ districts: [
+ { code: '620502', name: '秦州区' },
+ { code: '620503', name: '麦积区' },
+ { code: '620521', name: '清水县' },
+ { code: '620522', name: '秦安县' },
+ { code: '620523', name: '甘谷县' },
+ { code: '620524', name: '武山县' },
+ { code: '620525', name: '张家川回族自治县' }
+ ]
+ },
+ {
+ code: '6206',
+ name: '武威市',
+ districts: [
+ { code: '620602', name: '凉州区' },
+ { code: '620621', name: '民勤县' },
+ { code: '620622', name: '古浪县' },
+ { code: '620623', name: '天祝藏族自治县' }
+ ]
+ },
+ {
+ code: '6207',
+ name: '张掖市',
+ districts: [
+ { code: '620702', name: '甘州区' },
+ { code: '620721', name: '肃南裕固族自治县' },
+ { code: '620722', name: '民乐县' },
+ { code: '620723', name: '临泽县' },
+ { code: '620724', name: '高台县' },
+ { code: '620725', name: '山丹县' }
+ ]
+ },
+ {
+ code: '6208',
+ name: '平凉市',
+ districts: [
+ { code: '620802', name: '崆峒区' },
+ { code: '620821', name: '泾川县' },
+ { code: '620822', name: '灵台县' },
+ { code: '620823', name: '崇信县' },
+ { code: '620825', name: '庄浪县' },
+ { code: '620826', name: '静宁县' },
+ { code: '620881', name: '华亭市' }
+ ]
+ },
+ {
+ code: '6209',
+ name: '酒泉市',
+ districts: [
+ { code: '620902', name: '肃州区' },
+ { code: '620921', name: '金塔县' },
+ { code: '620922', name: '瓜州县' },
+ { code: '620923', name: '肃北蒙古族自治县' },
+ { code: '620924', name: '阿克塞哈萨克族自治县' },
+ { code: '620981', name: '玉门市' },
+ { code: '620982', name: '敦煌市' }
+ ]
+ },
+ {
+ code: '6210',
+ name: '庆阳市',
+ districts: [
+ { code: '621002', name: '西峰区' },
+ { code: '621021', name: '庆城县' },
+ { code: '621022', name: '环县' },
+ { code: '621023', name: '华池县' },
+ { code: '621024', name: '合水县' },
+ { code: '621025', name: '正宁县' },
+ { code: '621026', name: '宁县' },
+ { code: '621027', name: '镇原县' }
+ ]
+ },
+ {
+ code: '6211',
+ name: '定西市',
+ districts: [
+ { code: '621102', name: '安定区' },
+ { code: '621121', name: '通渭县' },
+ { code: '621122', name: '陇西县' },
+ { code: '621123', name: '渭源县' },
+ { code: '621124', name: '临洮县' },
+ { code: '621125', name: '漳县' },
+ { code: '621126', name: '岷县' }
+ ]
+ },
+ {
+ code: '6212',
+ name: '陇南市',
+ districts: [
+ { code: '621202', name: '武都区' },
+ { code: '621221', name: '成县' },
+ { code: '621222', name: '文县' },
+ { code: '621223', name: '宕昌县' },
+ { code: '621224', name: '康县' },
+ { code: '621225', name: '西和县' },
+ { code: '621226', name: '礼县' },
+ { code: '621227', name: '徽县' },
+ { code: '621228', name: '两当县' }
+ ]
+ },
+ {
+ code: '6229',
+ name: '临夏回族自治州',
+ districts: [
+ { code: '622901', name: '临夏市' },
+ { code: '622921', name: '临夏县' },
+ { code: '622922', name: '康乐县' },
+ { code: '622923', name: '永靖县' },
+ { code: '622924', name: '广河县' },
+ { code: '622925', name: '和政县' },
+ { code: '622926', name: '东乡族自治县' },
+ { code: '622927', name: '积石山保安族东乡族撒拉族自治县' }
+ ]
+ },
+ {
+ code: '6230',
+ name: '甘南藏族自治州',
+ districts: [
+ { code: '623001', name: '合作市' },
+ { code: '623021', name: '临潭县' },
+ { code: '623022', name: '卓尼县' },
+ { code: '623023', name: '舟曲县' },
+ { code: '623024', name: '迭部县' },
+ { code: '623025', name: '玛曲县' },
+ { code: '623026', name: '碌曲县' },
+ { code: '623027', name: '夏河县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '63',
+ name: '青海省',
+ cities: [
+ {
+ code: '6301',
+ name: '西宁市',
+ districts: [
+ { code: '630102', name: '城东区' },
+ { code: '630103', name: '城中区' },
+ { code: '630104', name: '城西区' },
+ { code: '630105', name: '城北区' },
+ { code: '630106', name: '湟中区' },
+ { code: '630121', name: '大通回族土族自治县' },
+ { code: '630123', name: '湟源县' }
+ ]
+ },
+ {
+ code: '6302',
+ name: '海东市',
+ districts: [
+ { code: '630202', name: '乐都区' },
+ { code: '630203', name: '平安区' },
+ { code: '630222', name: '民和回族土族自治县' },
+ { code: '630223', name: '互助土族自治县' },
+ { code: '630224', name: '化隆回族自治县' },
+ { code: '630225', name: '循化撒拉族自治县' }
+ ]
+ },
+ {
+ code: '6322',
+ name: '海北藏族自治州',
+ districts: [
+ { code: '632221', name: '门源回族自治县' },
+ { code: '632222', name: '祁连县' },
+ { code: '632223', name: '海晏县' },
+ { code: '632224', name: '刚察县' }
+ ]
+ },
+ {
+ code: '6323',
+ name: '黄南藏族自治州',
+ districts: [
+ { code: '632321', name: '同仁县' },
+ { code: '632322', name: '尖扎县' },
+ { code: '632323', name: '泽库县' },
+ { code: '632324', name: '河南蒙古族自治县' }
+ ]
+ },
+ {
+ code: '6325',
+ name: '海南藏族自治州',
+ districts: [
+ { code: '632521', name: '共和县' },
+ { code: '632522', name: '同德县' },
+ { code: '632523', name: '贵德县' },
+ { code: '632524', name: '兴海县' },
+ { code: '632525', name: '贵南县' }
+ ]
+ },
+ {
+ code: '6326',
+ name: '果洛藏族自治州',
+ districts: [
+ { code: '632621', name: '玛沁县' },
+ { code: '632622', name: '班玛县' },
+ { code: '632623', name: '甘德县' },
+ { code: '632624', name: '达日县' },
+ { code: '632625', name: '久治县' },
+ { code: '632626', name: '玛多县' }
+ ]
+ },
+ {
+ code: '6327',
+ name: '玉树藏族自治州',
+ districts: [
+ { code: '632701', name: '玉树市' },
+ { code: '632722', name: '杂多县' },
+ { code: '632723', name: '称多县' },
+ { code: '632724', name: '治多县' },
+ { code: '632725', name: '囊谦县' },
+ { code: '632726', name: '曲麻莱县' }
+ ]
+ },
+ {
+ code: '6328',
+ name: '海西蒙古族藏族自治州',
+ districts: [
+ { code: '632801', name: '格尔木市' },
+ { code: '632802', name: '德令哈市' },
+ { code: '632803', name: '茫崖市' },
+ { code: '632821', name: '乌兰县' },
+ { code: '632822', name: '都兰县' },
+ { code: '632823', name: '天峻县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '64',
+ name: '宁夏回族自治区',
+ cities: [
+ {
+ code: '6401',
+ name: '银川市',
+ districts: [
+ { code: '640104', name: '兴庆区' },
+ { code: '640105', name: '西夏区' },
+ { code: '640106', name: '金凤区' },
+ { code: '640121', name: '永宁县' },
+ { code: '640122', name: '贺兰县' },
+ { code: '640181', name: '灵武市' }
+ ]
+ },
+ {
+ code: '6402',
+ name: '石嘴山市',
+ districts: [
+ { code: '640202', name: '大武口区' },
+ { code: '640205', name: '惠农区' },
+ { code: '640221', name: '平罗县' }
+ ]
+ },
+ {
+ code: '6403',
+ name: '吴忠市',
+ districts: [
+ { code: '640302', name: '利通区' },
+ { code: '640303', name: '红寺堡区' },
+ { code: '640323', name: '盐池县' },
+ { code: '640324', name: '同心县' },
+ { code: '640381', name: '青铜峡市' }
+ ]
+ },
+ {
+ code: '6404',
+ name: '固原市',
+ districts: [
+ { code: '640402', name: '原州区' },
+ { code: '640422', name: '西吉县' },
+ { code: '640423', name: '隆德县' },
+ { code: '640424', name: '泾源县' },
+ { code: '640425', name: '彭阳县' }
+ ]
+ },
+ {
+ code: '6405',
+ name: '中卫市',
+ districts: [
+ { code: '640502', name: '沙坡头区' },
+ { code: '640521', name: '中宁县' },
+ { code: '640522', name: '海原县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '65',
+ name: '新疆维吾尔自治区',
+ cities: [
+ {
+ code: '6501',
+ name: '乌鲁木齐市',
+ districts: [
+ { code: '650102', name: '天山区' },
+ { code: '650103', name: '沙依巴克区' },
+ { code: '650104', name: '新市区' },
+ { code: '650105', name: '水磨沟区' },
+ { code: '650106', name: '头屯河区' },
+ { code: '650107', name: '达坂城区' },
+ { code: '650109', name: '米东区' },
+ { code: '650121', name: '乌鲁木齐县' }
+ ]
+ },
+ {
+ code: '6502',
+ name: '克拉玛依市',
+ districts: [
+ { code: '650202', name: '独山子区' },
+ { code: '650203', name: '克拉玛依区' },
+ { code: '650204', name: '白碱滩区' },
+ { code: '650205', name: '乌尔禾区' }
+ ]
+ },
+ {
+ code: '6504',
+ name: '吐鲁番市',
+ districts: [
+ { code: '650402', name: '高昌区' },
+ { code: '650421', name: '鄯善县' },
+ { code: '650422', name: '托克逊县' }
+ ]
+ },
+ {
+ code: '6505',
+ name: '哈密市',
+ districts: [
+ { code: '650502', name: '伊州区' },
+ { code: '650521', name: '巴里坤哈萨克自治县' },
+ { code: '650522', name: '伊吾县' }
+ ]
+ },
+ {
+ code: '6523',
+ name: '昌吉回族自治州',
+ districts: [
+ { code: '652301', name: '昌吉市' },
+ { code: '652302', name: '阜康市' },
+ { code: '652323', name: '呼图壁县' },
+ { code: '652324', name: '玛纳斯县' },
+ { code: '652325', name: '奇台县' },
+ { code: '652327', name: '吉木萨尔县' },
+ { code: '652328', name: '木垒哈萨克自治县' }
+ ]
+ },
+ {
+ code: '6527',
+ name: '博尔塔拉蒙古自治州',
+ districts: [
+ { code: '652701', name: '博乐市' },
+ { code: '652702', name: '阿拉山口市' },
+ { code: '652722', name: '精河县' },
+ { code: '652723', name: '温泉县' }
+ ]
+ },
+ {
+ code: '6528',
+ name: '巴音郭楞蒙古自治州',
+ districts: [
+ { code: '652801', name: '库尔勒市' },
+ { code: '652822', name: '轮台县' },
+ { code: '652823', name: '尉犁县' },
+ { code: '652824', name: '若羌县' },
+ { code: '652825', name: '且末县' },
+ { code: '652826', name: '焉耆回族自治县' },
+ { code: '652827', name: '和静县' },
+ { code: '652828', name: '和硕县' },
+ { code: '652829', name: '博湖县' }
+ ]
+ },
+ {
+ code: '6529',
+ name: '阿克苏地区',
+ districts: [
+ { code: '652901', name: '阿克苏市' },
+ { code: '652902', name: '库车市' },
+ { code: '652922', name: '温宿县' },
+ { code: '652924', name: '沙雅县' },
+ { code: '652925', name: '新和县' },
+ { code: '652926', name: '拜城县' },
+ { code: '652927', name: '乌什县' },
+ { code: '652928', name: '阿瓦提县' },
+ { code: '652929', name: '柯坪县' }
+ ]
+ },
+ {
+ code: '6530',
+ name: '克孜勒苏柯尔克孜自治州',
+ districts: [
+ { code: '653001', name: '阿图什市' },
+ { code: '653022', name: '阿克陶县' },
+ { code: '653023', name: '阿合奇县' },
+ { code: '653024', name: '乌恰县' }
+ ]
+ },
+ {
+ code: '6531',
+ name: '喀什地区',
+ districts: [
+ { code: '653101', name: '喀什市' },
+ { code: '653121', name: '疏附县' },
+ { code: '653122', name: '疏勒县' },
+ { code: '653123', name: '英吉沙县' },
+ { code: '653124', name: '泽普县' },
+ { code: '653125', name: '莎车县' },
+ { code: '653126', name: '叶城县' },
+ { code: '653127', name: '麦盖提县' },
+ { code: '653128', name: '岳普湖县' },
+ { code: '653129', name: '伽师县' },
+ { code: '653130', name: '巴楚县' },
+ { code: '653131', name: '塔什库尔干塔吉克自治县' }
+ ]
+ },
+ {
+ code: '6532',
+ name: '和田地区',
+ districts: [
+ { code: '653201', name: '和田市' },
+ { code: '653221', name: '和田县' },
+ { code: '653222', name: '墨玉县' },
+ { code: '653223', name: '皮山县' },
+ { code: '653224', name: '洛浦县' },
+ { code: '653225', name: '策勒县' },
+ { code: '653226', name: '于田县' },
+ { code: '653227', name: '民丰县' }
+ ]
+ },
+ {
+ code: '6540',
+ name: '伊犁哈萨克自治州',
+ districts: [
+ { code: '654002', name: '伊宁市' },
+ { code: '654003', name: '奎屯市' },
+ { code: '654004', name: '霍尔果斯市' },
+ { code: '654021', name: '伊宁县' },
+ { code: '654022', name: '察布查尔锡伯自治县' },
+ { code: '654023', name: '霍城县' },
+ { code: '654024', name: '巩留县' },
+ { code: '654025', name: '新源县' },
+ { code: '654026', name: '昭苏县' },
+ { code: '654027', name: '特克斯县' },
+ { code: '654028', name: '尼勒克县' }
+ ]
+ },
+ {
+ code: '6542',
+ name: '塔城地区',
+ districts: [
+ { code: '654201', name: '塔城市' },
+ { code: '654202', name: '乌苏市' },
+ { code: '654221', name: '额敏县' },
+ { code: '654223', name: '沙湾县' },
+ { code: '654224', name: '托里县' },
+ { code: '654225', name: '裕民县' },
+ { code: '654226', name: '和布克赛尔蒙古自治县' }
+ ]
+ },
+ {
+ code: '6543',
+ name: '阿勒泰地区',
+ districts: [
+ { code: '654301', name: '阿勒泰市' },
+ { code: '654321', name: '布尔津县' },
+ { code: '654322', name: '富蕴县' },
+ { code: '654323', name: '福海县' },
+ { code: '654324', name: '哈巴河县' },
+ { code: '654325', name: '青河县' },
+ { code: '654326', name: '吉木乃县' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '71',
+ name: '台湾省',
+ cities: [
+ {
+ code: '7101',
+ name: '台北市',
+ districts: [
+ { code: '710101', name: '中正区' },
+ { code: '710102', name: '大同区' },
+ { code: '710103', name: '中山区' },
+ { code: '710104', name: '松山区' },
+ { code: '710105', name: '大安区' },
+ { code: '710106', name: '万华区' },
+ { code: '710107', name: '信义区' },
+ { code: '710108', name: '士林区' },
+ { code: '710109', name: '北投区' },
+ { code: '710110', name: '内湖区' },
+ { code: '710111', name: '南港区' },
+ { code: '710112', name: '文山区' }
+ ]
+ },
+ {
+ code: '7102',
+ name: '高雄市',
+ districts: [
+ { code: '710201', name: '新兴区' },
+ { code: '710202', name: '前金区' },
+ { code: '710203', name: '苓雅区' },
+ { code: '710204', name: '盐埕区' },
+ { code: '710205', name: '鼓山区' },
+ { code: '710206', name: '旗津区' },
+ { code: '710207', name: '前镇区' },
+ { code: '710208', name: '三民区' },
+ { code: '710209', name: '左营区' },
+ { code: '710210', name: '楠梓区' },
+ { code: '710211', name: '小港区' },
+ { code: '710212', name: '凤山区' },
+ { code: '710213', name: '林园区' },
+ { code: '710214', name: '大寮区' },
+ { code: '710215', name: '大树区' },
+ { code: '710216', name: '大社区' },
+ { code: '710217', name: '仁武区' },
+ { code: '710218', name: '鸟松区' },
+ { code: '710219', name: '冈山区' },
+ { code: '710220', name: '桥头区' },
+ { code: '710221', name: '燕巢区' },
+ { code: '710222', name: '田寮区' },
+ { code: '710223', name: '阿莲区' },
+ { code: '710224', name: '路竹区' },
+ { code: '710225', name: '湖内区' },
+ { code: '710226', name: '茄萣区' },
+ { code: '710227', name: '永安区' },
+ { code: '710228', name: '弥陀区' },
+ { code: '710229', name: '梓官区' },
+ { code: '710230', name: '旗山区' },
+ { code: '710231', name: '美浓区' },
+ { code: '710232', name: '六龟区' },
+ { code: '710233', name: '甲仙区' },
+ { code: '710234', name: '杉林区' },
+ { code: '710235', name: '内门区' },
+ { code: '710236', name: '茂林区' },
+ { code: '710237', name: '桃源区' },
+ { code: '710238', name: '那玛夏区' }
+ ]
+ },
+ {
+ code: '7103',
+ name: '新北市',
+ districts: [
+ { code: '710301', name: '万里区' },
+ { code: '710302', name: '金山区' },
+ { code: '710303', name: '板桥区' },
+ { code: '710304', name: '汐止区' },
+ { code: '710305', name: '深坑区' },
+ { code: '710306', name: '石碇区' },
+ { code: '710307', name: '瑞芳区' },
+ { code: '710308', name: '平溪区' },
+ { code: '710309', name: '双溪区' },
+ { code: '710310', name: '贡寮区' },
+ { code: '710311', name: '新店区' },
+ { code: '710312', name: '坪林区' },
+ { code: '710313', name: '乌来区' },
+ { code: '710314', name: '永和区' },
+ { code: '710315', name: '中和区' },
+ { code: '710316', name: '土城区' },
+ { code: '710317', name: '三峡区' },
+ { code: '710318', name: '树林区' },
+ { code: '710319', name: '莺歌区' },
+ { code: '710320', name: '三重区' },
+ { code: '710321', name: '新庄区' },
+ { code: '710322', name: '泰山区' },
+ { code: '710323', name: '林口区' },
+ { code: '710324', name: '芦洲区' },
+ { code: '710325', name: '五股区' },
+ { code: '710326', name: '八里区' },
+ { code: '710327', name: '淡水区' },
+ { code: '710328', name: '三芝区' },
+ { code: '710329', name: '石门区' }
+ ]
+ },
+ {
+ code: '7104',
+ name: '台中市',
+ districts: [
+ { code: '710401', name: '中区' },
+ { code: '710402', name: '东区' },
+ { code: '710403', name: '南区' },
+ { code: '710404', name: '西区' },
+ { code: '710405', name: '北区' },
+ { code: '710406', name: '北屯区' },
+ { code: '710407', name: '西屯区' },
+ { code: '710408', name: '南屯区' },
+ { code: '710409', name: '太平区' },
+ { code: '710410', name: '大里区' },
+ { code: '710411', name: '雾峰区' },
+ { code: '710412', name: '乌日区' },
+ { code: '710413', name: '丰原区' },
+ { code: '710414', name: '后里区' },
+ { code: '710415', name: '石冈区' },
+ { code: '710416', name: '东势区' },
+ { code: '710417', name: '和平区' },
+ { code: '710418', name: '新社区' },
+ { code: '710419', name: '潭子区' },
+ { code: '710420', name: '大雅区' },
+ { code: '710421', name: '神冈区' },
+ { code: '710422', name: '大肚区' },
+ { code: '710423', name: '沙鹿区' },
+ { code: '710424', name: '龙井区' },
+ { code: '710425', name: '梧栖区' },
+ { code: '710426', name: '清水区' },
+ { code: '710427', name: '大甲区' },
+ { code: '710428', name: '外埔区' },
+ { code: '710429', name: '大安区' }
+ ]
+ },
+ {
+ code: '7105',
+ name: '台南市',
+ districts: [
+ { code: '710501', name: '中西区' },
+ { code: '710502', name: '东区' },
+ { code: '710503', name: '南区' },
+ { code: '710504', name: '北区' },
+ { code: '710505', name: '安平区' },
+ { code: '710506', name: '安南区' },
+ { code: '710507', name: '永康区' },
+ { code: '710508', name: '归仁区' },
+ { code: '710509', name: '新化区' },
+ { code: '710510', name: '左镇区' },
+ { code: '710511', name: '玉井区' },
+ { code: '710512', name: '楠西区' },
+ { code: '710513', name: '南化区' },
+ { code: '710514', name: '仁德区' },
+ { code: '710515', name: '关庙区' },
+ { code: '710516', name: '龙崎区' },
+ { code: '710517', name: '官田区' },
+ { code: '710518', name: '麻豆区' },
+ { code: '710519', name: '佳里区' },
+ { code: '710520', name: '西港区' },
+ { code: '710521', name: '七股区' },
+ { code: '710522', name: '将军区' },
+ { code: '710523', name: '学甲区' },
+ { code: '710524', name: '北门区' },
+ { code: '710525', name: '新营区' },
+ { code: '710526', name: '后壁区' },
+ { code: '710527', name: '白河区' },
+ { code: '710528', name: '东山区' },
+ { code: '710529', name: '六甲区' },
+ { code: '710530', name: '下营区' },
+ { code: '710531', name: '柳营区' },
+ { code: '710532', name: '盐水区' },
+ { code: '710533', name: '善化区' },
+ { code: '710534', name: '大内区' },
+ { code: '710535', name: '山上区' },
+ { code: '710536', name: '新市区' },
+ { code: '710537', name: '安定区' }
+ ]
+ },
+ {
+ code: '7106',
+ name: '桃园市',
+ districts: [
+ { code: '710601', name: '中坜区' },
+ { code: '710602', name: '平镇区' },
+ { code: '710603', name: '龙潭区' },
+ { code: '710604', name: '杨梅区' },
+ { code: '710605', name: '新屋区' },
+ { code: '710606', name: '观音区' },
+ { code: '710607', name: '桃园区' },
+ { code: '710608', name: '龟山区' },
+ { code: '710609', name: '八德区' },
+ { code: '710610', name: '大溪区' },
+ { code: '710611', name: '复兴区' },
+ { code: '710612', name: '大园区' },
+ { code: '710613', name: '芦竹区' }
+ ]
+ },
+ {
+ code: '7107',
+ name: '基隆市',
+ districts: [
+ { code: '710701', name: '仁爱区' },
+ { code: '710702', name: '信义区' },
+ { code: '710703', name: '中正区' },
+ { code: '710704', name: '中山区' },
+ { code: '710705', name: '安乐区' },
+ { code: '710706', name: '暖暖区' },
+ { code: '710707', name: '七堵区' }
+ ]
+ },
+ {
+ code: '7108',
+ name: '新竹市',
+ districts: [
+ { code: '710801', name: '东区' },
+ { code: '710802', name: '北区' },
+ { code: '710803', name: '香山区' }
+ ]
+ },
+ {
+ code: '7109',
+ name: '嘉义市',
+ districts: [
+ { code: '710901', name: '东区' },
+ { code: '710902', name: '西区' }
+ ]
+ },
+ {
+ code: '7110',
+ name: '新竹县',
+ districts: [
+ { code: '711001', name: '竹北市' },
+ { code: '711002', name: '湖口乡' },
+ { code: '711003', name: '新丰乡' },
+ { code: '711004', name: '新埔镇' },
+ { code: '711005', name: '关西镇' },
+ { code: '711006', name: '芎林乡' },
+ { code: '711007', name: '宝山乡' },
+ { code: '711008', name: '竹东镇' },
+ { code: '711009', name: '五峰乡' },
+ { code: '711010', name: '横山乡' },
+ { code: '711011', name: '尖石乡' },
+ { code: '711012', name: '北埔乡' },
+ { code: '711013', name: '峨眉乡' }
+ ]
+ },
+ {
+ code: '7111',
+ name: '苗栗县',
+ districts: [
+ { code: '711101', name: '竹南镇' },
+ { code: '711102', name: '头份市' },
+ { code: '711103', name: '三湾乡' },
+ { code: '711104', name: '南庄乡' },
+ { code: '711105', name: '狮潭乡' },
+ { code: '711106', name: '后龙镇' },
+ { code: '711107', name: '通霄镇' },
+ { code: '711108', name: '苑里镇' },
+ { code: '711109', name: '苗栗市' },
+ { code: '711110', name: '造桥乡' },
+ { code: '711111', name: '头屋乡' },
+ { code: '711112', name: '公馆乡' },
+ { code: '711113', name: '大湖乡' },
+ { code: '711114', name: '泰安乡' },
+ { code: '711115', name: '铜锣乡' },
+ { code: '711116', name: '三义乡' },
+ { code: '711117', name: '西湖乡' },
+ { code: '711118', name: '卓兰镇' }
+ ]
+ },
+ {
+ code: '7112',
+ name: '彰化县',
+ districts: [
+ { code: '711201', name: '彰化市' },
+ { code: '711202', name: '芬园乡' },
+ { code: '711203', name: '花坛乡' },
+ { code: '711204', name: '秀水乡' },
+ { code: '711205', name: '鹿港镇' },
+ { code: '711206', name: '福兴乡' },
+ { code: '711207', name: '线西乡' },
+ { code: '711208', name: '和美镇' },
+ { code: '711209', name: '伸港乡' },
+ { code: '711210', name: '员林市' },
+ { code: '711211', name: '社头乡' },
+ { code: '711212', name: '永靖乡' },
+ { code: '711213', name: '埔心乡' },
+ { code: '711214', name: '溪湖镇' },
+ { code: '711215', name: '大村乡' },
+ { code: '711216', name: '埔盐乡' },
+ { code: '711217', name: '田中镇' },
+ { code: '711218', name: '北斗镇' },
+ { code: '711219', name: '田尾乡' },
+ { code: '711220', name: '埤头乡' },
+ { code: '711221', name: '溪州乡' },
+ { code: '711222', name: '竹塘乡' },
+ { code: '711223', name: '二林镇' },
+ { code: '711224', name: '大城乡' },
+ { code: '711225', name: '芳苑乡' },
+ { code: '711226', name: '二水乡' }
+ ]
+ },
+ {
+ code: '7113',
+ name: '南投县',
+ districts: [
+ { code: '711301', name: '南投市' },
+ { code: '711302', name: '中寮乡' },
+ { code: '711303', name: '草屯镇' },
+ { code: '711304', name: '国姓乡' },
+ { code: '711305', name: '埔里镇' },
+ { code: '711306', name: '仁爱乡' },
+ { code: '711307', name: '名间乡' },
+ { code: '711308', name: '集集镇' },
+ { code: '711309', name: '水里乡' },
+ { code: '711310', name: '鱼池乡' },
+ { code: '711311', name: '信义乡' },
+ { code: '711312', name: '竹山镇' },
+ { code: '711313', name: '鹿谷乡' }
+ ]
+ },
+ {
+ code: '7114',
+ name: '云林县',
+ districts: [
+ { code: '711401', name: '斗南镇' },
+ { code: '711402', name: '大埤乡' },
+ { code: '711403', name: '虎尾镇' },
+ { code: '711404', name: '土库镇' },
+ { code: '711405', name: '褒忠乡' },
+ { code: '711406', name: '东势乡' },
+ { code: '711407', name: '台西乡' },
+ { code: '711408', name: '仑背乡' },
+ { code: '711409', name: '麦寮乡' },
+ { code: '711410', name: '斗六市' },
+ { code: '711411', name: '林内乡' },
+ { code: '711412', name: '古坑乡' },
+ { code: '711413', name: '莿桐乡' },
+ { code: '711414', name: '西螺镇' },
+ { code: '711415', name: '二仑乡' },
+ { code: '711416', name: '北港镇' },
+ { code: '711417', name: '水林乡' },
+ { code: '711418', name: '口湖乡' },
+ { code: '711419', name: '四湖乡' },
+ { code: '711420', name: '元长乡' }
+ ]
+ },
+ {
+ code: '7115',
+ name: '嘉义县',
+ districts: [
+ { code: '711501', name: '番路乡' },
+ { code: '711502', name: '梅山乡' },
+ { code: '711503', name: '竹崎乡' },
+ { code: '711504', name: '阿里山乡' },
+ { code: '711505', name: '中埔乡' },
+ { code: '711506', name: '大埔乡' },
+ { code: '711507', name: '水上乡' },
+ { code: '711508', name: '鹿草乡' },
+ { code: '711509', name: '太保市' },
+ { code: '711510', name: '朴子市' },
+ { code: '711511', name: '东石乡' },
+ { code: '711512', name: '六脚乡' },
+ { code: '711513', name: '新港乡' },
+ { code: '711514', name: '民雄乡' },
+ { code: '711515', name: '大林镇' },
+ { code: '711516', name: '溪口乡' },
+ { code: '711517', name: '义竹乡' },
+ { code: '711518', name: '布袋镇' }
+ ]
+ },
+ {
+ code: '7116',
+ name: '屏东县',
+ districts: [
+ { code: '711601', name: '屏东市' },
+ { code: '711602', name: '三地门乡' },
+ { code: '711603', name: '雾台乡' },
+ { code: '711604', name: '玛家乡' },
+ { code: '711605', name: '九如乡' },
+ { code: '711606', name: '里港乡' },
+ { code: '711607', name: '高树乡' },
+ { code: '711608', name: '盐埔乡' },
+ { code: '711609', name: '长治乡' },
+ { code: '711610', name: '麟洛乡' },
+ { code: '711611', name: '竹田乡' },
+ { code: '711612', name: '内埔乡' },
+ { code: '711613', name: '万丹乡' },
+ { code: '711614', name: '潮州镇' },
+ { code: '711615', name: '泰武乡' },
+ { code: '711616', name: '来义乡' },
+ { code: '711617', name: '万峦乡' },
+ { code: '711618', name: '崁顶乡' },
+ { code: '711619', name: '新埤乡' },
+ { code: '711620', name: '南州乡' },
+ { code: '711621', name: '林边乡' },
+ { code: '711622', name: '东港镇' },
+ { code: '711623', name: '琉球乡' },
+ { code: '711624', name: '佳冬乡' },
+ { code: '711625', name: '新园乡' },
+ { code: '711626', name: '枋寮乡' },
+ { code: '711627', name: '枋山乡' },
+ { code: '711628', name: '春日乡' },
+ { code: '711629', name: '狮子乡' },
+ { code: '711630', name: '车城乡' },
+ { code: '711631', name: '牡丹乡' },
+ { code: '711632', name: '恒春镇' },
+ { code: '711633', name: '满州乡' }
+ ]
+ },
+ {
+ code: '7117',
+ name: '宜兰县',
+ districts: [
+ { code: '711701', name: '宜兰市' },
+ { code: '711702', name: '头城镇' },
+ { code: '711703', name: '礁溪乡' },
+ { code: '711704', name: '壮围乡' },
+ { code: '711705', name: '员山乡' },
+ { code: '711706', name: '罗东镇' },
+ { code: '711707', name: '三星乡' },
+ { code: '711708', name: '大同乡' },
+ { code: '711709', name: '五结乡' },
+ { code: '711710', name: '冬山乡' },
+ { code: '711711', name: '苏澳镇' },
+ { code: '711712', name: '南澳乡' }
+ ]
+ },
+ {
+ code: '7118',
+ name: '花莲县',
+ districts: [
+ { code: '711801', name: '花莲市' },
+ { code: '711802', name: '新城乡' },
+ { code: '711803', name: '秀林乡' },
+ { code: '711804', name: '吉安乡' },
+ { code: '711805', name: '寿丰乡' },
+ { code: '711806', name: '凤林镇' },
+ { code: '711807', name: '光复乡' },
+ { code: '711808', name: '丰滨乡' },
+ { code: '711809', name: '瑞穗乡' },
+ { code: '711810', name: '万荣乡' },
+ { code: '711811', name: '玉里镇' },
+ { code: '711812', name: '卓溪乡' },
+ { code: '711813', name: '富里乡' }
+ ]
+ },
+ {
+ code: '7119',
+ name: '台东县',
+ districts: [
+ { code: '711901', name: '台东市' },
+ { code: '711902', name: '绿岛乡' },
+ { code: '711903', name: '兰屿乡' },
+ { code: '711904', name: '延平乡' },
+ { code: '711905', name: '卑南乡' },
+ { code: '711906', name: '鹿野乡' },
+ { code: '711907', name: '关山镇' },
+ { code: '711908', name: '海端乡' },
+ { code: '711909', name: '池上乡' },
+ { code: '711910', name: '东河乡' },
+ { code: '711911', name: '成功镇' },
+ { code: '711912', name: '长滨乡' },
+ { code: '711913', name: '太麻里乡' },
+ { code: '711914', name: '金峰乡' },
+ { code: '711915', name: '大武乡' },
+ { code: '711916', name: '达仁乡' }
+ ]
+ },
+ {
+ code: '7120',
+ name: '澎湖县',
+ districts: [
+ { code: '712001', name: '马公市' },
+ { code: '712002', name: '西屿乡' },
+ { code: '712003', name: '望安乡' },
+ { code: '712004', name: '七美乡' },
+ { code: '712005', name: '白沙乡' },
+ { code: '712006', name: '湖西乡' }
+ ]
+ },
+ {
+ code: '7121',
+ name: '金门县',
+ districts: [
+ { code: '712101', name: '金沙镇' },
+ { code: '712102', name: '金湖镇' },
+ { code: '712103', name: '金宁乡' },
+ { code: '712104', name: '金城镇' },
+ { code: '712105', name: '烈屿乡' },
+ { code: '712106', name: '乌坵乡' }
+ ]
+ },
+ {
+ code: '7122',
+ name: '连江县',
+ districts: [
+ { code: '712201', name: '南竿乡' },
+ { code: '712202', name: '北竿乡' },
+ { code: '712203', name: '莒光乡' },
+ { code: '712204', name: '东引乡' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '81',
+ name: '香港特别行政区',
+ cities: [
+ {
+ code: '8101',
+ name: '香港岛',
+ districts: [
+ { code: '810101', name: '中西区' },
+ { code: '810102', name: '湾仔区' },
+ { code: '810103', name: '东区' },
+ { code: '810104', name: '南区' }
+ ]
+ },
+ {
+ code: '8102',
+ name: '九龙',
+ districts: [
+ { code: '810201', name: '油尖旺区' },
+ { code: '810202', name: '深水埗区' },
+ { code: '810203', name: '九龙城区' },
+ { code: '810204', name: '黄大仙区' },
+ { code: '810205', name: '观塘区' }
+ ]
+ },
+ {
+ code: '8103',
+ name: '新界',
+ districts: [
+ { code: '810301', name: '北区' },
+ { code: '810302', name: '大埔区' },
+ { code: '810303', name: '沙田区' },
+ { code: '810304', name: '西贡区' },
+ { code: '810305', name: '元朗区' },
+ { code: '810306', name: '屯门区' },
+ { code: '810307', name: '荃湾区' },
+ { code: '810308', name: '葵青区' },
+ { code: '810309', name: '离岛区' }
+ ]
+ }
+ ]
+ },
+ {
+ code: '82',
+ name: '澳门特别行政区',
+ cities: [
+ {
+ code: '8201',
+ name: '澳门半岛',
+ districts: [
+ { code: '820101', name: '花地玛堂区' },
+ { code: '820102', name: '圣安多尼堂区' },
+ { code: '820103', name: '大堂区' },
+ { code: '820104', name: '望德堂区' },
+ { code: '820105', name: '风顺堂区' }
+ ]
+ },
+ {
+ code: '8202',
+ name: '氹仔',
+ districts: [
+ { code: '820201', name: '嘉模堂区' }
+ ]
+ },
+ {
+ code: '8203',
+ name: '路环',
+ districts: [
+ { code: '820301', name: '圣方济各堂区' }
+ ]
+ }
+ ]
+ }
+];
+
+// 获取国家列表
+export function getCountryList() {
+ return Object.entries(countries).map(([code, name]) => ({
+ code: code.toUpperCase(),
+ name,
+ label: name,
+ value: name
+ }));
+}
+
+// 获取中国省份列表
+export function getProvinceList() {
+ return chinaRegions.map(province => ({
+ code: province.code,
+ name: province.name,
+ label: province.name,
+ value: province.name
+ }));
+}
+
+// 根据省份获取城市列表
+export function getCitiesByProvince(provinceName: string) {
+ const province = chinaRegions.find(p => p.name === provinceName);
+ if (!province) return [];
+
+ return province.cities.map(city => ({
+ code: city.code,
+ name: city.name,
+ label: city.name,
+ value: city.name
+ }));
+}
+
+// 根据省份和城市获取区县列表
+export function getDistrictsByCity(provinceName: string, cityName: string) {
+ const province = chinaRegions.find(p => p.name === provinceName);
+ if (!province) return [];
+
+ const city = province.cities.find(c => c.name === cityName);
+ if (!city) return [];
+
+ return city.districts.map(district => ({
+ code: district.code,
+ name: district.name,
+ label: district.name,
+ value: district.name
+ }));
+}
+
+// 获取完整的中国地区数据(用于兼容现有代码)
+export function getChinaRegions() {
+ return chinaRegions;
+}
+
+// 简化的省份数据(兼容旧的chinaStates)
+export const chinaStates = [
+ '安徽省',
+ '北京市',
+ '重庆市',
+ '福建省',
+ '甘肃省',
+ '广东省',
+ '广西壮族自治区',
+ '贵州省',
+ '海南省',
+ '河北省',
+ '河南省',
+ '黑龙江省',
+ '湖北省',
+ '湖南省',
+ '吉林省',
+ '江苏省',
+ '江西省',
+ '辽宁省',
+ '内蒙古自治区',
+ '宁夏回族自治区',
+ '青海省',
+ '山东省',
+ '山西省',
+ '陕西省',
+ '上海市',
+ '四川省',
+ '天津市',
+ '西藏自治区',
+ '新疆维吾尔自治区',
+ '云南省',
+ '浙江省',
+ '香港特别行政区',
+ '澳门特别行政区',
+ '台湾省'
+];
\ No newline at end of file
diff --git a/dashboard/vite.config.ts.timestamp-1754345788699-f26a21cb45774.mjs b/dashboard/vite.config.ts.timestamp-1754345788699-f26a21cb45774.mjs
new file mode 100644
index 0000000..9f816a0
--- /dev/null
+++ b/dashboard/vite.config.ts.timestamp-1754345788699-f26a21cb45774.mjs
@@ -0,0 +1,69 @@
+// vite.config.ts
+import path from "path";
+import { defineConfig } from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/vite/dist/node/index.js";
+import vue from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/@vitejs/plugin-vue/dist/index.mjs";
+import vueJsx from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/@vitejs/plugin-vue-jsx/dist/index.mjs";
+import jingrowui from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/jingrow-ui/vite.js";
+import pluginRewriteAll from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/vite-plugin-rewrite-all/dist/index.mjs";
+import Components from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/unplugin-vue-components/dist/vite.mjs";
+import Icons from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/unplugin-icons/dist/vite.mjs";
+import IconsResolver from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/unplugin-icons/dist/resolver.mjs";
+import { sentryVitePlugin } from "file:///home/jingrow/jingrow-bench/apps/jcloud/dashboard/node_modules/@sentry/vite-plugin/dist/esm/index.mjs";
+var __vite_injected_original_dirname = "/home/jingrow/jingrow-bench/apps/jcloud/dashboard";
+var vite_config_default = defineConfig({
+ plugins: [
+ vue(),
+ vueJsx(),
+ pluginRewriteAll(),
+ jingrowui(),
+ Components({
+ dirs: [
+ "src/components",
+ // 'src2/components',
+ "node_modules/jingrow-ui/src/components"
+ ],
+ resolvers: [IconsResolver()]
+ }),
+ Icons(),
+ sentryVitePlugin({
+ url: process.env.SENTRY_URL,
+ org: process.env.SENTRY_ORG,
+ project: process.env.SENTRY_PROJECT,
+ applicationKey: "jcloud-dashboard",
+ authToken: process.env.SENTRY_AUTH_TOKEN
+ })
+ ],
+ resolve: {
+ alias: {
+ "@": path.resolve(__vite_injected_original_dirname, "src")
+ }
+ },
+ optimizeDeps: {
+ include: ["feather-icons", "showdown"]
+ },
+ build: {
+ outDir: "../jcloud/public/dashboard",
+ emptyOutDir: true,
+ sourcemap: true,
+ target: "es2015",
+ rollupOptions: {
+ input: {
+ main: path.resolve(__vite_injected_original_dirname, "index.html")
+ }
+ }
+ },
+ // @ts-ignore
+ test: {
+ globals: true,
+ environment: "jsdom",
+ setupFiles: "src/tests/setup/msw.js",
+ coverage: {
+ extension: [".vue", ".js"],
+ all: true
+ }
+ }
+});
+export {
+ vite_config_default as default
+};
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9qaW5ncm93L2ppbmdyb3ctYmVuY2gvYXBwcy9qY2xvdWQvZGFzaGJvYXJkXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvaG9tZS9qaW5ncm93L2ppbmdyb3ctYmVuY2gvYXBwcy9qY2xvdWQvZGFzaGJvYXJkL3ZpdGUuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9ob21lL2ppbmdyb3cvamluZ3Jvdy1iZW5jaC9hcHBzL2pjbG91ZC9kYXNoYm9hcmQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcclxuaW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XHJcbmltcG9ydCB2dWUgZnJvbSAnQHZpdGVqcy9wbHVnaW4tdnVlJztcclxuaW1wb3J0IHZ1ZUpzeCBmcm9tICdAdml0ZWpzL3BsdWdpbi12dWUtanN4JztcclxuaW1wb3J0IGppbmdyb3d1aSBmcm9tICdqaW5ncm93LXVpL3ZpdGUnO1xyXG5pbXBvcnQgcGx1Z2luUmV3cml0ZUFsbCBmcm9tICd2aXRlLXBsdWdpbi1yZXdyaXRlLWFsbCc7XHJcbmltcG9ydCBDb21wb25lbnRzIGZyb20gJ3VucGx1Z2luLXZ1ZS1jb21wb25lbnRzL3ZpdGUnO1xyXG5pbXBvcnQgSWNvbnMgZnJvbSAndW5wbHVnaW4taWNvbnMvdml0ZSc7XHJcbmltcG9ydCBJY29uc1Jlc29sdmVyIGZyb20gJ3VucGx1Z2luLWljb25zL3Jlc29sdmVyJztcclxuaW1wb3J0IHsgc2VudHJ5Vml0ZVBsdWdpbiB9IGZyb20gJ0BzZW50cnkvdml0ZS1wbHVnaW4nO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcclxuXHRwbHVnaW5zOiBbXHJcblx0XHR2dWUoKSxcclxuXHRcdHZ1ZUpzeCgpLFxyXG5cdFx0cGx1Z2luUmV3cml0ZUFsbCgpLFxyXG5cdFx0amluZ3Jvd3VpKCksXHJcblx0XHRDb21wb25lbnRzKHtcclxuXHRcdFx0ZGlyczogW1xyXG5cdFx0XHRcdCdzcmMvY29tcG9uZW50cycsXHJcblx0XHRcdFx0Ly8gJ3NyYzIvY29tcG9uZW50cycsXHJcblx0XHRcdFx0J25vZGVfbW9kdWxlcy9qaW5ncm93LXVpL3NyYy9jb21wb25lbnRzJ1xyXG5cdFx0XHRdLFxyXG5cdFx0XHRyZXNvbHZlcnM6IFtJY29uc1Jlc29sdmVyKCldXHJcblx0XHR9KSxcclxuXHRcdEljb25zKCksXHJcblx0XHRzZW50cnlWaXRlUGx1Z2luKHtcclxuXHRcdFx0dXJsOiBwcm9jZXNzLmVudi5TRU5UUllfVVJMLFxyXG5cdFx0XHRvcmc6IHByb2Nlc3MuZW52LlNFTlRSWV9PUkcsXHJcblx0XHRcdHByb2plY3Q6IHByb2Nlc3MuZW52LlNFTlRSWV9QUk9KRUNULFxyXG5cdFx0XHRhcHBsaWNhdGlvbktleTogJ2pjbG91ZC1kYXNoYm9hcmQnLFxyXG5cdFx0XHRhdXRoVG9rZW46IHByb2Nlc3MuZW52LlNFTlRSWV9BVVRIX1RPS0VOXHJcblx0XHR9KVxyXG5cdF0sXHJcblx0cmVzb2x2ZToge1xyXG5cdFx0YWxpYXM6IHtcclxuXHRcdFx0J0AnOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnc3JjJylcclxuXHRcdH1cclxuXHR9LFxyXG5cdG9wdGltaXplRGVwczoge1xyXG5cdFx0aW5jbHVkZTogWydmZWF0aGVyLWljb25zJywgJ3Nob3dkb3duJ11cclxuXHR9LFxyXG5cdGJ1aWxkOiB7XHJcblx0XHRvdXREaXI6ICcuLi9qY2xvdWQvcHVibGljL2Rhc2hib2FyZCcsXHJcblx0XHRlbXB0eU91dERpcjogdHJ1ZSxcclxuXHRcdHNvdXJjZW1hcDogdHJ1ZSxcclxuXHRcdHRhcmdldDogJ2VzMjAxNScsXHJcblx0XHRyb2xsdXBPcHRpb25zOiB7XHJcblx0XHRcdGlucHV0OiB7XHJcblx0XHRcdFx0bWFpbjogcGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJ2luZGV4Lmh0bWwnKVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fSxcclxuXHQvLyBAdHMtaWdub3JlXHJcblx0dGVzdDoge1xyXG5cdFx0Z2xvYmFsczogdHJ1ZSxcclxuXHRcdGVudmlyb25tZW50OiAnanNkb20nLFxyXG5cdFx0c2V0dXBGaWxlczogJ3NyYy90ZXN0cy9zZXR1cC9tc3cuanMnLFxyXG5cdFx0Y292ZXJhZ2U6IHtcclxuXHRcdFx0ZXh0ZW5zaW9uOiBbJy52dWUnLCAnLmpzJ10sXHJcblx0XHRcdGFsbDogdHJ1ZVxyXG5cdFx0fVxyXG5cdH1cclxufSk7XHJcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBcVUsT0FBTyxVQUFVO0FBQ3RWLFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUNoQixPQUFPLFlBQVk7QUFDbkIsT0FBTyxlQUFlO0FBQ3RCLE9BQU8sc0JBQXNCO0FBQzdCLE9BQU8sZ0JBQWdCO0FBQ3ZCLE9BQU8sV0FBVztBQUNsQixPQUFPLG1CQUFtQjtBQUMxQixTQUFTLHdCQUF3QjtBQVRqQyxJQUFNLG1DQUFtQztBQVd6QyxJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMzQixTQUFTO0FBQUEsSUFDUixJQUFJO0FBQUEsSUFDSixPQUFPO0FBQUEsSUFDUCxpQkFBaUI7QUFBQSxJQUNqQixVQUFVO0FBQUEsSUFDVixXQUFXO0FBQUEsTUFDVixNQUFNO0FBQUEsUUFDTDtBQUFBO0FBQUEsUUFFQTtBQUFBLE1BQ0Q7QUFBQSxNQUNBLFdBQVcsQ0FBQyxjQUFjLENBQUM7QUFBQSxJQUM1QixDQUFDO0FBQUEsSUFDRCxNQUFNO0FBQUEsSUFDTixpQkFBaUI7QUFBQSxNQUNoQixLQUFLLFFBQVEsSUFBSTtBQUFBLE1BQ2pCLEtBQUssUUFBUSxJQUFJO0FBQUEsTUFDakIsU0FBUyxRQUFRLElBQUk7QUFBQSxNQUNyQixnQkFBZ0I7QUFBQSxNQUNoQixXQUFXLFFBQVEsSUFBSTtBQUFBLElBQ3hCLENBQUM7QUFBQSxFQUNGO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUixPQUFPO0FBQUEsTUFDTixLQUFLLEtBQUssUUFBUSxrQ0FBVyxLQUFLO0FBQUEsSUFDbkM7QUFBQSxFQUNEO0FBQUEsRUFDQSxjQUFjO0FBQUEsSUFDYixTQUFTLENBQUMsaUJBQWlCLFVBQVU7QUFBQSxFQUN0QztBQUFBLEVBQ0EsT0FBTztBQUFBLElBQ04sUUFBUTtBQUFBLElBQ1IsYUFBYTtBQUFBLElBQ2IsV0FBVztBQUFBLElBQ1gsUUFBUTtBQUFBLElBQ1IsZUFBZTtBQUFBLE1BQ2QsT0FBTztBQUFBLFFBQ04sTUFBTSxLQUFLLFFBQVEsa0NBQVcsWUFBWTtBQUFBLE1BQzNDO0FBQUEsSUFDRDtBQUFBLEVBQ0Q7QUFBQTtBQUFBLEVBRUEsTUFBTTtBQUFBLElBQ0wsU0FBUztBQUFBLElBQ1QsYUFBYTtBQUFBLElBQ2IsWUFBWTtBQUFBLElBQ1osVUFBVTtBQUFBLE1BQ1QsV0FBVyxDQUFDLFFBQVEsS0FBSztBQUFBLE1BQ3pCLEtBQUs7QUFBQSxJQUNOO0FBQUEsRUFDRDtBQUNELENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==
diff --git a/guide-to-testing.md b/guide-to-testing.md
index decbcaf..338223d 100644
--- a/guide-to-testing.md
+++ b/guide-to-testing.md
@@ -3,7 +3,7 @@
Before we get into writing tests, please make sure you have pre-commit hook for
styling tools setup so CI won't fail from these
-Instructions [here](http://git.jingrow.com:3000/jingrow/jcloud/issues/424#issuecomment-1193375098)
+Instructions [here](http://git.jingrow.com/jingrow/jcloud/issues/424#issuecomment-1193375098)
# Writing Tests for Jcloud
@@ -66,16 +66,16 @@ keep reading.
There's also a decorator you can use to fake the result of an agent job. For
example, you may do it like so:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/983631ccb59f88e57fd60fdad1615e9abd87d99f/jcloud/api/tests/test_site.py#L243-L247
+http://git.jingrow.com/jingrow/jcloud/blob/983631ccb59f88e57fd60fdad1615e9abd87d99f/jcloud/api/tests/test_site.py#L243-L247
This way you can use the name of the type of job and fake a response from the same.
You may also fake the output obtained from the job which you can then use to test the callback that uses the same:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/983631ccb59f88e57fd60fdad1615e9abd87d99f/jcloud/api/tests/test_site.py#L305-L323
+http://git.jingrow.com/jingrow/jcloud/blob/983631ccb59f88e57fd60fdad1615e9abd87d99f/jcloud/api/tests/test_site.py#L305-L323
It is also possible to fake multiple jobs in the same context, for when multiple jobs are processed in the same request or job:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/983631ccb59f88e57fd60fdad1615e9abd87d99f/jcloud/jcloud/pagetype/site_migration/test_site_migration.py#L29-L77
+http://git.jingrow.com/jingrow/jcloud/blob/983631ccb59f88e57fd60fdad1615e9abd87d99f/jcloud/jcloud/pagetype/site_migration/test_site_migration.py#L29-L77
> Note that with this, you can't fake 2 results for the same type of job. This is still a limitation. As a workaround, you can have multiple `with` statements for such cases.
@@ -100,7 +100,7 @@ test files (for organization reasons). These functions will be doing the bare
minimum to make a valid document of that pagetype.
Eg: `create_test_bench` in `test_bench.py` can be imported and used whenever
-you need a valid bench (which itself has dependencies on many other doctypes)
+you need a valid bench (which itself has dependencies on many other pagetypes)
You can also add default args to these utility functions as you come across the
need. Just append to end so you won't have to rewrite pre-existing tests.
@@ -112,7 +112,7 @@ tell what it's trying to test is supposed without even having to read the code.
Making the method name small is pointless; we're never going to reference this
method anywhere in code, ever. Eg:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/2503e523284fb905eca60acf3271d3fb1dccbc3f/jcloud/jcloud/pagetype/site/test_site.py#L215-L228
+http://git.jingrow.com/jingrow/jcloud/blob/2503e523284fb905eca60acf3271d3fb1dccbc3f/jcloud/jcloud/pagetype/site/test_site.py#L215-L228
You can also go the extra mile and write a function docstring. This docstring
will be shown in the output when the testrunner detects that the test has
@@ -150,14 +150,14 @@ class TestBench(unittest.TestCase):
You can also use the patch decorator on test methods too. Eg:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/6dd6b2c8193b04f1aec1601d52ba09ce9dca8dfe/jcloud/tests/test_cleanup.py#L280-L290
+http://git.jingrow.com/jingrow/jcloud/blob/6dd6b2c8193b04f1aec1601d52ba09ce9dca8dfe/jcloud/tests/test_cleanup.py#L280-L290
The decorator passes the mocked function (which is a `Mock()` object) along as
an argument, so you can later do asserts on it (if you want to).
You can even use the decorator as context manager if you don't want to mock
things for the entirety of the test.
-http://git.jingrow.com:3000/jingrow/jcloud/blob/6dd6b2c8193b04f1aec1601d52ba09ce9dca8dfe/jcloud/tests/test_audit.py#L97-L102
+http://git.jingrow.com/jingrow/jcloud/blob/6dd6b2c8193b04f1aec1601d52ba09ce9dca8dfe/jcloud/tests/test_audit.py#L97-L102
Here, we're actually faking the output of the function which usually calls a
remote endpoint that's out of our control by adding the `new` argument to the
@@ -174,7 +174,7 @@ method.
> case you want to do asserts on it, you can use the `wraps` kwarg instead of
> new). Eg:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/23711e2799f2d24dfd7bbe2b6cd148f54f4b253b/jcloud/jcloud/pagetype/database_server_mariadb_variable/test_database_server_mariadb_variable.py#L138-L155
+http://git.jingrow.com/jingrow/jcloud/blob/23711e2799f2d24dfd7bbe2b6cd148f54f4b253b/jcloud/jcloud/pagetype/database_server_mariadb_variable/test_database_server_mariadb_variable.py#L138-L155
Here, we check what args was Ansible constructor was called with.
@@ -200,9 +200,9 @@ control/predict when the background job will run and finish. So, when your code
involves creating a background job, we can simply mock the call so that it runs
in foreground instead. There's a utility method you can use to achieve this with ease:
-http://git.jingrow.com:3000/jingrow/jcloud/blob/23711e2799f2d24dfd7bbe2b6cd148f54f4b253b/jcloud/jcloud/pagetype/database_server_mariadb_variable/test_database_server_mariadb_variable.py#L12
+http://git.jingrow.com/jingrow/jcloud/blob/23711e2799f2d24dfd7bbe2b6cd148f54f4b253b/jcloud/jcloud/pagetype/database_server_mariadb_variable/test_database_server_mariadb_variable.py#L12
-http://git.jingrow.com:3000/jingrow/jcloud/blob/23711e2799f2d24dfd7bbe2b6cd148f54f4b253b/jcloud/jcloud/pagetype/database_server_mariadb_variable/test_database_server_mariadb_variable.py#L104-L108
+http://git.jingrow.com/jingrow/jcloud/blob/23711e2799f2d24dfd7bbe2b6cd148f54f4b253b/jcloud/jcloud/pagetype/database_server_mariadb_variable/test_database_server_mariadb_variable.py#L104-L108
## Running tests
diff --git a/jcloud/api/account.py b/jcloud/api/account.py
index d4153dc..0e9ec02 100644
--- a/jcloud/api/account.py
+++ b/jcloud/api/account.py
@@ -73,6 +73,7 @@ def signup(email, product=None, referrer=None):
def signup_with_username(username, password, email=None, phone_number=None, referrer=None, product=None):
"""
使用用户名注册新账户,邮箱和手机号为可选
+ 返回格式: {"success": bool, "message": str, "dashboard_route": str}
"""
from jingrow.utils import validate_email_address
from jingrow.utils.password import update_password
@@ -83,11 +84,17 @@ def signup_with_username(username, password, email=None, phone_number=None, refe
try:
# 验证用户名
if not username or len(username) < 3:
- jingrow.throw("用户名至少需要3个字符")
+ return {
+ "success": False,
+ "message": "用户名至少需要3个字符"
+ }
# 检查用户名是否已存在
if jingrow.db.exists("User", {"username": username}):
- jingrow.throw("该用户名已被使用")
+ return {
+ "success": False,
+ "message": "该用户名已被使用"
+ }
# 如果提供了邮箱,验证邮箱格式并检查是否已存在
user_email = None
@@ -95,18 +102,30 @@ def signup_with_username(username, password, email=None, phone_number=None, refe
try:
validate_email_address(email, True)
except:
- jingrow.throw("请输入有效的邮箱地址")
+ return {
+ "success": False,
+ "message": "请输入有效的邮箱地址"
+ }
if jingrow.db.exists("User", {"email": email}):
- jingrow.throw("该邮箱已被注册")
+ return {
+ "success": False,
+ "message": "该邮箱已被注册"
+ }
user_email = email
# 如果提供了手机号,验证手机号格式并检查是否已存在
if phone_number:
if not re.match(r'^1[3-9]\d{9}$', phone_number):
- jingrow.throw("请输入有效的手机号码")
+ return {
+ "success": False,
+ "message": "请输入有效的手机号码"
+ }
if jingrow.db.exists("User", {"mobile_no": phone_number}):
- jingrow.throw("该手机号已被注册")
+ return {
+ "success": False,
+ "message": "该手机号已被注册"
+ }
# 创建用户,但先不设置密码
user_pg = {
@@ -179,11 +198,20 @@ def signup_with_username(username, password, email=None, phone_number=None, refe
jingrow.db.commit()
- # 返回与标准流程一致的响应
+ # 返回成功响应
return {
+ "success": True,
+ "message": "注册成功",
"dashboard_route": ""
}
+ except Exception as e:
+ # 捕获其他异常并返回错误消息
+ jingrow.log_error("signup_with_username 错误", str(e))
+ return {
+ "success": False,
+ "message": f"注册失败: {str(e)}"
+ }
finally:
# 恢复原始用户
jingrow.set_user(current_user)
@@ -1205,10 +1233,10 @@ def get_permission_options(name, ptype):
available_actions,
)
- doctypes = jingrow.get_all("Jcloud Method Permission", pluck="document_type", distinct=True)
+ pagetypes = jingrow.get_all("Jcloud Method Permission", pluck="document_type", distinct=True)
options = []
- for pagetype in doctypes:
+ for pagetype in pagetypes:
pg = jingrow.qb.PageType(pagetype)
perm_pg = jingrow.qb.PageType("Jcloud User Permission")
subtable = (
diff --git a/jcloud/api/aliyun_server_light.py b/jcloud/api/aliyun_server_light.py
new file mode 100644
index 0000000..ab229c2
--- /dev/null
+++ b/jcloud/api/aliyun_server_light.py
@@ -0,0 +1,1978 @@
+import jingrow
+import os
+import sys
+import json
+import random
+import time
+import uuid
+from datetime import datetime
+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
+
+from jcloud.utils import get_current_team
+
+
+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 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 force_reboot_instances(self, instance_ids, region_id='cn-shanghai'):
+ """强制重启实例(支持批量)"""
+ client = self._get_client(region_id)
+ try:
+ if isinstance(instance_ids, list):
+ instance_ids_str = json.dumps(instance_ids)
+ else:
+ instance_ids_str = instance_ids
+
+ request = swas__open20200601_models.RebootInstancesRequest(
+ region_id=region_id,
+ force_reboot=True,
+ instance_ids=instance_ids_str
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.reboot_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"强制重启实例 {instance_ids} 时发生错误: {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, 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
+ )
+ 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=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
+ )
+ if image_id:
+ request.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 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'):
+ """获取实例列表"""
+ 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': '获取地域列表失败'}
+
+ 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 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
+ )
+ 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"删除密钥对 {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)
+ 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': '获取实例详细信息失败'}
+
+ def get_instance_upgrade_plans(self, instance_id, region_id='cn-shanghai'):
+ """获取指定实例可升级的套餐列表"""
+ client = self._get_client(region_id)
+ try:
+ request = swas__open20200601_models.ListInstancePlansModificationRequest(
+ region_id=region_id,
+ instance_id=instance_id
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.list_instance_plans_modification_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 run_command(self, instance_id, command_content, parameters=None, command_name='CustomCommand', region_id='cn-shanghai', timeout=3600):
+ """在实例上执行命令"""
+ client = self._get_client(region_id)
+ try:
+ request = swas__open20200601_models.RunCommandRequest(
+ region_id=region_id,
+ command_content=command_content,
+ type='RunShellScript',
+ enable_parameter=True if parameters else False,
+ parameters=parameters or {},
+ instance_id=instance_id,
+ name=command_name,
+ timeout=timeout
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.run_command_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 create_firewall_rule(self, instance_id, rule_protocol, port, remark=None, region_id='cn-shanghai'):
+ """为实例创建防火墙规则"""
+ client = self._get_client(region_id)
+ try:
+ request = swas__open20200601_models.CreateFirewallRuleRequest(
+ region_id=region_id,
+ instance_id=instance_id,
+ rule_protocol=rule_protocol,
+ port=port,
+ remark=remark
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.create_firewall_rule_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_firewall_rules(self, instance_id, region_id='cn-shanghai', page_size=10, page_number=1):
+ """获取实例的防火墙规则列表
+ Args:
+ instance_id: 实例ID
+ region_id: 地域ID
+ page_size: 每页记录数,默认10,如果设置为-1则获取全部数据
+ page_number: 页码,默认1
+ """
+ client = self._get_client(region_id)
+ try:
+ if page_size == -1:
+ # 获取全部数据
+ all_rules = []
+ current_page = 1
+ request_page_size = 50 # 每次请求50条记录,减少API调用次数
+
+ while True:
+ request = swas__open20200601_models.ListFirewallRulesRequest(
+ region_id=region_id,
+ instance_id=instance_id,
+ page_size=request_page_size,
+ page_number=current_page
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.list_firewall_rules_with_options(request, runtime)
+
+ response_data = self._convert_response_to_dict(response.body)
+ current_rules = response_data.get('firewall_rules', [])
+
+ if not current_rules:
+ break
+
+ all_rules.extend(current_rules)
+
+ # 如果当前页记录数少于request_page_size,说明已经是最后一页
+ if len(current_rules) < request_page_size:
+ break
+
+ current_page += 1
+
+ # 构造返回结果
+ result_data = response_data.copy()
+ result_data['firewall_rules'] = all_rules
+ result_data['total_count'] = len(all_rules)
+
+ return {'success': True, 'data': result_data, 'message': f'获取防火墙规则列表成功,共{len(all_rules)}条记录'}
+ else:
+ # 分页获取数据
+ request = swas__open20200601_models.ListFirewallRulesRequest(
+ region_id=region_id,
+ instance_id=instance_id,
+ page_size=page_size,
+ page_number=page_number
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.list_firewall_rules_with_options(request, runtime)
+
+ response_data = self._convert_response_to_dict(response.body)
+ rules_count = len(response_data.get('firewall_rules', []))
+
+ return {'success': True, 'data': response_data, 'message': f'获取防火墙规则列表成功,第{page_number}页,共{rules_count}条记录'}
+
+ except Exception as e:
+ jingrow.log_error("获取防火墙规则列表失败", f"获取实例 {instance_id} 防火墙规则列表时发生错误: {str(e)}")
+ return {'success': False, 'error': str(e), 'message': '获取防火墙规则列表失败'}
+
+ def delete_firewall_rules(self, instance_id, rule_ids, region_id='cn-shanghai'):
+ """批量删除实例的防火墙规则"""
+ client = self._get_client(region_id)
+ try:
+ request = swas__open20200601_models.DeleteFirewallRulesRequest(
+ region_id=region_id,
+ instance_id=instance_id,
+ rule_ids=rule_ids
+ )
+ runtime = util_models.RuntimeOptions()
+ response = client.delete_firewall_rules_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': '批量删除防火墙规则失败'}
+
+
+# 全局管理器实例
+_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 reboot_aliyun_instance(instance_id, 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 = "Starting"
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+
+ # 3. 调用阿里云API重启实例
+ manager = _get_manager()
+ result = manager.reboot_instance(instance_id, 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 force_reboot_aliyun_instance(instance_id, 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 = "Starting"
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+
+ # 3. 调用阿里云API强制重启实例
+ manager = _get_manager()
+ result = manager.force_reboot_instances([instance_id], region_id)
+
+ if not result or not result.get('success'):
+ return result
+
+ # 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": "强制重启实例失败"}
+
+
+def monitor_server_status(instance_id, region_id):
+ """通用的服务器状态监控 - 监控服务器状态直到Running"""
+ 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 = 60 # 最多查询60次(10分钟)
+ # 阿里云轻量应用服务器可能的状态值
+ waiting_states = ['Starting', 'Stopping', 'Resetting', 'Stopped', 'Pending', 'Upgrading']
+
+ 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()
+ return
+ elif status in waiting_states:
+ # 继续等待
+ time.sleep(10)
+ else:
+ server.status = status
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+ return
+
+ 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'):
+ """升级实例配置并更新状态"""
+ try:
+ # 1. 查找对应的Jsite Server记录
+ server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id})
+ if not server:
+ return {"success": False, "message": "找不到对应的服务器记录"}
+
+ # 2. 立即更新状态为"升级中"
+ server.status = "Upgrading"
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+
+ # 3. 调用阿里云API升级实例
+ manager = _get_manager()
+ result = manager.upgrade_instance(instance_id, plan_id, region_id)
+
+ if not result or not result.get('success'):
+ # 如果升级失败,恢复状态
+ server.status = "Running"
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+ return result
+
+ # 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 renew_aliyun_instance(instance_id, period=1, region_id='cn-shanghai'):
+ """续费实例"""
+ manager = _get_manager()
+ return manager.renew_instance(instance_id, period, region_id)
+
+@jingrow.whitelist()
+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'):
+ """更新实例密码"""
+ 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()
+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()
+ result = manager.get_plans(region_id)
+
+ # 过滤指定类型的套餐
+ if result.get('success') and result.get('data') and 'plans' in result['data']:
+ allowed_types = ['NORMAL', 'MULTI_IP', 'INTERNATIONAL', 'CAPACITY']
+ filtered_plans = [
+ plan for plan in result['data']['plans']
+ if plan.get('plan_type') in allowed_types
+ ]
+
+ # 在origin_price上统一增加20%作为利润
+ for plan in filtered_plans:
+ if 'origin_price' in plan and plan['origin_price'] is not None:
+ original_price = float(plan['origin_price'])
+ plan['origin_price'] = int(original_price / (1 - 0.2)) # 确保20%利润率且价格为整数
+
+ result['data']['plans'] = filtered_plans
+
+ return result
+
+@jingrow.whitelist()
+def get_aliyun_images(image_type='system', region_id='cn-shanghai'):
+ """获取可用镜像列表"""
+ manager = _get_manager()
+ result = manager.get_images(image_type, region_id)
+
+ # 如果获取成功且有镜像数据,过滤并排序
+ if result.get('success') and result.get('data') and 'images' in result['data']:
+ images = result['data']['images']
+ # 过滤掉以 "alibaba" 开头的镜像(不区分大小写)
+ filtered_images = [
+ image for image in images
+ if not image.get('image_name', '').lower().startswith('alibaba')
+ ]
+ # 按镜像名称(image_name)进行字母排序
+ sorted_images = sorted(filtered_images, key=lambda x: x.get('image_name', '').lower())
+ result['data']['images'] = sorted_images
+
+ return result
+
+@jingrow.whitelist()
+def get_aliyun_regions():
+ """通过阿里云SDK实时获取可用地域列表"""
+ 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 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.get_instance_key_pair(instance_id, 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)
+
+@jingrow.whitelist()
+def get_aliyun_instance_upgrade_plans(instance_id):
+ """获取指定实例可升级的套餐列表"""
+ try:
+ # 获取当前实例信息
+ server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id})
+ if not server:
+ return {"success": False, "message": "找不到服务器记录"}
+
+ # 从实例记录中获取region_id
+ region_id = server.region
+ if not region_id:
+ return {"success": False, "message": "服务器记录缺少region信息"}
+
+ # 从get_plans获取所有套餐
+ manager = _get_manager()
+ result = manager.get_plans(region_id)
+
+ if not result or not result.get('success'):
+ return result
+
+ # 过滤套餐:只返回PlanType值一致的套餐,且价格大于等于当前套餐价格
+ if result.get('data') and 'plans' in result['data']:
+ # 先统一调整所有套餐价格
+ for plan in result['data']['plans']:
+ if 'origin_price' in plan and plan['origin_price'] is not None:
+ original_price = float(plan['origin_price'])
+ plan['origin_price'] = int(original_price / (1 - 0.2)) # 确保20%利润率且价格为整数
+
+ # 获取当前套餐价格
+ current_plan_price = 0
+ if server.planid:
+ for plan in result['data']['plans']:
+ if plan.get('plan_id') == server.planid:
+ current_plan_price = plan['origin_price']
+ break
+
+ # 过滤套餐
+ filtered_plans = []
+ for plan in result['data']['plans']:
+ # 检查plan_type是否与服务器的PlanType一致
+ if plan.get('plan_type') == server.plan_type:
+ # 检查support_platform是否包含当前实例的os_type
+ support_platform_str = plan.get('support_platform', '')
+ try:
+ support_platform_list = json.loads(support_platform_str) if support_platform_str else []
+ if server.os_type and server.os_type in support_platform_list:
+ # 只显示价格大于等于当前套餐价格的套餐
+ if plan['origin_price'] > current_plan_price:
+ filtered_plans.append(plan)
+ except:
+ continue
+
+ result['data']['plans'] = filtered_plans
+
+ return result
+
+ except Exception as e:
+ jingrow.log_error("获取可升级套餐列表失败", f"获取实例 {instance_id} 可升级套餐时发生错误: {str(e)}")
+ return {"success": False, "error": str(e), "message": "获取可升级套餐列表失败"}
+
+
+# 服务器订单和创建相关
+
+@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')
+ plan_type = kwargs.get('plan_type', '')
+
+ if not plan_id or not image_id:
+ jingrow.throw("缺少必要参数")
+
+ 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
+
+ # 生成订单号
+ order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6))
+
+ # 构建业务参数
+ biz_params = {
+ "plan_id": plan_id,
+ "image_id": image_id,
+ "period": period,
+ "region_id": region_id,
+ "plan_type": plan_type,
+ "monthly_price": monthly_price,
+ "total_amount": total_amount,
+ "plan_info": {
+ "core": selected_plan.get('core'),
+ "memory": selected_plan.get('memory'),
+ "disk_size": selected_plan.get('disk_size'),
+ "bandwidth": selected_plan.get('bandwidth')
+ }
+ }
+
+ # 创建订单记录
+ 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}个月",
+ "biz_params": json.dumps(biz_params, ensure_ascii=False)
+ })
+ 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)}
+
+@jingrow.whitelist()
+def create_server_renew_order(**kwargs):
+ """创建服务器续费订单"""
+ try:
+ server = kwargs.get('server')
+ renewal_months = kwargs.get('renewal_months', 1)
+
+ if not server:
+ jingrow.throw("缺少服务器信息")
+
+ # 验证输入
+ server_pg = jingrow.get_pg("Jsite Server", server)
+ if not server_pg:
+ jingrow.throw("服务器不存在")
+
+ team = server_pg.team
+
+ # 验证当前用户权限
+ current_team = get_current_team(True)
+ if current_team.name != team:
+ jingrow.throw("您没有权限为此服务器创建续费订单")
+
+ # 计算续费金额
+ renewal_months = int(renewal_months)
+ plan_price = server_pg.plan_price or 0
+ total_amount = plan_price * renewal_months
+
+ # 生成唯一订单号
+ order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6))
+
+ # 构建业务参数
+ biz_params = {
+ "server": server,
+ "renewal_months": renewal_months,
+ "plan_price": plan_price,
+ "total_amount": total_amount,
+ "instance_id": server_pg.instance_id,
+ "region_id": server_pg.region,
+ "plan_id": server_pg.planid
+ }
+
+ # 创建订单记录
+ order = jingrow.get_pg({
+ "pagetype": "Order",
+ "order_id": order_id,
+ "order_type": "服务器续费",
+ "team": team,
+ "status": "待支付",
+ "total_amount": total_amount,
+ "title": server_pg.instance_id,
+ "description": str(renewal_months), # 存储续费月数
+ "biz_params": json.dumps(biz_params, ensure_ascii=False)
+ })
+
+ order.insert(ignore_permissions=True)
+ jingrow.db.commit()
+
+ return {
+ "success": True,
+ "order": order.as_dict()
+ }
+ except Exception as e:
+ jingrow.log_error("续费订单错误", f"创建续费订单失败: {str(e)}")
+ return {
+ "success": False,
+ "message": f"创建续费订单失败: {str(e)}"
+ }
+
+@jingrow.whitelist()
+def create_server_upgrade_order(**kwargs):
+ """创建服务器升级订单"""
+ try:
+ server = kwargs.get('server')
+ new_plan_id = kwargs.get('new_plan_id')
+
+ if not server or not new_plan_id:
+ jingrow.throw("缺少必要参数")
+
+ # 验证输入
+ server_pg = jingrow.get_pg("Jsite Server", server)
+ if not server_pg:
+ jingrow.throw("服务器不存在")
+
+ team = server_pg.team
+
+ # 验证当前用户权限
+ current_team = get_current_team(True)
+ if current_team.name != team:
+ jingrow.throw("您没有权限为此服务器创建升级订单")
+
+ # 获取套餐列表
+ plans_response = get_aliyun_plans(server_pg.region)
+ if not plans_response or not plans_response.get('success'):
+ jingrow.throw("获取套餐列表失败")
+
+ plans = plans_response['data'].get('plans', [])
+
+ # 获取当前套餐价格
+ current_plan_price = 0
+ if server_pg.planid:
+ for plan in plans:
+ if plan.get('plan_id') == server_pg.planid:
+ current_plan_price = float(plan.get('origin_price', 0))
+ break
+
+ # 获取新套餐价格
+ new_plan = None
+ for plan in plans:
+ if plan.get('plan_id') == new_plan_id:
+ new_plan = plan
+ break
+
+ if not new_plan:
+ jingrow.throw("新套餐不存在")
+
+ new_plan_price = float(new_plan.get('origin_price', 0))
+
+ # 计算剩余天数
+ if server_pg.end_date:
+ try:
+ # 确保end_date是datetime.date对象
+ if isinstance(server_pg.end_date, str):
+ end_date = jingrow.utils.getdate(server_pg.end_date)
+ else:
+ end_date = server_pg.end_date
+ current_date = jingrow.utils.nowdate()
+ remaining_days = (end_date - current_date).days
+ if remaining_days <= 0:
+ remaining_days = 1 # 至少1天
+ except Exception as e:
+ jingrow.log_error("计算剩余天数失败", f"计算实例 {server_pg.instance_id} 剩余天数时发生错误: {str(e)}")
+ remaining_days = 30 # 出错时使用默认值
+ else:
+ remaining_days = 30 # 默认30天
+
+ # 计算升级费用:按天计费,全额补差价
+ daily_price_diff = (new_plan_price - current_plan_price) / 30
+ upgrade_amount = daily_price_diff * remaining_days
+
+ # 确保升级费用不为负数
+ if upgrade_amount < 0:
+ upgrade_amount = 0
+
+ # 生成唯一订单号
+ order_id = datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6))
+
+ # 构建业务参数
+ biz_params = {
+ "server": server,
+ "new_plan_id": new_plan_id,
+ "current_plan_price": current_plan_price,
+ "new_plan_price": new_plan_price,
+ "remaining_days": remaining_days,
+ "upgrade_amount": upgrade_amount,
+ "instance_id": server_pg.instance_id,
+ "region_id": server_pg.region,
+ "current_plan_id": server_pg.planid,
+ "new_plan_info": {
+ "core": new_plan.get('core'),
+ "memory": new_plan.get('memory'),
+ "disk_size": new_plan.get('disk_size'),
+ "bandwidth": new_plan.get('bandwidth')
+ }
+ }
+
+ # 创建订单记录
+ order = jingrow.get_pg({
+ "pagetype": "Order",
+ "order_id": order_id,
+ "order_type": "服务器升级",
+ "team": team,
+ "status": "待支付",
+ "total_amount": upgrade_amount,
+ "title": server_pg.instance_id,
+ "description": new_plan_id, # 存储新套餐ID
+ "biz_params": json.dumps(biz_params, ensure_ascii=False)
+ })
+
+ order.insert(ignore_permissions=True)
+ jingrow.db.commit()
+
+ return {
+ "success": True,
+ "order": order.as_dict(),
+ "upgrade_info": {
+ "current_plan_price": current_plan_price,
+ "new_plan_price": new_plan_price,
+ "remaining_days": remaining_days,
+ "daily_price_diff": daily_price_diff,
+ "upgrade_amount": upgrade_amount
+ }
+ }
+ except Exception as e:
+ jingrow.log_error("升级订单错误", f"创建升级订单失败: {str(e)}")
+ return {
+ "success": False,
+ "message": f"创建升级订单失败: {str(e)}"
+ }
+
+def renew_server(order_name):
+ """支付成功后异步续费服务器"""
+ try:
+ order = jingrow.get_pg("Order", order_name)
+ if not order:
+ raise Exception("订单不存在")
+
+ # 从biz_params中读取业务参数
+ biz_params = json.loads(order.biz_params) if order.biz_params else {}
+ server_name = biz_params.get("server")
+ renewal_months = biz_params.get("renewal_months", 1)
+ instance_id = biz_params.get("instance_id")
+ region_id = biz_params.get("region_id", "cn-shanghai")
+
+ if not server_name or not instance_id:
+ raise Exception("订单中缺少必要的服务器信息")
+
+ # 查找服务器记录
+ server = jingrow.get_pg("Jsite Server", server_name)
+ if not server:
+ raise Exception("找不到对应的服务器记录")
+
+ # 调用阿里云API续费
+ manager = _get_manager()
+ result = manager.renew_instance(instance_id, renewal_months, region_id)
+
+ if not result or not result.get('success'):
+ raise Exception(f"阿里云续费失败: {result.get('message', '未知错误')}")
+
+ # 更新服务器到期时间
+ server.end_date = jingrow.utils.add_months(server.end_date or jingrow.utils.nowdate(), renewal_months)
+ server.save(ignore_permissions=True)
+
+ # 更新订单状态
+ order.status = "交易成功"
+ order.save(ignore_permissions=True)
+
+ jingrow.db.commit()
+
+ return True
+
+ except Exception as e:
+ jingrow.log_error("服务器续费失败", f"订单 {order_name}: {str(e)}")
+ raise e
+
+def upgrade_server(order_name):
+ """支付成功后异步升级服务器"""
+ try:
+ order = jingrow.get_pg("Order", order_name)
+ if not order:
+ raise Exception("订单不存在")
+
+ # 从biz_params中读取业务参数
+ biz_params = json.loads(order.biz_params) if order.biz_params else {}
+ server_name = biz_params.get("server")
+ new_plan_id = biz_params.get("new_plan_id")
+ instance_id = biz_params.get("instance_id")
+ region_id = biz_params.get("region_id", "cn-shanghai")
+
+ if not server_name or not new_plan_id or not instance_id:
+ raise Exception("订单中缺少必要的服务器升级信息")
+
+ # 查找服务器记录
+ server = jingrow.get_pg("Jsite Server", server_name)
+ if not server:
+ raise Exception("找不到对应的服务器记录")
+
+ # 调用阿里云API升级实例
+ result = upgrade_aliyun_instance(instance_id, new_plan_id, region_id)
+
+ # 打印result到后台日志
+ jingrow.log_error("阿里云升级实例结果", f"订单 {order_name} 的升级结果: {result}")
+
+ if not result or not result.get('success'):
+ raise Exception(f"阿里云升级失败: {result.get('message', '未知错误')}")
+
+ # 更新服务器记录中的套餐信息
+ new_plan_info = biz_params.get("new_plan_info", {})
+ if new_plan_info:
+ server.planid = new_plan_id
+ server.cpu = new_plan_info.get("core")
+ server.memory = new_plan_info.get("memory")
+ server.disk_size = new_plan_info.get("disk_size")
+ server.bandwidth = new_plan_info.get("bandwidth")
+ server.plan_price = biz_params.get("new_plan_price", 0)
+ server.save(ignore_permissions=True)
+
+ # 更新订单状态
+ order.status = "交易成功"
+ order.save(ignore_permissions=True)
+
+ jingrow.db.commit()
+
+ # 延迟60秒执行update_server_record来更新服务器详细信息
+ time.sleep(60)
+ jingrow.enqueue(
+ "jcloud.api.aliyun_server_light.update_server_record",
+ instance_ids=json.dumps([instance_id]),
+ timeout=120
+ )
+
+ return True
+
+ except Exception as e:
+ jingrow.log_error("服务器升级失败", f"订单 {order_name}: {str(e)}")
+ raise e
+
+def create_aliyun_server(order_name):
+ """异步创建服务器"""
+ try:
+ order = jingrow.get_pg("Order", order_name)
+ if not order:
+ raise Exception("订单不存在")
+
+ # 从biz_params中读取业务参数
+ biz_params = json.loads(order.biz_params) if order.biz_params else {}
+ plan_id = biz_params.get("plan_id")
+ image_id = biz_params.get("image_id")
+ period = biz_params.get("period", 1)
+ region_id = biz_params.get("region_id", "cn-shanghai")
+ plan_type = biz_params.get("plan_type", "")
+ monthly_price = biz_params.get("monthly_price", 0)
+ plan_info = biz_params.get("plan_info", {})
+
+ if not plan_id or not image_id:
+ raise Exception("订单中缺少必要的服务器配置信息")
+
+ # 调用阿里云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', '未知错误')}")
+
+ # 阿里云API返回的是instance_ids数组,取第一个实例ID
+ instance_id = result['data']['instance_ids'][0]
+
+ # 创建本地服务器记录
+ server = jingrow.get_pg({
+ "pagetype": "Jsite Server",
+ "team": order.team,
+ "order_id": order.order_id,
+ "status": "Pending",
+ "instance_id": instance_id,
+ "region": region_id,
+ "image_id": image_id,
+ "end_date": jingrow.utils.add_months(jingrow.utils.nowdate(), period),
+ "title": f"{region_id} - {plan_info.get('core', '')}核/{plan_info.get('memory', '')}GB",
+ "planid": plan_id,
+ "plan_type": plan_type,
+ "period": period,
+ "plan_price": monthly_price,
+ "cpu": plan_info.get('core'),
+ "memory": plan_info.get('memory'),
+ "disk_size": plan_info.get('disk_size'),
+ "bandwidth": plan_info.get('bandwidth')
+ })
+ server.insert(ignore_permissions=True)
+
+ # 更新订单状态
+ order.status = "交易成功"
+ order.save(ignore_permissions=True)
+
+ jingrow.db.commit()
+
+ # 延迟60秒执行update_server_record来更新服务器详细信息
+ time.sleep(30)
+ jingrow.enqueue(
+ "jcloud.api.aliyun_server_light.update_server_record",
+ instance_ids=json.dumps([instance_id]),
+ timeout=120
+ )
+
+ return True
+
+ except Exception as e:
+ jingrow.log_error("服务器创建失败", f"订单 {order_name}: {str(e)}")
+ 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
+ uuid_suffix = str(uuid.uuid4())[:4]
+ key_pair_name = f"{region_id}-{instance_id[:8]}-{uuid_suffix}"
+
+ # 直接调用管理器方法创建密钥对
+ manager = _get_manager()
+ key_pair_result = manager.create_instance_key_pair(instance_id, key_pair_name, region_id)
+
+ 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}")
+ return {"success": False, "message": f"密钥对创建失败: {error_msg}"}
+
+ # 获取私钥 - 直接访问data字段
+ 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)
+ jingrow.db.commit()
+
+ return {
+ "success": True,
+ "message": "密钥对创建成功",
+ "key_pair_name": key_pair_name,
+ "private_key": private_key
+ }
+ else:
+ 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)}")
+ 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
+
+ # 获取阿里云实例详细信息
+ 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] # 取第一个实例
+
+ # 获取公网IP(必需,实例状态为Running且最多重试30次)
+ if not instance_info.get('public_ip_address') or instance_info.get('status') != 'Running':
+ for retry in range(30):
+ time.sleep(10)
+ retry_details = get_aliyun_instance_details(instance_ids, region_id)
+ if retry_details and retry_details.get('success'):
+ retry_instances = retry_details.get('data', {}).get('instances', [])
+ if retry_instances:
+ current_status = retry_instances[0].get('status', '')
+ current_public_ip = retry_instances[0].get('public_ip_address')
+ if current_status == 'Running' and current_public_ip:
+ # 条件满足后,重新获取完整的实例信息
+ instance_info = retry_instances[0]
+ break
+ else:
+ jingrow.log_error(f"获取实例信息失败-{instance_id}", f"实例 {instance_id} 在30次重试后仍未变为Running状态或获取到公网IP")
+ return {"success": False, "message": f"实例 {instance_id} 在30次重试后仍未变为Running状态或获取到公网IP"}
+
+ server.public_ip = instance_info.get('public_ip_address')
+
+ server.private_ip = instance_info.get('inner_ip_address') or instance_info.get('network_attributes', [{}])[0].get('private_ip_address')
+
+ # 更新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', '')
+ os_type = image_info.get('os_type', '')
+
+ server.system = f"{image_name} {image_version}".strip()
+ server.os_type = os_type
+
+ # 更新资源规格信息
+ 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')
+
+ # 更新套餐信息
+ plan_id = instance_info.get('plan_id')
+ if plan_id:
+ # 更新planid
+ server.planid = plan_id
+
+ # 直接从实例信息获取plan_type
+ plan_type = instance_info.get('plan_type')
+ if plan_type:
+ server.plan_type = plan_type
+
+ # 获取套餐信息来计算价格
+ plans_result = get_aliyun_plans(region_id)
+ if plans_result and plans_result.get('success') and plans_result.get('data', {}).get('plans'):
+ for plan in plans_result['data']['plans']:
+ if plan.get('plan_id') == plan_id:
+ # 使用调整后的价格(已经包含20%利润)
+ if 'origin_price' in plan and plan['origin_price'] is not None:
+ server.plan_price = float(plan['origin_price'])
+ break
+
+ # 保存更新
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+
+ # 同步防火墙规则(在保存服务器信息之后)
+ sync_firewall_rules(instance_id)
+
+ return {
+ "success": True,
+ "message": "服务器信息更新成功",
+ "updated_fields": {
+ "public_ip": instance_info.get('public_ip_address'),
+ "private_ip": server.private_ip,
+ "end_date": server.end_date,
+ "status": instance_info.get('status'),
+ "system": server.system,
+ "os_type": server.os_type,
+ "plan_price": server.plan_price,
+ "planid": server.planid
+ }
+ }
+
+ 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)}
+
+@jingrow.whitelist()
+def get_server_plan_price(instance_id):
+ """根据实例ID获取当前套餐的实时价格"""
+ try:
+ # 获取服务器记录
+ server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id})
+ if not server:
+ return {"success": False, "message": "找不到服务器记录"}
+
+ if not server.planid:
+ return {"success": False, "message": "服务器记录缺少套餐信息"}
+
+ # 获取套餐列表(使用get_aliyun_plans确保价格调整一致)
+ plans_result = get_aliyun_plans(server.region)
+
+ if not plans_result or not plans_result.get('success'):
+ return {"success": False, "message": "获取套餐列表失败"}
+
+ # 查找当前套餐
+ plans = plans_result.get('data', {}).get('plans', [])
+ current_plan = None
+ for plan in plans:
+ if plan.get('plan_id') == server.planid:
+ current_plan = plan
+ break
+
+ if not current_plan:
+ return {"success": False, "message": "找不到对应的套餐信息"}
+
+ # 使用已经调整过的价格
+ current_plan_price = float(current_plan.get('origin_price', 0))
+
+ return {
+ "success": True,
+ "plan_price": current_plan_price,
+ "plan_info": {
+ "plan_id": current_plan.get('plan_id'),
+ "cpu": current_plan.get('core'),
+ "memory": current_plan.get('memory'),
+ "disk_size": current_plan.get('disk_size'),
+ "bandwidth": current_plan.get('bandwidth')
+ }
+ }
+
+ except Exception as e:
+ jingrow.log_error("获取套餐价格失败", f"获取实例 {instance_id} 套餐价格时发生错误: {str(e)}")
+ return {"success": False, "error": str(e), "message": "获取套餐价格失败"}
+
+@jingrow.whitelist()
+def get_password(pagetype, name, fieldname):
+
+ return jingrow.get_pg(pagetype, name).get_password(fieldname)
+
+@jingrow.whitelist()
+def get_jsite_server(instance_id=None, name=None):
+ """获取Jsite Server记录"""
+ try:
+ if not instance_id and not name:
+ return {"success": False, "message": "请提供 instance_id 或 name 参数"}
+
+ server = None
+ if instance_id:
+ server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id})
+ elif name:
+ server = jingrow.get_pg("Jsite Server", name)
+
+ if not server:
+ return {"success": False, "message": "找不到对应的服务器记录"}
+
+ # 返回服务器信息(排除敏感字段)
+ server_data = server.as_dict()
+
+ # 移除敏感字段
+ sensitive_fields = ['password', 'private_key']
+ for field in sensitive_fields:
+ if field in server_data:
+ del server_data[field]
+
+ return {
+ "success": True,
+ "data": server_data,
+ "message": "获取服务器记录成功"
+ }
+
+ except Exception as e:
+ jingrow.log_error("获取Jsite Server记录失败", f"获取服务器记录时发生错误: {str(e)}")
+ return {"success": False, "message": f"获取服务器记录失败: {str(e)}"}
+
+@jingrow.whitelist()
+def create_aliyun_firewall_rule(instance_id, rule_protocol, port, remark=None, region_id='cn-shanghai'):
+ """创建阿里云轻量应用服务器防火墙规则"""
+ try:
+ # 调用管理器创建防火墙规则
+ manager = _get_manager()
+ result = manager.create_firewall_rule(instance_id, rule_protocol, port, remark, region_id)
+
+ # 如果创建成功,同步防火墙规则到本地记录
+ if result and result.get('success'):
+ sync_firewall_rules(instance_id)
+
+ return result
+
+ except Exception as e:
+ jingrow.log_error("创建防火墙规则失败", f"为实例 {instance_id} 创建防火墙规则时发生错误: {str(e)}")
+ return {"success": False, "error": str(e), "message": "创建防火墙规则失败"}
+
+@jingrow.whitelist()
+def get_aliyun_firewall_rules(instance_id, region_id='cn-shanghai', page_size=10, page_number=1):
+ """获取阿里云轻量应用服务器防火墙规则列表
+ Args:
+ instance_id: 实例ID
+ region_id: 地域ID,默认cn-shanghai
+ page_size: 每页记录数,默认10,如果设置为-1则获取全部数据
+ page_number: 页码,默认1
+ """
+ try:
+ # 调用管理器获取防火墙规则列表
+ manager = _get_manager()
+ result = manager.list_firewall_rules(instance_id, region_id, page_size, page_number)
+
+ return result
+
+ except Exception as e:
+ jingrow.log_error("获取防火墙规则列表失败", f"获取实例 {instance_id} 防火墙规则列表时发生错误: {str(e)}")
+ return {"success": False, "error": str(e), "message": "获取防火墙规则列表失败"}
+
+@jingrow.whitelist()
+def delete_aliyun_firewall_rules(instance_id, rule_ids, region_id='cn-shanghai'):
+ """批量删除阿里云轻量应用服务器防火墙规则"""
+ try:
+ # 调用管理器批量删除防火墙规则
+ manager = _get_manager()
+ result = manager.delete_firewall_rules(instance_id, rule_ids, region_id)
+
+ # 如果删除成功,同步防火墙规则到本地记录
+ if result and result.get('success'):
+ sync_firewall_rules(instance_id)
+
+ return result
+
+ except Exception as e:
+ jingrow.log_error("批量删除防火墙规则失败", f"删除实例 {instance_id} 防火墙规则时发生错误: {str(e)}")
+ return {"success": False, "error": str(e), "message": "批量删除防火墙规则失败"}
+
+@jingrow.whitelist()
+def sync_firewall_rules(instance_id):
+ """同步阿里云防火墙规则到本地Jsite Server记录"""
+ try:
+ # 查找对应的Jsite Server记录
+ server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id})
+ if not server:
+ return {"success": False, "message": "找不到对应的服务器记录"}
+
+ region_id = server.region
+ if not region_id:
+ return {"success": False, "message": "服务器记录缺少region信息"}
+
+ # 获取阿里云防火墙规则(获取全部数据)
+ firewall_result = get_aliyun_firewall_rules(instance_id, region_id, page_size=-1)
+ if not firewall_result or not firewall_result.get('success'):
+ error_msg = f"获取阿里云防火墙规则失败: {firewall_result.get('message', '未知错误')}"
+ jingrow.log_error("同步防火墙规则失败", error_msg)
+ return {"success": False, "message": error_msg}
+
+ firewall_rules_data = firewall_result.get('data', {}).get('firewall_rules', [])
+
+ # 转换防火墙规则格式
+ converted_rules = []
+ for rule in firewall_rules_data:
+ converted_rule = {
+ "rule_id": rule.get('rule_id', ''),
+ "rule_protocol": rule.get('rule_protocol', ''),
+ "port": rule.get('port', ''),
+ "source_cidr_ip": rule.get('source_cidr_ip', '0.0.0.0/0'),
+ "remark": rule.get('remark', '')
+ }
+ converted_rules.append(converted_rule)
+
+ # 清空现有的防火墙规则并添加新的
+ server.firewall_rules = []
+ for rule_data in converted_rules:
+ server.append('firewall_rules', rule_data)
+
+ # 保存更新
+ server.save(ignore_permissions=True)
+ jingrow.db.commit()
+
+ return {
+ "success": True,
+ "message": f"防火墙规则同步成功,共同步 {len(converted_rules)} 条规则",
+ "rules_count": len(converted_rules),
+ "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 get_jingrow_firewall_rules(**data):
+ """获取防火墙规则(从本地数据库获取,支持分页)"""
+ try:
+ instance_id = data.get('instance_id')
+ limit = data.get('limit', 10)
+ pageno = data.get('pageno', 1)
+
+ if not instance_id:
+ return {"success": False, "message": "缺少实例ID参数"}
+
+ # 查找对应的Jsite Server记录
+ server_records = jingrow.get_all(
+ "Jsite Server",
+ {"instance_id": instance_id},
+ ["name"]
+ )
+
+ if not server_records:
+ return {"success": False, "message": "未找到指定的服务器记录"}
+
+ server_record = server_records[0]
+
+ # 获取该服务器的所有防火墙规则
+ firewall_rules = jingrow.get_all(
+ "Firewall Rules",
+ {"parent": server_record.name},
+ ["name", "rule_id", "rule_protocol", "port", "source_cidr_ip", "remark", "idx"],
+ order_by="idx desc"
+ )
+
+ # 转换为API格式的数据结构
+ all_items = []
+ for rule in firewall_rules:
+ item = {
+ "rule_id": rule.rule_id or rule.name,
+ "rule_protocol": rule.rule_protocol or "",
+ "port": rule.port or "",
+ "source_cidr_ip": rule.source_cidr_ip or "0.0.0.0/0",
+ "remark": rule.remark or ""
+ }
+ all_items.append(item)
+
+ # 数据已经按idx降序排列(最新添加的在前面),无需再次排序
+ sorted_items = all_items
+
+ # 计算分页
+ total = len(sorted_items)
+ pagecount = (total + limit - 1) // limit
+ start_index = (pageno - 1) * limit
+ end_index = start_index + limit
+
+ # 返回当前页的记录
+ current_page_items = sorted_items[start_index:end_index]
+
+ # 返回格式化的防火墙规则信息
+ return {
+ "success": True,
+ "data": {
+ "pageno": pageno,
+ "limit": limit,
+ "total": total,
+ "pagecount": pagecount,
+ "firewall_rules": current_page_items
+ }
+ }
+
+ except Exception as e:
+ return {"success": False, "message": f"防火墙规则查询失败: {str(e)}"}
+
+@jingrow.whitelist()
+def execute_jsite_task(instance_id, mode='deploy', command_content=None, **kwargs):
+
+ try:
+ # 获取服务器信息并验证
+ server = jingrow.get_pg("Jsite Server", {"instance_id": instance_id})
+ if not server:
+ return {"success": False, "message": "找不到对应的服务器记录"}
+
+ region_id = server.region
+ # 验证command_content参数
+ if not command_content:
+ return {"success": False, "message": "缺少command_content参数"}
+
+ # 如果mode为deploy,开通3000/4000端口
+ if mode == 'deploy':
+ create_aliyun_firewall_rule(instance_id, 'TCP', '3000/4000', 'Jsite', region_id)
+
+ # 执行命令,不传入参数字典
+ manager = _get_manager()
+ result = manager.run_command(instance_id, command_content, parameters=None, command_name=mode, region_id=region_id, timeout=3600)
+
+ return result
+
+ except Exception as e:
+ jingrow.log_error("执行Jsite任务失败", f"实例 {instance_id}, 模式 {mode}, 错误: {str(e)}")
+ return {"success": False, "message": f"执行Jsite任务失败: {str(e)}"}
diff --git a/jcloud/api/billing.py b/jcloud/api/billing.py
index 0adbc82..5fb1294 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():
@@ -1088,6 +1089,21 @@ def handle_order_payment_complete(order_id):
process_balance_recharge(order)
elif order.order_type == "网站续费":
process_site_renew(order_id)
+ elif order.order_type == "服务器续费":
+ # 异步续费服务器
+ jingrow.enqueue('jcloud.api.aliyun_server_light.renew_server', order_name=order.name)
+ elif order.order_type == "服务器升级":
+ # 异步升级服务器
+ jingrow.enqueue('jcloud.api.aliyun_server_light.upgrade_server', order_name=order.name)
+ elif order.order_type == "新建服务器":
+ # 异步创建服务器
+ jingrow.enqueue('jcloud.api.aliyun_server_light.create_aliyun_server', order_name=order.name)
+ elif order.order_type == "域名注册":
+ # 异步注册域名
+ jingrow.enqueue('jcloud.api.domain_west.register_domain_from_order', order_name=order.name)
+ elif order.order_type == "域名续费":
+ # 异步续费域名
+ jingrow.enqueue('jcloud.api.domain_west.renew_domain_from_order', order_name=order.name)
return True
except Exception as e:
@@ -1220,7 +1236,6 @@ def create_alipay_order_for_recharge(amount):
jingrow.db.commit()
# 直接使用AlipayAPI类生成支付链接
- from jcloud.api.payment.alipay import AlipayAPI
api = AlipayAPI()
try:
@@ -1448,7 +1463,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
@@ -1565,6 +1580,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()
+
+ # 支付成功后,调用订单完成处理函数
+ handle_order_payment_complete(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):
"""创建支付宝订单支付链接"""
@@ -1587,7 +1672,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:
@@ -1675,7 +1759,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:
# 获取订单信息
@@ -1869,3 +1953,81 @@ def get_balance_transactions(page=1, page_size=20, search=None):
"total": 0,
"error": str(e)
}
+
+@jingrow.whitelist()
+def process_balance_payment_for_domain_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()
+
+ # 支付成功,订单状态已更新
+
+ # 调用统一的订单支付完成处理函数
+ handle_order_payment_complete(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)}"
+ }
+
+
+
+
+
+
+
diff --git a/jcloud/api/central.py b/jcloud/api/central.py
index 7f5bcef..ead798e 100644
--- a/jcloud/api/central.py
+++ b/jcloud/api/central.py
@@ -129,7 +129,6 @@ def check_subdomain_availability(subdomain):
{
"subdomain": subdomain,
"domain": get_jerp_domain(),
- "status": ("!=", "Archived"),
},
)
)
diff --git a/jcloud/api/client.py b/jcloud/api/client.py
index fee5b10..ac14ea8 100644
--- a/jcloud/api/client.py
+++ b/jcloud/api/client.py
@@ -10,7 +10,7 @@ import jingrow
from jingrow.client import set_value as _set_value
from jingrow.handler import run_pg_method as _run_pg_method
from jingrow.model import child_table_fields, default_fields
-from jingrow.model.base_document import get_controller
+from jingrow.model.base_page import get_controller
from jingrow.utils import cstr
from pypika.queries import QueryBuilder
@@ -77,6 +77,9 @@ ALLOWED_PAGETYPES = [
"Site Database User",
"Jcloud Settings",
"Mpesa Payment Record",
+ "Jsite Server",
+ "Jsite Domain",
+ "Domain Owner",
]
ALLOWED_PAGETYPES_FOR_SUPPORT = [
@@ -102,7 +105,7 @@ def get_list(
if filters is None:
filters = {}
- # these doctypes doesn't have a team field to filter by but are used in get or run_pg_method
+ # these pagetypes doesn't have a team field to filter by but are used in get or run_pg_method
if pagetype in ["Team", "User SSH Key"]:
return []
diff --git a/jcloud/api/developer/saas.py b/jcloud/api/developer/saas.py
index 2da4ddb..85b9469 100644
--- a/jcloud/api/developer/saas.py
+++ b/jcloud/api/developer/saas.py
@@ -249,7 +249,7 @@ def send_email_with_verification_code(email, otp):
args={
"full_name": jingrow.get_value("User", email, "full_name"),
"otp": otp,
- "image_path": "http://git.jingrow.com:3000/jingrow/gameplan/assets/9355208/447035d0-0686-41d2-910a-a3d21928ab94",
+ "image_path": "http://git.jingrow.com/jingrow/gameplan/assets/9355208/447035d0-0686-41d2-910a-a3d21928ab94",
},
now=True,
)
diff --git a/jcloud/api/domain_west.py b/jcloud/api/domain_west.py
new file mode 100644
index 0000000..2737bb2
--- /dev/null
+++ b/jcloud/api/domain_west.py
@@ -0,0 +1,3494 @@
+# Copyright (c) 2024, JINGROW
+# For license information, please see license.txt
+
+import jingrow
+import requests
+import time
+import hashlib
+import json
+import random
+from datetime import datetime
+from urllib.parse import urlencode
+from typing import Dict, Any, Optional, List
+from jcloud.utils import get_current_team
+from pypinyin import lazy_pinyin
+import base64
+
+class WestDomain:
+ """西部数码域名API客户端"""
+
+ def __init__(self, username: str, password: str):
+ """
+ 初始化西部数码API客户端
+
+ Args:
+ username: 西部数码用户名
+ password: 西部数码API密码
+ """
+ self.username = username.strip()
+ self.password = password.strip()
+ self.api_base_url = "https://api.west.cn/api/v2"
+ self.time = None
+ self.token = None
+
+ def _generate_token(self) -> str:
+ """生成认证token"""
+ self.time = self._get_current_timestamp()
+ token_string = f"{self.username}{self.password}{self.time}"
+ return hashlib.md5(token_string.encode('utf-8')).hexdigest()
+
+ def _get_current_timestamp(self) -> int:
+ """获取当前时间戳(毫秒)"""
+ return int(time.time() * 1000)
+
+ def _generate_common_parameters(self) -> Dict[str, str]:
+ """生成公共参数"""
+ self.token = self._generate_token()
+ return {
+ 'username': self.username,
+ 'time': str(self.time),
+ 'token': self.token,
+ }
+
+ def _make_request(self, action: str, method: str = 'GET',
+ query_params: Optional[Dict] = None,
+ body_params: Optional[Dict] = None) -> Dict[str, Any]:
+ """
+ 发送API请求
+
+ Args:
+ action: API动作路径
+ method: 请求方法 (GET/POST)
+ query_params: 查询参数
+ body_params: 请求体参数
+
+ Returns:
+ API响应结果
+ """
+ # 构建URL
+ common_params = self._generate_common_parameters()
+ param_string = urlencode(common_params)
+
+ url = f"{self.api_base_url}{action}"
+ if '?' in action:
+ url += f"&{param_string}"
+ else:
+ url += f"?{param_string}"
+
+ # 添加查询参数
+ if query_params:
+ url += f"&{urlencode(query_params)}"
+
+ headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+
+ try:
+ if method.upper() == 'POST':
+ data = body_params or {}
+ # 确保中文字符正确编码
+ response = requests.post(url, data=data, headers=headers, timeout=30)
+ else:
+ response = requests.get(url, headers=headers, timeout=30)
+
+ response.raise_for_status()
+
+ try:
+ result = response.json()
+ except json.JSONDecodeError:
+ result = {"status": "error", "message": "无法解析API响应"}
+
+ return result
+
+ except requests.exceptions.RequestException as e:
+ return {"status": "error", "message": f"API请求失败: {str(e)}"}
+
+ def check_balance(self) -> Dict[str, Any]:
+ """获取账户可用余额"""
+ return self._make_request('/info/?act=checkbalance', 'GET')
+
+ def get_domain_price(self, domain: str, year: int = 1) -> Dict[str, Any]:
+ """
+ 获取域名价格
+
+ Args:
+ domain: 域名
+ year: 注册年限
+ """
+ body_params = {
+ 'type': 'domain',
+ 'value': domain,
+ 'year': year,
+ }
+ return self._make_request('/info/?act=getprice', 'POST', body_params=body_params)
+
+ def get_domain_renew_price(self, domain: str, year: int = 1) -> Dict[str, Any]:
+ """
+ 获取域名续费价格
+
+ Args:
+ domain: 域名
+ year: 续费年限
+ """
+ body_params = {
+ 'act': 'getrenprice',
+ 'domain': domain,
+ 'year': year,
+ }
+ return self._make_request('/domain/', 'POST', body_params=body_params)
+
+ def query_domain(self, domain: str, suffix: str = '.com') -> Dict[str, Any]:
+ """
+ 域名查询
+
+ Args:
+ domain: 域名前缀
+ suffix: 域名后缀
+ """
+ body_params = {
+ 'domain': domain,
+ 'suffix': suffix,
+ }
+ return self._make_request('/domain/query/', 'POST', body_params=body_params)
+
+
+
+ def register_domain(self, domain: str, regyear: int = 1,
+ domainpwd: Optional[str] = None,
+ dns_host1: Optional[str] = None,
+ dns_host2: Optional[str] = None,
+ dns_host3: Optional[str] = None,
+ dns_host4: Optional[str] = None,
+ dns_host5: Optional[str] = None,
+ dns_host6: Optional[str] = None,
+ c_sysid: Optional[str] = None,
+ client_price: Optional[str] = None,
+ premium: Optional[str] = None,
+ domchannel: Optional[str] = None,
+ westusechn: Optional[str] = None) -> Dict[str, Any]:
+ """
+ 注册域名
+
+ Args:
+ domain: 域名(多个域名用英文逗号分隔)
+ regyear: 注册年限
+ domainpwd: 域名密码,不填则系统随机生成
+ dns_host1: 主DNS,必填
+ dns_host2: 辅DNS,必填
+ dns_host3-6: 可选DNS
+ c_sysid: 模板ID,必填
+ client_price: 价格保护,代理商用户购买价格,如果价格低于其成本价,激活失败防止亏损,不需要保护传99999
+ premium: 普通域名不需要传,溢价域名必须传"yes"才能注册
+ domchannel: 仅对.cn域名有效,传"hk"为特价渠道,空为默认,"cn"为普通渠道
+ westusechn: 空为国内渠道,如需国际合作渠道传"hk",此功能支持:.com(需特殊权限)、.top、.cyou、.icu、.vip、.xyz、.site、.shop、.co
+ """
+ body_params = {
+ 'act': 'regdomain',
+ 'domain': domain,
+ 'regyear': regyear,
+ }
+
+ # 添加可选参数
+ if domainpwd:
+ body_params['domainpwd'] = domainpwd
+ if dns_host1:
+ body_params['dns_host1'] = dns_host1
+ if dns_host2:
+ body_params['dns_host2'] = dns_host2
+ if dns_host3:
+ body_params['dns_host3'] = dns_host3
+ if dns_host4:
+ body_params['dns_host4'] = dns_host4
+ if dns_host5:
+ body_params['dns_host5'] = dns_host5
+ if dns_host6:
+ body_params['dns_host6'] = dns_host6
+ if c_sysid:
+ body_params['c_sysid'] = c_sysid
+ if client_price:
+ body_params['client_price'] = client_price
+ if premium:
+ body_params['premium'] = premium
+ if domchannel:
+ body_params['domchannel'] = domchannel
+ if westusechn:
+ body_params['westusechn'] = westusechn
+
+ return self._make_request('/audit/', 'POST', body_params=body_params)
+
+ def renew_domain(self, domain: str, year: int = 1,
+ expire_date: Optional[str] = None,
+ client_price: Optional[int] = None) -> Dict[str, Any]:
+ """
+ 域名续费
+
+ Args:
+ domain: 域名
+ year: 续费年限
+ expire_date: 到期时间
+ client_price: 客户价格
+ """
+ body_params = {
+ 'domain': domain,
+ 'year': year,
+ }
+
+ if expire_date:
+ body_params['expiredate'] = expire_date
+ if client_price:
+ body_params['client_price'] = client_price
+
+ return self._make_request('/domain/?act=renew', 'POST', body_params=body_params)
+
+ def get_domain_list(self, limit: int = 10, page: int = 1) -> Dict[str, Any]:
+ """
+ 获取域名列表
+
+ Args:
+ limit: 每页数量
+ page: 页码
+ """
+ query_params = {
+ 'limit': limit,
+ 'page': page,
+ }
+ return self._make_request('/domain/?act=getdomains', 'GET', query_params=query_params)
+
+ def get_domain_info(self, domain: str) -> Dict[str, Any]:
+ """
+ 获取域名详细信息
+
+ Args:
+ domain: 域名
+ """
+ body_params = {
+ 'domain': domain,
+ }
+ return self._make_request('/domain/?act=getinfo', 'POST', body_params=body_params)
+
+ def get_dns_records(self, domain: str) -> Dict[str, Any]:
+ """
+ 获取域名DNS记录
+
+ Args:
+ domain: 域名
+ """
+ body_params = {
+ 'domain': domain,
+ }
+ return self._make_request('/domain/?act=getdns', 'POST', body_params=body_params)
+
+ def get_dns_records_paginated(self, domain: str, limit: int = 20, pageno: int = 1) -> Dict[str, Any]:
+ """
+ 获取域名解析记录(支持分页)
+
+ Args:
+ domain: 域名
+ limit: 每页大小,默认20
+ pageno: 第几页,默认为1
+ """
+ body_params = {
+ 'act': 'getdnsrecord',
+ 'domain': domain,
+ 'limit': limit,
+ 'pageno': pageno,
+ }
+ return self._make_request('/domain/', 'POST', body_params=body_params)
+
+ def modify_dns_records(self, domain: str, records: List[Dict]) -> Dict[str, Any]:
+ """
+ 修改域名DNS记录
+
+ Args:
+ domain: 域名
+ records: DNS记录列表
+ """
+ body_params = {
+ 'domain': domain,
+ 'records': json.dumps(records),
+ }
+ return self._make_request('/domain/?act=modifydns', 'POST', body_params=body_params)
+
+ def add_dns_record(self, domain: str, record_type: str,
+ host: str, value: str, ttl: int = 600, level: int = 10, line: str = "") -> Dict[str, Any]:
+ """
+ 添加DNS记录
+
+ Args:
+ domain: 域名
+ record_type: 记录类型 (A, AAAA, CNAME, MX, NS, TXT, SRV)
+ host: 主机记录
+ value: 记录值
+ ttl: TTL值 (60~86400秒,默认900)
+ level: 优先级(MX记录使用,1-100,默认10)
+ line: 线路 (默认="", 电信="LTEL", 联通="LCNC", 移动="LMOB", 教育网="LEDU", 搜索引擎="LSEO")
+ """
+ body_params = {
+ 'act': 'adddnsrecord',
+ 'domain': domain,
+ 'host': host,
+ 'type': record_type,
+ 'value': value,
+ 'ttl': ttl,
+ 'level': level,
+ 'line': line,
+ }
+ return self._make_request('/domain/', 'POST', body_params=body_params)
+
+ def modify_dns_record(self, domain: str, value: str, ttl: int = 600, level: int = 10,
+ record_id: Optional[str] = None, host: Optional[str] = None,
+ record_type: Optional[str] = None, line: str = "", old_value: Optional[str] = None) -> Dict[str, Any]:
+ """
+ 修改DNS记录
+
+ Args:
+ domain: 域名
+ value: 新的解析值
+ ttl: TTL值 (60~86400秒,默认900)
+ level: 优先级(MX记录使用,1-100,默认10)
+ record_id: 解析记录编号(优先使用)
+ host: 主机头(当record_id未提供时必填)
+ record_type: 解析类型(当record_id未提供时必填)
+ line: 线路 (默认="", 电信="LTEL", 联通="LCNC", 移动="LMOB", 教育网="LEDU", 搜索引擎="LSEO")
+ old_value: 旧解析值(可选,用于确定唯一记录)
+ """
+ body_params = {
+ 'act': 'moddnsrecord',
+ 'domain': domain,
+ 'value': value,
+ 'ttl': ttl,
+ 'level': level,
+ 'line': line,
+ }
+
+ # 优先使用record_id
+ if record_id:
+ body_params['id'] = record_id
+ else:
+ # 必须提供host和type
+ if not host or not record_type:
+ return {"status": "error", "message": "当未提供记录ID时,主机头和解析类型为必填项"}
+ body_params['host'] = host
+ body_params['type'] = record_type
+
+ # 添加旧值(可选)
+ if old_value:
+ body_params['oldvalue'] = old_value
+
+ return self._make_request('/domain/', 'POST', body_params=body_params)
+
+ def delete_dns_record(self, domain: str, record_id: Optional[str] = None,
+ host: Optional[str] = None, record_type: Optional[str] = None,
+ value: Optional[str] = None, line: str = "") -> Dict[str, Any]:
+ """
+ 删除DNS记录
+
+ Args:
+ domain: 域名
+ record_id: 解析记录ID(优先使用)
+ host: 主机头(当record_id未提供时必填)
+ record_type: 解析类型(当record_id未提供时必填)
+ value: 解析值(可选)
+ line: 线路 (默认="", 电信="LTEL", 联通="LCNC", 移动="LMOB", 教育网="LEDU", 搜索引擎="LSEO")
+ """
+ body_params = {
+ 'act': 'deldnsrecord',
+ 'domain': domain,
+ 'line': line,
+ }
+
+ # 优先使用record_id
+ if record_id:
+ body_params['id'] = record_id
+ else:
+ # 必须提供host和type
+ if not host or not record_type:
+ return {"status": "error", "message": "当未提供记录ID时,主机头和解析类型为必填项"}
+ body_params['host'] = host
+ body_params['type'] = record_type
+ if value:
+ body_params['value'] = value
+
+ return self._make_request('/domain/', 'POST', body_params=body_params)
+
+ def transfer_domain(self, domain: str, auth_code: str) -> Dict[str, Any]:
+ """
+ 域名转入
+
+ Args:
+ domain: 域名
+ auth_code: 转移授权码
+ """
+ body_params = {
+ 'domain': domain,
+ 'auth_code': auth_code,
+ }
+ return self._make_request('/domain/?act=transfer', 'POST', body_params=body_params)
+
+ def lock_domain(self, domain: str, lock: bool = True) -> Dict[str, Any]:
+ """
+ 锁定/解锁域名
+
+ Args:
+ domain: 域名
+ lock: 是否锁定
+ """
+ action = '/domain/?act=lock' if lock else '/domain/?act=unlock'
+ body_params = {
+ 'domain': domain,
+ }
+ return self._make_request(action, 'POST', body_params=body_params)
+
+ def get_template_list(self, limit: int = 10, page: int = 1) -> Dict[str, Any]:
+ """
+ 获取域名模板列表
+
+ Args:
+ limit: 每页数量
+ page: 页码
+ """
+ body_params = {
+ 'act': 'gettemplates',
+ 'limit': limit,
+ 'page': page,
+ }
+ return self._make_request('/audit/', 'POST', body_params=body_params)
+
+ def get_template_detail(self, template_id: str) -> Dict[str, Any]:
+ """
+ 获取指定模板详情
+
+ Args:
+ template_id: 模板ID
+ """
+ body_params = {
+ 'act': 'auditinfo',
+ 'c_sysid': template_id,
+ }
+ return self._make_request('/audit/', 'POST', body_params=body_params)
+
+ def create_contact_template(self, template_data: Dict[str, Any]) -> Dict[str, Any]:
+ """
+ 创建域名模板
+
+ Args:
+ template_data: 模板数据,包含所有必要的字段
+ """
+ body_params = {
+ 'act': 'auditsub',
+ **template_data
+ }
+
+ # 对body_params进行GBK编码
+ encoded_data = urlencode(body_params, encoding='gbk').encode('gbk')
+
+ return self._make_request('/audit/', 'POST', body_params=encoded_data)
+
+ def modify_contact_template(self, template_data: Dict[str, Any]) -> Dict[str, Any]:
+ """
+ 修改实名模板资料
+
+ Args:
+ template_data: 模板数据,必须包含c_sysid字段
+ """
+ if not template_data.get('c_sysid'):
+ return {"status": "error", "message": "缺少必要参数:c_sysid"}
+
+ body_params = {
+ 'act': 'auditmod',
+ **template_data
+ }
+
+ # 对body_params进行GBK编码
+ encoded_data = urlencode(body_params, encoding='gbk').encode('gbk')
+
+ return self._make_request('/audit/', 'POST', body_params=encoded_data)
+
+ def delete_contact_template(self, template_id: str) -> Dict[str, Any]:
+ """
+ 删除指定模板
+
+ Args:
+ template_id: 模板标识(c_sysid)
+ """
+ if not template_id:
+ return {"status": "error", "message": "缺少必要参数:template_id"}
+
+ body_params = {
+ 'act': 'auditdel',
+ 'c_sysid': template_id,
+ }
+
+ return self._make_request('/audit/', 'POST', body_params=body_params)
+
+ def get_domain_real_info(self, domain: str) -> Dict[str, Any]:
+ """
+ 获取域名实名信息
+
+ Args:
+ domain: 域名
+
+ Returns:
+ 域名实名信息,包含所有者、管理联系人、技术联系人、缴费联系人等信息
+ """
+ body_params = {
+ 'act': 'domaininfo',
+ 'domain': domain,
+ }
+ return self._make_request('/audit/', 'POST', body_params=body_params)
+
+ def get_upload_token(self, c_sysid: str, f_type_org: str, f_code_org: str,
+ f_type_lxr: Optional[str] = None, f_code_lxr: Optional[str] = None) -> Dict[str, Any]:
+ """
+ 获取实名上传token
+
+ Args:
+ c_sysid: 模板标识
+ f_type_org: 证件类型,详情见附录
+ f_code_org: 证件号码
+ f_type_lxr: 联系人证件类型(企业时填写)
+ f_code_lxr: 联系人证件号码(企业时填写)
+
+ Returns:
+ API响应结果,包含上传token
+ """
+ body_params = {
+ 'act': 'uploadwcftoken',
+ 'c_sysid': c_sysid,
+ 'f_type_org': f_type_org,
+ 'f_code_org': f_code_org,
+ }
+
+ # 添加可选参数
+ if f_type_lxr:
+ body_params['f_type_lxr'] = f_type_lxr
+ if f_code_lxr:
+ body_params['f_code_lxr'] = f_code_lxr
+
+ return self._make_request('/audit/', 'POST', body_params=body_params)
+
+ def upload_real_name_files(self, token: str, file_org: str, file_lxr: Optional[str] = None) -> Dict[str, Any]:
+ """
+ 模板实名资料上传
+
+ Args:
+ token: 实名上传Token
+ file_org: 图片完整的base64
+ file_lxr: 企业联系人图片完整的base64(只有token中设置了联系人的才上传)
+
+ Returns:
+ API响应结果
+ """
+ upload_url = "https://netservice.vhostgo.com/wcfservice/Service1.svc/Wcf_AuditUploadFile"
+
+ # 构建请求数据
+ data = {
+ 'token': token,
+ 'file_org': file_org,
+ }
+
+ # 添加可选参数
+ if file_lxr:
+ data['file_lxr'] = file_lxr
+
+ headers = {
+ 'Content-Type': 'application/json'
+ }
+
+ try:
+ # 发送JSON格式的POST请求
+ response = requests.post(upload_url, json=data, headers=headers, timeout=30)
+
+ response.raise_for_status()
+
+ try:
+ result = response.json()
+ except json.JSONDecodeError:
+ result = {"status": "error", "message": "无法解析API响应"}
+
+ return result
+
+ except requests.exceptions.RequestException as e:
+ return {"status": "error", "message": f"API请求失败: {str(e)}"}
+
+ def modify_dns_server(self, domain: str, dns1: str, dns2: str,
+ dns3: Optional[str] = None, dns4: Optional[str] = None,
+ dns5: Optional[str] = None, dns6: Optional[str] = None) -> Dict[str, Any]:
+ """
+ 修改域名DNS服务器
+
+ Args:
+ domain: 要修改DNS的域名
+ dns1: 主DNS服务器
+ dns2: 辅DNS服务器
+ dns3: 第三个DNS服务器(可选)
+ dns4: 第四个DNS服务器(可选)
+ dns5: 第五个DNS服务器(可选)
+ dns6: 第六个DNS服务器(可选)
+
+ Returns:
+ API响应结果
+ """
+ body_params = {
+ 'act': 'moddns',
+ 'domain': domain,
+ 'dns1': dns1,
+ 'dns2': dns2,
+ }
+
+ # 添加可选的DNS服务器
+ if dns3:
+ body_params['dns3'] = dns3
+ if dns4:
+ body_params['dns4'] = dns4
+ if dns5:
+ body_params['dns5'] = dns5
+ if dns6:
+ body_params['dns6'] = dns6
+
+ return self._make_request('/domain/', 'POST', body_params=body_params)
+
+
+def format_date(date):
+ """格式化域名到期时间"""
+ if not date:
+ return None
+
+ if isinstance(date, str):
+ return date
+
+ return date.strftime('%Y-%m-%d')
+
+
+def get_west_client() -> WestDomain:
+ """获取西部数码域名API客户端实例"""
+ try:
+ # 从Jcloud Settings获取配置
+ settings = jingrow.get_single("Jcloud Settings")
+ if settings:
+ username = settings.get("west_username")
+ password = settings.get_password("west_api_password") if settings.get("west_api_password") else None
+
+ if username and password:
+ return WestDomain(username, password)
+ else:
+ return None
+ else:
+ return None
+ except Exception as e:
+ return None
+
+
+# API端点函数
+@jingrow.whitelist()
+def check_west_balance():
+ """检查西部数码账户余额"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ return client.check_balance()
+
+
+@jingrow.whitelist()
+def check_domain(domain: str, suffix: str = '.com'):
+ """查询域名是否可注册"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ response = client.query_domain(domain, suffix)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 直接检查响应格式
+ if response.get("result") != 200:
+ return {"status": "error", "message": "API查询失败"}
+
+ full_domain = domain + suffix
+ for item in response.get("data", []):
+ if item.get("name") == full_domain:
+ return {
+ "available": item.get("avail", 0) == 1,
+ "domain": full_domain,
+ "message": "域名可用" if item.get("avail", 0) == 1 else "域名已被注册"
+ }
+
+ return {"status": "error", "message": f"未找到域名 {full_domain} 的查询结果"}
+
+ except Exception as e:
+ return {"status": "error", "message": "域名查询响应解析失败"}
+
+
+@jingrow.whitelist()
+def get_west_domain_price(domain: str, year: int = 1):
+ """获取域名价格"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ response = client.get_domain_price(domain, year)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 直接检查响应格式
+ if response.get("result") != 200:
+ return {"status": "error", "message": "API查询失败"}
+
+ data = response.get("data", {})
+ original_price = data.get("buyprice", 0)
+
+ # 统一增加10%的利润,参考aliyun_server_light.py的方法
+ if original_price and original_price > 0:
+ # 确保10%利润率且价格为整数
+ adjusted_price = int(original_price / (1 - 0.1))
+ else:
+ adjusted_price = original_price
+
+ return {
+ "data": {
+ "price": adjusted_price,
+ "original_price": original_price, # 保留原价用于参考
+ "domain": domain,
+ "year": year
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "域名价格查询响应解析失败"}
+
+
+@jingrow.whitelist()
+def get_west_domain_renew_price(domain: str, year: int = 1):
+ """获取域名续费价格"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ response = client.get_domain_renew_price(domain, year)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 直接检查响应格式
+ if response.get("result") != 200:
+ return {"status": "error", "message": "API查询失败"}
+
+ data = response.get("data", {})
+ original_price = data.get("price", 0)
+
+ # 统一增加10%的利润,参考aliyun_server_light.py的方法
+ if original_price and original_price > 0:
+ # 确保10%利润率且价格为整数
+ adjusted_price = int(original_price / (1 - 0.1))
+ else:
+ adjusted_price = original_price
+
+ return {
+ "data": {
+ "price": adjusted_price,
+ "domain": domain,
+ "year": year,
+ "ispremium": data.get("ispremium", "n")
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "域名续费价格查询响应解析失败"}
+
+
+@jingrow.whitelist()
+def west_domain_register(domain: str, regyear: int = 1, dns_host1: str = "ns1.myhostadmin.net",
+ dns_host2: str = "ns2.myhostadmin.net", c_sysid: str = None,
+ domainpwd: str = None, dns_host3: str = None, dns_host4: str = None,
+ dns_host5: str = None, dns_host6: str = None, client_price: str = None,
+ premium: str = None, domchannel: str = None, westusechn: str = None):
+ """注册域名"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ return client.register_domain(
+ domain=domain,
+ regyear=regyear,
+ dns_host1=dns_host1,
+ dns_host2=dns_host2,
+ c_sysid=c_sysid,
+ domainpwd=domainpwd,
+ dns_host3=dns_host3,
+ dns_host4=dns_host4,
+ dns_host5=dns_host5,
+ dns_host6=dns_host6,
+ client_price=client_price,
+ premium=premium,
+ domchannel=domchannel,
+ westusechn=westusechn
+ )
+
+
+@jingrow.whitelist()
+def west_domain_renew(**data):
+ """域名续费"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ year = data.get('year', 1)
+ expire_date = data.get('expire_date')
+ client_price = data.get('client_price')
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ return client.renew_domain(domain, year, expire_date, client_price)
+
+
+@jingrow.whitelist()
+def west_domain_get_list(**data):
+ """获取域名列表"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ limit = data.get('limit', 10)
+ page = data.get('page', 1)
+
+ return client.get_domain_list(limit, page)
+
+
+@jingrow.whitelist()
+def west_domain_get_info(**data):
+ """获取域名信息"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ return client.get_domain_info(domain)
+
+
+@jingrow.whitelist()
+def get_west_domain_dns_records(**data):
+ """获取域名解析记录(支持分页)"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ limit = data.get('limit', 20)
+ pageno = data.get('pageno', 1)
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ # 获取所有记录(不分页)
+ response = client.get_dns_records_paginated(domain, 1000, 1) # 获取足够多的记录
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ return {"status": "error", "message": "API查询失败"}
+
+ data = response.get("data", {})
+ all_items = data.get("items", [])
+
+ # 对所有记录按类型排序
+ sorted_items = sorted(all_items, key=lambda x: x.get('type', ''))
+
+ # 计算分页
+ total = len(sorted_items)
+ pagecount = (total + limit - 1) // limit
+ start_index = (pageno - 1) * limit
+ end_index = start_index + limit
+
+ # 返回当前页的记录
+ current_page_items = sorted_items[start_index:end_index]
+
+ # 返回格式化的解析记录信息
+ return {
+ "status": "success",
+ "data": {
+ "pageno": pageno,
+ "limit": limit,
+ "total": total,
+ "pagecount": pagecount,
+ "items": current_page_items
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "域名解析记录查询响应解析失败"}
+
+
+@jingrow.whitelist()
+def get_jingrow_domain_dns_records(**data):
+ """获取域名解析记录(从本地数据库获取,支持分页)"""
+ try:
+ domain = data.get('domain')
+ limit = data.get('limit', 20)
+ pageno = data.get('pageno', 1)
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ # 查找对应的域名记录
+ domain_records = jingrow.get_all(
+ "Jsite Domain",
+ {"domain": domain},
+ ["name"]
+ )
+
+ if not domain_records:
+ return {"status": "error", "message": "未找到指定的域名记录"}
+
+ domain_record = domain_records[0]
+
+ # 获取该域名的所有DNS解析记录
+ dns_records = jingrow.get_all(
+ "Dns Resolution",
+ {"parent": domain_record.name},
+ ["name", "host", "type", "value", "ttl", "level", "line", "record_id"]
+ )
+
+ # 转换为API格式的数据结构
+ all_items = []
+ for record in dns_records:
+ item = {
+ "id": record.record_id or record.name,
+ "host": record.host or "",
+ "type": record.type or "",
+ "value": record.value or "",
+ "ttl": int(record.ttl) if record.ttl else 600,
+ "level": int(record.level) if record.level else 10,
+ "line": record.line or "",
+ "record_id": record.record_id or record.name
+ }
+ all_items.append(item)
+
+ # 对所有记录按类型排序
+ sorted_items = sorted(all_items, key=lambda x: x.get('type', ''))
+
+ # 计算分页
+ total = len(sorted_items)
+ pagecount = (total + limit - 1) // limit
+ start_index = (pageno - 1) * limit
+ end_index = start_index + limit
+
+ # 返回当前页的记录
+ current_page_items = sorted_items[start_index:end_index]
+
+ # 返回格式化的解析记录信息
+ return {
+ "status": "success",
+ "data": {
+ "pageno": pageno,
+ "limit": limit,
+ "total": total,
+ "pagecount": pagecount,
+ "items": current_page_items
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": f"域名解析记录查询失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def west_domain_modify_dns(**data):
+ """修改域名DNS记录(批量修改,兼容旧版本)"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ records = data.get('records')
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+ if not records:
+ return {"status": "error", "message": "缺少DNS记录参数"}
+
+ # 逐个修改记录
+ results = []
+ for record in records:
+ record_type = record.get('type')
+ host = record.get('host')
+ value = record.get('value')
+ ttl = record.get('ttl', 600)
+ level = record.get('level', 10)
+ line = record.get('line', '')
+ record_id = record.get('record_id')
+ old_value = record.get('old_value')
+
+ # 验证必要参数
+ if not value:
+ results.append({"status": "error", "message": "缺少解析值"})
+ continue
+
+ # 如果没有提供record_id,则必须提供host和record_type
+ if not record_id and (not host or not record_type):
+ results.append({"status": "error", "message": "当未提供记录ID时,主机头和解析类型为必填项"})
+ continue
+
+ # 验证TTL值
+ if ttl < 60 or ttl > 86400:
+ results.append({"status": "error", "message": "TTL值必须在60~86400秒之间"})
+ continue
+
+ # 验证优先级
+ if level < 1 or level > 100:
+ results.append({"status": "error", "message": "优先级必须在1~100之间"})
+ continue
+
+ # 验证记录类型
+ if record_type:
+ valid_types = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SRV']
+ if record_type not in valid_types:
+ results.append({"status": "error", "message": f"不支持的记录类型: {record_type}"})
+ continue
+
+ # 调用单个记录修改API
+ response = client.modify_dns_record(
+ domain, value, ttl, level, record_id, host, record_type, line, old_value
+ )
+
+ if response.get("status") == "error":
+ results.append(response)
+ elif response.get("result") != 200:
+ error_msg = response.get('msg', response.get('message', '未知错误'))
+ results.append({"status": "error", "message": f"修改DNS记录失败: {error_msg}"})
+ else:
+ results.append({
+ "status": "success",
+ "message": "DNS记录修改成功",
+ "data": {
+ "domain": domain,
+ "value": value,
+ "ttl": ttl,
+ "level": level,
+ "record_id": record_id,
+ "host": host,
+ "record_type": record_type,
+ "line": line
+ }
+ })
+
+ # 返回批量操作结果
+ success_count = sum(1 for r in results if r.get("status") == "success")
+ error_count = len(results) - success_count
+
+ return {
+ "status": "success" if error_count == 0 else "partial_success",
+ "message": f"批量修改完成,成功: {success_count},失败: {error_count}",
+ "results": results
+ }
+
+
+@jingrow.whitelist()
+def west_domain_add_dns_record(**data):
+ """添加DNS记录"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ record_type = data.get('record_type')
+ host = data.get('host')
+ value = data.get('value')
+ ttl = data.get('ttl', 600)
+ level = data.get('level', 10)
+ line = data.get('line', '')
+
+ if not all([domain, record_type, host, value]):
+ return {"status": "error", "message": "缺少必要参数"}
+
+ # 验证记录类型
+ valid_types = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SRV']
+ if record_type not in valid_types:
+ return {"status": "error", "message": f"不支持的记录类型: {record_type},支持的类型: {', '.join(valid_types)}"}
+
+ # 验证TTL值
+ if ttl < 60 or ttl > 86400:
+ return {"status": "error", "message": "TTL值必须在60~86400秒之间"}
+
+ # 验证优先级
+ if level < 1 or level > 100:
+ return {"status": "error", "message": "优先级必须在1~100之间"}
+
+ response = client.add_dns_record(domain, record_type, host, value, ttl, level, line)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ error_msg = response.get('msg', response.get('message', '未知错误'))
+ return {"status": "error", "message": f"添加DNS记录失败: {error_msg}"}
+
+ # 获取新增记录的ID
+ record_id = response.get("data", {}).get("id")
+
+ # 同步添加记录到本地数据库
+ if record_id:
+ try:
+ # 查找对应的域名记录
+ domain_records = jingrow.get_all(
+ "Jsite Domain",
+ {"domain": domain},
+ ["name"]
+ )
+
+ if domain_records:
+ domain_record = domain_records[0]
+ # 立即执行插入操作
+ # 先获取父记录
+ parent_pg = jingrow.get_pg("Jsite Domain", domain_record.name)
+ # 创建新的DNS记录作为子记录
+ dns_pg = jingrow.new_pg(
+ "Dns Resolution",
+ parent_pg=parent_pg,
+ parentfield="dns_resolution",
+ host=host,
+ type=record_type,
+ value=value,
+ ttl=str(ttl),
+ level=str(level),
+ line=line,
+ record_id=record_id
+ )
+ dns_pg.insert(ignore_permissions=True)
+ jingrow.db.commit()
+ except Exception as e:
+ jingrow.log_error(f"域名 {domain} DNS记录同步添加失败", f"错误: {str(e)}")
+
+ # 返回成功结果
+ return {
+ "status": "success",
+ "message": "DNS记录添加成功",
+ "data": {
+ "id": record_id,
+ "domain": domain,
+ "host": host,
+ "type": record_type,
+ "value": value,
+ "ttl": ttl,
+ "level": level,
+ "line": line
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "添加DNS记录响应解析失败"}
+
+
+@jingrow.whitelist()
+def west_domain_delete_dns_record(**data):
+ """删除DNS记录"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ record_id = data.get('record_id')
+ host = data.get('host')
+ record_type = data.get('record_type')
+ value = data.get('value')
+ line = data.get('line', '')
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ # 如果没有提供record_id,则必须提供host和record_type
+ if not record_id and (not host or not record_type):
+ return {"status": "error", "message": "当未提供记录ID时,主机头和解析类型为必填项"}
+
+ # 验证记录类型
+ if record_type:
+ valid_types = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SRV']
+ if record_type not in valid_types:
+ return {"status": "error", "message": f"不支持的记录类型: {record_type},支持的类型: {', '.join(valid_types)}"}
+
+ response = client.delete_dns_record(domain, record_id, host, record_type, value, line)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ error_msg = response.get('msg', response.get('message', '未知错误'))
+ return {"status": "error", "message": f"删除DNS记录失败: {error_msg}"}
+
+ # 同步删除本地数据库中的对应记录
+ if record_id:
+ try:
+ dns_records = jingrow.get_all(
+ "Dns Resolution",
+ {"record_id": record_id},
+ ["name"]
+ )
+
+ if dns_records:
+ dns_record = dns_records[0]
+ # 立即执行删除操作
+ jingrow.delete_pg("Dns Resolution", dns_record.name, ignore_permissions=True)
+ jingrow.db.commit()
+ except Exception as e:
+ jingrow.log_error(f"域名 {domain} DNS记录同步删除失败", f"错误: {str(e)}")
+
+ # 返回成功结果
+ return {
+ "status": "success",
+ "message": "DNS记录删除成功"
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "删除DNS记录响应解析失败"}
+
+
+@jingrow.whitelist()
+def west_domain_delete_dns_records(**data):
+ """批量删除DNS记录"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ record_ids = data.get('record_ids')
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ if not record_ids:
+ return {"status": "error", "message": "缺少记录ID列表"}
+
+ # 处理record_ids参数,支持字符串和列表格式
+ if isinstance(record_ids, str):
+ # 如果是逗号分隔的字符串,转换为列表
+ record_ids = [rid.strip() for rid in record_ids.split(',') if rid.strip()]
+ elif not isinstance(record_ids, list):
+ return {"status": "error", "message": "记录ID列表格式错误"}
+
+ if not record_ids:
+ return {"status": "error", "message": "记录ID列表不能为空"}
+
+ # 验证记录ID格式
+ for record_id in record_ids:
+ if not record_id or not isinstance(record_id, str):
+ return {"status": "error", "message": "记录ID格式错误"}
+
+ # 批量删除逻辑
+ results = []
+ success_count = 0
+ error_count = 0
+
+ for record_id in record_ids:
+ try:
+ result = client.delete_dns_record(domain, record_id)
+ if result.get("status") == "error" or result.get("result") != 200:
+ error_count += 1
+ error_msg = result.get('msg', result.get('message', '删除失败'))
+ results.append({
+ "record_id": record_id,
+ "status": "error",
+ "message": error_msg
+ })
+ else:
+ success_count += 1
+ results.append({
+ "record_id": record_id,
+ "status": "success"
+ })
+
+ # 同步删除本地数据库中的对应记录
+ try:
+ dns_records = jingrow.get_all(
+ "Dns Resolution",
+ {"record_id": record_id},
+ ["name"]
+ )
+
+ if dns_records:
+ dns_record = dns_records[0]
+ # 立即执行删除操作
+ jingrow.delete_pg("Dns Resolution", dns_record.name, ignore_permissions=True)
+ jingrow.db.commit()
+ except Exception as e:
+ jingrow.log_error(f"域名 {domain} DNS记录同步删除失败", f"记录ID: {record_id}, 错误: {str(e)}")
+
+ except Exception as e:
+ error_count += 1
+ results.append({
+ "record_id": record_id,
+ "status": "error",
+ "message": str(e)
+ })
+
+ # 返回批量删除结果
+ if error_count == 0:
+ return {
+ "status": "success",
+ "message": f"批量删除成功,共删除 {success_count} 条记录"
+ }
+ elif success_count == 0:
+ return {
+ "status": "error",
+ "message": f"批量删除失败,共 {error_count} 条记录删除失败"
+ }
+ else:
+ return {
+ "status": "partial_success",
+ "message": f"批量删除部分成功,成功 {success_count} 条,失败 {error_count} 条"
+ }
+
+
+@jingrow.whitelist()
+def west_domain_transfer(**data):
+ """域名转入"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ auth_code = data.get('auth_code')
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+ if not auth_code:
+ return {"status": "error", "message": "缺少转移授权码参数"}
+
+ return client.transfer_domain(domain, auth_code)
+
+
+@jingrow.whitelist()
+def west_domain_lock(**data):
+ """锁定域名"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ lock = data.get('lock', True)
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ return client.lock_domain(domain, lock)
+
+
+@jingrow.whitelist()
+def west_domain_get_templates(**data):
+ """获取域名模板列表"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ limit = data.get('limit', 10)
+ page = data.get('page', 1)
+
+ return client.get_template_list(limit, page)
+
+
+@jingrow.whitelist()
+def get_west_template_detail(**data):
+ """获取指定模板详情"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ template_id = data.get('template_id')
+ if not template_id:
+ return {"status": "error", "message": "缺少模板ID参数"}
+
+ return client.get_template_detail(template_id)
+
+
+@jingrow.whitelist()
+def create_domain_order(domain, period=1, payment_method='balance', domain_owner=None):
+ """创建域名注册订单"""
+ try:
+ # 获取当前用户团队
+ team = get_current_team(True)
+
+ # 验证域名格式
+ if not domain or '.' not in domain:
+ return {"success": False, "message": "域名格式不正确"}
+
+ # 验证域名所有者
+ if not domain_owner:
+ return {"success": False, "message": "请选择域名所有者"}
+
+ # 检查域名所有者是否存在
+ owner_exists = jingrow.get_all(
+ "Domain Owner",
+ {"name": domain_owner, "team": team.name},
+ ["name"]
+ )
+ if not owner_exists:
+ return {"success": False, "message": "域名所有者不存在"}
+
+ # 查询域名价格
+ client = get_west_client()
+ if not client:
+ return {"success": False, "message": "API客户端初始化失败"}
+
+ # 获取域名价格 - 根据实际购买的年限
+ price_result = get_west_domain_price(domain, period)
+ if price_result.get("status") == "error":
+ return {"success": False, "message": "获取域名价格失败"}
+
+ # 使用对应年限的总价
+ total_amount = price_result.get("data", {}).get("price", 0)
+
+ # 生成订单号
+ order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6))}"
+
+ # 获取域名所有者的c_sysid
+ domain_owner_pg = jingrow.get_pg("Domain Owner", domain_owner)
+ if not domain_owner_pg:
+ return {"success": False, "message": "域名所有者不存在"}
+
+ c_sysid = domain_owner_pg.c_sysid
+ if not c_sysid:
+ return {"success": False, "message": "域名所有者缺少系统ID,请重新创建域名所有者"}
+
+ # 构建业务参数
+ biz_params = {
+ "domain": domain,
+ "period": period,
+ "domain_owner": domain_owner,
+ "total_price": total_amount,
+ "auto_renew": False,
+ # 注册域名所需参数
+ "regyear": period,
+ "dns_host1": "ns1.myhostadmin.net",
+ "dns_host2": "ns2.myhostadmin.net",
+ "c_sysid": c_sysid,
+ "client_price": None
+ }
+
+ # 创建订单记录
+ order = jingrow.get_pg({
+ "pagetype": "Order",
+ "order_id": order_id,
+ "order_type": "域名注册",
+ "team": team.name,
+ "status": "待支付",
+ "total_amount": total_amount,
+ "title": domain,
+ "description": f"{period}年",
+ "biz_params": json.dumps(biz_params, ensure_ascii=False)
+ })
+ order.insert(ignore_permissions=True)
+
+ jingrow.db.commit()
+
+ return {
+ "success": True,
+ "message": "订单创建成功",
+ "order": order.as_dict()
+ }
+
+ except Exception as e:
+ return {"success": False, "message": f"创建订单失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def create_domain_renew_order(**kwargs):
+ """创建域名续费订单"""
+ try:
+ domain = kwargs.get('domain')
+ renewal_years = kwargs.get('renewal_years', 1)
+
+ if not domain:
+ jingrow.throw("缺少域名信息")
+
+ # 验证输入
+ domain_pg = jingrow.get_pg("Jsite Domain", domain)
+ if not domain_pg:
+ jingrow.throw("域名不存在")
+
+ team = domain_pg.team
+
+ # 验证当前用户权限
+ current_team = get_current_team(True)
+ if current_team.name != team:
+ jingrow.throw("您没有权限为此域名创建续费订单")
+
+ # 计算续费金额 - 使用现有的价格获取函数保持统一
+ renewal_years = int(renewal_years)
+
+ # 根据实际续费年限直接获取价格
+ price_data = get_west_domain_renew_price(domain_pg.domain, renewal_years)
+ if price_data.get("data", {}).get("price"):
+ total_amount = price_data["data"]["price"]
+ else:
+ return {"success": False, "message": "获取续费价格失败"}
+
+ # 生成唯一订单号
+ order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S%f')[:-3] + ''.join(random.choices('0123456789', k=6))}"
+
+ # 构建业务参数
+ biz_params = {
+ "domain": domain_pg.domain,
+ "domain_name": domain,
+ "renewal_years": renewal_years,
+ "total_price": total_amount,
+ "domain_owner": domain_pg.domain_owner,
+ # 续费域名所需参数
+ "year": renewal_years,
+ "expire_date": format_date(domain_pg.end_date),
+ "client_price": None
+ }
+
+ # 创建订单记录
+ order = jingrow.get_pg({
+ "pagetype": "Order",
+ "order_id": order_id,
+ "order_type": "域名续费",
+ "team": team,
+ "status": "待支付",
+ "total_amount": total_amount,
+ "title": domain_pg.domain,
+ "description": str(renewal_years), # 存储续费年数
+ "biz_params": json.dumps(biz_params, ensure_ascii=False)
+ })
+
+ order.insert(ignore_permissions=True)
+ jingrow.db.commit()
+
+ return {
+ "success": True,
+ "order": order.as_dict()
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "message": f"创建续费订单失败: {str(e)}"
+ }
+
+
+def register_domain_from_order(order_name):
+ """支付成功后异步注册域名"""
+ try:
+ order = jingrow.get_pg("Order", order_name)
+ if not order:
+ raise Exception("订单不存在")
+
+ # 从biz_params中读取业务参数
+ biz_params = json.loads(order.biz_params) if order.biz_params else {}
+ domain_name = biz_params.get("domain")
+ period = biz_params.get("period", 1)
+ domain_owner = biz_params.get("domain_owner")
+
+ if not domain_name:
+ raise Exception("订单中缺少域名信息")
+
+ # 调用西部数码API注册域名
+ client = get_west_client()
+ if not client:
+ raise Exception("API客户端初始化失败")
+
+ result = client.register_domain(
+ domain=domain_name,
+ regyear=biz_params.get("regyear", period),
+ dns_host1=biz_params.get("dns_host1", "ns1.myhostadmin.net"),
+ dns_host2=biz_params.get("dns_host2", "ns2.myhostadmin.net"),
+ c_sysid=biz_params.get("c_sysid"),
+ client_price=biz_params.get("client_price")
+ )
+
+ if not result or result.get('result') != 200:
+ error_msg = result.get('msg', result.get('message', '未知错误'))
+ raise Exception(f"域名注册失败: {error_msg}")
+
+ # 创建域名记录
+ domain_pg = jingrow.get_pg({
+ "pagetype": "Jsite Domain",
+ "domain": domain_name,
+ "team": order.team,
+ "order_id": order.order_id,
+ "period": period,
+ "domain_owner": domain_owner,
+ "registration_date": jingrow.utils.nowdate(),
+ "end_date": jingrow.utils.add_months(jingrow.utils.nowdate(), period * 12)
+ })
+ domain_pg.insert(ignore_permissions=True)
+
+ # 更新订单状态
+ order.status = "交易成功"
+ order.save(ignore_permissions=True)
+
+ jingrow.db.commit()
+
+ # 异步执行域名信息同步
+ try:
+ jingrow.enqueue(
+ "jcloud.api.domain_west.sync_domain_info_from_west",
+ domain=domain_name,
+ queue="default",
+ timeout=300
+ )
+ except Exception as e:
+ jingrow.log_error(f"域名 {domain_name} 异步同步任务创建失败", f"错误: {str(e)}")
+
+ return True
+
+ except Exception as e:
+ raise e
+
+
+def renew_domain_from_order(order_name):
+ """支付成功后异步续费域名"""
+ try:
+ order = jingrow.get_pg("Order", order_name)
+ if not order:
+ raise Exception("订单不存在")
+
+ # 从biz_params中读取业务参数
+ biz_params = json.loads(order.biz_params) if order.biz_params else {}
+ domain_name = biz_params.get("domain")
+ renewal_years = biz_params.get("renewal_years", 1)
+ domain_record_name = biz_params.get("domain_name") # 域名记录的名称
+
+ if not domain_name:
+ raise Exception("订单中缺少域名信息")
+
+ # 查找域名记录
+ domain = jingrow.get_pg("Jsite Domain", domain_record_name)
+ if not domain:
+ raise Exception("找不到对应的域名记录")
+
+ # 调用西部数码API续费域名
+ client = get_west_client()
+ if not client:
+ raise Exception("API客户端初始化失败")
+
+ # 验证域名到期时间
+ if not biz_params.get("expire_date"):
+ raise Exception("无法获取域名到期时间")
+
+ result = client.renew_domain(
+ domain_name,
+ biz_params.get("year", renewal_years),
+ biz_params.get("expire_date"),
+ biz_params.get("client_price")
+ )
+
+ if not result or result.get('result') != 200:
+ error_msg = result.get('msg', result.get('message', '未知错误'))
+ raise Exception(f"域名续费失败: {error_msg}")
+
+ # 更新域名到期时间
+ domain.end_date = jingrow.utils.add_months(domain.end_date or jingrow.utils.nowdate(), renewal_years * 12)
+ domain.save(ignore_permissions=True)
+
+ # 更新订单状态
+ order.status = "交易成功"
+ order.save(ignore_permissions=True)
+
+ jingrow.db.commit()
+
+ return True
+
+ except Exception as e:
+ raise e
+
+
+@jingrow.whitelist()
+def toggle_domain_auto_renew(pagetype, name, auto_renew):
+ """切换域名自动续费状态"""
+ try:
+ # 获取当前用户团队
+ team = get_current_team(True)
+
+ # 获取域名记录
+ domain = jingrow.get_pg(pagetype, name)
+ if not domain:
+ return {"success": False, "message": "找不到域名记录"}
+
+ # 验证权限
+ if domain.team != team.name:
+ return {"success": False, "message": "您没有权限操作此域名"}
+
+ # 更新自动续费状态
+ domain.auto_renew = bool(auto_renew)
+ domain.save(ignore_permissions=True)
+
+ return {"success": True, "message": "自动续费状态更新成功"}
+
+ except Exception as e:
+ return {"success": False, "message": f"操作失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def toggle_domain_whois_protection(pagetype, name, whois_protection):
+ """切换域名隐私保护状态"""
+ try:
+ # 获取当前用户团队
+ team = get_current_team(True)
+
+ # 获取域名记录
+ domain = jingrow.get_pg(pagetype, name)
+ if not domain:
+ return {"success": False, "message": "找不到域名记录"}
+
+ # 验证权限
+ if domain.team != team.name:
+ return {"success": False, "message": "您没有权限操作此域名"}
+
+ # 更新隐私保护状态
+ domain.whois_protection = bool(whois_protection)
+ domain.save(ignore_permissions=True)
+
+ return {"success": True, "message": "隐私保护状态更新成功"}
+
+ except Exception as e:
+ return {"success": False, "message": f"操作失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def delete_domain(pagetype, name):
+ """删除域名记录"""
+ try:
+ # 获取当前用户团队
+ team = get_current_team(True)
+
+ # 获取域名记录
+ domain = jingrow.get_pg(pagetype, name)
+ if not domain:
+ return {"success": False, "message": "找不到域名记录"}
+
+ # 验证权限
+ if domain.team != team.name:
+ return {"success": False, "message": "您没有权限操作此域名"}
+
+ # 删除域名记录
+ domain.delete(ignore_permissions=True)
+
+ return {"success": True, "message": "域名记录删除成功"}
+
+ except Exception as e:
+ return {"success": False, "message": f"删除失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def get_domain_owners(**data):
+ """获取当前团队的域名所有者列表(支持分页、搜索和过滤)"""
+ try:
+ team = get_current_team()
+ if not team:
+ return {"status": "Error", "message": "未找到当前团队"}
+
+ # 获取分页参数
+ limit = data.get('limit', 20)
+ pageno = data.get('pageno', 1)
+ search_query = data.get('searchQuery', '').strip()
+ selected_status = data.get('selectedStatus', '')
+
+ # 确保参数类型正确
+ try:
+ limit = int(limit)
+ pageno = int(pageno)
+ except (ValueError, TypeError):
+ limit = 20
+ pageno = 1
+
+ # 确保参数在合理范围内
+ limit = max(1, min(limit, 100)) # 限制每页最多100条
+ pageno = max(1, pageno)
+
+ # 获取当前团队的所有域名所有者
+ domain_owners = jingrow.get_all(
+ "Domain Owner",
+ {"team": team},
+ ["name", "title", "fullname", "c_regtype", "c_org_m", "c_ln_m", "c_fn_m",
+ "c_em", "c_ph", "c_st_m", "c_ct_m", "c_adr_m", "c_pc", "r_status",
+ "c_idtype_gswl", "c_idnum_gswl", "c_sysid"]
+ )
+
+ # 搜索过滤
+ if search_query:
+ search_query_lower = search_query.lower()
+ filtered_owners = []
+ for owner in domain_owners:
+ # 检查显示名称
+ display_name = ""
+ if owner.c_regtype == 'I':
+ if owner.fullname:
+ display_name = owner.fullname
+ else:
+ display_name = (owner.c_ln_m or '') + (owner.c_fn_m or '')
+ elif owner.c_regtype == 'E':
+ display_name = owner.c_org_m or owner.title or owner.name
+ else:
+ display_name = owner.title or owner.name
+
+ # 检查是否匹配搜索条件
+ if (search_query_lower in display_name.lower() or
+ search_query_lower in (owner.title or '').lower() or
+ search_query_lower in (owner.c_em or '').lower()):
+ filtered_owners.append(owner)
+ domain_owners = filtered_owners
+
+ # 状态过滤
+ if selected_status:
+ filtered_owners = []
+ for owner in domain_owners:
+ r_status = owner.r_status
+ is_verified = r_status == '1'
+ if str('1' if is_verified else '0') == selected_status:
+ filtered_owners.append(owner)
+ domain_owners = filtered_owners
+
+ # 计算分页信息
+ total = len(domain_owners)
+ pagecount = (total + limit - 1) // limit
+ start_index = (pageno - 1) * limit
+ end_index = start_index + limit
+
+ # 返回当前页的记录
+ current_page_items = domain_owners[start_index:end_index]
+
+ return {
+ "status": "Success",
+ "data": {
+ "pageno": pageno,
+ "limit": limit,
+ "total": total,
+ "pagecount": pagecount,
+ "items": current_page_items
+ }
+ }
+ except Exception as e:
+ return {"status": "Error", "message": f"获取域名所有者列表失败: {str(e)}"}
+
+@jingrow.whitelist()
+def get_domain_owner(name):
+ """获取单个域名所有者信息"""
+ try:
+ if not name:
+ return {"status": "Error", "message": "域名所有者名称不能为空"}
+
+ # 获取指定的域名所有者
+ domain_owner = jingrow.get_pg("Domain Owner", name)
+ if not domain_owner:
+ return {"status": "Error", "message": "未找到指定的域名所有者"}
+
+ # 检查权限(只能查看当前团队的所有者)
+ team = get_current_team()
+ if not team or domain_owner.team != team:
+ return {"status": "Error", "message": "无权访问该域名所有者信息"}
+
+ # 返回所有者信息
+ owner_data = {
+ "name": domain_owner.name,
+ "r_status": domain_owner.r_status,
+ "title": domain_owner.title,
+ "fullname": domain_owner.fullname,
+ "c_regtype": domain_owner.c_regtype,
+ "c_org_m": domain_owner.c_org_m,
+ "c_ln_m": domain_owner.c_ln_m,
+ "c_fn_m": domain_owner.c_fn_m,
+ "c_em": domain_owner.c_em,
+ "c_ph": domain_owner.c_ph,
+ "c_st_m": domain_owner.c_st_m,
+ "c_ct_m": domain_owner.c_ct_m,
+ "c_adr_m": domain_owner.c_adr_m,
+ "c_pc": domain_owner.c_pc,
+ "c_idtype_gswl": domain_owner.c_idtype_gswl,
+ "c_idnum_gswl": domain_owner.c_idnum_gswl
+ }
+
+ return {
+ "status": "Success",
+ "data": owner_data
+ }
+ except Exception as e:
+ return {"status": "Error", "message": f"获取域名所有者信息失败: {str(e)}"}
+
+@jingrow.whitelist()
+def create_domain_template(**data):
+ """创建域名模板"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ # 验证必填字段
+ required_fields = ['c_regtype', 'c_ln_m', 'c_fn_m', 'c_st_m', 'c_ct_m', 'c_adr_m', 'c_pc', 'c_em', 'c_idtype_gswl', 'c_idnum_gswl']
+ for field in required_fields:
+ if not data.get(field):
+ return {"status": "error", "message": f"字段 {field} 是必填项"}
+
+ # 验证电话号码字段(根据c_ph_type判断)
+ c_ph_type = data.get('c_ph_type', '0') # 默认为手机
+ if c_ph_type == '0': # 手机
+ if not data.get('c_ph'):
+ return {"status": "error", "message": "手机号码 c_ph 是必填项"}
+ elif c_ph_type == '1': # 座机
+ if not data.get('c_ph_code'):
+ return {"status": "error", "message": "座机区号 c_ph_code 是必填项"}
+ if not data.get('c_ph_num'):
+ return {"status": "error", "message": "座机号码 c_ph_num 是必填项"}
+
+ # 验证字段长度
+ if len(data.get('c_ln_m', '')) < 1 or len(data.get('c_ln_m', '')) > 16:
+ return {"status": "error", "message": "姓(中文)长度必须为1~16位"}
+
+ if len(data.get('c_fn_m', '')) < 1 or len(data.get('c_fn_m', '')) > 64:
+ return {"status": "error", "message": "名(中文)长度必须为1~64位"}
+
+ if len(data.get('c_st_m', '')) < 2 or len(data.get('c_st_m', '')) > 10:
+ return {"status": "error", "message": "省份(中文)长度必须为2~10位"}
+
+ # 验证完整姓名长度
+ fullname = data.get('c_ln_m', '') + data.get('c_fn_m', '')
+ if len(fullname) < 2 or len(fullname) > 64:
+ return {"status": "error", "message": "完整姓名(中文)长度必须为2~64位"}
+
+ # 生成英文姓名
+ if data.get('c_ln_m') and data.get('c_fn_m'):
+ last_name_pinyin = ' '.join(lazy_pinyin(data['c_ln_m']))
+ first_name_pinyin = ' '.join(lazy_pinyin(data['c_fn_m']))
+ data['c_ln'] = last_name_pinyin.title()
+ data['c_fn'] = first_name_pinyin.title()
+ else:
+ # 设置默认的英文姓名
+ data['c_ln'] = data.get('c_ln', '')
+ data['c_fn'] = data.get('c_fn', '')
+
+ # 生成英文地址
+ if data.get('c_adr_m'):
+ address_pinyin = ' '.join(lazy_pinyin(data['c_adr_m']))
+ data['c_adr'] = address_pinyin.title()
+ else:
+ data['c_adr'] = data.get('c_adr', '')
+
+ # 生成英文省份和城市
+ if data.get('c_st_m'):
+ state_pinyin = ' '.join(lazy_pinyin(data['c_st_m']))
+ data['c_st'] = state_pinyin.title()
+ else:
+ data['c_st'] = data.get('c_st', '')
+
+ if data.get('c_ct_m'):
+ city_pinyin = ' '.join(lazy_pinyin(data['c_ct_m']))
+ data['c_ct'] = city_pinyin.title()
+ else:
+ data['c_ct'] = data.get('c_ct', '')
+
+ # 生成英文单位名称
+ if data.get('c_org_m'):
+ org_pinyin = ' '.join(lazy_pinyin(data['c_org_m']))
+ data['c_org'] = org_pinyin.title()
+ else:
+ data['c_org'] = data.get('c_org', '')
+
+ # 设置默认值
+ template_data = {
+ 'c_regtype': data['c_regtype'],
+ 'c_ln_m': data['c_ln_m'],
+ 'c_fn_m': data['c_fn_m'],
+ 'c_co': data.get('c_co', 'CN'),
+ 'cocode': data.get('cocode', '+86'),
+ 'c_st_m': data['c_st_m'],
+ 'c_ct_m': data['c_ct_m'],
+ 'c_dt_m': data.get('c_dt_m', ''),
+ 'c_adr_m': data['c_adr_m'],
+ 'c_pc': data['c_pc'],
+ 'c_ph_type': data.get('c_ph_type', '0'),
+ 'c_em': data['c_em'],
+ 'c_ln': data.get('c_ln', ''), # 使用生成的英文姓,如果没有则为空
+ 'c_fn': data.get('c_fn', ''), # 使用生成的英文名,如果没有则为空
+ 'c_st': data.get('c_st', ''), # 使用生成的英文省份,如果没有则为空
+ 'c_ct': data.get('c_ct', ''), # 使用生成的英文城市,如果没有则为空
+ 'c_adr': data.get('c_adr', ''), # 使用生成的英文地址,如果没有则为空
+ 'c_idtype_gswl': data['c_idtype_gswl'],
+ 'c_idnum_gswl': data['c_idnum_gswl'],
+ 'fullname': data.get('c_ln_m', '') + data.get('c_fn_m', '') # 完整姓名
+ }
+
+ # 个人类型时不传入c_org_m参数
+ if data['c_regtype'] == 'E' and data.get('c_org_m'):
+ template_data['c_org_m'] = data['c_org_m']
+ template_data['c_org'] = data.get('c_org', '') # 英文单位名称
+
+ # 验证企业类型必须填写单位名称
+ if data['c_regtype'] == 'E' and not data.get('c_org_m'):
+ return {"status": "error", "message": "企业类型必须填写单位名称"}
+
+ # 添加区县信息
+ if data.get('c_dt_m'):
+ template_data['c_dt_m'] = data['c_dt_m']
+
+ # 添加香港域名相关字段
+ if data.get('c_idtype_hk'):
+ template_data['c_idtype_hk'] = data['c_idtype_hk']
+ if data.get('c_idnum_hk'):
+ template_data['c_idnum_hk'] = data['c_idnum_hk']
+
+ # 根据c_ph_type添加电话相关字段
+ if data.get('c_ph_type') == '1': # 座机
+ if data.get('c_ph_code'):
+ template_data['c_ph_code'] = data['c_ph_code']
+ if data.get('c_ph_num'):
+ template_data['c_ph_num'] = data['c_ph_num']
+ # 构建完整的电话号码格式
+ if data.get('c_ph_code') and data.get('c_ph_num'):
+ template_data['c_ph_all'] = f"{data.get('cocode', '+86')}.{data['c_ph_code']}-{data['c_ph_num']}"
+ else: # 手机
+ if data.get('c_ph'):
+ template_data['c_ph'] = data['c_ph']
+
+ try:
+ result = client.create_contact_template(template_data)
+
+ if result.get('result') == 200:
+ c_sysid = result.get('data', {}).get('c_sysid')
+ return {
+ "status": "success",
+ "message": "域名模板创建成功",
+ "data": {
+ "c_sysid": c_sysid
+ }
+ }
+ else:
+ error_msg = result.get('msg', result.get('message', '未知错误'))
+ return {"status": "error", "message": f"创建域名模板失败: {error_msg}"}
+
+ except Exception as e:
+ return {"status": "error", "message": f"创建域名模板失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def create_domain_owner(**data):
+ """创建新的域名所有者"""
+ try:
+ team = get_current_team()
+ if not team:
+ return {"status": "Error", "message": "未找到当前团队"}
+
+ # 验证基本必填字段
+ basic_required_fields = ['c_regtype', 'c_ln_m', 'c_fn_m', 'c_st_m', 'c_ct_m', 'c_adr_m', 'c_pc', 'c_em']
+ for field in basic_required_fields:
+ if not data.get(field):
+ return {"status": "Error", "message": f"字段 {field} 是必填项"}
+
+ # 验证电话号码字段(根据c_ph_type判断)
+ c_ph_type = data.get('c_ph_type', '0') # 默认为手机
+ if c_ph_type == '0': # 手机
+ if not data.get('c_ph'):
+ return {"status": "Error", "message": "手机号码 c_ph 是必填项"}
+ elif c_ph_type == '1': # 座机
+ if not data.get('c_ph_code'):
+ return {"status": "Error", "message": "座机区号 c_ph_code 是必填项"}
+ if not data.get('c_ph_num'):
+ return {"status": "Error", "message": "座机号码 c_ph_num 是必填项"}
+
+ # 生成英文姓名
+ if data.get('c_ln_m') and data.get('c_fn_m'):
+ last_name_pinyin = ' '.join(lazy_pinyin(data['c_ln_m']))
+ first_name_pinyin = ' '.join(lazy_pinyin(data['c_fn_m']))
+ data['c_ln'] = last_name_pinyin.title()
+ data['c_fn'] = first_name_pinyin.title()
+ else:
+ # 设置默认的英文姓名
+ data['c_ln'] = data.get('c_ln', '')
+ data['c_fn'] = data.get('c_fn', '')
+
+ # 生成英文地址
+ if data.get('c_adr_m'):
+ address_pinyin = ' '.join(lazy_pinyin(data['c_adr_m']))
+ data['c_adr'] = address_pinyin.title()
+ else:
+ data['c_adr'] = data.get('c_adr', '')
+
+ # 生成英文省份和城市
+ if data.get('c_st_m'):
+ state_pinyin = ' '.join(lazy_pinyin(data['c_st_m']))
+ data['c_st'] = state_pinyin.title()
+ else:
+ data['c_st'] = data.get('c_st', '')
+
+ if data.get('c_ct_m'):
+ city_pinyin = ' '.join(lazy_pinyin(data['c_ct_m']))
+ data['c_ct'] = city_pinyin.title()
+ else:
+ data['c_ct'] = data.get('c_ct', '')
+
+ # 生成英文单位名称
+ if data.get('c_org_m'):
+ org_pinyin = ' '.join(lazy_pinyin(data['c_org_m']))
+ data['c_org'] = org_pinyin.title()
+ else:
+ data['c_org'] = data.get('c_org', '')
+
+ # 设置默认值
+ data['team'] = team
+ data['c_co'] = data.get('c_co', 'CN') # 中国
+ data['cocode'] = data.get('cocode', '+86')
+ data['c_ph_type'] = data.get('c_ph_type', '0') # 手机
+ data['reg_contact_type'] = data.get('reg_contact_type', 'cg') # 常规
+
+ # 确保c_sysid字段存在(如果提供)
+ if data.get('c_sysid'):
+ data['c_sysid'] = data['c_sysid']
+
+ # 生成完整姓名和标题
+ data['fullname'] = data.get('c_ln_m', '') + data.get('c_fn_m', '')
+
+ if data['c_regtype'] == 'I': # 个人
+ data['title'] = f"{data['c_ln_m']}{data['c_fn_m']}"
+ else: # 企业
+ data['title'] = f"{data['c_org_m']}"
+
+ # 创建域名所有者记录
+ domain_owner = jingrow.get_pg({
+ "pagetype": "Domain Owner",
+ **data
+ })
+ domain_owner.insert(ignore_permissions=True)
+
+ return {
+ "status": "Success",
+ "message": "域名所有者创建成功",
+ "data": {
+ "name": domain_owner.name,
+ "title": domain_owner.title
+ }
+ }
+ except Exception as e:
+ return {"status": "Error", "message": f"创建域名所有者失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def create_domain_owner_with_template(**data):
+ """创建域名所有者(包含模板创建)"""
+ try:
+ # 第一步:创建域名模板
+ template_result = create_domain_template(**data)
+
+ if template_result.get("status") != "success":
+ return template_result
+
+ # 获取模板ID
+ c_sysid = template_result.get("data", {}).get("c_sysid")
+ if not c_sysid:
+ return {"status": "Error", "message": "创建域名模板成功但未返回模板ID"}
+
+ # 第二步:创建域名所有者记录
+ data['c_sysid'] = c_sysid
+ owner_result = create_domain_owner(**data)
+
+ if owner_result.get("status") != "Success":
+ return owner_result
+
+ # 成功完成
+ result = {
+ "status": "Success",
+ "message": "域名所有者创建成功",
+ "data": {
+ "c_sysid": c_sysid,
+ "owner_name": owner_result.get("data", {}).get("name"),
+ "owner_title": owner_result.get("data", {}).get("title")
+ }
+ }
+ return result
+
+ except Exception as e:
+ return {"status": "Error", "message": f"创建域名所有者失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def get_west_domain_real_info(**data):
+ """获取域名实名信息"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ response = client.get_domain_real_info(domain)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ return {"status": "error", "message": "API查询失败"}
+
+ data = response.get("data", {})
+
+ # 返回格式化的实名信息
+ return {
+ "status": "success",
+ "data": {
+ "domain": data.get("domain"),
+ "c_sysid": data.get("c_sysid"),
+ "c_regtype": data.get("c_regtype"),
+ "c_status": data.get("c_status"),
+ "c_failinfo": data.get("c_failinfo"),
+ "status": data.get("status"),
+ "regdate": data.get("regdate"),
+ "rexpiredate": data.get("rexpiredate"),
+ "dns_hosts": {
+ "dns_host1": data.get("dns_host1"),
+ "dns_host2": data.get("dns_host2"),
+ "dns_host3": data.get("dns_host3"),
+ "dns_host4": data.get("dns_host4"),
+ "dns_host5": data.get("dns_host5"),
+ "dns_host6": data.get("dns_host6")
+ },
+ "owner": {
+ "dom_ln": data.get("dom_ln"),
+ "dom_fn": data.get("dom_fn"),
+ "dom_ln_m": data.get("dom_ln_m"),
+ "dom_fn_m": data.get("dom_fn_m"),
+ "dom_org": data.get("dom_org"),
+ "dom_org_m": data.get("dom_org_m"),
+ "dom_co": data.get("dom_co"),
+ "dom_st": data.get("dom_st"),
+ "dom_st_m": data.get("dom_st_m"),
+ "dom_ct": data.get("dom_ct"),
+ "dom_ct_m": data.get("dom_ct_m"),
+ "dom_adr1": data.get("dom_adr1"),
+ "dom_adr_m": data.get("dom_adr_m"),
+ "dom_pc": data.get("dom_pc"),
+ "dom_ph": data.get("dom_ph"),
+ "dom_fax": data.get("dom_fax"),
+ "dom_em": data.get("dom_em")
+ },
+ "admin": {
+ "admi_ln": data.get("admi_ln"),
+ "admi_fn": data.get("admi_fn"),
+ "admi_ln_m": data.get("admi_ln_m"),
+ "admi_fn_m": data.get("admi_fn_m"),
+ "admi_co": data.get("admi_co"),
+ "admi_st": data.get("admi_st"),
+ "admi_st_m": data.get("admi_st_m"),
+ "admi_ct": data.get("admi_ct"),
+ "admi_ct_m": data.get("admi_ct_m"),
+ "admi_adr1": data.get("admi_adr1"),
+ "admi_adr_m": data.get("admi_adr_m"),
+ "admi_pc": data.get("admi_pc"),
+ "admi_ph": data.get("admi_ph"),
+ "admi_fax": data.get("admi_fax"),
+ "admi_em": data.get("admi_em")
+ },
+ "tech": {
+ "tech_ln": data.get("tech_ln"),
+ "tech_fn": data.get("tech_fn"),
+ "tech_ln_m": data.get("tech_ln_m"),
+ "tech_fn_m": data.get("tech_fn_m"),
+ "tech_co": data.get("tech_co"),
+ "tech_st": data.get("tech_st"),
+ "tech_st_m": data.get("tech_st_m"),
+ "tech_ct": data.get("tech_ct"),
+ "tech_ct_m": data.get("tech_ct_m"),
+ "tech_adr1": data.get("tech_adr1"),
+ "tech_adr_m": data.get("tech_adr_m"),
+ "tech_pc": data.get("tech_pc"),
+ "tech_ph": data.get("tech_ph"),
+ "tech_fax": data.get("tech_fax"),
+ "tech_em": data.get("tech_em")
+ },
+ "billing": {
+ "bill_ln": data.get("bill_ln"),
+ "bill_fn": data.get("bill_fn"),
+ "bill_ln_m": data.get("bill_ln_m"),
+ "bill_fn_m": data.get("bill_fn_m"),
+ "bill_co": data.get("bill_co"),
+ "bill_st": data.get("bill_st"),
+ "bill_st_m": data.get("bill_st_m"),
+ "bill_ct": data.get("bill_ct"),
+ "bill_ct_m": data.get("bill_ct_m"),
+ "bill_adr1": data.get("bill_adr1"),
+ "bill_adr_m": data.get("bill_adr_m"),
+ "bill_pc": data.get("bill_pc"),
+ "bill_ph": data.get("bill_ph"),
+ "bill_fax": data.get("bill_fax"),
+ "bill_em": data.get("bill_em")
+ },
+ "real_name_status": {
+ "r_status": data.get("r_status"),
+ "r_failinfo": data.get("r_failinfo"),
+ "c_ispublic": data.get("c_ispublic")
+ },
+ "orgfile": data.get("orgfile", {})
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "域名实名信息查询响应解析失败"}
+
+
+@jingrow.whitelist()
+def get_west_upload_token(**data):
+ """获取西部数码实名上传token"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ c_sysid = data.get('c_sysid')
+ f_type_org = data.get('f_type_org')
+ f_code_org = data.get('f_code_org')
+ f_type_lxr = data.get('f_type_lxr')
+ f_code_lxr = data.get('f_code_lxr')
+
+ if not all([c_sysid, f_type_org, f_code_org]):
+ return {"status": "error", "message": "缺少必要参数:c_sysid, f_type_org, f_code_org"}
+
+ response = client.get_upload_token(c_sysid, f_type_org, f_code_org, f_type_lxr, f_code_lxr)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ return {"status": "error", "message": "API查询失败"}
+
+ return {
+ "status": "success",
+ "data": {
+ "token": response.get("data"),
+ "clientid": response.get("clientid")
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "获取实名上传token响应解析失败"}
+
+
+@jingrow.whitelist()
+def west_upload_real_name_files(**data):
+ """西部数码模板实名资料上传"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ token = data.get('token')
+ file_org = data.get('file_org')
+ file_lxr = data.get('file_lxr')
+
+ if not all([token, file_org]):
+ return {"status": "error", "message": "缺少必要参数:token, file_org"}
+
+ response = client.upload_real_name_files(token, file_org, file_lxr)
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 解析响应格式
+ d = response.get("d", {})
+ result = d.get("Result")
+ msg = d.get("Msg", "")
+
+ if result == 200:
+ return {
+ "status": "success",
+ "message": "实名资料上传成功",
+ "data": {
+ "result": result,
+ "msg": msg
+ }
+ }
+ else:
+ return {
+ "status": "error",
+ "message": f"实名资料上传失败: {msg}"
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "实名资料上传响应解析失败"}
+
+
+@jingrow.whitelist()
+def west_domain_modify_dns_record(**data):
+ """修改DNS记录 - 只修改可修改的字段(值、TTL、优先级、线路)"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ value = data.get('value')
+ ttl = data.get('ttl', 600)
+ level = data.get('level', 10)
+ record_id = data.get('record_id')
+ line = data.get('line', '')
+
+ # 验证TTL值
+ if ttl < 60 or ttl > 86400:
+ return {"status": "error", "message": "TTL值必须在60~86400秒之间"}
+
+ # 验证优先级
+ if level < 1 or level > 100:
+ return {"status": "error", "message": "优先级必须在1~100之间"}
+
+ # 只传递可修改的字段给底层API
+ response = client.modify_dns_record(
+ domain, value, ttl, level, record_id, line
+ )
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ error_msg = response.get('msg', response.get('message', '未知错误'))
+ return {"status": "error", "message": f"修改DNS记录失败: {error_msg}"}
+
+ # 同步更新本地数据库中的对应记录
+ if record_id:
+ try:
+ # 查找对应的DNS记录
+ dns_records = jingrow.get_all(
+ "Dns Resolution",
+ {"record_id": record_id},
+ ["name"]
+ )
+
+ if dns_records:
+ dns_record = dns_records[0]
+ # 立即执行更新操作
+ jingrow.set_value("Dns Resolution", dns_record.name, {
+ "value": value,
+ "ttl": str(ttl),
+ "level": str(level),
+ "line": line
+ })
+ jingrow.db.commit()
+ except Exception as e:
+ jingrow.log_error(f"域名 {domain} DNS记录同步更新失败", f"错误: {str(e)}")
+
+ # 返回成功结果
+ return {
+ "status": "success",
+ "message": "DNS记录修改成功",
+ "data": {
+ "domain": domain,
+ "value": value,
+ "ttl": ttl,
+ "level": level,
+ "record_id": record_id,
+ "line": line
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "修改DNS记录响应解析失败"}
+
+
+@jingrow.whitelist()
+def test_dns_record_management():
+ """测试DNS记录管理功能"""
+ try:
+ # 测试参数
+ test_domain = "test.com"
+ test_host = "www"
+ test_value = "127.0.0.1"
+ test_record_type = "A"
+
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ # 测试1: 添加DNS记录
+ add_result = client.add_dns_record(
+ domain=test_domain,
+ record_type=test_record_type,
+ host=test_host,
+ value=test_value,
+ ttl=900,
+ level=10,
+ line=""
+ )
+
+ # 测试2: 获取DNS记录列表
+ list_result = client.get_dns_records_paginated(test_domain, 20, 1)
+
+ # 测试3: 修改DNS记录(如果添加成功)
+ if add_result.get("result") == 200:
+ record_id = add_result.get("data", {}).get("id")
+ if record_id:
+ modify_result = client.modify_dns_record(
+ domain=test_domain,
+ value="127.0.0.2",
+ ttl=600,
+ level=10,
+ record_id=record_id,
+ line=""
+ )
+
+ # 测试4: 删除DNS记录
+ delete_result = client.delete_dns_record(
+ domain=test_domain,
+ record_id=record_id
+ )
+
+ return {
+ "status": "success",
+ "message": "DNS记录管理功能测试完成",
+ "data": {
+ "add_result": add_result,
+ "list_result": list_result
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": f"DNS记录管理功能测试失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def west_domain_modify_dns_server(**data):
+ """修改域名DNS服务器"""
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ dns1 = data.get('dns1')
+ dns2 = data.get('dns2')
+ dns3 = data.get('dns3')
+ dns4 = data.get('dns4')
+ dns5 = data.get('dns5')
+ dns6 = data.get('dns6')
+
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+ if not dns1:
+ return {"status": "error", "message": "缺少主DNS服务器参数"}
+ if not dns2:
+ return {"status": "error", "message": "缺少辅DNS服务器参数"}
+
+ # 验证DNS服务器格式
+ dns_servers = [dns1, dns2]
+ if dns3:
+ dns_servers.append(dns3)
+ if dns4:
+ dns_servers.append(dns4)
+ if dns5:
+ dns_servers.append(dns5)
+ if dns6:
+ dns_servers.append(dns6)
+
+ # 验证DNS服务器格式(简单验证)
+ for dns in dns_servers:
+ if dns and not ('.' in dns and len(dns) > 3):
+ return {"status": "error", "message": f"DNS服务器格式不正确: {dns}"}
+
+ response = client.modify_dns_server(
+ domain=domain,
+ dns1=dns1,
+ dns2=dns2,
+ dns3=dns3,
+ dns4=dns4,
+ dns5=dns5,
+ dns6=dns6
+ )
+
+ if response.get("status") == "error":
+ return response
+
+ try:
+ # 检查响应格式
+ if response.get("result") != 200:
+ error_msg = response.get('msg', response.get('message', '未知错误'))
+ return {"status": "error", "message": f"修改DNS服务器失败: {error_msg}"}
+
+ # 异步更新本地域名记录的DNS服务器字段
+ try:
+ domain_records = jingrow.get_all(
+ "Jsite Domain",
+ {"domain": domain},
+ ["name"]
+ )
+
+ if domain_records:
+ jingrow.enqueue(
+ "jingrow.client.set_value",
+ pagetype="Jsite Domain",
+ name=domain_records[0].name,
+ fieldname={
+ "dns_host1": dns1,
+ "dns_host2": dns2,
+ "dns_host3": dns3,
+ "dns_host4": dns4,
+ "dns_host5": dns5,
+ "dns_host6": dns6
+ }
+ )
+ except Exception as e:
+ jingrow.log_error(f"域名 {domain} DNS服务器更新失败", f"错误: {str(e)}")
+
+ # 返回成功结果
+ return {
+ "status": "success",
+ "message": "DNS服务器修改成功",
+ "data": {
+ "domain": domain,
+ "dns_servers": {
+ "dns1": dns1,
+ "dns2": dns2,
+ "dns3": dns3,
+ "dns4": dns4,
+ "dns5": dns5,
+ "dns6": dns6
+ },
+ "clientid": response.get("clientid")
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": "修改DNS服务器响应解析失败"}
+
+
+@jingrow.whitelist()
+def sync_domain_info_from_west(**data):
+ """从西部数据同步域名信息"""
+
+ try:
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ domain = data.get('domain')
+ if not domain:
+ return {"status": "error", "message": "缺少域名参数"}
+
+ # 获取域名DNS记录 - 参考get_west_domain_dns_records使用分页API
+ dns_response = client.get_dns_records_paginated(domain, 1000, 1) # 获取足够多的记录
+
+ # 获取域名实名信息
+ real_info_response = client.get_domain_real_info(domain)
+
+ # 处理DNS记录 - 参考get_west_domain_dns_records的处理方式
+ dns_data = {}
+ if dns_response.get("result") == 200:
+ data = dns_response.get("data", {})
+ all_items = data.get("items", [])
+ # 对所有记录按类型排序
+ sorted_items = sorted(all_items, key=lambda x: x.get('type', ''))
+ dns_data = {
+ "items": sorted_items,
+ "total": len(sorted_items)
+ }
+
+ real_data = real_info_response.get("data", {}) if real_info_response.get("result") == 200 else {}
+
+ # 获取当前域名记录的团队信息
+ domain_pg = jingrow.get_pg("Jsite Domain", {"domain": domain})
+ if not domain_pg:
+ return {"status": "error", "message": "本地未找到该域名记录"}
+
+ # 从域名记录获取团队信息
+ team_name = domain_pg.team
+ if not team_name:
+ return {"status": "error", "message": "域名记录中未找到团队信息"}
+
+ team = jingrow.get_pg("Team", team_name)
+ if not team:
+ return {"status": "error", "message": "未找到团队信息"}
+
+ # 同步Domain Owner信息
+ domain_owner_name = None
+ if real_data and real_data.get("r_status") == 1:
+ # 从实名信息中提取c_sysid和所有者信息
+ c_sysid = real_data.get("c_sysid")
+ owner_info = real_data.get("owner", {})
+
+
+
+ if c_sysid:
+ # 如果owner_info为空,使用real_data中的所有者字段
+ if not owner_info:
+ owner_info = real_data
+
+ if owner_info:
+ # 根据c_sysid查找是否已存在Domain Owner - 只按c_sysid查找,不限制团队
+ existing_owners = jingrow.get_all(
+ "Domain Owner",
+ {"c_sysid": c_sysid},
+ ["name", "team"]
+ )
+
+
+
+ if existing_owners:
+ # 更新已存在的Domain Owner
+ domain_owner_name = existing_owners[0].name
+ existing_team = existing_owners[0].team
+ try:
+ domain_owner_pg = jingrow.get_pg("Domain Owner", domain_owner_name)
+ if domain_owner_pg:
+ # 更新团队信息(如果不同或为空)
+ if not domain_owner_pg.team or domain_owner_pg.team != team.name:
+ domain_owner_pg.team = team.name
+
+ # 更新所有者信息
+ owner_name = owner_info.get("dom_org_m") or owner_info.get("dom_ln_m", "") + owner_info.get("dom_fn_m", "")
+ domain_owner_pg.fullname = owner_name
+ domain_owner_pg.c_status = real_data.get("c_status")
+ domain_owner_pg.r_status = real_data.get("r_status")
+
+ # 更新国家代码和电话代码
+ domain_owner_pg.c_co = owner_info.get("dom_co", "CN")
+ domain_owner_pg.cocode = owner_info.get("dom_ph", "").split('.')[0] if '.' in owner_info.get("dom_ph", "") else "+86" # 从手机号提取电话代码
+
+
+ # 根据类型更新不同字段
+ c_regtype = "E" if owner_info.get("dom_org_m") else "I"
+ domain_owner_pg.c_regtype = c_regtype
+
+ if c_regtype == "E": # 企业
+ domain_owner_pg.c_org_m = owner_info.get("dom_org_m", "")
+ domain_owner_pg.c_org = owner_info.get("dom_org", "")
+ domain_owner_pg.c_ln_m = owner_info.get("dom_ln_m", "")
+ domain_owner_pg.c_fn_m = owner_info.get("dom_fn_m", "")
+ domain_owner_pg.c_ln = owner_info.get("dom_ln", "")
+ domain_owner_pg.c_fn = owner_info.get("dom_fn", "")
+ domain_owner_pg.title = owner_info.get("dom_org_m", "")
+ else: # 个人
+ domain_owner_pg.c_ln_m = owner_info.get("dom_ln_m", "")
+ domain_owner_pg.c_fn_m = owner_info.get("dom_fn_m", "")
+ domain_owner_pg.c_ln = owner_info.get("dom_ln", "")
+ domain_owner_pg.c_fn = owner_info.get("dom_fn", "")
+ domain_owner_pg.title = owner_info.get("dom_ln_m", "") + owner_info.get("dom_fn_m", "")
+
+ # 更新地址信息
+ domain_owner_pg.c_st_m = owner_info.get("dom_st_m", "")
+ domain_owner_pg.c_ct_m = owner_info.get("dom_ct_m", "")
+ domain_owner_pg.c_adr_m = owner_info.get("dom_adr_m", "")
+ domain_owner_pg.c_pc = owner_info.get("dom_pc", "")
+ domain_owner_pg.c_st = owner_info.get("dom_st", "")
+ domain_owner_pg.c_ct = owner_info.get("dom_ct", "")
+ domain_owner_pg.c_adr = owner_info.get("dom_adr", "")
+ domain_owner_pg.c_em = owner_info.get("dom_em", "")
+ domain_owner_pg.c_ph = owner_info.get("dom_ph", "")
+ domain_owner_pg.c_ph_type = "0" # 默认为手机
+
+ # 更新证件信息(从orgfile中获取)
+ orgfile = real_data.get("orgfile", {})
+ if orgfile.get("f_code"):
+ domain_owner_pg.c_idnum_gswl = orgfile.get("f_code")
+ if orgfile.get("f_type"):
+ domain_owner_pg.c_idtype_gswl = str(orgfile.get("f_type"))
+
+ domain_owner_pg.save(ignore_permissions=True)
+ except Exception as e:
+ jingrow.log_error(f"更新Domain Owner失败", f"域名: {domain}, c_sysid: {c_sysid}, 错误: {str(e)}")
+ else:
+ # 创建新的Domain Owner
+ try:
+ # 判断所有者类型(个人或企业)
+ c_regtype = "E" if owner_info.get("dom_org_m") else "I"
+
+ # 生成所有者名称
+ owner_name = owner_info.get("dom_org_m") or owner_info.get("dom_ln_m", "") + owner_info.get("dom_fn_m", "")
+
+ # 构建Domain Owner数据
+ owner_data = {
+ "pagetype": "Domain Owner",
+ "team": team.name, # 使用当前团队
+ "c_sysid": c_sysid,
+ "c_regtype": c_regtype,
+ "fullname": owner_name,
+ "c_co": owner_info.get("dom_co", "CN"), # 从数据中获取国家代码
+ "cocode": owner_info.get("dom_ph", "").split('.')[0] if '.' in owner_info.get("dom_ph", "") else "+86", # 从手机号提取电话代码
+ "reg_contact_type": "cg",
+ "c_status": real_data.get("c_status"),
+ "r_status": real_data.get("r_status"),
+ "title": owner_name
+ }
+
+ # 根据类型设置不同字段
+ if c_regtype == "E": # 企业
+ owner_data.update({
+ "c_org_m": owner_info.get("dom_org_m", ""),
+ "c_org": owner_info.get("dom_org", ""),
+ "c_ln_m": owner_info.get("dom_ln_m", ""),
+ "c_fn_m": owner_info.get("dom_fn_m", ""),
+ "c_ln": owner_info.get("dom_ln", ""),
+ "c_fn": owner_info.get("dom_fn", ""),
+ "title": owner_info.get("dom_org_m", "")
+ })
+ else: # 个人
+ owner_data.update({
+ "c_ln_m": owner_info.get("dom_ln_m", ""),
+ "c_fn_m": owner_info.get("dom_fn_m", ""),
+ "c_ln": owner_info.get("dom_ln", ""),
+ "c_fn": owner_info.get("dom_fn", ""),
+ "title": owner_info.get("dom_ln_m", "") + owner_info.get("dom_fn_m", "")
+ })
+
+ # 设置地址信息
+ owner_data.update({
+ "c_st_m": owner_info.get("dom_st_m", ""),
+ "c_ct_m": owner_info.get("dom_ct_m", ""),
+ "c_adr_m": owner_info.get("dom_adr_m", ""),
+ "c_pc": owner_info.get("dom_pc", ""),
+ "c_st": owner_info.get("dom_st", ""),
+ "c_ct": owner_info.get("dom_ct", ""),
+ "c_adr": owner_info.get("dom_adr", ""),
+ "c_em": owner_info.get("dom_em", ""),
+ "c_ph": owner_info.get("dom_ph", ""),
+ "c_ph_type": "0" # 默认为手机
+ })
+
+ # 设置证件信息(从orgfile中获取)
+ orgfile = real_data.get("orgfile", {})
+ if orgfile.get("f_code"):
+ owner_data["c_idnum_gswl"] = orgfile.get("f_code")
+ if orgfile.get("f_type"):
+ owner_data["c_idtype_gswl"] = str(orgfile.get("f_type"))
+
+ # 创建Domain Owner记录
+ domain_owner_pg = jingrow.get_pg(owner_data)
+ domain_owner_pg.insert(ignore_permissions=True)
+ domain_owner_name = domain_owner_pg.name
+
+ except Exception as e:
+ jingrow.log_error(f"创建Domain Owner失败", f"域名: {domain}, c_sysid: {c_sysid}, 错误: {str(e)}")
+ domain_owner_name = None
+
+ # 更新本地域名记录
+ try:
+ # domain_pg 已经在前面获取过了,直接使用
+
+ # 更新基本信息 - 从实名信息中获取
+ if real_data:
+ domain_pg.registration_date = real_data.get("regdate")
+ domain_pg.end_date = real_data.get("rexpiredate")
+ domain_pg.status = real_data.get("status")
+
+ # 更新DNS服务器信息
+ domain_pg.dns_host1 = real_data.get("dns_host1", "")
+ domain_pg.dns_host2 = real_data.get("dns_host2", "")
+ domain_pg.dns_host3 = real_data.get("dns_host3", "")
+ domain_pg.dns_host4 = real_data.get("dns_host4", "")
+ domain_pg.dns_host5 = real_data.get("dns_host5", "")
+ domain_pg.dns_host6 = real_data.get("dns_host6", "")
+
+ # 更新Domain Owner
+ if domain_owner_name:
+ domain_pg.domain_owner = domain_owner_name
+
+ # 更新DNS解析记录
+ if dns_data and "items" in dns_data:
+ # 清空现有DNS解析记录
+ domain_pg.dns_resolution = []
+
+ # 添加新的DNS解析记录
+ for record in dns_data.get("items", []):
+ # 确保必需字段有值 - 注意字段名是item不是host
+ host = record.get("item", "") # 修正字段名
+ record_type = record.get("type", "")
+ value = record.get("value", "")
+
+ # 只有当必需字段都有值时才添加记录
+ if host and record_type and value:
+ domain_pg.append("dns_resolution", {
+ "type": record_type,
+ "host": host,
+ "value": value,
+ "ttl": record.get("ttl", 600),
+ "level": record.get("level", 10),
+ "line": record.get("line", ""),
+ "record_id": record.get("id", ""),
+ "record_status": record.get("status", "")
+ })
+
+ # 保存更新
+ domain_pg.save()
+
+ return {
+ "status": "success",
+ "message": "域名信息同步成功",
+ "data": {
+ "dns_records": dns_data,
+ "real_info": real_data,
+ "domain_owner": domain_owner_name
+ }
+ }
+
+ except Exception as e:
+ return {"status": "error", "message": f"更新本地记录失败: {str(e)}"}
+
+ except Exception as e:
+ return {"status": "error", "message": f"同步失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def upload_domain_real_name_files(**data):
+ """上传域名实名资料文件"""
+ import base64
+
+ # 尝试从jingrow.request中获取参数
+ try:
+ # 检查是否是FormData格式
+ if jingrow.request.content_type and 'multipart/form-data' in jingrow.request.content_type:
+ # 处理FormData格式
+ request_data = {}
+ for key in jingrow.request.form:
+ request_data[key] = jingrow.request.form[key]
+ else:
+ # 处理JSON格式
+ request_data = jingrow.request.get_json() if jingrow.request.is_json else {}
+ except Exception as e:
+ request_data = {}
+
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ # 从多个来源提取参数
+ domain = (data.get('domain') or
+ request_data.get('domain') or
+ data.get('domainDoc.domain'))
+
+ id_type = (data.get('idType') or
+ request_data.get('idType') or
+ 'idcard')
+
+ id_number = (data.get('idNumber') or
+ request_data.get('idNumber'))
+
+ if not all([domain, id_number]):
+ return {"status": "error", "message": f"缺少必要参数:domain={domain}, idNumber={id_number}"}
+
+ try:
+ # 获取域名信息
+ domain_list = jingrow.get_list("Jsite Domain", filters={"domain": domain})
+
+ if domain_list:
+ domain_name = domain_list[0].get('name')
+ # 通过name获取完整的域名信息
+ domain_info = jingrow.get_pg("Jsite Domain", domain_name)
+ else:
+ return {"status": "error", "message": f"未找到主域名{domain}"}
+
+ # 获取域名所有者信息
+ owner_name = domain_info.get('domain_owner') or domain_info.get('owner')
+ if not owner_name:
+ return {"status": "error", "message": "域名所有者信息不存在"}
+
+ owner_info = jingrow.get_pg("Domain Owner", owner_name)
+ if not owner_info:
+ return {"status": "error", "message": "域名所有者信息不存在"}
+
+ # 获取上传token
+ c_sysid = owner_info.get('c_sysid')
+ if not c_sysid:
+ return {"status": "error", "message": "域名所有者未关联所有者模板ID"}
+
+ # 证件类型映射到西部数码参数
+ id_type_map = {
+ "SFZ": "1", # 身份证
+ "HZ": "5", # 护照
+ "GAJMTX": "6", # 港澳居民来往内地通行证
+ "TWJMTX": "11", # 台湾居民来往大陆通行证
+ "WJLSFZ": "12", # 外国人永久居留身份证
+ "GAJZZ": "30", # 港澳台居民居住证
+ "ORG": "2", # 组织机构代码证
+ "YYZZ": "3", # 工商营业执照
+ "TYDM": "4" # 统一社会信用代码证书
+ }
+ f_type_org = id_type_map.get(id_type, "1") # 默认使用身份证
+ f_code_org = id_number
+
+ # 获取上传token
+ token_response = client.get_upload_token(c_sysid, f_type_org, f_code_org)
+ if token_response.get("status") == "error":
+ return token_response
+
+ if token_response.get("result") != 200:
+ return {"status": "error", "message": "获取上传token失败"}
+
+ token = token_response.get("data")
+ if not token:
+ return {"status": "error", "message": "获取上传token失败"}
+
+ # 处理文件上传 - 从FormData中提取文件并转换为base64
+ files = []
+
+ # 从jingrow.request.files中获取文件
+ if hasattr(jingrow.request, 'files') and jingrow.request.files:
+ for key in jingrow.request.files:
+ file_obj = jingrow.request.files[key]
+ if hasattr(file_obj, 'read'):
+ # 读取文件内容并转换为base64
+ file_content = file_obj.read()
+ file_base64 = base64.b64encode(file_content).decode('utf-8')
+ files.append(file_base64)
+
+ # 如果从request.files中没有找到文件,尝试从data中获取
+ if not files:
+ for key, value in data.items():
+ if key.startswith('files_') and hasattr(value, 'read'):
+ # 读取文件内容并转换为base64
+ file_content = value.read()
+ file_base64 = base64.b64encode(file_content).decode('utf-8')
+ files.append(file_base64)
+
+ # 如果还是没有文件,尝试从data.files中获取(前端传递的文件数组)
+ if not files and 'files' in data:
+ for file_obj in data['files']:
+ if hasattr(file_obj, 'read'):
+ # 读取文件内容并转换为base64
+ file_content = file_obj.read()
+ file_base64 = base64.b64encode(file_content).decode('utf-8')
+ files.append(file_base64)
+
+ # 如果还是没有文件,尝试从jingrow.form_dict中获取
+ if not files and hasattr(jingrow, 'form_dict'):
+ for key, value in jingrow.form_dict.items():
+ if key.startswith('files_') and hasattr(value, 'read'):
+ # 读取文件内容并转换为base64
+ file_content = value.read()
+ file_base64 = base64.b64encode(file_content).decode('utf-8')
+ files.append(file_base64)
+
+ if not files:
+ return {"status": "error", "message": "请上传证件材料"}
+
+ # 上传文件到西部数码
+ file_org = files[0] # 使用第一个文件作为证件文件
+ file_lxr = files[1] if len(files) > 1 else None # 第二个文件作为联系人文件(可选)
+
+ upload_response = client.upload_real_name_files(token, file_org, file_lxr)
+
+ if upload_response.get("status") == "error":
+ return upload_response
+
+ # 解析响应
+ d = upload_response.get("d", {})
+ result = d.get("Result")
+ msg = d.get("Msg", "")
+
+ if result == 200:
+ response_data = {
+ "status": "success",
+ "message": "实名资料上传成功,请等待审核",
+ "data": {
+ "result": result,
+ "msg": msg
+ }
+ }
+ return response_data
+ else:
+ response_data = {
+ "status": "error",
+ "message": f"实名资料上传失败: {msg}"
+ }
+ return response_data
+
+ except Exception as e:
+ return {"status": "error", "message": "上传实名资料失败,请重试"}
+
+
+@jingrow.whitelist()
+def modify_west_contact_template(**data):
+ """
+ 修改西部数码实名模板资料
+
+ 必填参数:
+ - c_sysid: 模板标识
+
+ 可选参数:
+ - c_ln_m: (中文)联系人姓
+ - c_fn_m: (中文)联系人名
+ - c_co: 所属国家简称(CN: 中国, US: 美国)
+ - cocode: 国家电话代码(+86: 中国, +1: 美国)
+ - c_st_m: (中文)所属省
+ - c_ct_m: (中文)所属市
+ - c_dt_m: (中文)所属县
+ - c_adr_m: (中文)通讯地址
+ - c_pc: 邮编
+ - c_ph_type: 联系电话类型(0:手机, 1:座机)
+ - c_ph: 手机号码(当c_ph_type为0时必填)
+ - c_ph_code: 座机号码区号(当c_ph_type为1时必填)
+ - c_ph_num: 座机号码(当c_ph_type为1时必填)
+ - c_ph_fj: 座机号码分机号(可选)
+ - c_em: 所有者电子邮箱
+ - c_ln: (英文)联系人姓
+ - c_fn: (英文)联系人名
+ - c_st: (英文)省份
+ - c_ct: (英文)城市
+ - c_adr: (英文)通讯地址
+ - reg_contact_type: 适用范围(hk,gswl等)
+ - c_idtype_hk: hk域名证件类型
+ - c_idnum_hk: hk域名证件号码
+ - c_idtype_gswl: 特殊中文域名证件类型
+ - c_idnum_gswl: 特殊中文域名证件号码
+ """
+ client = get_west_client()
+ if not client:
+ return {"status": "error", "message": "API客户端初始化失败"}
+
+ # 验证必填字段
+ c_sysid = data.get('c_sysid')
+ if not c_sysid:
+ return {"status": "error", "message": "缺少必要参数:c_sysid"}
+
+ # 验证电话号码字段(根据c_ph_type判断)
+ c_ph_type = data.get('c_ph_type', '0') # 默认为手机
+ if c_ph_type == '0' and data.get('c_ph'): # 手机
+ # 如果提供了手机号码,验证格式
+ pass
+ elif c_ph_type == '1' and (data.get('c_ph_code') or data.get('c_ph_num')): # 座机
+ # 如果提供了座机信息,验证完整性
+ if data.get('c_ph_code') and not data.get('c_ph_num'):
+ return {"status": "error", "message": "座机号码 c_ph_num 是必填项"}
+ if data.get('c_ph_num') and not data.get('c_ph_code'):
+ return {"status": "error", "message": "座机区号 c_ph_code 是必填项"}
+
+ # 验证字段长度
+ if data.get('c_ln_m'):
+ if len(data['c_ln_m']) < 1 or len(data['c_ln_m']) > 16:
+ return {"status": "error", "message": "姓(中文)长度必须为1~16位"}
+
+ if data.get('c_fn_m'):
+ if len(data['c_fn_m']) < 1 or len(data['c_fn_m']) > 64:
+ return {"status": "error", "message": "名(中文)长度必须为1~64位"}
+
+ if data.get('c_st_m'):
+ if len(data['c_st_m']) < 2 or len(data['c_st_m']) > 10:
+ return {"status": "error", "message": "省份(中文)长度必须为2~10位"}
+
+ # 验证完整姓名长度
+ if data.get('c_ln_m') and data.get('c_fn_m'):
+ fullname = data['c_ln_m'] + data['c_fn_m']
+ if len(fullname) < 2 or len(fullname) > 64:
+ return {"status": "error", "message": "完整姓名(中文)长度必须为2~64位"}
+
+ # 生成英文姓名(如果提供了中文姓名)
+ if data.get('c_ln_m') and data.get('c_fn_m'):
+ try:
+ from pypinyin import lazy_pinyin
+ last_name_pinyin = ' '.join(lazy_pinyin(data['c_ln_m']))
+ first_name_pinyin = ' '.join(lazy_pinyin(data['c_fn_m']))
+ data['c_ln'] = last_name_pinyin.title()
+ data['c_fn'] = first_name_pinyin.title()
+ except ImportError:
+ # 如果没有pypinyin库,使用默认值
+ data['c_ln'] = data.get('c_ln', '')
+ data['c_fn'] = data.get('c_fn', '')
+
+ # 生成英文地址(如果提供了中文地址)
+ if data.get('c_adr_m'):
+ try:
+ from pypinyin import lazy_pinyin
+ address_pinyin = ' '.join(lazy_pinyin(data['c_adr_m']))
+ data['c_adr'] = address_pinyin.title()
+ except ImportError:
+ data['c_adr'] = data.get('c_adr', '')
+
+ # 生成英文省份和城市(如果提供了中文省份和城市)
+ if data.get('c_st_m'):
+ try:
+ from pypinyin import lazy_pinyin
+ state_pinyin = ' '.join(lazy_pinyin(data['c_st_m']))
+ data['c_st'] = state_pinyin.title()
+ except ImportError:
+ data['c_st'] = data.get('c_st', '')
+
+ if data.get('c_ct_m'):
+ try:
+ from pypinyin import lazy_pinyin
+ city_pinyin = ' '.join(lazy_pinyin(data['c_ct_m']))
+ data['c_ct'] = city_pinyin.title()
+ except ImportError:
+ data['c_ct'] = data.get('c_ct', '')
+
+ # 设置默认值
+ template_data = {
+ 'c_sysid': c_sysid,
+ 'c_co': data.get('c_co', 'CN'),
+ 'cocode': data.get('cocode', '+86'),
+ 'c_ph_type': data.get('c_ph_type', '0'),
+ }
+
+ # 添加可选字段(只添加有值的字段)
+ optional_fields = [
+ 'c_ln_m', 'c_fn_m', 'c_st_m', 'c_ct_m', 'c_dt_m', 'c_adr_m', 'c_pc',
+ 'c_em', 'c_ln', 'c_fn', 'c_st', 'c_ct', 'c_adr', 'reg_contact_type',
+ 'c_idtype_hk', 'c_idnum_hk', 'c_idtype_gswl', 'c_idnum_gswl'
+ ]
+
+ for field in optional_fields:
+ if data.get(field):
+ template_data[field] = data[field]
+
+ # 根据c_ph_type添加电话相关字段
+ if data.get('c_ph_type') == '1': # 座机
+ if data.get('c_ph_code'):
+ template_data['c_ph_code'] = data['c_ph_code']
+ if data.get('c_ph_num'):
+ template_data['c_ph_num'] = data['c_ph_num']
+ if data.get('c_ph_fj'):
+ template_data['c_ph_fj'] = data['c_ph_fj']
+ else: # 手机
+ if data.get('c_ph'):
+ template_data['c_ph'] = data['c_ph']
+
+ try:
+ result = client.modify_contact_template(template_data)
+
+ if result.get('result') == 200:
+ return {
+ "status": "success",
+ "message": "实名模板资料修改成功",
+ "data": {
+ "c_sysid": result.get('data', {}).get('c_sysid', c_sysid)
+ }
+ }
+ else:
+ error_msg = result.get('msg', result.get('message', '未知错误'))
+ return {"status": "error", "message": f"修改实名模板资料失败: {error_msg}"}
+
+ except Exception as e:
+ return {"status": "error", "message": f"修改实名模板资料失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def update_domain_owner(name, **data):
+ """更新域名所有者信息"""
+ try:
+ if not name:
+ return {"status": "Error", "message": "域名所有者名称不能为空"}
+
+ # 获取指定的域名所有者
+ domain_owner = jingrow.get_pg("Domain Owner", name)
+ if not domain_owner:
+ return {"status": "Error", "message": "未找到指定的域名所有者"}
+
+ # 检查权限(只能更新当前团队的所有者)
+ team = get_current_team()
+
+ if not team:
+ return {"status": "Error", "message": "未找到当前团队"}
+
+ if domain_owner.team != team:
+ return {"status": "Error", "message": "无权更新该域名所有者信息"}
+
+ # 检查是否有 c_sysid(西部数码模板标识)
+ c_sysid = getattr(domain_owner, 'c_sysid', None)
+ if not c_sysid:
+ return {"status": "Error", "message": "域名所有者没有模板标识,无法更新"}
+
+ # 构建西部数码模板更新数据
+ template_data = {
+ 'c_sysid': c_sysid
+ }
+
+ # 添加需要更新的字段
+ field_mapping = {
+ 'c_regtype': 'c_regtype',
+ 'c_org_m': 'c_org_m',
+ 'c_ln_m': 'c_ln_m',
+ 'c_fn_m': 'c_fn_m',
+ 'c_em': 'c_em',
+ 'c_ph': 'c_ph',
+ 'c_st_m': 'c_st_m',
+ 'c_ct_m': 'c_ct_m',
+ 'c_adr_m': 'c_adr_m',
+ 'c_pc': 'c_pc',
+ 'c_ph_type': 'c_ph_type',
+ 'c_ph_code': 'c_ph_code',
+ 'c_ph_num': 'c_ph_num',
+ 'c_ph_fj': 'c_ph_fj',
+ 'reg_contact_type': 'reg_contact_type',
+ 'c_idtype_gswl': 'c_idtype_gswl',
+ 'c_idnum_gswl': 'c_idnum_gswl'
+ }
+
+ for local_field, template_field in field_mapping.items():
+ if local_field in data:
+ template_data[template_field] = data[local_field]
+
+ # 添加现有字段(如果新数据中没有提供)
+ for local_field, template_field in field_mapping.items():
+ if template_field not in template_data and hasattr(domain_owner, local_field):
+ current_value = getattr(domain_owner, local_field, None)
+ if current_value:
+ template_data[template_field] = current_value
+
+ # 调用西部数码模板更新接口
+ try:
+ west_result = modify_west_contact_template(**template_data)
+
+ if west_result.get('status') != 'success':
+ error_msg = west_result.get('message', '所有者模板更新失败')
+ return {"status": "Error", "message": f"所有者模板更新失败: {error_msg}"}
+
+ except Exception as e:
+ return {"status": "Error", "message": f"所有者模板更新失败: {str(e)}"}
+
+ # 西部数码模板更新成功后,更新本地记录
+ updated_fields = []
+ for key, value in data.items():
+ if hasattr(domain_owner, key):
+ old_value = getattr(domain_owner, key, None)
+ setattr(domain_owner, key, value)
+ updated_fields.append(f"{key}: {old_value} -> {value}")
+
+ # 重新生成完整姓名和标题
+ if 'c_ln_m' in data or 'c_fn_m' in data or 'c_org_m' in data or 'c_regtype' in data:
+ # 获取最新的姓名信息
+ c_ln_m = data.get('c_ln_m', getattr(domain_owner, 'c_ln_m', ''))
+ c_fn_m = data.get('c_fn_m', getattr(domain_owner, 'c_fn_m', ''))
+ c_org_m = data.get('c_org_m', getattr(domain_owner, 'c_org_m', ''))
+ c_regtype = data.get('c_regtype', getattr(domain_owner, 'c_regtype', 'I'))
+
+ # 重新生成完整姓名
+ domain_owner.fullname = c_ln_m + c_fn_m
+
+ # 重新生成标题
+ if c_regtype == 'I': # 个人
+ domain_owner.title = f"{c_ln_m}{c_fn_m}"
+ else: # 企业
+ domain_owner.title = f"{c_org_m}"
+
+ # 重新生成英文姓名(如果中文姓名有变化)
+ if 'c_ln_m' in data or 'c_fn_m' in data:
+ if c_ln_m:
+ last_name_pinyin = ' '.join(lazy_pinyin(c_ln_m))
+ domain_owner.c_ln = last_name_pinyin.title()
+ if c_fn_m:
+ first_name_pinyin = ' '.join(lazy_pinyin(c_fn_m))
+ domain_owner.c_fn = first_name_pinyin.title()
+
+ # 重新生成英文单位名称(如果中文单位名称有变化)
+ if 'c_org_m' in data and c_org_m:
+ org_pinyin = ' '.join(lazy_pinyin(c_org_m))
+ domain_owner.c_org = org_pinyin.title()
+
+ updated_fields.append(f"fullname: 已更新")
+ updated_fields.append(f"title: 已更新")
+ if 'c_ln_m' in data or 'c_fn_m' in data:
+ updated_fields.append(f"英文姓名: 已更新")
+ if 'c_org_m' in data:
+ updated_fields.append(f"英文单位名称: 已更新")
+
+ # 保存更新,使用 ignore_permissions=True 忽略权限检查
+ domain_owner.save(ignore_permissions=True)
+ jingrow.db.commit()
+
+ return {
+ "status": "Success",
+ "message": "域名所有者信息更新成功",
+ "data": {
+ "name": domain_owner.name,
+ "title": domain_owner.title
+ }
+ }
+ except Exception as e:
+ return {"status": "Error", "message": f"更新域名所有者信息失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def delete_domain_owner(name):
+ """删除所有者模板"""
+ try:
+ if not name:
+ return {"status": "Error", "message": "域名所有者名称不能为空"}
+
+ # 获取指定的域名所有者
+ domain_owner = jingrow.get_pg("Domain Owner", name)
+ if not domain_owner:
+ return {"status": "Error", "message": "未找到指定的域名所有者"}
+
+ # 检查权限(只能删除当前团队的所有者)
+ team = get_current_team()
+ if not team:
+ return {"status": "Error", "message": "未找到当前团队"}
+
+ if domain_owner.team != team:
+ return {"status": "Error", "message": "无权删除该域名所有者信息"}
+
+ # 检查是否有 c_sysid(西部数码模板标识)
+ c_sysid = getattr(domain_owner, 'c_sysid', None)
+
+ # 如果有模板标识,先删除西部数码的模板
+ if c_sysid:
+ try:
+ client = get_west_client()
+ if not client:
+ return {"status": "Error", "message": "API客户端初始化失败"}
+
+ # 调用西部数码删除模板接口
+ west_result = client.delete_contact_template(c_sysid)
+
+ if west_result.get('result') != 200:
+ error_msg = west_result.get('msg', west_result.get('message', '所有者模板删除失败'))
+ return {"status": "Error", "message": f"所有者模板删除失败: {error_msg}"}
+
+ except Exception as e:
+ return {"status": "Error", "message": f"所有者模板删除失败: {str(e)}"}
+
+ # 删除本地记录(无论是否有模板标识)
+ try:
+ domain_owner.delete(ignore_permissions=True)
+ jingrow.db.commit()
+
+ return {"status": "Success", "message": "所有者模板删除成功"}
+
+ except Exception as e:
+ return {"status": "Error", "message": f"所有者模板删除失败: {str(e)}"}
+
+ except Exception as e:
+ return {"status": "Error", "message": f"所有者模板删除失败: {str(e)}"}
+
+
+@jingrow.whitelist()
+def check_template_usage(template_name):
+ """检查模板是否被域名使用"""
+ try:
+ if not template_name:
+ return {"status": "Error", "message": "模板名称不能为空"}
+
+ # 检查是否有域名使用了这个模板
+ used_domains = jingrow.get_all(
+ "Jsite Domain",
+ {"domain_owner": template_name},
+ ["name", "domain"]
+ )
+
+ return {
+ "status": "Success",
+ "data": {
+ "is_used": len(used_domains) > 0,
+ "used_domains": used_domains
+ }
+ }
+ except Exception as e:
+ return {"status": "Error", "message": f"检查模板使用情况失败: {str(e)}"}
+
+
diff --git a/jcloud/api/github.py b/jcloud/api/github.py
index a90caaa..95667e0 100644
--- a/jcloud/api/github.py
+++ b/jcloud/api/github.py
@@ -75,7 +75,7 @@ def get_access_token(installation_id: str | None = None):
"Accept": "application/vnd.github.machine-man-preview+json",
}
response = requests.post(
- f"http://git.jingrow.com:3000/api/v1/app/installations/{installation_id}/access_tokens",
+ f"http://git.jingrow.com/api/v1/app/installations/{installation_id}/access_tokens",
headers=headers,
).json()
return response.get("token")
@@ -111,7 +111,7 @@ def installations(token):
"Authorization": f"token {token}",
"Accept": "application/vnd.github.machine-man-preview+json",
}
- response = requests.get("http://git.jingrow.com:3000/api/v1/user/installations", headers=headers)
+ response = requests.get("http://git.jingrow.com/api/v1/user/installations", headers=headers)
data = response.json()
installations = []
if response.ok:
@@ -140,7 +140,7 @@ def repositories(installation, token):
current_page, is_last_page = 1, False
while not is_last_page:
response = requests.get(
- f"http://git.jingrow.com:3000/api/v1/user/installations/{installation}/repositories",
+ f"http://git.jingrow.com/api/v1/user/installations/{installation}/repositories",
params={"per_page": 100, "page": current_page},
headers=headers,
)
@@ -172,13 +172,13 @@ def repository(owner, name, installation=None):
headers = {
"Authorization": f"token {token}",
}
- repo = requests.get(f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{name}", headers=headers).json()
+ repo = requests.get(f"http://git.jingrow.com/api/v1/repos/{owner}/{name}", headers=headers).json()
current_page, is_last_page = 1, False
branches = []
while not is_last_page:
response = requests.get(
- f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{name}/branches",
+ f"http://git.jingrow.com/api/v1/repos/{owner}/{name}/branches",
params={"per_page": 100, "page": current_page},
headers=headers,
)
@@ -201,7 +201,7 @@ def repository(owner, name, installation=None):
def app(owner, repository, branch, installation=None):
headers = get_auth_headers(installation)
response = requests.get(
- f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{repository}/branches/{branch}",
+ f"http://git.jingrow.com/api/v1/repos/{owner}/{repository}/branches/{branch}",
headers=headers,
)
@@ -211,7 +211,7 @@ def app(owner, repository, branch, installation=None):
branch_info = response.json()
sha = branch_info["commit"]["commit"]["tree"]["sha"]
contents = requests.get(
- f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{repository}/git/trees/{sha}",
+ f"http://git.jingrow.com/api/v1/repos/{owner}/{repository}/git/trees/{sha}",
params={"recursive": True},
headers=headers,
).json()
@@ -250,7 +250,7 @@ def branches(owner, name, installation=None):
headers = {}
response = requests.get(
- f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{name}/branches",
+ f"http://git.jingrow.com/api/v1/repos/{owner}/{name}/branches",
params={"per_page": 100},
headers=headers,
)
@@ -262,8 +262,18 @@ def branches(owner, name, installation=None):
def get_auth_headers(installation_id: str | None = None) -> "dict[str, str]":
- if token := get_access_token(installation_id):
- return {"Authorization": f"token {token}"}
+ settings = jingrow.get_single("Jcloud Settings")
+ git_service_type = settings.get("git_service_type") or "gitea"
+
+ if git_service_type.lower() == "gitea":
+ git_access_token = settings.get_password("git_access_token")
+ if git_access_token:
+ return {"Authorization": f"token {git_access_token}"}
+
+ if git_service_type.lower() == "github" and installation_id:
+ if token := get_access_token(installation_id):
+ return {"Authorization": f"token {token}"}
+
return {}
@@ -287,7 +297,7 @@ def _get_app_name_and_title_from_hooks(
continue
hooks = requests.get(
- f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{repository}/contents/{directory}/hooks.py",
+ f"http://git.jingrow.com/api/v1/repos/{owner}/{repository}/contents/{directory}/hooks.py",
params={"ref": branch_info["name"]},
headers=headers,
).json()
diff --git a/jcloud/api/jlocal.py b/jcloud/api/jlocal.py
new file mode 100644
index 0000000..df54772
--- /dev/null
+++ b/jcloud/api/jlocal.py
@@ -0,0 +1,1053 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2024, JINGROW
+# For license information, please see license.txt
+
+import jingrow
+from jcloud.api.client import dashboard_whitelist
+from jcloud.utils import get_current_team
+
+
+def _get_team_user_names(apps):
+ """批量获取团队关联用户名称,避免N+1查询问题"""
+ if not apps:
+ return {}
+
+ # 收集所有团队ID
+ team_ids = [app.team for app in apps if app.team]
+ if not team_ids:
+ return {}
+
+ # 批量获取团队信息
+ teams = jingrow.get_all(
+ "Team",
+ filters={"name": ["in", team_ids]},
+ fields=["name", "user"]
+ )
+
+ # 收集所有用户ID
+ user_ids = [team.user for team in teams if team.user]
+ if not user_ids:
+ return {}
+
+ # 批量获取用户信息
+ users = jingrow.get_all(
+ "User",
+ filters={"name": ["in", user_ids]},
+ fields=["name"]
+ )
+
+ # 构建映射关系
+ team_to_user = {team.name: team.user for team in teams if team.user}
+ user_names = {user.name: user.name for user in users}
+
+ # 返回团队到用户名称的映射
+ result = {}
+ for team_id, user_id in team_to_user.items():
+ if user_id in user_names:
+ result[team_id] = user_names[user_id]
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_app_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取本地应用列表"""
+
+ # 构建查询条件
+ query_filters = {"public": 1}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "app_name asc"
+
+ # 获取应用列表
+ apps = jingrow.get_all(
+ "Local App",
+ filters=query_filters,
+ fields=[
+ "name", "app_name", "title", "subtitle", "category",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "app_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(apps)
+
+ # 格式化返回数据
+ result = []
+ for app in apps:
+ app_data = {
+ "name": app.name,
+ "app_name": app.app_name,
+ "title": app.title,
+ "subtitle": app.subtitle,
+ "category": app.category,
+ "enabled": app.enabled,
+ "public": app.public,
+ "team": team_user_names.get(app.team),
+ "status": app.status,
+ "repository_url": app.repository_url,
+ "file_url": app.file_url,
+ "app_image": app.app_image,
+ "creation": app.creation,
+ "modified": app.modified
+ }
+ result.append(app_data)
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_node_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取本地节点列表"""
+
+ # 构建查询条件
+ query_filters = {"public": 1}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "node_type asc"
+
+ # 获取节点列表
+ nodes = jingrow.get_all(
+ "Local Node",
+ filters=query_filters,
+ fields=[
+ "name", "node_type", "title", "subtitle",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "node_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(nodes)
+
+ # 格式化返回数据
+ result = []
+ for node in nodes:
+ node_data = {
+ "name": node.name,
+ "node_type": node.node_type,
+ "title": node.title,
+ "subtitle": node.subtitle,
+ "enabled": node.enabled,
+ "public": node.public,
+ "team": team_user_names.get(node.team),
+ "status": node.status,
+ "repository_url": node.repository_url,
+ "file_url": node.file_url,
+ "node_image": node.node_image,
+ "creation": node.creation,
+ "modified": node.modified
+ }
+ result.append(node_data)
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_agent_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取本地Agent列表"""
+
+ # 构建查询条件
+ query_filters = {"public": 1}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "agent_name asc"
+
+ # 获取Agent列表
+ agents = jingrow.get_all(
+ "Local Agent",
+ filters=query_filters,
+ fields=[
+ "name", "agent_name", "title", "subtitle",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "agent_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(agents)
+
+ # 格式化返回数据
+ result = []
+ for agent in agents:
+ agent_data = {
+ "name": agent.name,
+ "agent_name": agent.agent_name,
+ "title": agent.title,
+ "subtitle": agent.subtitle,
+ "enabled": agent.enabled,
+ "public": agent.public,
+ "team": team_user_names.get(agent.team),
+ "status": agent.status,
+ "repository_url": agent.repository_url,
+ "file_url": agent.file_url,
+ "agent_image": agent.agent_image,
+ "creation": agent.creation,
+ "modified": agent.modified
+ }
+ result.append(agent_data)
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_tool_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取本地工具列表"""
+
+ # 构建查询条件
+ query_filters = {"public": 1}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "tool_name asc"
+
+ # 获取工具列表
+ tools = jingrow.get_all(
+ "Local Tool",
+ filters=query_filters,
+ fields=[
+ "name", "tool_name", "title", "subtitle", "category", "version",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "icon", "color", "tool_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(tools)
+
+ # 格式化返回数据
+ result = []
+ for tool in tools:
+ tool_data = {
+ "name": tool.name,
+ "tool_name": tool.tool_name,
+ "title": tool.title,
+ "subtitle": tool.subtitle,
+ "category": tool.category,
+ "version": tool.version,
+ "enabled": tool.enabled,
+ "public": tool.public,
+ "team": team_user_names.get(tool.team),
+ "status": tool.status,
+ "repository_url": tool.repository_url,
+ "file_url": tool.file_url,
+ "icon": tool.icon,
+ "color": tool.color,
+ "tool_image": tool.tool_image,
+ "creation": tool.creation,
+ "modified": tool.modified
+ }
+ result.append(tool_data)
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_app(name):
+ """获取本地应用详情"""
+
+ if not jingrow.db.exists("Local App", name):
+ return {"success": False, "message": "应用不存在"}
+
+ app = jingrow.get_pg("Local App", name)
+
+ # 获取团队关联用户的名称
+ team_user_names = _get_team_user_names([app])
+
+ app_data = {
+ "name": app.name,
+ "app_name": app.app_name,
+ "title": app.title,
+ "subtitle": app.subtitle,
+ "description": app.description,
+ "category": app.category,
+ "enabled": app.enabled,
+ "public": app.public,
+ "team": team_user_names.get(app.team),
+ "status": app.status,
+ "repository_url": app.repository_url,
+ "file_url": app.file_url,
+ "app_image": app.app_image,
+ "creation": app.creation,
+ "modified": app.modified
+ }
+
+ return app_data
+
+
+@dashboard_whitelist()
+def get_my_local_app_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取当前团队的应用列表"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ # 构建查询条件
+ query_filters = {"team": team}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "app_name asc"
+
+ # 获取应用列表
+ apps = jingrow.get_all(
+ "Local App",
+ filters=query_filters,
+ fields=[
+ "name", "app_name", "title", "subtitle", "category",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "app_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(apps)
+
+ # 格式化返回数据
+ result = []
+ for app in apps:
+ app_data = {
+ "name": app.name,
+ "app_name": app.app_name,
+ "title": app.title,
+ "subtitle": app.subtitle,
+ "category": app.category,
+ "enabled": app.enabled,
+ "public": app.public,
+ "team": team_user_names.get(app.team),
+ "status": app.status,
+ "repository_url": app.repository_url,
+ "file_url": app.file_url,
+ "app_image": app.app_image,
+ "creation": app.creation,
+ "modified": app.modified
+ }
+ result.append(app_data)
+
+ return result
+
+
+@dashboard_whitelist()
+def create_local_app(app_data):
+ """创建本地应用"""
+ team = get_current_team()
+
+ if isinstance(app_data, str):
+ import json
+ app_data = json.loads(app_data)
+
+ # 检查应用名称是否已存在
+ existing_app = jingrow.db.exists("Local App", {"app_name": app_data["app_name"]})
+ if existing_app:
+ return {"error": "应用名称已存在,请使用其他名称"}
+
+ # 创建应用
+ app = jingrow.get_pg({
+ "pagetype": "Local App",
+ "app_name": app_data["app_name"],
+ "title": app_data["title"],
+ "subtitle": app_data.get("subtitle", ""),
+ "description": app_data.get("description", ""),
+ "category": app_data.get("category"),
+ "team": team,
+ "repository_url": app_data.get("repository_url"),
+ "file_url": app_data.get("file_url"),
+ "app_image": app_data.get("app_image")
+ })
+
+ app.insert()
+ return {"success": True, "name": app.name, "message": "应用创建成功"}
+
+
+@dashboard_whitelist()
+def update_local_app(name, app_data):
+ """更新本地应用"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local App", name):
+ return {"success": False, "message": "应用不存在"}
+
+ app = jingrow.get_pg("Local App", name)
+
+ # 验证权限:只能更新自己团队的应用
+ if app.team != team:
+ return {"success": False, "message": "您没有权限操作此应用"}
+
+ # 解析 JSON 数据
+ if isinstance(app_data, str):
+ try:
+ import json
+ app_data = json.loads(app_data)
+ except json.JSONDecodeError:
+ return {"success": False, "message": "JSON 数据格式错误"}
+
+ # 更新字段
+ updated_fields = []
+ for field, value in app_data.items():
+ if hasattr(app, field):
+ setattr(app, field, value)
+ updated_fields.append(field)
+
+ # 检查是否有字段被更新
+ if not updated_fields:
+ return {"success": False, "message": "没有有效的字段被更新"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ app.save(ignore_permissions=True)
+ return {"success": True, "name": app.name, "updated_fields": updated_fields, "message": "应用更新成功"}
+
+
+@dashboard_whitelist()
+def delete_local_app(name):
+ """删除本地应用"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local App", name):
+ return {"success": False, "message": "应用不存在"}
+
+ app = jingrow.get_pg("Local App", name)
+
+ # 验证权限:只能删除自己团队的应用
+ if app.team != team:
+ return {"success": False, "message": "您没有权限操作此应用"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ app.delete(ignore_permissions=True)
+
+ return {"success": True, "message": "应用删除成功"}
+
+
+@dashboard_whitelist()
+def get_my_local_node_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取当前团队的节点列表"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ # 构建查询条件
+ query_filters = {"team": team}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "node_type asc"
+
+ # 获取节点列表
+ nodes = jingrow.get_all(
+ "Local Node",
+ filters=query_filters,
+ fields=[
+ "name", "node_type", "title", "subtitle",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "node_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(nodes)
+
+ # 格式化返回数据
+ result = []
+ for node in nodes:
+ node_data = {
+ "name": node.name,
+ "node_type": node.node_type,
+ "title": node.title,
+ "subtitle": node.subtitle,
+ "enabled": node.enabled,
+ "public": node.public,
+ "team": team_user_names.get(node.team),
+ "status": node.status,
+ "repository_url": node.repository_url,
+ "file_url": node.file_url,
+ "node_image": node.node_image,
+ "creation": node.creation,
+ "modified": node.modified
+ }
+ result.append(node_data)
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_node(name):
+ """获取本地节点详情"""
+
+ if not jingrow.db.exists("Local Node", name):
+ return {"success": False, "message": "节点不存在"}
+
+ node = jingrow.get_pg("Local Node", name)
+
+ # 获取团队关联用户的名称
+ team_user_names = _get_team_user_names([node])
+
+ node_data = {
+ "name": node.name,
+ "node_type": node.node_type,
+ "title": node.title,
+ "subtitle": node.subtitle,
+ "description": node.description,
+ "enabled": node.enabled,
+ "public": node.public,
+ "team": team_user_names.get(node.team),
+ "status": node.status,
+ "repository_url": node.repository_url,
+ "file_url": node.file_url,
+ "node_image": node.node_image,
+ "creation": node.creation,
+ "modified": node.modified
+ }
+
+ return node_data
+
+
+@dashboard_whitelist()
+def create_local_node(node_data):
+ """创建本地节点"""
+ team = get_current_team()
+
+ if isinstance(node_data, str):
+ import json
+ node_data = json.loads(node_data)
+
+ # 检查节点类型是否已存在
+ existing_node = jingrow.db.exists("Local Node", {"node_type": node_data["node_type"]})
+ if existing_node:
+ return {"error": "节点类型已存在,请使用其他类型"}
+
+ # 创建节点
+ node = jingrow.get_pg({
+ "pagetype": "Local Node",
+ "node_type": node_data["node_type"],
+ "title": node_data["title"],
+ "subtitle": node_data.get("subtitle", ""),
+ "description": node_data.get("description", ""),
+ "team": team,
+ "repository_url": node_data.get("repository_url"),
+ "file_url": node_data.get("file_url"),
+ "node_image": node_data.get("node_image")
+ })
+
+ node.insert()
+ return {"success": True, "name": node.name, "message": "节点创建成功"}
+
+
+@dashboard_whitelist()
+def update_local_node(name, node_data):
+ """更新本地节点"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local Node", name):
+ return {"success": False, "message": "节点不存在"}
+
+ node = jingrow.get_pg("Local Node", name)
+
+ # 验证权限:只能更新自己团队的节点
+ if node.team != team:
+ return {"success": False, "message": "您没有权限操作此节点"}
+
+ # 解析 JSON 数据
+ if isinstance(node_data, str):
+ try:
+ import json
+ node_data = json.loads(node_data)
+ except json.JSONDecodeError:
+ return {"success": False, "message": "JSON 数据格式错误"}
+
+ # 更新字段
+ updated_fields = []
+ for field, value in node_data.items():
+ if hasattr(node, field):
+ setattr(node, field, value)
+ updated_fields.append(field)
+
+ # 检查是否有字段被更新
+ if not updated_fields:
+ return {"success": False, "message": "没有有效的字段被更新"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ node.save(ignore_permissions=True)
+ return {"success": True, "name": node.name, "updated_fields": updated_fields, "message": "节点更新成功"}
+
+
+@dashboard_whitelist()
+def get_my_local_agent_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取当前团队的Agent列表"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ # 构建查询条件
+ query_filters = {"team": team}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "agent_name asc"
+
+ # 获取Agent列表
+ agents = jingrow.get_all(
+ "Local Agent",
+ filters=query_filters,
+ fields=[
+ "name", "agent_name", "title", "subtitle",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "agent_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(agents)
+
+ # 格式化返回数据
+ result = []
+ for agent in agents:
+ agent_data = {
+ "name": agent.name,
+ "agent_name": agent.agent_name,
+ "title": agent.title,
+ "subtitle": agent.subtitle,
+ "enabled": agent.enabled,
+ "public": agent.public,
+ "team": team_user_names.get(agent.team),
+ "status": agent.status,
+ "repository_url": agent.repository_url,
+ "file_url": agent.file_url,
+ "agent_image": agent.agent_image,
+ "creation": agent.creation,
+ "modified": agent.modified
+ }
+ result.append(agent_data)
+
+ return result
+
+
+@dashboard_whitelist()
+def get_my_local_tool_list(filters=None, order_by=None, limit_start=None, limit_page_length=None):
+ """获取当前团队的工具列表"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ # 构建查询条件
+ query_filters = {"team": team}
+
+ if filters:
+ if isinstance(filters, str):
+ import json
+ filters = json.loads(filters)
+ query_filters.update(filters)
+
+ # 默认排序
+ if not order_by:
+ order_by = "tool_name asc"
+
+ # 获取工具列表
+ tools = jingrow.get_all(
+ "Local Tool",
+ filters=query_filters,
+ fields=[
+ "name", "tool_name", "title", "subtitle", "category", "version",
+ "enabled", "public", "team", "status", "repository_url",
+ "file_url", "icon", "color", "tool_image", "creation", "modified"
+ ],
+ order_by=order_by,
+ limit_start=limit_start,
+ limit_page_length=limit_page_length
+ )
+
+ # 批量获取团队用户名称
+ team_user_names = _get_team_user_names(tools)
+
+ # 格式化返回数据
+ result = []
+ for tool in tools:
+ tool_data = {
+ "name": tool.name,
+ "tool_name": tool.tool_name,
+ "title": tool.title,
+ "subtitle": tool.subtitle,
+ "category": tool.category,
+ "version": tool.version,
+ "enabled": tool.enabled,
+ "public": tool.public,
+ "team": team_user_names.get(tool.team),
+ "status": tool.status,
+ "repository_url": tool.repository_url,
+ "file_url": tool.file_url,
+ "icon": tool.icon,
+ "color": tool.color,
+ "tool_image": tool.tool_image,
+ "creation": tool.creation,
+ "modified": tool.modified
+ }
+ result.append(tool_data)
+
+ return result
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_agent(name):
+ """获取本地Agent详情"""
+
+ if not jingrow.db.exists("Local Agent", name):
+ return {"success": False, "message": "Agent不存在"}
+
+ agent = jingrow.get_pg("Local Agent", name)
+
+ # 获取团队关联用户的名称
+ team_user_names = _get_team_user_names([agent])
+
+ agent_data = {
+ "name": agent.name,
+ "agent_name": agent.agent_name,
+ "title": agent.title,
+ "subtitle": agent.subtitle,
+ "description": agent.description,
+ "enabled": agent.enabled,
+ "public": agent.public,
+ "team": team_user_names.get(agent.team),
+ "status": agent.status,
+ "repository_url": agent.repository_url,
+ "file_url": agent.file_url,
+ "agent_image": agent.agent_image,
+ "agent_flow": agent.agent_flow,
+ "creation": agent.creation,
+ "modified": agent.modified
+ }
+
+ return agent_data
+
+
+@jingrow.whitelist(allow_guest=True)
+def get_local_tool(name):
+ """获取本地工具详情"""
+
+ if not jingrow.db.exists("Local Tool", name):
+ return {"success": False, "message": "工具不存在"}
+
+ tool = jingrow.get_pg("Local Tool", name)
+
+ # 获取团队关联用户的名称
+ team_user_names = _get_team_user_names([tool])
+
+ tool_data = {
+ "name": tool.name,
+ "tool_name": tool.tool_name,
+ "title": tool.title,
+ "subtitle": tool.subtitle,
+ "description": tool.description,
+ "category": tool.category,
+ "version": tool.version,
+ "icon": tool.icon,
+ "color": tool.color,
+ "enabled": tool.enabled,
+ "public": tool.public,
+ "team": team_user_names.get(tool.team),
+ "status": tool.status,
+ "repository_url": tool.repository_url,
+ "file_url": tool.file_url,
+ "tool_image": tool.tool_image,
+ "creation": tool.creation,
+ "modified": tool.modified
+ }
+
+ return tool_data
+
+
+@dashboard_whitelist()
+def create_local_agent(agent_data):
+ """创建本地Agent"""
+ team = get_current_team()
+
+ if isinstance(agent_data, str):
+ import json
+ agent_data = json.loads(agent_data)
+
+ # 检查Agent名称是否已存在
+ existing_agent = jingrow.db.exists("Local Agent", {"agent_name": agent_data["agent_name"]})
+ if existing_agent:
+ return {"error": "Agent名称已存在,请使用其他名称"}
+
+ # 创建Agent
+ agent_flow = agent_data.get("agent_flow")
+
+ agent = jingrow.get_pg({
+ "pagetype": "Local Agent",
+ "agent_name": agent_data["agent_name"],
+ "title": agent_data["title"],
+ "subtitle": agent_data.get("subtitle", ""),
+ "description": agent_data.get("description", ""),
+ "team": team,
+ "repository_url": agent_data.get("repository_url"),
+ "file_url": agent_data.get("file_url"),
+ "agent_image": agent_data.get("agent_image"),
+ "agent_flow": agent_flow
+ })
+
+ agent.insert()
+ return {"success": True, "name": agent.name, "message": "Agent创建成功"}
+
+
+@dashboard_whitelist()
+def create_local_tool(tool_data):
+ """创建本地工具"""
+ team = get_current_team()
+
+ if isinstance(tool_data, str):
+ import json
+ tool_data = json.loads(tool_data)
+
+ # 检查工具名称是否已存在
+ existing_tool = jingrow.db.exists("Local Tool", {"tool_name": tool_data["tool_name"]})
+ if existing_tool:
+ return {"error": "工具名称已存在,请使用其他名称"}
+
+ # 创建工具
+ tool = jingrow.get_pg({
+ "pagetype": "Local Tool",
+ "tool_name": tool_data["tool_name"],
+ "title": tool_data["title"],
+ "subtitle": tool_data.get("subtitle", ""),
+ "description": tool_data.get("description", ""),
+ "category": tool_data.get("category"),
+ "version": tool_data.get("version"),
+ "icon": tool_data.get("icon"),
+ "color": tool_data.get("color"),
+ "team": team,
+ "repository_url": tool_data.get("repository_url"),
+ "file_url": tool_data.get("file_url"),
+ "tool_image": tool_data.get("tool_image")
+ })
+
+ tool.insert()
+ return {"success": True, "name": tool.name, "message": "工具创建成功"}
+
+
+@dashboard_whitelist()
+def update_local_agent(name, agent_data):
+ """更新本地Agent"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local Agent", name):
+ return {"success": False, "message": "Agent不存在"}
+
+ agent = jingrow.get_pg("Local Agent", name)
+
+ # 验证权限:只能更新自己团队的Agent
+ if agent.team != team:
+ return {"success": False, "message": "您没有权限操作此Agent"}
+
+ # 解析 JSON 数据
+ if isinstance(agent_data, str):
+ try:
+ import json
+ agent_data = json.loads(agent_data)
+ except json.JSONDecodeError:
+ return {"success": False, "message": "JSON 数据格式错误"}
+
+ # 更新字段
+ updated_fields = []
+ for field, value in agent_data.items():
+ if hasattr(agent, field):
+ setattr(agent, field, value)
+ updated_fields.append(field)
+
+ # 检查是否有字段被更新
+ if not updated_fields:
+ return {"success": False, "message": "没有有效的字段被更新"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ agent.save(ignore_permissions=True)
+ return {"success": True, "name": agent.name, "updated_fields": updated_fields, "message": "Agent更新成功"}
+
+
+@dashboard_whitelist()
+def update_local_tool(name, tool_data):
+ """更新本地工具"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local Tool", name):
+ return {"success": False, "message": "工具不存在"}
+
+ tool = jingrow.get_pg("Local Tool", name)
+
+ # 验证权限:只能更新自己团队的工具
+ if tool.team != team:
+ return {"success": False, "message": "您没有权限操作此工具"}
+
+ # 解析 JSON 数据
+ if isinstance(tool_data, str):
+ try:
+ import json
+ tool_data = json.loads(tool_data)
+ except json.JSONDecodeError:
+ return {"success": False, "message": "JSON 数据格式错误"}
+
+ # 更新字段
+ updated_fields = []
+ for field, value in tool_data.items():
+ if hasattr(tool, field):
+ setattr(tool, field, value)
+ updated_fields.append(field)
+
+ # 检查是否有字段被更新
+ if not updated_fields:
+ return {"success": False, "message": "没有有效的字段被更新"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ tool.save(ignore_permissions=True)
+ return {"success": True, "name": tool.name, "updated_fields": updated_fields, "message": "工具更新成功"}
+
+
+@dashboard_whitelist()
+def delete_local_node(name):
+ """删除本地节点"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local Node", name):
+ return {"success": False, "message": "节点不存在"}
+
+ node = jingrow.get_pg("Local Node", name)
+
+ # 验证权限:只能删除自己团队的节点
+ if node.team != team:
+ return {"success": False, "message": "您没有权限操作此节点"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ node.delete(ignore_permissions=True)
+
+ return {"success": True, "message": "节点删除成功"}
+
+
+@dashboard_whitelist()
+def delete_local_agent(name):
+ """删除本地Agent"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local Agent", name):
+ return {"success": False, "message": "Agent不存在"}
+
+ agent = jingrow.get_pg("Local Agent", name)
+
+ # 验证权限:只能删除自己团队的Agent
+ if agent.team != team:
+ return {"success": False, "message": "您没有权限操作此Agent"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ agent.delete(ignore_permissions=True)
+
+ return {"success": True, "message": "Agent删除成功"}
+
+
+@dashboard_whitelist()
+def delete_local_tool(name):
+ """删除本地工具"""
+
+ team = get_current_team()
+ if not team:
+ return {"success": False, "message": "未找到当前团队信息"}
+
+ if not jingrow.db.exists("Local Tool", name):
+ return {"success": False, "message": "工具不存在"}
+
+ tool = jingrow.get_pg("Local Tool", name)
+
+ # 验证权限:只能删除自己团队的工具
+ if tool.team != team:
+ return {"success": False, "message": "您没有权限操作此工具"}
+
+ # 使用 ignore_permissions=True 因为我们已经手动验证了团队权限
+ tool.delete(ignore_permissions=True)
+
+ return {"success": True, "message": "工具删除成功"}
diff --git a/jcloud/api/saas.py b/jcloud/api/saas.py
index 6185c32..34a40db 100644
--- a/jcloud/api/saas.py
+++ b/jcloud/api/saas.py
@@ -200,7 +200,6 @@ def check_subdomain_availability(subdomain, app):
{
"subdomain": subdomain,
"domain": get_saas_domain(app),
- "status": ("!=", "Archived"),
},
)
)
diff --git a/jcloud/api/server.py b/jcloud/api/server.py
index 78c251c..44d4375 100644
--- a/jcloud/api/server.py
+++ b/jcloud/api/server.py
@@ -22,11 +22,11 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.cluster.cluster import Cluster
-def poly_get_pg(doctypes, name):
- for pagetype in doctypes:
+def poly_get_pg(pagetypes, name):
+ for pagetype in pagetypes:
if jingrow.db.exists(pagetype, name):
return jingrow.get_pg(pagetype, name)
- return jingrow.get_pg(doctypes[-1], name)
+ return jingrow.get_pg(pagetypes[-1], name)
MOUNTPOINT_REGEX = "(/|/opt/volumes/mariadb|/opt/volumes/benches)"
@@ -89,7 +89,7 @@ def all(server_filter=None): # noqa: C901
query = app_server_query + database_server_query
# union isn't supported in qb for run method
- # http://git.jingrow.com:3000/jingrow/jingrow/issues/15609
+ # http://git.jingrow.com/jingrow/jingrow/issues/15609
servers = jingrow.db.sql(query.get_sql(), as_dict=True)
for server in servers:
server_plan_name = jingrow.get_value("Server", server.name, "plan")
diff --git a/jcloud/api/site.py b/jcloud/api/site.py
index cb0fe9c..b310ef3 100644
--- a/jcloud/api/site.py
+++ b/jcloud/api/site.py
@@ -58,21 +58,21 @@ if TYPE_CHECKING:
NAMESERVERS = ["1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4"]
-def protected(doctypes):
+def protected(pagetypes):
"""
This decorator is stupid. It works in magical ways. It checks whether the
- owner of the Pagetype (one of `doctypes`) is the same as the current team.
+ owner of the Pagetype (one of `pagetypes`) is the same as the current team.
The stupid magical part of this decorator is how it gets the name of the
Pagetype (see: `get_protected_pagetype_name`); in order of precedence:
1. kwargs value with key `name`
2. first value in kwargs value with key `filters` i.e. ≈ `kwargs['filters'].values()[0]`
3. first value in the args tuple
- 4. kwargs value with key `snake_case(doctypes[0])`
+ 4. kwargs value with key `snake_case(pagetypes[0])`
"""
- if not isinstance(doctypes, list):
- doctypes = [doctypes]
+ if not isinstance(pagetypes, list):
+ pagetypes = [pagetypes]
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
@@ -82,12 +82,12 @@ def protected(doctypes):
if user_type == "System User":
return wrapped(*args, **kwargs)
- name = get_protected_pagetype_name(args, kwargs, doctypes)
+ name = get_protected_pagetype_name(args, kwargs, pagetypes)
if not name:
jingrow.throw("找不到名称,不允许API访问", jingrow.PermissionError)
team = get_current_team()
- for pagetype in doctypes:
+ for pagetype in pagetypes:
owner = jingrow.db.get_value(pagetype, name, "team")
if owner == team or has_role("Jcloud Support Agent"):
@@ -98,7 +98,7 @@ def protected(doctypes):
return wrapper
-def get_protected_pagetype_name(args: list, kwargs: dict, doctypes: list[str]):
+def get_protected_pagetype_name(args: list, kwargs: dict, pagetypes: list[str]):
# 1. Name from kwargs["name"]
if name := kwargs.get("name"):
return name
@@ -112,11 +112,11 @@ def get_protected_pagetype_name(args: list, kwargs: dict, doctypes: list[str]):
if len(args) >= 1 and args[0]:
return args[0]
- if len(doctypes) == 0:
+ if len(pagetypes) == 0:
return None
- # 4. Name from snakecased first `doctypes` name
- pagetype = doctypes[0]
+ # 4. Name from snakecased first `pagetypes` name
+ pagetype = pagetypes[0]
key = pagetype.lower().replace(" ", "_")
return kwargs.get(key)
diff --git a/jcloud/api/spaces.py b/jcloud/api/spaces.py
index d5b8118..fa5fe20 100644
--- a/jcloud/api/spaces.py
+++ b/jcloud/api/spaces.py
@@ -156,7 +156,7 @@ def exists(subdomain, domain) -> bool:
jingrow.db.exists("Blocked Domain", {"name": subdomain, "root_domain": domain})
or jingrow.db.exists(
"Code Server",
- {"subdomain": subdomain, "domain": domain, "status": ("!=", "Archived")},
+ {"subdomain": subdomain, "domain": domain},
)
)
diff --git a/jcloud/api/tests/test_bench.py b/jcloud/api/tests/test_bench.py
index 5521fe5..77b3562 100644
--- a/jcloud/api/tests/test_bench.py
+++ b/jcloud/api/tests/test_bench.py
@@ -47,7 +47,7 @@ class TestAPIBench(JingrowTestCase):
self.app = create_test_app()
self.app_source = self.app.add_source(
self.version,
- repository_url="http://git.jingrow.com:3000/jingrow/jingrow",
+ repository_url="http://git.jingrow.com/jingrow/jingrow",
branch="version-15",
team=get_current_team(),
public=True,
diff --git a/jcloud/api/tests/test_marketplace.py b/jcloud/api/tests/test_marketplace.py
index 757b94d..537e0d7 100644
--- a/jcloud/api/tests/test_marketplace.py
+++ b/jcloud/api/tests/test_marketplace.py
@@ -62,7 +62,7 @@ PAYLOAD = [
"name": "develop",
"commit": {
"sha": "d11768d928ec7996810898cf627c4d57e8bb917d",
- "url": "http://git.jingrow.com:3000/api/v1/repos/jingrow/jingrow/commits/d11768d928ec7996810898cf627c4d57e8bb917d",
+ "url": "http://git.jingrow.com/api/v1/repos/jingrow/jingrow/commits/d11768d928ec7996810898cf627c4d57e8bb917d",
},
"protected": True,
},
@@ -70,7 +70,7 @@ PAYLOAD = [
"name": "enterprise-staging",
"commit": {
"sha": "3716ef769bbb45d5376c5d6f6ed9a2d52583ef1c",
- "url": "http://git.jingrow.com:3000/api/v1/repos/jingrow/jingrow/commits/3716ef769bbb45d5376c5d6f6ed9a2d52583ef1c",
+ "url": "http://git.jingrow.com/api/v1/repos/jingrow/jingrow/commits/3716ef769bbb45d5376c5d6f6ed9a2d52583ef1c",
},
"protected": False,
},
@@ -297,7 +297,7 @@ class TestAPIMarketplace(unittest.TestCase):
"name": "email_delivery_service",
"title": "Email Delivery Service",
"version": "v0.4",
- "repository_url": "http://git.jingrow.com:3000/jingrow/email_delivery_service",
+ "repository_url": "http://git.jingrow.com/jingrow/email_delivery_service",
"branch": "develop",
"github_installation_id": "",
}
@@ -365,7 +365,7 @@ class TestAPIMarketplace(unittest.TestCase):
def test_branches(self):
jingrow.set_user(self.team.user)
responses.get(
- url=f"http://git.jingrow.com:3000/api/v1/repos/{self.app_source.repository_owner}/{self.app_source.repository}/branches?per_page=100",
+ url=f"http://git.jingrow.com/api/v1/repos/{self.app_source.repository_owner}/{self.app_source.repository}/branches?per_page=100",
json=PAYLOAD,
status=200,
headers={},
diff --git a/jcloud/auth.py b/jcloud/auth.py
index b41506e..3fb8cf1 100644
--- a/jcloud/auth.py
+++ b/jcloud/auth.py
@@ -32,6 +32,7 @@ ALLOWED_PATHS = [
"/api/action/ping",
"/api/action/login",
"/api/action/logout",
+ "/api/action/jingrow.auth.get_logged_user",
"/api/action/jcloud.jcloud.pagetype.razorpay_webhook_log.razorpay_webhook_log.razorpay_webhook_handler",
"/api/action/jcloud.jcloud.pagetype.razorpay_webhook_log.razorpay_webhook_log.razorpay_authorized_payment_handler",
"/api/action/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler",
diff --git a/jcloud/bootstrap.py b/jcloud/bootstrap.py
index 2d3b9c2..5be9cbc 100644
--- a/jcloud/bootstrap.py
+++ b/jcloud/bootstrap.py
@@ -327,7 +327,7 @@ def setup_apps():
"pagetype": "App Source",
"app": app.name,
"branch": "develop",
- "repository_url": "http://git.jingrow.com:3000/jingrow/jingrow",
+ "repository_url": "http://git.jingrow.com/jingrow/jingrow",
"public": True,
"team": "Administrator",
"versions": [{"version": "v0.1"}],
diff --git a/jcloud/docker/Dockerfile b/jcloud/docker/Dockerfile
index 542fb37..dc25a80 100644
--- a/jcloud/docker/Dockerfile
+++ b/jcloud/docker/Dockerfile
@@ -1,5 +1,5 @@
# syntax = docker/dockerfile:experimental
-FROM ubuntu:20.04
+FROM ubuntu:22.04
ENV LANG C.UTF-8
ENV DEBIAN_FRONTEND noninteractive
@@ -15,6 +15,7 @@ RUN --mount=type=cache,target=/var/cache/apt apt-get update \
git \
mariadb-client \
libmariadb-dev \
+ pkg-config \
pv \
ntp \
wget \
@@ -37,6 +38,8 @@ RUN --mount=type=cache,target=/var/cache/apt apt-get update \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
+ # Chromium and dependencies
+ chromium-browser \
# wkhtmltopdf dependencies
ca-certificates \
fontconfig \
@@ -61,7 +64,7 @@ COPY --chown=root:root supervisord.conf /etc/supervisor/supervisord.conf
# Install Redis from PPA
RUN --mount=type=cache,target=/var/cache/apt curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \
- && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb focal main" | tee /etc/apt/sources.list.d/redis.list \
+ && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb jammy main" | tee /etc/apt/sources.list.d/redis.list \
&& apt-get update \
&& apt-get install --yes --no-install-suggests --no-install-recommends \
redis-server \
@@ -69,13 +72,12 @@ RUN --mount=type=cache,target=/var/cache/apt curl -fsSL https://packages.redis.i
# Install Python from DeadSnakes PPA
ENV {{ pg.get_dependency_version("python", True) }}
-RUN --mount=type=cache,target=/var/cache/apt add-apt-repository ppa:deadsnakes/ppa \
+RUN --mount=type=cache,target=/var/cache/apt add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get update \
&& apt-get install --yes --no-install-suggests --no-install-recommends \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-dev \
python${PYTHON_VERSION}-venv \
- python${PYTHON_VERSION}-distutils \
&& rm -rf /var/lib/apt/lists/* \
`#stage-pre-python`
@@ -83,17 +85,17 @@ RUN --mount=type=cache,target=/var/cache/apt add-apt-repository ppa:deadsnakes/p
# Install wkhtmltopdf
ENV {{ pg.get_dependency_version("wkhtmltopdf", True) }}
{% if pg.get_dependency_version("wkhtmltopdf") == '0.12.6' %}
-RUN wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb \
- && dpkg -i wkhtmltox_0.12.6-1.focal_amd64.deb \
- && rm wkhtmltox_0.12.6-1.focal_amd64.deb \
+RUN wget http://npm.jingrow.com:105/wkhtmltox_0.12.6.1-2.jammy_amd64.deb \
+ && dpkg -i wkhtmltox_0.12.6.1-2.jammy_amd64.deb \
+ && rm wkhtmltox_0.12.6.1-2.jammy_amd64.deb \
`#stage-pre-wkhtmltopdf`
{% elif pg.get_dependency_version("wkhtmltopdf") == '0.12.5' %}
-RUN wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.focal_amd64.deb \
- && dpkg -i wkhtmltox_0.12.5-1.focal_amd64.deb \
- && rm wkhtmltox_0.12.5-1.focal_amd64.deb \
+RUN wget http://npm.jingrow.com:105/wkhtmltox_0.12.5-1.jammy_amd64.deb \
+ && dpkg -i wkhtmltox_0.12.5-1.jammy_amd64.deb \
+ && rm wkhtmltox_0.12.5-1.jammy_amd64.deb \
`#stage-pre-wkhtmltopdf`
{% elif pg.get_dependency_version("wkhtmltopdf") == '0.12.4' %}
-RUN wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz \
+RUN wget http://npm.jingrow.com:105/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz \
&& tar -xvf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz \
&& mv wkhtmltox/bin/wkhtmlto* /usr/local/bin/ \
&& rm -rf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz wkhtmltox \
@@ -105,7 +107,7 @@ RUN curl -fsSL https://code-server.dev/install.sh | sh `#stage-pre-code-server`
{% endif %}
# Install Fonts
-RUN git clone --progress --depth 1 http://git.jingrow.com:3000/jingrow/fonts.git /tmp/fonts \
+RUN git clone --progress --depth 1 http://git.jingrow.com/jpub/fonts.git /tmp/fonts \
&& rm -rf /etc/fonts && mv /tmp/fonts/etc_fonts /etc/fonts \
&& rm -rf /usr/share/fonts && mv /tmp/fonts/usr_share_fonts /usr/share/fonts \
&& rm -rf /tmp/fonts \
@@ -152,44 +154,46 @@ RUN --mount=type=cache,target=/var/cache/apt {{ p.after_install }} \
# symlink mysqldump to mariadb-dump
-RUN ln -s /usr/bin/mysqldump /usr/bin/mariadb-dump
+RUN test -f /usr/bin/mariadb-dump || ln -s /usr/bin/mysqldump /usr/bin/mariadb-dump
-# Switch to jingrow
-USER jingrow
WORKDIR /home/jingrow
-
-# Install Node using NVM
-ENV NVM_DIR /home/jingrow/.nvm
-ENV {{ pg.get_dependency_version("nvm", True) }}
+# Install Node using NodeSource (as root to avoid GitHub access via nvm)
ENV {{ pg.get_dependency_version("node", True) }}
-
-RUN wget https://raw.githubusercontent.com/nvm-sh/nvm/v${NVM_VERSION}/install.sh \
- && bash install.sh \
- && . "/home/jingrow/.nvm/nvm.sh" \
- && nvm install ${NODE_VERSION} \
- && nvm use v${NODE_VERSION} \
- && nvm alias default v${NODE_VERSION} \
- && rm install.sh \
- && nvm cache clear \
+USER root
+RUN NODE_MAJOR=$(echo ${NODE_VERSION} | cut -d. -f1) \
+ && curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
+ && apt-get update \
+ && apt-get install --yes --no-install-suggests --no-install-recommends nodejs \
+ && rm -rf /var/lib/apt/lists/* \
`#stage-pre-node`
-ENV PATH "$PATH:/home/jingrow/.nvm/versions/node/v${NODE_VERSION}/bin"
+# Install Yarn globally
+RUN npm install -g yarn `#stage-pre-yarn`
-# Install Yarn
-RUN --mount=type=cache,target=/home/jingrow/.cache,uid=1000,gid=1000 npm install -g yarn `#stage-pre-yarn`
+# Switch back to jingrow
+USER jingrow
# Install Bench
-ENV PATH "$PATH:/home/jingrow/.local/bin"
+# Set environment variables first
+{% for v in pg.environment_variables %}
+ENV {{v.key}} {{ v.value }}
+{% endfor %}
+# Install Bench
+ENV PATH "$PATH:/home/jingrow/.local/bin"
RUN wget https://bootstrap.pypa.io/get-pip.py && python${PYTHON_VERSION} get-pip.py `#stage-pre-pip`
RUN python${PYTHON_VERSION} -m pip install --upgrade pip `#stage-pre-pip-upgrade`
RUN python${PYTHON_VERSION} -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple `#stage-pre-pip-mirror`
RUN python${PYTHON_VERSION} -m pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn `#stage-pre-pip-trusted`
ENV {{ pg.get_dependency_version("bench", True) }}
-RUN --mount=type=cache,target=/home/jingrow/.cache,uid=1000,gid=1000 python${PYTHON_VERSION} -m pip install --upgrade --resume-retries 5 --timeout 100 git+http://git.jingrow.com:3000/jingrow/bench.git `#stage-bench-bench`
+# Now bench install command can use the environment variables
+RUN --mount=type=cache,target=/home/jingrow/.cache,uid=1000,gid=1000 \
+ python${PYTHON_VERSION} -m pip install --upgrade --resume-retries 5 --timeout 100 \
+ git+http://${GIT_USERNAME:+$GIT_USERNAME:$GIT_ACCESS_TOKEN@}git.jingrow.com/jingrow/bench.git \
+ `#stage-bench-bench`
RUN --mount=type=cache,target=/home/jingrow/.cache,uid=1000,gid=1000 python${PYTHON_VERSION} -m pip install Jinja2~=3.0.3
RUN --mount=type=cache,target=/home/jingrow/.cache,uid=1000,gid=1000 python${PYTHON_VERSION} -m pip install --upgrade setuptools
@@ -200,11 +204,6 @@ ENV PYTHONUNBUFFERED 1
# For the sake of completing the step
RUN `#stage-bench-env`
-# Set environment variables
-{% for v in pg.environment_variables %}
-ENV {{v.key}} {{ v.value }}
-{% endfor %}
-
# Install Jingrow app
ENV PIP_RETRIES=10
ENV PIP_TIMEOUT=300
diff --git a/jcloud/docker/Dockerfile_Bench_5_2_1 b/jcloud/docker/Dockerfile_Bench_5_2_1
index 7ec22bb..baffc5f 100644
--- a/jcloud/docker/Dockerfile_Bench_5_2_1
+++ b/jcloud/docker/Dockerfile_Bench_5_2_1
@@ -93,7 +93,7 @@ RUN wget https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/${WKHTMLTO
RUN curl -fsSL https://code-server.dev/install.sh | sh `#stage-pre-code-server`
# Install Fonts
-RUN git clone --progress --depth 1 http://git.jingrow.com:3000/jingrow/fonts.git /tmp/fonts \
+RUN git clone --progress --depth 1 http://git.jingrow.com/jpub/fonts.git /tmp/fonts \
&& rm -rf /etc/fonts && mv /tmp/fonts/etc_fonts /etc/fonts \
&& rm -rf /usr/share/fonts && mv /tmp/fonts/usr_share_fonts /usr/share/fonts \
&& rm -rf /tmp/fonts \
diff --git a/jcloud/experimental/pagetype/referral_bonus/referral_bonus.py b/jcloud/experimental/pagetype/referral_bonus/referral_bonus.py
index 984844f..f587cf4 100644
--- a/jcloud/experimental/pagetype/referral_bonus/referral_bonus.py
+++ b/jcloud/experimental/pagetype/referral_bonus/referral_bonus.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ReferralBonus(Document):
+class ReferralBonus(Page):
@jingrow.whitelist()
def allocate_credits(self):
# Credits have already been allocated
diff --git a/jcloud/hooks.py b/jcloud/hooks.py
index af7139e..59d3711 100644
--- a/jcloud/hooks.py
+++ b/jcloud/hooks.py
@@ -394,10 +394,10 @@ persistent_cache_keys = [
"defaults",
"pagetype_form_meta",
"pagetype_meta",
- "doctypes_with_web_view",
+ "pagetypes_with_web_view",
"document_cache::*",
"document_naming_rule_map",
- "domain_restricted_doctypes",
+ "domain_restricted_pagetypes",
"domain_restricted_pages",
"energy_point_rule_map",
"jingrow.utils.scheduler.schedule_jobs_based_on_activity*", # dormant checks
diff --git a/jcloud/infrastructure/pagetype/ssh_access_audit/ssh_access_audit.py b/jcloud/infrastructure/pagetype/ssh_access_audit/ssh_access_audit.py
index 2d16e00..9c9dcc6 100644
--- a/jcloud/infrastructure/pagetype/ssh_access_audit/ssh_access_audit.py
+++ b/jcloud/infrastructure/pagetype/ssh_access_audit/ssh_access_audit.py
@@ -16,7 +16,7 @@ from ansible.parsing.dataloader import DataLoader
from ansible.playbook.play import Play
from ansible.plugins.callback import CallbackBase
from ansible.vars.manager import VariableManager
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import reconnect_on_failure
@@ -32,7 +32,7 @@ SERVER_TYPES = [
]
-class SSHAccessAudit(Document):
+class SSHAccessAudit(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/ssh_access_audit_host/ssh_access_audit_host.py b/jcloud/infrastructure/pagetype/ssh_access_audit_host/ssh_access_audit_host.py
index a55cfdd..590a12b 100644
--- a/jcloud/infrastructure/pagetype/ssh_access_audit_host/ssh_access_audit_host.py
+++ b/jcloud/infrastructure/pagetype/ssh_access_audit_host/ssh_access_audit_host.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SSHAccessAuditHost(Document):
+class SSHAccessAuditHost(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/ssh_access_audit_violation/ssh_access_audit_violation.py b/jcloud/infrastructure/pagetype/ssh_access_audit_violation/ssh_access_audit_violation.py
index 9aba5d2..6458da3 100644
--- a/jcloud/infrastructure/pagetype/ssh_access_audit_violation/ssh_access_audit_violation.py
+++ b/jcloud/infrastructure/pagetype/ssh_access_audit_violation/ssh_access_audit_violation.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SSHAccessAuditViolation(Document):
+class SSHAccessAuditViolation(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/virtual_disk_resize/virtual_disk_resize.py b/jcloud/infrastructure/pagetype/virtual_disk_resize/virtual_disk_resize.py
index 356ead6..28471e6 100644
--- a/jcloud/infrastructure/pagetype/virtual_disk_resize/virtual_disk_resize.py
+++ b/jcloud/infrastructure/pagetype/virtual_disk_resize/virtual_disk_resize.py
@@ -10,14 +10,14 @@ from enum import Enum
import botocore
import jingrow
from jingrow.core.utils import find, find_all
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.ansible_console.ansible_console import AnsibleAdHoc
SUPPORTED_FILESYSTEMS = ["ext4"]
-class VirtualDiskResize(Document):
+class VirtualDiskResize(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -480,7 +480,7 @@ class VirtualDiskResize(Document):
for method, wait_for_completion in methods:
steps.append(
{
- "step": method.__pg__,
+ "step": method.__doc__,
"method": method.__name__,
"wait_for_completion": wait_for_completion,
}
diff --git a/jcloud/infrastructure/pagetype/virtual_machine_migration/virtual_machine_migration.py b/jcloud/infrastructure/pagetype/virtual_machine_migration/virtual_machine_migration.py
index 3d77c8b..27c362e 100644
--- a/jcloud/infrastructure/pagetype/virtual_machine_migration/virtual_machine_migration.py
+++ b/jcloud/infrastructure/pagetype/virtual_machine_migration/virtual_machine_migration.py
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.ansible_console.ansible_console import AnsibleAdHoc
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
StepStatus = Enum("StepStatus", ["Pending", "Running", "Success", "Failure"])
-class VirtualMachineMigration(Document):
+class VirtualMachineMigration(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -289,7 +289,7 @@ class VirtualMachineMigration(Document):
for method, wait_for_completion in methods:
steps.append(
{
- "step": method.__pg__,
+ "step": method.__doc__,
"method": method.__name__,
"wait_for_completion": wait_for_completion,
}
diff --git a/jcloud/infrastructure/pagetype/virtual_machine_migration_bind_mount/virtual_machine_migration_bind_mount.py b/jcloud/infrastructure/pagetype/virtual_machine_migration_bind_mount/virtual_machine_migration_bind_mount.py
index bb70f58..795481a 100644
--- a/jcloud/infrastructure/pagetype/virtual_machine_migration_bind_mount/virtual_machine_migration_bind_mount.py
+++ b/jcloud/infrastructure/pagetype/virtual_machine_migration_bind_mount/virtual_machine_migration_bind_mount.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineMigrationBindMount(Document):
+class VirtualMachineMigrationBindMount(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/virtual_machine_migration_mount/virtual_machine_migration_mount.py b/jcloud/infrastructure/pagetype/virtual_machine_migration_mount/virtual_machine_migration_mount.py
index 916016c..29a804f 100644
--- a/jcloud/infrastructure/pagetype/virtual_machine_migration_mount/virtual_machine_migration_mount.py
+++ b/jcloud/infrastructure/pagetype/virtual_machine_migration_mount/virtual_machine_migration_mount.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineMigrationMount(Document):
+class VirtualMachineMigrationMount(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/virtual_machine_migration_step/virtual_machine_migration_step.py b/jcloud/infrastructure/pagetype/virtual_machine_migration_step/virtual_machine_migration_step.py
index 8965faa..44df041 100644
--- a/jcloud/infrastructure/pagetype/virtual_machine_migration_step/virtual_machine_migration_step.py
+++ b/jcloud/infrastructure/pagetype/virtual_machine_migration_step/virtual_machine_migration_step.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineMigrationStep(Document):
+class VirtualMachineMigrationStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/virtual_machine_migration_volume/virtual_machine_migration_volume.py b/jcloud/infrastructure/pagetype/virtual_machine_migration_volume/virtual_machine_migration_volume.py
index f8e6128..61cdafa 100644
--- a/jcloud/infrastructure/pagetype/virtual_machine_migration_volume/virtual_machine_migration_volume.py
+++ b/jcloud/infrastructure/pagetype/virtual_machine_migration_volume/virtual_machine_migration_volume.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineMigrationVolume(Document):
+class VirtualMachineMigrationVolume(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/infrastructure/pagetype/virtual_machine_replacement/virtual_machine_replacement.py b/jcloud/infrastructure/pagetype/virtual_machine_replacement/virtual_machine_replacement.py
index 56ded8d..a8954ef 100644
--- a/jcloud/infrastructure/pagetype/virtual_machine_replacement/virtual_machine_replacement.py
+++ b/jcloud/infrastructure/pagetype/virtual_machine_replacement/virtual_machine_replacement.py
@@ -9,7 +9,7 @@ from enum import Enum
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
if TYPE_CHECKING:
from jcloud.infrastructure.pagetype.virtual_machine_migration_step.virtual_machine_migration_step import (
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
StepStatus = Enum("StepStatus", ["Pending", "Running", "Success", "Failure"])
-class VirtualMachineReplacement(Document):
+class VirtualMachineReplacement(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -113,7 +113,7 @@ class VirtualMachineReplacement(Document):
for method, wait_for_completion in methods:
steps.append(
{
- "step": method.__pg__,
+ "step": method.__doc__,
"method": method.__name__,
"wait_for_completion": wait_for_completion,
}
diff --git a/jcloud/jcloud/pagetype/account_request/account_request.py b/jcloud/jcloud/pagetype/account_request/account_request.py
index 414a35e..cff9e56 100644
--- a/jcloud/jcloud/pagetype/account_request/account_request.py
+++ b/jcloud/jcloud/pagetype/account_request/account_request.py
@@ -6,7 +6,7 @@ from __future__ import annotations
import json
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_url, random_string
from jcloud.utils import get_country_info, is_valid_email_address
@@ -14,7 +14,7 @@ from jcloud.utils.otp import generate_otp
from jcloud.utils.telemetry import capture
-class AccountRequest(Document):
+class AccountRequest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -206,7 +206,7 @@ class AccountRequest(Document):
if not args.get("image_path"):
args.update(
{
- "image_path": "http://git.jingrow.com:3000/jingrow/gameplan/assets/9355208/447035d0-0686-41d2-910a-a3d21928ab94"
+ "image_path": "http://git.jingrow.com/jingrow/gameplan/assets/9355208/447035d0-0686-41d2-910a-a3d21928ab94"
}
)
# Telemetry: Verification Email Sent
diff --git a/jcloud/jcloud/pagetype/account_request_jcloud_role/account_request_jcloud_role.py b/jcloud/jcloud/pagetype/account_request_jcloud_role/account_request_jcloud_role.py
index ff4397a..1c48466 100644
--- a/jcloud/jcloud/pagetype/account_request_jcloud_role/account_request_jcloud_role.py
+++ b/jcloud/jcloud/pagetype/account_request_jcloud_role/account_request_jcloud_role.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AccountRequestJcloudRole(Document):
+class AccountRequestJcloudRole(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/add_on_settings/add_on_settings.py b/jcloud/jcloud/pagetype/add_on_settings/add_on_settings.py
index 5000b16..0abc579 100644
--- a/jcloud/jcloud/pagetype/add_on_settings/add_on_settings.py
+++ b/jcloud/jcloud/pagetype/add_on_settings/add_on_settings.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AddOnSettings(Document):
+class AddOnSettings(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/agent_job/agent_job.py b/jcloud/jcloud/pagetype/agent_job/agent_job.py
index c40b2b0..6bea6dc 100644
--- a/jcloud/jcloud/pagetype/agent_job/agent_job.py
+++ b/jcloud/jcloud/pagetype/agent_job/agent_job.py
@@ -9,7 +9,7 @@ import traceback
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.monitor import add_data_to_monitor
from jingrow.utils import (
add_days,
@@ -36,7 +36,7 @@ from jcloud.utils import has_role, log_error, timer
AGENT_LOG_KEY = "agent-jobs"
-class AgentJob(Document):
+class AgentJob(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/agent_job/agent_job_notifications.py b/jcloud/jcloud/pagetype/agent_job/agent_job_notifications.py
index f524d82..2fc5183 100644
--- a/jcloud/jcloud/pagetype/agent_job/agent_job_notifications.py
+++ b/jcloud/jcloud/pagetype/agent_job/agent_job_notifications.py
@@ -12,7 +12,7 @@ import jingrow
Used to create notifications if the Agent Job error is something that can
be handled by the user.
-Based on http://git.jingrow.com:3000/jingrow/jcloud/pull/1544
+Based on http://git.jingrow.com/jingrow/jcloud/pull/1544
To handle an error:
1. Create a pg page that helps the user get out of it under: jingrow.com/docs/common-issues
@@ -202,7 +202,7 @@ def update_with_row_size_too_large_err(details: Details, job: AgentJob):
details[
"message"
] = f"""The server encountered a row size too large error while migrating the site {job.site} .
- This tends to happen on doctypes with many custom fields
+ This tends to happen on pagetypes with many custom fields
To rectify this issue, please follow the steps mentioned in Help .
"""
diff --git a/jcloud/jcloud/pagetype/agent_job_step/agent_job_step.py b/jcloud/jcloud/pagetype/agent_job_step/agent_job_step.py
index 3033d9c..635622f 100644
--- a/jcloud/jcloud/pagetype/agent_job_step/agent_job_step.py
+++ b/jcloud/jcloud/pagetype/agent_job_step/agent_job_step.py
@@ -3,10 +3,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AgentJobStep(Document):
+class AgentJobStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/agent_job_type/agent_job_type.py b/jcloud/jcloud/pagetype/agent_job_type/agent_job_type.py
index 088647f..0ac2c04 100644
--- a/jcloud/jcloud/pagetype/agent_job_type/agent_job_type.py
+++ b/jcloud/jcloud/pagetype/agent_job_type/agent_job_type.py
@@ -3,12 +3,12 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import ttl_cache
-class AgentJobType(Document):
+class AgentJobType(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/agent_job_type_step/agent_job_type_step.py b/jcloud/jcloud/pagetype/agent_job_type_step/agent_job_type_step.py
index b41146b..a489d44 100644
--- a/jcloud/jcloud/pagetype/agent_job_type_step/agent_job_type_step.py
+++ b/jcloud/jcloud/pagetype/agent_job_type_step/agent_job_type_step.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AgentJobTypeStep(Document):
+class AgentJobTypeStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/agent_request_failure/agent_request_failure.py b/jcloud/jcloud/pagetype/agent_request_failure/agent_request_failure.py
index 4dfb3b6..decd245 100644
--- a/jcloud/jcloud/pagetype/agent_request_failure/agent_request_failure.py
+++ b/jcloud/jcloud/pagetype/agent_request_failure/agent_request_failure.py
@@ -3,13 +3,13 @@
import jingrow
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.utils import log_error
-class AgentRequestFailure(Document):
+class AgentRequestFailure(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/alertmanager_webhook_log/alertmanager_webhook_log.py b/jcloud/jcloud/pagetype/alertmanager_webhook_log/alertmanager_webhook_log.py
index 717eed6..4f81324 100644
--- a/jcloud/jcloud/pagetype/alertmanager_webhook_log/alertmanager_webhook_log.py
+++ b/jcloud/jcloud/pagetype/alertmanager_webhook_log/alertmanager_webhook_log.py
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_url_to_form
from jingrow.utils.background_jobs import enqueue_pg
from jingrow.utils.data import add_to_date
@@ -43,7 +43,7 @@ Labels:
"""
-class AlertmanagerWebhookLog(Document):
+class AlertmanagerWebhookLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -228,7 +228,7 @@ class AlertmanagerWebhookLog(Document):
return jingrow.render_template(TELEGRAM_NOTIFICATION_TEMPLATE, context)
def guess_pagetype(self, name):
- doctypes = [
+ pagetypes = [
"Site",
"Bench",
"Server",
@@ -241,7 +241,7 @@ class AlertmanagerWebhookLog(Document):
"Site Domain",
"Trace Server",
]
- for pagetype in doctypes:
+ for pagetype in pagetypes:
if jingrow.db.exists(pagetype, name):
return pagetype
return None
diff --git a/jcloud/jcloud/pagetype/alertmanager_webhook_log_reaction_job/alertmanager_webhook_log_reaction_job.py b/jcloud/jcloud/pagetype/alertmanager_webhook_log_reaction_job/alertmanager_webhook_log_reaction_job.py
index 15fac4b..36594b1 100644
--- a/jcloud/jcloud/pagetype/alertmanager_webhook_log_reaction_job/alertmanager_webhook_log_reaction_job.py
+++ b/jcloud/jcloud/pagetype/alertmanager_webhook_log_reaction_job/alertmanager_webhook_log_reaction_job.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AlertmanagerWebhookLogReactionJob(Document):
+class AlertmanagerWebhookLogReactionJob(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/alipay_payment_record/alipay_payment_record.py b/jcloud/jcloud/pagetype/alipay_payment_record/alipay_payment_record.py
index 0194b1d..aa5c723 100644
--- a/jcloud/jcloud/pagetype/alipay_payment_record/alipay_payment_record.py
+++ b/jcloud/jcloud/pagetype/alipay_payment_record/alipay_payment_record.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AlipayPaymentRecord(Document):
+class AlipayPaymentRecord(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ansible_console/ansible_console.py b/jcloud/jcloud/pagetype/ansible_console/ansible_console.py
index 3d250d2..090f93f 100644
--- a/jcloud/jcloud/pagetype/ansible_console/ansible_console.py
+++ b/jcloud/jcloud/pagetype/ansible_console/ansible_console.py
@@ -13,13 +13,13 @@ from ansible.parsing.dataloader import DataLoader
from ansible.playbook.play import Play
from ansible.plugins.callback import CallbackBase
from ansible.vars.manager import VariableManager
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_timedelta
from jcloud.utils import reconnect_on_failure
-class AnsibleConsole(Document):
+class AnsibleConsole(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ansible_console_log/ansible_console_log.py b/jcloud/jcloud/pagetype/ansible_console_log/ansible_console_log.py
index 1f0746d..41a120b 100644
--- a/jcloud/jcloud/pagetype/ansible_console_log/ansible_console_log.py
+++ b/jcloud/jcloud/pagetype/ansible_console_log/ansible_console_log.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AnsibleConsoleLog(Document):
+class AnsibleConsoleLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ansible_console_output/ansible_console_output.py b/jcloud/jcloud/pagetype/ansible_console_output/ansible_console_output.py
index 5f8feba..c33c9a0 100644
--- a/jcloud/jcloud/pagetype/ansible_console_output/ansible_console_output.py
+++ b/jcloud/jcloud/pagetype/ansible_console_output/ansible_console_output.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AnsibleConsoleOutput(Document):
+class AnsibleConsoleOutput(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ansible_play/ansible_play.py b/jcloud/jcloud/pagetype/ansible_play/ansible_play.py
index 0373d79..f1de2de 100644
--- a/jcloud/jcloud/pagetype/ansible_play/ansible_play.py
+++ b/jcloud/jcloud/pagetype/ansible_play/ansible_play.py
@@ -4,14 +4,14 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cstr
from jcloud.api.client import is_owned_by_team
from jcloud.utils import poly_get_pagetype
-class AnsiblePlay(Document):
+class AnsiblePlay(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ansible_task/ansible_task.py b/jcloud/jcloud/pagetype/ansible_task/ansible_task.py
index 293b9db..fe10b67 100644
--- a/jcloud/jcloud/pagetype/ansible_task/ansible_task.py
+++ b/jcloud/jcloud/pagetype/ansible_task/ansible_task.py
@@ -4,10 +4,10 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AnsibleTask(Document):
+class AnsibleTask(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app/app.py b/jcloud/jcloud/pagetype/app/app.py
index 4a6526c..a2eedeb 100644
--- a/jcloud/jcloud/pagetype/app/app.py
+++ b/jcloud/jcloud/pagetype/app/app.py
@@ -7,14 +7,14 @@ import typing
import rq
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils.jobs import has_job_timeout_exceeded
if typing.TYPE_CHECKING:
from jcloud.jcloud.pagetype.app_source.app_source import AppSource
-class App(Document):
+class App(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app/test_app.py b/jcloud/jcloud/pagetype/app/test_app.py
index f2cfbbe..9a70db9 100644
--- a/jcloud/jcloud/pagetype/app/test_app.py
+++ b/jcloud/jcloud/pagetype/app/test_app.py
@@ -30,7 +30,7 @@ class TestApp(unittest.TestCase):
source = app.add_source(
"v0.2",
- "http://git.jingrow.com:3000/jingrow/jingrow",
+ "http://git.jingrow.com/jingrow/jingrow",
"version-12",
create_test_team().name,
)
@@ -46,7 +46,7 @@ class TestApp(unittest.TestCase):
source = app.add_source(
"v0.2",
- "http://git.jingrow.com:3000/jingrow/jerp",
+ "http://git.jingrow.com/jingrow/jerp",
"version-12",
create_test_team().name,
)
@@ -61,13 +61,13 @@ class TestApp(unittest.TestCase):
source_1 = app.add_source(
"v0.2",
- "http://git.jingrow.com:3000/jingrow/jingrow",
+ "http://git.jingrow.com/jingrow/jingrow",
"version-12",
create_test_team().name,
)
source_2 = app.add_source(
"v0.3",
- "http://git.jingrow.com:3000/jingrow/jingrow",
+ "http://git.jingrow.com/jingrow/jingrow",
"version-13",
create_test_team().name,
)
@@ -85,7 +85,7 @@ class TestApp(unittest.TestCase):
source_1 = app.add_source(
"v0.2",
- "http://git.jingrow.com:3000/jingrow/jerp_documentation",
+ "http://git.jingrow.com/jingrow/jerp_documentation",
"master",
team_name,
)
@@ -95,7 +95,7 @@ class TestApp(unittest.TestCase):
source_2 = app.add_source(
"v0.3",
- "http://git.jingrow.com:3000/jingrow/jerp_documentation",
+ "http://git.jingrow.com/jingrow/jerp_documentation",
"master",
team_name,
)
@@ -109,7 +109,7 @@ class TestApp(unittest.TestCase):
app = create_test_app("jingrow", "Jingrow Framework")
source_1 = app.add_source(
"v0.2",
- "http://git.jingrow.com:3000/jingrow/jingrow",
+ "http://git.jingrow.com/jingrow/jingrow",
"version-12",
create_test_team().name,
)
@@ -119,7 +119,7 @@ class TestApp(unittest.TestCase):
source_2 = app.add_source(
"v0.3",
- "http://git.jingrow.com:3000/jingrow/jingrow",
+ "http://git.jingrow.com/jingrow/jingrow",
"version-13",
create_test_team().name,
)
diff --git a/jcloud/jcloud/pagetype/app_group/app_group.py b/jcloud/jcloud/pagetype/app_group/app_group.py
index d22238b..b22d77a 100644
--- a/jcloud/jcloud/pagetype/app_group/app_group.py
+++ b/jcloud/jcloud/pagetype/app_group/app_group.py
@@ -4,10 +4,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppGroup(Document):
+class AppGroup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app_patch/app_patch.py b/jcloud/jcloud/pagetype/app_patch/app_patch.py
index 74bedb5..1e8329c 100644
--- a/jcloud/jcloud/pagetype/app_patch/app_patch.py
+++ b/jcloud/jcloud/pagetype/app_patch/app_patch.py
@@ -7,7 +7,7 @@ from typing import Optional, TypedDict
import jingrow
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.api.client import dashboard_whitelist
@@ -38,7 +38,7 @@ if typing.TYPE_CHECKING:
from jcloud.jcloud.pagetype.agent_job.agent_job import AgentJob
-class AppPatch(Document):
+class AppPatch(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app_release/app_release.py b/jcloud/jcloud/pagetype/app_release/app_release.py
index 69d42cb..b66f8d2 100644
--- a/jcloud/jcloud/pagetype/app_release/app_release.py
+++ b/jcloud/jcloud/pagetype/app_release/app_release.py
@@ -9,7 +9,7 @@ from datetime import datetime
from typing import Optional, TypedDict
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.github import get_access_token
from jcloud.jcloud.pagetype.app_source.app_source import AppSource
@@ -31,7 +31,7 @@ class AppReleasePair(TypedDict):
new: AppReleaseDict
-class AppRelease(Document):
+class AppRelease(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -223,7 +223,7 @@ class AppRelease(Document):
# Do not edit without updating deploy_notifications.py
raise Exception("App installation token could not be fetched", self.app)
- return f"https://x-access-token:{token}@git.jingrow.com:3000/{source.repository_owner}/{source.repository}"
+ return f"https://x-access-token:{token}@git.jingrow.com/{source.repository_owner}/{source.repository}"
def on_trash(self):
if self.clone_directory and os.path.exists(self.clone_directory):
diff --git a/jcloud/jcloud/pagetype/app_release_approval_request/app_release_approval_request.py b/jcloud/jcloud/pagetype/app_release_approval_request/app_release_approval_request.py
index 3a3047e..efe2fe2 100644
--- a/jcloud/jcloud/pagetype/app_release_approval_request/app_release_approval_request.py
+++ b/jcloud/jcloud/pagetype/app_release_approval_request/app_release_approval_request.py
@@ -6,7 +6,7 @@ import json
import re
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import make_autoname
from pygments import highlight
from pygments.formatters import HtmlFormatter as HF
@@ -15,7 +15,7 @@ from pygments.lexers import PythonLexer as PL
from jcloud.jcloud.pagetype.app_release.app_release import AppRelease
-class AppReleaseApprovalRequest(Document):
+class AppReleaseApprovalRequest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app_release_difference/app_release_difference.py b/jcloud/jcloud/pagetype/app_release_difference/app_release_difference.py
index 3f7fab2..7909433 100644
--- a/jcloud/jcloud/pagetype/app_release_difference/app_release_difference.py
+++ b/jcloud/jcloud/pagetype/app_release_difference/app_release_difference.py
@@ -7,13 +7,13 @@ import json
import re
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from github import Github
from jcloud.api.github import get_access_token
-class AppReleaseDifference(Document):
+class AppReleaseDifference(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app_rename/app_rename.py b/jcloud/jcloud/pagetype/app_rename/app_rename.py
index 9eb478e..05487f8 100644
--- a/jcloud/jcloud/pagetype/app_rename/app_rename.py
+++ b/jcloud/jcloud/pagetype/app_rename/app_rename.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppRename(Document):
+class AppRename(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app_source/app_source.json b/jcloud/jcloud/pagetype/app_source/app_source.json
index 2811e4e..050b340 100644
--- a/jcloud/jcloud/pagetype/app_source/app_source.json
+++ b/jcloud/jcloud/pagetype/app_source/app_source.json
@@ -2,7 +2,6 @@
"actions": [],
"allow_rename": 1,
"creation": "2022-01-28 20:07:40.451028",
- "pagetype": "PageType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -166,15 +165,16 @@
"index_web_pages_for_search": 1,
"links": [
{
- "link_pagetype": "Error Log",
- "link_fieldname": "reference_name"
+ "link_fieldname": "reference_name",
+ "link_pagetype": "Error Log"
}
],
- "modified": "2024-04-05 10:12:54.374115",
+ "modified": "2025-09-06 01:23:01.905284",
"modified_by": "Administrator",
"module": "Jcloud",
"name": "App Source",
"owner": "Administrator",
+ "pagetype": "PageType",
"permissions": [
{
"create": 1,
@@ -201,6 +201,7 @@
"write": 1
}
],
+ "row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/jcloud/jcloud/pagetype/app_source/app_source.py b/jcloud/jcloud/pagetype/app_source/app_source.py
index 7f11049..476ab65 100644
--- a/jcloud/jcloud/pagetype/app_source/app_source.py
+++ b/jcloud/jcloud/pagetype/app_source/app_source.py
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, List, Optional
import jingrow
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import make_autoname
from jcloud.api.github import get_access_token, get_auth_headers
from jcloud.overrides import get_permission_query_conditions_for_pagetype
@@ -17,22 +17,22 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.app_release.app_release import AppRelease
-class AppSource(Document):
+class AppSource(Page):
# 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
from jcloud.jcloud.pagetype.app_source_version.app_source_version import AppSourceVersion
+ from jingrow.types import DF
app: DF.Link
app_title: DF.Data
branch: DF.Data
enabled: DF.Check
- jingrow: DF.Check
github_installation_id: DF.Data | None
+ jingrow: DF.Check
last_github_poll_failed: DF.Check
last_github_response: DF.Code | None
last_synced: DF.Datetime | None
@@ -93,7 +93,7 @@ class AppSource(Document):
versions.add(row.version)
def before_save(self):
- # Assumes repository_url looks like http://git.jingrow.com:3000/jingrow/jerp
+ # Assumes repository_url looks like http://git.jingrow.com/jingrow/jerp
self.repository_url = self.repository_url.removesuffix(".git")
_, self.repository_owner, self.repository = self.repository_url.rsplit("/", 2)
@@ -198,7 +198,7 @@ class AppSource(Document):
def poll_gitea(self, commit_hash: None | str = None) -> requests.Response:
"""专门用于Gitea API的请求函数"""
headers = self.get_auth_headers()
- git_url = jingrow.db.get_single_value("Jcloud Settings", "git_url") or "http://git.jingrow.com:3000"
+ git_url = jingrow.db.get_single_value("Jcloud Settings", "git_url") or "http://git.jingrow.com"
url = f"{git_url}/api/v1/repos/{self.repository_owner}/{self.repository}"
if commit_hash:
@@ -237,24 +237,54 @@ class AppSource(Document):
)
def get_auth_headers(self) -> dict:
- return get_auth_headers(self.github_installation_id)
+ settings = jingrow.get_single("Jcloud Settings")
+ git_service_type = settings.get("git_service_type") or "gitea"
+
+ if git_service_type.lower() == "gitea":
+ git_access_token = settings.get_password("git_access_token")
+ if git_access_token:
+ return {"Authorization": f"token {git_access_token}"}
+
+ if git_service_type.lower() == "github" and self.github_installation_id:
+ return get_auth_headers(self.github_installation_id)
+
+ return {}
def get_access_token(self) -> Optional[str]:
- if self.github_installation_id:
+ settings = jingrow.get_single("Jcloud Settings")
+ git_service_type = settings.get("git_service_type") or "gitea"
+
+ if git_service_type.lower() == "gitea":
+ git_access_token = settings.get_password("git_access_token")
+ if git_access_token:
+ return git_access_token
+
+ if git_service_type.lower() == "github" and self.github_installation_id:
return get_access_token(self.github_installation_id)
- return jingrow.get_value("Jcloud Settings", None, "github_access_token")
+ return settings.get_password("github_access_token")
def get_repo_url(self) -> str:
- if not self.github_installation_id:
+ settings = jingrow.get_single("Jcloud Settings")
+ if not settings:
return self.repository_url
-
- token = get_access_token(self.github_installation_id)
- if token is None:
- # Do not edit without updating deploy_notifications.py
- raise Exception("App installation token could not be fetched", self.app)
-
- return f"https://x-access-token:{token}@git.jingrow.com:3000/{self.repository_owner}/{self.repository}"
+
+ git_service_type = settings.get("git_service_type") or "gitea"
+
+ if git_service_type.lower() == "gitea":
+ git_username = settings.get("git_username")
+ git_access_token = settings.get_password("git_access_token")
+ if git_username and git_access_token:
+ return f"http://{git_username}:{git_access_token}@git.jingrow.com/{self.repository_owner}/{self.repository}"
+
+ if git_service_type.lower() == "github" and self.github_installation_id:
+ token = get_access_token(self.github_installation_id)
+ if token is None:
+ # Do not edit without updating deploy_notifications.py
+ raise Exception("App installation token could not be fetched", self.app)
+ return f"https://x-access-token:{token}@git.jingrow.com/{self.repository_owner}/{self.repository}"
+
+ return self.repository_url
def get_gitea_commit_info(self, commit_hash: None | str = None) -> tuple[str, dict, bool]:
"""专门处理Gitea API响应的函数"""
diff --git a/jcloud/jcloud/pagetype/app_source_version/app_source_version.py b/jcloud/jcloud/pagetype/app_source_version/app_source_version.py
index f9f5b0a..e3d00ac 100644
--- a/jcloud/jcloud/pagetype/app_source_version/app_source_version.py
+++ b/jcloud/jcloud/pagetype/app_source_version/app_source_version.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppSourceVersion(Document):
+class AppSourceVersion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/app_tag/app_tag.py b/jcloud/jcloud/pagetype/app_tag/app_tag.py
index 7b823dd..d8814e8 100644
--- a/jcloud/jcloud/pagetype/app_tag/app_tag.py
+++ b/jcloud/jcloud/pagetype/app_tag/app_tag.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppTag(Document):
+class AppTag(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/audit_log/audit_log.py b/jcloud/jcloud/pagetype/audit_log/audit_log.py
index 04f9f3d..509d0dc 100644
--- a/jcloud/jcloud/pagetype/audit_log/audit_log.py
+++ b/jcloud/jcloud/pagetype/audit_log/audit_log.py
@@ -3,12 +3,12 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.telegram_message.telegram_message import TelegramMessage
-class AuditLog(Document):
+class AuditLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/aws_savings_plan_recommendation/aws_savings_plan_recommendation.py b/jcloud/jcloud/pagetype/aws_savings_plan_recommendation/aws_savings_plan_recommendation.py
index fa76d3f..5f2f3b4 100644
--- a/jcloud/jcloud/pagetype/aws_savings_plan_recommendation/aws_savings_plan_recommendation.py
+++ b/jcloud/jcloud/pagetype/aws_savings_plan_recommendation/aws_savings_plan_recommendation.py
@@ -5,7 +5,7 @@ from __future__ import annotations
import boto3
import jingrow
import jingrow.utils
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cint, flt
from jcloud.jcloud.pagetype.telegram_message.telegram_message import TelegramMessage
@@ -14,7 +14,7 @@ from jcloud.utils import log_error
AWS_HOURS_IN_A_MONTH = 730
-class AWSSavingsPlanRecommendation(Document):
+class AWSSavingsPlanRecommendation(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/backup_bucket/backup_bucket.py b/jcloud/jcloud/pagetype/backup_bucket/backup_bucket.py
index 4d8fe50..20d5293 100644
--- a/jcloud/jcloud/pagetype/backup_bucket/backup_bucket.py
+++ b/jcloud/jcloud/pagetype/backup_bucket/backup_bucket.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BackupBucket(Document):
+class BackupBucket(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/backup_restoration_test/backup_restoration_test.py b/jcloud/jcloud/pagetype/backup_restoration_test/backup_restoration_test.py
index 7c1d27f..e8d8919 100644
--- a/jcloud/jcloud/pagetype/backup_restoration_test/backup_restoration_test.py
+++ b/jcloud/jcloud/pagetype/backup_restoration_test/backup_restoration_test.py
@@ -2,13 +2,13 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.site import _new
from jcloud.jcloud.pagetype.site.site import prepare_site
-class BackupRestorationTest(Document):
+class BackupRestorationTest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/balance_transaction/balance_transaction.py b/jcloud/jcloud/pagetype/balance_transaction/balance_transaction.py
index 86e3aca..53131b4 100644
--- a/jcloud/jcloud/pagetype/balance_transaction/balance_transaction.py
+++ b/jcloud/jcloud/pagetype/balance_transaction/balance_transaction.py
@@ -3,12 +3,12 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.overrides import get_permission_query_conditions_for_pagetype
-class BalanceTransaction(Document):
+class BalanceTransaction(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/balance_transaction_allocation/balance_transaction_allocation.py b/jcloud/jcloud/pagetype/balance_transaction_allocation/balance_transaction_allocation.py
index d3f0969..7a00b60 100644
--- a/jcloud/jcloud/pagetype/balance_transaction_allocation/balance_transaction_allocation.py
+++ b/jcloud/jcloud/pagetype/balance_transaction_allocation/balance_transaction_allocation.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BalanceTransactionAllocation(Document):
+class BalanceTransactionAllocation(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench/bench.py b/jcloud/jcloud/pagetype/bench/bench.py
index 9522c39..e6d3c3f 100644
--- a/jcloud/jcloud/pagetype/bench/bench.py
+++ b/jcloud/jcloud/pagetype/bench/bench.py
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING, Generator, Iterable, Literal
import jingrow
import pytz
from jingrow.exceptions import DoesNotExistError
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import append_number_if_name_exists, make_autoname
from jingrow.utils import get_system_timezone
@@ -51,7 +51,7 @@ if TYPE_CHECKING:
]
-class Bench(Document):
+class Bench(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_app/bench_app.py b/jcloud/jcloud/pagetype/bench_app/bench_app.py
index 2326fb1..df53fd0 100644
--- a/jcloud/jcloud/pagetype/bench_app/bench_app.py
+++ b/jcloud/jcloud/pagetype/bench_app/bench_app.py
@@ -3,10 +3,10 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchApp(Document):
+class BenchApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_dependency/bench_dependency.py b/jcloud/jcloud/pagetype/bench_dependency/bench_dependency.py
index 9bafa4a..0b67a04 100644
--- a/jcloud/jcloud/pagetype/bench_dependency/bench_dependency.py
+++ b/jcloud/jcloud/pagetype/bench_dependency/bench_dependency.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchDependency(Document):
+class BenchDependency(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_dependency_version/bench_dependency_version.py b/jcloud/jcloud/pagetype/bench_dependency_version/bench_dependency_version.py
index 60116ac..ecce80a 100644
--- a/jcloud/jcloud/pagetype/bench_dependency_version/bench_dependency_version.py
+++ b/jcloud/jcloud/pagetype/bench_dependency_version/bench_dependency_version.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchDependencyVersion(Document):
+class BenchDependencyVersion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_get_app_cache/bench_get_app_cache.py b/jcloud/jcloud/pagetype/bench_get_app_cache/bench_get_app_cache.py
index f28c40f..26794ac 100644
--- a/jcloud/jcloud/pagetype/bench_get_app_cache/bench_get_app_cache.py
+++ b/jcloud/jcloud/pagetype/bench_get_app_cache/bench_get_app_cache.py
@@ -4,13 +4,13 @@
from datetime import datetime
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.deploy_candidate.cache_utils import run_command_in_docker_cache
from jcloud.utils import ttl_cache
-class BenchGetAppCache(Document):
+class BenchGetAppCache(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -34,7 +34,7 @@ class BenchGetAppCache(Document):
def load_from_db(self):
db = {v.name: v for v in BenchGetAppCache.get_data()}
- return super(Document, self).__init__(db[self.name])
+ return super(Page, self).__init__(db[self.name])
def delete(self):
run_command_in_docker_cache(f"rm bench/apps/{self.file_name}")
diff --git a/jcloud/jcloud/pagetype/bench_mount/bench_mount.py b/jcloud/jcloud/pagetype/bench_mount/bench_mount.py
index 5c93220..d46c761 100644
--- a/jcloud/jcloud/pagetype/bench_mount/bench_mount.py
+++ b/jcloud/jcloud/pagetype/bench_mount/bench_mount.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchMount(Document):
+class BenchMount(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_shell/bench_shell.py b/jcloud/jcloud/pagetype/bench_shell/bench_shell.py
index 2ab6a37..3321538 100644
--- a/jcloud/jcloud/pagetype/bench_shell/bench_shell.py
+++ b/jcloud/jcloud/pagetype/bench_shell/bench_shell.py
@@ -5,13 +5,13 @@ import json
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
if TYPE_CHECKING:
from jcloud.jcloud.pagetype.bench.bench import Bench
-class BenchShell(Document):
+class BenchShell(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_shell_log/bench_shell_log.py b/jcloud/jcloud/pagetype/bench_shell_log/bench_shell_log.py
index 077aa7e..d7e526e 100644
--- a/jcloud/jcloud/pagetype/bench_shell_log/bench_shell_log.py
+++ b/jcloud/jcloud/pagetype/bench_shell_log/bench_shell_log.py
@@ -5,7 +5,7 @@ from datetime import datetime
from typing import Optional, TypedDict
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
ExecuteResult = TypedDict(
"ExecuteResult",
@@ -23,7 +23,7 @@ ExecuteResult = TypedDict(
)
-class BenchShellLog(Document):
+class BenchShellLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_site_update/bench_site_update.py b/jcloud/jcloud/pagetype/bench_site_update/bench_site_update.py
index e842c42..2f73a43 100644
--- a/jcloud/jcloud/pagetype/bench_site_update/bench_site_update.py
+++ b/jcloud/jcloud/pagetype/bench_site_update/bench_site_update.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchSiteUpdate(Document):
+class BenchSiteUpdate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_update/bench_update.py b/jcloud/jcloud/pagetype/bench_update/bench_update.py
index 9c27fa5..b185426 100644
--- a/jcloud/jcloud/pagetype/bench_update/bench_update.py
+++ b/jcloud/jcloud/pagetype/bench_update/bench_update.py
@@ -6,7 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import get_current_team
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.bench.bench import Bench
-class BenchUpdate(Document):
+class BenchUpdate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_update_app/bench_update_app.py b/jcloud/jcloud/pagetype/bench_update_app/bench_update_app.py
index 0c47c59..e88b42c 100644
--- a/jcloud/jcloud/pagetype/bench_update_app/bench_update_app.py
+++ b/jcloud/jcloud/pagetype/bench_update_app/bench_update_app.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchUpdateApp(Document):
+class BenchUpdateApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/bench_variable/bench_variable.py b/jcloud/jcloud/pagetype/bench_variable/bench_variable.py
index b882a1d..4e03255 100644
--- a/jcloud/jcloud/pagetype/bench_variable/bench_variable.py
+++ b/jcloud/jcloud/pagetype/bench_variable/bench_variable.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BenchVariable(Document):
+class BenchVariable(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/blocked_domain/blocked_domain.py b/jcloud/jcloud/pagetype/blocked_domain/blocked_domain.py
index b1ff06f..c95419c 100644
--- a/jcloud/jcloud/pagetype/blocked_domain/blocked_domain.py
+++ b/jcloud/jcloud/pagetype/blocked_domain/blocked_domain.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class BlockedDomain(Document):
+class BlockedDomain(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/build_cache_shell/build_cache_shell.py b/jcloud/jcloud/pagetype/build_cache_shell/build_cache_shell.py
index b03ddfb..d0b4226 100644
--- a/jcloud/jcloud/pagetype/build_cache_shell/build_cache_shell.py
+++ b/jcloud/jcloud/pagetype/build_cache_shell/build_cache_shell.py
@@ -4,12 +4,12 @@
import json
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
-class BuildCacheShell(Document):
+class BuildCacheShell(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/certificate_authority/certificate_authority.py b/jcloud/jcloud/pagetype/certificate_authority/certificate_authority.py
index 499dfb3..f1cc3a7 100644
--- a/jcloud/jcloud/pagetype/certificate_authority/certificate_authority.py
+++ b/jcloud/jcloud/pagetype/certificate_authority/certificate_authority.py
@@ -13,12 +13,12 @@ from pathlib import Path
import jingrow
import OpenSSL
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import developer_mode_only
-class CertificateAuthority(Document):
+class CertificateAuthority(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/child_team_member/child_team_member.py b/jcloud/jcloud/pagetype/child_team_member/child_team_member.py
index b21dc49..ed5e86c 100644
--- a/jcloud/jcloud/pagetype/child_team_member/child_team_member.py
+++ b/jcloud/jcloud/pagetype/child_team_member/child_team_member.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ChildTeamMember(Document):
+class ChildTeamMember(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/cloud_region/cloud_region.py b/jcloud/jcloud/pagetype/cloud_region/cloud_region.py
index 47b4c9f..7e8ea48 100644
--- a/jcloud/jcloud/pagetype/cloud_region/cloud_region.py
+++ b/jcloud/jcloud/pagetype/cloud_region/cloud_region.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class CloudRegion(Document):
+class CloudRegion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/cluster/cluster.py b/jcloud/jcloud/pagetype/cluster/cluster.py
index 2a4885a..985311e 100644
--- a/jcloud/jcloud/pagetype/cluster/cluster.py
+++ b/jcloud/jcloud/pagetype/cluster/cluster.py
@@ -13,7 +13,7 @@ from typing import ClassVar, Generator
import boto3
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from hcloud import APIException, Client
from hcloud.networks.domain import NetworkSubnet
from oci.config import validate_config
@@ -43,7 +43,7 @@ if typing.TYPE_CHECKING:
from jcloud.jcloud.pagetype.virtual_machine.virtual_machine import VirtualMachine
-class Cluster(Document):
+class Cluster(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -235,7 +235,7 @@ class Cluster(Document):
@property
def images_available(self) -> float:
- return len(self.get_same_region_vmis()) / len(self.server_doctypes)
+ return len(self.get_same_region_vmis()) / len(self.server_pagetypes)
def validate_cidr_block(self):
if not self.cidr_block:
@@ -672,18 +672,18 @@ class Cluster(Document):
return VirtualMachineImage.get_available_for_series(series, self.region, platform=platform)
@property
- def server_doctypes(self):
- server_doctypes = {**self.base_servers}
+ def server_pagetypes(self):
+ server_pagetypes = {**self.base_servers}
if not self.public:
- server_doctypes = {**server_doctypes, **self.private_servers}
- return server_doctypes
+ server_pagetypes = {**server_pagetypes, **self.private_servers}
+ return server_pagetypes
def get_same_region_vmis(self, get_series=False):
return jingrow.get_all(
"Virtual Machine Image",
filters={
"region": self.region,
- "series": ("in", list(self.server_doctypes.values())),
+ "series": ("in", list(self.server_pagetypes.values())),
"status": "Available",
},
pluck="name" if not get_series else "series",
@@ -691,7 +691,7 @@ class Cluster(Document):
def get_other_region_vmis(self, get_series=False):
vmis = []
- for series in list(self.server_doctypes.values()):
+ for series in list(self.server_pagetypes.values()):
vmis.extend(
jingrow.get_all(
"Virtual Machine Image",
diff --git a/jcloud/jcloud/pagetype/code_server/code_server.py b/jcloud/jcloud/pagetype/code_server/code_server.py
index 560e55a..2b6dc0c 100644
--- a/jcloud/jcloud/pagetype/code_server/code_server.py
+++ b/jcloud/jcloud/pagetype/code_server/code_server.py
@@ -2,7 +2,7 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import append_number_if_name_exists
from jcloud.agent import Agent
@@ -10,7 +10,7 @@ from jcloud.utils import log_error
from jcloud.utils.dns import _change_dns_record, create_dns_record
-class CodeServer(Document):
+class CodeServer(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/communication_email/communication_email.py b/jcloud/jcloud/pagetype/communication_email/communication_email.py
index 50bd7f4..4b3d4f1 100644
--- a/jcloud/jcloud/pagetype/communication_email/communication_email.py
+++ b/jcloud/jcloud/pagetype/communication_email/communication_email.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class CommunicationEmail(Document):
+class CommunicationEmail(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/cookie_preference_log/cookie_preference_log.py b/jcloud/jcloud/pagetype/cookie_preference_log/cookie_preference_log.py
index bafd80e..b86f1a8 100644
--- a/jcloud/jcloud/pagetype/cookie_preference_log/cookie_preference_log.py
+++ b/jcloud/jcloud/pagetype/cookie_preference_log/cookie_preference_log.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class CookiePreferenceLog(Document):
+class CookiePreferenceLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/currency_exchange/currency_exchange.py b/jcloud/jcloud/pagetype/currency_exchange/currency_exchange.py
index 47777c8..329034c 100644
--- a/jcloud/jcloud/pagetype/currency_exchange/currency_exchange.py
+++ b/jcloud/jcloud/pagetype/currency_exchange/currency_exchange.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class CurrencyExchange(Document):
+class CurrencyExchange(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/dashboard_banner/dashboard_banner.py b/jcloud/jcloud/pagetype/dashboard_banner/dashboard_banner.py
index d98d1ee..5f34dc8 100644
--- a/jcloud/jcloud/pagetype/dashboard_banner/dashboard_banner.py
+++ b/jcloud/jcloud/pagetype/dashboard_banner/dashboard_banner.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DashboardBanner(Document):
+class DashboardBanner(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/database_server_mariadb_variable/database_server_mariadb_variable.py b/jcloud/jcloud/pagetype/database_server_mariadb_variable/database_server_mariadb_variable.py
index 20d489c..da81978 100644
--- a/jcloud/jcloud/pagetype/database_server_mariadb_variable/database_server_mariadb_variable.py
+++ b/jcloud/jcloud/pagetype/database_server_mariadb_variable/database_server_mariadb_variable.py
@@ -4,13 +4,13 @@
from typing import Any
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.runner import Ansible
from jcloud.utils import log_error
-class DatabaseServerMariaDBVariable(Document):
+class DatabaseServerMariaDBVariable(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy/deploy.py b/jcloud/jcloud/pagetype/deploy/deploy.py
index acdec32..0eb30b9 100644
--- a/jcloud/jcloud/pagetype/deploy/deploy.py
+++ b/jcloud/jcloud/pagetype/deploy/deploy.py
@@ -4,14 +4,14 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import append_number_if_name_exists
from jcloud.overrides import get_permission_query_conditions_for_pagetype
from jcloud.utils import log_error
-class Deploy(Document):
+class Deploy(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_bench/deploy_bench.py b/jcloud/jcloud/pagetype/deploy_bench/deploy_bench.py
index eb47788..1c67eff 100644
--- a/jcloud/jcloud/pagetype/deploy_bench/deploy_bench.py
+++ b/jcloud/jcloud/pagetype/deploy_bench/deploy_bench.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployBench(Document):
+class DeployBench(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate/deploy_candidate.py b/jcloud/jcloud/pagetype/deploy_candidate/deploy_candidate.py
index 111977a..cb0802d 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate/deploy_candidate.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate/deploy_candidate.py
@@ -21,7 +21,7 @@ from typing import Any, Literal
import jingrow
import jingrow.utils
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import make_autoname
from jingrow.utils import now_datetime as now
from jingrow.utils import rounded
@@ -64,7 +64,7 @@ if typing.TYPE_CHECKING:
from jcloud.jcloud.pagetype.release_group.release_group import ReleaseGroup
-class DeployCandidate(Document):
+class DeployCandidate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate/deploy_notifications.py b/jcloud/jcloud/pagetype/deploy_candidate/deploy_notifications.py
index d798c6d..7cde2aa 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate/deploy_notifications.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate/deploy_notifications.py
@@ -18,7 +18,7 @@ from jcloud.jcloud.pagetype.deploy_candidate.utils import (
Used to create notifications if the Deploy error is something that can
be handled by the user.
-Ref: http://git.jingrow.com:3000/jingrow/jcloud/pull/1544
+Ref: http://git.jingrow.com/jingrow/jcloud/pull/1544
To handle an error:
1. Create a pg page that helps the user get out of it under: jingrow.com/docs/common-issues
@@ -41,7 +41,7 @@ Details = TypedDict(
MatchStrings = str | list[str]
if typing.TYPE_CHECKING:
- from jingrow import Document
+ from jingrow import Page
from jcloud.jcloud.pagetype.deploy_candidate.deploy_candidate import DeployCandidate
from jcloud.jcloud.pagetype.deploy_candidate_app.deploy_candidate_app import (
@@ -1019,7 +1019,7 @@ def get_ct_row(
match_value: str,
field: str,
ct_field: str,
-) -> Optional["Document"]:
+) -> Optional["Page"]:
ct = dc.get(field)
if not ct:
return
diff --git a/jcloud/jcloud/pagetype/deploy_candidate/test_deploy_candidate.py b/jcloud/jcloud/pagetype/deploy_candidate/test_deploy_candidate.py
index ddba7fc..3498ec8 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate/test_deploy_candidate.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate/test_deploy_candidate.py
@@ -316,14 +316,14 @@ def create_cache_test_release_group(
def create_cache_test_apps(team: "Team") -> dict[str, "AppInfo"]:
info = [
(
- "http://git.jingrow.com:3000/jingrow/jingrow",
+ "http://git.jingrow.com/jingrow/jingrow",
"Jingrow Framework",
"v0.1",
"develop",
"d26c67df75a95ef43d329eadd48d7998ea656856",
),
(
- "http://git.jingrow.com:3000/jingrow/wiki",
+ "http://git.jingrow.com/jingrow/wiki",
"Jingrow Wiki",
"v0.1",
"master",
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_app/deploy_candidate_app.py b/jcloud/jcloud/pagetype/deploy_candidate_app/deploy_candidate_app.py
index 3577fd3..6915510 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_app/deploy_candidate_app.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_app/deploy_candidate_app.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployCandidateApp(Document):
+class DeployCandidateApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_build_step/deploy_candidate_build_step.py b/jcloud/jcloud/pagetype/deploy_candidate_build_step/deploy_candidate_build_step.py
index 06c3b9c..18c769e 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_build_step/deploy_candidate_build_step.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_build_step/deploy_candidate_build_step.py
@@ -4,10 +4,10 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployCandidateBuildStep(Document):
+class DeployCandidateBuildStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_dependency/deploy_candidate_dependency.py b/jcloud/jcloud/pagetype/deploy_candidate_dependency/deploy_candidate_dependency.py
index 2f8a1ed..aa81e23 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_dependency/deploy_candidate_dependency.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_dependency/deploy_candidate_dependency.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployCandidateDependency(Document):
+class DeployCandidateDependency(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_difference/deploy_candidate_difference.py b/jcloud/jcloud/pagetype/deploy_candidate_difference/deploy_candidate_difference.py
index 8e57c1c..cae6270 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_difference/deploy_candidate_difference.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_difference/deploy_candidate_difference.py
@@ -5,12 +5,12 @@
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.overrides import get_permission_query_conditions_for_pagetype
-class DeployCandidateDifference(Document):
+class DeployCandidateDifference(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_difference_app/deploy_candidate_difference_app.py b/jcloud/jcloud/pagetype/deploy_candidate_difference_app/deploy_candidate_difference_app.py
index 6c0484b..95f826f 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_difference_app/deploy_candidate_difference_app.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_difference_app/deploy_candidate_difference_app.py
@@ -4,10 +4,10 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployCandidateDifferenceApp(Document):
+class DeployCandidateDifferenceApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_package/deploy_candidate_package.py b/jcloud/jcloud/pagetype/deploy_candidate_package/deploy_candidate_package.py
index 95a5798..c1ce888 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_package/deploy_candidate_package.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_package/deploy_candidate_package.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployCandidatePackage(Document):
+class DeployCandidatePackage(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/deploy_candidate_variable/deploy_candidate_variable.py b/jcloud/jcloud/pagetype/deploy_candidate_variable/deploy_candidate_variable.py
index 95cd5c8..92f75e3 100644
--- a/jcloud/jcloud/pagetype/deploy_candidate_variable/deploy_candidate_variable.py
+++ b/jcloud/jcloud/pagetype/deploy_candidate_variable/deploy_candidate_variable.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeployCandidateVariable(Document):
+class DeployCandidateVariable(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/dns_resolution/__init__.py b/jcloud/jcloud/pagetype/dns_resolution/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/dns_resolution/dns_resolution.json b/jcloud/jcloud/pagetype/dns_resolution/dns_resolution.json
new file mode 100644
index 0000000..7914e6f
--- /dev/null
+++ b/jcloud/jcloud/pagetype/dns_resolution/dns_resolution.json
@@ -0,0 +1,96 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-07-31 19:34:32.233545",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "record_id",
+ "host",
+ "type",
+ "line",
+ "value",
+ "ttl",
+ "level",
+ "record_status"
+ ],
+ "fields": [
+ {
+ "columns": 1,
+ "fieldname": "host",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "主机名",
+ "reqd": 1
+ },
+ {
+ "columns": 1,
+ "fieldname": "type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "类型",
+ "options": "\nA\nAAAA\nCNAME\nMX\nNS\nTXT\nSRV",
+ "reqd": 1
+ },
+ {
+ "columns": 4,
+ "fieldname": "value",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "对应值",
+ "reqd": 1
+ },
+ {
+ "columns": 1,
+ "default": "600",
+ "fieldname": "ttl",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "TTL"
+ },
+ {
+ "columns": 1,
+ "fieldname": "level",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "优先级"
+ },
+ {
+ "columns": 1,
+ "fieldname": "line",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "线路",
+ "options": "\nLTEL\nLCNC\nLMOB\nLEDU\nLSEO"
+ },
+ {
+ "columns": 1,
+ "fieldname": "record_status",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "状态"
+ },
+ {
+ "columns": 1,
+ "fieldname": "record_id",
+ "fieldtype": "Data",
+ "label": "记录ID",
+ "read_only": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2025-08-05 03:04:50.722407",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Dns Resolution",
+ "owner": "Administrator",
+ "pagetype": "PageType",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/dns_resolution/dns_resolution.py b/jcloud/jcloud/pagetype/dns_resolution/dns_resolution.py
new file mode 100644
index 0000000..a189a4a
--- /dev/null
+++ b/jcloud/jcloud/pagetype/dns_resolution/dns_resolution.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class DnsResolution(Page):
+ # 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
+
+ host: DF.Data
+ level: DF.Data | None
+ line: DF.Literal["", "LTEL", "LCNC", "LMOB", "LEDU", "LSEO"]
+ parent: DF.Data
+ parentfield: DF.Data
+ parenttype: DF.Data
+ record_id: DF.Data | None
+ record_status: DF.Data | None
+ ttl: DF.Data | None
+ type: DF.Literal["", "A", "AAAA", "CNAME", "MX", "NS", "TXT", "SRV"]
+ value: DF.Data
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/domain_owner/__init__.py b/jcloud/jcloud/pagetype/domain_owner/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/domain_owner/domain_owner.js b/jcloud/jcloud/pagetype/domain_owner/domain_owner.js
new file mode 100644
index 0000000..f928302
--- /dev/null
+++ b/jcloud/jcloud/pagetype/domain_owner/domain_owner.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+// jingrow.ui.form.on("Domain Owner", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/jcloud/jcloud/pagetype/domain_owner/domain_owner.json b/jcloud/jcloud/pagetype/domain_owner/domain_owner.json
new file mode 100644
index 0000000..698233a
--- /dev/null
+++ b/jcloud/jcloud/pagetype/domain_owner/domain_owner.json
@@ -0,0 +1,269 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-08-01 13:48:37.031674",
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "c_sysid",
+ "fullname",
+ "c_co",
+ "cocode",
+ "c_em",
+ "reg_contact_type",
+ "c_idtype_gswl",
+ "c_idnum_gswl",
+ "column_break_rqiv",
+ "c_regtype",
+ "c_ph_type",
+ "c_ph",
+ "c_ph_code",
+ "c_ph_num",
+ "c_ph_fj",
+ "c_pc",
+ "team",
+ "r_status",
+ "c_status",
+ "section_break_ssfs",
+ "c_org_m",
+ "c_ln_m",
+ "c_fn_m",
+ "c_st_m",
+ "c_ct_m",
+ "c_dt_m",
+ "c_adr_m",
+ "column_break_cqxi",
+ "c_org",
+ "c_fn",
+ "c_ln",
+ "c_st",
+ "c_ct",
+ "c_adr"
+ ],
+ "fields": [
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title"
+ },
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "label": "团队",
+ "options": "Team"
+ },
+ {
+ "fieldname": "c_regtype",
+ "fieldtype": "Select",
+ "label": "所有者类型",
+ "options": "\nI\nE"
+ },
+ {
+ "fieldname": "c_org_m",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "(中文)所有者单位名称"
+ },
+ {
+ "fieldname": "fullname",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "姓名"
+ },
+ {
+ "fieldname": "c_ln_m",
+ "fieldtype": "Data",
+ "label": "(中文)联系人姓"
+ },
+ {
+ "fieldname": "c_fn_m",
+ "fieldtype": "Data",
+ "label": "(中文)联系人名"
+ },
+ {
+ "fieldname": "c_co",
+ "fieldtype": "Data",
+ "label": "所属国家或地区简称"
+ },
+ {
+ "fieldname": "cocode",
+ "fieldtype": "Data",
+ "label": "国家或地区电话代码"
+ },
+ {
+ "fieldname": "c_st_m",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "(中文)所属省"
+ },
+ {
+ "fieldname": "c_ct_m",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "(中文)所属市"
+ },
+ {
+ "fieldname": "c_dt_m",
+ "fieldtype": "Data",
+ "label": "(中文)所属县"
+ },
+ {
+ "fieldname": "c_adr_m",
+ "fieldtype": "Data",
+ "label": "(中文)通讯地址"
+ },
+ {
+ "fieldname": "c_pc",
+ "fieldtype": "Data",
+ "label": "邮编"
+ },
+ {
+ "default": "0",
+ "description": "0:手机,1:座机",
+ "fieldname": "c_ph_type",
+ "fieldtype": "Select",
+ "label": "联系电话类型",
+ "options": "0\n1"
+ },
+ {
+ "fieldname": "c_ph",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "手机号码"
+ },
+ {
+ "fieldname": "c_ph_code",
+ "fieldtype": "Data",
+ "label": "座机号码区号"
+ },
+ {
+ "fieldname": "c_ph_num",
+ "fieldtype": "Data",
+ "label": "座机号码"
+ },
+ {
+ "fieldname": "c_ph_fj",
+ "fieldtype": "Data",
+ "label": "座机号码分机号"
+ },
+ {
+ "fieldname": "c_em",
+ "fieldtype": "Data",
+ "label": "电子邮箱"
+ },
+ {
+ "fieldname": "c_org",
+ "fieldtype": "Data",
+ "label": "(英文)所有者单位名称"
+ },
+ {
+ "fieldname": "c_ln",
+ "fieldtype": "Data",
+ "label": "(英文)联系人名",
+ "length": 50
+ },
+ {
+ "fieldname": "c_fn",
+ "fieldtype": "Data",
+ "label": "(英文)联系人姓",
+ "length": 50
+ },
+ {
+ "fieldname": "c_st",
+ "fieldtype": "Data",
+ "label": "(英文)省份",
+ "length": 50
+ },
+ {
+ "fieldname": "c_ct",
+ "fieldtype": "Data",
+ "label": "(英文)城市",
+ "length": 50
+ },
+ {
+ "fieldname": "c_adr",
+ "fieldtype": "Data",
+ "label": "(英文)通讯地址",
+ "length": 150
+ },
+ {
+ "fieldname": "column_break_rqiv",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_ssfs",
+ "fieldtype": "Section Break",
+ "label": "联系信息"
+ },
+ {
+ "fieldname": "column_break_cqxi",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "cg",
+ "fieldname": "reg_contact_type",
+ "fieldtype": "Select",
+ "label": "适用后缀",
+ "options": "\ncg\nhk\ntw\njingwai"
+ },
+ {
+ "fieldname": "c_idtype_gswl",
+ "fieldtype": "Data",
+ "label": "实名证件"
+ },
+ {
+ "fieldname": "c_idnum_gswl",
+ "fieldtype": "Data",
+ "label": "证件号码"
+ },
+ {
+ "fieldname": "c_sysid",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "模板标识"
+ },
+ {
+ "fieldname": "r_status",
+ "fieldtype": "Select",
+ "label": "实名认证",
+ "options": "0\n1\n2\n3\n4\n5"
+ },
+ {
+ "fieldname": "c_status",
+ "fieldtype": "Select",
+ "label": "初审状态",
+ "options": "0\n1\n2\n3\n4"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2025-08-05 16:44:31.926846",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Domain Owner",
+ "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
+ }
+ ],
+ "row_format": "Dynamic",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/domain_owner/domain_owner.py b/jcloud/jcloud/pagetype/domain_owner/domain_owner.py
new file mode 100644
index 0000000..81f3763
--- /dev/null
+++ b/jcloud/jcloud/pagetype/domain_owner/domain_owner.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class DomainOwner(Page):
+ # 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
+
+ c_adr: DF.Data | None
+ c_adr_m: DF.Data | None
+ c_co: DF.Data | None
+ c_ct: DF.Data | None
+ c_ct_m: DF.Data | None
+ c_dt_m: DF.Data | None
+ c_em: DF.Data | None
+ c_fn: DF.Data | None
+ c_fn_m: DF.Data | None
+ c_idnum_gswl: DF.Data | None
+ c_idtype_gswl: DF.Data | None
+ c_ln: DF.Data | None
+ c_ln_m: DF.Data | None
+ c_org: DF.Data | None
+ c_org_m: DF.Data | None
+ c_pc: DF.Data | None
+ c_ph: DF.Data | None
+ c_ph_code: DF.Data | None
+ c_ph_fj: DF.Data | None
+ c_ph_num: DF.Data | None
+ c_ph_type: DF.Literal["0", "1"]
+ c_regtype: DF.Literal["", "I", "E"]
+ c_st: DF.Data | None
+ c_st_m: DF.Data | None
+ c_status: DF.Literal["0", "1", "2", "3", "4"]
+ c_sysid: DF.Data | None
+ cocode: DF.Data | None
+ fullname: DF.Data | None
+ r_status: DF.Literal["0", "1", "2", "3", "4", "5"]
+ reg_contact_type: DF.Literal["", "cg", "hk", "tw", "jingwai"]
+ team: DF.Link | None
+ title: DF.Data | None
+ # end: auto-generated types
+
+ dashboard_fields = (
+ "fullname",
+ "title",
+ "c_fn",
+ "c_ln",
+ "c_em",
+ "c_ph",
+ "c_ph_num",
+ "c_ph_type",
+ "c_org",
+ "c_adr",
+ "c_ct",
+ "c_st",
+ "c_pc",
+ "c_regtype",
+ "reg_contact_type",
+ "team",
+ "cocode",
+ "c_idtype_gswl",
+ "c_idnum_gswl",
+ "c_sysid",
+ "r_status"
+ )
+
+ @staticmethod
+ def get_list_query(query):
+ DomainOwner = jingrow.qb.PageType("Domain Owner")
+ query = query.where(DomainOwner.team == jingrow.local.team().name)
+ return query.run(as_dict=True)
+
+ def get_pg(self, pg):
+ return pg
diff --git a/jcloud/jcloud/pagetype/domain_owner/test_domain_owner.py b/jcloud/jcloud/pagetype/domain_owner/test_domain_owner.py
new file mode 100644
index 0000000..db0bbe8
--- /dev/null
+++ b/jcloud/jcloud/pagetype/domain_owner/test_domain_owner.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestDomainOwner(JingrowTestCase):
+ pass
diff --git a/jcloud/jcloud/pagetype/drip_email/drip_email.py b/jcloud/jcloud/pagetype/drip_email/drip_email.py
index 5d7e9bc..ea2783c 100644
--- a/jcloud/jcloud/pagetype/drip_email/drip_email.py
+++ b/jcloud/jcloud/pagetype/drip_email/drip_email.py
@@ -9,13 +9,13 @@ import jingrow
import rq
import rq.exceptions
import rq.timeouts
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.make_random import get_random
from jcloud.utils import log_error
-class DripEmail(Document):
+class DripEmail(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/firewall_rules/__init__.py b/jcloud/jcloud/pagetype/firewall_rules/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/firewall_rules/firewall_rules.json b/jcloud/jcloud/pagetype/firewall_rules/firewall_rules.json
new file mode 100644
index 0000000..7e2509f
--- /dev/null
+++ b/jcloud/jcloud/pagetype/firewall_rules/firewall_rules.json
@@ -0,0 +1,64 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-08-09 17:32:59.586173",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "rule_id",
+ "rule_protocol",
+ "port",
+ "source_cidr_ip",
+ "remark"
+ ],
+ "fields": [
+ {
+ "fieldname": "port",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "端口范围"
+ },
+ {
+ "fieldname": "remark",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "备注"
+ },
+ {
+ "columns": 2,
+ "fieldname": "rule_id",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "规则ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "rule_protocol",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "协议",
+ "options": "TCP\nUDP\nICMP"
+ },
+ {
+ "fieldname": "source_cidr_ip",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "来源IP"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2025-08-09 19:45:50.104225",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Firewall Rules",
+ "owner": "Administrator",
+ "pagetype": "PageType",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/firewall_rules/firewall_rules.py b/jcloud/jcloud/pagetype/firewall_rules/firewall_rules.py
new file mode 100644
index 0000000..a1da0f6
--- /dev/null
+++ b/jcloud/jcloud/pagetype/firewall_rules/firewall_rules.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class FirewallRules(Page):
+ # 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
+
+ parent: DF.Data
+ parentfield: DF.Data
+ parenttype: DF.Data
+ port: DF.Data | None
+ remark: DF.Data | None
+ rule_id: DF.Data | None
+ rule_protocol: DF.Literal["TCP", "UDP", "ICMP"]
+ source_cidr_ip: DF.Data | None
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/github_webhook_log/github_webhook_log.py b/jcloud/jcloud/pagetype/github_webhook_log/github_webhook_log.py
index 7fcecb1..35326fa 100644
--- a/jcloud/jcloud/pagetype/github_webhook_log/github_webhook_log.py
+++ b/jcloud/jcloud/pagetype/github_webhook_log/github_webhook_log.py
@@ -9,7 +9,7 @@ import json
from typing import TYPE_CHECKING, Optional
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.query_builder import Interval
from jingrow.query_builder.functions import Now
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.app_source.app_source import AppSource
-class GitHubWebhookLog(Document):
+class GitHubWebhookLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/incident_alerts/incident_alerts.py b/jcloud/jcloud/pagetype/incident_alerts/incident_alerts.py
index 4f7356a..c6237c5 100644
--- a/jcloud/jcloud/pagetype/incident_alerts/incident_alerts.py
+++ b/jcloud/jcloud/pagetype/incident_alerts/incident_alerts.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class IncidentAlerts(Document):
+class IncidentAlerts(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/incident_settings/incident_settings.py b/jcloud/jcloud/pagetype/incident_settings/incident_settings.py
index 8d8ee60..e79b990 100644
--- a/jcloud/jcloud/pagetype/incident_settings/incident_settings.py
+++ b/jcloud/jcloud/pagetype/incident_settings/incident_settings.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class IncidentSettings(Document):
+class IncidentSettings(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/incident_settings_self_hosted_user/incident_settings_self_hosted_user.py b/jcloud/jcloud/pagetype/incident_settings_self_hosted_user/incident_settings_self_hosted_user.py
index 42bf6fd..d036395 100644
--- a/jcloud/jcloud/pagetype/incident_settings_self_hosted_user/incident_settings_self_hosted_user.py
+++ b/jcloud/jcloud/pagetype/incident_settings_self_hosted_user/incident_settings_self_hosted_user.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class IncidentSettingsSelfHostedUser(Document):
+class IncidentSettingsSelfHostedUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/incident_settings_user/incident_settings_user.py b/jcloud/jcloud/pagetype/incident_settings_user/incident_settings_user.py
index 9f912c7..5d0d646 100644
--- a/jcloud/jcloud/pagetype/incident_settings_user/incident_settings_user.py
+++ b/jcloud/jcloud/pagetype/incident_settings_user/incident_settings_user.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class IncidentSettingsUser(Document):
+class IncidentSettingsUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/incident_suggestion/incident_suggestion.py b/jcloud/jcloud/pagetype/incident_suggestion/incident_suggestion.py
index 99396e2..416a520 100644
--- a/jcloud/jcloud/pagetype/incident_suggestion/incident_suggestion.py
+++ b/jcloud/jcloud/pagetype/incident_suggestion/incident_suggestion.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class IncidentSuggestion(Document):
+class IncidentSuggestion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/incident_updates/incident_updates.py b/jcloud/jcloud/pagetype/incident_updates/incident_updates.py
index 1fda771..d70d5d0 100644
--- a/jcloud/jcloud/pagetype/incident_updates/incident_updates.py
+++ b/jcloud/jcloud/pagetype/incident_updates/incident_updates.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class IncidentUpdates(Document):
+class IncidentUpdates(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/invoice/invoice.py b/jcloud/jcloud/pagetype/invoice/invoice.py
index bebc1cf..6ed7f5b 100644
--- a/jcloud/jcloud/pagetype/invoice/invoice.py
+++ b/jcloud/jcloud/pagetype/invoice/invoice.py
@@ -5,7 +5,7 @@ from __future__ import annotations
import jingrow
from jingrow import _
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cint, flt, getdate
from jingrow.utils.data import fmt_money
@@ -20,7 +20,7 @@ from jcloud.utils.billing import (
)
-class Invoice(Document):
+class Invoice(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/invoice_credit_allocation/invoice_credit_allocation.py b/jcloud/jcloud/pagetype/invoice_credit_allocation/invoice_credit_allocation.py
index c4fac8e..7eb87cb 100644
--- a/jcloud/jcloud/pagetype/invoice_credit_allocation/invoice_credit_allocation.py
+++ b/jcloud/jcloud/pagetype/invoice_credit_allocation/invoice_credit_allocation.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class InvoiceCreditAllocation(Document):
+class InvoiceCreditAllocation(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/invoice_discount/invoice_discount.py b/jcloud/jcloud/pagetype/invoice_discount/invoice_discount.py
index 3cc5d16..a684bf6 100644
--- a/jcloud/jcloud/pagetype/invoice_discount/invoice_discount.py
+++ b/jcloud/jcloud/pagetype/invoice_discount/invoice_discount.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class InvoiceDiscount(Document):
+class InvoiceDiscount(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/invoice_item/invoice_item.py b/jcloud/jcloud/pagetype/invoice_item/invoice_item.py
index a126eed..4c645eb 100644
--- a/jcloud/jcloud/pagetype/invoice_item/invoice_item.py
+++ b/jcloud/jcloud/pagetype/invoice_item/invoice_item.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class InvoiceItem(Document):
+class InvoiceItem(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/invoice_transaction_fee/invoice_transaction_fee.py b/jcloud/jcloud/pagetype/invoice_transaction_fee/invoice_transaction_fee.py
index 7f5eb69..e560287 100644
--- a/jcloud/jcloud/pagetype/invoice_transaction_fee/invoice_transaction_fee.py
+++ b/jcloud/jcloud/pagetype/invoice_transaction_fee/invoice_transaction_fee.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class InvoiceTransactionFee(Document):
+class InvoiceTransactionFee(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_feedback/jcloud_feedback.py b/jcloud/jcloud/pagetype/jcloud_feedback/jcloud_feedback.py
index dfe3030..534e8eb 100644
--- a/jcloud/jcloud/pagetype/jcloud_feedback/jcloud_feedback.py
+++ b/jcloud/jcloud/pagetype/jcloud_feedback/jcloud_feedback.py
@@ -3,10 +3,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudFeedback(Document):
+class JcloudFeedback(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_job/jcloud_job.py b/jcloud/jcloud/pagetype/jcloud_job/jcloud_job.py
index dcac2e3..75ee74f 100644
--- a/jcloud/jcloud/pagetype/jcloud_job/jcloud_job.py
+++ b/jcloud/jcloud/pagetype/jcloud_job/jcloud_job.py
@@ -4,10 +4,10 @@
import json
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudJob(Document):
+class JcloudJob(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_job_step/jcloud_job_step.py b/jcloud/jcloud/pagetype/jcloud_job_step/jcloud_job_step.py
index 8838799..0d7e38e 100644
--- a/jcloud/jcloud/pagetype/jcloud_job_step/jcloud_job_step.py
+++ b/jcloud/jcloud/pagetype/jcloud_job_step/jcloud_job_step.py
@@ -4,11 +4,11 @@
import json
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.safe_exec import safe_exec
-class JcloudJobStep(Document):
+class JcloudJobStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_job_type/jcloud_job_type.py b/jcloud/jcloud/pagetype/jcloud_job_type/jcloud_job_type.py
index d959780..da7ae3c 100644
--- a/jcloud/jcloud/pagetype/jcloud_job_type/jcloud_job_type.py
+++ b/jcloud/jcloud/pagetype/jcloud_job_type/jcloud_job_type.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudJobType(Document):
+class JcloudJobType(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_job_type_step/jcloud_job_type_step.py b/jcloud/jcloud/pagetype/jcloud_job_type_step/jcloud_job_type_step.py
index 6fa461f..ae8e8c3 100644
--- a/jcloud/jcloud/pagetype/jcloud_job_type_step/jcloud_job_type_step.py
+++ b/jcloud/jcloud/pagetype/jcloud_job_type_step/jcloud_job_type_step.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudJobTypeStep(Document):
+class JcloudJobTypeStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_method_permission/jcloud_method_permission.py b/jcloud/jcloud/pagetype/jcloud_method_permission/jcloud_method_permission.py
index f29f4f0..92d95a3 100644
--- a/jcloud/jcloud/pagetype/jcloud_method_permission/jcloud_method_permission.py
+++ b/jcloud/jcloud/pagetype/jcloud_method_permission/jcloud_method_permission.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudMethodPermission(Document):
+class JcloudMethodPermission(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -24,11 +24,11 @@ class JcloudMethodPermission(Document):
def available_actions():
result = {}
- doctypes = jingrow.get_all(
+ pagetypes = jingrow.get_all(
"Jcloud Method Permission", pluck="document_type", distinct=True
)
- for pagetype in doctypes:
+ for pagetype in pagetypes:
result[pagetype] = {
perm["checkbox_label"]: perm["method"]
for perm in jingrow.get_all(
diff --git a/jcloud/jcloud/pagetype/jcloud_notification/jcloud_notification.py b/jcloud/jcloud/pagetype/jcloud_notification/jcloud_notification.py
index c3d38ae..2fa4613 100644
--- a/jcloud/jcloud/pagetype/jcloud_notification/jcloud_notification.py
+++ b/jcloud/jcloud/pagetype/jcloud_notification/jcloud_notification.py
@@ -3,12 +3,12 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import dashboard_whitelist
-class JcloudNotification(Document):
+class JcloudNotification(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_permission_group/jcloud_permission_group.py b/jcloud/jcloud/pagetype/jcloud_permission_group/jcloud_permission_group.py
index d18cd66..bdc682e 100644
--- a/jcloud/jcloud/pagetype/jcloud_permission_group/jcloud_permission_group.py
+++ b/jcloud/jcloud/pagetype/jcloud_permission_group/jcloud_permission_group.py
@@ -2,16 +2,16 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import dashboard_whitelist
DEFAULT_PERMISSIONS = {
- "*": {"*": {"*": True}} # all doctypes # all documents # all methods
+ "*": {"*": {"*": True}} # all pagetypes # all documents # all methods
}
-class JcloudPermissionGroup(Document):
+class JcloudPermissionGroup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -59,7 +59,7 @@ class JcloudPermissionGroup(Document):
return
for pagetype, pagetype_perms in permissions.items():
- if pagetype not in get_all_restrictable_doctypes() and pagetype != "*":
+ if pagetype not in get_all_restrictable_pagetypes() and pagetype != "*":
jingrow.throw(f"{pagetype} is not a valid pagetype.")
if not isinstance(pagetype_perms, dict):
@@ -160,7 +160,7 @@ class JcloudPermissionGroup(Document):
if not (jingrow.local.system_user() or user_belongs_to_group or user_is_team_owner):
jingrow.throw(f"{user} does not belong to {self.name}")
- if pagetype not in get_all_restrictable_doctypes():
+ if pagetype not in get_all_restrictable_pagetypes():
jingrow.throw(f"{pagetype} is not a valid restrictable pagetype.")
restrictable_methods = get_all_restrictable_methods(pagetype)
@@ -222,7 +222,7 @@ def has_method_permission(
user = jingrow.session.user
- if pagetype not in get_all_restrictable_doctypes():
+ if pagetype not in get_all_restrictable_pagetypes():
return True
if method not in get_all_restrictable_methods(pagetype):
@@ -244,7 +244,7 @@ def has_method_permission(
def get_permitted_methods(pagetype: str, name: str, group_names: list = None) -> list:
user = jingrow.session.user
- if pagetype not in get_all_restrictable_doctypes():
+ if pagetype not in get_all_restrictable_pagetypes():
jingrow.throw(f"{pagetype} is not a valid restrictable pagetype.")
permissions_by_group = {}
@@ -320,7 +320,7 @@ def resolve_pg_permissions(pagetype, permissions_by_group: dict) -> dict:
return method_perms
-def get_all_restrictable_doctypes() -> list:
+def get_all_restrictable_pagetypes() -> list:
return ["Site", "Release Group"]
diff --git a/jcloud/jcloud/pagetype/jcloud_permission_group_user/jcloud_permission_group_user.py b/jcloud/jcloud/pagetype/jcloud_permission_group_user/jcloud_permission_group_user.py
index 5b56ac9..1682f45 100644
--- a/jcloud/jcloud/pagetype/jcloud_permission_group_user/jcloud_permission_group_user.py
+++ b/jcloud/jcloud/pagetype/jcloud_permission_group_user/jcloud_permission_group_user.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudPermissionGroupUser(Document):
+class JcloudPermissionGroupUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_role/jcloud_role.py b/jcloud/jcloud/pagetype/jcloud_role/jcloud_role.py
index 5793fb1..44da320 100644
--- a/jcloud/jcloud/pagetype/jcloud_role/jcloud_role.py
+++ b/jcloud/jcloud/pagetype/jcloud_role/jcloud_role.py
@@ -3,12 +3,12 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import dashboard_whitelist
-class JcloudRole(Document):
+class JcloudRole(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -205,7 +205,7 @@ def check_role_permissions(pagetype: str, name: str | None = None) -> list[str]
return []
-def add_permission_for_newly_created_pg(pg: Document) -> None:
+def add_permission_for_newly_created_pg(pg: Page) -> None:
"""
Used to bulk insert permissions right after a site/release group/server is created
for users with create permission for respective pagetype is enabled
diff --git a/jcloud/jcloud/pagetype/jcloud_role_permission/jcloud_role_permission.py b/jcloud/jcloud/pagetype/jcloud_role_permission/jcloud_role_permission.py
index ff9022c..9236a72 100644
--- a/jcloud/jcloud/pagetype/jcloud_role_permission/jcloud_role_permission.py
+++ b/jcloud/jcloud/pagetype/jcloud_role_permission/jcloud_role_permission.py
@@ -3,12 +3,12 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import dashboard_whitelist
-class JcloudRolePermission(Document):
+class JcloudRolePermission(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_role_user/jcloud_role_user.py b/jcloud/jcloud/pagetype/jcloud_role_user/jcloud_role_user.py
index db7eb4c..88a33f7 100644
--- a/jcloud/jcloud/pagetype/jcloud_role_user/jcloud_role_user.py
+++ b/jcloud/jcloud/pagetype/jcloud_role_user/jcloud_role_user.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudRoleUser(Document):
+class JcloudRoleUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.json b/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.json
index be76a40..e1a8bed 100644
--- a/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.json
+++ b/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.json
@@ -1,7 +1,6 @@
{
"actions": [],
"creation": "2022-02-08 15:13:48.372783",
- "pagetype": "PageType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
@@ -147,7 +146,12 @@
"telegram_bot_token",
"section_break_vvyh",
"aliyun_access_key_id",
+ "column_break_ssjr",
"aliyun_access_secret",
+ "west_section",
+ "west_username",
+ "column_break_knph",
+ "west_api_password",
"mailgun_settings_section",
"mailgun_api_key",
"root_domain",
@@ -200,8 +204,10 @@
"infrastructure_tab",
"git_section",
"git_service_type",
+ "git_username",
"column_break_jhbn",
"git_url",
+ "git_access_token",
"agent_section",
"agent_repository_owner",
"agent_sentry_dsn",
@@ -1420,25 +1426,25 @@
},
{
"default": "https://openapi.alipay.com/gateway.do",
- "description": "\u652f\u4ed8\u5b9d\u7f51\u5173\u5730\u5740\uff0c\u4f8b\u5982\uff1ahttps://openapi.alipay.com/gateway.do",
+ "description": "支付宝网关地址,例如:https://openapi.alipay.com/gateway.do",
"fieldname": "alipay_server_url",
"fieldtype": "Data",
"label": "Alipay Server URL"
},
{
- "description": "\u652f\u4ed8\u5b9d\u5e94\u7528ID",
+ "description": "支付宝应用ID",
"fieldname": "alipay_app_id",
"fieldtype": "Data",
"label": "Alipay App ID"
},
{
- "description": "\u652f\u4ed8\u5b8c\u6210\u540e\u7684\u8df3\u8f6c\u5730\u5740",
+ "description": "支付完成后的跳转地址",
"fieldname": "alipay_return_url",
"fieldtype": "Data",
"label": "Alipay Return URL"
},
{
- "description": "\u652f\u4ed8\u7ed3\u679c\u901a\u77e5\u5730\u5740",
+ "description": "支付结果通知地址",
"fieldname": "alipay_notify_url",
"fieldtype": "Data",
"label": "Alipay Notify URL"
@@ -1448,13 +1454,13 @@
"fieldtype": "Column Break"
},
{
- "description": "\u5e94\u7528\u79c1\u94a5\uff0c\u7528\u4e8e\u7b7e\u540d",
+ "description": "应用私钥,用于签名",
"fieldname": "alipay_app_private_key",
"fieldtype": "Long Text",
"label": "Alipay App Private Key"
},
{
- "description": "\u652f\u4ed8\u5b9d\u516c\u94a5\uff0c\u7528\u4e8e\u9a8c\u8bc1\u7b7e\u540d",
+ "description": "支付宝公钥,用于验证签名",
"fieldname": "alipay_public_key",
"fieldtype": "Long Text",
"label": "Alipay Public Key"
@@ -1466,25 +1472,25 @@
"label": "Wechatpay Settings"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8AppID",
+ "description": "微信支付AppID",
"fieldname": "wechatpay_appid",
"fieldtype": "Data",
"label": "Wechatpay App ID"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8\u5546\u6237\u53f7",
+ "description": "微信支付商户号",
"fieldname": "wechatpay_mchid",
"fieldtype": "Data",
"label": "Wechatpay Merchant ID"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8\u7ed3\u679c\u901a\u77e5\u5730\u5740",
+ "description": "微信支付结果通知地址",
"fieldname": "wechatpay_notify_url",
"fieldtype": "Data",
"label": "Wechatpay Notify URL"
},
{
- "description": "\u8bc1\u4e66\u5e8f\u5217\u53f7",
+ "description": "证书序列号",
"fieldname": "wechatpay_cert_serial_no",
"fieldtype": "Data",
"label": "Wechatpay Certificate Serial Number"
@@ -1494,25 +1500,25 @@
"fieldtype": "Column Break"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8APIv3\u5bc6\u94a5",
+ "description": "微信支付APIv3密钥",
"fieldname": "wechatpay_apiv3_key",
"fieldtype": "Data",
"label": "Wechatpay API v3 Key"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8\u5546\u6237\u79c1\u94a5",
+ "description": "微信支付商户私钥",
"fieldname": "wechatpay_private_key",
"fieldtype": "Long Text",
"label": "Wechatpay Private Key"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8\u5e73\u53f0\u516c\u94a5",
+ "description": "微信支付平台公钥",
"fieldname": "wechatpay_public_key",
"fieldtype": "Long Text",
"label": "Wechatpay Public Key"
},
{
- "description": "\u5fae\u4fe1\u652f\u4ed8\u516c\u94a5ID",
+ "description": "微信支付公钥ID",
"fieldname": "wechatpay_public_key_id",
"fieldtype": "Data",
"label": "Wechatpay Public Key ID"
@@ -1570,15 +1576,49 @@
{
"fieldname": "column_break_jhbn",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_ssjr",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "west_section",
+ "fieldtype": "Section Break",
+ "label": "West"
+ },
+ {
+ "fieldname": "column_break_knph",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "west_username",
+ "fieldtype": "Data",
+ "label": "West Username"
+ },
+ {
+ "fieldname": "west_api_password",
+ "fieldtype": "Password",
+ "label": "West Api Password"
+ },
+ {
+ "fieldname": "git_access_token",
+ "fieldtype": "Password",
+ "label": "Git Access Token"
+ },
+ {
+ "fieldname": "git_username",
+ "fieldtype": "Data",
+ "label": "Git Username"
}
],
"issingle": 1,
"links": [],
- "modified": "2025-04-06 19:58:13.368427",
+ "modified": "2025-08-22 01:17:48.550460",
"modified_by": "Administrator",
"module": "Jcloud",
"name": "Jcloud Settings",
"owner": "Administrator",
+ "pagetype": "PageType",
"permissions": [
{
"create": 1,
@@ -1592,6 +1632,7 @@
}
],
"quick_entry": 1,
+ "row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.py b/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.py
index 1b6cdb7..c4f2774 100644
--- a/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.py
+++ b/jcloud/jcloud/pagetype/jcloud_settings/jcloud_settings.py
@@ -5,7 +5,7 @@ from __future__ import annotations
import boto3
import jingrow
from boto3.session import Session
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_url
from twilio.rest import Client
@@ -14,7 +14,7 @@ from jcloud.jcloud.pagetype.telegram_message.telegram_message import TelegramMes
from jcloud.telegram_utils import Telegram
-class JcloudSettings(Document):
+class JcloudSettings(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -80,8 +80,10 @@ class JcloudSettings(Document):
enforce_storage_limits: DF.Check
free_credits_cny: DF.Currency
free_credits_usd: DF.Currency
+ git_access_token: DF.Password | None
git_service_type: DF.Literal["gitea", "github"]
git_url: DF.Data | None
+ git_username: DF.Data | None
github_access_token: DF.Data | None
github_app_client_id: DF.Data | None
github_app_client_secret: DF.Data | None
@@ -184,6 +186,8 @@ class JcloudSettings(Document):
wechatpay_private_key: DF.LongText | None
wechatpay_public_key: DF.LongText | None
wechatpay_public_key_id: DF.Data | None
+ west_api_password: DF.Password | None
+ west_username: DF.Data | None
# end: auto-generated types
dashboard_fields = (
@@ -233,9 +237,9 @@ class JcloudSettings(Document):
"default_events": ["create", "push", "release"],
"default_permissions": {"contents": "read"},
# These keys aren't documented under the app creation from manifest
- # https://docs.git.jingrow.com:3000/en/free-pro-team@latest/developers/apps/creating-a-github-app-from-a-manifest
+ # https://docs.git.jingrow.com/en/free-pro-team@latest/developers/apps/creating-a-github-app-from-a-manifest
# But are shown under app creation using url parameters
- # https://docs.git.jingrow.com:3000/en/free-pro-team@latest/developers/apps/creating-a-github-app-using-url-parameters
+ # https://docs.git.jingrow.com/en/free-pro-team@latest/developers/apps/creating-a-github-app-using-url-parameters
# They seem to work. This might change later
"callback_url": get_url("github/authorize"),
"request_oauth_on_install": True,
diff --git a/jcloud/jcloud/pagetype/jcloud_tag/jcloud_tag.py b/jcloud/jcloud/pagetype/jcloud_tag/jcloud_tag.py
index 26295ec..bc46e63 100644
--- a/jcloud/jcloud/pagetype/jcloud_tag/jcloud_tag.py
+++ b/jcloud/jcloud/pagetype/jcloud_tag/jcloud_tag.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudTag(Document):
+class JcloudTag(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_user_permission/jcloud_user_permission.py b/jcloud/jcloud/pagetype/jcloud_user_permission/jcloud_user_permission.py
index b30063b..eac5ece 100644
--- a/jcloud/jcloud/pagetype/jcloud_user_permission/jcloud_user_permission.py
+++ b/jcloud/jcloud/pagetype/jcloud_user_permission/jcloud_user_permission.py
@@ -4,12 +4,12 @@
from typing import Dict
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
ALLOWED_CONFIG_PERMS = ["global", "restricted"]
-class JcloudUserPermission(Document):
+class JcloudUserPermission(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_webhook/jcloud_webhook.py b/jcloud/jcloud/pagetype/jcloud_webhook/jcloud_webhook.py
index 3f373b3..e1a579e 100644
--- a/jcloud/jcloud/pagetype/jcloud_webhook/jcloud_webhook.py
+++ b/jcloud/jcloud/pagetype/jcloud_webhook/jcloud_webhook.py
@@ -12,14 +12,14 @@ import jingrow
import jingrow.query_builder
import jingrow.query_builder.functions
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import dashboard_whitelist
from jcloud.overrides import get_permission_query_conditions_for_pagetype
from jcloud.utils import is_valid_hostname
-class JcloudWebhook(Document):
+class JcloudWebhook(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_webhook_attempt/jcloud_webhook_attempt.py b/jcloud/jcloud/pagetype/jcloud_webhook_attempt/jcloud_webhook_attempt.py
index f957ca4..fe18b29 100644
--- a/jcloud/jcloud/pagetype/jcloud_webhook_attempt/jcloud_webhook_attempt.py
+++ b/jcloud/jcloud/pagetype/jcloud_webhook_attempt/jcloud_webhook_attempt.py
@@ -4,10 +4,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudWebhookAttempt(Document):
+class JcloudWebhookAttempt(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_webhook_event/jcloud_webhook_event.py b/jcloud/jcloud/pagetype/jcloud_webhook_event/jcloud_webhook_event.py
index 6a2aca8..5586622 100644
--- a/jcloud/jcloud/pagetype/jcloud_webhook_event/jcloud_webhook_event.py
+++ b/jcloud/jcloud/pagetype/jcloud_webhook_event/jcloud_webhook_event.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudWebhookEvent(Document):
+class JcloudWebhookEvent(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_webhook_log/jcloud_webhook_log.py b/jcloud/jcloud/pagetype/jcloud_webhook_log/jcloud_webhook_log.py
index 6cc342f..81dbd23 100644
--- a/jcloud/jcloud/pagetype/jcloud_webhook_log/jcloud_webhook_log.py
+++ b/jcloud/jcloud/pagetype/jcloud_webhook_log/jcloud_webhook_log.py
@@ -7,13 +7,13 @@ import json
import jingrow
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import add_to_date, now
from jcloud.overrides import get_permission_query_conditions_for_pagetype
-class JcloudWebhookLog(Document):
+class JcloudWebhookLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jcloud_webhook_selected_event/jcloud_webhook_selected_event.py b/jcloud/jcloud/pagetype/jcloud_webhook_selected_event/jcloud_webhook_selected_event.py
index 365db1d..c62ca1a 100644
--- a/jcloud/jcloud/pagetype/jcloud_webhook_selected_event/jcloud_webhook_selected_event.py
+++ b/jcloud/jcloud/pagetype/jcloud_webhook_selected_event/jcloud_webhook_selected_event.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JcloudWebhookSelectedEvent(Document):
+class JcloudWebhookSelectedEvent(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jerp_app/jerp_app.py b/jcloud/jcloud/pagetype/jerp_app/jerp_app.py
index 32e2adc..44b0f33 100644
--- a/jcloud/jcloud/pagetype/jerp_app/jerp_app.py
+++ b/jcloud/jcloud/pagetype/jerp_app/jerp_app.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JERPApp(Document):
+class JERPApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jerp_consultant/jerp_consultant.py b/jcloud/jcloud/pagetype/jerp_consultant/jerp_consultant.py
index 4725177..d4bf22b 100644
--- a/jcloud/jcloud/pagetype/jerp_consultant/jerp_consultant.py
+++ b/jcloud/jcloud/pagetype/jerp_consultant/jerp_consultant.py
@@ -4,11 +4,11 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_fullname
-class JERPConsultant(Document):
+class JERPConsultant(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jerp_consultant_region/jerp_consultant_region.py b/jcloud/jcloud/pagetype/jerp_consultant_region/jerp_consultant_region.py
index b9ee79d..4eeac5b 100644
--- a/jcloud/jcloud/pagetype/jerp_consultant_region/jerp_consultant_region.py
+++ b/jcloud/jcloud/pagetype/jerp_consultant_region/jerp_consultant_region.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JERPConsultantRegion(Document):
+class JERPConsultantRegion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jerp_site_settings/jerp_site_settings.py b/jcloud/jcloud/pagetype/jerp_site_settings/jerp_site_settings.py
index fbd01fe..4f58680 100644
--- a/jcloud/jcloud/pagetype/jerp_site_settings/jerp_site_settings.py
+++ b/jcloud/jcloud/pagetype/jerp_site_settings/jerp_site_settings.py
@@ -4,10 +4,10 @@
import json
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JERPSiteSettings(Document):
+class JERPSiteSettings(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jingrow_version/jingrow_version.py b/jcloud/jcloud/pagetype/jingrow_version/jingrow_version.py
index d03d109..149d6a7 100644
--- a/jcloud/jcloud/pagetype/jingrow_version/jingrow_version.py
+++ b/jcloud/jcloud/pagetype/jingrow_version/jingrow_version.py
@@ -6,7 +6,7 @@
# import jingrow
import copy
-from jingrow.model.document import Document
+from jingrow.model.page import Page
DEFAULT_DEPENDENCIES = [
{"dependency": "NVM_VERSION", "version": "0.36.0"},
@@ -17,7 +17,7 @@ DEFAULT_DEPENDENCIES = [
]
-class JingrowVersion(Document):
+class JingrowVersion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jingrow_version_dependency/jingrow_version_dependency.py b/jcloud/jcloud/pagetype/jingrow_version_dependency/jingrow_version_dependency.py
index b7d62e8..4e422e7 100644
--- a/jcloud/jcloud/pagetype/jingrow_version_dependency/jingrow_version_dependency.py
+++ b/jcloud/jcloud/pagetype/jingrow_version_dependency/jingrow_version_dependency.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class JingrowVersionDependency(Document):
+class JingrowVersionDependency(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/jsite_domain/__init__.py b/jcloud/jcloud/pagetype/jsite_domain/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.js b/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.js
new file mode 100644
index 0000000..cfefac7
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.js
@@ -0,0 +1,60 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+jingrow.ui.form.on("Jsite Domain", {
+ refresh(frm) {
+ // 添加同步按钮到右上角
+ frm.add_custom_button(__('同步域名信息'), function() {
+ sync_domain_info(frm);
+ });
+ },
+});
+
+// 同步域名信息函数
+function sync_domain_info(frm) {
+ if (!frm.pg.domain) {
+ jingrow.msgprint(__('当前记录没有域名信息'));
+ return;
+ }
+
+ // 显示加载状态
+ frm.dashboard.clear_headline();
+ frm.dashboard.set_headline(__('正在同步域名信息...'));
+
+ // 调用API同步域名信息
+ jingrow.call({
+ method: 'jcloud.api.domain_west.sync_domain_info_from_west',
+ args: {
+ domain: frm.pg.domain
+ },
+ callback: function(r) {
+ frm.dashboard.clear_headline();
+
+ // 检查响应结构
+ if (r && r.message && r.message.status === 'success') {
+ jingrow.msgprint({
+ title: __('同步成功'),
+ message: __('域名信息已成功同步'),
+ indicator: 'green'
+ });
+
+ // 刷新页面以显示更新后的数据
+ frm.refresh();
+ } else {
+ jingrow.msgprint({
+ title: __('同步失败'),
+ message: (r && r.message && r.message.message) || __('同步域名信息时发生错误'),
+ indicator: 'red'
+ });
+ }
+ },
+ error: function(err) {
+ frm.dashboard.clear_headline();
+ jingrow.msgprint({
+ title: __('同步失败'),
+ message: __('网络错误或服务器异常'),
+ indicator: 'red'
+ });
+ }
+ });
+}
diff --git a/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.json b/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.json
new file mode 100644
index 0000000..0c71df3
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.json
@@ -0,0 +1,234 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-07-31 18:45:57.055094",
+ "engine": "InnoDB",
+ "field_order": [
+ "domain",
+ "domain_owner",
+ "price",
+ "registration_date",
+ "team",
+ "admin_password",
+ "whois_protection",
+ "column_break_vndy",
+ "status",
+ "domain_registrar",
+ "order_id",
+ "end_date",
+ "period",
+ "group",
+ "auto_renew",
+ "section_break_yomj",
+ "description",
+ "dns_resolution_tab",
+ "dns_resolution",
+ "dns_tab",
+ "dns_host1",
+ "dns_host3",
+ "dns_host5",
+ "column_break_qlix",
+ "dns_host2",
+ "dns_host4",
+ "dns_host6"
+ ],
+ "fields": [
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "label": "团队",
+ "options": "Team"
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "状态",
+ "options": "ok\nclienthold\nclientHold\nclientupdateprohibited\nclientUpdateProhibited\nclienttransferprohibited\nclientTransferProhibited\nclientdeleteprohibited\nclientDeleteProhibited\nclientrenewprohibited\nclientRenewProhibited"
+ },
+ {
+ "fieldname": "order_id",
+ "fieldtype": "Data",
+ "label": "订单ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "price",
+ "fieldtype": "Int",
+ "label": "价格(元/年)"
+ },
+ {
+ "default": "0",
+ "fieldname": "auto_renew",
+ "fieldtype": "Check",
+ "label": "自动续费"
+ },
+ {
+ "fieldname": "period",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "购买时长(年)",
+ "read_only": 1
+ },
+ {
+ "fieldname": "end_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "到期时间",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_vndy",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "domain",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "域名",
+ "reqd": 1
+ },
+ {
+ "fieldname": "domain_owner",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "域名所有者",
+ "options": "Domain Owner"
+ },
+ {
+ "fieldname": "registration_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "注册日期",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "whois_protection",
+ "fieldtype": "Check",
+ "label": "Whois保护"
+ },
+ {
+ "fieldname": "admin_password",
+ "fieldtype": "Password",
+ "label": "管理密码"
+ },
+ {
+ "fieldname": "domain_registrar",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "域名注册商"
+ },
+ {
+ "fieldname": "section_break_yomj",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "描述"
+ },
+ {
+ "fieldname": "group",
+ "fieldtype": "Data",
+ "label": "分组"
+ },
+ {
+ "fieldname": "dns_tab",
+ "fieldtype": "Tab Break",
+ "label": "DNS"
+ },
+ {
+ "default": "ns1.myhostadmin.net",
+ "fieldname": "dns_host1",
+ "fieldtype": "Data",
+ "label": "域名DNS1"
+ },
+ {
+ "default": "ns3.myhostadmin.net",
+ "fieldname": "dns_host3",
+ "fieldtype": "Data",
+ "label": "域名DNS3"
+ },
+ {
+ "default": "ns5.myhostadmin.net",
+ "fieldname": "dns_host5",
+ "fieldtype": "Data",
+ "label": "域名DNS5"
+ },
+ {
+ "fieldname": "column_break_qlix",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "ns2.myhostadmin.net",
+ "fieldname": "dns_host2",
+ "fieldtype": "Data",
+ "label": "域名DNS2"
+ },
+ {
+ "default": "ns4.myhostadmin.net",
+ "fieldname": "dns_host4",
+ "fieldtype": "Data",
+ "label": "域名DNS4"
+ },
+ {
+ "default": "ns6.myhostadmin.net",
+ "fieldname": "dns_host6",
+ "fieldtype": "Data",
+ "label": "域名DNS6"
+ },
+ {
+ "fieldname": "dns_resolution",
+ "fieldtype": "Table",
+ "label": "DNS解析记录",
+ "options": "Dns Resolution"
+ },
+ {
+ "fieldname": "dns_resolution_tab",
+ "fieldtype": "Tab Break",
+ "label": "域名解析"
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2025-08-05 17:46:02.311435",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Jsite Domain",
+ "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
+ }
+ ],
+ "row_format": "Dynamic",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.py b/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.py
new file mode 100644
index 0000000..e906856
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_domain/jsite_domain.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+import jingrow
+from jingrow.model.page import Page
+
+
+class JsiteDomain(Page):
+ # 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 jcloud.jcloud.pagetype.dns_resolution.dns_resolution import DnsResolution
+ from jingrow.types import DF
+
+ admin_password: DF.Password | None
+ auto_renew: DF.Check
+ description: DF.SmallText | None
+ dns_host1: DF.Data | None
+ dns_host2: DF.Data | None
+ dns_host3: DF.Data | None
+ dns_host4: DF.Data | None
+ dns_host5: DF.Data | None
+ dns_host6: DF.Data | None
+ dns_resolution: DF.Table[DnsResolution]
+ domain: DF.Data
+ domain_owner: DF.Link | None
+ domain_registrar: DF.Data | None
+ end_date: DF.Datetime | None
+ group: DF.Data | None
+ order_id: DF.Data | None
+ period: DF.Int
+ price: DF.Int
+ registration_date: DF.Datetime | None
+ status: DF.Literal["ok", "clienthold", "clientHold", "clientupdateprohibited", "clientUpdateProhibited", "clienttransferprohibited", "clientTransferProhibited", "clientdeleteprohibited", "clientDeleteProhibited", "clientrenewprohibited", "clientRenewProhibited"]
+ team: DF.Link | None
+ whois_protection: DF.Check
+ # end: auto-generated types
+
+ dashboard_fields = (
+ "domain",
+ "status",
+ "domain_owner",
+ "domain_registrar",
+ "registration_date",
+ "end_date",
+ "price",
+ "period",
+ "auto_renew",
+ "team",
+ "order_id",
+ "description",
+ "whois_protection",
+ "admin_password",
+ "group",
+ "dns_host1",
+ "dns_host2",
+ "dns_host3",
+ "dns_host4",
+ "dns_host5",
+ "dns_host6"
+ )
+
+ @staticmethod
+ def get_list_query(query):
+ JsiteDomain = jingrow.qb.PageType("Jsite Domain")
+ query = query.where(JsiteDomain.team == jingrow.local.team().name)
+ return query.run(as_dict=True)
+
+ def get_pg(self, pg):
+ return pg
diff --git a/jcloud/jcloud/pagetype/jsite_domain/test_jsite_domain.py b/jcloud/jcloud/pagetype/jsite_domain/test_jsite_domain.py
new file mode 100644
index 0000000..bc667d3
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_domain/test_jsite_domain.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestJsiteDomain(JingrowTestCase):
+ pass
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..344899b
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.js
@@ -0,0 +1,296 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+jingrow.ui.form.on("Jsite Server", {
+ refresh(frm) {
+ if (frm.pg.instance_id) {
+ frm.add_custom_button(__('重启'), function() {
+ // 弹出确认对话框
+ jingrow.confirm(
+ __('确定要重启服务器吗?重启过程中服务器将暂时不可用。'),
+ function() {
+ restart_server(frm);
+ }
+ );
+ });
+
+ 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'
+ });
+ }
+ },
+ __('重置服务器密码')
+ );
+ });
+
+ frm.add_custom_button(__('重置密钥对'), function() {
+ // 弹出确认对话框
+ jingrow.confirm(
+ __('确定要重置密钥对吗?这将删除旧的密钥对并创建新的密钥对。重置后需要使用新的私钥才能连接服务器。'),
+ function() {
+ reset_key_pair(frm);
+ }
+ );
+ });
+
+ frm.add_custom_button(__('重置系统'), function() {
+ // 弹出确认对话框
+ jingrow.confirm(
+ __('确定要重置系统吗?这将清除所有数据并重新安装系统,操作不可逆!'),
+ function() {
+ reset_system(frm);
+ }
+ );
+ });
+
+ frm.add_custom_button(__('创建防火墙规则'), function() {
+ // 弹出防火墙规则创建对话框
+ let dialog = new jingrow.ui.Dialog({
+ title: __('创建防火墙规则'),
+ fields: [
+ {
+ fieldtype: 'Select',
+ label: __('协议'),
+ fieldname: 'rule_protocol',
+ options: [
+ {label: 'TCP', value: 'TCP'},
+ {label: 'UDP', value: 'UDP'},
+ {label: 'ICMP', value: 'ICMP'}
+ ],
+ reqd: 1,
+ default: 'TCP'
+ },
+ {
+ fieldtype: 'Data',
+ label: __('端口'),
+ fieldname: 'port',
+ reqd: 1,
+ description: __('单个端口号或端口范围(例如:80 或 8000/9000)')
+ },
+ {
+ fieldtype: 'Data',
+ label: __('备注'),
+ fieldname: 'remark',
+ description: __('防火墙规则的说明(可选)')
+ }
+ ],
+ primary_action_label: __('创建'),
+ primary_action: function() {
+ let values = dialog.get_values();
+ if (values.rule_protocol && values.port) {
+ create_firewall_rule(frm, values.rule_protocol, values.port, values.remark);
+ dialog.hide();
+ } else {
+ jingrow.msgprint({
+ title: __('错误'),
+ message: __('请填写协议和端口信息'),
+ indicator: 'red'
+ });
+ }
+ }
+ });
+ dialog.show();
+ });
+
+ frm.add_custom_button(__('同步防火墙规则'), function() {
+ // 弹出确认对话框
+ jingrow.confirm(
+ __('确定要同步防火墙规则吗?'),
+ function() {
+ sync_firewall_rules(frm);
+ }
+ );
+ });
+
+ frm.add_custom_button(__('同步服务器数据'), function() {
+ // 弹出确认对话框
+ jingrow.confirm(
+ __('确定要同步服务器数据吗?这将从阿里云同步最新的服务器信息,包括状态、IP地址、系统信息、规格和防火墙规则等。'),
+ function() {
+ sync_server_data(frm);
+ }
+ );
+ });
+ }
+
+ // 为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: 'jcloud.api.aliyun_server_light.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'));
+ }
+ });
+ }
+ }
+});
+
+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
+ }
+ });
+}
+
+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
+ }
+ });
+}
+
+function reset_key_pair(frm) {
+ jingrow.call({
+ method: 'jcloud.api.aliyun_server_light.reset_server_key_pair',
+ args: {
+ instance_id: frm.pg.instance_id
+ }
+ });
+}
+
+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
+ }
+ });
+}
+
+function create_firewall_rule(frm, rule_protocol, port, remark) {
+ jingrow.call({
+ method: 'jcloud.api.aliyun_server_light.create_aliyun_firewall_rule',
+ args: {
+ instance_id: frm.pg.instance_id,
+ rule_protocol: rule_protocol,
+ port: port,
+ remark: remark,
+ region_id: frm.pg.region
+ },
+ callback: function(r) {
+ if (r.message && r.message.success) {
+ jingrow.msgprint({
+ title: __('成功'),
+ message: __('防火墙规则创建成功'),
+ indicator: 'green'
+ });
+ // 创建成功后自动同步防火墙规则
+ setTimeout(function() {
+ sync_firewall_rules(frm);
+ }, 2000);
+ } else {
+ jingrow.msgprint({
+ title: __('错误'),
+ message: r.message ? r.message.message : __('创建防火墙规则失败'),
+ indicator: 'red'
+ });
+ }
+ }
+ });
+}
+
+function sync_firewall_rules(frm) {
+ jingrow.call({
+ method: 'jcloud.api.aliyun_server_light.sync_firewall_rules',
+ args: {
+ instance_id: frm.pg.instance_id
+ },
+ callback: function(r) {
+ if (r.message && r.message.success) {
+ jingrow.msgprint({
+ title: __('成功'),
+ message: r.message.message || __('防火墙规则同步成功'),
+ indicator: 'green'
+ });
+ // 刷新表单以显示最新的防火墙规则
+ frm.reload_pg();
+ } else {
+ jingrow.msgprint({
+ title: __('错误'),
+ message: r.message ? r.message.message : __('同步防火墙规则失败'),
+ indicator: 'red'
+ });
+ }
+ }
+ });
+}
+
+function sync_server_data(frm) {
+ jingrow.call({
+ method: 'jcloud.api.aliyun_server_light.update_server_record',
+ args: {
+ instance_ids: JSON.stringify([frm.pg.instance_id])
+ },
+ callback: function(r) {
+ if (r.message && r.message.success) {
+ jingrow.msgprint({
+ title: __('成功'),
+ message: r.message.message || __('服务器数据同步成功'),
+ indicator: 'green'
+ });
+ // 刷新表单以显示最新的服务器数据
+ frm.reload_pg();
+ } else {
+ jingrow.msgprint({
+ title: __('错误'),
+ message: r.message ? r.message.message : __('同步服务器数据失败'),
+ indicator: 'red'
+ });
+ }
+ }
+ });
+}
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..c699b7f
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.json
@@ -0,0 +1,285 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-01-27 10:00:00",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "team",
+ "plan_type",
+ "plan_price",
+ "column_break_4",
+ "status",
+ "order_id",
+ "planid",
+ "end_date",
+ "auto_renew",
+ "server_section",
+ "instance_id",
+ "cpu",
+ "disk_size",
+ "region",
+ "system",
+ "image_id",
+ "column_break_aliyun",
+ "memory",
+ "bandwidth",
+ "public_ip",
+ "private_ip",
+ "os_type",
+ "period",
+ "ssh_section",
+ "ssh_user",
+ "ssh_port",
+ "password",
+ "column_break_20",
+ "key_pair_name",
+ "private_key",
+ "firewall_rules_tab",
+ "firewall_rules"
+ ],
+ "fields": [
+ {
+ "default": "Pending",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "状态",
+ "options": "Pending\nStarting\nRunning\nStopping\nStopped\nResetting\nUpgrading\nDisabled",
+ "read_only": 1
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "标题"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "label": "团队",
+ "options": "Team"
+ },
+ {
+ "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": "column_break_aliyun",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "instance_id",
+ "fieldtype": "Data",
+ "label": "实例ID",
+ "read_only": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "end_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "到期时间",
+ "read_only": 1
+ },
+ {
+ "fieldname": "region",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "地域",
+ "read_only": 1
+ },
+ {
+ "fieldname": "server_section",
+ "fieldtype": "Section Break",
+ "label": "服务器信息"
+ },
+ {
+ "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": "内存(GB)",
+ "read_only": 1
+ },
+ {
+ "fieldname": "disk_size",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "硬盘容量(GB)",
+ "read_only": 1
+ },
+ {
+ "fieldname": "order_id",
+ "fieldtype": "Data",
+ "label": "订单ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "planid",
+ "fieldtype": "Data",
+ "label": "套餐ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "period",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "label": "购买时长(月)",
+ "read_only": 1
+ },
+ {
+ "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
+ },
+ {
+ "fieldname": "image_id",
+ "fieldtype": "Data",
+ "label": "镜像ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "system",
+ "fieldtype": "Data",
+ "label": "系统",
+ "read_only": 1
+ },
+ {
+ "fieldname": "password",
+ "fieldtype": "Password",
+ "label": "服务器密码"
+ },
+ {
+ "fieldname": "key_pair_name",
+ "fieldtype": "Data",
+ "label": "密钥对名称"
+ },
+ {
+ "fieldname": "plan_price",
+ "fieldtype": "Int",
+ "label": "套餐价格(元/月)"
+ },
+ {
+ "fieldname": "os_type",
+ "fieldtype": "Data",
+ "label": "系统类型"
+ },
+ {
+ "fieldname": "plan_type",
+ "fieldtype": "Data",
+ "label": "套餐类型"
+ },
+ {
+ "default": "0",
+ "fieldname": "auto_renew",
+ "fieldtype": "Check",
+ "label": "自动续费"
+ },
+ {
+ "fieldname": "firewall_rules_tab",
+ "fieldtype": "Tab Break",
+ "label": "Firewall Rules"
+ },
+ {
+ "fieldname": "firewall_rules",
+ "fieldtype": "Table",
+ "label": "Firewall Rules",
+ "options": "Firewall Rules"
+ },
+ {
+ "fetch_from": "virtual_machine.public_ip_address",
+ "fieldname": "private_ip",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "内网IP",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2025-08-15 15:19:18.272352",
+ "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..f61c316
--- /dev/null
+++ b/jcloud/jcloud/pagetype/jsite_server/jsite_server.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+import jingrow
+from jingrow.model.page import Page
+
+
+class JsiteServer(Page):
+ # 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 jcloud.jcloud.pagetype.firewall_rules.firewall_rules import FirewallRules
+ from jingrow.types import DF
+
+ auto_renew: DF.Check
+ bandwidth: DF.Data | None
+ cpu: DF.Data | None
+ disk_size: DF.Data | None
+ end_date: DF.Datetime | None
+ firewall_rules: DF.Table[FirewallRules]
+ 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
+ os_type: DF.Data | None
+ password: DF.Password | None
+ period: DF.Int
+ plan_price: DF.Int
+ plan_type: DF.Data | None
+ planid: DF.Data | None
+ private_ip: DF.Data | None
+ private_key: DF.Text | None
+ public_ip: DF.Data | None
+ region: DF.Data | None
+ ssh_port: DF.Int
+ ssh_user: 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
+
+ dashboard_fields = (
+ "title",
+ "status",
+ "region",
+ "cpu",
+ "memory",
+ "disk_size",
+ "public_ip",
+ "private_ip",
+ "end_date",
+ "bandwidth",
+ "team",
+ "instance_id",
+ "order_id",
+ "planid",
+ "image_id",
+ "system",
+ "plan_price",
+ "key_pair_name",
+ "private_key",
+ "password"
+ )
+
+ @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
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
diff --git a/jcloud/jcloud/pagetype/local_agent/__init__.py b/jcloud/jcloud/pagetype/local_agent/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/local_agent/local_agent.js b/jcloud/jcloud/pagetype/local_agent/local_agent.js
new file mode 100644
index 0000000..10c7ed3
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_agent/local_agent.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+// jingrow.ui.form.on("Local Agent", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/jcloud/jcloud/pagetype/local_agent/local_agent.json b/jcloud/jcloud/pagetype/local_agent/local_agent.json
new file mode 100644
index 0000000..d2f6051
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_agent/local_agent.json
@@ -0,0 +1,150 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-10-22 15:50:31.087391",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "agent_name",
+ "title",
+ "subtitle",
+ "public",
+ "enabled",
+ "column_break_5",
+ "status",
+ "file_url",
+ "repository_url",
+ "team",
+ "agent_image",
+ "section_break_otbv",
+ "description",
+ "agent_flow_tab",
+ "agent_flow"
+ ],
+ "fields": [
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "label": "Team",
+ "options": "Team",
+ "read_only": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "public",
+ "fieldtype": "Check",
+ "label": "Public"
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled"
+ },
+ {
+ "fieldname": "section_break_otbv",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Jeditor",
+ "label": "Description"
+ },
+ {
+ "fieldname": "repository_url",
+ "fieldtype": "Data",
+ "label": "Repository URL"
+ },
+ {
+ "fieldname": "file_url",
+ "fieldtype": "Data",
+ "label": "File URL"
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "default": "Published",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "Published\nUnpublished\nDraft"
+ },
+ {
+ "fieldname": "subtitle",
+ "fieldtype": "Small Text",
+ "label": "Subtitle"
+ },
+ {
+ "fieldname": "agent_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Agent Name",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "agent_image",
+ "fieldtype": "Attach Image",
+ "label": "Agent Image"
+ },
+ {
+ "fieldname": "agent_flow_tab",
+ "fieldtype": "Tab Break",
+ "label": "Agent Flow"
+ },
+ {
+ "fieldname": "agent_flow",
+ "fieldtype": "JSON",
+ "label": "Agent Flow"
+ }
+ ],
+ "links": [],
+ "modified": "2025-11-21 18:53:14.977048",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Local Agent",
+ "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"
+ },
+ {
+ "create": 1,
+ "read": 1,
+ "role": "Jcloud Member"
+ }
+ ],
+ "row_format": "Dynamic",
+ "show_title_field_in_link": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/local_agent/local_agent.py b/jcloud/jcloud/pagetype/local_agent/local_agent.py
new file mode 100644
index 0000000..efe201f
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_agent/local_agent.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class LocalAgent(Page):
+ # 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
+
+ agent_flow: DF.JSON | None
+ agent_image: DF.AttachImage | None
+ agent_name: DF.Data
+ enabled: DF.Check
+ file_url: DF.Data | None
+ public: DF.Check
+ repository_url: DF.Data | None
+ status: DF.Literal["Published", "Unpublished", "Draft"]
+ subtitle: DF.SmallText | None
+ team: DF.Link | None
+ title: DF.Data
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/local_agent/test_local_agent.py b/jcloud/jcloud/pagetype/local_agent/test_local_agent.py
new file mode 100644
index 0000000..c404ee4
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_agent/test_local_agent.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestLocalAgent(JingrowTestCase):
+ pass
diff --git a/jcloud/jcloud/pagetype/local_app/__init__.py b/jcloud/jcloud/pagetype/local_app/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/local_app/local_app.js b/jcloud/jcloud/pagetype/local_app/local_app.js
new file mode 100644
index 0000000..ada04cd
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_app/local_app.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+// jingrow.ui.form.on("Local App", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/jcloud/jcloud/pagetype/local_app/local_app.json b/jcloud/jcloud/pagetype/local_app/local_app.json
new file mode 100644
index 0000000..74f7c90
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_app/local_app.json
@@ -0,0 +1,148 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-10-22 15:50:31.087391",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "app_name",
+ "title",
+ "subtitle",
+ "public",
+ "enabled",
+ "column_break_5",
+ "status",
+ "category",
+ "file_url",
+ "repository_url",
+ "team",
+ "app_image",
+ "section_break_otbv",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Team",
+ "options": "Team",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "public",
+ "fieldtype": "Check",
+ "label": "Public"
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled"
+ },
+ {
+ "fieldname": "category",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Category",
+ "options": "Marketplace App Category"
+ },
+ {
+ "fieldname": "section_break_otbv",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Jeditor",
+ "label": "Description"
+ },
+ {
+ "fieldname": "repository_url",
+ "fieldtype": "Data",
+ "label": "Repository URL"
+ },
+ {
+ "fieldname": "file_url",
+ "fieldtype": "Data",
+ "label": "File URL"
+ },
+ {
+ "fieldname": "app_image",
+ "fieldtype": "Attach Image",
+ "label": "App Image"
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "app_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "App Name",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "Pending Review\nPublished\nUnpublished\nDraft"
+ },
+ {
+ "fieldname": "subtitle",
+ "fieldtype": "Small Text",
+ "label": "Subtitle"
+ }
+ ],
+ "links": [],
+ "modified": "2025-11-21 18:52:39.660644",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Local App",
+ "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"
+ },
+ {
+ "create": 1,
+ "read": 1,
+ "role": "Jcloud Member"
+ }
+ ],
+ "row_format": "Dynamic",
+ "show_title_field_in_link": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/local_app/local_app.py b/jcloud/jcloud/pagetype/local_app/local_app.py
new file mode 100644
index 0000000..b612879
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_app/local_app.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class LocalApp(Page):
+ # 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
+
+ app_image: DF.AttachImage | None
+ app_name: DF.Data
+ category: DF.Link | None
+ enabled: DF.Check
+ file_url: DF.Data | None
+ public: DF.Check
+ repository_url: DF.Data | None
+ status: DF.Literal["Pending Review", "Published", "Unpublished", "Draft"]
+ subtitle: DF.SmallText | None
+ team: DF.Link | None
+ title: DF.Data
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/local_app/test_local_app.py b/jcloud/jcloud/pagetype/local_app/test_local_app.py
new file mode 100644
index 0000000..56fd35d
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_app/test_local_app.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestLocalApp(JingrowTestCase):
+ pass
diff --git a/jcloud/jcloud/pagetype/local_marketplace/__init__.py b/jcloud/jcloud/pagetype/local_marketplace/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.js b/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.js
new file mode 100644
index 0000000..03b3525
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+// jingrow.ui.form.on("Local Marketplace", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.json b/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.json
new file mode 100644
index 0000000..82f188d
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.json
@@ -0,0 +1,458 @@
+{
+ "actions": [],
+ "allow_guest_to_view": 1,
+ "allow_rename": 1,
+ "creation": "2024-12-19 10:00:00",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "image",
+ "app",
+ "title",
+ "column_break_3",
+ "team",
+ "route",
+ "section_break_7",
+ "jingrow_approved",
+ "subscription_type",
+ "subscription_level",
+ "column_break_10",
+ "categories",
+ "published",
+ "section_break_5",
+ "sources",
+ "descriptions_tab",
+ "description",
+ "app_screenshots_section",
+ "screenshots",
+ "section_break_15",
+ "long_description",
+ "links_section",
+ "website",
+ "support",
+ "documentation",
+ "column_break_16",
+ "privacy_policy",
+ "terms_of_service",
+ "saas_tab",
+ "outgoing_email",
+ "outgoing_sender_name",
+ "signup_email_template_section",
+ "message",
+ "column_break_32",
+ "signature",
+ "column_break_30",
+ "subject",
+ "poll_method",
+ "column_break_33",
+ "custom_verify_template",
+ "subscription_update_hook",
+ "site_config_section",
+ "site_config",
+ "scripts_tab",
+ "run_after_install_script",
+ "after_install_script",
+ "run_after_uninstall_script",
+ "after_uninstall_script",
+ "review_tab",
+ "stop_auto_review",
+ "review_stage",
+ "status",
+ "dashboard_tab",
+ "onboarding_related_section",
+ "show_for_site_creation",
+ "localisation_apps",
+ "section_break_tlpw",
+ "average_rating"
+ ],
+ "fields": [
+ {
+ "fieldname": "image",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Image"
+ },
+ {
+ "fetch_from": "app.title",
+ "fetch_if_empty": 1,
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "Draft\nPublished\nIn Review\nAttention Required\nRejected\nDisabled"
+ },
+ {
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "Please add a short description about your app here...",
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Description",
+ "reqd": 1
+ },
+ {
+ "fieldname": "long_description",
+ "fieldtype": "Text Editor",
+ "label": "Long Description"
+ },
+ {
+ "fieldname": "links_section",
+ "fieldtype": "Tab Break",
+ "label": "Support Links"
+ },
+ {
+ "fieldname": "website",
+ "fieldtype": "Data",
+ "label": "Website",
+ "options": "URL"
+ },
+ {
+ "fieldname": "support",
+ "fieldtype": "Data",
+ "label": "Support",
+ "options": "URL"
+ },
+ {
+ "fieldname": "privacy_policy",
+ "fieldtype": "Data",
+ "label": "Privacy Policy",
+ "options": "URL"
+ },
+ {
+ "fieldname": "documentation",
+ "fieldtype": "Data",
+ "label": "Documentation",
+ "options": "URL"
+ },
+ {
+ "fieldname": "terms_of_service",
+ "fieldtype": "Data",
+ "label": "Terms of Service",
+ "options": "URL"
+ },
+ {
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route"
+ },
+ {
+ "default": "0",
+ "fieldname": "published",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Published",
+ "read_only": 1
+ },
+ {
+ "fieldname": "app",
+ "fieldtype": "Link",
+ "label": "App",
+ "options": "App",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Team",
+ "options": "Team"
+ },
+ {
+ "fieldname": "sources",
+ "fieldtype": "Table",
+ "label": "Sources",
+ "options": "Marketplace App Version"
+ },
+ {
+ "fieldname": "app_screenshots_section",
+ "fieldtype": "Section Break",
+ "label": "App Screenshots"
+ },
+ {
+ "fieldname": "screenshots",
+ "fieldtype": "Table",
+ "label": "Screenshots",
+ "options": "Marketplace App Screenshot"
+ },
+ {
+ "fieldname": "section_break_15",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "categories",
+ "fieldtype": "Table",
+ "label": "Categories",
+ "options": "Marketplace App Categories"
+ },
+ {
+ "default": "Free",
+ "fieldname": "subscription_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Subscription Type",
+ "options": "Free\nPaid\nFreemium"
+ },
+ {
+ "fieldname": "signup_email_template_section",
+ "fieldtype": "Section Break",
+ "label": "Signup Email Template"
+ },
+ {
+ "default": "0",
+ "fieldname": "custom_verify_template",
+ "fieldtype": "Check",
+ "label": "Use custom verify template"
+ },
+ {
+ "fieldname": "subject",
+ "fieldtype": "Data",
+ "label": "Subject"
+ },
+ {
+ "fieldname": "message",
+ "fieldtype": "Text Editor",
+ "label": "Message"
+ },
+ {
+ "fieldname": "signature",
+ "fieldtype": "Text Editor",
+ "label": "Signature"
+ },
+ {
+ "fieldname": "poll_method",
+ "fieldtype": "Data",
+ "label": "Poll Method"
+ },
+ {
+ "fieldname": "subscription_update_hook",
+ "fieldtype": "Data",
+ "label": "Subscription Update Hook"
+ },
+ {
+ "fieldname": "saas_tab",
+ "fieldtype": "Tab Break",
+ "label": "SaaS"
+ },
+ {
+ "fieldname": "column_break_32",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_30",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_33",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "descriptions_tab",
+ "fieldtype": "Tab Break",
+ "label": "Descriptions"
+ },
+ {
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "outgoing_email",
+ "fieldtype": "Data",
+ "label": "Outgoing Email"
+ },
+ {
+ "fieldname": "outgoing_sender_name",
+ "fieldtype": "Data",
+ "label": "Outgoing Sender Name"
+ },
+ {
+ "fieldname": "scripts_tab",
+ "fieldtype": "Tab Break",
+ "label": "Scripts"
+ },
+ {
+ "fieldname": "after_install_script",
+ "fieldtype": "Code",
+ "label": "After Install Script",
+ "options": "Python"
+ },
+ {
+ "fieldname": "after_uninstall_script",
+ "fieldtype": "Code",
+ "label": "After Uninstall Script",
+ "options": "Python"
+ },
+ {
+ "default": "0",
+ "fieldname": "run_after_install_script",
+ "fieldtype": "Check",
+ "label": "Rut After Install Script"
+ },
+ {
+ "default": "0",
+ "fieldname": "run_after_uninstall_script",
+ "fieldtype": "Check",
+ "label": "Run After Uninstall Script"
+ },
+ {
+ "fieldname": "review_tab",
+ "fieldtype": "Tab Break",
+ "label": "Review"
+ },
+ {
+ "default": "Not Started",
+ "fieldname": "review_stage",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Review Stage",
+ "options": "Not Started\nDescription Missing\nLogo Missing\nApp Release Not Reviewed\nReady for Review\nReady to Publish\nRejected"
+ },
+ {
+ "default": "0",
+ "fieldname": "jingrow_approved",
+ "fieldtype": "Check",
+ "label": "Jingrow Approved"
+ },
+ {
+ "default": "0",
+ "fieldname": "stop_auto_review",
+ "fieldtype": "Check",
+ "label": "Stop Auto Review"
+ },
+ {
+ "description": "This keys are added to site config on saas signup",
+ "fieldname": "site_config",
+ "fieldtype": "JSON",
+ "label": "Site Config"
+ },
+ {
+ "fieldname": "site_config_section",
+ "fieldtype": "Section Break",
+ "label": "Site Config"
+ },
+ {
+ "fieldname": "onboarding_related_section",
+ "fieldtype": "Section Break",
+ "label": "Onboarding/Site Creation Related"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_for_site_creation",
+ "fieldtype": "Check",
+ "label": "Show for site creation"
+ },
+ {
+ "fieldname": "dashboard_tab",
+ "fieldtype": "Tab Break",
+ "label": "Dashboard"
+ },
+ {
+ "fieldname": "section_break_tlpw",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "average_rating",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "label": "Average Rating",
+ "precision": "2"
+ },
+ {
+ "fieldname": "localisation_apps",
+ "fieldtype": "Table",
+ "label": "Localisation Apps",
+ "options": "Marketplace Localisation App"
+ },
+ {
+ "default": "1",
+ "fieldname": "subscription_level",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Subscription Level",
+ "options": "1\n2\n3\n4\n5"
+ }
+ ],
+ "has_web_view": 1,
+ "image_field": "image",
+ "index_web_pages_for_search": 1,
+ "is_published_field": "published",
+ "links": [
+ {
+ "group": "General",
+ "link_fieldname": "marketplace_app",
+ "link_pagetype": "App Release Approval Request"
+ },
+ {
+ "group": "App Subscription",
+ "link_fieldname": "app",
+ "link_pagetype": "Marketplace App Plan"
+ },
+ {
+ "group": "App Subscription",
+ "link_fieldname": "app",
+ "link_pagetype": "Marketplace App Subscription"
+ }
+ ],
+ "modified": "2025-10-22 15:50:31.087391",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Local Marketplace",
+ "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
+ }
+ ],
+ "row_format": "Dynamic",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [
+ {
+ "color": "Gray",
+ "title": "Draft"
+ },
+ {
+ "color": "Green",
+ "title": "Published"
+ }
+ ],
+ "title_field": "title",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.py b/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.py
new file mode 100644
index 0000000..994cc60
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_marketplace/local_marketplace.py
@@ -0,0 +1,60 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class LocalMarketplace(Page):
+ # 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 jcloud.jcloud.pagetype.marketplace_app_categories.marketplace_app_categories import MarketplaceAppCategories
+ from jcloud.jcloud.pagetype.marketplace_app_screenshot.marketplace_app_screenshot import MarketplaceAppScreenshot
+ from jcloud.jcloud.pagetype.marketplace_app_version.marketplace_app_version import MarketplaceAppVersion
+ from jcloud.jcloud.pagetype.marketplace_localisation_app.marketplace_localisation_app import MarketplaceLocalisationApp
+ from jingrow.types import DF
+
+ after_install_script: DF.Code | None
+ after_uninstall_script: DF.Code | None
+ app: DF.Link
+ average_rating: DF.Float
+ categories: DF.Table[MarketplaceAppCategories]
+ custom_verify_template: DF.Check
+ description: DF.SmallText
+ documentation: DF.Data | None
+ image: DF.AttachImage | None
+ jingrow_approved: DF.Check
+ localisation_apps: DF.Table[MarketplaceLocalisationApp]
+ long_description: DF.TextEditor | None
+ message: DF.TextEditor | None
+ outgoing_email: DF.Data | None
+ outgoing_sender_name: DF.Data | None
+ poll_method: DF.Data | None
+ privacy_policy: DF.Data | None
+ published: DF.Check
+ review_stage: DF.Literal["Not Started", "Description Missing", "Logo Missing", "App Release Not Reviewed", "Ready for Review", "Ready to Publish", "Rejected"]
+ route: DF.Data | None
+ run_after_install_script: DF.Check
+ run_after_uninstall_script: DF.Check
+ screenshots: DF.Table[MarketplaceAppScreenshot]
+ show_for_site_creation: DF.Check
+ signature: DF.TextEditor | None
+ site_config: DF.JSON | None
+ sources: DF.Table[MarketplaceAppVersion]
+ status: DF.Literal["Draft", "Published", "In Review", "Attention Required", "Rejected", "Disabled"]
+ stop_auto_review: DF.Check
+ subject: DF.Data | None
+ subscription_level: DF.Literal["1", "2", "3", "4", "5"]
+ subscription_type: DF.Literal["Free", "Paid", "Freemium"]
+ subscription_update_hook: DF.Data | None
+ support: DF.Data | None
+ team: DF.Link | None
+ terms_of_service: DF.Data | None
+ title: DF.Data
+ website: DF.Data | None
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/local_marketplace/templates/local_marketplace.html b/jcloud/jcloud/pagetype/local_marketplace/templates/local_marketplace.html
new file mode 100644
index 0000000..db12309
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_marketplace/templates/local_marketplace.html
@@ -0,0 +1,7 @@
+{% extends "templates/web.html" %}
+
+{% block page_content %}
+{{ title }}
+{% endblock %}
+
+
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/local_marketplace/templates/local_marketplace_row.html b/jcloud/jcloud/pagetype/local_marketplace/templates/local_marketplace_row.html
new file mode 100644
index 0000000..8ef1ac6
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_marketplace/templates/local_marketplace_row.html
@@ -0,0 +1,4 @@
+
+
diff --git a/jcloud/jcloud/pagetype/local_marketplace/test_local_marketplace.py b/jcloud/jcloud/pagetype/local_marketplace/test_local_marketplace.py
new file mode 100644
index 0000000..6ec48c1
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_marketplace/test_local_marketplace.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestLocalMarketplace(JingrowTestCase):
+ pass
diff --git a/jcloud/jcloud/pagetype/local_node/__init__.py b/jcloud/jcloud/pagetype/local_node/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/local_node/local_node.js b/jcloud/jcloud/pagetype/local_node/local_node.js
new file mode 100644
index 0000000..a59b740
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_node/local_node.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+// jingrow.ui.form.on("Local Node", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/jcloud/jcloud/pagetype/local_node/local_node.json b/jcloud/jcloud/pagetype/local_node/local_node.json
new file mode 100644
index 0000000..039ac48
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_node/local_node.json
@@ -0,0 +1,140 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-10-22 15:50:31.087391",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "node_type",
+ "title",
+ "subtitle",
+ "public",
+ "enabled",
+ "column_break_5",
+ "status",
+ "file_url",
+ "repository_url",
+ "team",
+ "node_image",
+ "section_break_otbv",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Team",
+ "options": "Team",
+ "read_only": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "public",
+ "fieldtype": "Check",
+ "label": "Public"
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled"
+ },
+ {
+ "fieldname": "section_break_otbv",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Jeditor",
+ "label": "Description"
+ },
+ {
+ "fieldname": "repository_url",
+ "fieldtype": "Data",
+ "label": "Repository URL"
+ },
+ {
+ "fieldname": "file_url",
+ "fieldtype": "Data",
+ "label": "File URL"
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "default": "Published",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "Pending Review\nPublished\nUnpublished\nDraft"
+ },
+ {
+ "fieldname": "subtitle",
+ "fieldtype": "Small Text",
+ "label": "Subtitle"
+ },
+ {
+ "fieldname": "node_type",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Node Type",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "node_image",
+ "fieldtype": "Attach Image",
+ "label": "Node Image"
+ }
+ ],
+ "links": [],
+ "modified": "2025-11-21 18:53:30.635980",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Local Node",
+ "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"
+ },
+ {
+ "create": 1,
+ "read": 1,
+ "role": "Jcloud Member"
+ }
+ ],
+ "row_format": "Dynamic",
+ "show_title_field_in_link": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/local_node/local_node.py b/jcloud/jcloud/pagetype/local_node/local_node.py
new file mode 100644
index 0000000..9ec53de
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_node/local_node.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class LocalNode(Page):
+ # 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
+
+ enabled: DF.Check
+ file_url: DF.Data | None
+ node_image: DF.AttachImage | None
+ node_type: DF.Data
+ public: DF.Check
+ repository_url: DF.Data | None
+ status: DF.Literal["Pending Review", "Published", "Unpublished", "Draft"]
+ subtitle: DF.SmallText | None
+ team: DF.Link | None
+ title: DF.Data
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/local_node/test_local_node.py b/jcloud/jcloud/pagetype/local_node/test_local_node.py
new file mode 100644
index 0000000..7817af3
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_node/test_local_node.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestLocalNode(JingrowTestCase):
+ pass
diff --git a/jcloud/jcloud/pagetype/local_tool/__init__.py b/jcloud/jcloud/pagetype/local_tool/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/jcloud/jcloud/pagetype/local_tool/local_tool.js b/jcloud/jcloud/pagetype/local_tool/local_tool.js
new file mode 100644
index 0000000..d552996
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_tool/local_tool.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, Jingrow and contributors
+// For license information, please see license.txt
+
+// jingrow.ui.form.on("Local Tool", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/jcloud/jcloud/pagetype/local_tool/local_tool.json b/jcloud/jcloud/pagetype/local_tool/local_tool.json
new file mode 100644
index 0000000..a02509a
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_tool/local_tool.json
@@ -0,0 +1,163 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2025-10-22 15:50:31.087391",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "tool_name",
+ "title",
+ "subtitle",
+ "category",
+ "version",
+ "public",
+ "enabled",
+ "column_break_5",
+ "status",
+ "file_url",
+ "repository_url",
+ "team",
+ "icon",
+ "color",
+ "tool_image",
+ "section_break_otbv",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "team",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Team",
+ "options": "Team",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "public",
+ "fieldtype": "Check",
+ "label": "Public"
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled"
+ },
+ {
+ "fieldname": "section_break_otbv",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Jeditor",
+ "label": "Description"
+ },
+ {
+ "fieldname": "repository_url",
+ "fieldtype": "Data",
+ "label": "Repository URL"
+ },
+ {
+ "fieldname": "file_url",
+ "fieldtype": "Data",
+ "label": "File URL"
+ },
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "Pending Review\nPublished\nUnpublished\nDraft"
+ },
+ {
+ "fieldname": "subtitle",
+ "fieldtype": "Small Text",
+ "label": "Subtitle"
+ },
+ {
+ "fieldname": "tool_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Tool Name",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "tool_image",
+ "fieldtype": "Attach Image",
+ "label": "Tool Image"
+ },
+ {
+ "fieldname": "version",
+ "fieldtype": "Data",
+ "label": "Version"
+ },
+ {
+ "fieldname": "icon",
+ "fieldtype": "Icon",
+ "label": "Icon"
+ },
+ {
+ "fieldname": "color",
+ "fieldtype": "Color",
+ "label": "Color"
+ },
+ {
+ "fieldname": "category",
+ "fieldtype": "Data",
+ "label": "Category"
+ }
+ ],
+ "links": [],
+ "modified": "2025-11-21 18:53:02.875551",
+ "modified_by": "Administrator",
+ "module": "Jcloud",
+ "name": "Local Tool",
+ "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"
+ },
+ {
+ "create": 1,
+ "read": 1,
+ "role": "Jcloud Member"
+ }
+ ],
+ "row_format": "Dynamic",
+ "show_title_field_in_link": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/jcloud/jcloud/pagetype/local_tool/local_tool.py b/jcloud/jcloud/pagetype/local_tool/local_tool.py
new file mode 100644
index 0000000..3d2eefe
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_tool/local_tool.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2025, Jingrow and contributors
+# For license information, please see license.txt
+
+# import jingrow
+from jingrow.model.page import Page
+
+
+class LocalTool(Page):
+ # 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
+
+ category: DF.Data | None
+ color: DF.Color | None
+ enabled: DF.Check
+ file_url: DF.Data | None
+ public: DF.Check
+ repository_url: DF.Data | None
+ status: DF.Literal["Pending Review", "Published", "Unpublished", "Draft"]
+ subtitle: DF.SmallText | None
+ team: DF.Link | None
+ title: DF.Data
+ tool_image: DF.AttachImage | None
+ tool_name: DF.Data
+ version: DF.Data | None
+ # end: auto-generated types
+ pass
diff --git a/jcloud/jcloud/pagetype/local_tool/test_local_tool.py b/jcloud/jcloud/pagetype/local_tool/test_local_tool.py
new file mode 100644
index 0000000..dbb0082
--- /dev/null
+++ b/jcloud/jcloud/pagetype/local_tool/test_local_tool.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2025, Jingrow and Contributors
+# See license.txt
+
+# import jingrow
+from jingrow.tests.utils import JingrowTestCase
+
+
+class TestLocalTool(JingrowTestCase):
+ pass
diff --git a/jcloud/jcloud/pagetype/log_counter/log_counter.py b/jcloud/jcloud/pagetype/log_counter/log_counter.py
index 1f45ca7..fc2b83b 100644
--- a/jcloud/jcloud/pagetype/log_counter/log_counter.py
+++ b/jcloud/jcloud/pagetype/log_counter/log_counter.py
@@ -7,7 +7,7 @@ from typing import Optional, TypedDict
import jingrow
import jingrow.utils
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.query_builder import PageType
from jingrow.query_builder.functions import Count
from pypika import Order
@@ -27,7 +27,7 @@ Counts = TypedDict(
)
-class LogCounter(Document):
+class LogCounter(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mail_log/mail_log.py b/jcloud/jcloud/pagetype/mail_log/mail_log.py
index 7a27051..d1b72fa 100644
--- a/jcloud/jcloud/pagetype/mail_log/mail_log.py
+++ b/jcloud/jcloud/pagetype/mail_log/mail_log.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MailLog(Document):
+class MailLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mail_setup/mail_setup.py b/jcloud/jcloud/pagetype/mail_setup/mail_setup.py
index 900df00..d9aa261 100644
--- a/jcloud/jcloud/pagetype/mail_setup/mail_setup.py
+++ b/jcloud/jcloud/pagetype/mail_setup/mail_setup.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MailSetup(Document):
+class MailSetup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/malware_scan/malware_scan.py b/jcloud/jcloud/pagetype/malware_scan/malware_scan.py
index 4f0791d..f240c81 100644
--- a/jcloud/jcloud/pagetype/malware_scan/malware_scan.py
+++ b/jcloud/jcloud/pagetype/malware_scan/malware_scan.py
@@ -2,13 +2,13 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.runner import Ansible
from jcloud.utils import log_error
-class MalwareScan(Document):
+class MalwareScan(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/managed_database_service/managed_database_service.py b/jcloud/jcloud/pagetype/managed_database_service/managed_database_service.py
index 57e9905..1649efb 100644
--- a/jcloud/jcloud/pagetype/managed_database_service/managed_database_service.py
+++ b/jcloud/jcloud/pagetype/managed_database_service/managed_database_service.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ManagedDatabaseService(Document):
+class ManagedDatabaseService(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mariadb_stalk/mariadb_stalk.py b/jcloud/jcloud/pagetype/mariadb_stalk/mariadb_stalk.py
index ad51ba3..e1701d3 100644
--- a/jcloud/jcloud/pagetype/mariadb_stalk/mariadb_stalk.py
+++ b/jcloud/jcloud/pagetype/mariadb_stalk/mariadb_stalk.py
@@ -5,7 +5,7 @@ import gzip
from datetime import datetime
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.query_builder import Interval
from jingrow.query_builder.functions import Now
from jingrow.utils import add_to_date, convert_utc_to_system_timezone, now_datetime
@@ -13,7 +13,7 @@ from jingrow.utils import add_to_date, convert_utc_to_system_timezone, now_datet
from jcloud.utils import log_error
-class MariaDBStalk(Document):
+class MariaDBStalk(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mariadb_stalk_diagnostic/mariadb_stalk_diagnostic.py b/jcloud/jcloud/pagetype/mariadb_stalk_diagnostic/mariadb_stalk_diagnostic.py
index 21d0544..133fb6a 100644
--- a/jcloud/jcloud/pagetype/mariadb_stalk_diagnostic/mariadb_stalk_diagnostic.py
+++ b/jcloud/jcloud/pagetype/mariadb_stalk_diagnostic/mariadb_stalk_diagnostic.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MariaDBStalkDiagnostic(Document):
+class MariaDBStalkDiagnostic(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mariadb_variable/mariadb_variable.py b/jcloud/jcloud/pagetype/mariadb_variable/mariadb_variable.py
index 1255336..543d179 100644
--- a/jcloud/jcloud/pagetype/mariadb_variable/mariadb_variable.py
+++ b/jcloud/jcloud/pagetype/mariadb_variable/mariadb_variable.py
@@ -2,12 +2,12 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.database_server.database_server import DatabaseServer
-class MariaDBVariable(Document):
+class MariaDBVariable(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/marketplace_app/marketplace_app.py b/jcloud/jcloud/pagetype/marketplace_app/marketplace_app.py
index e8f4930..dd36613 100644
--- a/jcloud/jcloud/pagetype/marketplace_app/marketplace_app.py
+++ b/jcloud/jcloud/pagetype/marketplace_app/marketplace_app.py
@@ -296,7 +296,7 @@ class MarketplaceApp(WebsiteGenerator):
for variant in variants:
try:
readme = requests.get(
- f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{repository}/contents/{variant}",
+ f"http://git.jingrow.com/api/v1/repos/{owner}/{repository}/contents/{variant}",
headers=headers,
params={"ref": branch},
).json()
diff --git a/jcloud/jcloud/pagetype/marketplace_app_categories/marketplace_app_categories.py b/jcloud/jcloud/pagetype/marketplace_app_categories/marketplace_app_categories.py
index c2cda9c..4cab816 100644
--- a/jcloud/jcloud/pagetype/marketplace_app_categories/marketplace_app_categories.py
+++ b/jcloud/jcloud/pagetype/marketplace_app_categories/marketplace_app_categories.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceAppCategories(Document):
+class MarketplaceAppCategories(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/marketplace_app_category/marketplace_app_category.py b/jcloud/jcloud/pagetype/marketplace_app_category/marketplace_app_category.py
index 594297c..fb1545b 100644
--- a/jcloud/jcloud/pagetype/marketplace_app_category/marketplace_app_category.py
+++ b/jcloud/jcloud/pagetype/marketplace_app_category/marketplace_app_category.py
@@ -3,11 +3,11 @@
# For license information, please see license.txt
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.website.utils import cleanup_page_name
-class MarketplaceAppCategory(Document):
+class MarketplaceAppCategory(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/marketplace_app_screenshot/marketplace_app_screenshot.py b/jcloud/jcloud/pagetype/marketplace_app_screenshot/marketplace_app_screenshot.py
index fda3fcb..bda209f 100644
--- a/jcloud/jcloud/pagetype/marketplace_app_screenshot/marketplace_app_screenshot.py
+++ b/jcloud/jcloud/pagetype/marketplace_app_screenshot/marketplace_app_screenshot.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceAppScreenshot(Document):
+class MarketplaceAppScreenshot(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/marketplace_app_version/marketplace_app_version.py b/jcloud/jcloud/pagetype/marketplace_app_version/marketplace_app_version.py
index a780b96..3dacdbe 100644
--- a/jcloud/jcloud/pagetype/marketplace_app_version/marketplace_app_version.py
+++ b/jcloud/jcloud/pagetype/marketplace_app_version/marketplace_app_version.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceAppVersion(Document):
+class MarketplaceAppVersion(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/marketplace_localisation_app/marketplace_localisation_app.py b/jcloud/jcloud/pagetype/marketplace_localisation_app/marketplace_localisation_app.py
index 2e2318f..91fca44 100644
--- a/jcloud/jcloud/pagetype/marketplace_localisation_app/marketplace_localisation_app.py
+++ b/jcloud/jcloud/pagetype/marketplace_localisation_app/marketplace_localisation_app.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceLocalisationApp(Document):
+class MarketplaceLocalisationApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/module_setup_guide/module_setup_guide.py b/jcloud/jcloud/pagetype/module_setup_guide/module_setup_guide.py
index dfa6a38..0c27985 100644
--- a/jcloud/jcloud/pagetype/module_setup_guide/module_setup_guide.py
+++ b/jcloud/jcloud/pagetype/module_setup_guide/module_setup_guide.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ModuleSetupGuide(Document):
+class ModuleSetupGuide(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mpesa_payment_record/mpesa_payment_record.py b/jcloud/jcloud/pagetype/mpesa_payment_record/mpesa_payment_record.py
index 416eaf0..9080cd1 100644
--- a/jcloud/jcloud/pagetype/mpesa_payment_record/mpesa_payment_record.py
+++ b/jcloud/jcloud/pagetype/mpesa_payment_record/mpesa_payment_record.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MpesaPaymentRecord(Document):
+class MpesaPaymentRecord(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mpesa_request_log/mpesa_request_log.py b/jcloud/jcloud/pagetype/mpesa_request_log/mpesa_request_log.py
index 3d2ed18..96e4d02 100644
--- a/jcloud/jcloud/pagetype/mpesa_request_log/mpesa_request_log.py
+++ b/jcloud/jcloud/pagetype/mpesa_request_log/mpesa_request_log.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MpesaRequestLog(Document):
+class MpesaRequestLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/mpesa_setup/mpesa_setup.py b/jcloud/jcloud/pagetype/mpesa_setup/mpesa_setup.py
index b22a22c..43fe443 100644
--- a/jcloud/jcloud/pagetype/mpesa_setup/mpesa_setup.py
+++ b/jcloud/jcloud/pagetype/mpesa_setup/mpesa_setup.py
@@ -3,10 +3,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MpesaSetup(Document):
+class MpesaSetup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/oauth_domain_mapping/oauth_domain_mapping.py b/jcloud/jcloud/pagetype/oauth_domain_mapping/oauth_domain_mapping.py
index 454961e..cb02f60 100644
--- a/jcloud/jcloud/pagetype/oauth_domain_mapping/oauth_domain_mapping.py
+++ b/jcloud/jcloud/pagetype/oauth_domain_mapping/oauth_domain_mapping.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class OAuthDomainMapping(Document):
+class OAuthDomainMapping(Page):
pass
diff --git a/jcloud/jcloud/pagetype/order/order.json b/jcloud/jcloud/pagetype/order/order.json
index 333313a..2ea2120 100644
--- a/jcloud/jcloud/pagetype/order/order.json
+++ b/jcloud/jcloud/pagetype/order/order.json
@@ -2,18 +2,20 @@
"actions": [],
"allow_rename": 1,
"creation": "2025-03-23 21:29:54.329381",
- "pagetype": "PageType",
"engine": "InnoDB",
"field_order": [
"title",
"order_id",
- "trade_no",
- "team",
"total_amount",
- "payment_method",
"order_type",
+ "team",
+ "column_break_rwfr",
+ "status",
+ "trade_no",
+ "payment_method",
"description",
- "status"
+ "section_break_apft",
+ "biz_params"
],
"fields": [
{
@@ -37,7 +39,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 +47,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 +80,33 @@
"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新建服务器\n服务器续费\n服务器升级",
"read_only": 1
+ },
+ {
+ "fieldname": "biz_params",
+ "fieldtype": "JSON",
+ "label": "业务参数",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_rwfr",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_apft",
+ "fieldtype": "Section Break"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2025-03-26 03:34:52.624889",
+ "modified": "2025-08-01 13:16:55.791861",
"modified_by": "Administrator",
"module": "Jcloud",
"name": "Order",
"owner": "Administrator",
+ "pagetype": "PageType",
"permissions": [
{
"create": 1,
@@ -104,6 +121,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..7717460 100644
--- a/jcloud/jcloud/pagetype/order/order.py
+++ b/jcloud/jcloud/pagetype/order/order.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class Order(Document):
+class Order(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -14,9 +14,10 @@ class Order(Document):
if TYPE_CHECKING:
from jingrow.types import DF
+ biz_params: DF.JSON | None
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\u6ce8\u518c", "\u57df\u540d\u7eed\u8d39", "\u65b0\u5efa\u670d\u52a1\u5668", "\u670d\u52a1\u5668\u7eed\u8d39", "\u670d\u52a1\u5668\u5347\u7ea7"]
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
diff --git a/jcloud/jcloud/pagetype/partner_lead/partner_lead.py b/jcloud/jcloud/pagetype/partner_lead/partner_lead.py
index 977359e..73a353e 100644
--- a/jcloud/jcloud/pagetype/partner_lead/partner_lead.py
+++ b/jcloud/jcloud/pagetype/partner_lead/partner_lead.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PartnerLead(Document):
+class PartnerLead(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/partner_payment_payout/partner_payment_payout.py b/jcloud/jcloud/pagetype/partner_payment_payout/partner_payment_payout.py
index 8ab0c34..4cde66e 100644
--- a/jcloud/jcloud/pagetype/partner_payment_payout/partner_payment_payout.py
+++ b/jcloud/jcloud/pagetype/partner_payment_payout/partner_payment_payout.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PartnerPaymentPayout(Document):
+class PartnerPaymentPayout(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/partner_payment_payout_item/partner_payment_payout_item.py b/jcloud/jcloud/pagetype/partner_payment_payout_item/partner_payment_payout_item.py
index 68fded7..8bf0c48 100644
--- a/jcloud/jcloud/pagetype/partner_payment_payout_item/partner_payment_payout_item.py
+++ b/jcloud/jcloud/pagetype/partner_payment_payout_item/partner_payment_payout_item.py
@@ -3,10 +3,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PartnerPaymentPayoutItem(Document):
+class PartnerPaymentPayoutItem(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/payment_dispute/payment_dispute.py b/jcloud/jcloud/pagetype/payment_dispute/payment_dispute.py
index 869bbb8..242763e 100644
--- a/jcloud/jcloud/pagetype/payment_dispute/payment_dispute.py
+++ b/jcloud/jcloud/pagetype/payment_dispute/payment_dispute.py
@@ -4,12 +4,12 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.telegram_message.telegram_message import TelegramMessage
-class PaymentDispute(Document):
+class PaymentDispute(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/payment_gateway/payment_gateway.py b/jcloud/jcloud/pagetype/payment_gateway/payment_gateway.py
index f708c50..5024c6c 100644
--- a/jcloud/jcloud/pagetype/payment_gateway/payment_gateway.py
+++ b/jcloud/jcloud/pagetype/payment_gateway/payment_gateway.py
@@ -3,10 +3,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PaymentGateway(Document):
+class PaymentGateway(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/payment_partner_transaction/payment_partner_transaction.py b/jcloud/jcloud/pagetype/payment_partner_transaction/payment_partner_transaction.py
index 9b26824..c6a657d 100644
--- a/jcloud/jcloud/pagetype/payment_partner_transaction/payment_partner_transaction.py
+++ b/jcloud/jcloud/pagetype/payment_partner_transaction/payment_partner_transaction.py
@@ -3,10 +3,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PaymentPartnerTransaction(Document):
+class PaymentPartnerTransaction(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/payout_order/payout_order.py b/jcloud/jcloud/pagetype/payout_order/payout_order.py
index 3329e3b..b4c1bcd 100644
--- a/jcloud/jcloud/pagetype/payout_order/payout_order.py
+++ b/jcloud/jcloud/pagetype/payout_order/payout_order.py
@@ -6,14 +6,14 @@ from itertools import groupby
from typing import List
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.invoice_item.invoice_item import InvoiceItem
from jcloud.jcloud.pagetype.payout_order_item.payout_order_item import PayoutOrderItem
from jcloud.utils import log_error
-class PayoutOrder(Document):
+class PayoutOrder(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/payout_order_item/payout_order_item.py b/jcloud/jcloud/pagetype/payout_order_item/payout_order_item.py
index 9f91114..1ddb637 100644
--- a/jcloud/jcloud/pagetype/payout_order_item/payout_order_item.py
+++ b/jcloud/jcloud/pagetype/payout_order_item/payout_order_item.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PayoutOrderItem(Document):
+class PayoutOrderItem(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/physical_backup_group/physical_backup_group.py b/jcloud/jcloud/pagetype/physical_backup_group/physical_backup_group.py
index c31ebdf..c684357 100644
--- a/jcloud/jcloud/pagetype/physical_backup_group/physical_backup_group.py
+++ b/jcloud/jcloud/pagetype/physical_backup_group/physical_backup_group.py
@@ -4,12 +4,12 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
-class PhysicalBackupGroup(Document):
+class PhysicalBackupGroup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/physical_backup_group_site/physical_backup_group_site.py b/jcloud/jcloud/pagetype/physical_backup_group_site/physical_backup_group_site.py
index b8b3814..9cdff81 100644
--- a/jcloud/jcloud/pagetype/physical_backup_group_site/physical_backup_group_site.py
+++ b/jcloud/jcloud/pagetype/physical_backup_group_site/physical_backup_group_site.py
@@ -7,12 +7,12 @@ import time
import jingrow
from jingrow.exceptions import DoesNotExistError
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
-class PhysicalBackupGroupSite(Document):
+class PhysicalBackupGroupSite(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/physical_backup_restoration/physical_backup_restoration.py b/jcloud/jcloud/pagetype/physical_backup_restoration/physical_backup_restoration.py
index c15112d..6551f3d 100644
--- a/jcloud/jcloud/pagetype/physical_backup_restoration/physical_backup_restoration.py
+++ b/jcloud/jcloud/pagetype/physical_backup_restoration/physical_backup_restoration.py
@@ -11,7 +11,7 @@ from enum import Enum
from typing import TYPE_CHECKING, Callable
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.jcloud.pagetype.ansible_console.ansible_console import AnsibleAdHoc
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
StepStatus = Enum("StepStatus", ["Pending", "Running", "Skipped", "Success", "Failure"])
-class PhysicalBackupRestoration(Document):
+class PhysicalBackupRestoration(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -97,7 +97,7 @@ class PhysicalBackupRestoration(Document):
for method, wait_for_completion, is_async, is_cleanup_step in methods:
steps.append(
{
- "step": method.__pg__,
+ "step": method.__doc__,
"method": method.__name__,
"wait_for_completion": wait_for_completion,
"is_async": is_async,
diff --git a/jcloud/jcloud/pagetype/physical_backup_restoration_step/physical_backup_restoration_step.py b/jcloud/jcloud/pagetype/physical_backup_restoration_step/physical_backup_restoration_step.py
index bd79d37..86e84a7 100644
--- a/jcloud/jcloud/pagetype/physical_backup_restoration_step/physical_backup_restoration_step.py
+++ b/jcloud/jcloud/pagetype/physical_backup_restoration_step/physical_backup_restoration_step.py
@@ -4,10 +4,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PhysicalBackupRestorationStep(Document):
+class PhysicalBackupRestorationStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/physical_restoration_test/physical_restoration_test.py b/jcloud/jcloud/pagetype/physical_restoration_test/physical_restoration_test.py
index 3635c3b..4a7c864 100644
--- a/jcloud/jcloud/pagetype/physical_restoration_test/physical_restoration_test.py
+++ b/jcloud/jcloud/pagetype/physical_restoration_test/physical_restoration_test.py
@@ -6,14 +6,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
if TYPE_CHECKING:
from jcloud.jcloud.pagetype.physical_backup_group.physical_backup_group import PhysicalBackupGroup
from jcloud.jcloud.pagetype.site_update.site_update import PhysicalBackupRestoration
-class PhysicalRestorationTest(Document):
+class PhysicalRestorationTest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/physical_restoration_test_result/physical_restoration_test_result.py b/jcloud/jcloud/pagetype/physical_restoration_test_result/physical_restoration_test_result.py
index 600a053..3b2c35d 100644
--- a/jcloud/jcloud/pagetype/physical_restoration_test_result/physical_restoration_test_result.py
+++ b/jcloud/jcloud/pagetype/physical_restoration_test_result/physical_restoration_test_result.py
@@ -4,10 +4,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PhysicalRestorationTestResult(Document):
+class PhysicalRestorationTestResult(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/plan_change/plan_change.py b/jcloud/jcloud/pagetype/plan_change/plan_change.py
index f7f70a3..cc85718 100644
--- a/jcloud/jcloud/pagetype/plan_change/plan_change.py
+++ b/jcloud/jcloud/pagetype/plan_change/plan_change.py
@@ -3,10 +3,10 @@
import jingrow
from jingrow import _
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PlanChange(Document):
+class PlanChange(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/plan_feature/plan_feature.py b/jcloud/jcloud/pagetype/plan_feature/plan_feature.py
index 7a8b242..55b719e 100644
--- a/jcloud/jcloud/pagetype/plan_feature/plan_feature.py
+++ b/jcloud/jcloud/pagetype/plan_feature/plan_feature.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PlanFeature(Document):
+class PlanFeature(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/prometheus_alert_rule/prometheus_alert_rule.py b/jcloud/jcloud/pagetype/prometheus_alert_rule/prometheus_alert_rule.py
index f762c44..b8b933f 100644
--- a/jcloud/jcloud/pagetype/prometheus_alert_rule/prometheus_alert_rule.py
+++ b/jcloud/jcloud/pagetype/prometheus_alert_rule/prometheus_alert_rule.py
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import jingrow
import yaml
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.server.server import Server
-class PrometheusAlertRule(Document):
+class PrometheusAlertRule(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/prometheus_alert_rule_cluster/prometheus_alert_rule_cluster.py b/jcloud/jcloud/pagetype/prometheus_alert_rule_cluster/prometheus_alert_rule_cluster.py
index 3fcc35a..9d4d788 100644
--- a/jcloud/jcloud/pagetype/prometheus_alert_rule_cluster/prometheus_alert_rule_cluster.py
+++ b/jcloud/jcloud/pagetype/prometheus_alert_rule_cluster/prometheus_alert_rule_cluster.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PrometheusAlertRuleCluster(Document):
+class PrometheusAlertRuleCluster(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/proxy_server_domain/proxy_server_domain.py b/jcloud/jcloud/pagetype/proxy_server_domain/proxy_server_domain.py
index 53cbea6..81f80b6 100644
--- a/jcloud/jcloud/pagetype/proxy_server_domain/proxy_server_domain.py
+++ b/jcloud/jcloud/pagetype/proxy_server_domain/proxy_server_domain.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ProxyServerDomain(Document):
+class ProxyServerDomain(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/razorpay_payment_record/razorpay_payment_record.py b/jcloud/jcloud/pagetype/razorpay_payment_record/razorpay_payment_record.py
index 46bcb50..377630b 100644
--- a/jcloud/jcloud/pagetype/razorpay_payment_record/razorpay_payment_record.py
+++ b/jcloud/jcloud/pagetype/razorpay_payment_record/razorpay_payment_record.py
@@ -5,14 +5,14 @@ from __future__ import annotations
from datetime import datetime, timedelta
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.team.team import _enqueue_finalize_unpaid_invoices_for_team
from jcloud.utils import log_error
from jcloud.utils.billing import get_razorpay_client
-class RazorpayPaymentRecord(Document):
+class RazorpayPaymentRecord(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/razorpay_webhook_log/razorpay_webhook_log.py b/jcloud/jcloud/pagetype/razorpay_webhook_log/razorpay_webhook_log.py
index 11200b9..5b94543 100644
--- a/jcloud/jcloud/pagetype/razorpay_webhook_log/razorpay_webhook_log.py
+++ b/jcloud/jcloud/pagetype/razorpay_webhook_log/razorpay_webhook_log.py
@@ -3,13 +3,13 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import log_error
from jcloud.utils.billing import get_razorpay_client
-class RazorpayWebhookLog(Document):
+class RazorpayWebhookLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/region/region.py b/jcloud/jcloud/pagetype/region/region.py
index c291a1e..f7ebbb0 100644
--- a/jcloud/jcloud/pagetype/region/region.py
+++ b/jcloud/jcloud/pagetype/region/region.py
@@ -3,10 +3,10 @@
# For license information, please see license.txt
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class Region(Document):
+class Region(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group/release_group.py b/jcloud/jcloud/pagetype/release_group/release_group.py
index 6bf372c..09735ca 100644
--- a/jcloud/jcloud/pagetype/release_group/release_group.py
+++ b/jcloud/jcloud/pagetype/release_group/release_group.py
@@ -13,7 +13,7 @@ import semantic_version as sv
from jingrow import _
from jingrow.core.pagetype.version.version import get_diff
from jingrow.core.utils import find, find_all
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import append_number_if_name_exists
from jingrow.query_builder.functions import Count
from jingrow.utils import cstr, flt, get_url, sbool
@@ -58,7 +58,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.deploy_candidate.deploy_candidate import DeployCandidate
-class ReleaseGroup(Document, TagHelpers):
+class ReleaseGroup(Page, TagHelpers):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group/test_release_group.py b/jcloud/jcloud/pagetype/release_group/test_release_group.py
index 8a780cc..f3bc598 100644
--- a/jcloud/jcloud/pagetype/release_group/test_release_group.py
+++ b/jcloud/jcloud/pagetype/release_group/test_release_group.py
@@ -70,7 +70,7 @@ class TestReleaseGroup(unittest.TestCase):
def test_create_release_group(self):
app = create_test_app("jingrow", "Jingrow Framework")
source = app.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jingrow", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jingrow", "version-12", team=self.team
)
group = new_release_group(
"Test Group",
@@ -83,11 +83,11 @@ class TestReleaseGroup(unittest.TestCase):
def test_create_release_group_set_app_from_source(self):
app1 = create_test_app("jingrow", "Jingrow Framework")
source1 = app1.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jingrow", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jingrow", "version-12", team=self.team
)
app2 = create_test_app("jerp", "JERP")
source2 = app2.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jerp", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jerp", "version-12", team=self.team
)
group = new_release_group(
"Test Group",
@@ -100,7 +100,7 @@ class TestReleaseGroup(unittest.TestCase):
def test_create_release_group_fail_when_first_app_is_not_jingrow(self):
app = create_test_app("jerp", "JERP")
source = app.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jerp", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jerp", "version-12", team=self.team
)
self.assertRaises(
jingrow.ValidationError,
@@ -114,7 +114,7 @@ class TestReleaseGroup(unittest.TestCase):
def test_create_release_group_fail_when_duplicate_apps(self):
app = create_test_app("jingrow", "Jingrow Framework")
source = app.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jingrow", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jingrow", "version-12", team=self.team
)
self.assertRaises(
jingrow.ValidationError,
@@ -131,7 +131,7 @@ class TestReleaseGroup(unittest.TestCase):
def test_create_release_group_fail_when_version_mismatch(self):
app = create_test_app("jingrow", "Jingrow Framework")
source = app.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jingrow", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jingrow", "version-12", team=self.team
)
self.assertRaises(
jingrow.ValidationError,
@@ -145,7 +145,7 @@ class TestReleaseGroup(unittest.TestCase):
def test_create_release_group_fail_with_duplicate_titles(self):
app = create_test_app("jingrow", "Jingrow Framework")
source = app.add_source(
- "v0.2", "http://git.jingrow.com:3000/jingrow/jingrow", "version-12", team=self.team
+ "v0.2", "http://git.jingrow.com/jingrow/jingrow", "version-12", team=self.team
)
new_release_group(
"Test Group",
diff --git a/jcloud/jcloud/pagetype/release_group_app/release_group_app.py b/jcloud/jcloud/pagetype/release_group_app/release_group_app.py
index 3a882e4..867964a 100644
--- a/jcloud/jcloud/pagetype/release_group_app/release_group_app.py
+++ b/jcloud/jcloud/pagetype/release_group_app/release_group_app.py
@@ -4,13 +4,13 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cstr
from jcloud.api.bench import apps
-class ReleaseGroupApp(Document):
+class ReleaseGroupApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group_dependency/release_group_dependency.py b/jcloud/jcloud/pagetype/release_group_dependency/release_group_dependency.py
index f276881..3976214 100644
--- a/jcloud/jcloud/pagetype/release_group_dependency/release_group_dependency.py
+++ b/jcloud/jcloud/pagetype/release_group_dependency/release_group_dependency.py
@@ -2,11 +2,11 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import is_owned_by_team
-class ReleaseGroupDependency(Document):
+class ReleaseGroupDependency(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group_mount/release_group_mount.py b/jcloud/jcloud/pagetype/release_group_mount/release_group_mount.py
index 8932344..d6d4562 100644
--- a/jcloud/jcloud/pagetype/release_group_mount/release_group_mount.py
+++ b/jcloud/jcloud/pagetype/release_group_mount/release_group_mount.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ReleaseGroupMount(Document):
+class ReleaseGroupMount(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group_package/release_group_package.py b/jcloud/jcloud/pagetype/release_group_package/release_group_package.py
index 566f7c7..ac04600 100644
--- a/jcloud/jcloud/pagetype/release_group_package/release_group_package.py
+++ b/jcloud/jcloud/pagetype/release_group_package/release_group_package.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ReleaseGroupPackage(Document):
+class ReleaseGroupPackage(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group_server/release_group_server.py b/jcloud/jcloud/pagetype/release_group_server/release_group_server.py
index 709a3fb..975d81d 100644
--- a/jcloud/jcloud/pagetype/release_group_server/release_group_server.py
+++ b/jcloud/jcloud/pagetype/release_group_server/release_group_server.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ReleaseGroupServer(Document):
+class ReleaseGroupServer(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/release_group_variable/release_group_variable.py b/jcloud/jcloud/pagetype/release_group_variable/release_group_variable.py
index 06de3c9..0d556bc 100644
--- a/jcloud/jcloud/pagetype/release_group_variable/release_group_variable.py
+++ b/jcloud/jcloud/pagetype/release_group_variable/release_group_variable.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ReleaseGroupVariable(Document):
+class ReleaseGroupVariable(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/remote_file/remote_file.py b/jcloud/jcloud/pagetype/remote_file/remote_file.py
index efff066..34d39d9 100644
--- a/jcloud/jcloud/pagetype/remote_file/remote_file.py
+++ b/jcloud/jcloud/pagetype/remote_file/remote_file.py
@@ -9,7 +9,7 @@ import pprint
import jingrow
import requests
from boto3 import client, resource
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.password import get_decrypted_password
@@ -150,7 +150,7 @@ def delete_remote_backup_objects(remote_files):
return remote_files
-class RemoteFile(Document):
+class RemoteFile(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/remote_operation_log/remote_operation_log.py b/jcloud/jcloud/pagetype/remote_operation_log/remote_operation_log.py
index 3149929..f420b94 100644
--- a/jcloud/jcloud/pagetype/remote_operation_log/remote_operation_log.py
+++ b/jcloud/jcloud/pagetype/remote_operation_log/remote_operation_log.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class RemoteOperationLog(Document):
+class RemoteOperationLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/resource_tag/resource_tag.py b/jcloud/jcloud/pagetype/resource_tag/resource_tag.py
index ee2c2a4..cbfe862 100644
--- a/jcloud/jcloud/pagetype/resource_tag/resource_tag.py
+++ b/jcloud/jcloud/pagetype/resource_tag/resource_tag.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ResourceTag(Document):
+class ResourceTag(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/root_domain/root_domain.py b/jcloud/jcloud/pagetype/root_domain/root_domain.py
index 0fae4c5..2a2619c 100644
--- a/jcloud/jcloud/pagetype/root_domain/root_domain.py
+++ b/jcloud/jcloud/pagetype/root_domain/root_domain.py
@@ -10,12 +10,12 @@ import boto3
import jingrow
import requests
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import log_error
-class RootDomain(Document):
+class RootDomain(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/scheduled_auto_update_log/scheduled_auto_update_log.py b/jcloud/jcloud/pagetype/scheduled_auto_update_log/scheduled_auto_update_log.py
index e7af438..fa8b733 100644
--- a/jcloud/jcloud/pagetype/scheduled_auto_update_log/scheduled_auto_update_log.py
+++ b/jcloud/jcloud/pagetype/scheduled_auto_update_log/scheduled_auto_update_log.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ScheduledAutoUpdateLog(Document):
+class ScheduledAutoUpdateLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/security_update/security_update.py b/jcloud/jcloud/pagetype/security_update/security_update.py
index 8a87611..80c627b 100644
--- a/jcloud/jcloud/pagetype/security_update/security_update.py
+++ b/jcloud/jcloud/pagetype/security_update/security_update.py
@@ -4,14 +4,14 @@
import re
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import now_datetime
from jcloud.runner import Ansible
from jcloud.utils import log_error
-class SecurityUpdate(Document):
+class SecurityUpdate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/security_update_check/security_update_check.py b/jcloud/jcloud/pagetype/security_update_check/security_update_check.py
index f4836a6..50f4b5d 100644
--- a/jcloud/jcloud/pagetype/security_update_check/security_update_check.py
+++ b/jcloud/jcloud/pagetype/security_update_check/security_update_check.py
@@ -2,14 +2,14 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.telegram_message.telegram_message import TelegramMessage
from jcloud.runner import Ansible
from jcloud.utils import log_error
-class SecurityUpdateCheck(Document):
+class SecurityUpdateCheck(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/self_hosted_server/self_hosted_server.py b/jcloud/jcloud/pagetype/self_hosted_server/self_hosted_server.py
index 03bb6f1..00e286d 100644
--- a/jcloud/jcloud/pagetype/self_hosted_server/self_hosted_server.py
+++ b/jcloud/jcloud/pagetype/self_hosted_server/self_hosted_server.py
@@ -6,7 +6,7 @@ from __future__ import annotations
import json
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import make_autoname
from jcloud.runner import Ansible
@@ -15,7 +15,7 @@ from jcloud.utils import log_error
# from tldextract import extract as sdext
-class SelfHostedServer(Document):
+class SelfHostedServer(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/self_hosted_site_apps/self_hosted_site_apps.py b/jcloud/jcloud/pagetype/self_hosted_site_apps/self_hosted_site_apps.py
index 8490501..c4eb6ab 100644
--- a/jcloud/jcloud/pagetype/self_hosted_site_apps/self_hosted_site_apps.py
+++ b/jcloud/jcloud/pagetype/self_hosted_site_apps/self_hosted_site_apps.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SelfHostedSiteApps(Document):
+class SelfHostedSiteApps(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/serial_console_log/serial_console_log.py b/jcloud/jcloud/pagetype/serial_console_log/serial_console_log.py
index 4e10a3a..efe3047 100644
--- a/jcloud/jcloud/pagetype/serial_console_log/serial_console_log.py
+++ b/jcloud/jcloud/pagetype/serial_console_log/serial_console_log.py
@@ -8,12 +8,12 @@ from io import StringIO
import jingrow
import pexpect
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.deploy_candidate.deploy_candidate import ansi_escape
-class SerialConsoleLog(Document):
+class SerialConsoleLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/server/patches/set_plan_and_subscription.py b/jcloud/jcloud/pagetype/server/patches/set_plan_and_subscription.py
index 816da38..90dd7ef 100644
--- a/jcloud/jcloud/pagetype/server/patches/set_plan_and_subscription.py
+++ b/jcloud/jcloud/pagetype/server/patches/set_plan_and_subscription.py
@@ -5,8 +5,8 @@ import jingrow
def execute():
- DOCTYPES = ["Server", "Database Server"]
- for pagetype in DOCTYPES:
+ PAGETYPES = ["Server", "Database Server"]
+ for pagetype in PAGETYPES:
server_names = jingrow.get_all(
pagetype,
{"status": ("!=", "Archived"), "virtual_machine": ("is", "set")},
diff --git a/jcloud/jcloud/pagetype/server/server.py b/jcloud/jcloud/pagetype/server/server.py
index 783224b..a6f95cf 100644
--- a/jcloud/jcloud/pagetype/server/server.py
+++ b/jcloud/jcloud/pagetype/server/server.py
@@ -14,7 +14,7 @@ import jingrow
from jingrow import _
from jingrow.core.utils import find, find_all
from jingrow.installer import subprocess
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cint
from jingrow.utils.user import is_system_user
@@ -32,7 +32,7 @@ if typing.TYPE_CHECKING:
from jcloud.jcloud.pagetype.virtual_machine.virtual_machine import VirtualMachine
-class BaseServer(Document, TagHelpers):
+class BaseServer(Page, TagHelpers):
dashboard_fields = (
"title",
"plan",
@@ -459,7 +459,7 @@ class BaseServer(Document, TagHelpers):
def get_agent_repository_url(self):
settings = jingrow.get_single("Jcloud Settings")
repository_owner = settings.agent_repository_owner or "jingrow"
- return f"http://git.jingrow.com:3000/{repository_owner}/agent"
+ return f"http://git.jingrow.com/{repository_owner}/agent"
def get_agent_repository_branch(self):
settings = jingrow.get_single("Jcloud Settings")
diff --git a/jcloud/jcloud/pagetype/server_mount/server_mount.py b/jcloud/jcloud/pagetype/server_mount/server_mount.py
index 44399e8..cc5e22f 100644
--- a/jcloud/jcloud/pagetype/server_mount/server_mount.py
+++ b/jcloud/jcloud/pagetype/server_mount/server_mount.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ServerMount(Document):
+class ServerMount(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
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
diff --git a/jcloud/jcloud/pagetype/silenced_alert/silenced_alert.py b/jcloud/jcloud/pagetype/silenced_alert/silenced_alert.py
index f51b5af..204d3ff 100644
--- a/jcloud/jcloud/pagetype/silenced_alert/silenced_alert.py
+++ b/jcloud/jcloud/pagetype/silenced_alert/silenced_alert.py
@@ -7,13 +7,13 @@ from datetime import timezone
import jingrow
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.data import format_duration, get_datetime
from jcloud.utils import log_error
-class SilencedAlert(Document):
+class SilencedAlert(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site/site.py b/jcloud/jcloud/pagetype/site/site.py
index 81b2830..ec4999f 100644
--- a/jcloud/jcloud/pagetype/site/site.py
+++ b/jcloud/jcloud/pagetype/site/site.py
@@ -19,7 +19,7 @@ import requests
from jingrow import _
from jingrow.core.utils import find
from jingrow.jingrowclient import JingrowClient, JingrowException
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import append_number_if_name_exists
from jingrow.utils import (
add_to_date,
@@ -107,7 +107,7 @@ PAGETYPE_SERVER_TYPE_MAP = {
}
-class Site(Document, TagHelpers):
+class Site(Page, TagHelpers):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -758,7 +758,7 @@ class Site(Document, TagHelpers):
create_site_status_update_webhook_event(self.name)
- def remove_dns_record(self, domain: Document, proxy_server: str, site: str):
+ def remove_dns_record(self, domain: Page, proxy_server: str, site: str):
"""Remove dns record of site pointing to proxy."""
_change_dns_record(method="DELETE", domain=domain, proxy_server=proxy_server, record_name=site)
@@ -2687,7 +2687,6 @@ class Site(Document, TagHelpers):
{
"subdomain": subdomain,
"domain": domain,
- "status": ("!=", "Archived"),
},
)
)
@@ -2784,8 +2783,8 @@ class Site(Document, TagHelpers):
"group": "危险操作",
},
{
- "action": "从现有站点恢复",
- "description": "从另一个站点恢复数据库、公共和私有文件",
+ "action": "从指定站点恢复",
+ "description": "从指定站点的备份文件恢复数据库、公共和私有文件",
"button_label": "恢复",
"pg_method": "restore_site_from_files",
"group": "危险操作",
diff --git a/jcloud/jcloud/pagetype/site_activity/site_activity.py b/jcloud/jcloud/pagetype/site_activity/site_activity.py
index a9a90dc..76b1956 100644
--- a/jcloud/jcloud/pagetype/site_activity/site_activity.py
+++ b/jcloud/jcloud/pagetype/site_activity/site_activity.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteActivity(Document):
+class SiteActivity(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_analytics/site_analytics.py b/jcloud/jcloud/pagetype/site_analytics/site_analytics.py
index edfe909..488811e 100644
--- a/jcloud/jcloud/pagetype/site_analytics/site_analytics.py
+++ b/jcloud/jcloud/pagetype/site_analytics/site_analytics.py
@@ -2,12 +2,12 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.query_builder import Interval
from jingrow.query_builder.functions import Now
-class SiteAnalytics(Document):
+class SiteAnalytics(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_analytics_active/site_analytics_active.py b/jcloud/jcloud/pagetype/site_analytics_active/site_analytics_active.py
index 22d2f3a..9beb69e 100644
--- a/jcloud/jcloud/pagetype/site_analytics_active/site_analytics_active.py
+++ b/jcloud/jcloud/pagetype/site_analytics_active/site_analytics_active.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteAnalyticsActive(Document):
+class SiteAnalyticsActive(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_analytics_app/site_analytics_app.py b/jcloud/jcloud/pagetype/site_analytics_app/site_analytics_app.py
index fe3cf0a..c2934eb 100644
--- a/jcloud/jcloud/pagetype/site_analytics_app/site_analytics_app.py
+++ b/jcloud/jcloud/pagetype/site_analytics_app/site_analytics_app.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteAnalyticsApp(Document):
+class SiteAnalyticsApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_analytics_login/site_analytics_login.py b/jcloud/jcloud/pagetype/site_analytics_login/site_analytics_login.py
index 5e8f4ad..434a476 100644
--- a/jcloud/jcloud/pagetype/site_analytics_login/site_analytics_login.py
+++ b/jcloud/jcloud/pagetype/site_analytics_login/site_analytics_login.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteAnalyticsLogin(Document):
+class SiteAnalyticsLogin(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_analytics_pagetype/site_analytics_pagetype.py b/jcloud/jcloud/pagetype/site_analytics_pagetype/site_analytics_pagetype.py
index 6676b2a..22348e7 100644
--- a/jcloud/jcloud/pagetype/site_analytics_pagetype/site_analytics_pagetype.py
+++ b/jcloud/jcloud/pagetype/site_analytics_pagetype/site_analytics_pagetype.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteAnalyticsPageType(Document):
+class SiteAnalyticsPageType(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_analytics_user/site_analytics_user.py b/jcloud/jcloud/pagetype/site_analytics_user/site_analytics_user.py
index 5338d9a..6baf794 100644
--- a/jcloud/jcloud/pagetype/site_analytics_user/site_analytics_user.py
+++ b/jcloud/jcloud/pagetype/site_analytics_user/site_analytics_user.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteAnalyticsUser(Document):
+class SiteAnalyticsUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_app/site_app.py b/jcloud/jcloud/pagetype/site_app/site_app.py
index 8fa054b..9c7d7d1 100644
--- a/jcloud/jcloud/pagetype/site_app/site_app.py
+++ b/jcloud/jcloud/pagetype/site_app/site_app.py
@@ -3,13 +3,13 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cstr
from jcloud.api.site import get_installed_apps
-class SiteApp(Document):
+class SiteApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_backup/site_backup.py b/jcloud/jcloud/pagetype/site_backup/site_backup.py
index 2cedc40..e0472f3 100644
--- a/jcloud/jcloud/pagetype/site_backup/site_backup.py
+++ b/jcloud/jcloud/pagetype/site_backup/site_backup.py
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
import jingrow
import jingrow.utils
from jingrow.desk.pagetype.tag.tag import add_tag
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.jcloud.pagetype.ansible_console.ansible_console import AnsibleAdHoc
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
from datetime import datetime
-class SiteBackup(Document):
+class SiteBackup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_config/site_config.py b/jcloud/jcloud/pagetype/site_config/site_config.py
index 37634aa..a0a06c1 100644
--- a/jcloud/jcloud/pagetype/site_config/site_config.py
+++ b/jcloud/jcloud/pagetype/site_config/site_config.py
@@ -4,10 +4,10 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class Config(Document):
+class Config(Page):
dashboard_fields = ["key", "type", "value"]
def get_type(self):
diff --git a/jcloud/jcloud/pagetype/site_config_key/site_config_key.py b/jcloud/jcloud/pagetype/site_config_key/site_config_key.py
index 0587959..3860b21 100644
--- a/jcloud/jcloud/pagetype/site_config_key/site_config_key.py
+++ b/jcloud/jcloud/pagetype/site_config_key/site_config_key.py
@@ -3,10 +3,10 @@
# For license information, please see license.txt
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteConfigKey(Document):
+class SiteConfigKey(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_config_key_blacklist/site_config_key_blacklist.py b/jcloud/jcloud/pagetype/site_config_key_blacklist/site_config_key_blacklist.py
index a8d389e..e20aa41 100644
--- a/jcloud/jcloud/pagetype/site_config_key_blacklist/site_config_key_blacklist.py
+++ b/jcloud/jcloud/pagetype/site_config_key_blacklist/site_config_key_blacklist.py
@@ -3,10 +3,10 @@
# For license information, please see license.txt
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteConfigKeyBlacklist(Document):
+class SiteConfigKeyBlacklist(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_database_table_permission/site_database_table_permission.py b/jcloud/jcloud/pagetype/site_database_table_permission/site_database_table_permission.py
index 9297660..e7a1751 100644
--- a/jcloud/jcloud/pagetype/site_database_table_permission/site_database_table_permission.py
+++ b/jcloud/jcloud/pagetype/site_database_table_permission/site_database_table_permission.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteDatabaseTablePermission(Document):
+class SiteDatabaseTablePermission(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_database_user/site_database_user.py b/jcloud/jcloud/pagetype/site_database_user/site_database_user.py
index ed8a057..9a99d38 100644
--- a/jcloud/jcloud/pagetype/site_database_user/site_database_user.py
+++ b/jcloud/jcloud/pagetype/site_database_user/site_database_user.py
@@ -6,7 +6,7 @@ import re
from collections import Counter
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.api.client import dashboard_whitelist
@@ -14,7 +14,7 @@ from jcloud.overrides import get_permission_query_conditions_for_pagetype
from jcloud.jcloud.pagetype.site_activity.site_activity import log_site_activity
-class SiteDatabaseUser(Document):
+class SiteDatabaseUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_domain/site_domain.py b/jcloud/jcloud/pagetype/site_domain/site_domain.py
index aa44175..211ac6f 100644
--- a/jcloud/jcloud/pagetype/site_domain/site_domain.py
+++ b/jcloud/jcloud/pagetype/site_domain/site_domain.py
@@ -7,7 +7,7 @@ from typing import ClassVar
import jingrow
import rq
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.api.site import check_dns
@@ -24,7 +24,7 @@ from jcloud.utils.dns import create_dns_record
from jcloud.utils.jobs import has_job_timeout_exceeded
-class SiteDomain(Document):
+class SiteDomain(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_group_deploy/site_group_deploy.py b/jcloud/jcloud/pagetype/site_group_deploy/site_group_deploy.py
index 6b98f95..fb2d174 100644
--- a/jcloud/jcloud/pagetype/site_group_deploy/site_group_deploy.py
+++ b/jcloud/jcloud/pagetype/site_group_deploy/site_group_deploy.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteGroupDeploy(Document):
+class SiteGroupDeploy(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_group_deploy_app/site_group_deploy_app.py b/jcloud/jcloud/pagetype/site_group_deploy_app/site_group_deploy_app.py
index fbe0999..7e893f2 100644
--- a/jcloud/jcloud/pagetype/site_group_deploy_app/site_group_deploy_app.py
+++ b/jcloud/jcloud/pagetype/site_group_deploy_app/site_group_deploy_app.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteGroupDeployApp(Document):
+class SiteGroupDeployApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_migration/site_migration.py b/jcloud/jcloud/pagetype/site_migration/site_migration.py
index b1768f1..27432a6 100644
--- a/jcloud/jcloud/pagetype/site_migration/site_migration.py
+++ b/jcloud/jcloud/pagetype/site_migration/site_migration.py
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.agent import Agent
from jcloud.exceptions import (
@@ -48,7 +48,7 @@ def get_ongoing_migration(site: Link, scheduled=False):
return jingrow.db.exists("Site Migration", {"site": site, "status": ("in", ongoing_statuses)})
-class SiteMigration(Document):
+class SiteMigration(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -250,7 +250,7 @@ class SiteMigration(Document):
def _add_setup_redirects_step(self):
step = {
- "step_title": self.setup_redirects.__pg__,
+ "step_title": self.setup_redirects.__doc__,
"status": "Pending",
"method_name": self.setup_redirects.__name__,
}
@@ -332,7 +332,7 @@ class SiteMigration(Document):
self.append(
"steps",
{
- "step_title": self.archive_site_on_destination_server.__pg__,
+ "step_title": self.archive_site_on_destination_server.__doc__,
"method_name": self.archive_site_on_destination_server.__name__,
"status": "Pending",
},
@@ -418,47 +418,47 @@ class SiteMigration(Document):
def add_steps_for_cluster_migration(self):
steps = [
{
- "step_title": self.deactivate_site_on_source_server.__pg__,
+ "step_title": self.deactivate_site_on_source_server.__doc__,
"method_name": self.deactivate_site_on_source_server.__name__,
"status": "Pending",
},
{
- "step_title": self.backup_source_site.__pg__,
+ "step_title": self.backup_source_site.__doc__,
"method_name": self.backup_source_site.__name__,
"status": "Pending",
},
{
- "step_title": self.restore_site_on_destination_server.__pg__,
+ "step_title": self.restore_site_on_destination_server.__doc__,
"method_name": self.restore_site_on_destination_server.__name__,
"status": "Pending",
},
{
- "step_title": self.restore_site_on_destination_proxy.__pg__,
+ "step_title": self.restore_site_on_destination_proxy.__doc__,
"method_name": self.restore_site_on_destination_proxy.__name__,
"status": "Pending",
},
{
- "step_title": self.remove_site_from_source_proxy.__pg__,
+ "step_title": self.remove_site_from_source_proxy.__doc__,
"method_name": self.remove_site_from_source_proxy.__name__,
"status": "Pending",
},
{
- "step_title": self.archive_site_on_source.__pg__,
+ "step_title": self.archive_site_on_source.__doc__,
"method_name": self.archive_site_on_source.__name__,
"status": "Pending",
},
{
- "step_title": self.update_site_record_fields.__pg__,
+ "step_title": self.update_site_record_fields.__doc__,
"method_name": self.update_site_record_fields.__name__,
"status": "Pending",
},
{
- "step_title": self.reset_site_status_on_destination.__pg__,
+ "step_title": self.reset_site_status_on_destination.__doc__,
"method_name": self.reset_site_status_on_destination.__name__,
"status": "Pending",
},
{
- "step_title": self.adjust_plan_if_required.__pg__,
+ "step_title": self.adjust_plan_if_required.__doc__,
"method_name": self.adjust_plan_if_required.__name__,
"status": "Pending",
},
@@ -469,47 +469,47 @@ class SiteMigration(Document):
def add_steps_for_server_migration(self):
steps = [
{
- "step_title": self.deactivate_site_on_source_server.__pg__,
+ "step_title": self.deactivate_site_on_source_server.__doc__,
"method_name": self.deactivate_site_on_source_server.__name__,
"status": "Pending",
},
{
- "step_title": self.backup_source_site.__pg__,
+ "step_title": self.backup_source_site.__doc__,
"method_name": self.backup_source_site.__name__,
"status": "Pending",
},
{
- "step_title": self.restore_site_on_destination_server.__pg__,
+ "step_title": self.restore_site_on_destination_server.__doc__,
"method_name": self.restore_site_on_destination_server.__name__,
"status": "Pending",
},
{
- "step_title": self.archive_site_on_source.__pg__,
+ "step_title": self.archive_site_on_source.__doc__,
"method_name": self.archive_site_on_source.__name__,
"status": "Pending",
},
{
- "step_title": self.remove_site_from_source_proxy.__pg__,
+ "step_title": self.remove_site_from_source_proxy.__doc__,
"method_name": self.remove_site_from_source_proxy.__name__,
"status": "Pending",
},
{
- "step_title": self.restore_site_on_destination_proxy.__pg__,
+ "step_title": self.restore_site_on_destination_proxy.__doc__,
"method_name": self.restore_site_on_destination_proxy.__name__,
"status": "Pending",
},
{
- "step_title": self.update_site_record_fields.__pg__,
+ "step_title": self.update_site_record_fields.__doc__,
"method_name": self.update_site_record_fields.__name__,
"status": "Pending",
},
{
- "step_title": self.reset_site_status_on_destination.__pg__,
+ "step_title": self.reset_site_status_on_destination.__doc__,
"method_name": self.reset_site_status_on_destination.__name__,
"status": "Pending",
},
{
- "step_title": self.adjust_plan_if_required.__pg__,
+ "step_title": self.adjust_plan_if_required.__doc__,
"method_name": self.adjust_plan_if_required.__name__,
"status": "Pending",
},
diff --git a/jcloud/jcloud/pagetype/site_migration_step/site_migration_step.py b/jcloud/jcloud/pagetype/site_migration_step/site_migration_step.py
index 99f5076..ebfb6d9 100644
--- a/jcloud/jcloud/pagetype/site_migration_step/site_migration_step.py
+++ b/jcloud/jcloud/pagetype/site_migration_step/site_migration_step.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteMigrationStep(Document):
+class SiteMigrationStep(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_plan/plan.py b/jcloud/jcloud/pagetype/site_plan/plan.py
index 50c2876..c9cd8d9 100644
--- a/jcloud/jcloud/pagetype/site_plan/plan.py
+++ b/jcloud/jcloud/pagetype/site_plan/plan.py
@@ -1,11 +1,11 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import rounded
from jcloud.utils import group_children_in_result
-class Plan(Document):
+class Plan(Page):
def get_price_for_interval(self, interval, currency):
price_per_day = self.get_price_per_day(currency)
diff --git a/jcloud/jcloud/pagetype/site_plan_allowed_app/site_plan_allowed_app.py b/jcloud/jcloud/pagetype/site_plan_allowed_app/site_plan_allowed_app.py
index 2a73d71..9d3babb 100644
--- a/jcloud/jcloud/pagetype/site_plan_allowed_app/site_plan_allowed_app.py
+++ b/jcloud/jcloud/pagetype/site_plan_allowed_app/site_plan_allowed_app.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SitePlanAllowedApp(Document):
+class SitePlanAllowedApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_plan_change/site_plan_change.py b/jcloud/jcloud/pagetype/site_plan_change/site_plan_change.py
index af00fce..9100a9b 100644
--- a/jcloud/jcloud/pagetype/site_plan_change/site_plan_change.py
+++ b/jcloud/jcloud/pagetype/site_plan_change/site_plan_change.py
@@ -5,12 +5,12 @@ from __future__ import annotations
import jingrow
from jingrow import _
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils.webhook import create_webhook_event
-class SitePlanChange(Document):
+class SitePlanChange(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_plan_release_group/site_plan_release_group.py b/jcloud/jcloud/pagetype/site_plan_release_group/site_plan_release_group.py
index 442af55..d118af4 100644
--- a/jcloud/jcloud/pagetype/site_plan_release_group/site_plan_release_group.py
+++ b/jcloud/jcloud/pagetype/site_plan_release_group/site_plan_release_group.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SitePlanReleaseGroup(Document):
+class SitePlanReleaseGroup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_replication/site_replication.py b/jcloud/jcloud/pagetype/site_replication/site_replication.py
index ec681b3..06d07d1 100644
--- a/jcloud/jcloud/pagetype/site_replication/site_replication.py
+++ b/jcloud/jcloud/pagetype/site_replication/site_replication.py
@@ -4,13 +4,13 @@
from typing import List
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.site import _new
from jcloud.jcloud.pagetype.site.site import prepare_site
-class SiteReplication(Document):
+class SiteReplication(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -70,12 +70,12 @@ class SiteReplication(Document):
jingrow.log_error("Site Replication Error")
@classmethod
- def get_all_running_site_replications(cls) -> List[Document]:
+ def get_all_running_site_replications(cls) -> List[Page]:
replications = jingrow.get_all(cls.pagetype, dict(status="Running"), pluck="name")
return cls.get_docs(replications)
@classmethod
- def get_docs(cls, names: List[str]) -> List[Document]:
+ def get_docs(cls, names: List[str]) -> List[Page]:
return [jingrow.get_pg(cls.pagetype, name) for name in names]
diff --git a/jcloud/jcloud/pagetype/site_update/site_update.py b/jcloud/jcloud/pagetype/site_update/site_update.py
index 7f640c7..4b2e9cf 100644
--- a/jcloud/jcloud/pagetype/site_update/site_update.py
+++ b/jcloud/jcloud/pagetype/site_update/site_update.py
@@ -12,7 +12,7 @@ import jingrow
import jingrow.utils
import pytz
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import convert_utc_to_system_timezone
from jingrow.utils.caching import site_cache
from jingrow.utils.data import cint
@@ -32,7 +32,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.site.site import Site
-class SiteUpdate(Document):
+class SiteUpdate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_usage/site_usage.py b/jcloud/jcloud/pagetype/site_usage/site_usage.py
index 02fd690..7add4c4 100644
--- a/jcloud/jcloud/pagetype/site_usage/site_usage.py
+++ b/jcloud/jcloud/pagetype/site_usage/site_usage.py
@@ -4,12 +4,12 @@
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.query_builder import Interval
from jingrow.query_builder.functions import Now
-class SiteUsage(Document):
+class SiteUsage(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_user/site_user.py b/jcloud/jcloud/pagetype/site_user/site_user.py
index 4e9cd67..065fa10 100644
--- a/jcloud/jcloud/pagetype/site_user/site_user.py
+++ b/jcloud/jcloud/pagetype/site_user/site_user.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteUser(Document):
+class SiteUser(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/site_user_session/site_user_session.py b/jcloud/jcloud/pagetype/site_user_session/site_user_session.py
index b01d6e9..9d49008 100644
--- a/jcloud/jcloud/pagetype/site_user_session/site_user_session.py
+++ b/jcloud/jcloud/pagetype/site_user_session/site_user_session.py
@@ -4,10 +4,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteUserSession(Document):
+class SiteUserSession(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -48,7 +48,7 @@ class SiteUserSession(Document):
args.update(
{
"otp": self.otp,
- "image_path": "http://git.jingrow.com:3000/jingrow/gameplan/assets/9355208/447035d0-0686-41d2-910a-a3d21928ab94",
+ "image_path": "http://git.jingrow.com/jingrow/gameplan/assets/9355208/447035d0-0686-41d2-910a-a3d21928ab94",
}
)
diff --git a/jcloud/jcloud/pagetype/sql_playground_log/sql_playground_log.py b/jcloud/jcloud/pagetype/sql_playground_log/sql_playground_log.py
index a2f8935..b0d0b53 100644
--- a/jcloud/jcloud/pagetype/sql_playground_log/sql_playground_log.py
+++ b/jcloud/jcloud/pagetype/sql_playground_log/sql_playground_log.py
@@ -2,12 +2,12 @@
# For license information, please see license.txt
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.overrides import get_permission_query_conditions_for_pagetype
-class SQLPlaygroundLog(Document):
+class SQLPlaygroundLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ssh_certificate/ssh_certificate.py b/jcloud/jcloud/pagetype/ssh_certificate/ssh_certificate.py
index 1f1881a..f558c72 100644
--- a/jcloud/jcloud/pagetype/ssh_certificate/ssh_certificate.py
+++ b/jcloud/jcloud/pagetype/ssh_certificate/ssh_certificate.py
@@ -11,7 +11,7 @@ import subprocess
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import log_error
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
)
-class SSHCertificate(Document):
+class SSHCertificate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ssh_certificate_authority/ssh_certificate_authority.py b/jcloud/jcloud/pagetype/ssh_certificate_authority/ssh_certificate_authority.py
index d8dd32f..f2dafd9 100644
--- a/jcloud/jcloud/pagetype/ssh_certificate_authority/ssh_certificate_authority.py
+++ b/jcloud/jcloud/pagetype/ssh_certificate_authority/ssh_certificate_authority.py
@@ -8,13 +8,13 @@ import subprocess
import docker
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import cint
from jcloud.utils import log_error
-class SSHCertificateAuthority(Document):
+class SSHCertificateAuthority(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/ssh_key/ssh_key.py b/jcloud/jcloud/pagetype/ssh_key/ssh_key.py
index e589d6f..9e2fadc 100644
--- a/jcloud/jcloud/pagetype/ssh_key/ssh_key.py
+++ b/jcloud/jcloud/pagetype/ssh_key/ssh_key.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SSHKey(Document):
+class SSHKey(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/storage_integration_bucket/storage_integration_bucket.py b/jcloud/jcloud/pagetype/storage_integration_bucket/storage_integration_bucket.py
index a752fe4..70132f2 100644
--- a/jcloud/jcloud/pagetype/storage_integration_bucket/storage_integration_bucket.py
+++ b/jcloud/jcloud/pagetype/storage_integration_bucket/storage_integration_bucket.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class StorageIntegrationBucket(Document):
+class StorageIntegrationBucket(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/storage_integration_subscription/storage_integration_subscription.py b/jcloud/jcloud/pagetype/storage_integration_subscription/storage_integration_subscription.py
index 0db17b7..8a24aa6 100644
--- a/jcloud/jcloud/pagetype/storage_integration_subscription/storage_integration_subscription.py
+++ b/jcloud/jcloud/pagetype/storage_integration_subscription/storage_integration_subscription.py
@@ -6,13 +6,13 @@ import math
import boto3
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.password import get_decrypted_password
from jcloud.agent import Agent
-class StorageIntegrationSubscription(Document):
+class StorageIntegrationSubscription(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/stripe_micro_charge_record/stripe_micro_charge_record.py b/jcloud/jcloud/pagetype/stripe_micro_charge_record/stripe_micro_charge_record.py
index 8aa0eb5..1b57104 100644
--- a/jcloud/jcloud/pagetype/stripe_micro_charge_record/stripe_micro_charge_record.py
+++ b/jcloud/jcloud/pagetype/stripe_micro_charge_record/stripe_micro_charge_record.py
@@ -2,12 +2,12 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils.billing import get_stripe
-class StripeMicroChargeRecord(Document):
+class StripeMicroChargeRecord(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/stripe_payment_event/stripe_payment_event.py b/jcloud/jcloud/pagetype/stripe_payment_event/stripe_payment_event.py
index 4afa126..7ac0179 100644
--- a/jcloud/jcloud/pagetype/stripe_payment_event/stripe_payment_event.py
+++ b/jcloud/jcloud/pagetype/stripe_payment_event/stripe_payment_event.py
@@ -5,13 +5,13 @@ from __future__ import annotations
from datetime import datetime
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils.billing import convert_stripe_money
from jcloud.api.billing import get_stripe
-class StripePaymentEvent(Document):
+class StripePaymentEvent(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/stripe_payment_method/stripe_payment_method.py b/jcloud/jcloud/pagetype/stripe_payment_method/stripe_payment_method.py
index e66e426..d18ccde 100644
--- a/jcloud/jcloud/pagetype/stripe_payment_method/stripe_payment_method.py
+++ b/jcloud/jcloud/pagetype/stripe_payment_method/stripe_payment_method.py
@@ -5,7 +5,7 @@
import jingrow
from jingrow.contacts.address_and_contact import load_address_and_contact
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.billing import get_stripe
from jcloud.api.client import dashboard_whitelist
@@ -14,7 +14,7 @@ from jcloud.utils import log_error
from jcloud.utils.telemetry import capture
-class StripePaymentMethod(Document):
+class StripePaymentMethod(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/stripe_webhook_log/stripe_webhook_log.py b/jcloud/jcloud/pagetype/stripe_webhook_log/stripe_webhook_log.py
index d7e11c4..4fd5176 100644
--- a/jcloud/jcloud/pagetype/stripe_webhook_log/stripe_webhook_log.py
+++ b/jcloud/jcloud/pagetype/stripe_webhook_log/stripe_webhook_log.py
@@ -6,7 +6,7 @@ import re
from datetime import datetime
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
import jcloud.utils
from jcloud.api.billing import get_stripe
@@ -16,7 +16,7 @@ class InvalidStripeWebhookEvent(Exception):
http_status_code = 400
-class StripeWebhookLog(Document):
+class StripeWebhookLog(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/subscription/subscription.py b/jcloud/jcloud/pagetype/subscription/subscription.py
index 587825f..8f5c055 100644
--- a/jcloud/jcloud/pagetype/subscription/subscription.py
+++ b/jcloud/jcloud/pagetype/subscription/subscription.py
@@ -5,7 +5,7 @@ from __future__ import annotations
import jingrow
import rq
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.query_builder.functions import Coalesce, Count
from jingrow.utils import cint, flt
@@ -15,7 +15,7 @@ from jcloud.utils import log_error
from jcloud.utils.jobs import has_job_timeout_exceeded
-class Subscription(Document):
+class Subscription(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -293,14 +293,14 @@ def paid_plans():
"price_cny": (">", 0),
"enabled": 1,
}
- doctypes = [
+ pagetypes = [
"Site Plan",
"Marketplace App Plan",
"Server Plan",
"Server Storage Plan",
"Cluster Plan",
]
- for pagetype in doctypes:
+ for pagetype in pagetypes:
paid_plans += jingrow.get_all(pagetype, filter, pluck="name", ignore_ifnull=True)
return list(set(paid_plans))
diff --git a/jcloud/jcloud/pagetype/team/team.py b/jcloud/jcloud/pagetype/team/team.py
index bf90113..74367bc 100644
--- a/jcloud/jcloud/pagetype/team/team.py
+++ b/jcloud/jcloud/pagetype/team/team.py
@@ -9,7 +9,7 @@ import jingrow
from jingrow import _
from jingrow.contacts.address_and_contact import load_address_and_contact
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_fullname, get_url_to_form, random_string
from jcloud.api.client import dashboard_whitelist
@@ -24,7 +24,7 @@ from jcloud.utils.billing import (
from jcloud.utils.telemetry import capture
-class Team(Document):
+class Team(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -774,7 +774,7 @@ class Team(Document):
return get_team_members(self.name)
@dashboard_whitelist()
- def invite_team_member(self, email, roles=None):
+ def invite_team_member(self, username, roles=None):
JcloudRole = jingrow.qb.PageType("Jcloud Role")
JcloudRoleUser = jingrow.qb.PageType("Jcloud Role User")
@@ -790,26 +790,36 @@ class Team(Document):
if jingrow.session.user != self.user and not has_admin_access.run():
jingrow.throw(_("Only team owner can invite team members"))
- jingrow.utils.validate_email_address(email, True)
+ # 验证用户名是否存在
+ if not username:
+ jingrow.throw(_("用户名不能为空"))
+
+ # 检查用户是否存在(通过 username 或 name)
+ user = jingrow.db.get_value("User", {"username": username}, ["name", "enabled"], as_dict=True)
+ if not user:
+ # 如果通过 username 找不到,尝试通过 name(因为 name 可能是 username)
+ user = jingrow.db.get_value("User", username, ["name", "enabled"], as_dict=True)
+
+ if not user:
+ jingrow.throw(_("用户不存在"))
+
+ if not user.enabled:
+ jingrow.throw(_("用户已被禁用"))
- if jingrow.db.exists("Team Member", {"user": email, "parent": self.name, "parenttype": "Team"}):
- jingrow.throw(_("Team member already exists"))
+ # 检查是否已经是团队成员
+ if jingrow.db.exists("Team Member", {"user": user.name, "parent": self.name, "parenttype": "Team"}):
+ jingrow.throw(_("该用户已经是团队成员"))
- account_request = jingrow.get_pg(
- {
- "pagetype": "Account Request",
- "team": self.name,
- "email": email,
- "role": "Jcloud Member",
- "invited_by": self.user,
- "send_email": True,
- }
- )
+ # 直接添加团队成员
+ self.append("team_members", {"user": user.name})
+ self.save(ignore_permissions=True)
- for role in roles:
- account_request.append("jcloud_roles", {"jcloud_role": role})
-
- account_request.insert()
+ # 添加角色
+ if roles:
+ for role in roles:
+ role_pg = jingrow.get_pg("Jcloud Role", role)
+ if role_pg:
+ role_pg.add_user(user.name)
@jingrow.whitelist()
def get_balance(self):
diff --git a/jcloud/jcloud/pagetype/team_change/team_change.py b/jcloud/jcloud/pagetype/team_change/team_change.py
index 780a340..513230d 100644
--- a/jcloud/jcloud/pagetype/team_change/team_change.py
+++ b/jcloud/jcloud/pagetype/team_change/team_change.py
@@ -4,10 +4,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TeamChange(Document):
+class TeamChange(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/team_member/team_member.py b/jcloud/jcloud/pagetype/team_member/team_member.py
index 6e56921..b5113ff 100644
--- a/jcloud/jcloud/pagetype/team_member/team_member.py
+++ b/jcloud/jcloud/pagetype/team_member/team_member.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TeamMember(Document):
+class TeamMember(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/team_member_deletion_request/team_member_deletion_request.py b/jcloud/jcloud/pagetype/team_member_deletion_request/team_member_deletion_request.py
index d62a49a..d695f44 100644
--- a/jcloud/jcloud/pagetype/team_member_deletion_request/team_member_deletion_request.py
+++ b/jcloud/jcloud/pagetype/team_member_deletion_request/team_member_deletion_request.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TeamMemberDeletionRequest(Document):
+class TeamMemberDeletionRequest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/team_member_impersonation/team_member_impersonation.py b/jcloud/jcloud/pagetype/team_member_impersonation/team_member_impersonation.py
index f742471..93bdc57 100644
--- a/jcloud/jcloud/pagetype/team_member_impersonation/team_member_impersonation.py
+++ b/jcloud/jcloud/pagetype/team_member_impersonation/team_member_impersonation.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TeamMemberImpersonation(Document):
+class TeamMemberImpersonation(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/team_onboarding/team_onboarding.py b/jcloud/jcloud/pagetype/team_onboarding/team_onboarding.py
index e44420f..6eacf07 100644
--- a/jcloud/jcloud/pagetype/team_onboarding/team_onboarding.py
+++ b/jcloud/jcloud/pagetype/team_onboarding/team_onboarding.py
@@ -4,10 +4,10 @@
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TeamOnboarding(Document):
+class TeamOnboarding(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/telegram_group/telegram_group.py b/jcloud/jcloud/pagetype/telegram_group/telegram_group.py
index decf29f..aa1cfd6 100644
--- a/jcloud/jcloud/pagetype/telegram_group/telegram_group.py
+++ b/jcloud/jcloud/pagetype/telegram_group/telegram_group.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TelegramGroup(Document):
+class TelegramGroup(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/telegram_group_topic/telegram_group_topic.py b/jcloud/jcloud/pagetype/telegram_group_topic/telegram_group_topic.py
index d347575..3b76b7a 100644
--- a/jcloud/jcloud/pagetype/telegram_group_topic/telegram_group_topic.py
+++ b/jcloud/jcloud/pagetype/telegram_group_topic/telegram_group_topic.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class TelegramGroupTopic(Document):
+class TelegramGroupTopic(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/telegram_message/telegram_message.py b/jcloud/jcloud/pagetype/telegram_message/telegram_message.py
index e35b960..054b4a6 100644
--- a/jcloud/jcloud/pagetype/telegram_message/telegram_message.py
+++ b/jcloud/jcloud/pagetype/telegram_message/telegram_message.py
@@ -4,7 +4,7 @@
import traceback
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from telegram.error import NetworkError, RetryAfter
from jcloud.telegram_utils import Telegram
@@ -12,7 +12,7 @@ from jingrow.query_builder import Interval
from jingrow.query_builder.functions import Now
-class TelegramMessage(Document):
+class TelegramMessage(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/tls_certificate/tls_certificate.py b/jcloud/jcloud/pagetype/tls_certificate/tls_certificate.py
index b8fbea4..4886a92 100644
--- a/jcloud/jcloud/pagetype/tls_certificate/tls_certificate.py
+++ b/jcloud/jcloud/pagetype/tls_certificate/tls_certificate.py
@@ -7,7 +7,7 @@ from datetime import datetime
import jingrow
import OpenSSL
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.site import check_dns_cname_a
from jcloud.overrides import get_permission_query_conditions_for_pagetype
@@ -15,7 +15,7 @@ from jcloud.runner import Ansible
from jcloud.utils import get_current_team, log_error
-class TLSCertificate(Document):
+class TLSCertificate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -141,7 +141,7 @@ class TLSCertificate(Document):
@jingrow.whitelist()
def trigger_server_tls_setup_callback(self):
- server_doctypes = [
+ server_pagetypes = [
"Proxy Server",
"Server",
"Database Server",
@@ -151,7 +151,7 @@ class TLSCertificate(Document):
"Analytics Server",
"Trace Server",
]
- for server_pagetype in server_doctypes:
+ for server_pagetype in server_pagetypes:
servers = jingrow.get_all(
server_pagetype,
{"status": "Active", "name": ("like", f"%.{self.domain}")}
@@ -279,7 +279,7 @@ def retrigger_failed_wildcard_tls_callbacks():
"""
可用于手动重新为失败的通配符证书部署。
"""
- server_doctypes = [
+ server_pagetypes = [
"Proxy Server",
"Server",
"Database Server",
@@ -289,7 +289,7 @@ def retrigger_failed_wildcard_tls_callbacks():
"Analytics Server",
"Trace Server",
]
- for server_pagetype in server_doctypes:
+ for server_pagetype in server_pagetypes:
servers = jingrow.get_all(server_pagetype, {"status": "Active"}, pluck="name")
for srv in servers:
plays = jingrow.get_all(
diff --git a/jcloud/jcloud/pagetype/usage_record/usage_record.py b/jcloud/jcloud/pagetype/usage_record/usage_record.py
index 622a632..019400d 100644
--- a/jcloud/jcloud/pagetype/usage_record/usage_record.py
+++ b/jcloud/jcloud/pagetype/usage_record/usage_record.py
@@ -3,10 +3,10 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class UsageRecord(Document):
+class UsageRecord(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/user_2fa/user_2fa.py b/jcloud/jcloud/pagetype/user_2fa/user_2fa.py
index d20a807..bf68b6e 100644
--- a/jcloud/jcloud/pagetype/user_2fa/user_2fa.py
+++ b/jcloud/jcloud/pagetype/user_2fa/user_2fa.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class User2FA(Document):
+class User2FA(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/user_ssh_certificate/user_ssh_certificate.py b/jcloud/jcloud/pagetype/user_ssh_certificate/user_ssh_certificate.py
index 05b094c..5560bb5 100644
--- a/jcloud/jcloud/pagetype/user_ssh_certificate/user_ssh_certificate.py
+++ b/jcloud/jcloud/pagetype/user_ssh_certificate/user_ssh_certificate.py
@@ -12,12 +12,12 @@ import subprocess
import jingrow
from jingrow import safe_decode
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import log_error
-class UserSSHCertificate(Document):
+class UserSSHCertificate(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/user_ssh_key/user_ssh_key.py b/jcloud/jcloud/pagetype/user_ssh_key/user_ssh_key.py
index f0748cc..1e14753 100644
--- a/jcloud/jcloud/pagetype/user_ssh_key/user_ssh_key.py
+++ b/jcloud/jcloud/pagetype/user_ssh_key/user_ssh_key.py
@@ -7,7 +7,7 @@ import struct
import subprocess
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.api.client import dashboard_whitelist
@@ -20,7 +20,7 @@ class SSHFingerprintError(ValueError):
pass
-class UserSSHKey(Document):
+class UserSSHKey(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/version_upgrade/version_upgrade.py b/jcloud/jcloud/pagetype/version_upgrade/version_upgrade.py
index 427f374..9c53b7c 100644
--- a/jcloud/jcloud/pagetype/version_upgrade/version_upgrade.py
+++ b/jcloud/jcloud/pagetype/version_upgrade/version_upgrade.py
@@ -6,7 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.jcloud_notification.jcloud_notification import (
create_new_notification,
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.site.site import Site
-class VersionUpgrade(Document):
+class VersionUpgrade(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -145,12 +145,12 @@ class VersionUpgrade(Document):
return cls.get_docs(upgrades)
@classmethod
- def get_all_ongoing_version_upgrades(cls) -> list[Document]:
+ def get_all_ongoing_version_upgrades(cls) -> list[Page]:
upgrades = jingrow.get_all(cls.pagetype, {"status": ("in", ["Pending", "Running"])})
return cls.get_docs(upgrades)
@classmethod
- def get_docs(cls, names: list[str]) -> list[Document]:
+ def get_docs(cls, names: list[str]) -> list[Page]:
return [jingrow.get_pg(cls.pagetype, name) for name in names]
diff --git a/jcloud/jcloud/pagetype/virtual_disk_snapshot/virtual_disk_snapshot.py b/jcloud/jcloud/pagetype/virtual_disk_snapshot/virtual_disk_snapshot.py
index 538fd6f..47ef066 100644
--- a/jcloud/jcloud/pagetype/virtual_disk_snapshot/virtual_disk_snapshot.py
+++ b/jcloud/jcloud/pagetype/virtual_disk_snapshot/virtual_disk_snapshot.py
@@ -7,13 +7,13 @@ import jingrow
import jingrow.utils
import pytz
from botocore.exceptions import ClientError
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from oci.core import BlockstorageClient
from jcloud.utils import log_error
-class VirtualDiskSnapshot(Document):
+class VirtualDiskSnapshot(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/virtual_machine/virtual_machine.py b/jcloud/jcloud/pagetype/virtual_machine/virtual_machine.py
index fcae975..045684a 100644
--- a/jcloud/jcloud/pagetype/virtual_machine/virtual_machine.py
+++ b/jcloud/jcloud/pagetype/virtual_machine/virtual_machine.py
@@ -5,6 +5,7 @@ from __future__ import annotations
import base64
import ipaddress
import time
+from typing import Literal
import boto3
import botocore
@@ -12,7 +13,7 @@ import jingrow
import rq
from jingrow.core.utils import find
from jingrow.desk.utils import slug
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.model.naming import make_autoname
from hcloud import APIException, Client
from hcloud.images import Image
@@ -38,7 +39,7 @@ from jcloud.overrides import get_permission_query_conditions_for_pagetype
from jcloud.utils import log_error
from jcloud.utils.jobs import has_job_timeout_exceeded
-server_doctypes = [
+server_pagetypes = [
"Server",
"Database Server",
"Proxy Server",
@@ -47,7 +48,7 @@ server_doctypes = [
]
-class VirtualMachine(Document):
+class VirtualMachine(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
@@ -62,7 +63,7 @@ class VirtualMachine(Document):
from jcloud.jcloud.pagetype.virtual_machine_volume.virtual_machine_volume import VirtualMachineVolume
availability_zone: DF.Data
- cloud_provider: DF.Literal["", "AWS EC2", "OCI", "Hetzner"]
+ cloud_provider: DF.Data
cluster: DF.Link
disk_size: DF.Int
domain: DF.Link
@@ -71,7 +72,7 @@ class VirtualMachine(Document):
instance_id: DF.Data | None
machine_image: DF.Data | None
machine_type: DF.Data
- platform: DF.Literal["x86_64", "arm64"]
+ platform: DF.Data
private_dns_name: DF.Data | None
private_ip_address: DF.Data | None
public_dns_name: DF.Data | None
@@ -80,9 +81,9 @@ class VirtualMachine(Document):
region: DF.Link
root_disk_size: DF.Int
security_group_id: DF.Data | None
- series: DF.Literal["n", "f", "m", "c", "p", "e", "r"]
+ series: DF.Data
ssh_key: DF.Link
- status: DF.Literal["Draft", "Pending", "Running", "Stopped", "Terminated"]
+ status: DF.Data
subnet_cidr_block: DF.Data | None
subnet_id: DF.Data | None
team: DF.Link | None
@@ -392,7 +393,7 @@ class VirtualMachine(Document):
return jingrow.render_template(cloud_init_template, context, is_path=True)
def get_server(self):
- for pagetype in server_doctypes:
+ for pagetype in server_pagetypes:
server = jingrow.db.get_value(pagetype, {"virtual_machine": self.name}, "name")
if server:
return jingrow.get_pg(pagetype, server)
@@ -550,7 +551,7 @@ class VirtualMachine(Document):
except APIException:
is_deleted = True
if server_instance and not is_deleted:
- # cluster: Document = jingrow.get_pg("Cluster", self.cluster)
+ # cluster: Page = jingrow.get_pg("Cluster", self.cluster)
self.status = self.get_hetzner_status_map()[server_instance.status]
self.machine_type = server_instance.server_type.name
self.private_ip_address = server_instance.private_net[0].ip
@@ -740,7 +741,7 @@ class VirtualMachine(Document):
"Terminated": "Archived",
"Stopped": "Pending",
}
- for pagetype in server_doctypes:
+ for pagetype in server_pagetypes:
server = jingrow.get_all(pagetype, {"virtual_machine": self.name}, pluck="name")
if server:
server = server[0]
diff --git a/jcloud/jcloud/pagetype/virtual_machine_image/virtual_machine_image.py b/jcloud/jcloud/pagetype/virtual_machine_image/virtual_machine_image.py
index 42bd92b..0b54710 100644
--- a/jcloud/jcloud/pagetype/virtual_machine_image/virtual_machine_image.py
+++ b/jcloud/jcloud/pagetype/virtual_machine_image/virtual_machine_image.py
@@ -5,14 +5,14 @@ from __future__ import annotations
import boto3
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from oci.core import ComputeClient
from oci.core.models import CreateImageDetails
from tenacity import retry, stop_after_attempt, wait_fixed
from tenacity.retry import retry_if_result
-class VirtualMachineImage(Document):
+class VirtualMachineImage(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/virtual_machine_image_volume/virtual_machine_image_volume.py b/jcloud/jcloud/pagetype/virtual_machine_image_volume/virtual_machine_image_volume.py
index 13a8bc5..364af17 100644
--- a/jcloud/jcloud/pagetype/virtual_machine_image_volume/virtual_machine_image_volume.py
+++ b/jcloud/jcloud/pagetype/virtual_machine_image_volume/virtual_machine_image_volume.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineImageVolume(Document):
+class VirtualMachineImageVolume(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/virtual_machine_temporary_volume/virtual_machine_temporary_volume.py b/jcloud/jcloud/pagetype/virtual_machine_temporary_volume/virtual_machine_temporary_volume.py
index ea97d9c..1e2cc64 100644
--- a/jcloud/jcloud/pagetype/virtual_machine_temporary_volume/virtual_machine_temporary_volume.py
+++ b/jcloud/jcloud/pagetype/virtual_machine_temporary_volume/virtual_machine_temporary_volume.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineTemporaryVolume(Document):
+class VirtualMachineTemporaryVolume(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/virtual_machine_volume/virtual_machine_volume.py b/jcloud/jcloud/pagetype/virtual_machine_volume/virtual_machine_volume.py
index bb6d52a..cfb2313 100644
--- a/jcloud/jcloud/pagetype/virtual_machine_volume/virtual_machine_volume.py
+++ b/jcloud/jcloud/pagetype/virtual_machine_volume/virtual_machine_volume.py
@@ -4,10 +4,10 @@
# import jingrow
from __future__ import annotations
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class VirtualMachineVolume(Document):
+class VirtualMachineVolume(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/wechatpay_payment_record/wechatpay_payment_record.py b/jcloud/jcloud/pagetype/wechatpay_payment_record/wechatpay_payment_record.py
index 11baee9..0cab551 100644
--- a/jcloud/jcloud/pagetype/wechatpay_payment_record/wechatpay_payment_record.py
+++ b/jcloud/jcloud/pagetype/wechatpay_payment_record/wechatpay_payment_record.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class WechatpayPaymentRecord(Document):
+class WechatpayPaymentRecord(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/pagetype/wireguard_peer/wireguard_peer.py b/jcloud/jcloud/pagetype/wireguard_peer/wireguard_peer.py
index 55dc7c4..6acc11f 100644
--- a/jcloud/jcloud/pagetype/wireguard_peer/wireguard_peer.py
+++ b/jcloud/jcloud/pagetype/wireguard_peer/wireguard_peer.py
@@ -6,13 +6,13 @@ import json
import subprocess
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.runner import Ansible
from jcloud.utils import log_error
-class WireguardPeer(Document):
+class WireguardPeer(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/jcloud/report/marketplace_app_repository_visibility/marketplace_app_repository_visibility.py b/jcloud/jcloud/report/marketplace_app_repository_visibility/marketplace_app_repository_visibility.py
index a647a62..b2c5e79 100644
--- a/jcloud/jcloud/report/marketplace_app_repository_visibility/marketplace_app_repository_visibility.py
+++ b/jcloud/jcloud/report/marketplace_app_repository_visibility/marketplace_app_repository_visibility.py
@@ -40,13 +40,13 @@ def send_emails(columns, data):
def check_repository_visibility(repository_url, personal_access_token):
try:
- repo_parts = repository_url.split("git.jingrow.com:3000/")[1].rstrip(".git").split("/")
+ repo_parts = repository_url.split("git.jingrow.com/")[1].rstrip(".git").split("/")
owner = repo_parts[0]
repo_name = repo_parts[1]
except IndexError:
return "Error: Invalid repository URL format."
- api_url = f"http://git.jingrow.com:3000/api/v1/repos/{owner}/{repo_name}"
+ api_url = f"http://git.jingrow.com/api/v1/repos/{owner}/{repo_name}"
headers = {"Authorization": f"token {personal_access_token}"}
diff --git a/jcloud/marketplace/pagetype/app_plan_version/app_plan_version.py b/jcloud/marketplace/pagetype/app_plan_version/app_plan_version.py
index f78c356..afefcee 100644
--- a/jcloud/marketplace/pagetype/app_plan_version/app_plan_version.py
+++ b/jcloud/marketplace/pagetype/app_plan_version/app_plan_version.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppPlanVersion(Document):
+class AppPlanVersion(Page):
pass
diff --git a/jcloud/marketplace/pagetype/app_release_approval_code_comments/app_release_approval_code_comments.py b/jcloud/marketplace/pagetype/app_release_approval_code_comments/app_release_approval_code_comments.py
index a9596fc..0964b2c 100644
--- a/jcloud/marketplace/pagetype/app_release_approval_code_comments/app_release_approval_code_comments.py
+++ b/jcloud/marketplace/pagetype/app_release_approval_code_comments/app_release_approval_code_comments.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppReleaseApprovalCodeComments(Document):
+class AppReleaseApprovalCodeComments(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/marketplace/pagetype/app_user_review/app_user_review.py b/jcloud/marketplace/pagetype/app_user_review/app_user_review.py
index 75a4b62..11a630a 100644
--- a/jcloud/marketplace/pagetype/app_user_review/app_user_review.py
+++ b/jcloud/marketplace/pagetype/app_user_review/app_user_review.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AppUserReview(Document):
+class AppUserReview(Page):
def after_insert(self):
self.update_average_rating()
diff --git a/jcloud/marketplace/pagetype/auto_release_team/auto_release_team.py b/jcloud/marketplace/pagetype/auto_release_team/auto_release_team.py
index 44e17dc..1aea503 100644
--- a/jcloud/marketplace/pagetype/auto_release_team/auto_release_team.py
+++ b/jcloud/marketplace/pagetype/auto_release_team/auto_release_team.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AutoReleaseTeam(Document):
+class AutoReleaseTeam(Page):
pass
diff --git a/jcloud/marketplace/pagetype/developer_review_reply/developer_review_reply.py b/jcloud/marketplace/pagetype/developer_review_reply/developer_review_reply.py
index 9133041..f973245 100644
--- a/jcloud/marketplace/pagetype/developer_review_reply/developer_review_reply.py
+++ b/jcloud/marketplace/pagetype/developer_review_reply/developer_review_reply.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class DeveloperReviewReply(Document):
+class DeveloperReviewReply(Page):
pass
diff --git a/jcloud/marketplace/pagetype/featured_app/featured_app.py b/jcloud/marketplace/pagetype/featured_app/featured_app.py
index d06cace..291adcd 100644
--- a/jcloud/marketplace/pagetype/featured_app/featured_app.py
+++ b/jcloud/marketplace/pagetype/featured_app/featured_app.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class FeaturedApp(Document):
+class FeaturedApp(Page):
pass
diff --git a/jcloud/marketplace/pagetype/marketplace_add_on/marketplace_add_on.py b/jcloud/marketplace/pagetype/marketplace_add_on/marketplace_add_on.py
index 147976d..f443a43 100644
--- a/jcloud/marketplace/pagetype/marketplace_add_on/marketplace_add_on.py
+++ b/jcloud/marketplace/pagetype/marketplace_add_on/marketplace_add_on.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceAddOn(Document):
+class MarketplaceAddOn(Page):
pass
diff --git a/jcloud/marketplace/pagetype/marketplace_app_payment/marketplace_app_payment.py b/jcloud/marketplace/pagetype/marketplace_app_payment/marketplace_app_payment.py
index 3599b51..2c87b35 100644
--- a/jcloud/marketplace/pagetype/marketplace_app_payment/marketplace_app_payment.py
+++ b/jcloud/marketplace/pagetype/marketplace_app_payment/marketplace_app_payment.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceAppPayment(Document):
+class MarketplaceAppPayment(Page):
def has_threshold_passed(self):
exchange_rate = jingrow.db.get_single_value("Jcloud Settings", "usd_rate")
total = self.total_usd + (self.total_cny / exchange_rate) if exchange_rate > 0 else 80
diff --git a/jcloud/marketplace/pagetype/marketplace_app_plans/marketplace_app_plans.py b/jcloud/marketplace/pagetype/marketplace_app_plans/marketplace_app_plans.py
index 2cf761c..14c8d8d 100644
--- a/jcloud/marketplace/pagetype/marketplace_app_plans/marketplace_app_plans.py
+++ b/jcloud/marketplace/pagetype/marketplace_app_plans/marketplace_app_plans.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceAppPlans(Document):
+class MarketplaceAppPlans(Page):
pass
diff --git a/jcloud/marketplace/pagetype/marketplace_app_subscription/marketplace_app_subscription.py b/jcloud/marketplace/pagetype/marketplace_app_subscription/marketplace_app_subscription.py
index d5623d1..074b761 100644
--- a/jcloud/marketplace/pagetype/marketplace_app_subscription/marketplace_app_subscription.py
+++ b/jcloud/marketplace/pagetype/marketplace_app_subscription/marketplace_app_subscription.py
@@ -3,12 +3,12 @@
import jingrow
import requests
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.site.site import Site
-class MarketplaceAppSubscription(Document):
+class MarketplaceAppSubscription(Page):
def validate(self):
self.set_secret_key()
self.validate_marketplace_app_plan()
diff --git a/jcloud/marketplace/pagetype/marketplace_promotional_banner/marketplace_promotional_banner.py b/jcloud/marketplace/pagetype/marketplace_promotional_banner/marketplace_promotional_banner.py
index b3d26fe..89665d3 100644
--- a/jcloud/marketplace/pagetype/marketplace_promotional_banner/marketplace_promotional_banner.py
+++ b/jcloud/marketplace/pagetype/marketplace_promotional_banner/marketplace_promotional_banner.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplacePromotionalBanner(Document):
+class MarketplacePromotionalBanner(Page):
pass
diff --git a/jcloud/marketplace/pagetype/marketplace_publisher_profile/marketplace_publisher_profile.py b/jcloud/marketplace/pagetype/marketplace_publisher_profile/marketplace_publisher_profile.py
index 390e2bd..0239f82 100644
--- a/jcloud/marketplace/pagetype/marketplace_publisher_profile/marketplace_publisher_profile.py
+++ b/jcloud/marketplace/pagetype/marketplace_publisher_profile/marketplace_publisher_profile.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplacePublisherProfile(Document):
+class MarketplacePublisherProfile(Page):
pass
diff --git a/jcloud/marketplace/pagetype/marketplace_settings/marketplace_settings.py b/jcloud/marketplace/pagetype/marketplace_settings/marketplace_settings.py
index 2bfdc33..85663ef 100644
--- a/jcloud/marketplace/pagetype/marketplace_settings/marketplace_settings.py
+++ b/jcloud/marketplace/pagetype/marketplace_settings/marketplace_settings.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class MarketplaceSettings(Document):
+class MarketplaceSettings(Page):
pass
diff --git a/jcloud/partner/pagetype/partner_approval_request/partner_approval_request.py b/jcloud/partner/pagetype/partner_approval_request/partner_approval_request.py
index ec0b2b9..1d04d22 100644
--- a/jcloud/partner/pagetype/partner_approval_request/partner_approval_request.py
+++ b/jcloud/partner/pagetype/partner_approval_request/partner_approval_request.py
@@ -3,13 +3,13 @@
from __future__ import annotations
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import get_url
from jcloud.api.client import dashboard_whitelist
-class PartnerApprovalRequest(Document):
+class PartnerApprovalRequest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/partner/pagetype/partner_tier/partner_tier.py b/jcloud/partner/pagetype/partner_tier/partner_tier.py
index ecaf20c..16102db 100644
--- a/jcloud/partner/pagetype/partner_tier/partner_tier.py
+++ b/jcloud/partner/pagetype/partner_tier/partner_tier.py
@@ -3,10 +3,10 @@
from __future__ import annotations
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class PartnerTier(Document):
+class PartnerTier(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/patches.txt b/jcloud/patches.txt
index 461c0c8..842b36d 100644
--- a/jcloud/patches.txt
+++ b/jcloud/patches.txt
@@ -24,7 +24,7 @@ jcloud.patches.v0_0_1.add_domains_to_site_config
execute:jingrow.reload_pg('jcloud', 'pagetype', 'Remote File')
# jcloud.patches.v0_0_1.add_site_to_remote_file # 2020-11-12 run via run-patch command in active site state
jcloud.patches.v0_0_1.new_onboarding
-jcloud.patches.v0_0_1.remove_obsolete_doctypes
+jcloud.patches.v0_0_1.remove_obsolete_pagetypes
jcloud.patches.v0_0_1.make_default_site_domain
jcloud.patches.v0_0_1.update_site_config_pg
jcloud.patches.v0_0_1.create_certificate_authorities
@@ -75,7 +75,7 @@ jcloud.patches.v0_0_1.add_domains_in_site_config_preview
jcloud.patches.v0_0_1.use_private_ip_for_upstreams
jcloud.jcloud.pagetype.site.patches.set_plan_in_site
jcloud.jcloud.pagetype.app_release.patches.set_status_to_draft
-jcloud.patches.v0_0_4.remove_legacy_billing_doctypes
+jcloud.patches.v0_0_4.remove_legacy_billing_pagetypes
# jcloud.jcloud.pagetype.invoice.patches.set_free_credits # 2021-08-11 run via run-patch command
jcloud.jcloud.pagetype.team.patches.set_payment_mode
jcloud.patches.v0_0_1.add_team_name_as_default_notify_email
@@ -109,7 +109,7 @@ jcloud.jcloud.pagetype.virtual_disk_snapshot.patches.rename_aws_fields
jcloud.jcloud.pagetype.virtual_machine_volume.patches.rename_aws_fields
jcloud.patches.v0_7_0.convert_marketplace_description_to_html
jcloud.jcloud.pagetype.team.patches.remove_invalid_email_addresses
-jcloud.saas.pagetype.product_trial.patches.rename_saas_product_doctypes_to_product_trial
+jcloud.saas.pagetype.product_trial.patches.rename_saas_product_pagetypes_to_product_trial
[post_model_sync]
jcloud.patches.v0_7_0.rename_plan_to_site_plan
@@ -119,7 +119,7 @@ jcloud.jcloud.pagetype.agent_job.patches.update_status_for_undelivered_jobs #202
jcloud.jcloud.pagetype.jcloud_role.patches.migrate_permissions
jcloud.jcloud.pagetype.jcloud_role.patches.change_fields_from_enable_to_allow
jcloud.jcloud.pagetype.stripe_webhook_log.patches.add_payment_method_for_failed_events
-jcloud.patches.v0_7_0.add_team_field_for_site_related_doctypes
+jcloud.patches.v0_7_0.add_team_field_for_site_related_pagetypes
jcloud.patches.v0_7_0.add_team_field_for_site_backups_archived
jcloud.jcloud.pagetype.server_storage_plan.patches.add_subscription_for_servers_with_additional_disk
jcloud.jcloud.pagetype.jcloud_notification.patches.link_reference_pagetype_to_notifications
diff --git a/jcloud/patches/v0_0_1/create_default_cluster.py b/jcloud/patches/v0_0_1/create_default_cluster.py
index a2d0d48..f7a8a1e 100644
--- a/jcloud/patches/v0_0_1/create_default_cluster.py
+++ b/jcloud/patches/v0_0_1/create_default_cluster.py
@@ -9,7 +9,7 @@ def execute():
jingrow.reload_pg("jcloud", "pagetype", "cluster")
cluster = jingrow.get_pg({"pagetype": "Cluster", "name": "Default", "default": True})
cluster.insert()
- doctypes = ["Server", "Proxy Server", "Database Server", "Bench", "Site"]
- for pagetype in doctypes:
+ pagetypes = ["Server", "Proxy Server", "Database Server", "Bench", "Site"]
+ for pagetype in pagetypes:
jingrow.reload_pg("jcloud", "pagetype", jingrow.scrub(pagetype))
jingrow.db.set_value(pagetype, {"name": ("like", "%")}, "cluster", "Default")
diff --git a/jcloud/patches/v0_0_1/remove_obsolete_doctypes.py b/jcloud/patches/v0_0_1/remove_obsolete_pagetypes.py
similarity index 86%
rename from jcloud/patches/v0_0_1/remove_obsolete_doctypes.py
rename to jcloud/patches/v0_0_1/remove_obsolete_pagetypes.py
index 1be8e48..f7ee443 100644
--- a/jcloud/patches/v0_0_1/remove_obsolete_doctypes.py
+++ b/jcloud/patches/v0_0_1/remove_obsolete_pagetypes.py
@@ -7,7 +7,7 @@ import jingrow
def execute():
- obsolete_doctypes = [
+ obsolete_pagetypes = [
"Credit Ledger Entry",
"Custom Domain",
"Site Analytics",
@@ -16,6 +16,6 @@ def execute():
"Usage Report",
"User Account",
]
- for pagetype in obsolete_doctypes:
+ for pagetype in obsolete_pagetypes:
if jingrow.db.exists("PageType", pagetype):
jingrow.delete_pg("PageType", pagetype)
diff --git a/jcloud/patches/v0_0_1/set_hostname_in_server.py b/jcloud/patches/v0_0_1/set_hostname_in_server.py
index 73c0cde..3b52409 100644
--- a/jcloud/patches/v0_0_1/set_hostname_in_server.py
+++ b/jcloud/patches/v0_0_1/set_hostname_in_server.py
@@ -6,8 +6,8 @@ import jingrow
def execute():
- doctypes = ["Server", "Proxy Server", "Database Server"]
- for pagetype in doctypes:
+ pagetypes = ["Server", "Proxy Server", "Database Server"]
+ for pagetype in pagetypes:
jingrow.reload_pg("jcloud", "pagetype", jingrow.scrub(pagetype))
servers = jingrow.get_all(pagetype, {"hostname": ("is", "not set")})
domain = jingrow.db.get_single_value("Jcloud Settings", "domain")
diff --git a/jcloud/patches/v0_0_4/remove_legacy_billing_doctypes.py b/jcloud/patches/v0_0_4/remove_legacy_billing_doctypes.py
deleted file mode 100644
index 305e9b4..0000000
--- a/jcloud/patches/v0_0_4/remove_legacy_billing_doctypes.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (c) 2021, JINGROW
-# For license information, please see license.txt
-
-
-import jingrow
-
-
-def execute():
- # these doctypes are only deleted from PageType table, their tables will exist
- doctypes = ["Payment", "Payment Ledger Entry"]
- jingrow.db.sql("DELETE from tabPageType where name in %s", [doctypes])
diff --git a/jcloud/patches/v0_0_4/remove_legacy_billing_pagetypes.py b/jcloud/patches/v0_0_4/remove_legacy_billing_pagetypes.py
new file mode 100644
index 0000000..68feca3
--- /dev/null
+++ b/jcloud/patches/v0_0_4/remove_legacy_billing_pagetypes.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2021, JINGROW
+# For license information, please see license.txt
+
+
+import jingrow
+
+
+def execute():
+ # these pagetypes are only deleted from PageType table, their tables will exist
+ pagetypes = ["Payment", "Payment Ledger Entry"]
+ jingrow.db.sql("DELETE from tabPageType where name in %s", [pagetypes])
diff --git a/jcloud/patches/v0_7_0/add_team_field_for_site_related_doctypes.py b/jcloud/patches/v0_7_0/add_team_field_for_site_related_pagetypes.py
similarity index 100%
rename from jcloud/patches/v0_7_0/add_team_field_for_site_related_doctypes.py
rename to jcloud/patches/v0_7_0/add_team_field_for_site_related_pagetypes.py
diff --git a/jcloud/saas/README.md b/jcloud/saas/README.md
index 7d906ba..9235786 100644
--- a/jcloud/saas/README.md
+++ b/jcloud/saas/README.md
@@ -1,6 +1,6 @@
### New SaaS Flow (Product Trial)
-It has 2 doctypes.
+It has 2 pagetypes.
1. **Product Trial** - Hold the configuration for a specific product.
2. **Product Trial Request** - This holds the records of request for a specific product from a user.
@@ -52,17 +52,17 @@ If `is_standby` field is checked, that site can be allocated to a user.
- **Redirect To After Login** - After SaaS signup/login, user is directly logged-in to his site. By default, we redirect the user to desk of site. With this option, we can configure the redirect path. For example, for gameplan the path would be `/g`
#### FC Dashboard
-- UI/UX - The pages are available in http://git.jingrow.com:3000/jingrow/jcloud/tree/master/dashboard/src2/pages/saas
-- The required apis for these pages are available in http://git.jingrow.com:3000/jingrow/jcloud/blob/master/jcloud/api/product_trial.py
+- UI/UX - The pages are available in http://git.jingrow.com/jingrow/jcloud/tree/master/dashboard/src2/pages/saas
+- The required apis for these pages are available in http://git.jingrow.com/jingrow/jcloud/blob/master/jcloud/api/product_trial.py
#### Billing APIs for Integration in Framework
> [!CAUTION]
> Changes in any of these APIs can cause disruption in on-site billing system.
-- All the required APIs for billing in site is available in http://git.jingrow.com:3000/jingrow/jcloud/tree/master/jcloud/saas/api
-- These APIs use a different type of authentication mechanism. Check this readme for more info http://git.jingrow.com:3000/jingrow/jcloud/blob/master/jcloud/saas/api/readme.md
+- All the required APIs for billing in site is available in http://git.jingrow.com/jingrow/jcloud/tree/master/jcloud/saas/api
+- These APIs use a different type of authentication mechanism. Check this readme for more info http://git.jingrow.com/jingrow/jcloud/blob/master/jcloud/saas/api/readme.md
- Reference of integration in framework
- - http://git.jingrow.com:3000/jingrow/jingrow/tree/develop/billing
- - http://git.jingrow.com:3000/jingrow/jingrow/blob/develop/jingrow/integrations/jingrow_providers/jingrowcloud_billing.py
+ - http://git.jingrow.com/jingrow/jingrow/tree/develop/billing
+ - http://git.jingrow.com/jingrow/jingrow/blob/develop/jingrow/integrations/jingrow_providers/jingrowcloud_billing.py
diff --git a/jcloud/saas/pagetype/account_request_rules/account_request_rules.py b/jcloud/saas/pagetype/account_request_rules/account_request_rules.py
index d3292f3..3631657 100644
--- a/jcloud/saas/pagetype/account_request_rules/account_request_rules.py
+++ b/jcloud/saas/pagetype/account_request_rules/account_request_rules.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class AccountRequestRules(Document):
+class AccountRequestRules(Page):
pass
diff --git a/jcloud/saas/pagetype/hybrid_saas_pool/hybrid_saas_pool.py b/jcloud/saas/pagetype/hybrid_saas_pool/hybrid_saas_pool.py
index eb8089a..756e304 100644
--- a/jcloud/saas/pagetype/hybrid_saas_pool/hybrid_saas_pool.py
+++ b/jcloud/saas/pagetype/hybrid_saas_pool/hybrid_saas_pool.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class HybridSaasPool(Document):
+class HybridSaasPool(Page):
pass
diff --git a/jcloud/saas/pagetype/product_trial/patches/rename_saas_product_doctypes_to_product_trial.py b/jcloud/saas/pagetype/product_trial/patches/rename_saas_product_pagetypes_to_product_trial.py
similarity index 95%
rename from jcloud/saas/pagetype/product_trial/patches/rename_saas_product_doctypes_to_product_trial.py
rename to jcloud/saas/pagetype/product_trial/patches/rename_saas_product_pagetypes_to_product_trial.py
index 1ec6c2b..c631d52 100644
--- a/jcloud/saas/pagetype/product_trial/patches/rename_saas_product_doctypes_to_product_trial.py
+++ b/jcloud/saas/pagetype/product_trial/patches/rename_saas_product_pagetypes_to_product_trial.py
@@ -7,11 +7,11 @@ from jingrow.model.utils.rename_field import rename_field
def execute():
- rename_doctypes()
+ rename_pagetypes()
rename_fields()
-def rename_doctypes():
+def rename_pagetypes():
renames = {
"SaaS Product": "Product Trial",
"SaaS Product App": "Product Trial App",
diff --git a/jcloud/saas/pagetype/product_trial/product_trial.py b/jcloud/saas/pagetype/product_trial/product_trial.py
index 9b736d1..835d2f2 100644
--- a/jcloud/saas/pagetype/product_trial/product_trial.py
+++ b/jcloud/saas/pagetype/product_trial/product_trial.py
@@ -7,7 +7,7 @@ import json
import jingrow
import jingrow.utils
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.data import get_url
from jingrow.utils.momentjs import get_all_timezones
@@ -15,7 +15,7 @@ from jcloud.utils import log_error, validate_subdomain
from jcloud.utils.unique_name_generator import generate as generate_random_name
-class ProductTrial(Document):
+class ProductTrial(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/saas/pagetype/product_trial_app/product_trial_app.py b/jcloud/saas/pagetype/product_trial_app/product_trial_app.py
index 963b10c..3fd3036 100644
--- a/jcloud/saas/pagetype/product_trial_app/product_trial_app.py
+++ b/jcloud/saas/pagetype/product_trial_app/product_trial_app.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ProductTrialApp(Document):
+class ProductTrialApp(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/saas/pagetype/product_trial_request/product_trial_request.py b/jcloud/saas/pagetype/product_trial_request/product_trial_request.py
index bc8e65b..6b6e9c4 100644
--- a/jcloud/saas/pagetype/product_trial_request/product_trial_request.py
+++ b/jcloud/saas/pagetype/product_trial_request/product_trial_request.py
@@ -10,7 +10,7 @@ from contextlib import suppress
from typing import TYPE_CHECKING
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils.data import add_to_date, now_datetime
from jingrow.utils.momentjs import get_all_timezones
from jingrow.utils.password import decrypt as decrypt_password
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
from jcloud.jcloud.pagetype.site.site import Site
-class ProductTrialRequest(Document):
+class ProductTrialRequest(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/saas/pagetype/product_trial_signup_field/product_trial_signup_field.py b/jcloud/saas/pagetype/product_trial_signup_field/product_trial_signup_field.py
index 3b04463..5e35478 100644
--- a/jcloud/saas/pagetype/product_trial_signup_field/product_trial_signup_field.py
+++ b/jcloud/saas/pagetype/product_trial_signup_field/product_trial_signup_field.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class ProductTrialSignupField(Document):
+class ProductTrialSignupField(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/saas/pagetype/saas_app/saas_app.py b/jcloud/saas/pagetype/saas_app/saas_app.py
index 8060169..236f031 100644
--- a/jcloud/saas/pagetype/saas_app/saas_app.py
+++ b/jcloud/saas/pagetype/saas_app/saas_app.py
@@ -2,13 +2,13 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.saas.pagetype.saas_app_plan.saas_app_plan import get_app_plan_features
from jcloud.utils import get_current_team
-class SaasApp(Document):
+class SaasApp(Page):
def autoname(self):
self.name = self.app
diff --git a/jcloud/saas/pagetype/saas_app_plan/saas_app_plan.py b/jcloud/saas/pagetype/saas_app_plan/saas_app_plan.py
index d9ae18e..a42bd64 100644
--- a/jcloud/saas/pagetype/saas_app_plan/saas_app_plan.py
+++ b/jcloud/saas/pagetype/saas_app_plan/saas_app_plan.py
@@ -4,13 +4,13 @@
from typing import List
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.jcloud.pagetype.invoice.invoice import calculate_gst
from jcloud.utils import get_current_team
-class SaasAppPlan(Document):
+class SaasAppPlan(Page):
def validate(self):
self.validate_plan()
self.validate_payout_percentage()
diff --git a/jcloud/saas/pagetype/saas_app_subscription/saas_app_subscription.py b/jcloud/saas/pagetype/saas_app_subscription/saas_app_subscription.py
index 49a361b..7489d1b 100644
--- a/jcloud/saas/pagetype/saas_app_subscription/saas_app_subscription.py
+++ b/jcloud/saas/pagetype/saas_app_subscription/saas_app_subscription.py
@@ -5,13 +5,13 @@ import json
from datetime import datetime
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.utils import add_to_date
from jcloud.utils import log_error
-class SaasAppSubscription(Document):
+class SaasAppSubscription(Page):
def validate(self):
self.set_secret_key()
self.validate_saas_app_plan()
diff --git a/jcloud/saas/pagetype/saas_app_version/saas_app_version.py b/jcloud/saas/pagetype/saas_app_version/saas_app_version.py
index 6af0230..2551d60 100644
--- a/jcloud/saas/pagetype/saas_app_version/saas_app_version.py
+++ b/jcloud/saas/pagetype/saas_app_version/saas_app_version.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SaasAppVersion(Document):
+class SaasAppVersion(Page):
pass
diff --git a/jcloud/saas/pagetype/saas_feedback/saas_feedback.py b/jcloud/saas/pagetype/saas_feedback/saas_feedback.py
index 3f9d7b7..fd99da6 100644
--- a/jcloud/saas/pagetype/saas_feedback/saas_feedback.py
+++ b/jcloud/saas/pagetype/saas_feedback/saas_feedback.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SaasFeedback(Document):
+class SaasFeedback(Page):
pass
diff --git a/jcloud/saas/pagetype/saas_pool_rules/saas_pool_rules.py b/jcloud/saas/pagetype/saas_pool_rules/saas_pool_rules.py
index 79bfff2..a9df1a8 100644
--- a/jcloud/saas/pagetype/saas_pool_rules/saas_pool_rules.py
+++ b/jcloud/saas/pagetype/saas_pool_rules/saas_pool_rules.py
@@ -2,8 +2,8 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SaasPoolRules(Document):
+class SaasPoolRules(Page):
pass
diff --git a/jcloud/saas/pagetype/saas_remote_login/saas_remote_login.py b/jcloud/saas/pagetype/saas_remote_login/saas_remote_login.py
index d71059d..2a8ff91 100644
--- a/jcloud/saas/pagetype/saas_remote_login/saas_remote_login.py
+++ b/jcloud/saas/pagetype/saas_remote_login/saas_remote_login.py
@@ -2,11 +2,11 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
EXPIRES_IN_HOURS = 6
-class SaasRemoteLogin(Document):
+class SaasRemoteLogin(Page):
def before_insert(self):
self.expires_on = jingrow.utils.add_to_date(None, hours=EXPIRES_IN_HOURS)
diff --git a/jcloud/saas/pagetype/saas_settings/saas_settings.py b/jcloud/saas/pagetype/saas_settings/saas_settings.py
index 541cb6c..87d6ee4 100644
--- a/jcloud/saas/pagetype/saas_settings/saas_settings.py
+++ b/jcloud/saas/pagetype/saas_settings/saas_settings.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
# import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SaasSettings(Document):
+class SaasSettings(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/saas/pagetype/site_access_token/site_access_token.py b/jcloud/saas/pagetype/site_access_token/site_access_token.py
index 5a77b50..c9375bc 100644
--- a/jcloud/saas/pagetype/site_access_token/site_access_token.py
+++ b/jcloud/saas/pagetype/site_access_token/site_access_token.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
import jingrow
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-class SiteAccessToken(Document):
+class SiteAccessToken(Page):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
diff --git a/jcloud/tests/before_test.py b/jcloud/tests/before_test.py
index d8f778e..cb06291 100644
--- a/jcloud/tests/before_test.py
+++ b/jcloud/tests/before_test.py
@@ -6,15 +6,15 @@ import os
import jingrow
from jingrow import set_user as _set_user
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jingrow.tests.utils import JingrowTestCase
from jcloud.utils import _get_current_team, _system_user
-def pg_equal(self: Document, other: Document) -> bool:
- """Partial equality checking of Document object"""
- if not isinstance(other, Document):
+def pg_equal(self: Page, other: Page) -> bool:
+ """Partial equality checking of Page object"""
+ if not isinstance(other, Page):
return False
if self.pagetype == other.pagetype and self.name == other.name:
return True
@@ -31,7 +31,7 @@ def execute():
cssutils.log.setLevel(50)
# Monkey patch certain methods for when tests are running
- Document.__eq__ = pg_equal
+ Page.__eq__ = pg_equal
JingrowTestCase.setUp = lambda self: jingrow.db.truncate("Agent Request Failure")
diff --git a/jcloud/translations/zh.csv b/jcloud/translations/zh.csv
index 9576df8..cb25077 100644
--- a/jcloud/translations/zh.csv
+++ b/jcloud/translations/zh.csv
@@ -87,7 +87,7 @@ Draft,草案,
Due Date,到期日,
Duration,持续时间,
JERP Partner,JERP合作伙伴,
-Email,电子邮件,
+Email,邮箱,
Email Account,邮件帐户,
Email Domain,电子邮件域名,
Email Id,电子邮件ID,
diff --git a/jcloud/utils/__init__.py b/jcloud/utils/__init__.py
index 3650d0b..f133000 100644
--- a/jcloud/utils/__init__.py
+++ b/jcloud/utils/__init__.py
@@ -587,12 +587,12 @@ class ttl_cache:
return wrapper_func
-def poly_get_pagetype(doctypes, name):
- """Get the pagetype value from the given name of a pg from a list of doctypes"""
- for pagetype in doctypes:
+def poly_get_pagetype(pagetypes, name):
+ """Get the pagetype value from the given name of a pg from a list of pagetypes"""
+ for pagetype in pagetypes:
if jingrow.db.exists(pagetype, name):
return pagetype
- return doctypes[-1]
+ return pagetypes[-1]
def reconnect_on_failure():
diff --git a/jcloud/utils/dns.py b/jcloud/utils/dns.py
index 03a5aef..a5fd3a2 100644
--- a/jcloud/utils/dns.py
+++ b/jcloud/utils/dns.py
@@ -1,7 +1,7 @@
import boto3
import jingrow
from jingrow.core.utils import find
-from jingrow.model.document import Document
+from jingrow.model.page import Page
from jcloud.utils import log_error
@@ -22,7 +22,7 @@ def create_dns_record(pg, record_name=None):
def _change_dns_record(
- method: str, domain: Document, proxy_server: str, record_name: str = None
+ method: str, domain: Page, proxy_server: str, record_name: str = None
):
"""
Change dns record of site
diff --git a/jcloud/utils/webhook.py b/jcloud/utils/webhook.py
index e5ad21a..3bc82a9 100644
--- a/jcloud/utils/webhook.py
+++ b/jcloud/utils/webhook.py
@@ -7,10 +7,10 @@ import json
import jingrow
from jingrow.model import default_fields
-from jingrow.model.document import Document
+from jingrow.model.page import Page
-def create_webhook_event(event: str, payload: dict | Document, team: str) -> bool:
+def create_webhook_event(event: str, payload: dict | Page, team: str) -> bool:
try:
# Check if team has configured webhook against this event
JcloudWebhookSelectedEvent = jingrow.qb.PageType("Jcloud Webhook Selected Event")
@@ -33,7 +33,7 @@ def create_webhook_event(event: str, payload: dict | Document, team: str) -> boo
data = {}
if isinstance(payload, dict):
data = jingrow._dict(payload)
- elif isinstance(payload, Document):
+ elif isinstance(payload, Page):
data = _process_document_payload(payload)
else:
jingrow.throw("Invalid data type")
@@ -63,7 +63,7 @@ def create_webhook_event(event: str, payload: dict | Document, team: str) -> boo
return False
-def _process_document_payload(payload: Document):
+def _process_document_payload(payload: Page):
# convert payload to dict
# send fields mentioned in dashboard_fields, as other fields can have sensitive information
fields = list(default_fields)
diff --git a/jcloud/www/github/redirect.py b/jcloud/www/github/redirect.py
index da210b5..c845b5a 100644
--- a/jcloud/www/github/redirect.py
+++ b/jcloud/www/github/redirect.py
@@ -16,7 +16,7 @@ def get_context(context):
headers = {"Accept": "application/vnd.github.v3+json"}
response = jingrow._dict(
requests.post(
- f"http://git.jingrow.com:3000/api/v1/app-manifests/{code}/conversions", headers=headers
+ f"http://git.jingrow.com/api/v1/app-manifests/{code}/conversions", headers=headers
).json()
)
diff --git a/jcloud/www/internal/bench/overview.md b/jcloud/www/internal/bench/overview.md
index bc61cc1..62caa41 100644
--- a/jcloud/www/internal/bench/overview.md
+++ b/jcloud/www/internal/bench/overview.md
@@ -4,13 +4,13 @@ title: Build a Bench
# Build a Bench
-This is information about doctypes required to manage benches and apps on FC
+This is information about pagetypes required to manage benches and apps on FC
from [desk](https://jingrow.com/app). Roughly, the build process goes
-through these doctypes (that you're concerned with) in order.
+through these pagetypes (that you're concerned with) in order.
App => App Source => Release Group => Deploy Candidate => Bench
-To build a bench, we need documents of the following doctypes.
+To build a bench, we need documents of the following pagetypes.
## App
diff --git a/jcloud/www/internal/jcloud/overview.md b/jcloud/www/internal/jcloud/overview.md
index c35bc9f..59c9a61 100644
--- a/jcloud/www/internal/jcloud/overview.md
+++ b/jcloud/www/internal/jcloud/overview.md
@@ -52,7 +52,7 @@ After setting these, click **Obtain TLS Certificate** (Requires **Basic** > **Do
1. Create a GitHub Personal Access Token and add it in GitHub Access Token field.
This is nedded for custom apps that are added without using the app creation flow (Dashboard > New App... ).
- As these are subject stricter [rate limits](https://docs.git.jingrow.com:3000/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#rate-limiting) (5000 requests/hour vs 60 requests/hour).
+ As these are subject stricter [rate limits](https://docs.git.jingrow.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#rate-limiting) (5000 requests/hour vs 60 requests/hour).
### Docker
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