Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 184 additions & 26 deletions frontend/src/views/setting/license-required/index.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
<template>
<div
class="flex items-center justify-center min-h-screen relative bg-gray-100"
v-loading="restoring"
:element-loading-text="$t('license.restoreCommunityStarting')"
fullscreen
>
<div class="license-required-page flex items-center justify-center min-h-screen relative bg-gray-100">
<div class="absolute inset-0 bg-cover bg-center bg-no-repeat" :style="backgroundStyle"></div>
<div
:style="{ width: containerWidth, height: containerHeight }"
Expand Down Expand Up @@ -102,6 +97,20 @@
</div>

<CommunityRestoreDialog ref="communityRestoreRef" @started="handleCommunityRestoreStarted" />

<Transition name="restore-mask">
<div v-if="restoring" class="restore-mask" role="status" aria-live="polite" aria-busy="true">
<div class="restore-status-card">
<div class="restore-spinner" aria-hidden="true">
<span></span>
</div>
<div class="restore-status-title">{{ $t('license.restoreCommunity') }}</div>
<div class="restore-status-description">
{{ $t('license.restoreCommunityStarting') }}
</div>
</div>
</div>
</Transition>
</div>
</template>

Expand Down Expand Up @@ -130,7 +139,6 @@ const {
isEnterpriseLicensed,
isFxplay,
isIntl,
isLoading: restoring,
isLogin,
isMasterProductPro,
isOffline,
Expand All @@ -140,13 +148,16 @@ const {
themeConfig,
} = useGlobalStore();
const loading = ref(false);
const restoring = ref(isOnRestart.value);
const communityRestoreRef = ref<InstanceType<typeof CommunityRestoreDialog>>();
const uploadRef = ref<UploadInstance>();
const uploaderFiles = ref<UploadFiles>([]);
const licenseInfo = reactive({
deviceID: '',
});
let restoreTimer: number | undefined;
let pageInitialized = false;
let pageInitialization: Promise<void> | undefined;
const defaultLoginImage = new URL('@/assets/images/1panel-login-enterprise.png', import.meta.url).href;
const defaultLoginBgImage = new URL('@/assets/images/1panel-login-bg.jpg', import.meta.url).href;
const loadedLoginImage = ref<string | null>(null);
Expand Down Expand Up @@ -205,10 +216,6 @@ const applyLoginThemeColor = () => {
'--login-btn-link-hover-color',
adjustColorToRGBA(loginBtnLinkColor, -10, 80),
);
document.documentElement.style.setProperty(
'--login-loading-mask-color',
adjustColorToRGBA(loginBtnLinkColor, 0, 60),
);
};

const loadLoginTheme = async () => {
Expand Down Expand Up @@ -283,6 +290,40 @@ const loadLicenseInfo = async () => {
isEnterpriseLicensed.value = false;
};

const initializePage = () => {
if (pageInitialized) {
return Promise.resolve();
}
if (pageInitialization) {
return pageInitialization;
}

loading.value = true;
pageInitialization = (async () => {
const shouldLoadLicense = await loadLoginTheme();
if (!shouldLoadLicense) {
pageInitialized = true;
return;
}
await loadXpackLoginTheme();
await loadBackground();
await loadLicenseInfo();
pageInitialized = true;
})().finally(() => {
loading.value = false;
pageInitialization = undefined;
});
return pageInitialization;
};

const resumePageInitialization = async () => {
try {
await initializePage();
} catch {
// Request errors are already presented by the shared API interceptor.
}
};

const loadRestoreInfo = async () => {
const res = await getCommunityRestoreStatus();
restoring.value = res.data.state === 'Running';
Expand Down Expand Up @@ -331,13 +372,16 @@ const pollRestoreStatus = async () => {
restoring.value = false;
isOnRestart.value = false;
MsgError(res.data.message);
await resumePageInitialization();
return;
}
restoring.value = res.data.state === 'Running';
isOnRestart.value = restoring.value;
if (restoring.value) {
scheduleRestorePoll();
return;
}
await resumePageInitialization();
} catch {
await waitForCommunityService();
}
Expand Down Expand Up @@ -400,22 +444,22 @@ onMounted(async () => {
window.addEventListener('resize', updateSize);
loading.value = true;
try {
const shouldLoadLicense = await loadLoginTheme();
if (!shouldLoadLicense) {
return;
}
await loadXpackLoginTheme();
await loadBackground();
await loadLicenseInfo();
if (!isEnterpriseLicensed.value) {
try {
await loadRestoreInfo();
} catch {
if (restoring.value) {
scheduleRestorePoll();
await waitForCommunityService();
return;
}
}
if (restoring.value) {
scheduleRestorePoll();
return;
}
Comment thread
ssongliu marked this conversation as resolved.
} finally {
loading.value = false;
}
await initializePage();
});

onUnmounted(() => {
Expand All @@ -429,6 +473,10 @@ onUnmounted(() => {
border-radius: 0;
}

.license-required-page {
isolation: isolate;
}

.community-restore-link {
position: absolute;
top: 16px;
Expand Down Expand Up @@ -554,15 +602,125 @@ onUnmounted(() => {
line-height: 20px;
}

:deep(.el-loading-mask) {
background-color: var(--login-loading-mask-color) !important;
.restore-mask {
position: fixed;
z-index: 2000;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgb(15 23 42 / 48%);
backdrop-filter: blur(8px) saturate(90%);
}

.restore-status-card {
display: flex;
width: min(400px, 100%);
flex-direction: column;
align-items: center;
padding: 32px 36px;
border: 1px solid rgb(255 255 255 / 70%);
border-radius: 16px;
background: rgb(255 255 255 / 96%);
box-shadow:
0 24px 64px rgb(15 23 42 / 24%),
0 4px 16px rgb(15 23 42 / 10%);
text-align: center;
}

.el-loading-spinner .path {
stroke: #ffffff;
.restore-spinner {
position: relative;
width: 54px;
height: 54px;
margin-bottom: 22px;

&::before,
&::after {
position: absolute;
inset: 0;
border: 3px solid rgb(148 163 184 / 22%);
border-radius: 50%;
content: '';
}

.el-loading-text {
color: #ffffff;
&::after {
border-color: var(--login-btn-link-color, #005eeb) transparent transparent;
animation: restore-spin 0.9s linear infinite;
}

span {
position: absolute;
inset: 19px;
border-radius: 50%;
background: var(--login-btn-link-color, #005eeb);
box-shadow: 0 0 0 6px color-mix(in srgb, var(--login-btn-link-color, #005eeb) 10%, transparent);
animation: restore-pulse 1.5s ease-in-out infinite;
}
}

.restore-status-title {
color: var(--el-text-color-primary, #1f2937);
font-size: 18px;
font-weight: 600;
line-height: 26px;
}

.restore-status-description {
max-width: 320px;
margin-top: 8px;
color: var(--el-text-color-secondary, #64748b);
font-size: 14px;
line-height: 22px;
}

.restore-mask-enter-active,
.restore-mask-leave-active {
transition: opacity 0.22s ease;

.restore-status-card {
transition:
opacity 0.22s ease,
transform 0.22s ease;
}
}

.restore-mask-enter-from,
.restore-mask-leave-to {
opacity: 0;

.restore-status-card {
opacity: 0;
transform: translateY(8px) scale(0.98);
}
}

@keyframes restore-spin {
to {
transform: rotate(360deg);
}
}

@keyframes restore-pulse {
0%,
100% {
opacity: 0.75;
transform: scale(0.9);
}

50% {
opacity: 1;
transform: scale(1);
}
}

@media (prefers-reduced-motion: reduce) {
.restore-spinner::after {
animation-duration: 1.8s;
}

.restore-spinner span {
animation: none;
}
}
</style>
Loading