mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
fix(costcenter): ensure PAYG subscribe button opens plan dialog (#6973)
* fix(costcenter): ensure PAYG subscribe button opens plan dialog * fix(desktop): update running apps for explicit deep links * update
This commit is contained in:
@@ -18,6 +18,52 @@ export type CronJobStatus = {
|
||||
commit: () => Promise<void>;
|
||||
};
|
||||
const TIMEOUT = 5000;
|
||||
const RUNNABLE_TRANSACTION_STATUSES = [TransactionStatus.READY, TransactionStatus.RUNNING];
|
||||
|
||||
const getRunnableTransactionUidList = async () => {
|
||||
const transactions = await globalPrisma.precommitTransaction.findMany({
|
||||
where: {
|
||||
status: {
|
||||
in: RUNNABLE_TRANSACTION_STATUSES
|
||||
}
|
||||
},
|
||||
select: {
|
||||
uid: true
|
||||
}
|
||||
});
|
||||
return transactions.map(({ uid }) => uid);
|
||||
};
|
||||
|
||||
const findRunnableTransactionDetail = async ({
|
||||
regionUid,
|
||||
status,
|
||||
transactionUidList,
|
||||
updatedAt
|
||||
}: {
|
||||
regionUid: string;
|
||||
status: TransactionStatus;
|
||||
transactionUidList: string[];
|
||||
updatedAt?: {
|
||||
lte: Date;
|
||||
};
|
||||
}) => {
|
||||
if (transactionUidList.length === 0) return null;
|
||||
|
||||
return globalPrisma.transactionDetail.findFirst({
|
||||
where: {
|
||||
regionUid,
|
||||
status,
|
||||
transactionUid: {
|
||||
in: transactionUidList
|
||||
},
|
||||
...(updatedAt ? { updatedAt } : {})
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'asc'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getJob = (
|
||||
transaction: Prisma.Result<
|
||||
typeof globalPrisma.precommitTransaction,
|
||||
@@ -40,49 +86,22 @@ export const runTransactionjob = async () => {
|
||||
// console.log('run transactionjob', new Date());
|
||||
const regionUid = getRegionUid();
|
||||
let isTimeoutTransactionDetail = false;
|
||||
const runnableTransactionUidList = await getRunnableTransactionUidList();
|
||||
// find task
|
||||
let transactionDetail = await globalPrisma.transactionDetail.findFirst({
|
||||
where: {
|
||||
regionUid,
|
||||
status: TransactionStatus.READY,
|
||||
precommitTransaction: {
|
||||
is: {
|
||||
status: {
|
||||
in: [TransactionStatus.READY, TransactionStatus.RUNNING]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
include: {
|
||||
precommitTransaction: true
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'asc'
|
||||
}
|
||||
let transactionDetail = await findRunnableTransactionDetail({
|
||||
regionUid,
|
||||
status: TransactionStatus.READY,
|
||||
transactionUidList: runnableTransactionUidList
|
||||
});
|
||||
if (!transactionDetail) {
|
||||
transactionDetail = await globalPrisma.transactionDetail.findFirst({
|
||||
where: {
|
||||
regionUid,
|
||||
// death lock
|
||||
status: TransactionStatus.RUNNING,
|
||||
updatedAt: {
|
||||
lte: dayjs().subtract(TIMEOUT, 'ms').toDate()
|
||||
},
|
||||
precommitTransaction: {
|
||||
is: {
|
||||
status: {
|
||||
in: [TransactionStatus.READY, TransactionStatus.RUNNING]
|
||||
}
|
||||
}
|
||||
}
|
||||
transactionDetail = await findRunnableTransactionDetail({
|
||||
regionUid,
|
||||
// death lock
|
||||
status: TransactionStatus.RUNNING,
|
||||
updatedAt: {
|
||||
lte: dayjs().subtract(TIMEOUT, 'ms').toDate()
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'asc'
|
||||
},
|
||||
include: {
|
||||
precommitTransaction: true
|
||||
}
|
||||
transactionUidList: runnableTransactionUidList
|
||||
});
|
||||
isTimeoutTransactionDetail = true;
|
||||
}
|
||||
@@ -90,7 +109,12 @@ export const runTransactionjob = async () => {
|
||||
if (!transactionDetail) {
|
||||
return;
|
||||
}
|
||||
const transaction = transactionDetail.precommitTransaction;
|
||||
const transaction = await globalPrisma.precommitTransaction.findUnique({
|
||||
where: {
|
||||
uid: transactionDetail.transactionUid
|
||||
}
|
||||
});
|
||||
if (!transaction) return;
|
||||
|
||||
const job = getJob(transaction);
|
||||
if (!job) return;
|
||||
@@ -173,21 +197,40 @@ export const finishTransactionJob = async () => {
|
||||
const transactionList = await globalPrisma.precommitTransaction.findMany({
|
||||
where: {
|
||||
status: {
|
||||
in: [TransactionStatus.RUNNING, TransactionStatus.READY]
|
||||
in: RUNNABLE_TRANSACTION_STATUSES
|
||||
}
|
||||
},
|
||||
include: {
|
||||
transactionDetail: {
|
||||
select: {
|
||||
status: true,
|
||||
regionUid: true
|
||||
}
|
||||
}
|
||||
select: {
|
||||
uid: true
|
||||
}
|
||||
});
|
||||
const transactionUidList = transactionList.map(({ uid }) => uid);
|
||||
if (transactionUidList.length === 0) return;
|
||||
|
||||
const transactionDetailList = await globalPrisma.transactionDetail.findMany({
|
||||
where: {
|
||||
transactionUid: {
|
||||
in: transactionUidList
|
||||
}
|
||||
},
|
||||
select: {
|
||||
status: true,
|
||||
regionUid: true,
|
||||
transactionUid: true
|
||||
}
|
||||
});
|
||||
const transactionDetailMap = transactionDetailList.reduce<
|
||||
Record<string, { status: TransactionStatus; regionUid: string }[]>
|
||||
>((acc, detail) => {
|
||||
acc[detail.transactionUid] = acc[detail.transactionUid] ?? [];
|
||||
acc[detail.transactionUid].push(detail);
|
||||
return acc;
|
||||
}, {});
|
||||
const needFinishTransactionList = transactionList
|
||||
.filter((tx) => {
|
||||
const finishList = tx.transactionDetail.filter((d) => d.status === TransactionStatus.FINISH);
|
||||
const finishList = (transactionDetailMap[tx.uid] ?? []).filter(
|
||||
(d) => d.status === TransactionStatus.FINISH
|
||||
);
|
||||
return regionList.every(({ uid }) => finishList.findIndex((f) => f.regionUid === uid) >= 0);
|
||||
})
|
||||
.map((tx) => tx.uid);
|
||||
|
||||
@@ -13,6 +13,45 @@ import useSessionStore from './session';
|
||||
export const BRAIN_APP_KEY = 'system-brain';
|
||||
export const SESSION_RESTORE_APP_KEY = 'sealos_desktop_restore_app_key';
|
||||
|
||||
const buildAppUrl = ({
|
||||
baseUrl,
|
||||
pathname = '/',
|
||||
raw,
|
||||
query
|
||||
}: {
|
||||
baseUrl: string;
|
||||
pathname?: string;
|
||||
raw?: string;
|
||||
query?: Record<string, string>;
|
||||
}) => {
|
||||
let finalUrl = baseUrl;
|
||||
|
||||
if (pathname && pathname !== '/') {
|
||||
const normalizedBaseUrl = finalUrl.endsWith('/') ? finalUrl.slice(0, -1) : finalUrl;
|
||||
const normalizedPath = pathname.startsWith('/') ? pathname : '/' + pathname;
|
||||
finalUrl = normalizedBaseUrl + normalizedPath;
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
finalUrl += finalUrl.includes('?') ? '&' : '?';
|
||||
finalUrl += raw;
|
||||
} else if (query) {
|
||||
finalUrl = formatUrl(finalUrl, query);
|
||||
}
|
||||
|
||||
return finalUrl;
|
||||
};
|
||||
|
||||
const hasExplicitNavigationTarget = ({
|
||||
pathname,
|
||||
raw,
|
||||
query
|
||||
}: {
|
||||
pathname?: string;
|
||||
raw?: string;
|
||||
query?: Record<string, string>;
|
||||
}) => !!((pathname && pathname !== '/') || raw || (query && Object.keys(query).length > 0));
|
||||
|
||||
export class AppInfo {
|
||||
pid: number;
|
||||
isShow: boolean;
|
||||
@@ -167,6 +206,21 @@ const useAppStore = create<TOSState>()(
|
||||
// 未支持多实例
|
||||
let alreadyApp = get().runningInfo.find((x) => x.key === app.key);
|
||||
if (alreadyApp) {
|
||||
const shouldNavigateRunningApp = hasExplicitNavigationTarget({ pathname, raw, query });
|
||||
|
||||
if (shouldNavigateRunningApp) {
|
||||
const baseUrl = app.data?.url || alreadyApp.data?.url;
|
||||
alreadyApp = {
|
||||
...alreadyApp,
|
||||
data: {
|
||||
...alreadyApp.data,
|
||||
url: baseUrl
|
||||
? buildAppUrl({ baseUrl, pathname, raw, query })
|
||||
: alreadyApp.data.url
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (appSize) {
|
||||
get().updateOpenedAppInfo({
|
||||
...alreadyApp,
|
||||
@@ -175,8 +229,12 @@ const useAppStore = create<TOSState>()(
|
||||
cacheSize: appSize
|
||||
});
|
||||
} else {
|
||||
if (shouldNavigateRunningApp) {
|
||||
get().updateOpenedAppInfo(alreadyApp);
|
||||
}
|
||||
get().switchAppById(alreadyApp.pid);
|
||||
}
|
||||
|
||||
get().setToHighestLayerById(alreadyApp.pid);
|
||||
return;
|
||||
}
|
||||
@@ -198,22 +256,7 @@ const useAppStore = create<TOSState>()(
|
||||
_app.isShow = true;
|
||||
|
||||
if (_app.data?.url) {
|
||||
let finalUrl = _app.data.url;
|
||||
|
||||
if (pathname && pathname !== '/') {
|
||||
const baseUrl = finalUrl.endsWith('/') ? finalUrl.slice(0, -1) : finalUrl;
|
||||
const normalizedPath = pathname.startsWith('/') ? pathname : '/' + pathname;
|
||||
finalUrl = baseUrl + normalizedPath;
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
finalUrl += finalUrl.includes('?') ? '&' : '?';
|
||||
finalUrl += raw;
|
||||
} else if (query) {
|
||||
finalUrl = formatUrl(finalUrl, query);
|
||||
}
|
||||
|
||||
_app.data.url = finalUrl;
|
||||
_app.data.url = buildAppUrl({ baseUrl: _app.data.url, pathname, raw, query });
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
|
||||
@@ -189,20 +189,28 @@ export function PlanHeader({ children, onRenewSuccess }: PlanHeaderProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{children?.({
|
||||
trigger: (
|
||||
<Tooltip open={canManagePayment ? false : undefined}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="lg" variant="outline" disabled={!canManagePayment}>
|
||||
{canManagePayment ? (
|
||||
children?.({
|
||||
trigger: (
|
||||
<Button size="lg" variant="outline">
|
||||
<span>{inDebt ? t('common:renew') : t('common:subscribe_plan')}</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex">
|
||||
<Button size="lg" variant="outline" disabled>
|
||||
<span>{inDebt ? t('common:renew') : t('common:subscribe_plan')}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t('common:can_not_manage_payments')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t('common:can_not_manage_payments')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user