Merge pull request 'main' (#1) from main into v1

Reviewed-on: http://git.jingrow.com:3000/jingrow/jcloud/pulls/1
This commit is contained in:
jingrow 2025-07-18 22:50:47 +08:00
commit 21bd71bc91
65 changed files with 3296 additions and 3162 deletions

View File

@ -22,7 +22,7 @@ export default async function call(method, args) {
updateState(this, 'RequestStarted', null);
const res = await fetch(`/api/method/${method}`, {
const res = await fetch(`/api/action/${method}`, {
method: 'POST',
headers,
body: JSON.stringify(args)

View File

@ -64,7 +64,7 @@ export default class FileUploader {
reject(error);
}
};
xhr.open('POST', '/api/method/upload_file', true);
xhr.open('POST', '/api/action/upload_file', true);
xhr.setRequestHeader('Accept', 'application/json');
if (window.csrf_token && window.csrf_token !== '{{ csrf_token }}') {
xhr.setRequestHeader('X-Jingrow-CSRF-Token', window.csrf_token);

View File

@ -22,7 +22,7 @@ export default class S3FileUploader {
async function getUploadLink() {
try {
let response = await fetch(
`/api/method/jcloud.api.site.get_upload_link?file=${file.name}`
`/api/action/jcloud.api.site.get_upload_link?file=${file.name}`
);
let data = await response.json();
return data.message;

View File

@ -81,7 +81,7 @@ if (window.jcloud_frontend_posthog_host?.includes('https://')) {
if (import.meta.env.DEV) {
request({
url: '/api/method/jcloud.www.dashboard.get_context_for_dev'
url: '/api/action/jcloud.www.dashboard.get_context_for_dev'
}).then(values => {
for (let key in values) {
window[key] = values[key];

View File

@ -12,7 +12,7 @@ const FAKE_BASE_URL = 'http://fc.tests';
const restHandlers = [
rest.post(
FAKE_BASE_URL + '/api/method/jcloud.api.site.features',
FAKE_BASE_URL + '/api/action/jcloud.api.site.features',
(req, res, ctx) => {
return res(ctx.status(200), ctx.json({ message: apps }));
}

View File

@ -322,7 +322,7 @@ SitePlansCards: defineAsyncComponent(() => import('./SitePlansCards.vue')),
this.isChangingPlan = true;
const plan_name = this.selectedPlan?.name;
let request = createResource({
url: '/api/method/jcloud.api.client.run_pg_method',
url: '/api/action/jcloud.api.client.run_pg_method',
params: {
dt: 'Site',
dn: this.site,

View File

@ -41,7 +41,7 @@
<div class="text-base text-gray-600" v-else-if="column.type == 'Timestamp'">
<div class="flex">
<Tooltip :text="$format.date(value)">
{{ value ? $dayjs(value).fromNow() : '' }}
{{ value ? (column.format ? formattedValue : $dayjs(value).fromNow()) : '' }}
</Tooltip>
</div>
</div>

View File

@ -67,7 +67,7 @@
<Button
@click="openRenewalDialog"
v-if="$site.pg.site_end_date && !$site.pg.current_plan?.is_trial_plan"
class="px-5 !bg-blue-600 !hover:bg-blue-700 !active:bg-blue-800 !text-white"
class="px-5 !bg-[#1fc76f] !hover:bg-[#19b862] !active:bg-[#169e54] !text-white"
>
续费
</Button>

View File

@ -188,7 +188,7 @@
<div class="w-full">
<button v-if="!showPaymentProcessing && !paymentSuccess"
type="button"
class="w-full px-4 py-2 bg-blue-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-blue-700 focus:outline-none"
class="w-full px-4 py-2 bg-[#1fc76f] border border-transparent rounded-md text-sm font-medium text-white hover:bg-[#19b862] focus:outline-none"
@click="createRenewalOrder"
:disabled="isLoading"
>
@ -198,7 +198,7 @@
<div class="flex justify-between w-full" v-else>
<button
type="button"
class="px-4 py-2 bg-blue-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-blue-700 focus:outline-none ml-auto"
class="px-4 py-2 bg-[#1fc76f] border border-transparent rounded-md text-sm font-medium text-white hover:bg-[#19b862] focus:outline-none ml-auto"
@click="cancel"
v-if="paymentSuccess"
>

View File

@ -113,7 +113,7 @@ function payUnpaidInvoices() {
let invoice = _unpaidInvoices;
if (invoice.stripe_invoice_url && team.pg.payment_mode === 'Card') {
window.open(
`/api/method/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`
);
} else {
showAddPrepaidCreditsDialog.value = true;

View File

@ -14,7 +14,7 @@
v-model="paymentGatewayDetails.url"
name="url"
type="text"
placeholder="https://xyz.com/api/method/<endpoint>"
placeholder="https://xyz.com/api/action/<endpoint>"
/>
<div class="flex gap-4">
<FormControl

View File

@ -186,7 +186,7 @@ export default {
async fetchTeams() {
try {
const response = await jingrowRequest({
url: '/api/method/jcloud.api.regional_payments.mpesa.utils.display_mpesa_payment_partners',
url: '/api/action/jcloud.api.regional_payments.mpesa.utils.display_mpesa_payment_partners',
method: 'GET',
});
if (Array.isArray(response)) {
@ -206,7 +206,7 @@ export default {
async fetchTaxPercentage() {
try {
const taxPercentage = await jingrowRequest({
url: '/api/method/jcloud.api.regional_payments.mpesa.utils.get_tax_percentage',
url: '/api/action/jcloud.api.regional_payments.mpesa.utils.get_tax_percentage',
method: 'GET',
params: {
payment_partner: this.partnerInput.value,

View File

@ -172,7 +172,7 @@ method: 'GET',
async fetchPartners() {
try {
const response = await jingrowRequest({
url: '/api/method/jcloud.api.regional_payments.mpesa.utils.display_payment_partners',
url: '/api/action/jcloud.api.regional_payments.mpesa.utils.display_payment_partners',
method: 'GET',
});
if (Array.isArray(response)) {
@ -188,7 +188,7 @@ method: 'GET',
async fetchPaymentGateway() {
try {
const response = await jingrowRequest({
url: '/api/method/jcloud.api.regional_payments.mpesa.utils.display_payment_gateway',
url: '/api/action/jcloud.api.regional_payments.mpesa.utils.display_payment_gateway',
method: 'GET',
});
if (Array.isArray(response)) {

View File

@ -365,7 +365,7 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
this.$team.pg.payment_mode === 'Card'
) {
window.open(
`/api/method/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`,
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`,
);
} else {
this.showAddPrepaidCreditsDialog = true;

View File

@ -7,6 +7,46 @@ import { isMobile } from '../../utils/device';
import { duration } from '../../utils/format';
import ObjectList from '../ObjectList.vue';
// job_type
const jobTypeI18nMap = {
'Update Site Status': '更新站点状态',
'Update Site Configuration': '更新站点配置',
'Install App on Site': '安装应用到站点',
'Uninstall App from Site': '从站点卸载应用',
'Backup Site': '备份站点',
'Restore Site': '恢复站点',
'Create Server': '创建服务器',
'Update In Place': '原地升级',
//
};
function jobTypeI18n(type) {
return jobTypeI18nMap[type] || type;
}
//
const statusI18nMap = {
'Pending': '待处理',
'Running': '运行中',
'Success': '成功',
'Failure': '失败',
};
function statusI18n(status) {
return statusI18nMap[status] || status;
}
//
function formatTimeZh(time) {
if (!time) return '';
const date = typeof time === 'string' ? new Date(time) : time;
const now = new Date();
const diff = (now.getTime() - date.getTime()) / 1000;
if (diff < 60) return '刚刚';
if (diff < 3600) return Math.floor(diff / 60) + '分钟前';
if (diff < 86400) return Math.floor(diff / 3600) + '小时前';
if (diff < 2592000) return Math.floor(diff / 86400) + '天前';
return date.toLocaleDateString('zh-CN');
}
export default {
name: 'SiteJobs',
props: ['name'],
@ -61,22 +101,15 @@ export default {
{
label: '任务类型',
fieldname: 'job_type',
class: 'font-medium'
class: 'font-medium',
format: jobTypeI18n
},
{
label: '状态',
fieldname: 'status',
type: 'Badge',
width: 0.5,
format(value) {
const statusMap = {
'Pending': '待处理',
'Running': '运行中',
'Success': '成功',
'Failure': '失败'
};
return statusMap[value] || value;
}
format: statusI18n
},
{
label: '站点',
@ -102,7 +135,8 @@ export default {
fieldname: 'creation',
type: 'Timestamp',
width: 0.5,
align: 'right'
align: 'right',
format: formatTimeZh
}
].filter(c => (c.condition ? c.condition() : true))
};

View File

@ -42,7 +42,7 @@ export async function switchToTeam(team) {
let canSwitch = false;
try {
canSwitch = await jingrowRequest({
url: '/api/method/jcloud.api.account.can_switch_to_team',
url: '/api/action/jcloud.api.account.can_switch_to_team',
params: { team }
});
} catch (error) {
@ -62,7 +62,7 @@ export async function switchToTeam(team) {
export async function isLastSite(team) {
let count = 0;
count = await jingrowRequest({
url: '/api/method/jcloud.api.account.get_site_count',
url: '/api/action/jcloud.api.account.get_site_count',
params: { team }
});
return Boolean(count === 1);

View File

@ -79,7 +79,7 @@ getInitialData().then(() => {
replaysOnErrorSampleRate: 1.0,
beforeSend(event, hint) {
const ignoreErrors = [
/api\/method\/jcloud.api.client/,
/api\/action\/jcloud.api.client/,
/dynamically imported module/,
/NetworkError when attempting to fetch resource/,
/Failed to fetch/,
@ -154,7 +154,7 @@ getInitialData().then(() => {
function getInitialData() {
if (import.meta.env.DEV) {
return jingrowRequest({
url: '/api/method/jcloud.www.dashboard.get_context_for_dev',
url: '/api/action/jcloud.www.dashboard.get_context_for_dev',
}).then((values) => Object.assign(window, values));
} else {
return Promise.resolve();

View File

@ -1,230 +1,284 @@
import { defineAsyncComponent, h } from 'vue';
import { toast } from 'vue-sonner';
import { getTeam } from '../../data/team';
import router from '../../router';
import { confirmDialog, icon, renderDialog } from '../../utils/components';
import { planTitle } from '../../utils/format';
import type {
ColumnField,
DialogConfig,
FilterField,
Tab,
TabList
} from './types';
import { getUpsellBanner } from '.';
import { isMobile } from '../../utils/device';
import { getToastErrorMessage } from '../../utils/toast';
export function getAppsTab(forSite: boolean) {
return {
label: '应用',
icon: icon('grid'),
route: 'apps',
type: 'list',
condition: docResource => forSite && docResource.pg?.status !== 'Archived',
list: getAppsTabList(forSite)
} satisfies Tab as Tab;
}
function getAppsTabList(forSite: boolean) {
const options = forSite ? siteAppListOptions : benchAppListOptions;
const list: TabList = {
pagetype: '',
filters: () => ({}),
...options,
columns: getAppsTabColumns(forSite),
searchField: !forSite ? 'title' : undefined,
filterControls: r => {
if (forSite) return [];
else
return [
{
type: 'select',
label: '分支',
class: !isMobile() ? 'w-24' : '',
fieldname: 'branch',
options: [
'',
...new Set(r.listResource.data?.map(i => String(i.branch)) || [])
]
},
{
type: 'select',
label: '所有者',
class: !isMobile() ? 'w-24' : '',
fieldname: 'repository_owner',
options: [
'',
...new Set(
r.listResource.data?.map(
i => String(i.repository_url).split('/').at(-2) || ''
) || []
)
]
}
] satisfies FilterField[];
}
};
return list;
}
function getAppsTabColumns(forSite: boolean) {
const appTabColumns: ColumnField[] = [
{
label: '应用',
fieldname: 'title',
width: 1,
suffix(row) {
if (!row.is_app_patched) {
return;
}
return h(
'div',
{
title: '应用已打补丁',
class: 'rounded-full bg-gray-100 p-1'
},
h(icon('hash', 'w-3 h-3'))
);
},
format: (value, row) => value || row.app_title
},
{
label: '计划',
width: 0.75,
class: 'text-gray-600 text-sm',
format(_, row) {
const planText = planTitle(row.plan_info);
if (planText) return `${planText}/月`;
else return '免费';
}
},
{
label: '版本',
fieldname: 'branch',
type: 'Badge',
width: 1,
}
];
if (forSite) return appTabColumns;
return appTabColumns.filter(c => c.label !== '计划');
}
const siteAppListOptions: Partial<TabList> = {
pagetype: 'Site App',
filters: res => {
return { parenttype: 'Site', parent: res.pg?.name };
},
primaryAction({ listResource: apps, documentResource: site }) {
return {
label: '安装应用',
slots: {
prefix: icon('plus')
},
onClick() {
const InstallAppDialog = defineAsyncComponent(
() => import('../../components/site/InstallAppDialog.vue')
);
renderDialog(
h(InstallAppDialog, {
site: site.name,
onInstalled() {
apps.reload();
}
})
);
}
};
},
rowActions({ row, listResource: apps, documentResource: site }) {
let $team = getTeam();
return [
{
label: '在 Desk 中查看',
condition: () => $team.pg?.is_desk_user,
onClick() {
window.open(`/app/app-source/${row.name}`, '_blank');
}
},
{
label: '更改计划',
condition: () => row.plan_info && row.plans.length > 1,
onClick() {
let SiteAppPlanChangeDialog = defineAsyncComponent(
() => import('../../components/site/SiteAppPlanSelectDialog.vue')
);
renderDialog(
h(SiteAppPlanChangeDialog, {
app: row,
currentPlan: row.plans.find(
(plan: Record<string, any>) => plan.name === row.plan_info.name
),
onPlanChanged() {
apps.reload();
}
})
);
}
},
{
label: '卸载',
condition: () => row.app !== 'jingrow',
onClick() {
const dialogConfig: DialogConfig = {
title: `卸载应用`,
message: `您确定要从站点 <b>${site.pg?.name}</b> 卸载应用 <b>${row.title}</b> 吗?<br>
`,
onSuccess({ hide }) {
if (site.uninstallApp.loading) return;
toast.promise(
site.uninstallApp.submit({
app: row.app
}),
{
loading: '正在安排应用卸载...',
success: (jobId: string) => {
hide();
router.push({
name: 'Site Job',
params: {
name: site.name,
id: jobId
}
});
return '应用卸载已安排';
},
error: (e: Error) => getToastErrorMessage(e)
}
);
}
};
confirmDialog(dialogConfig);
}
}
];
}
};
const benchAppListOptions: Partial<TabList> = {
pagetype: 'Bench App',
filters: res => {
return { parenttype: 'Bench', parent: res.pg?.name };
},
rowActions({ row }) {
let $team = getTeam();
return [
{
label: '在 Desk 中查看',
condition: () => $team.pg?.is_desk_user,
onClick() {
window.open(`/app/app-release/${row.release}`, '_blank');
}
}
];
}
import { defineAsyncComponent, h } from 'vue';
import { toast } from 'vue-sonner';
import { getTeam } from '../../data/team';
import router from '../../router';
import { confirmDialog, icon, renderDialog } from '../../utils/components';
import { planTitle } from '../../utils/format';
import type {
ColumnField,
DialogConfig,
FilterField,
Tab,
TabList
} from './types';
import { getUpsellBanner } from '.';
import { isMobile } from '../../utils/device';
import { getToastErrorMessage } from '../../utils/toast';
export function getAppsTab(forSite: boolean) {
return {
label: '应用',
icon: icon('grid'),
route: 'apps',
type: 'list',
condition: docResource => forSite && docResource.pg?.status !== 'Archived',
list: getAppsTabList(forSite)
} satisfies Tab as Tab;
}
function getAppsTabList(forSite: boolean) {
const options = forSite ? siteAppListOptions : benchAppListOptions;
const list: TabList = {
pagetype: '',
filters: () => ({}),
...options,
columns: getAppsTabColumns(forSite),
searchField: !forSite ? 'title' : undefined,
filterControls: r => {
if (forSite) return [];
else
return [
{
type: 'select',
label: '分支',
class: !isMobile() ? 'w-24' : '',
fieldname: 'branch',
options: [
'',
...new Set(r.listResource.data?.map(i => String(i.branch)) || [])
]
},
{
type: 'select',
label: '所有者',
class: !isMobile() ? 'w-24' : '',
fieldname: 'repository_owner',
options: [
'',
...new Set(
r.listResource.data?.map(
i => String(i.repository_url).split('/').at(-2) || ''
) || []
)
]
}
] satisfies FilterField[];
}
};
return list;
}
function getAppsTabColumns(forSite: boolean) {
const appTabColumns: ColumnField[] = [
{
label: '应用',
fieldname: 'title',
width: 1,
suffix(row) {
if (!row.is_app_patched) {
return;
}
return h(
'div',
{
title: '应用已打补丁',
class: 'rounded-full bg-gray-100 p-1'
},
h(icon('hash', 'w-3 h-3'))
);
},
format: (value, row) => value || row.app_title
},
{
label: '计划',
width: 0.75,
class: 'text-gray-600 text-sm',
format(_, row) {
const planText = planTitle(row.plan_info);
if (planText) return `${planText}/月`;
else return '免费';
}
},
{
label: '版本',
fieldname: 'branch',
type: 'Badge',
width: 1,
}
];
// 为站点应用添加操作列,包含卸载按钮
if (forSite) {
appTabColumns.push({
label: '操作',
width: 0.75,
align: 'right',
type: 'Button',
Button: ({ row, listResource, documentResource }) => {
// 如果是 jingrow 应用,不显示卸载按钮
if (row.app === 'jingrow') {
return null;
}
return {
label: '卸载',
variant: 'ghost',
class: 'text-red-600 hover:text-red-700 hover:bg-red-50',
slots: {
prefix: icon('trash-2')
},
onClick: () => {
const appName = row.title || row.app_title;
const dialogConfig: DialogConfig = {
title: `卸载应用`,
message: `您确定要从站点 <b>${documentResource.pg?.name}</b> 卸载应用 <b>${appName}</b> 吗?<br>
`,
onSuccess({ hide }) {
// 立即从列表中移除该应用(乐观更新)
const currentData = listResource.data || [];
const updatedData = currentData.filter(item => item.name !== row.name);
listResource.data = updatedData;
const promise = documentResource.uninstallApp.submit({
app: row.app
});
toast.promise(promise, {
loading: '正在安排应用卸载...',
success: (jobId: string) => {
hide();
return '应用卸载已安排';
},
error: (e: Error) => {
// 如果失败,重新加载列表恢复状态
listResource.reload();
return getToastErrorMessage(e);
}
});
}
};
confirmDialog(dialogConfig);
}
};
}
});
return appTabColumns;
}
return appTabColumns.filter(c => c.label !== '计划');
}
const siteAppListOptions: Partial<TabList> = {
pagetype: 'Site App',
filters: res => {
return { parenttype: 'Site', parent: res.pg?.name };
},
primaryAction({ listResource: apps, documentResource: site }) {
return {
label: '安装应用',
slots: {
prefix: icon('plus')
},
onClick() {
const InstallAppDialog = defineAsyncComponent(
() => import('../../components/site/InstallAppDialog.vue')
);
renderDialog(
h(InstallAppDialog, {
site: site.name,
onInstalled() {
apps.reload();
}
})
);
}
};
},
rowActions({ row, listResource: apps, documentResource: site }) {
let $team = getTeam();
return [
{
label: '在 Desk 中查看',
condition: () => $team.pg?.is_desk_user,
onClick() {
window.open(`/app/app-source/${row.name}`, '_blank');
}
},
{
label: '更改计划',
condition: () => row.plan_info && row.plans.length > 1,
onClick() {
let SiteAppPlanChangeDialog = defineAsyncComponent(
() => import('../../components/site/SiteAppPlanSelectDialog.vue')
);
renderDialog(
h(SiteAppPlanChangeDialog, {
app: row,
currentPlan: row.plans.find(
(plan: Record<string, any>) => plan.name === row.plan_info.name
),
onPlanChanged() {
apps.reload();
}
})
);
}
},
{
label: '卸载',
condition: () => row.app !== 'jingrow',
onClick() {
const appName = row.title || row.app_title;
const dialogConfig: DialogConfig = {
title: `卸载应用`,
message: `您确定要从站点 <b>${site.pg?.name}</b> 卸载应用 <b>${appName}</b> 吗?<br>
`,
onSuccess({ hide }) {
toast.promise(
site.uninstallApp.submit({
app: row.app
}),
{
loading: '正在安排应用卸载...',
success: (jobId: string) => {
hide();
apps.reload();
return '应用卸载已安排';
},
error: (e: Error) => {
return getToastErrorMessage(e);
}
}
);
}
};
confirmDialog(dialogConfig);
}
}
];
}
};
const benchAppListOptions: Partial<TabList> = {
pagetype: 'Bench App',
filters: res => {
return { parenttype: 'Bench', parent: res.pg?.name };
},
rowActions({ row }) {
let $team = getTeam();
return [
{
label: '在 Desk 中查看',
condition: () => $team.pg?.is_desk_user,
onClick() {
window.open(`/app/app-release/${row.release}`, '_blank');
}
}
];
}
};

View File

@ -7,6 +7,34 @@ import { ColumnField, Tab } from './types';
type JobDocTypes = 'Site' | 'Bench' | 'Server' | 'Release Group';
// 英文 job_type 到中文的映射
const jobTypeI18nMap: Record<string, string> = {
'Update Site Status': '更新站点状态',
'Update Site Configuration': '更新站点配置',
'Install App on Site': '安装应用到站点',
'Uninstall App from Site': '从站点卸载应用',
'Backup Site': '备份站点',
'Restore Site': '恢复站点',
'Create Server': '创建服务器',
'Update In Place': '原地升级',
// 可按需补充更多
};
function jobTypeI18n(type: string) {
return jobTypeI18nMap[type] || type;
}
const statusI18nMap: Record<string, string> = {
'Pending': '待处理',
'Running': '运行中',
'Success': '成功',
'Failure': '失败',
};
function statusI18n(status: string) {
return statusI18nMap[status] || status;
}
export function getJobsTab(pagetype: JobDocTypes) {
const jobRoute = getJobRoute(pagetype);
@ -25,12 +53,7 @@ export function getJobsTab(pagetype: JobDocTypes) {
else if (pagetype === 'Release Group') return { group: res.name };
throw unreachable;
},
route(row) {
return {
name: jobRoute,
params: { id: row.name }
};
},
route: undefined,
orderBy: 'creation desc',
searchField: 'job_type',
fields: ['end', 'job_id'],
@ -85,13 +108,15 @@ function getJobTabColumns(pagetype: JobDocTypes) {
{
label: '任务类型',
fieldname: 'job_type',
class: 'font-medium'
class: 'font-medium',
format: jobTypeI18n
},
{
label: '状态',
fieldname: 'status',
type: 'Badge',
width: 0.5
width: 0.5,
format: statusI18n
},
{
label: '站点',
@ -116,10 +141,24 @@ function getJobTabColumns(pagetype: JobDocTypes) {
fieldname: 'creation',
type: 'Timestamp',
width: 0.75,
align: 'right'
align: 'right',
format: (value) => formatTimeZh(value)
}
];
if (pagetype !== 'Site') return columns;
return columns.filter(c => c.fieldname !== 'site');
}
// 中文时间格式化函数
function formatTimeZh(time: string | Date) {
if (!time) return '';
const date = typeof time === 'string' ? new Date(time) : time;
const now = new Date();
const diff = (now.getTime() - date.getTime()) / 1000;
if (diff < 60) return '刚刚';
if (diff < 3600) return Math.floor(diff / 60) + '分钟前';
if (diff < 86400) return Math.floor(diff / 3600) + '小时前';
if (diff < 2592000) return Math.floor(diff / 86400) + '天前';
return date.toLocaleDateString('zh-CN');
}

View File

@ -1,216 +1,228 @@
import type { defineAsyncComponent, h, Component } from 'vue';
import type { icon } from '../../utils/components';
type ListResource = {
data: Record<string, unknown>[];
reload: () => void;
runDocMethod: {
submit: (r: { method: string; [key: string]: any }) => Promise<unknown>;
};
delete: {
submit: (name: string, cb: { onSuccess: () => void }) => Promise<unknown>;
};
};
export interface ResourceBase {
url: string;
auto: boolean;
cache: string[];
}
export interface ResourceWithParams extends ResourceBase {
params: Record<string, unknown>;
}
export interface ResourceWithMakeParams extends ResourceBase {
makeParams: () => Record<string, unknown>;
}
export type Resource = ResourceWithParams | ResourceWithMakeParams;
export interface DocumentResource {
name: string;
pg: Record<string, any>;
[key: string]: any;
}
type Icon = ReturnType<typeof icon>;
type AsyncComponent = ReturnType<typeof defineAsyncComponent>;
export interface DashboardObject {
pagetype: string;
whitelistedMethods: Record<string, string>;
list: List;
detail: Detail;
routes: RouteDetail[];
}
export interface Detail {
titleField: string;
statusBadge: StatusBadge;
breadcrumbs?: Breadcrumbs;
route: string;
tabs: Tab[];
actions: (r: { documentResource: DocumentResource }) => Action[];
}
export interface List {
route: string;
title: string;
fields: string[]; // TODO: Incomplete
searchField: string;
columns: ColumnField[];
orderBy: string;
filterControls: FilterControls;
primaryAction?: PrimaryAction;
}
type R = {
listResource: ListResource;
documentResource: DocumentResource;
};
type FilterControls = (r: R) => FilterField[];
type PrimaryAction = (r: R) => {
label: string;
variant?: string;
slots: {
prefix: Icon;
};
onClick?: () => void;
};
type StatusBadge = (r: { documentResource: DocumentResource }) => {
label: string;
};
export type Breadcrumb = { label: string; route: string };
export type BreadcrumbArgs = {
documentResource: DocumentResource;
items: Breadcrumb[];
};
export type Breadcrumbs = (r: BreadcrumbArgs) => Breadcrumb[];
export interface FilterField {
label: string;
fieldname: string;
type: string;
class?: string;
options?:
| {
pagetype: string;
filters?: {
pagetype_name?: string;
};
}
| string[];
}
export interface ColumnField {
label: string;
fieldname?: string;
class?: string;
width?: string | number;
type?: string;
format?: (value: any, row: Row) => string | undefined;
link?: (value: unknown, row: Row) => string;
prefix?: (row: Row) => Component | undefined;
suffix?: (row: Row) => Component | undefined;
theme?: (value: unknown) => string;
align?: 'left' | 'right';
}
export type Row = Record<string, any>;
export interface Tab {
label: string;
icon: Icon;
route: string;
type: string;
condition?: (r: DocumentResource) => boolean;
childrenRoutes?: string[];
component?: AsyncComponent;
props?: (r: DocumentResource) => Record<string, unknown>;
list?: TabList;
}
export interface TabList {
pagetype?: string;
orderBy?: string;
filters?: (r: DocumentResource) => Record<string, unknown>;
route?: (row: Row) => Route;
pageLength?: number;
columns: ColumnField[];
fields?: Record<string, string[]>[] | string[];
rowActions?: (r: {
row: Row;
listResource: ListResource;
documentResource: DocumentResource;
}) => Action[];
primaryAction?: PrimaryAction;
filterControls?: FilterControls;
banner?: (r: {
documentResource: DocumentResource;
}) => BannerConfig | undefined;
searchField?: string;
experimental?: boolean;
documentation?: string;
resource?: (r: { documentResource: DocumentResource }) => Resource;
}
interface Action {
label: string;
slots?: {
prefix?: Icon;
};
theme?: string;
variant?: string;
onClick?: () => void;
condition?: () => boolean;
route?: Route;
options?: Option[];
}
export interface Route {
name: string;
params: Record<string, unknown>;
}
export interface RouteDetail {
name: string;
path: string;
component: Component;
}
interface Option {
label: string;
icon: Icon | AsyncComponent;
condition: () => boolean;
onClick: () => void;
}
export interface BannerConfig {
title: string;
}
dismissable: boolean;
id: string;
type?: string;
button?: {
label: string;
variant: string;
onClick?: () => void;
};
}
export interface DialogConfig {
title: string;
message: string;
primaryAction?: { onClick: () => void };
onSuccess?: (o: { hide: () => void }) => void;
}
export interface Process {
program: string;
name: string;
status: string;
uptime?: number;
uptime_string?: string;
message?: string;
group?: string;
pid?: number;
import type { defineAsyncComponent, h, Component } from 'vue';
import type { icon } from '../../utils/components';
type ListResource = {
data: Record<string, unknown>[];
reload: () => void;
runDocMethod: {
submit: (r: { method: string; [key: string]: any }) => Promise<unknown>;
};
delete: {
submit: (name: string, cb: { onSuccess: () => void }) => Promise<unknown>;
};
};
export interface ResourceBase {
url: string;
auto: boolean;
cache: string[];
}
export interface ResourceWithParams extends ResourceBase {
params: Record<string, unknown>;
}
export interface ResourceWithMakeParams extends ResourceBase {
makeParams: () => Record<string, unknown>;
}
export type Resource = ResourceWithParams | ResourceWithMakeParams;
export interface DocumentResource {
name: string;
pg: Record<string, any>;
[key: string]: any;
}
type Icon = ReturnType<typeof icon>;
type AsyncComponent = ReturnType<typeof defineAsyncComponent>;
export interface DashboardObject {
pagetype: string;
whitelistedMethods: Record<string, string>;
list: List;
detail: Detail;
routes: RouteDetail[];
}
export interface Detail {
titleField: string;
statusBadge: StatusBadge;
breadcrumbs?: Breadcrumbs;
route: string;
tabs: Tab[];
actions: (r: { documentResource: DocumentResource }) => Action[];
}
export interface List {
route: string;
title: string;
fields: string[]; // TODO: Incomplete
searchField: string;
columns: ColumnField[];
orderBy: string;
filterControls: FilterControls;
primaryAction?: PrimaryAction;
}
type R = {
listResource: ListResource;
documentResource: DocumentResource;
};
type FilterControls = (r: R) => FilterField[];
type PrimaryAction = (r: R) => {
label: string;
variant?: string;
slots: {
prefix: Icon;
};
onClick?: () => void;
};
type StatusBadge = (r: { documentResource: DocumentResource }) => {
label: string;
};
export type Breadcrumb = { label: string; route: string };
export type BreadcrumbArgs = {
documentResource: DocumentResource;
items: Breadcrumb[];
};
export type Breadcrumbs = (r: BreadcrumbArgs) => Breadcrumb[];
export interface FilterField {
label: string;
fieldname: string;
type: string;
class?: string;
options?:
| {
pagetype: string;
filters?: {
pagetype_name?: string;
};
}
| string[];
}
export interface ColumnField {
label: string;
fieldname?: string;
class?: string;
width?: string | number;
type?: string;
format?: (value: any, row: Row) => string | undefined;
link?: (value: unknown, row: Row) => string;
prefix?: (row: Row) => Component | undefined;
suffix?: (row: Row) => Component | undefined;
theme?: (value: unknown) => string;
align?: 'left' | 'right';
Button?: (r: {
row: Row;
listResource: ListResource;
documentResource: DocumentResource;
}) => {
label: string;
variant?: string;
class?: string;
slots?: {
prefix?: Icon;
};
onClick?: () => void;
} | null;
}
export type Row = Record<string, any>;
export interface Tab {
label: string;
icon: Icon;
route: string;
type: string;
condition?: (r: DocumentResource) => boolean;
childrenRoutes?: string[];
component?: AsyncComponent;
props?: (r: DocumentResource) => Record<string, unknown>;
list?: TabList;
}
export interface TabList {
pagetype?: string;
orderBy?: string;
filters?: (r: DocumentResource) => Record<string, unknown>;
route?: (row: Row) => Route;
pageLength?: number;
columns: ColumnField[];
fields?: Record<string, string[]>[] | string[];
rowActions?: (r: {
row: Row;
listResource: ListResource;
documentResource: DocumentResource;
}) => Action[];
primaryAction?: PrimaryAction;
filterControls?: FilterControls;
banner?: (r: {
documentResource: DocumentResource;
}) => BannerConfig | undefined;
searchField?: string;
experimental?: boolean;
documentation?: string;
resource?: (r: { documentResource: DocumentResource }) => Resource;
}
interface Action {
label: string;
slots?: {
prefix?: Icon;
};
theme?: string;
variant?: string;
onClick?: () => void;
condition?: () => boolean;
route?: Route;
options?: Option[];
}
export interface Route {
name: string;
params: Record<string, unknown>;
}
export interface RouteDetail {
name: string;
path: string;
component: Component;
}
interface Option {
label: string;
icon: Icon | AsyncComponent;
condition: () => boolean;
onClick: () => void;
}
export interface BannerConfig {
title: string;
dismissable: boolean;
id: string;
type?: string;
button?: {
label: string;
variant: string;
onClick?: () => void;
};
}
export interface DialogConfig {
title: string;
message: string;
primaryAction?: { onClick: () => void };
onSuccess?: (o: { hide: () => void }) => void;
}
export interface Process {
program: string;
name: string;
status: string;
uptime?: number;
uptime_string?: string;
message?: string;
group?: string;
pid?: number;
}

View File

@ -66,7 +66,7 @@ export default {
async onClick() {
toast.promise(
jingrowRequest({
url: '/api/method/jcloud.api.notifications.mark_all_notifications_as_read',
url: '/api/action/jcloud.api.notifications.mark_all_notifications_as_read',
}),
{
success: () => {

View File

@ -22,6 +22,7 @@ import { trialDays } from '../utils/site';
import { clusterOptions, getUpsellBanner } from './common';
import { getAppsTab } from './common/apps';
import { isMobile } from '../utils/device';
import { getJobsTab } from './common/jobs';
export default {
pagetype: 'Site',
@ -881,6 +882,7 @@ export default {
},
},
},
getJobsTab('Site'),
{
label: '操作',
icon: icon('sliders'),
@ -1257,7 +1259,7 @@ export default {
condition: () =>
site.pg.status !== 'Archived' && site.pg?.setup_wizard_complete,
onClick() {
window.open(`https://${site.name}`, '_blank');
window.open(`https://${site.name}/app`, '_blank');
},
},
{
@ -1340,5 +1342,10 @@ export default {
path: 'updates/:id',
component: () => import('../pages/SiteUpdate.vue'),
},
{
name: 'Site Job',
path: 'jobs/:id',
component: () => import('../pages/JobPage.vue')
}
],
};

View File

@ -191,7 +191,7 @@ export default {
e.stopPropagation();
if (row.stripe_invoice_url && row.payment_mode == 'Card') {
window.open(
`/api/method/jcloud.api.client.run_pg_method?dt=Invoice&dn=${row.name}&method=stripe_payment_url`,
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${row.name}&method=stripe_payment_url`,
);
} else {
this.showBuyPrepaidCreditsDialog = true;

View File

@ -26,7 +26,7 @@ export default {
this.loading = true;
try {
const response = await jingrowRequest({
url: '/api/method/jcloud.api.regional_payments.mpesa.utils.display_invoices_by_partner',
url: '/api/action/jcloud.api.regional_payments.mpesa.utils.display_invoices_by_partner',
method: 'GET',
});
this.invoices = response;

View File

@ -239,6 +239,7 @@ export const statusMap = {
export const deployTypeMap = {
'Migrate': '迁移',
'Pull': '拉取',
'Update': '更新',
'Install': '安装',
'Uninstall': '卸载',

File diff suppressed because it is too large Load Diff

View File

@ -350,7 +350,7 @@ server {
http2_push_preload on;
}
location ~ ^/api/method/jcloud.api.developer.saas.* {
location ~ ^/api/action/jcloud.api.developer.saas.* {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";

View File

@ -478,7 +478,7 @@ class Agent:
"site_backup": {
"name": site_backup.name,
"snapshot_request_key": site_backup.snapshot_request_key,
"snapshot_trigger_url": f"{jcloud_public_base_url}/api/method/jcloud.api.site_backup.create_snapshot",
"snapshot_trigger_url": f"{jcloud_public_base_url}/api/action/jcloud.api.site_backup.create_snapshot",
},
}
return self.create_agent_job(

View File

@ -381,7 +381,7 @@ def send_login_link(email):
minutes = 10
jingrow.cache().set_value(f"one_time_login_key:{key}", email, expires_in_sec=minutes * 60)
link = get_url(f"/api/method/jcloud.api.account.login_using_key?key={key}")
link = get_url(f"/api/action/jcloud.api.account.login_using_key?key={key}")
if jingrow.conf.developer_mode:
print()

View File

@ -30,9 +30,9 @@ class AliyunSMSClient:
def initialize(self):
"""初始化配置信息"""
try:
# 检查 Jcloud Settings 是否存在
if jingrow.exists("Jcloud Settings"):
settings = jingrow.get_single("Jcloud Settings")
# 直接尝试获取 Jcloud Settings
settings = jingrow.get_single("Jcloud Settings")
if 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
else:
@ -40,7 +40,6 @@ class AliyunSMSClient:
self.access_key_id = None
self.access_secret = None
jingrow.log_error("阿里云SMS客户端: Jcloud Settings 尚未配置,请在设置中完成配置")
except Exception as e:
jingrow.log_error(f"阿里云SMS客户端初始化: {str(e)}")
self.access_key_id = None
@ -143,8 +142,11 @@ def get_sms_client():
return sms_client
def send_custom_sms(phone_numbers, message_content, sign_name, template_code):
client = get_sms_client()
return client.send_sms(phone_numbers, template_code, message_content, sign_name)
result = client.send_sms(phone_numbers, template_code, message_content, sign_name)
return result
def generate_verification_code(length=4):
"""生成指定长度的随机数字验证码"""
@ -169,13 +171,10 @@ def verify_code(mobile_no, verification_code, template_code):
return False
def send_renew_sms(phone_numbers, days_remaining, site_end_date):
"""发送网站续费通知短信"""
template_code = "SMS_481605243" # 网站续费通知短信模板编码
template_code = "SMS_489640674" # 网站续费通知短信模板编码
sign_name = "向日葵网络" # 短信签名名称
message_content = {
"day": str(days_remaining),
"site_end_date": str(site_end_date)
}
return send_custom_sms(phone_numbers, message_content, sign_name, template_code)

View File

@ -268,7 +268,7 @@ class RequestGroupByChart(StackedGroupByChart):
def setup_search_filters(self):
super().setup_search_filters()
self.search = self.search.filter("match_phrase", json__transaction_type="request").exclude(
"match_phrase", json__request__path="/api/method/ping"
"match_phrase", json__request__path="/api/action/ping"
)
if self.resource_type == ResourceType.SITE:
self.search = self.search.filter("match_phrase", json__site=self.name)
@ -411,8 +411,8 @@ def get_advanced_analytics(name, timezone, duration="7d"):
def get_more_request_detail_fn_names():
return {
"/api/method/run_pg_method": get_run_pg_method_methodnames.__name__,
"/api/method/jingrow.desk.query_report.run": get_query_report_run_reports.__name__,
"/api/action/run_pg_method": get_run_pg_method_methodnames.__name__,
"/api/action/jingrow.desk.query_report.run": get_query_report_run_reports.__name__,
}
@ -542,7 +542,7 @@ class RunDocMethodMethodNames(RequestGroupByChart):
def setup_search_filters(self):
super().setup_search_filters()
self.search = self.search.filter("match_phrase", json__request__path="/api/method/run_pg_method")
self.search = self.search.filter("match_phrase", json__request__path="/api/action/run_pg_method")
def get_run_pg_method_methodnames(site, agg_type, timezone, timespan, timegrain):
@ -557,7 +557,7 @@ class QueryReportRunReports(RequestGroupByChart):
def setup_search_filters(self):
super().setup_search_filters()
self.search = self.search.filter(
"match_phrase", json__request__path="/api/method/jingrow.desk.query_report.run"
"match_phrase", json__request__path="/api/action/jingrow.desk.query_report.run"
)
@ -761,7 +761,7 @@ def request_logs(name, timezone, date, sort=None, start=0):
{"match_phrase": {"json.site": name}},
{"range": {"@timestamp": {"gt": f"{date}||-1d/d", "lte": f"{date}||/d"}}},
],
"must_not": [{"match_phrase": {"json.request.path": "/api/method/ping"}}],
"must_not": [{"match_phrase": {"json.request.path": "/api/action/ping"}}],
}
},
"sort": sort_value,

View File

@ -8,7 +8,6 @@ import json
import segno
import io
import base64
import traceback
import jingrow
from jingrow import _ # Import this for translation functionality
from jingrow.core.utils import find
@ -708,7 +707,7 @@ def generate_stk_push(**kwargs):
mpesa_setup = get_mpesa_setup_for_team(partner[0])
try:
callback_url = (
get_request_site_address(True) + "/api/method/jcloud.api.billing.verify_m_pesa_transaction"
get_request_site_address(True) + "/api/action/jcloud.api.billing.verify_m_pesa_transaction"
)
env = "production" if not mpesa_setup.sandbox else "sandbox"
# for sandbox, business shortcode is same as till number
@ -783,28 +782,24 @@ def handle_transaction_result(transaction_response, integration_request):
create_mpesa_request_log(
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
)
jingrow.log_error(f"Mpesa: Transaction failed with error {e}")
elif result_code == 1037: # User unreachable (Phone off or timeout)
status = "Failed"
create_mpesa_request_log(
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
)
jingrow.log_error("Mpesa: User cannot be reached (Phone off or timeout)")
elif result_code == 1032: # User cancelled the request
status = "Cancelled"
create_mpesa_request_log(
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
)
jingrow.log_error("Mpesa: Request cancelled by user")
else: # Other failure codes
status = "Failed"
create_mpesa_request_log(
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
)
jingrow.log_error(f"Mpesa: Transaction failed with ResultCode {result_code}")
return status
@ -1003,7 +998,7 @@ def handle_alipay_notification():
return "success"
except Exception as e:
jingrow.log_error(f"处理失败: {str(e)}\n{traceback.format_exc()}", "支付宝错误")
jingrow.log_error("支付宝错误", f"处理失败: {str(e)}")
return "fail"
@jingrow.whitelist(allow_guest=True)
@ -1075,17 +1070,11 @@ def handle_wechatpay_notification():
return "SUCCESS"
except Exception as e:
jingrow.log_error(
f"处理微信支付通知数据失败: {str(e)}\n调用栈: {traceback.format_exc()}\n请求头: {headers}\n请求体: {body}",
"微信支付解密错误"
)
jingrow.log_error("微信支付解密错误", f"处理微信支付通知数据失败: {str(e)}\n请求头: {headers}\n请求体: {body}")
return "SUCCESS" # 返回成功避免微信重复发送通知
except Exception as e:
jingrow.log_error(
f"处理微信支付通知失败: {str(e)}\n调用栈: {traceback.format_exc()}",
"微信支付错误"
)
jingrow.log_error("微信支付错误", f"处理微信支付通知失败: {str(e)}")
return "SUCCESS" # 返回成功避免微信重复发送通知
@ -1102,10 +1091,7 @@ def handle_order_payment_complete(order_id):
return True
except Exception as e:
jingrow.log_error(
f"处理订单 {order_id} 支付完成事件失败: {str(e)}\n{traceback.format_exc()}",
f"订单处理错误"
)
jingrow.log_error("订单处理错误", f"处理订单 {order_id} 支付完成事件失败: {str(e)}")
return False
def process_balance_recharge(order):
@ -1129,10 +1115,7 @@ def process_balance_recharge(order):
jingrow.db.commit()
except Exception as e:
jingrow.log_error(
f"余额充值失败: 团队 {order.team}, 金额 {order.total_amount}, 错误: {str(e)}\n{traceback.format_exc()}",
"余额充值错误"
)
jingrow.log_error("余额充值错误", f"余额充值失败: 团队 {order.team}, 金额 {order.total_amount}, 错误: {str(e)}")
raise
def process_site_renew(order_id):
@ -1170,16 +1153,17 @@ def process_site_renew(order_id):
# 更新站点到期日期
site.site_end_date = new_end_date
site.save(ignore_permissions=True)
# 续费后自动激活站点(如有需要)
if site.status in ["Inactive", "Suspended"]:
try:
site.activate()
except Exception as e:
jingrow.log_error("站点自动激活失败", f"站点 {site_name} 续费后自动激活失败: {str(e)}")
# 更新订单状态为交易成功,防止重复处理
jingrow.db.set_value("Order", order.name, "status", "交易成功")
# 记录成功的审计日志
jingrow.log_error(
message=f"网站续费成功: {site_name}, 支付方式:{order.payment_method}, 订单号:{order_id}, 续费 {renewal_months} 个月, 到期日延长至 {new_end_date}",
title="网站续费成功"
)
return {
"name": site.name,
"url": site_name,
@ -1254,7 +1238,7 @@ def create_alipay_order_for_recharge(amount):
"payment_record": payment_record.name
}
except Exception as e:
jingrow.log_error(f"创建支付宝订单失败: {str(e)}", "Order")
jingrow.log_error("Order", f"创建支付宝订单失败: {str(e)}")
jingrow.throw(f"创建支付宝订单失败: {str(e)}")
@ -1303,7 +1287,7 @@ def create_wechatpay_order_for_recharge(amount):
# 检查URL是否为空
if not qr_code_url:
jingrow.log_error("微信支付URL生成为空", "微信支付错误")
jingrow.log_error("微信支付错误", "微信支付URL生成为空")
# 使用提供的函数生成二维码图片
qr_code_image = generate_qr_code(qr_code_url)
@ -1316,7 +1300,7 @@ def create_wechatpay_order_for_recharge(amount):
return result
except Exception as e:
jingrow.log_error(f"创建微信支付订单失败: {str(e)}\n{traceback.format_exc()}", "微信支付错误")
jingrow.log_error("微信支付错误", f"创建微信支付订单失败: {str(e)}")
jingrow.throw(f"创建微信支付订单失败")
@ -1361,7 +1345,7 @@ def create_order(**kwargs):
}
except Exception as e:
jingrow.log_error(f"创建站点订单失败: {str(e)}\n{traceback.format_exc()}", "订单错误")
jingrow.log_error("订单错误", f"创建站点订单失败: {str(e)}")
return {
"success": False,
"message": f"创建订单失败: {str(e)}"
@ -1418,7 +1402,7 @@ def create_renewal_order(site, renewal_months=1):
"order": order.as_dict()
}
except Exception as e:
jingrow.log_error(f"创建续费订单失败: {str(e)}", "续费订单错误")
jingrow.log_error("续费订单错误", f"创建续费订单失败: {str(e)}")
return {
"success": False,
"message": f"创建续费订单失败: {str(e)}"
@ -1484,7 +1468,7 @@ def process_balance_payment_for_order(order_id):
}
except Exception as e:
jingrow.log_error(f"余额支付失败: {str(e)}\n{traceback.format_exc()}", "支付错误")
jingrow.log_error("支付错误", f"余额支付失败: {str(e)}")
return {
"status": "Error",
"message": f"余额支付失败: {str(e)}"
@ -1574,10 +1558,7 @@ def process_balance_payment_for_renew_order(order_id):
raise inner_error
except Exception as e:
jingrow.log_error(
message=f"余额支付续费失败: {str(e)}\n{traceback.format_exc()}",
title="续费支付错误"
)
jingrow.log_error("续费支付错误", f"余额支付续费失败: {str(e)}")
return {
"success": False,
"status": "Error",
@ -1628,7 +1609,7 @@ def process_alipay_order(order_id):
"success": True
}
except Exception as e:
jingrow.log_error(f"创建支付宝订单失败: {str(e)}", "Order")
jingrow.log_error("Order", f"创建支付宝订单失败: {str(e)}")
jingrow.throw(f"创建支付宝订单失败: {str(e)}")
@jingrow.whitelist()
@ -1668,7 +1649,7 @@ def process_wechatpay_order(order_id):
# 检查URL是否为空
if not qr_code_url:
jingrow.log_error("微信支付URL生成为空", "微信支付错误")
jingrow.log_error("微信支付错误", "微信支付URL生成为空")
jingrow.throw("生成支付URL失败")
# 生成二维码图片
@ -1686,11 +1667,11 @@ def process_wechatpay_order(order_id):
}
except Exception as e:
jingrow.log_error(f"创建微信支付订单失败: {str(e)}\n{traceback.format_exc()}", "微信支付错误")
jingrow.log_error("微信支付错误", f"创建微信支付订单失败: {str(e)}")
jingrow.throw(f"创建微信支付订单失败")
except Exception as e:
jingrow.log_error(f"创建微信支付订单失败: {str(e)}\n{traceback.format_exc()}", "微信支付错误")
jingrow.log_error("微信支付错误", f"创建微信支付订单失败: {str(e)}")
jingrow.throw(f"创建微信支付订单失败")
@jingrow.whitelist()
@ -1709,7 +1690,7 @@ def check_site_order_payment_status(order_id):
}
except Exception as e:
jingrow.log_error(f"检查订单状态失败: {str(e)}\n{traceback.format_exc()}", "订单错误")
jingrow.log_error("订单错误", f"检查订单状态失败: {str(e)}")
return {
"success": False,
"message": f"检查订单状态失败: {str(e)}"
@ -1774,7 +1755,7 @@ def get_orders(page=1, page_size=20, search=None):
}
except Exception as e:
jingrow.log_error(f"获取订单列表失败: {str(e)}\n{traceback.format_exc()}", "订单列表错误")
jingrow.log_error("订单列表错误", f"获取订单列表失败: {str(e)}")
return {
"orders": [],
"total": 0,
@ -1811,7 +1792,7 @@ def get_order_details(name):
}
except Exception as e:
jingrow.log_error(f"获取订单详情失败: {str(e)}\n{traceback.format_exc()}", "订单详情错误")
jingrow.log_error("订单详情错误", f"获取订单详情失败: {str(e)}")
return {
"error": str(e)
}
@ -1882,7 +1863,7 @@ def get_balance_transactions(page=1, page_size=20, search=None):
}
except Exception as e:
jingrow.log_error(f"获取余额记录失败: {str(e)}\n{traceback.format_exc()}", "余额记录错误")
jingrow.log_error("余额记录错误", f"获取余额记录失败: {str(e)}")
return {
"transactions": [],
"total": 0,

View File

@ -160,7 +160,7 @@ class DeveloperApiHandler:
jingrow.db.commit()
return get_url(
f"/api/method/jcloud.api.marketplace.login_via_token?token={token}&team={team}&site={self.app_subscription_pg.site}"
f"/api/action/jcloud.api.marketplace.login_via_token?token={token}&team={team}&site={self.app_subscription_pg.site}"
)

View File

@ -73,7 +73,7 @@ class SaasApiHandler:
).insert(ignore_permissions=True)
domain = jingrow.db.get_value("Saas App", self.app_subscription_pg.app, "custom_domain")
return f"https://{domain}/api/method/jcloud.api.saas.login_via_token?token={token}&team={self.app_subscription_pg.team}"
return f"https://{domain}/api/action/jcloud.api.saas.login_via_token?token={token}&team={self.app_subscription_pg.team}"
def get_trial_expiry(self):
return jingrow.db.get_value("Site", self.app_subscription_pg.site, "trial_end_date")

View File

@ -285,7 +285,7 @@ def event_log():
try:
host_name = jingrow.db.get_value("Site", site, "host_name") or site
requests.post(
f"https://{host_name}/api/method/email_delivery_service.controller.update_status",
f"https://{host_name}/api/action/email_delivery_service.controller.update_status",
data=data,
)
except Exception as e:

View File

@ -1181,7 +1181,7 @@ def get_discount_percent(plan, discount=0.0):
if team.jerp_partner and jingrow.get_value("Marketplace App Plan", plan, "partner_discount"):
client = get_jingrow_io_connection()
response = client.session.post(
f"{client.url}/api/method/partner_relationship_management.api.get_partner_type",
f"{client.url}/api/action/partner_relationship_management.api.get_partner_type",
data={"email": team.partner_email},
headers=client.headers,
)

View File

@ -1099,7 +1099,7 @@ def get(name):
site_name = jingrow.db.get_value("Site Domain", name, "site")
if site_name:
jingrow.local.response["type"] = "redirect"
jingrow.local.response["location"] = f"/api/method/jcloud.api.site.get?name={site_name}"
jingrow.local.response["location"] = f"/api/action/jcloud.api.site.get?name={site_name}"
return None
raise
rg_info = jingrow.db.get_value("Release Group", site.group, ["team", "version", "public"], as_dict=True)

View File

@ -12,48 +12,48 @@ JCLOUD_AUTH_MAX_ENTRIES = 1000000
ALLOWED_PATHS = [
"/api/method/create-site-migration",
"/api/method/create-version-upgrade",
"/api/method/migrate-to-private-bench",
"/api/method/find-my-sites",
"/api/method/jingrow.core.pagetype.communication.email.mark_email_as_seen",
"/api/method/jingrow.realtime.get_user_info",
"/api/method/jingrow.realtime.can_subscribe_pg",
"/api/method/jingrow.realtime.can_subscribe_pagetype",
"/api/method/jingrow.realtime.has_permission",
"/api/method/jingrow.www.login.login_via_jingrow",
"/api/method/jingrow.integrations.oauth2.authorize",
"/api/method/jingrow.integrations.oauth2.approve",
"/api/method/jingrow.integrations.oauth2.get_token",
"/api/method/jingrow.integrations.oauth2.openid_profile",
"/api/method/jingrow.integrations.oauth2_logins.login_via_jingrow",
"/api/method/jingrow.website.pagetype.web_page_view.web_page_view.make_view_log",
"/api/method/get-user-sites-list-for-new-ticket",
"/api/method/ping",
"/api/method/login",
"/api/method/logout",
"/api/method/jcloud.jcloud.pagetype.razorpay_webhook_log.razorpay_webhook_log.razorpay_webhook_handler",
"/api/method/jcloud.jcloud.pagetype.razorpay_webhook_log.razorpay_webhook_log.razorpay_authorized_payment_handler",
"/api/method/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler",
"/api/method/upload_file",
"/api/method/jingrow.search.web_search",
"/api/method/jingrow.email.queue.unsubscribe",
"/api/method/jcloud.utils.telemetry.capture_read_event",
"/api/method/validate_plan_change",
"/api/method/marketplace-apps",
"/api/method/jcloud.www.dashboard.get_context_for_dev",
"/api/method/jingrow.website.pagetype.web_form.web_form.accept",
"/api/method/jingrow.core.pagetype.user.user.test_password_strength",
"/api/method/jingrow.core.pagetype.user.user.update_password",
"/api/method/get_central_migration_data",
"/api/action/create-site-migration",
"/api/action/create-version-upgrade",
"/api/action/migrate-to-private-bench",
"/api/action/find-my-sites",
"/api/action/jingrow.core.pagetype.communication.email.mark_email_as_seen",
"/api/action/jingrow.realtime.get_user_info",
"/api/action/jingrow.realtime.can_subscribe_pg",
"/api/action/jingrow.realtime.can_subscribe_pagetype",
"/api/action/jingrow.realtime.has_permission",
"/api/action/jingrow.www.login.login_via_jingrow",
"/api/action/jingrow.integrations.oauth2.authorize",
"/api/action/jingrow.integrations.oauth2.approve",
"/api/action/jingrow.integrations.oauth2.get_token",
"/api/action/jingrow.integrations.oauth2.openid_profile",
"/api/action/jingrow.integrations.oauth2_logins.login_via_jingrow",
"/api/action/jingrow.website.pagetype.web_page_view.web_page_view.make_view_log",
"/api/action/get-user-sites-list-for-new-ticket",
"/api/action/ping",
"/api/action/login",
"/api/action/logout",
"/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",
"/api/action/upload_file",
"/api/action/jingrow.search.web_search",
"/api/action/jingrow.email.queue.unsubscribe",
"/api/action/jcloud.utils.telemetry.capture_read_event",
"/api/action/validate_plan_change",
"/api/action/marketplace-apps",
"/api/action/jcloud.www.dashboard.get_context_for_dev",
"/api/action/jingrow.website.pagetype.web_form.web_form.accept",
"/api/action/jingrow.core.pagetype.user.user.test_password_strength",
"/api/action/jingrow.core.pagetype.user.user.update_password",
"/api/action/get_central_migration_data",
]
ALLOWED_WILDCARD_PATHS = [
"/api/method/jcloud.api.",
"/api/method/jcloud.saas.",
"/api/method/wiki.",
"/api/method/jingrow.integrations.oauth2_logins.",
"/api/method/jcloud.www.marketplace.index.",
"/api/action/jcloud.api.",
"/api/action/jcloud.saas.",
"/api/action/wiki.",
"/api/action/jingrow.integrations.oauth2_logins.",
"/api/action/jcloud.www.marketplace.index.",
]
DENIED_PATHS = [
@ -70,7 +70,7 @@ DENIED_WILDCARD_PATHS = [
def hook(): # noqa: C901
if jingrow.form_dict.cmd:
path = f"/api/method/{jingrow.form_dict.cmd}"
path = f"/api/action/{jingrow.form_dict.cmd}"
else:
path = jingrow.request.path

View File

@ -30,7 +30,7 @@ def start_ngrok_and_set_webhook(context):
print(f"Inspect logs at {tunnel.api_url}")
stripe = get_stripe()
url = f"{public_url}/api/method/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler"
url = f"{public_url}/api/action/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler"
stripe.WebhookEndpoint.modify(
jingrow.db.get_single_value("Jcloud Settings", "stripe_webhook_endpoint_id"), url=url
)

View File

@ -72,7 +72,7 @@ website_redirects = [
{"source": "/dashboard/f-login", "target": get_jingrow_io_auth_url() or "/"},
{
"source": "/suspended-site",
"target": "/api/method/jcloud.api.handle_suspended_site_redirection",
"target": "/api/action/jcloud.api.handle_suspended_site_redirection",
},
{"source": "/f-login", "target": "/dashboard/f-login"},
{"source": "/signup", "target": "/jerp/signup"},
@ -178,7 +178,6 @@ scheduler_events = {
"jcloud.experimental.pagetype.referral_bonus.referral_bonus.credit_referral_bonuses",
"jcloud.jcloud.pagetype.log_counter.log_counter.record_counts",
"jcloud.jcloud.pagetype.incident.incident.notify_ignored_servers",
"jcloud.jcloud.pagetype.site.site.send_renew_notification",
],
"daily_long": [
"jcloud.jcloud.audit.check_bench_fields",
@ -302,6 +301,12 @@ scheduler_events = {
"jcloud.jcloud.pagetype.tls_certificate.tls_certificate.retrigger_failed_wildcard_tls_callbacks",
"jcloud.infrastructure.pagetype.ssh_access_audit.ssh_access_audit.run",
],
"0 1 * * *": [
"jcloud.jcloud.pagetype.site.site.deactivate_expired_sites",
],
"0 10 * * *": [
"jcloud.jcloud.pagetype.site.site.send_renew_notification",
],
},
}

View File

@ -198,7 +198,7 @@ class AccountRequest(Document):
"invited_by": self.invited_by,
"link": url,
"read_pixel_path": get_url(
f"/api/method/jcloud.utils.telemetry.capture_read_event?email={self.email}"
f"/api/action/jcloud.utils.telemetry.capture_read_event?email={self.email}"
),
"otp": self.otp,
}
@ -225,7 +225,7 @@ class AccountRequest(Document):
def get_verification_url(self):
if self.saas:
return get_url(f"/api/method/jcloud.api.saas.validate_account_request?key={self.request_key}")
return get_url(f"/api/action/jcloud.api.saas.validate_account_request?key={self.request_key}")
if self.product_trial:
return get_url(
f"/dashboard/saas/{self.product_trial}/oauth?key={self.request_key}&email={self.email}"

View File

@ -744,7 +744,7 @@ class Invoice(Document):
def get_pdf(self):
print_format = self.meta.default_print_format
return jingrow.utils.get_url(
f"/api/method/jingrow.utils.print_format.download_pdf?pagetype=Invoice&name={self.name}&format={print_format}&no_letterhead=0"
f"/api/action/jingrow.utils.print_format.download_pdf?pagetype=Invoice&name={self.name}&format={print_format}&no_letterhead=0"
)
@jingrow.whitelist()
@ -766,7 +766,7 @@ class Invoice(Document):
return None
client = self.get_jingrowio_connection()
response = client.session.post(
f"{client.url}/api/method/create-fc-invoice",
f"{client.url}/api/action/create-fc-invoice",
headers=client.headers,
data={
"team": team.as_json(),
@ -819,7 +819,7 @@ class Invoice(Document):
"no_letterhead": 0,
}
)
url = client.url + "/api/method/jingrow.utils.print_format.download_pdf?" + params
url = client.url + "/api/action/jingrow.utils.print_format.download_pdf?" + params
with client.session.get(url, headers=client.headers, stream=True) as r:
r.raise_for_status()
@ -917,7 +917,7 @@ class Invoice(Document):
"no_letterhead": 0,
}
)
url = f"{client.url}/api/method/jingrow.utils.print_format.download_pdf?{params}"
url = f"{client.url}/api/action/jingrow.utils.print_format.download_pdf?{params}"
with client.session.get(url, headers=client.headers, stream=True) as r:
r.raise_for_status()
@ -1138,7 +1138,7 @@ def create_sales_invoice_on_external_site(transaction_response):
# Post to the external site's sales invoice creation API
response = client.session.post(
f"{client.url}/api/method/jingrow.client.insert",
f"{client.url}/api/action/jingrow.client.insert",
headers=client.headers,
json={"pg": data},
)

View File

@ -195,7 +195,7 @@ class JcloudSettings(Document):
def create_stripe_webhook(self):
stripe = get_stripe()
url = jingrow.utils.get_url(
"/api/method/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler"
"/api/action/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler"
)
webhook = stripe.WebhookEndpoint.create(
url=url,
@ -226,7 +226,7 @@ class JcloudSettings(Document):
return {
"name": app_name,
"url": "https://jingrow.cloud",
"hook_attributes": {"url": get_url("api/method/jcloud.api.github.hook")},
"hook_attributes": {"url": get_url("api/action/jcloud.api.github.hook")},
"redirect_url": get_url("github/redirect"),
"description": "Managed Jingrow Hosting",
"public": True,

View File

@ -101,7 +101,7 @@ class PrometheusAlertRule(Document):
{
"name": "web.hook",
"webhook_configs": [
{"url": jingrow.utils.get_url("api/method/jcloud.api.monitoring.alert")}
{"url": jingrow.utils.get_url("api/action/jcloud.api.monitoring.alert")}
],
}
],

View File

@ -453,7 +453,7 @@ class ReleaseGroup(Document, TagHelpers):
with suppress(AttributeError, RuntimeError):
if (
not jingrow.flags.in_test
and jingrow.request.path == "/api/method/jcloud.api.bench.change_branch"
and jingrow.request.path == "/api/action/jcloud.api.bench.change_branch"
):
return # Separate validation exists in set_app_source
for app in self.apps:
@ -819,7 +819,7 @@ class ReleaseGroup(Document, TagHelpers):
}
).insert()
link = get_url(f"/api/method/jcloud.api.bench.confirm_bench_transfer?key={key}")
link = get_url(f"/api/action/jcloud.api.bench.confirm_bench_transfer?key={key}")
if jingrow.conf.developer_mode:
print(f"Bench transfer link for {team_mail_id}\n{link}\n")

View File

@ -299,7 +299,10 @@ class Site(Document, TagHelpers):
status = jingrow.get_value(inst.pagetype, inst.name, "status", for_update=True)
if status not in allowed_status:
jingrow.throw(
f"Site action not allowed for site with status: {jingrow.bold(status)}.\nAllowed status are: {jingrow.bold(comma_and(allowed_status))}."
jingrow._("不允许对状态为 {0} 的站点执行此操作。\n允许的状态为:{1}").format(
jingrow.bold(jingrow._(status)),
jingrow.bold(comma_and([jingrow._(s) for s in allowed_status]))
)
)
return func(inst, *args, **kwargs)
@ -1394,7 +1397,7 @@ class Site(Document, TagHelpers):
}
).insert()
link = get_url(f"/api/method/jcloud.api.site.confirm_site_transfer?key={key}")
link = get_url(f"/api/action/jcloud.api.site.confirm_site_transfer?key={key}")
if jingrow.conf.developer_mode:
print(f"\nSite transfer link for {team_mail_id}\n{link}\n")
@ -1511,7 +1514,7 @@ class Site(Document, TagHelpers):
if user == "Administrator":
password = get_decrypted_password("Site", self.name, "admin_password")
response = requests.post(
f"https://{self.name}/api/method/login",
f"https://{self.name}/api/action/login",
data={"usr": user, "pwd": password},
)
sid = response.cookies.get("sid")
@ -1678,7 +1681,7 @@ class Site(Document, TagHelpers):
"pagetype": "Webhook",
"webhook_pagetype": "User",
"enabled": 1,
"request_url": "https://jingrow.com/api/method/jcloud.api.site_login.sync_product_site_user",
"request_url": "https://jingrow.com/api/action/jcloud.api.site_login.sync_product_site_user",
"request_method": "POST",
"request_structure": "JSON",
"webhook_json": """{ "user_info": { "email": "{{pg.email}}", "enabled": "{{pg.enabled}}" } }""",
@ -1796,7 +1799,7 @@ class Site(Document, TagHelpers):
self.save()
def ping(self):
return requests.get(f"https://{self.name}/api/method/ping")
return requests.get(f"https://{self.name}/api/action/ping")
def _set_configuration(self, config: list[dict]):
"""Similar to _update_configuration but will replace full configuration at once
@ -3760,7 +3763,7 @@ def create_site_status_update_webhook_event(site: str):
return
create_webhook_event("Site Status Update", record, record.team)
@jingrow.whitelist()
def send_renew_notification():
"""
发送站点续费通知给用户:
@ -3882,3 +3885,25 @@ def send_renew_notification():
)
except Exception as e:
jingrow.log_error(f"站点 {site.name} 发送续费通知失败: {str(e)}", "Renewal Notification Error")
@jingrow.whitelist()
def deactivate_expired_sites():
"""
自动将已到期的站点site_end_date<=今天且状态为Active或Suspended设为Inactive
每天定时任务调用
"""
today = jingrow.utils.today()
sites = jingrow.get_all(
"Site",
filters={
"status": ["in", ["Active", "Suspended"]],
"site_end_date": ["<", today]
},
fields=["name", "status"]
)
for site in sites:
try:
site_pg = jingrow.get_pg("Site", site.name)
site_pg.deactivate()
except Exception as e:
jingrow.log_error("Auto Inactivate Site Error", f"站点 {site.name} 到期自动停用失败: {str(e)}")

View File

@ -921,7 +921,7 @@ class Team(Document):
# fetch partner level from framework.jingrow.com
client = get_jingrow_io_connection()
response = client.session.get(
f"{client.url}/api/method/get_partner_level",
f"{client.url}/api/action/get_partner_level",
headers=client.headers,
params={"email": self.partner_email},
)

View File

@ -133,7 +133,7 @@ class TeamDeletionRequest(PersonalDataDeletionRequest):
def generate_url_for_confirmation(self):
params = get_signed_params({"team": self.team})
api = jingrow.utils.get_url("/api/method/jcloud.api.account.delete_team")
api = jingrow.utils.get_url("/api/action/jcloud.api.account.delete_team")
url = f"{api}?{params}"
if jingrow.conf.developer_mode:
@ -173,7 +173,7 @@ class TeamDeletionRequest(PersonalDataDeletionRequest):
client = get_jingrow_io_connection()
response = client.session.delete(
f"{client.url}/api/method/delete-fc-team",
f"{client.url}/api/action/delete-fc-team",
data={"team": self.team},
headers=client.headers,
)

View File

@ -41,7 +41,7 @@ class TestTeamDeletionRequest(unittest.TestCase):
deletion_url = self.team_deletion_request.generate_url_for_confirmation()
self.assertTrue(
deletion_url.startswith(
jingrow.utils.get_url("/api/method/jcloud.api.account.delete_team")
jingrow.utils.get_url("/api/action/jcloud.api.account.delete_team")
)
)

View File

@ -155,7 +155,7 @@ class MarketplaceAppSubscription(Document):
try:
for path in paths:
requests.post(
f"https://{self.site}/api/method/{path}",
f"https://{self.site}/api/action/{path}",
data={"app": self.app, "plan": self.plan},
)
except Exception:

View File

@ -75,7 +75,7 @@ class PartnerApprovalRequest(Document):
jingrow.throw("Failed to create approval request. Please contact support.")
customer = jingrow.db.get_value("Team", self.requested_by, "user")
link = get_url(f"/api/method/jcloud.api.partner.approve_partner_request?key={self.key}")
link = get_url(f"/api/action/jcloud.api.partner.approve_partner_request?key={self.key}")
jingrow.sendmail(
subject="Partner Approval Request",

View File

@ -59,7 +59,7 @@ scrape_configs:
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
regex: 'https://(.*)/api/method/ping'
regex: 'https://(.*)/api/action/ping'
- target_label: __address__
replacement: '{{ server }}'
file_sd_configs:
@ -80,7 +80,7 @@ scrape_configs:
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
regex: 'https://(.*)/api/method/ping'
regex: 'https://(.*)/api/action/ping'
- target_label: __address__
replacement: '{{ server }}'
file_sd_configs:

View File

@ -5,11 +5,11 @@ OIDC_ISSUER = "Jingrow"
OIDC_SCOPE = "openid email"
OIDC_AUTHORIZATION_ENDPOINT = (
"{{ sentry_oauth_server_url }}/api/method/jingrow.integrations.oauth2.authorize"
"{{ sentry_oauth_server_url }}/api/action/jingrow.integrations.oauth2.authorize"
)
OIDC_TOKEN_ENDPOINT = (
"{{ sentry_oauth_server_url }}/api/method/jingrow.integrations.oauth2.get_token"
"{{ sentry_oauth_server_url }}/api/action/jingrow.integrations.oauth2.get_token"
)
OIDC_USERINFO_ENDPOINT = (
"{{ sentry_oauth_server_url }}/api/method/jingrow.integrations.oauth2.openid_profile"
"{{ sentry_oauth_server_url }}/api/action/jingrow.integrations.oauth2.openid_profile"
)

View File

@ -18,7 +18,7 @@ export default async function call(method, args) {
headers['X-Jingrow-CSRF-Token'] = window.csrf_token;
}
const res = await fetch(`/api/method/${method}`, {
const res = await fetch(`/api/action/${method}`, {
method: 'POST',
headers,
body: JSON.stringify(args),

View File

@ -10,7 +10,7 @@ from html2text import html2text
def get_remote_script(remote_site):
print("Retrieving Site Migrator...")
request_url = f"https://{remote_site}/api/method/jcloud.api.script"
request_url = f"https://{remote_site}/api/action/jcloud.api.script"
request = requests.get(request_url)
if request.status_code / 100 != 2:

View File

@ -14,7 +14,7 @@ import requests
def jingrowcloud_migrator():
print("Retreiving Site Migrator...")
remote_site = "jingrow.com"
request_url = "https://{}/api/method/jcloud.api.script_2".format(remote_site)
request_url = "https://{}/api/action/jcloud.api.script_2".format(remote_site)
request = requests.get(request_url)
if request.status_code / 100 != 2:

View File

@ -30,7 +30,7 @@ Sometimes, we may need to pass the secret token to frontend for some specific ta
**Request**
```bash
curl --location --request POST 'http://fc.local:8000/api/method/jcloud.saas.api.auth.generate_access_token' \
curl --location --request POST 'http://fc.local:8000/api/action/jcloud.saas.api.auth.generate_access_token' \
--header 'x-site: oka-hdz-qpj.tanmoy.fc.jingrow.dev' \
--header 'x-site-token: 004f85a3ae93927d2f0fcc668d11cb71'
```

View File

@ -471,32 +471,32 @@ def jingrowcloud_migrator(local_site, jingrow_provider):
remote_site = jingrow_provider or jingrow.conf.jingrowcloud_url
scheme = "https"
login_url = "{}://{}/api/method/login".format(scheme, remote_site)
upload_url = "{}://{}/api/method/jcloud.api.site.new".format(scheme, remote_site)
remote_link_url = "{}://{}/api/method/jcloud.api.site.get_upload_link".format(
login_url = "{}://{}/api/action/login".format(scheme, remote_site)
upload_url = "{}://{}/api/action/jcloud.api.site.new".format(scheme, remote_site)
remote_link_url = "{}://{}/api/action/jcloud.api.site.get_upload_link".format(
scheme, remote_site
)
register_remote_url = "{}://{}/api/method/jcloud.api.site.uploaded_backup_info".format(
register_remote_url = "{}://{}/api/action/jcloud.api.site.uploaded_backup_info".format(
scheme, remote_site
)
options_url = "{}://{}/api/method/jcloud.api.site.options_for_new".format(
options_url = "{}://{}/api/action/jcloud.api.site.options_for_new".format(
scheme, remote_site
)
site_exists_url = "{}://{}/api/method/jcloud.api.site.exists".format(
site_exists_url = "{}://{}/api/action/jcloud.api.site.exists".format(
scheme, remote_site
)
site_info_url = "{}://{}/api/method/jcloud.api.site.get".format(scheme, remote_site)
account_details_url = "{}://{}/api/method/jcloud.api.account.get".format(
site_info_url = "{}://{}/api/action/jcloud.api.site.get".format(scheme, remote_site)
account_details_url = "{}://{}/api/action/jcloud.api.account.get".format(
scheme, remote_site
)
all_site_url = "{}://{}/api/method/jcloud.api.site.all".format(scheme, remote_site)
restore_site_url = "{}://{}/api/method/jcloud.api.site.restore".format(
all_site_url = "{}://{}/api/action/jcloud.api.site.all".format(scheme, remote_site)
restore_site_url = "{}://{}/api/action/jcloud.api.site.restore".format(
scheme, remote_site
)
finish_multipart_url = "{}://{}/api/method/jcloud.api.site.multipart_exit".format(
finish_multipart_url = "{}://{}/api/action/jcloud.api.site.multipart_exit".format(
scheme, remote_site
)
site_plans_url = "{}://{}/api/method/jcloud.api.site.get_site_plans".format(
site_plans_url = "{}://{}/api/action/jcloud.api.site.get_site_plans".format(
scheme, remote_site
)

View File

@ -51,7 +51,7 @@
{% endmacro %}
{% macro form(fields, action='') %}
<form action="/api/method/{{ action }}" method="POST">
<form action="/api/action/{{ action }}" method="POST">
<section class="space-y-4">
{%- for df in fields -%}
<p class="space-y-2">

View File

@ -6,7 +6,7 @@ Action,行动,
Action Type,动作类型,
Actions,操作,
Activate Site,激活站点,
Active,,
Active,,
Add,添加,
Add Domain,添加域名,
Additional Permissions,额外的权限,
@ -131,7 +131,7 @@ IP Address,IP地址,
Image,图像,
Impersonate Team,模拟团队,
In Progress,进行中,
Inactive,非活动的,
Inactive,未激活,
Index,索引,
Info,信息,
Instance Type,实例类型,
@ -187,7 +187,7 @@ Patch,补丁,
Payment Date,付款日期,
Payment Gateway,支付网关,
Payment Mode,支付方式,
Pending,,
Pending,处理,
Pending Verification,待验证,
Percent,百分之,
Permissions,权限,

1 API Key API密钥
6 Action Type 动作类型
7 Actions 操作
8 Activate Site 激活站点
9 Active 活动 激活
10 Add 添加
11 Add Domain 添加域名
12 Additional Permissions 额外的权限
131 Image 图像
132 Impersonate Team 模拟团队
133 In Progress 进行中
134 Inactive 非活动的 未激活
135 Index 索引
136 Info 信息
137 Instance Type 实例类型
187 Payment Date 付款日期
188 Payment Gateway 支付网关
189 Payment Mode 支付方式
190 Pending 等待 待处理
191 Pending Verification 待验证
192 Percent 百分之
193 Permissions 权限

View File

@ -342,7 +342,7 @@ class RemoteJingrowSite:
def _validate_jingrow_site(self):
"""Validates if Jingrow Site and sets RemoteBackupRetrieval.site"""
res = requests.get(f"{self.user_site}/api/method/jingrow.ping", timeout=(5, 10))
res = requests.get(f"{self.user_site}/api/action/jingrow.ping", timeout=(5, 10))
if not res.ok:
jingrow.throw("Invalid Jingrow Site")
@ -354,7 +354,7 @@ class RemoteJingrowSite:
def _validate_user_permissions(self):
"""Validates user permssions on Jingrow Site and sets RemoteBackupRetrieval.user_sid"""
response = requests.post(
f"{self.site}/api/method/login",
f"{self.site}/api/action/login",
data={"usr": self.user_login, "pwd": self.password_login},
timeout=(5, 10),
)
@ -393,7 +393,7 @@ class RemoteJingrowSite:
headers = {"Accept": "application/json", "Content-Type": "application/json"}
suffix = f"?sid={self.user_sid}" if self.user_sid else ""
res = requests.get(
f"{self.site}/api/method/jingrow.utils.backups.fetch_latest_backups{suffix}",
f"{self.site}/api/action/jingrow.utils.backups.fetch_latest_backups{suffix}",
headers=headers,
timeout=(5, 10),
)

View File

@ -225,7 +225,7 @@ def get_partner_external_connection(mpesa_setup):
api_secret = pg.get_password("api_secret")
url = pg.url
site_name = url.split("/api/method")[0]
site_name = url.split("/api/action")[0]
# Establish connection
jingrow.local._external_conn = JingrowClient(site_name, api_key=api_key, api_secret=api_secret)
return jingrow.local._external_conn

View File

@ -137,7 +137,7 @@ function initiateRequestForLoginToJingrowCloud() {
function requestLoginToFC(freezing_msg) {
jingrow.request.call({
url: `${jingrow_cloud_base_endpoint}/api/method/jcloud.api.developer.saas.send_verification_code`,
url: `${jingrow_cloud_base_endpoint}/api/action/jcloud.api.developer.saas.send_verification_code`,
type: 'POST',
args: {
domain: window.location.hostname,
@ -198,7 +198,7 @@ function showFCLogchinalog(email) {
return;
}
jingrow.request.call({
url: `${jingrow_cloud_base_endpoint}/api/method/jcloud.api.developer.saas.verify_verification_code`,
url: `${jingrow_cloud_base_endpoint}/api/action/jcloud.api.developer.saas.verify_verification_code`,
type: 'POST',
args: {
domain: window.location.hostname,
@ -211,14 +211,14 @@ function showFCLogchinalog(email) {
if (r.login_token) {
fc_login_dialog.hide();
window.open(
`${jingrow_cloud_base_endpoint}/api/method/jcloud.api.developer.saas.login_to_fc?token=${r.login_token}`,
`${jingrow_cloud_base_endpoint}/api/action/jcloud.api.developer.saas.login_to_fc?token=${r.login_token}`,
'_blank',
);
jingrow.msgprint({
title: __('Jingrow Login Successful'),
indicator: 'green',
message: __(
`<p>You will be redirected to Jingrow soon.</p><p>If you haven\'t been redirected, <a href="${jingrow_cloud_base_endpoint}/api/method/jcloud.api.developer.saas.login_to_fc?token=${r.login_token}" target="_blank">Click here to login</a></p>`,
`<p>You will be redirected to Jingrow soon.</p><p>If you haven\'t been redirected, <a href="${jingrow_cloud_base_endpoint}/api/action/jcloud.api.developer.saas.login_to_fc?token=${r.login_token}" target="_blank">Click here to login</a></p>`,
),
});
} else {

1094
yarn.lock

File diff suppressed because it is too large Load Diff