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:
commit
21bd71bc91
@ -22,7 +22,7 @@ export default async function call(method, args) {
|
|||||||
|
|
||||||
updateState(this, 'RequestStarted', null);
|
updateState(this, 'RequestStarted', null);
|
||||||
|
|
||||||
const res = await fetch(`/api/method/${method}`, {
|
const res = await fetch(`/api/action/${method}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(args)
|
body: JSON.stringify(args)
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export default class FileUploader {
|
|||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
xhr.open('POST', '/api/method/upload_file', true);
|
xhr.open('POST', '/api/action/upload_file', true);
|
||||||
xhr.setRequestHeader('Accept', 'application/json');
|
xhr.setRequestHeader('Accept', 'application/json');
|
||||||
if (window.csrf_token && window.csrf_token !== '{{ csrf_token }}') {
|
if (window.csrf_token && window.csrf_token !== '{{ csrf_token }}') {
|
||||||
xhr.setRequestHeader('X-Jingrow-CSRF-Token', window.csrf_token);
|
xhr.setRequestHeader('X-Jingrow-CSRF-Token', window.csrf_token);
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export default class S3FileUploader {
|
|||||||
async function getUploadLink() {
|
async function getUploadLink() {
|
||||||
try {
|
try {
|
||||||
let response = await fetch(
|
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();
|
let data = await response.json();
|
||||||
return data.message;
|
return data.message;
|
||||||
|
|||||||
@ -81,7 +81,7 @@ if (window.jcloud_frontend_posthog_host?.includes('https://')) {
|
|||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
request({
|
request({
|
||||||
url: '/api/method/jcloud.www.dashboard.get_context_for_dev'
|
url: '/api/action/jcloud.www.dashboard.get_context_for_dev'
|
||||||
}).then(values => {
|
}).then(values => {
|
||||||
for (let key in values) {
|
for (let key in values) {
|
||||||
window[key] = values[key];
|
window[key] = values[key];
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const FAKE_BASE_URL = 'http://fc.tests';
|
|||||||
|
|
||||||
const restHandlers = [
|
const restHandlers = [
|
||||||
rest.post(
|
rest.post(
|
||||||
FAKE_BASE_URL + '/api/method/jcloud.api.site.features',
|
FAKE_BASE_URL + '/api/action/jcloud.api.site.features',
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
return res(ctx.status(200), ctx.json({ message: apps }));
|
return res(ctx.status(200), ctx.json({ message: apps }));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -322,7 +322,7 @@ SitePlansCards: defineAsyncComponent(() => import('./SitePlansCards.vue')),
|
|||||||
this.isChangingPlan = true;
|
this.isChangingPlan = true;
|
||||||
const plan_name = this.selectedPlan?.name;
|
const plan_name = this.selectedPlan?.name;
|
||||||
let request = createResource({
|
let request = createResource({
|
||||||
url: '/api/method/jcloud.api.client.run_pg_method',
|
url: '/api/action/jcloud.api.client.run_pg_method',
|
||||||
params: {
|
params: {
|
||||||
dt: 'Site',
|
dt: 'Site',
|
||||||
dn: this.site,
|
dn: this.site,
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
<div class="text-base text-gray-600" v-else-if="column.type == 'Timestamp'">
|
<div class="text-base text-gray-600" v-else-if="column.type == 'Timestamp'">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<Tooltip :text="$format.date(value)">
|
<Tooltip :text="$format.date(value)">
|
||||||
{{ value ? $dayjs(value).fromNow() : '' }}
|
{{ value ? (column.format ? formattedValue : $dayjs(value).fromNow()) : '' }}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -67,7 +67,7 @@
|
|||||||
<Button
|
<Button
|
||||||
@click="openRenewalDialog"
|
@click="openRenewalDialog"
|
||||||
v-if="$site.pg.site_end_date && !$site.pg.current_plan?.is_trial_plan"
|
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>
|
</Button>
|
||||||
|
|||||||
@ -188,7 +188,7 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<button v-if="!showPaymentProcessing && !paymentSuccess"
|
<button v-if="!showPaymentProcessing && !paymentSuccess"
|
||||||
type="button"
|
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"
|
@click="createRenewalOrder"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
>
|
>
|
||||||
@ -198,7 +198,7 @@
|
|||||||
<div class="flex justify-between w-full" v-else>
|
<div class="flex justify-between w-full" v-else>
|
||||||
<button
|
<button
|
||||||
type="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"
|
@click="cancel"
|
||||||
v-if="paymentSuccess"
|
v-if="paymentSuccess"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -113,7 +113,7 @@ function payUnpaidInvoices() {
|
|||||||
let invoice = _unpaidInvoices;
|
let invoice = _unpaidInvoices;
|
||||||
if (invoice.stripe_invoice_url && team.pg.payment_mode === 'Card') {
|
if (invoice.stripe_invoice_url && team.pg.payment_mode === 'Card') {
|
||||||
window.open(
|
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 {
|
} else {
|
||||||
showAddPrepaidCreditsDialog.value = true;
|
showAddPrepaidCreditsDialog.value = true;
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
v-model="paymentGatewayDetails.url"
|
v-model="paymentGatewayDetails.url"
|
||||||
name="url"
|
name="url"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="https://xyz.com/api/method/<endpoint>"
|
placeholder="https://xyz.com/api/action/<endpoint>"
|
||||||
/>
|
/>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<FormControl
|
<FormControl
|
||||||
|
|||||||
@ -186,7 +186,7 @@ export default {
|
|||||||
async fetchTeams() {
|
async fetchTeams() {
|
||||||
try {
|
try {
|
||||||
const response = await jingrowRequest({
|
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',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
if (Array.isArray(response)) {
|
if (Array.isArray(response)) {
|
||||||
@ -206,7 +206,7 @@ export default {
|
|||||||
async fetchTaxPercentage() {
|
async fetchTaxPercentage() {
|
||||||
try {
|
try {
|
||||||
const taxPercentage = await jingrowRequest({
|
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',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
payment_partner: this.partnerInput.value,
|
payment_partner: this.partnerInput.value,
|
||||||
|
|||||||
@ -172,7 +172,7 @@ method: 'GET',
|
|||||||
async fetchPartners() {
|
async fetchPartners() {
|
||||||
try {
|
try {
|
||||||
const response = await jingrowRequest({
|
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',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
if (Array.isArray(response)) {
|
if (Array.isArray(response)) {
|
||||||
@ -188,7 +188,7 @@ method: 'GET',
|
|||||||
async fetchPaymentGateway() {
|
async fetchPaymentGateway() {
|
||||||
try {
|
try {
|
||||||
const response = await jingrowRequest({
|
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',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
if (Array.isArray(response)) {
|
if (Array.isArray(response)) {
|
||||||
|
|||||||
@ -365,7 +365,7 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
|
|||||||
this.$team.pg.payment_mode === 'Card'
|
this.$team.pg.payment_mode === 'Card'
|
||||||
) {
|
) {
|
||||||
window.open(
|
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 {
|
} else {
|
||||||
this.showAddPrepaidCreditsDialog = true;
|
this.showAddPrepaidCreditsDialog = true;
|
||||||
|
|||||||
@ -7,6 +7,46 @@ import { isMobile } from '../../utils/device';
|
|||||||
import { duration } from '../../utils/format';
|
import { duration } from '../../utils/format';
|
||||||
import ObjectList from '../ObjectList.vue';
|
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 {
|
export default {
|
||||||
name: 'SiteJobs',
|
name: 'SiteJobs',
|
||||||
props: ['name'],
|
props: ['name'],
|
||||||
@ -61,22 +101,15 @@ export default {
|
|||||||
{
|
{
|
||||||
label: '任务类型',
|
label: '任务类型',
|
||||||
fieldname: 'job_type',
|
fieldname: 'job_type',
|
||||||
class: 'font-medium'
|
class: 'font-medium',
|
||||||
|
format: jobTypeI18n
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
fieldname: 'status',
|
fieldname: 'status',
|
||||||
type: 'Badge',
|
type: 'Badge',
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
format(value) {
|
format: statusI18n
|
||||||
const statusMap = {
|
|
||||||
'Pending': '待处理',
|
|
||||||
'Running': '运行中',
|
|
||||||
'Success': '成功',
|
|
||||||
'Failure': '失败'
|
|
||||||
};
|
|
||||||
return statusMap[value] || value;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '站点',
|
label: '站点',
|
||||||
@ -102,7 +135,8 @@ export default {
|
|||||||
fieldname: 'creation',
|
fieldname: 'creation',
|
||||||
type: 'Timestamp',
|
type: 'Timestamp',
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
align: 'right'
|
align: 'right',
|
||||||
|
format: formatTimeZh
|
||||||
}
|
}
|
||||||
].filter(c => (c.condition ? c.condition() : true))
|
].filter(c => (c.condition ? c.condition() : true))
|
||||||
};
|
};
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export async function switchToTeam(team) {
|
|||||||
let canSwitch = false;
|
let canSwitch = false;
|
||||||
try {
|
try {
|
||||||
canSwitch = await jingrowRequest({
|
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 }
|
params: { team }
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -62,7 +62,7 @@ export async function switchToTeam(team) {
|
|||||||
export async function isLastSite(team) {
|
export async function isLastSite(team) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
count = await jingrowRequest({
|
count = await jingrowRequest({
|
||||||
url: '/api/method/jcloud.api.account.get_site_count',
|
url: '/api/action/jcloud.api.account.get_site_count',
|
||||||
params: { team }
|
params: { team }
|
||||||
});
|
});
|
||||||
return Boolean(count === 1);
|
return Boolean(count === 1);
|
||||||
|
|||||||
@ -79,7 +79,7 @@ getInitialData().then(() => {
|
|||||||
replaysOnErrorSampleRate: 1.0,
|
replaysOnErrorSampleRate: 1.0,
|
||||||
beforeSend(event, hint) {
|
beforeSend(event, hint) {
|
||||||
const ignoreErrors = [
|
const ignoreErrors = [
|
||||||
/api\/method\/jcloud.api.client/,
|
/api\/action\/jcloud.api.client/,
|
||||||
/dynamically imported module/,
|
/dynamically imported module/,
|
||||||
/NetworkError when attempting to fetch resource/,
|
/NetworkError when attempting to fetch resource/,
|
||||||
/Failed to fetch/,
|
/Failed to fetch/,
|
||||||
@ -154,7 +154,7 @@ getInitialData().then(() => {
|
|||||||
function getInitialData() {
|
function getInitialData() {
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
return jingrowRequest({
|
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));
|
}).then((values) => Object.assign(window, values));
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
@ -1,230 +1,284 @@
|
|||||||
import { defineAsyncComponent, h } from 'vue';
|
import { defineAsyncComponent, h } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { getTeam } from '../../data/team';
|
import { getTeam } from '../../data/team';
|
||||||
import router from '../../router';
|
import router from '../../router';
|
||||||
import { confirmDialog, icon, renderDialog } from '../../utils/components';
|
import { confirmDialog, icon, renderDialog } from '../../utils/components';
|
||||||
import { planTitle } from '../../utils/format';
|
import { planTitle } from '../../utils/format';
|
||||||
import type {
|
import type {
|
||||||
ColumnField,
|
ColumnField,
|
||||||
DialogConfig,
|
DialogConfig,
|
||||||
FilterField,
|
FilterField,
|
||||||
Tab,
|
Tab,
|
||||||
TabList
|
TabList
|
||||||
} from './types';
|
} from './types';
|
||||||
import { getUpsellBanner } from '.';
|
import { getUpsellBanner } from '.';
|
||||||
import { isMobile } from '../../utils/device';
|
import { isMobile } from '../../utils/device';
|
||||||
import { getToastErrorMessage } from '../../utils/toast';
|
import { getToastErrorMessage } from '../../utils/toast';
|
||||||
|
|
||||||
export function getAppsTab(forSite: boolean) {
|
export function getAppsTab(forSite: boolean) {
|
||||||
return {
|
return {
|
||||||
label: '应用',
|
label: '应用',
|
||||||
icon: icon('grid'),
|
icon: icon('grid'),
|
||||||
route: 'apps',
|
route: 'apps',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
condition: docResource => forSite && docResource.pg?.status !== 'Archived',
|
condition: docResource => forSite && docResource.pg?.status !== 'Archived',
|
||||||
list: getAppsTabList(forSite)
|
list: getAppsTabList(forSite)
|
||||||
} satisfies Tab as Tab;
|
} satisfies Tab as Tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAppsTabList(forSite: boolean) {
|
function getAppsTabList(forSite: boolean) {
|
||||||
const options = forSite ? siteAppListOptions : benchAppListOptions;
|
const options = forSite ? siteAppListOptions : benchAppListOptions;
|
||||||
const list: TabList = {
|
const list: TabList = {
|
||||||
pagetype: '',
|
pagetype: '',
|
||||||
filters: () => ({}),
|
filters: () => ({}),
|
||||||
...options,
|
...options,
|
||||||
columns: getAppsTabColumns(forSite),
|
columns: getAppsTabColumns(forSite),
|
||||||
searchField: !forSite ? 'title' : undefined,
|
searchField: !forSite ? 'title' : undefined,
|
||||||
filterControls: r => {
|
filterControls: r => {
|
||||||
if (forSite) return [];
|
if (forSite) return [];
|
||||||
else
|
else
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
label: '分支',
|
label: '分支',
|
||||||
class: !isMobile() ? 'w-24' : '',
|
class: !isMobile() ? 'w-24' : '',
|
||||||
fieldname: 'branch',
|
fieldname: 'branch',
|
||||||
options: [
|
options: [
|
||||||
'',
|
'',
|
||||||
...new Set(r.listResource.data?.map(i => String(i.branch)) || [])
|
...new Set(r.listResource.data?.map(i => String(i.branch)) || [])
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
label: '所有者',
|
label: '所有者',
|
||||||
class: !isMobile() ? 'w-24' : '',
|
class: !isMobile() ? 'w-24' : '',
|
||||||
fieldname: 'repository_owner',
|
fieldname: 'repository_owner',
|
||||||
options: [
|
options: [
|
||||||
'',
|
'',
|
||||||
...new Set(
|
...new Set(
|
||||||
r.listResource.data?.map(
|
r.listResource.data?.map(
|
||||||
i => String(i.repository_url).split('/').at(-2) || ''
|
i => String(i.repository_url).split('/').at(-2) || ''
|
||||||
) || []
|
) || []
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] satisfies FilterField[];
|
] satisfies FilterField[];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAppsTabColumns(forSite: boolean) {
|
function getAppsTabColumns(forSite: boolean) {
|
||||||
const appTabColumns: ColumnField[] = [
|
const appTabColumns: ColumnField[] = [
|
||||||
{
|
{
|
||||||
label: '应用',
|
label: '应用',
|
||||||
fieldname: 'title',
|
fieldname: 'title',
|
||||||
width: 1,
|
width: 1,
|
||||||
suffix(row) {
|
suffix(row) {
|
||||||
if (!row.is_app_patched) {
|
if (!row.is_app_patched) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
'div',
|
'div',
|
||||||
{
|
{
|
||||||
title: '应用已打补丁',
|
title: '应用已打补丁',
|
||||||
class: 'rounded-full bg-gray-100 p-1'
|
class: 'rounded-full bg-gray-100 p-1'
|
||||||
},
|
},
|
||||||
h(icon('hash', 'w-3 h-3'))
|
h(icon('hash', 'w-3 h-3'))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
format: (value, row) => value || row.app_title
|
format: (value, row) => value || row.app_title
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '计划',
|
label: '计划',
|
||||||
width: 0.75,
|
width: 0.75,
|
||||||
class: 'text-gray-600 text-sm',
|
class: 'text-gray-600 text-sm',
|
||||||
format(_, row) {
|
format(_, row) {
|
||||||
const planText = planTitle(row.plan_info);
|
const planText = planTitle(row.plan_info);
|
||||||
if (planText) return `${planText}/月`;
|
if (planText) return `${planText}/月`;
|
||||||
else return '免费';
|
else return '免费';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '版本',
|
label: '版本',
|
||||||
fieldname: 'branch',
|
fieldname: 'branch',
|
||||||
type: 'Badge',
|
type: 'Badge',
|
||||||
width: 1,
|
width: 1,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (forSite) return appTabColumns;
|
// 为站点应用添加操作列,包含卸载按钮
|
||||||
return appTabColumns.filter(c => c.label !== '计划');
|
if (forSite) {
|
||||||
}
|
appTabColumns.push({
|
||||||
|
label: '操作',
|
||||||
const siteAppListOptions: Partial<TabList> = {
|
width: 0.75,
|
||||||
pagetype: 'Site App',
|
align: 'right',
|
||||||
filters: res => {
|
type: 'Button',
|
||||||
return { parenttype: 'Site', parent: res.pg?.name };
|
Button: ({ row, listResource, documentResource }) => {
|
||||||
},
|
// 如果是 jingrow 应用,不显示卸载按钮
|
||||||
primaryAction({ listResource: apps, documentResource: site }) {
|
if (row.app === 'jingrow') {
|
||||||
return {
|
return null;
|
||||||
label: '安装应用',
|
}
|
||||||
slots: {
|
|
||||||
prefix: icon('plus')
|
return {
|
||||||
},
|
label: '卸载',
|
||||||
onClick() {
|
variant: 'ghost',
|
||||||
const InstallAppDialog = defineAsyncComponent(
|
class: 'text-red-600 hover:text-red-700 hover:bg-red-50',
|
||||||
() => import('../../components/site/InstallAppDialog.vue')
|
slots: {
|
||||||
);
|
prefix: icon('trash-2')
|
||||||
|
},
|
||||||
renderDialog(
|
onClick: () => {
|
||||||
h(InstallAppDialog, {
|
const appName = row.title || row.app_title;
|
||||||
site: site.name,
|
const dialogConfig: DialogConfig = {
|
||||||
onInstalled() {
|
title: `卸载应用`,
|
||||||
apps.reload();
|
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;
|
||||||
rowActions({ row, listResource: apps, documentResource: site }) {
|
|
||||||
let $team = getTeam();
|
const promise = documentResource.uninstallApp.submit({
|
||||||
|
app: row.app
|
||||||
return [
|
});
|
||||||
{
|
|
||||||
label: '在 Desk 中查看',
|
toast.promise(promise, {
|
||||||
condition: () => $team.pg?.is_desk_user,
|
loading: '正在安排应用卸载...',
|
||||||
onClick() {
|
success: (jobId: string) => {
|
||||||
window.open(`/app/app-source/${row.name}`, '_blank');
|
hide();
|
||||||
}
|
return '应用卸载已安排';
|
||||||
},
|
},
|
||||||
{
|
error: (e: Error) => {
|
||||||
label: '更改计划',
|
// 如果失败,重新加载列表恢复状态
|
||||||
condition: () => row.plan_info && row.plans.length > 1,
|
listResource.reload();
|
||||||
onClick() {
|
return getToastErrorMessage(e);
|
||||||
let SiteAppPlanChangeDialog = defineAsyncComponent(
|
}
|
||||||
() => import('../../components/site/SiteAppPlanSelectDialog.vue')
|
});
|
||||||
);
|
}
|
||||||
renderDialog(
|
};
|
||||||
h(SiteAppPlanChangeDialog, {
|
confirmDialog(dialogConfig);
|
||||||
app: row,
|
}
|
||||||
currentPlan: row.plans.find(
|
};
|
||||||
(plan: Record<string, any>) => plan.name === row.plan_info.name
|
}
|
||||||
),
|
});
|
||||||
onPlanChanged() {
|
|
||||||
apps.reload();
|
return appTabColumns;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
);
|
return appTabColumns.filter(c => c.label !== '计划');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
const siteAppListOptions: Partial<TabList> = {
|
||||||
label: '卸载',
|
pagetype: 'Site App',
|
||||||
condition: () => row.app !== 'jingrow',
|
filters: res => {
|
||||||
onClick() {
|
return { parenttype: 'Site', parent: res.pg?.name };
|
||||||
const dialogConfig: DialogConfig = {
|
},
|
||||||
title: `卸载应用`,
|
primaryAction({ listResource: apps, documentResource: site }) {
|
||||||
message: `您确定要从站点 <b>${site.pg?.name}</b> 卸载应用 <b>${row.title}</b> 吗?<br>
|
return {
|
||||||
所有与此应用相关的页面类型和模块将被移除。`,
|
label: '安装应用',
|
||||||
onSuccess({ hide }) {
|
slots: {
|
||||||
if (site.uninstallApp.loading) return;
|
prefix: icon('plus')
|
||||||
toast.promise(
|
},
|
||||||
site.uninstallApp.submit({
|
onClick() {
|
||||||
app: row.app
|
const InstallAppDialog = defineAsyncComponent(
|
||||||
}),
|
() => import('../../components/site/InstallAppDialog.vue')
|
||||||
{
|
);
|
||||||
loading: '正在安排应用卸载...',
|
|
||||||
success: (jobId: string) => {
|
renderDialog(
|
||||||
hide();
|
h(InstallAppDialog, {
|
||||||
router.push({
|
site: site.name,
|
||||||
name: 'Site Job',
|
onInstalled() {
|
||||||
params: {
|
apps.reload();
|
||||||
name: site.name,
|
}
|
||||||
id: jobId
|
})
|
||||||
}
|
);
|
||||||
});
|
}
|
||||||
return '应用卸载已安排';
|
};
|
||||||
},
|
},
|
||||||
error: (e: Error) => getToastErrorMessage(e)
|
rowActions({ row, listResource: apps, documentResource: site }) {
|
||||||
}
|
let $team = getTeam();
|
||||||
);
|
|
||||||
}
|
return [
|
||||||
};
|
{
|
||||||
confirmDialog(dialogConfig);
|
label: '在 Desk 中查看',
|
||||||
}
|
condition: () => $team.pg?.is_desk_user,
|
||||||
}
|
onClick() {
|
||||||
];
|
window.open(`/app/app-source/${row.name}`, '_blank');
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
{
|
||||||
const benchAppListOptions: Partial<TabList> = {
|
label: '更改计划',
|
||||||
pagetype: 'Bench App',
|
condition: () => row.plan_info && row.plans.length > 1,
|
||||||
filters: res => {
|
onClick() {
|
||||||
return { parenttype: 'Bench', parent: res.pg?.name };
|
let SiteAppPlanChangeDialog = defineAsyncComponent(
|
||||||
},
|
() => import('../../components/site/SiteAppPlanSelectDialog.vue')
|
||||||
rowActions({ row }) {
|
);
|
||||||
let $team = getTeam();
|
renderDialog(
|
||||||
return [
|
h(SiteAppPlanChangeDialog, {
|
||||||
{
|
app: row,
|
||||||
label: '在 Desk 中查看',
|
currentPlan: row.plans.find(
|
||||||
condition: () => $team.pg?.is_desk_user,
|
(plan: Record<string, any>) => plan.name === row.plan_info.name
|
||||||
onClick() {
|
),
|
||||||
window.open(`/app/app-release/${row.release}`, '_blank');
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@ -7,6 +7,34 @@ import { ColumnField, Tab } from './types';
|
|||||||
|
|
||||||
type JobDocTypes = 'Site' | 'Bench' | 'Server' | 'Release Group';
|
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) {
|
export function getJobsTab(pagetype: JobDocTypes) {
|
||||||
const jobRoute = getJobRoute(pagetype);
|
const jobRoute = getJobRoute(pagetype);
|
||||||
|
|
||||||
@ -25,12 +53,7 @@ export function getJobsTab(pagetype: JobDocTypes) {
|
|||||||
else if (pagetype === 'Release Group') return { group: res.name };
|
else if (pagetype === 'Release Group') return { group: res.name };
|
||||||
throw unreachable;
|
throw unreachable;
|
||||||
},
|
},
|
||||||
route(row) {
|
route: undefined,
|
||||||
return {
|
|
||||||
name: jobRoute,
|
|
||||||
params: { id: row.name }
|
|
||||||
};
|
|
||||||
},
|
|
||||||
orderBy: 'creation desc',
|
orderBy: 'creation desc',
|
||||||
searchField: 'job_type',
|
searchField: 'job_type',
|
||||||
fields: ['end', 'job_id'],
|
fields: ['end', 'job_id'],
|
||||||
@ -85,13 +108,15 @@ function getJobTabColumns(pagetype: JobDocTypes) {
|
|||||||
{
|
{
|
||||||
label: '任务类型',
|
label: '任务类型',
|
||||||
fieldname: 'job_type',
|
fieldname: 'job_type',
|
||||||
class: 'font-medium'
|
class: 'font-medium',
|
||||||
|
format: jobTypeI18n
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
fieldname: 'status',
|
fieldname: 'status',
|
||||||
type: 'Badge',
|
type: 'Badge',
|
||||||
width: 0.5
|
width: 0.5,
|
||||||
|
format: statusI18n
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '站点',
|
label: '站点',
|
||||||
@ -116,10 +141,24 @@ function getJobTabColumns(pagetype: JobDocTypes) {
|
|||||||
fieldname: 'creation',
|
fieldname: 'creation',
|
||||||
type: 'Timestamp',
|
type: 'Timestamp',
|
||||||
width: 0.75,
|
width: 0.75,
|
||||||
align: 'right'
|
align: 'right',
|
||||||
|
format: (value) => formatTimeZh(value)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (pagetype !== 'Site') return columns;
|
if (pagetype !== 'Site') return columns;
|
||||||
return columns.filter(c => c.fieldname !== 'site');
|
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');
|
||||||
}
|
}
|
||||||
@ -1,216 +1,228 @@
|
|||||||
import type { defineAsyncComponent, h, Component } from 'vue';
|
import type { defineAsyncComponent, h, Component } from 'vue';
|
||||||
import type { icon } from '../../utils/components';
|
import type { icon } from '../../utils/components';
|
||||||
|
|
||||||
type ListResource = {
|
type ListResource = {
|
||||||
data: Record<string, unknown>[];
|
data: Record<string, unknown>[];
|
||||||
reload: () => void;
|
reload: () => void;
|
||||||
runDocMethod: {
|
runDocMethod: {
|
||||||
submit: (r: { method: string; [key: string]: any }) => Promise<unknown>;
|
submit: (r: { method: string; [key: string]: any }) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
delete: {
|
delete: {
|
||||||
submit: (name: string, cb: { onSuccess: () => void }) => Promise<unknown>;
|
submit: (name: string, cb: { onSuccess: () => void }) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export interface ResourceBase {
|
export interface ResourceBase {
|
||||||
url: string;
|
url: string;
|
||||||
auto: boolean;
|
auto: boolean;
|
||||||
cache: string[];
|
cache: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceWithParams extends ResourceBase {
|
export interface ResourceWithParams extends ResourceBase {
|
||||||
params: Record<string, unknown>;
|
params: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceWithMakeParams extends ResourceBase {
|
export interface ResourceWithMakeParams extends ResourceBase {
|
||||||
makeParams: () => Record<string, unknown>;
|
makeParams: () => Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Resource = ResourceWithParams | ResourceWithMakeParams;
|
export type Resource = ResourceWithParams | ResourceWithMakeParams;
|
||||||
|
|
||||||
export interface DocumentResource {
|
export interface DocumentResource {
|
||||||
name: string;
|
name: string;
|
||||||
pg: Record<string, any>;
|
pg: Record<string, any>;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Icon = ReturnType<typeof icon>;
|
type Icon = ReturnType<typeof icon>;
|
||||||
type AsyncComponent = ReturnType<typeof defineAsyncComponent>;
|
type AsyncComponent = ReturnType<typeof defineAsyncComponent>;
|
||||||
|
|
||||||
export interface DashboardObject {
|
export interface DashboardObject {
|
||||||
pagetype: string;
|
pagetype: string;
|
||||||
whitelistedMethods: Record<string, string>;
|
whitelistedMethods: Record<string, string>;
|
||||||
list: List;
|
list: List;
|
||||||
detail: Detail;
|
detail: Detail;
|
||||||
routes: RouteDetail[];
|
routes: RouteDetail[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Detail {
|
export interface Detail {
|
||||||
titleField: string;
|
titleField: string;
|
||||||
statusBadge: StatusBadge;
|
statusBadge: StatusBadge;
|
||||||
breadcrumbs?: Breadcrumbs;
|
breadcrumbs?: Breadcrumbs;
|
||||||
route: string;
|
route: string;
|
||||||
tabs: Tab[];
|
tabs: Tab[];
|
||||||
actions: (r: { documentResource: DocumentResource }) => Action[];
|
actions: (r: { documentResource: DocumentResource }) => Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface List {
|
export interface List {
|
||||||
route: string;
|
route: string;
|
||||||
title: string;
|
title: string;
|
||||||
fields: string[]; // TODO: Incomplete
|
fields: string[]; // TODO: Incomplete
|
||||||
searchField: string;
|
searchField: string;
|
||||||
columns: ColumnField[];
|
columns: ColumnField[];
|
||||||
orderBy: string;
|
orderBy: string;
|
||||||
filterControls: FilterControls;
|
filterControls: FilterControls;
|
||||||
primaryAction?: PrimaryAction;
|
primaryAction?: PrimaryAction;
|
||||||
}
|
}
|
||||||
type R = {
|
type R = {
|
||||||
listResource: ListResource;
|
listResource: ListResource;
|
||||||
documentResource: DocumentResource;
|
documentResource: DocumentResource;
|
||||||
};
|
};
|
||||||
type FilterControls = (r: R) => FilterField[];
|
type FilterControls = (r: R) => FilterField[];
|
||||||
type PrimaryAction = (r: R) => {
|
type PrimaryAction = (r: R) => {
|
||||||
label: string;
|
label: string;
|
||||||
variant?: string;
|
variant?: string;
|
||||||
slots: {
|
slots: {
|
||||||
prefix: Icon;
|
prefix: Icon;
|
||||||
};
|
};
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
type StatusBadge = (r: { documentResource: DocumentResource }) => {
|
type StatusBadge = (r: { documentResource: DocumentResource }) => {
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
export type Breadcrumb = { label: string; route: string };
|
export type Breadcrumb = { label: string; route: string };
|
||||||
export type BreadcrumbArgs = {
|
export type BreadcrumbArgs = {
|
||||||
documentResource: DocumentResource;
|
documentResource: DocumentResource;
|
||||||
items: Breadcrumb[];
|
items: Breadcrumb[];
|
||||||
};
|
};
|
||||||
export type Breadcrumbs = (r: BreadcrumbArgs) => Breadcrumb[];
|
export type Breadcrumbs = (r: BreadcrumbArgs) => Breadcrumb[];
|
||||||
|
|
||||||
export interface FilterField {
|
export interface FilterField {
|
||||||
label: string;
|
label: string;
|
||||||
fieldname: string;
|
fieldname: string;
|
||||||
type: string;
|
type: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
options?:
|
options?:
|
||||||
| {
|
| {
|
||||||
pagetype: string;
|
pagetype: string;
|
||||||
filters?: {
|
filters?: {
|
||||||
pagetype_name?: string;
|
pagetype_name?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| string[];
|
| string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ColumnField {
|
export interface ColumnField {
|
||||||
label: string;
|
label: string;
|
||||||
fieldname?: string;
|
fieldname?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
width?: string | number;
|
width?: string | number;
|
||||||
type?: string;
|
type?: string;
|
||||||
format?: (value: any, row: Row) => string | undefined;
|
format?: (value: any, row: Row) => string | undefined;
|
||||||
link?: (value: unknown, row: Row) => string;
|
link?: (value: unknown, row: Row) => string;
|
||||||
prefix?: (row: Row) => Component | undefined;
|
prefix?: (row: Row) => Component | undefined;
|
||||||
suffix?: (row: Row) => Component | undefined;
|
suffix?: (row: Row) => Component | undefined;
|
||||||
theme?: (value: unknown) => string;
|
theme?: (value: unknown) => string;
|
||||||
align?: 'left' | 'right';
|
align?: 'left' | 'right';
|
||||||
}
|
Button?: (r: {
|
||||||
|
row: Row;
|
||||||
export type Row = Record<string, any>;
|
listResource: ListResource;
|
||||||
|
documentResource: DocumentResource;
|
||||||
export interface Tab {
|
}) => {
|
||||||
label: string;
|
label: string;
|
||||||
icon: Icon;
|
variant?: string;
|
||||||
route: string;
|
class?: string;
|
||||||
type: string;
|
slots?: {
|
||||||
condition?: (r: DocumentResource) => boolean;
|
prefix?: Icon;
|
||||||
childrenRoutes?: string[];
|
};
|
||||||
component?: AsyncComponent;
|
onClick?: () => void;
|
||||||
props?: (r: DocumentResource) => Record<string, unknown>;
|
} | null;
|
||||||
list?: TabList;
|
}
|
||||||
}
|
|
||||||
|
export type Row = Record<string, any>;
|
||||||
export interface TabList {
|
|
||||||
pagetype?: string;
|
export interface Tab {
|
||||||
orderBy?: string;
|
label: string;
|
||||||
filters?: (r: DocumentResource) => Record<string, unknown>;
|
icon: Icon;
|
||||||
route?: (row: Row) => Route;
|
route: string;
|
||||||
pageLength?: number;
|
type: string;
|
||||||
columns: ColumnField[];
|
condition?: (r: DocumentResource) => boolean;
|
||||||
fields?: Record<string, string[]>[] | string[];
|
childrenRoutes?: string[];
|
||||||
rowActions?: (r: {
|
component?: AsyncComponent;
|
||||||
row: Row;
|
props?: (r: DocumentResource) => Record<string, unknown>;
|
||||||
listResource: ListResource;
|
list?: TabList;
|
||||||
documentResource: DocumentResource;
|
}
|
||||||
}) => Action[];
|
|
||||||
primaryAction?: PrimaryAction;
|
export interface TabList {
|
||||||
filterControls?: FilterControls;
|
pagetype?: string;
|
||||||
banner?: (r: {
|
orderBy?: string;
|
||||||
documentResource: DocumentResource;
|
filters?: (r: DocumentResource) => Record<string, unknown>;
|
||||||
}) => BannerConfig | undefined;
|
route?: (row: Row) => Route;
|
||||||
searchField?: string;
|
pageLength?: number;
|
||||||
experimental?: boolean;
|
columns: ColumnField[];
|
||||||
documentation?: string;
|
fields?: Record<string, string[]>[] | string[];
|
||||||
resource?: (r: { documentResource: DocumentResource }) => Resource;
|
rowActions?: (r: {
|
||||||
}
|
row: Row;
|
||||||
|
listResource: ListResource;
|
||||||
interface Action {
|
documentResource: DocumentResource;
|
||||||
label: string;
|
}) => Action[];
|
||||||
slots?: {
|
primaryAction?: PrimaryAction;
|
||||||
prefix?: Icon;
|
filterControls?: FilterControls;
|
||||||
};
|
banner?: (r: {
|
||||||
theme?: string;
|
documentResource: DocumentResource;
|
||||||
variant?: string;
|
}) => BannerConfig | undefined;
|
||||||
onClick?: () => void;
|
searchField?: string;
|
||||||
condition?: () => boolean;
|
experimental?: boolean;
|
||||||
route?: Route;
|
documentation?: string;
|
||||||
options?: Option[];
|
resource?: (r: { documentResource: DocumentResource }) => Resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Route {
|
interface Action {
|
||||||
name: string;
|
label: string;
|
||||||
params: Record<string, unknown>;
|
slots?: {
|
||||||
}
|
prefix?: Icon;
|
||||||
|
};
|
||||||
export interface RouteDetail {
|
theme?: string;
|
||||||
name: string;
|
variant?: string;
|
||||||
path: string;
|
onClick?: () => void;
|
||||||
component: Component;
|
condition?: () => boolean;
|
||||||
}
|
route?: Route;
|
||||||
|
options?: Option[];
|
||||||
interface Option {
|
}
|
||||||
label: string;
|
|
||||||
icon: Icon | AsyncComponent;
|
export interface Route {
|
||||||
condition: () => boolean;
|
name: string;
|
||||||
onClick: () => void;
|
params: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BannerConfig {
|
export interface RouteDetail {
|
||||||
title: string;
|
name: string;
|
||||||
}
|
path: string;
|
||||||
dismissable: boolean;
|
component: Component;
|
||||||
id: string;
|
}
|
||||||
type?: string;
|
|
||||||
button?: {
|
interface Option {
|
||||||
label: string;
|
label: string;
|
||||||
variant: string;
|
icon: Icon | AsyncComponent;
|
||||||
onClick?: () => void;
|
condition: () => boolean;
|
||||||
};
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogConfig {
|
export interface BannerConfig {
|
||||||
title: string;
|
title: string;
|
||||||
message: string;
|
dismissable: boolean;
|
||||||
primaryAction?: { onClick: () => void };
|
id: string;
|
||||||
onSuccess?: (o: { hide: () => void }) => void;
|
type?: string;
|
||||||
}
|
button?: {
|
||||||
|
label: string;
|
||||||
export interface Process {
|
variant: string;
|
||||||
program: string;
|
onClick?: () => void;
|
||||||
name: string;
|
};
|
||||||
status: string;
|
}
|
||||||
uptime?: number;
|
|
||||||
uptime_string?: string;
|
export interface DialogConfig {
|
||||||
message?: string;
|
title: string;
|
||||||
group?: string;
|
message: string;
|
||||||
pid?: number;
|
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;
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ export default {
|
|||||||
async onClick() {
|
async onClick() {
|
||||||
toast.promise(
|
toast.promise(
|
||||||
jingrowRequest({
|
jingrowRequest({
|
||||||
url: '/api/method/jcloud.api.notifications.mark_all_notifications_as_read',
|
url: '/api/action/jcloud.api.notifications.mark_all_notifications_as_read',
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
success: () => {
|
success: () => {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { trialDays } from '../utils/site';
|
|||||||
import { clusterOptions, getUpsellBanner } from './common';
|
import { clusterOptions, getUpsellBanner } from './common';
|
||||||
import { getAppsTab } from './common/apps';
|
import { getAppsTab } from './common/apps';
|
||||||
import { isMobile } from '../utils/device';
|
import { isMobile } from '../utils/device';
|
||||||
|
import { getJobsTab } from './common/jobs';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
pagetype: 'Site',
|
pagetype: 'Site',
|
||||||
@ -881,6 +882,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
getJobsTab('Site'),
|
||||||
{
|
{
|
||||||
label: '操作',
|
label: '操作',
|
||||||
icon: icon('sliders'),
|
icon: icon('sliders'),
|
||||||
@ -1257,7 +1259,7 @@ export default {
|
|||||||
condition: () =>
|
condition: () =>
|
||||||
site.pg.status !== 'Archived' && site.pg?.setup_wizard_complete,
|
site.pg.status !== 'Archived' && site.pg?.setup_wizard_complete,
|
||||||
onClick() {
|
onClick() {
|
||||||
window.open(`https://${site.name}`, '_blank');
|
window.open(`https://${site.name}/app`, '_blank');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1340,5 +1342,10 @@ export default {
|
|||||||
path: 'updates/:id',
|
path: 'updates/:id',
|
||||||
component: () => import('../pages/SiteUpdate.vue'),
|
component: () => import('../pages/SiteUpdate.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Site Job',
|
||||||
|
path: 'jobs/:id',
|
||||||
|
component: () => import('../pages/JobPage.vue')
|
||||||
|
}
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -191,7 +191,7 @@ export default {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (row.stripe_invoice_url && row.payment_mode == 'Card') {
|
if (row.stripe_invoice_url && row.payment_mode == 'Card') {
|
||||||
window.open(
|
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 {
|
} else {
|
||||||
this.showBuyPrepaidCreditsDialog = true;
|
this.showBuyPrepaidCreditsDialog = true;
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export default {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const response = await jingrowRequest({
|
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',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
this.invoices = response;
|
this.invoices = response;
|
||||||
|
|||||||
@ -239,6 +239,7 @@ export const statusMap = {
|
|||||||
|
|
||||||
export const deployTypeMap = {
|
export const deployTypeMap = {
|
||||||
'Migrate': '迁移',
|
'Migrate': '迁移',
|
||||||
|
'Pull': '拉取',
|
||||||
'Update': '更新',
|
'Update': '更新',
|
||||||
'Install': '安装',
|
'Install': '安装',
|
||||||
'Uninstall': '卸载',
|
'Uninstall': '卸载',
|
||||||
|
|||||||
3887
dashboard/yarn.lock
3887
dashboard/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -350,7 +350,7 @@ server {
|
|||||||
http2_push_preload on;
|
http2_push_preload on;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/api/method/jcloud.api.developer.saas.* {
|
location ~ ^/api/action/jcloud.api.developer.saas.* {
|
||||||
if ($request_method = 'OPTIONS') {
|
if ($request_method = 'OPTIONS') {
|
||||||
add_header Access-Control-Allow-Origin "*" always;
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||||
|
|||||||
@ -478,7 +478,7 @@ class Agent:
|
|||||||
"site_backup": {
|
"site_backup": {
|
||||||
"name": site_backup.name,
|
"name": site_backup.name,
|
||||||
"snapshot_request_key": site_backup.snapshot_request_key,
|
"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(
|
return self.create_agent_job(
|
||||||
|
|||||||
@ -381,7 +381,7 @@ def send_login_link(email):
|
|||||||
minutes = 10
|
minutes = 10
|
||||||
jingrow.cache().set_value(f"one_time_login_key:{key}", email, expires_in_sec=minutes * 60)
|
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:
|
if jingrow.conf.developer_mode:
|
||||||
print()
|
print()
|
||||||
|
|||||||
@ -30,9 +30,9 @@ class AliyunSMSClient:
|
|||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""初始化配置信息"""
|
"""初始化配置信息"""
|
||||||
try:
|
try:
|
||||||
# 检查 Jcloud Settings 是否存在
|
# 直接尝试获取 Jcloud Settings
|
||||||
if jingrow.exists("Jcloud Settings"):
|
settings = jingrow.get_single("Jcloud Settings")
|
||||||
settings = jingrow.get_single("Jcloud Settings")
|
if settings:
|
||||||
self.access_key_id = settings.get("aliyun_access_key_id")
|
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
|
self.access_secret = settings.get_password("aliyun_access_secret") if settings.get("aliyun_access_secret") else None
|
||||||
else:
|
else:
|
||||||
@ -40,7 +40,6 @@ class AliyunSMSClient:
|
|||||||
self.access_key_id = None
|
self.access_key_id = None
|
||||||
self.access_secret = None
|
self.access_secret = None
|
||||||
jingrow.log_error("阿里云SMS客户端: Jcloud Settings 尚未配置,请在设置中完成配置")
|
jingrow.log_error("阿里云SMS客户端: Jcloud Settings 尚未配置,请在设置中完成配置")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"阿里云SMS客户端初始化: {str(e)}")
|
jingrow.log_error(f"阿里云SMS客户端初始化: {str(e)}")
|
||||||
self.access_key_id = None
|
self.access_key_id = None
|
||||||
@ -143,8 +142,11 @@ def get_sms_client():
|
|||||||
return sms_client
|
return sms_client
|
||||||
|
|
||||||
def send_custom_sms(phone_numbers, message_content, sign_name, template_code):
|
def send_custom_sms(phone_numbers, message_content, sign_name, template_code):
|
||||||
|
|
||||||
client = get_sms_client()
|
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):
|
def generate_verification_code(length=4):
|
||||||
"""生成指定长度的随机数字验证码"""
|
"""生成指定长度的随机数字验证码"""
|
||||||
@ -169,13 +171,10 @@ def verify_code(mobile_no, verification_code, template_code):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def send_renew_sms(phone_numbers, days_remaining, site_end_date):
|
def send_renew_sms(phone_numbers, days_remaining, site_end_date):
|
||||||
"""发送网站续费通知短信"""
|
|
||||||
template_code = "SMS_481605243" # 网站续费通知短信模板编码
|
template_code = "SMS_489640674" # 网站续费通知短信模板编码
|
||||||
sign_name = "向日葵网络" # 短信签名名称
|
sign_name = "向日葵网络" # 短信签名名称
|
||||||
|
|
||||||
message_content = {
|
message_content = {
|
||||||
"day": str(days_remaining),
|
|
||||||
"site_end_date": str(site_end_date)
|
"site_end_date": str(site_end_date)
|
||||||
}
|
}
|
||||||
|
|
||||||
return send_custom_sms(phone_numbers, message_content, sign_name, template_code)
|
return send_custom_sms(phone_numbers, message_content, sign_name, template_code)
|
||||||
@ -268,7 +268,7 @@ class RequestGroupByChart(StackedGroupByChart):
|
|||||||
def setup_search_filters(self):
|
def setup_search_filters(self):
|
||||||
super().setup_search_filters()
|
super().setup_search_filters()
|
||||||
self.search = self.search.filter("match_phrase", json__transaction_type="request").exclude(
|
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:
|
if self.resource_type == ResourceType.SITE:
|
||||||
self.search = self.search.filter("match_phrase", json__site=self.name)
|
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():
|
def get_more_request_detail_fn_names():
|
||||||
return {
|
return {
|
||||||
"/api/method/run_pg_method": get_run_pg_method_methodnames.__name__,
|
"/api/action/run_pg_method": get_run_pg_method_methodnames.__name__,
|
||||||
"/api/method/jingrow.desk.query_report.run": get_query_report_run_reports.__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):
|
def setup_search_filters(self):
|
||||||
super().setup_search_filters()
|
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):
|
def get_run_pg_method_methodnames(site, agg_type, timezone, timespan, timegrain):
|
||||||
@ -557,7 +557,7 @@ class QueryReportRunReports(RequestGroupByChart):
|
|||||||
def setup_search_filters(self):
|
def setup_search_filters(self):
|
||||||
super().setup_search_filters()
|
super().setup_search_filters()
|
||||||
self.search = self.search.filter(
|
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}},
|
{"match_phrase": {"json.site": name}},
|
||||||
{"range": {"@timestamp": {"gt": f"{date}||-1d/d", "lte": f"{date}||/d"}}},
|
{"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,
|
"sort": sort_value,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import json
|
|||||||
import segno
|
import segno
|
||||||
import io
|
import io
|
||||||
import base64
|
import base64
|
||||||
import traceback
|
|
||||||
import jingrow
|
import jingrow
|
||||||
from jingrow import _ # Import this for translation functionality
|
from jingrow import _ # Import this for translation functionality
|
||||||
from jingrow.core.utils import find
|
from jingrow.core.utils import find
|
||||||
@ -708,7 +707,7 @@ def generate_stk_push(**kwargs):
|
|||||||
mpesa_setup = get_mpesa_setup_for_team(partner[0])
|
mpesa_setup = get_mpesa_setup_for_team(partner[0])
|
||||||
try:
|
try:
|
||||||
callback_url = (
|
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"
|
env = "production" if not mpesa_setup.sandbox else "sandbox"
|
||||||
# for sandbox, business shortcode is same as till number
|
# 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(
|
create_mpesa_request_log(
|
||||||
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
|
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)
|
elif result_code == 1037: # User unreachable (Phone off or timeout)
|
||||||
status = "Failed"
|
status = "Failed"
|
||||||
create_mpesa_request_log(
|
create_mpesa_request_log(
|
||||||
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
|
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
|
elif result_code == 1032: # User cancelled the request
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
create_mpesa_request_log(
|
create_mpesa_request_log(
|
||||||
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
|
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
|
||||||
)
|
)
|
||||||
jingrow.log_error("Mpesa: Request cancelled by user")
|
|
||||||
|
|
||||||
else: # Other failure codes
|
else: # Other failure codes
|
||||||
status = "Failed"
|
status = "Failed"
|
||||||
create_mpesa_request_log(
|
create_mpesa_request_log(
|
||||||
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
|
transaction_response, "Host", "Mpesa Exjcloud", integration_request, None, status
|
||||||
)
|
)
|
||||||
jingrow.log_error(f"Mpesa: Transaction failed with ResultCode {result_code}")
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
@ -1003,7 +998,7 @@ def handle_alipay_notification():
|
|||||||
|
|
||||||
return "success"
|
return "success"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"处理失败: {str(e)}\n{traceback.format_exc()}", "支付宝错误")
|
jingrow.log_error("支付宝错误", f"处理失败: {str(e)}")
|
||||||
return "fail"
|
return "fail"
|
||||||
|
|
||||||
@jingrow.whitelist(allow_guest=True)
|
@jingrow.whitelist(allow_guest=True)
|
||||||
@ -1075,17 +1070,11 @@ def handle_wechatpay_notification():
|
|||||||
return "SUCCESS"
|
return "SUCCESS"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(
|
jingrow.log_error("微信支付解密错误", f"处理微信支付通知数据失败: {str(e)}\n请求头: {headers}\n请求体: {body}")
|
||||||
f"处理微信支付通知数据失败: {str(e)}\n调用栈: {traceback.format_exc()}\n请求头: {headers}\n请求体: {body}",
|
|
||||||
"微信支付解密错误"
|
|
||||||
)
|
|
||||||
return "SUCCESS" # 返回成功避免微信重复发送通知
|
return "SUCCESS" # 返回成功避免微信重复发送通知
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(
|
jingrow.log_error("微信支付错误", f"处理微信支付通知失败: {str(e)}")
|
||||||
f"处理微信支付通知失败: {str(e)}\n调用栈: {traceback.format_exc()}",
|
|
||||||
"微信支付错误"
|
|
||||||
)
|
|
||||||
return "SUCCESS" # 返回成功避免微信重复发送通知
|
return "SUCCESS" # 返回成功避免微信重复发送通知
|
||||||
|
|
||||||
|
|
||||||
@ -1102,10 +1091,7 @@ def handle_order_payment_complete(order_id):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(
|
jingrow.log_error("订单处理错误", f"处理订单 {order_id} 支付完成事件失败: {str(e)}")
|
||||||
f"处理订单 {order_id} 支付完成事件失败: {str(e)}\n{traceback.format_exc()}",
|
|
||||||
f"订单处理错误"
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def process_balance_recharge(order):
|
def process_balance_recharge(order):
|
||||||
@ -1129,10 +1115,7 @@ def process_balance_recharge(order):
|
|||||||
jingrow.db.commit()
|
jingrow.db.commit()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(
|
jingrow.log_error("余额充值错误", f"余额充值失败: 团队 {order.team}, 金额 {order.total_amount}, 错误: {str(e)}")
|
||||||
f"余额充值失败: 团队 {order.team}, 金额 {order.total_amount}, 错误: {str(e)}\n{traceback.format_exc()}",
|
|
||||||
"余额充值错误"
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def process_site_renew(order_id):
|
def process_site_renew(order_id):
|
||||||
@ -1170,16 +1153,17 @@ def process_site_renew(order_id):
|
|||||||
# 更新站点到期日期
|
# 更新站点到期日期
|
||||||
site.site_end_date = new_end_date
|
site.site_end_date = new_end_date
|
||||||
site.save(ignore_permissions=True)
|
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.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 {
|
return {
|
||||||
"name": site.name,
|
"name": site.name,
|
||||||
"url": site_name,
|
"url": site_name,
|
||||||
@ -1254,7 +1238,7 @@ def create_alipay_order_for_recharge(amount):
|
|||||||
"payment_record": payment_record.name
|
"payment_record": payment_record.name
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建支付宝订单失败: {str(e)}", "Order")
|
jingrow.log_error("Order", f"创建支付宝订单失败: {str(e)}")
|
||||||
jingrow.throw(f"创建支付宝订单失败: {str(e)}")
|
jingrow.throw(f"创建支付宝订单失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@ -1303,7 +1287,7 @@ def create_wechatpay_order_for_recharge(amount):
|
|||||||
|
|
||||||
# 检查URL是否为空
|
# 检查URL是否为空
|
||||||
if not qr_code_url:
|
if not qr_code_url:
|
||||||
jingrow.log_error("微信支付URL生成为空", "微信支付错误")
|
jingrow.log_error("微信支付错误", "微信支付URL生成为空")
|
||||||
|
|
||||||
# 使用提供的函数生成二维码图片
|
# 使用提供的函数生成二维码图片
|
||||||
qr_code_image = generate_qr_code(qr_code_url)
|
qr_code_image = generate_qr_code(qr_code_url)
|
||||||
@ -1316,7 +1300,7 @@ def create_wechatpay_order_for_recharge(amount):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建微信支付订单失败: {str(e)}\n{traceback.format_exc()}", "微信支付错误")
|
jingrow.log_error("微信支付错误", f"创建微信支付订单失败: {str(e)}")
|
||||||
jingrow.throw(f"创建微信支付订单失败")
|
jingrow.throw(f"创建微信支付订单失败")
|
||||||
|
|
||||||
|
|
||||||
@ -1361,7 +1345,7 @@ def create_order(**kwargs):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建站点订单失败: {str(e)}\n{traceback.format_exc()}", "订单错误")
|
jingrow.log_error("订单错误", f"创建站点订单失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"创建订单失败: {str(e)}"
|
"message": f"创建订单失败: {str(e)}"
|
||||||
@ -1418,7 +1402,7 @@ def create_renewal_order(site, renewal_months=1):
|
|||||||
"order": order.as_dict()
|
"order": order.as_dict()
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建续费订单失败: {str(e)}", "续费订单错误")
|
jingrow.log_error("续费订单错误", f"创建续费订单失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"创建续费订单失败: {str(e)}"
|
"message": f"创建续费订单失败: {str(e)}"
|
||||||
@ -1484,7 +1468,7 @@ def process_balance_payment_for_order(order_id):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"余额支付失败: {str(e)}\n{traceback.format_exc()}", "支付错误")
|
jingrow.log_error("支付错误", f"余额支付失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"status": "Error",
|
"status": "Error",
|
||||||
"message": f"余额支付失败: {str(e)}"
|
"message": f"余额支付失败: {str(e)}"
|
||||||
@ -1574,10 +1558,7 @@ def process_balance_payment_for_renew_order(order_id):
|
|||||||
raise inner_error
|
raise inner_error
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(
|
jingrow.log_error("续费支付错误", f"余额支付续费失败: {str(e)}")
|
||||||
message=f"余额支付续费失败: {str(e)}\n{traceback.format_exc()}",
|
|
||||||
title="续费支付错误"
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"status": "Error",
|
"status": "Error",
|
||||||
@ -1628,7 +1609,7 @@ def process_alipay_order(order_id):
|
|||||||
"success": True
|
"success": True
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建支付宝订单失败: {str(e)}", "Order")
|
jingrow.log_error("Order", f"创建支付宝订单失败: {str(e)}")
|
||||||
jingrow.throw(f"创建支付宝订单失败: {str(e)}")
|
jingrow.throw(f"创建支付宝订单失败: {str(e)}")
|
||||||
|
|
||||||
@jingrow.whitelist()
|
@jingrow.whitelist()
|
||||||
@ -1668,7 +1649,7 @@ def process_wechatpay_order(order_id):
|
|||||||
|
|
||||||
# 检查URL是否为空
|
# 检查URL是否为空
|
||||||
if not qr_code_url:
|
if not qr_code_url:
|
||||||
jingrow.log_error("微信支付URL生成为空", "微信支付错误")
|
jingrow.log_error("微信支付错误", "微信支付URL生成为空")
|
||||||
jingrow.throw("生成支付URL失败")
|
jingrow.throw("生成支付URL失败")
|
||||||
|
|
||||||
# 生成二维码图片
|
# 生成二维码图片
|
||||||
@ -1686,11 +1667,11 @@ def process_wechatpay_order(order_id):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建微信支付订单失败: {str(e)}\n{traceback.format_exc()}", "微信支付错误")
|
jingrow.log_error("微信支付错误", f"创建微信支付订单失败: {str(e)}")
|
||||||
jingrow.throw(f"创建微信支付订单失败")
|
jingrow.throw(f"创建微信支付订单失败")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"创建微信支付订单失败: {str(e)}\n{traceback.format_exc()}", "微信支付错误")
|
jingrow.log_error("微信支付错误", f"创建微信支付订单失败: {str(e)}")
|
||||||
jingrow.throw(f"创建微信支付订单失败")
|
jingrow.throw(f"创建微信支付订单失败")
|
||||||
|
|
||||||
@jingrow.whitelist()
|
@jingrow.whitelist()
|
||||||
@ -1709,7 +1690,7 @@ def check_site_order_payment_status(order_id):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"检查订单状态失败: {str(e)}\n{traceback.format_exc()}", "订单错误")
|
jingrow.log_error("订单错误", f"检查订单状态失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"检查订单状态失败: {str(e)}"
|
"message": f"检查订单状态失败: {str(e)}"
|
||||||
@ -1774,7 +1755,7 @@ def get_orders(page=1, page_size=20, search=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"获取订单列表失败: {str(e)}\n{traceback.format_exc()}", "订单列表错误")
|
jingrow.log_error("订单列表错误", f"获取订单列表失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"total": 0,
|
"total": 0,
|
||||||
@ -1811,7 +1792,7 @@ def get_order_details(name):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"获取订单详情失败: {str(e)}\n{traceback.format_exc()}", "订单详情错误")
|
jingrow.log_error("订单详情错误", f"获取订单详情失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"error": str(e)
|
"error": str(e)
|
||||||
}
|
}
|
||||||
@ -1882,7 +1863,7 @@ def get_balance_transactions(page=1, page_size=20, search=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"获取余额记录失败: {str(e)}\n{traceback.format_exc()}", "余额记录错误")
|
jingrow.log_error("余额记录错误", f"获取余额记录失败: {str(e)}")
|
||||||
return {
|
return {
|
||||||
"transactions": [],
|
"transactions": [],
|
||||||
"total": 0,
|
"total": 0,
|
||||||
|
|||||||
@ -160,7 +160,7 @@ class DeveloperApiHandler:
|
|||||||
jingrow.db.commit()
|
jingrow.db.commit()
|
||||||
|
|
||||||
return get_url(
|
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}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,7 @@ class SaasApiHandler:
|
|||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
domain = jingrow.db.get_value("Saas App", self.app_subscription_pg.app, "custom_domain")
|
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):
|
def get_trial_expiry(self):
|
||||||
return jingrow.db.get_value("Site", self.app_subscription_pg.site, "trial_end_date")
|
return jingrow.db.get_value("Site", self.app_subscription_pg.site, "trial_end_date")
|
||||||
|
|||||||
@ -285,7 +285,7 @@ def event_log():
|
|||||||
try:
|
try:
|
||||||
host_name = jingrow.db.get_value("Site", site, "host_name") or site
|
host_name = jingrow.db.get_value("Site", site, "host_name") or site
|
||||||
requests.post(
|
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,
|
data=data,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -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"):
|
if team.jerp_partner and jingrow.get_value("Marketplace App Plan", plan, "partner_discount"):
|
||||||
client = get_jingrow_io_connection()
|
client = get_jingrow_io_connection()
|
||||||
response = client.session.post(
|
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},
|
data={"email": team.partner_email},
|
||||||
headers=client.headers,
|
headers=client.headers,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1099,7 +1099,7 @@ def get(name):
|
|||||||
site_name = jingrow.db.get_value("Site Domain", name, "site")
|
site_name = jingrow.db.get_value("Site Domain", name, "site")
|
||||||
if site_name:
|
if site_name:
|
||||||
jingrow.local.response["type"] = "redirect"
|
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
|
return None
|
||||||
raise
|
raise
|
||||||
rg_info = jingrow.db.get_value("Release Group", site.group, ["team", "version", "public"], as_dict=True)
|
rg_info = jingrow.db.get_value("Release Group", site.group, ["team", "version", "public"], as_dict=True)
|
||||||
|
|||||||
@ -12,48 +12,48 @@ JCLOUD_AUTH_MAX_ENTRIES = 1000000
|
|||||||
|
|
||||||
|
|
||||||
ALLOWED_PATHS = [
|
ALLOWED_PATHS = [
|
||||||
"/api/method/create-site-migration",
|
"/api/action/create-site-migration",
|
||||||
"/api/method/create-version-upgrade",
|
"/api/action/create-version-upgrade",
|
||||||
"/api/method/migrate-to-private-bench",
|
"/api/action/migrate-to-private-bench",
|
||||||
"/api/method/find-my-sites",
|
"/api/action/find-my-sites",
|
||||||
"/api/method/jingrow.core.pagetype.communication.email.mark_email_as_seen",
|
"/api/action/jingrow.core.pagetype.communication.email.mark_email_as_seen",
|
||||||
"/api/method/jingrow.realtime.get_user_info",
|
"/api/action/jingrow.realtime.get_user_info",
|
||||||
"/api/method/jingrow.realtime.can_subscribe_pg",
|
"/api/action/jingrow.realtime.can_subscribe_pg",
|
||||||
"/api/method/jingrow.realtime.can_subscribe_pagetype",
|
"/api/action/jingrow.realtime.can_subscribe_pagetype",
|
||||||
"/api/method/jingrow.realtime.has_permission",
|
"/api/action/jingrow.realtime.has_permission",
|
||||||
"/api/method/jingrow.www.login.login_via_jingrow",
|
"/api/action/jingrow.www.login.login_via_jingrow",
|
||||||
"/api/method/jingrow.integrations.oauth2.authorize",
|
"/api/action/jingrow.integrations.oauth2.authorize",
|
||||||
"/api/method/jingrow.integrations.oauth2.approve",
|
"/api/action/jingrow.integrations.oauth2.approve",
|
||||||
"/api/method/jingrow.integrations.oauth2.get_token",
|
"/api/action/jingrow.integrations.oauth2.get_token",
|
||||||
"/api/method/jingrow.integrations.oauth2.openid_profile",
|
"/api/action/jingrow.integrations.oauth2.openid_profile",
|
||||||
"/api/method/jingrow.integrations.oauth2_logins.login_via_jingrow",
|
"/api/action/jingrow.integrations.oauth2_logins.login_via_jingrow",
|
||||||
"/api/method/jingrow.website.pagetype.web_page_view.web_page_view.make_view_log",
|
"/api/action/jingrow.website.pagetype.web_page_view.web_page_view.make_view_log",
|
||||||
"/api/method/get-user-sites-list-for-new-ticket",
|
"/api/action/get-user-sites-list-for-new-ticket",
|
||||||
"/api/method/ping",
|
"/api/action/ping",
|
||||||
"/api/method/login",
|
"/api/action/login",
|
||||||
"/api/method/logout",
|
"/api/action/logout",
|
||||||
"/api/method/jcloud.jcloud.pagetype.razorpay_webhook_log.razorpay_webhook_log.razorpay_webhook_handler",
|
"/api/action/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/action/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/action/jcloud.jcloud.pagetype.stripe_webhook_log.stripe_webhook_log.stripe_webhook_handler",
|
||||||
"/api/method/upload_file",
|
"/api/action/upload_file",
|
||||||
"/api/method/jingrow.search.web_search",
|
"/api/action/jingrow.search.web_search",
|
||||||
"/api/method/jingrow.email.queue.unsubscribe",
|
"/api/action/jingrow.email.queue.unsubscribe",
|
||||||
"/api/method/jcloud.utils.telemetry.capture_read_event",
|
"/api/action/jcloud.utils.telemetry.capture_read_event",
|
||||||
"/api/method/validate_plan_change",
|
"/api/action/validate_plan_change",
|
||||||
"/api/method/marketplace-apps",
|
"/api/action/marketplace-apps",
|
||||||
"/api/method/jcloud.www.dashboard.get_context_for_dev",
|
"/api/action/jcloud.www.dashboard.get_context_for_dev",
|
||||||
"/api/method/jingrow.website.pagetype.web_form.web_form.accept",
|
"/api/action/jingrow.website.pagetype.web_form.web_form.accept",
|
||||||
"/api/method/jingrow.core.pagetype.user.user.test_password_strength",
|
"/api/action/jingrow.core.pagetype.user.user.test_password_strength",
|
||||||
"/api/method/jingrow.core.pagetype.user.user.update_password",
|
"/api/action/jingrow.core.pagetype.user.user.update_password",
|
||||||
"/api/method/get_central_migration_data",
|
"/api/action/get_central_migration_data",
|
||||||
]
|
]
|
||||||
|
|
||||||
ALLOWED_WILDCARD_PATHS = [
|
ALLOWED_WILDCARD_PATHS = [
|
||||||
"/api/method/jcloud.api.",
|
"/api/action/jcloud.api.",
|
||||||
"/api/method/jcloud.saas.",
|
"/api/action/jcloud.saas.",
|
||||||
"/api/method/wiki.",
|
"/api/action/wiki.",
|
||||||
"/api/method/jingrow.integrations.oauth2_logins.",
|
"/api/action/jingrow.integrations.oauth2_logins.",
|
||||||
"/api/method/jcloud.www.marketplace.index.",
|
"/api/action/jcloud.www.marketplace.index.",
|
||||||
]
|
]
|
||||||
|
|
||||||
DENIED_PATHS = [
|
DENIED_PATHS = [
|
||||||
@ -70,7 +70,7 @@ DENIED_WILDCARD_PATHS = [
|
|||||||
|
|
||||||
def hook(): # noqa: C901
|
def hook(): # noqa: C901
|
||||||
if jingrow.form_dict.cmd:
|
if jingrow.form_dict.cmd:
|
||||||
path = f"/api/method/{jingrow.form_dict.cmd}"
|
path = f"/api/action/{jingrow.form_dict.cmd}"
|
||||||
else:
|
else:
|
||||||
path = jingrow.request.path
|
path = jingrow.request.path
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ def start_ngrok_and_set_webhook(context):
|
|||||||
print(f"Inspect logs at {tunnel.api_url}")
|
print(f"Inspect logs at {tunnel.api_url}")
|
||||||
|
|
||||||
stripe = get_stripe()
|
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(
|
stripe.WebhookEndpoint.modify(
|
||||||
jingrow.db.get_single_value("Jcloud Settings", "stripe_webhook_endpoint_id"), url=url
|
jingrow.db.get_single_value("Jcloud Settings", "stripe_webhook_endpoint_id"), url=url
|
||||||
)
|
)
|
||||||
|
|||||||
@ -72,7 +72,7 @@ website_redirects = [
|
|||||||
{"source": "/dashboard/f-login", "target": get_jingrow_io_auth_url() or "/"},
|
{"source": "/dashboard/f-login", "target": get_jingrow_io_auth_url() or "/"},
|
||||||
{
|
{
|
||||||
"source": "/suspended-site",
|
"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": "/f-login", "target": "/dashboard/f-login"},
|
||||||
{"source": "/signup", "target": "/jerp/signup"},
|
{"source": "/signup", "target": "/jerp/signup"},
|
||||||
@ -178,7 +178,6 @@ scheduler_events = {
|
|||||||
"jcloud.experimental.pagetype.referral_bonus.referral_bonus.credit_referral_bonuses",
|
"jcloud.experimental.pagetype.referral_bonus.referral_bonus.credit_referral_bonuses",
|
||||||
"jcloud.jcloud.pagetype.log_counter.log_counter.record_counts",
|
"jcloud.jcloud.pagetype.log_counter.log_counter.record_counts",
|
||||||
"jcloud.jcloud.pagetype.incident.incident.notify_ignored_servers",
|
"jcloud.jcloud.pagetype.incident.incident.notify_ignored_servers",
|
||||||
"jcloud.jcloud.pagetype.site.site.send_renew_notification",
|
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"jcloud.jcloud.audit.check_bench_fields",
|
"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.jcloud.pagetype.tls_certificate.tls_certificate.retrigger_failed_wildcard_tls_callbacks",
|
||||||
"jcloud.infrastructure.pagetype.ssh_access_audit.ssh_access_audit.run",
|
"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",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -198,7 +198,7 @@ class AccountRequest(Document):
|
|||||||
"invited_by": self.invited_by,
|
"invited_by": self.invited_by,
|
||||||
"link": url,
|
"link": url,
|
||||||
"read_pixel_path": get_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,
|
"otp": self.otp,
|
||||||
}
|
}
|
||||||
@ -225,7 +225,7 @@ class AccountRequest(Document):
|
|||||||
|
|
||||||
def get_verification_url(self):
|
def get_verification_url(self):
|
||||||
if self.saas:
|
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:
|
if self.product_trial:
|
||||||
return get_url(
|
return get_url(
|
||||||
f"/dashboard/saas/{self.product_trial}/oauth?key={self.request_key}&email={self.email}"
|
f"/dashboard/saas/{self.product_trial}/oauth?key={self.request_key}&email={self.email}"
|
||||||
|
|||||||
@ -744,7 +744,7 @@ class Invoice(Document):
|
|||||||
def get_pdf(self):
|
def get_pdf(self):
|
||||||
print_format = self.meta.default_print_format
|
print_format = self.meta.default_print_format
|
||||||
return jingrow.utils.get_url(
|
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()
|
@jingrow.whitelist()
|
||||||
@ -766,7 +766,7 @@ class Invoice(Document):
|
|||||||
return None
|
return None
|
||||||
client = self.get_jingrowio_connection()
|
client = self.get_jingrowio_connection()
|
||||||
response = client.session.post(
|
response = client.session.post(
|
||||||
f"{client.url}/api/method/create-fc-invoice",
|
f"{client.url}/api/action/create-fc-invoice",
|
||||||
headers=client.headers,
|
headers=client.headers,
|
||||||
data={
|
data={
|
||||||
"team": team.as_json(),
|
"team": team.as_json(),
|
||||||
@ -819,7 +819,7 @@ class Invoice(Document):
|
|||||||
"no_letterhead": 0,
|
"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:
|
with client.session.get(url, headers=client.headers, stream=True) as r:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@ -917,7 +917,7 @@ class Invoice(Document):
|
|||||||
"no_letterhead": 0,
|
"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:
|
with client.session.get(url, headers=client.headers, stream=True) as r:
|
||||||
r.raise_for_status()
|
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
|
# Post to the external site's sales invoice creation API
|
||||||
response = client.session.post(
|
response = client.session.post(
|
||||||
f"{client.url}/api/method/jingrow.client.insert",
|
f"{client.url}/api/action/jingrow.client.insert",
|
||||||
headers=client.headers,
|
headers=client.headers,
|
||||||
json={"pg": data},
|
json={"pg": data},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -195,7 +195,7 @@ class JcloudSettings(Document):
|
|||||||
def create_stripe_webhook(self):
|
def create_stripe_webhook(self):
|
||||||
stripe = get_stripe()
|
stripe = get_stripe()
|
||||||
url = jingrow.utils.get_url(
|
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(
|
webhook = stripe.WebhookEndpoint.create(
|
||||||
url=url,
|
url=url,
|
||||||
@ -226,7 +226,7 @@ class JcloudSettings(Document):
|
|||||||
return {
|
return {
|
||||||
"name": app_name,
|
"name": app_name,
|
||||||
"url": "https://jingrow.cloud",
|
"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"),
|
"redirect_url": get_url("github/redirect"),
|
||||||
"description": "Managed Jingrow Hosting",
|
"description": "Managed Jingrow Hosting",
|
||||||
"public": True,
|
"public": True,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class PrometheusAlertRule(Document):
|
|||||||
{
|
{
|
||||||
"name": "web.hook",
|
"name": "web.hook",
|
||||||
"webhook_configs": [
|
"webhook_configs": [
|
||||||
{"url": jingrow.utils.get_url("api/method/jcloud.api.monitoring.alert")}
|
{"url": jingrow.utils.get_url("api/action/jcloud.api.monitoring.alert")}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -453,7 +453,7 @@ class ReleaseGroup(Document, TagHelpers):
|
|||||||
with suppress(AttributeError, RuntimeError):
|
with suppress(AttributeError, RuntimeError):
|
||||||
if (
|
if (
|
||||||
not jingrow.flags.in_test
|
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
|
return # Separate validation exists in set_app_source
|
||||||
for app in self.apps:
|
for app in self.apps:
|
||||||
@ -819,7 +819,7 @@ class ReleaseGroup(Document, TagHelpers):
|
|||||||
}
|
}
|
||||||
).insert()
|
).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:
|
if jingrow.conf.developer_mode:
|
||||||
print(f"Bench transfer link for {team_mail_id}\n{link}\n")
|
print(f"Bench transfer link for {team_mail_id}\n{link}\n")
|
||||||
|
|||||||
@ -299,7 +299,10 @@ class Site(Document, TagHelpers):
|
|||||||
status = jingrow.get_value(inst.pagetype, inst.name, "status", for_update=True)
|
status = jingrow.get_value(inst.pagetype, inst.name, "status", for_update=True)
|
||||||
if status not in allowed_status:
|
if status not in allowed_status:
|
||||||
jingrow.throw(
|
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)
|
return func(inst, *args, **kwargs)
|
||||||
|
|
||||||
@ -1394,7 +1397,7 @@ class Site(Document, TagHelpers):
|
|||||||
}
|
}
|
||||||
).insert()
|
).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:
|
if jingrow.conf.developer_mode:
|
||||||
print(f"\nSite transfer link for {team_mail_id}\n{link}\n")
|
print(f"\nSite transfer link for {team_mail_id}\n{link}\n")
|
||||||
@ -1511,7 +1514,7 @@ class Site(Document, TagHelpers):
|
|||||||
if user == "Administrator":
|
if user == "Administrator":
|
||||||
password = get_decrypted_password("Site", self.name, "admin_password")
|
password = get_decrypted_password("Site", self.name, "admin_password")
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"https://{self.name}/api/method/login",
|
f"https://{self.name}/api/action/login",
|
||||||
data={"usr": user, "pwd": password},
|
data={"usr": user, "pwd": password},
|
||||||
)
|
)
|
||||||
sid = response.cookies.get("sid")
|
sid = response.cookies.get("sid")
|
||||||
@ -1678,7 +1681,7 @@ class Site(Document, TagHelpers):
|
|||||||
"pagetype": "Webhook",
|
"pagetype": "Webhook",
|
||||||
"webhook_pagetype": "User",
|
"webhook_pagetype": "User",
|
||||||
"enabled": 1,
|
"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_method": "POST",
|
||||||
"request_structure": "JSON",
|
"request_structure": "JSON",
|
||||||
"webhook_json": """{ "user_info": { "email": "{{pg.email}}", "enabled": "{{pg.enabled}}" } }""",
|
"webhook_json": """{ "user_info": { "email": "{{pg.email}}", "enabled": "{{pg.enabled}}" } }""",
|
||||||
@ -1796,7 +1799,7 @@ class Site(Document, TagHelpers):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def ping(self):
|
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]):
|
def _set_configuration(self, config: list[dict]):
|
||||||
"""Similar to _update_configuration but will replace full configuration at once
|
"""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
|
return
|
||||||
create_webhook_event("Site Status Update", record, record.team)
|
create_webhook_event("Site Status Update", record, record.team)
|
||||||
|
|
||||||
|
@jingrow.whitelist()
|
||||||
def send_renew_notification():
|
def send_renew_notification():
|
||||||
"""
|
"""
|
||||||
发送站点续费通知给用户:
|
发送站点续费通知给用户:
|
||||||
@ -3882,3 +3885,25 @@ def send_renew_notification():
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
jingrow.log_error(f"站点 {site.name} 发送续费通知失败: {str(e)}", "Renewal Notification Error")
|
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)}")
|
||||||
|
|||||||
@ -921,7 +921,7 @@ class Team(Document):
|
|||||||
# fetch partner level from framework.jingrow.com
|
# fetch partner level from framework.jingrow.com
|
||||||
client = get_jingrow_io_connection()
|
client = get_jingrow_io_connection()
|
||||||
response = client.session.get(
|
response = client.session.get(
|
||||||
f"{client.url}/api/method/get_partner_level",
|
f"{client.url}/api/action/get_partner_level",
|
||||||
headers=client.headers,
|
headers=client.headers,
|
||||||
params={"email": self.partner_email},
|
params={"email": self.partner_email},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -133,7 +133,7 @@ class TeamDeletionRequest(PersonalDataDeletionRequest):
|
|||||||
|
|
||||||
def generate_url_for_confirmation(self):
|
def generate_url_for_confirmation(self):
|
||||||
params = get_signed_params({"team": self.team})
|
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}"
|
url = f"{api}?{params}"
|
||||||
|
|
||||||
if jingrow.conf.developer_mode:
|
if jingrow.conf.developer_mode:
|
||||||
@ -173,7 +173,7 @@ class TeamDeletionRequest(PersonalDataDeletionRequest):
|
|||||||
|
|
||||||
client = get_jingrow_io_connection()
|
client = get_jingrow_io_connection()
|
||||||
response = client.session.delete(
|
response = client.session.delete(
|
||||||
f"{client.url}/api/method/delete-fc-team",
|
f"{client.url}/api/action/delete-fc-team",
|
||||||
data={"team": self.team},
|
data={"team": self.team},
|
||||||
headers=client.headers,
|
headers=client.headers,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class TestTeamDeletionRequest(unittest.TestCase):
|
|||||||
deletion_url = self.team_deletion_request.generate_url_for_confirmation()
|
deletion_url = self.team_deletion_request.generate_url_for_confirmation()
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
deletion_url.startswith(
|
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")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -155,7 +155,7 @@ class MarketplaceAppSubscription(Document):
|
|||||||
try:
|
try:
|
||||||
for path in paths:
|
for path in paths:
|
||||||
requests.post(
|
requests.post(
|
||||||
f"https://{self.site}/api/method/{path}",
|
f"https://{self.site}/api/action/{path}",
|
||||||
data={"app": self.app, "plan": self.plan},
|
data={"app": self.app, "plan": self.plan},
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@ -75,7 +75,7 @@ class PartnerApprovalRequest(Document):
|
|||||||
jingrow.throw("Failed to create approval request. Please contact support.")
|
jingrow.throw("Failed to create approval request. Please contact support.")
|
||||||
customer = jingrow.db.get_value("Team", self.requested_by, "user")
|
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(
|
jingrow.sendmail(
|
||||||
subject="Partner Approval Request",
|
subject="Partner Approval Request",
|
||||||
|
|||||||
@ -59,7 +59,7 @@ scrape_configs:
|
|||||||
target_label: __param_target
|
target_label: __param_target
|
||||||
- source_labels: [__param_target]
|
- source_labels: [__param_target]
|
||||||
target_label: instance
|
target_label: instance
|
||||||
regex: 'https://(.*)/api/method/ping'
|
regex: 'https://(.*)/api/action/ping'
|
||||||
- target_label: __address__
|
- target_label: __address__
|
||||||
replacement: '{{ server }}'
|
replacement: '{{ server }}'
|
||||||
file_sd_configs:
|
file_sd_configs:
|
||||||
@ -80,7 +80,7 @@ scrape_configs:
|
|||||||
target_label: __param_target
|
target_label: __param_target
|
||||||
- source_labels: [__param_target]
|
- source_labels: [__param_target]
|
||||||
target_label: instance
|
target_label: instance
|
||||||
regex: 'https://(.*)/api/method/ping'
|
regex: 'https://(.*)/api/action/ping'
|
||||||
- target_label: __address__
|
- target_label: __address__
|
||||||
replacement: '{{ server }}'
|
replacement: '{{ server }}'
|
||||||
file_sd_configs:
|
file_sd_configs:
|
||||||
|
|||||||
@ -5,11 +5,11 @@ OIDC_ISSUER = "Jingrow"
|
|||||||
OIDC_SCOPE = "openid email"
|
OIDC_SCOPE = "openid email"
|
||||||
|
|
||||||
OIDC_AUTHORIZATION_ENDPOINT = (
|
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 = (
|
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 = (
|
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"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default async function call(method, args) {
|
|||||||
headers['X-Jingrow-CSRF-Token'] = window.csrf_token;
|
headers['X-Jingrow-CSRF-Token'] = window.csrf_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`/api/method/${method}`, {
|
const res = await fetch(`/api/action/${method}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(args),
|
body: JSON.stringify(args),
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from html2text import html2text
|
|||||||
|
|
||||||
def get_remote_script(remote_site):
|
def get_remote_script(remote_site):
|
||||||
print("Retrieving Site Migrator...")
|
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)
|
request = requests.get(request_url)
|
||||||
|
|
||||||
if request.status_code / 100 != 2:
|
if request.status_code / 100 != 2:
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import requests
|
|||||||
def jingrowcloud_migrator():
|
def jingrowcloud_migrator():
|
||||||
print("Retreiving Site Migrator...")
|
print("Retreiving Site Migrator...")
|
||||||
remote_site = "jingrow.com"
|
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)
|
request = requests.get(request_url)
|
||||||
|
|
||||||
if request.status_code / 100 != 2:
|
if request.status_code / 100 != 2:
|
||||||
|
|||||||
@ -30,7 +30,7 @@ Sometimes, we may need to pass the secret token to frontend for some specific ta
|
|||||||
**Request**
|
**Request**
|
||||||
|
|
||||||
```bash
|
```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: oka-hdz-qpj.tanmoy.fc.jingrow.dev' \
|
||||||
--header 'x-site-token: 004f85a3ae93927d2f0fcc668d11cb71'
|
--header 'x-site-token: 004f85a3ae93927d2f0fcc668d11cb71'
|
||||||
```
|
```
|
||||||
|
|||||||
@ -471,32 +471,32 @@ def jingrowcloud_migrator(local_site, jingrow_provider):
|
|||||||
remote_site = jingrow_provider or jingrow.conf.jingrowcloud_url
|
remote_site = jingrow_provider or jingrow.conf.jingrowcloud_url
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
|
|
||||||
login_url = "{}://{}/api/method/login".format(scheme, remote_site)
|
login_url = "{}://{}/api/action/login".format(scheme, remote_site)
|
||||||
upload_url = "{}://{}/api/method/jcloud.api.site.new".format(scheme, remote_site)
|
upload_url = "{}://{}/api/action/jcloud.api.site.new".format(scheme, remote_site)
|
||||||
remote_link_url = "{}://{}/api/method/jcloud.api.site.get_upload_link".format(
|
remote_link_url = "{}://{}/api/action/jcloud.api.site.get_upload_link".format(
|
||||||
scheme, remote_site
|
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
|
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
|
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
|
scheme, remote_site
|
||||||
)
|
)
|
||||||
site_info_url = "{}://{}/api/method/jcloud.api.site.get".format(scheme, remote_site)
|
site_info_url = "{}://{}/api/action/jcloud.api.site.get".format(scheme, remote_site)
|
||||||
account_details_url = "{}://{}/api/method/jcloud.api.account.get".format(
|
account_details_url = "{}://{}/api/action/jcloud.api.account.get".format(
|
||||||
scheme, remote_site
|
scheme, remote_site
|
||||||
)
|
)
|
||||||
all_site_url = "{}://{}/api/method/jcloud.api.site.all".format(scheme, remote_site)
|
all_site_url = "{}://{}/api/action/jcloud.api.site.all".format(scheme, remote_site)
|
||||||
restore_site_url = "{}://{}/api/method/jcloud.api.site.restore".format(
|
restore_site_url = "{}://{}/api/action/jcloud.api.site.restore".format(
|
||||||
scheme, remote_site
|
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
|
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
|
scheme, remote_site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro form(fields, action='') %}
|
{% macro form(fields, action='') %}
|
||||||
<form action="/api/method/{{ action }}" method="POST">
|
<form action="/api/action/{{ action }}" method="POST">
|
||||||
<section class="space-y-4">
|
<section class="space-y-4">
|
||||||
{%- for df in fields -%}
|
{%- for df in fields -%}
|
||||||
<p class="space-y-2">
|
<p class="space-y-2">
|
||||||
|
|||||||
@ -6,7 +6,7 @@ Action,行动,
|
|||||||
Action Type,动作类型,
|
Action Type,动作类型,
|
||||||
Actions,操作,
|
Actions,操作,
|
||||||
Activate Site,激活站点,
|
Activate Site,激活站点,
|
||||||
Active,活动,
|
Active,激活,
|
||||||
Add,添加,
|
Add,添加,
|
||||||
Add Domain,添加域名,
|
Add Domain,添加域名,
|
||||||
Additional Permissions,额外的权限,
|
Additional Permissions,额外的权限,
|
||||||
@ -131,7 +131,7 @@ IP Address,IP地址,
|
|||||||
Image,图像,
|
Image,图像,
|
||||||
Impersonate Team,模拟团队,
|
Impersonate Team,模拟团队,
|
||||||
In Progress,进行中,
|
In Progress,进行中,
|
||||||
Inactive,非活动的,
|
Inactive,未激活,
|
||||||
Index,索引,
|
Index,索引,
|
||||||
Info,信息,
|
Info,信息,
|
||||||
Instance Type,实例类型,
|
Instance Type,实例类型,
|
||||||
@ -187,7 +187,7 @@ Patch,补丁,
|
|||||||
Payment Date,付款日期,
|
Payment Date,付款日期,
|
||||||
Payment Gateway,支付网关,
|
Payment Gateway,支付网关,
|
||||||
Payment Mode,支付方式,
|
Payment Mode,支付方式,
|
||||||
Pending,等待,
|
Pending,待处理,
|
||||||
Pending Verification,待验证,
|
Pending Verification,待验证,
|
||||||
Percent,百分之,
|
Percent,百分之,
|
||||||
Permissions,权限,
|
Permissions,权限,
|
||||||
|
|||||||
|
@ -342,7 +342,7 @@ class RemoteJingrowSite:
|
|||||||
|
|
||||||
def _validate_jingrow_site(self):
|
def _validate_jingrow_site(self):
|
||||||
"""Validates if Jingrow Site and sets RemoteBackupRetrieval.site"""
|
"""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:
|
if not res.ok:
|
||||||
jingrow.throw("Invalid Jingrow Site")
|
jingrow.throw("Invalid Jingrow Site")
|
||||||
@ -354,7 +354,7 @@ class RemoteJingrowSite:
|
|||||||
def _validate_user_permissions(self):
|
def _validate_user_permissions(self):
|
||||||
"""Validates user permssions on Jingrow Site and sets RemoteBackupRetrieval.user_sid"""
|
"""Validates user permssions on Jingrow Site and sets RemoteBackupRetrieval.user_sid"""
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.site}/api/method/login",
|
f"{self.site}/api/action/login",
|
||||||
data={"usr": self.user_login, "pwd": self.password_login},
|
data={"usr": self.user_login, "pwd": self.password_login},
|
||||||
timeout=(5, 10),
|
timeout=(5, 10),
|
||||||
)
|
)
|
||||||
@ -393,7 +393,7 @@ class RemoteJingrowSite:
|
|||||||
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
||||||
suffix = f"?sid={self.user_sid}" if self.user_sid else ""
|
suffix = f"?sid={self.user_sid}" if self.user_sid else ""
|
||||||
res = requests.get(
|
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,
|
headers=headers,
|
||||||
timeout=(5, 10),
|
timeout=(5, 10),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -225,7 +225,7 @@ def get_partner_external_connection(mpesa_setup):
|
|||||||
api_secret = pg.get_password("api_secret")
|
api_secret = pg.get_password("api_secret")
|
||||||
url = pg.url
|
url = pg.url
|
||||||
|
|
||||||
site_name = url.split("/api/method")[0]
|
site_name = url.split("/api/action")[0]
|
||||||
# Establish connection
|
# Establish connection
|
||||||
jingrow.local._external_conn = JingrowClient(site_name, api_key=api_key, api_secret=api_secret)
|
jingrow.local._external_conn = JingrowClient(site_name, api_key=api_key, api_secret=api_secret)
|
||||||
return jingrow.local._external_conn
|
return jingrow.local._external_conn
|
||||||
|
|||||||
@ -137,7 +137,7 @@ function initiateRequestForLoginToJingrowCloud() {
|
|||||||
|
|
||||||
function requestLoginToFC(freezing_msg) {
|
function requestLoginToFC(freezing_msg) {
|
||||||
jingrow.request.call({
|
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',
|
type: 'POST',
|
||||||
args: {
|
args: {
|
||||||
domain: window.location.hostname,
|
domain: window.location.hostname,
|
||||||
@ -198,7 +198,7 @@ function showFCLogchinalog(email) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jingrow.request.call({
|
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',
|
type: 'POST',
|
||||||
args: {
|
args: {
|
||||||
domain: window.location.hostname,
|
domain: window.location.hostname,
|
||||||
@ -211,14 +211,14 @@ function showFCLogchinalog(email) {
|
|||||||
if (r.login_token) {
|
if (r.login_token) {
|
||||||
fc_login_dialog.hide();
|
fc_login_dialog.hide();
|
||||||
window.open(
|
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',
|
'_blank',
|
||||||
);
|
);
|
||||||
jingrow.msgprint({
|
jingrow.msgprint({
|
||||||
title: __('Jingrow Login Successful'),
|
title: __('Jingrow Login Successful'),
|
||||||
indicator: 'green',
|
indicator: 'green',
|
||||||
message: __(
|
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 {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user