feat: add router expires alert (#12855)

This commit is contained in:
ssongliu
2026-05-26 17:28:26 +08:00
committed by GitHub
parent 7307b9d6fb
commit 7d20118f38
13 changed files with 160 additions and 64 deletions
+2 -10
View File
@@ -106,7 +106,7 @@ func OperationLog() gin.HandlerFunc {
writer := responseBodyWriter{
ResponseWriter: c.Writer,
body: &bytes.Buffer{},
captureBody: shouldCaptureResponseBody(c.Request.URL.Path),
captureBody: !strings.Contains(strings.ToLower(c.Request.URL.Path), "download"),
}
c.Writer = &writer
now := time.Now()
@@ -272,16 +272,8 @@ func (r *responseBodyWriter) Write(b []byte) (int, error) {
return r.ResponseWriter.Write(b)
}
func shouldCaptureResponseBody(reqPath string) bool {
reqPath = strings.ToLower(reqPath)
if strings.Contains(reqPath, "download") {
return false
}
return true
}
func loadLogInfo(path string) string {
path = replaceStr(path, "/api/v2", "/core", "/xpack")
path = replaceStr(path, "/api/v2", "/core", "/xpack", "/enterprise")
if !strings.Contains(path, "/") {
return ""
}
+101 -19
View File
@@ -1,25 +1,44 @@
<template>
<el-card class="router_card p-1 sm:p-0">
<div class="flex w-full flex-col justify-start sm:items-center items-start sm:justify-between sm:flex-row">
<el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button
class="router_card_button"
:label="button.label"
:value="button.label"
v-for="(button, index) in buttonArray"
size="large"
:key="index"
>
<el-badge :value="button.count" v-if="button.count" is-dot>
<span>{{ button.label }}</span>
</el-badge>
</el-radio-button>
</el-radio-group>
<div class="flex flex-col gap-2 sm:flex-row">
<slot name="route-button"></slot>
<div>
<el-card class="router_card p-1 sm:p-0">
<div class="flex w-full flex-col justify-start sm:items-center items-start sm:justify-between sm:flex-row">
<el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button
class="router_card_button"
:label="button.label"
:value="button.label"
v-for="(button, index) in buttonArray"
size="large"
:key="index"
>
<el-badge :value="button.count" v-if="button.count" is-dot>
<span>{{ button.label }}</span>
</el-badge>
</el-radio-button>
</el-radio-group>
<div class="flex flex-col gap-2 sm:flex-row">
<slot name="route-button"></slot>
</div>
</div>
</el-card>
<div class="mt-3" v-if="showExpiresAt && expiresAlertVisible && productProExpires && productProExpires !== 0">
<el-alert type="warning" @close="handleExpiresAlertClose">
<template #title>
<div class="text-xs">
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
<span>
{{ $t(expiresAlertKey, [expiresInfo]) }}
</span>
<span @click="goXpack" class="flex items-center justify-center gap-0.5 jump">
<el-icon><Position /></el-icon>
{{ $t('firewall.quickJump') }}
</span>
</div>
</div>
</template>
</el-alert>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
@@ -27,6 +46,7 @@ import { routerToNameWithQuery, routerToPathWithQuery } from '@/utils/router';
import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { hasPermissionMetaAccess, hasRouteAccess } from '@/utils/rbac';
import { useGlobalStore } from '@/composables/useGlobalStore';
defineOptions({ name: 'RouterButton' });
@@ -35,9 +55,14 @@ const props = defineProps({
type: Array<RouterButton>,
required: true,
},
showExpiresAt: {
type: Boolean,
default: false,
},
});
const router = useRouter();
const { isEnterprise, isIntl, productProExpires } = useGlobalStore();
const buttonArray = computed(() => {
return props.buttons.filter((button) => {
if (!hasPermissionMetaAccess(button.permission)) {
@@ -52,6 +77,9 @@ const buttonArray = computed(() => {
});
const activeName = ref('');
const expiresInfo = ref(0);
const expiresAlertVisible = ref(false);
const expiresAlertKey = computed(() => (isEnterprise.value ? 'xpack.expiresEnterpriseAlert' : 'xpack.expiresProAlert'));
const handleChange = (label: string) => {
const btn = buttonArray.value.find((btn) => btn.label === label);
@@ -63,6 +91,7 @@ const handleChange = (label: string) => {
onMounted(() => {
syncActiveName();
loadExpiresAlert();
});
watch(
@@ -72,6 +101,13 @@ watch(
},
);
watch(
() => [props.showExpiresAt, productProExpires.value],
() => {
loadExpiresAlert();
},
);
function syncActiveName() {
if (!buttonArray.value.length) {
activeName.value = '';
@@ -91,6 +127,52 @@ function syncActiveName() {
}
}
}
function getExpiresAlertDateKey() {
const newDate = new Date();
return newDate.getFullYear() + '-' + newDate.getMonth() + '-' + newDate.getDate();
}
function loadExpiresAlert() {
const expires = productProExpires.value;
if (!props.showExpiresAt || !expires || expires === 0) {
expiresInfo.value = 0;
expiresAlertVisible.value = false;
return;
}
if (getExpiresAlertDateKey() === localStorage.getItem('xpack-expires-alert')) {
expiresInfo.value = 0;
expiresAlertVisible.value = false;
return;
}
const currentTimestamp = Date.now() / 1000;
if (expires < currentTimestamp) {
expiresInfo.value = 0;
expiresAlertVisible.value = false;
return;
}
const daySeconds = 24 * 60 * 60;
const diffSeconds = Math.abs(expires - currentTimestamp);
expiresInfo.value = Math.floor(diffSeconds / daySeconds) + 1;
expiresAlertVisible.value = expiresInfo.value <= 15;
}
function goXpack() {
if (isIntl.value && !isEnterprise.value) {
window.open('https://1panel.hk/pricing', '_blank', 'noopener,noreferrer');
return;
}
const url = isEnterprise.value ? 'https://1panel.cn/enterprise.html' : 'https://www.lxware.cn/1panel';
window.open(url, '_blank', 'noopener,noreferrer');
}
function handleExpiresAlertClose() {
localStorage.setItem('xpack-expires-alert', getExpiresAlertDateKey());
loadExpiresAlert();
}
</script>
<style lang="scss" scoped>
+6 -4
View File
@@ -2003,6 +2003,8 @@ const message = {
nodes: 'Node',
commands: 'Quick Commands',
opsReport: 'Ops Report',
users: 'User Management',
auth: 'Login Authentication',
},
websiteLog: 'Website logs',
runLog: 'Run logs',
@@ -3954,10 +3956,10 @@ const message = {
noFail: 'Mount failure does not affect system startup',
},
xpack: {
expiresTrialAlert:
'Friendly reminder: Your Commercial Edition trial will expire in {0} days, and all Commercial Edition features will no longer be accessible. Please renew or upgrade to the full version in a timely manner.',
expiresAlert:
'Friendly reminder: Your Commercial Edition license will expire in {0} days, and all Commercial Edition features will no longer be accessible. Please renew promptly to ensure continued usage.',
expiresEnterpriseAlert:
'Friendly reminder: Your Enterprise Edition license will expire in {0} days, and all Enterprise Edition features will no longer be accessible. Please renew promptly to ensure continued usage.',
expiresProAlert:
'Friendly reminder: Your Pro Edition license will expire in {0} days, and all Pro Edition features will no longer be accessible. Please renew promptly to ensure continued usage.',
menu: 'Pro',
upage: 'AI Website Builder',
proAlert: 'Upgrade to Commercial Edition to use this feature',
+6 -4
View File
@@ -2054,6 +2054,8 @@ const message = {
nodes: 'Nodo',
commands: 'Comandos rápidos',
opsReport: 'Informe de operaciones',
users: 'Gestión de usuarios',
auth: 'Autenticación de inicio de sesión',
},
websiteLog: 'Logs de sitio web',
runLog: 'Logs de ejecución',
@@ -4012,10 +4014,10 @@ const message = {
noFail: 'El fallo de montaje no afecta al inicio del sistema',
},
xpack: {
expiresTrialAlert:
'Aviso: Tu prueba de la edición comercial expirará en {0} días y todas las funciones comerciales dejarán de estar disponibles. Renueva o actualiza a la versión completa a tiempo.',
expiresAlert:
'Aviso: Tu licencia la edición comercial expirará en {0} días y todas las funciones comerciales dejarán de estar disponibles. Renueva pronto para asegurar el uso continuo.',
expiresEnterpriseAlert:
'Aviso: Tu licencia de Enterprise Edition expirará en {0} días y todas las funciones de Enterprise Edition dejarán de estar disponibles. Renueva pronto para asegurar el uso continuo.',
expiresProAlert:
'Aviso: Tu licencia de Pro Edition expirará en {0} días y todas las funciones de Pro Edition dejarán de estar disponibles. Renueva pronto para asegurar el uso continuo.',
menu: 'Pro',
upage: 'Constructor Web con IA',
proAlert: 'Actualiza a la edición comercial para usar esta función',
+6 -4
View File
@@ -2025,6 +2025,8 @@ const message = {
nodes: 'ノード',
commands: 'クイックコマンド',
opsReport: '運用レポート',
users: 'ユーザー管理',
auth: 'ログイン認証',
},
websiteLog: 'ウェブサイトログ',
runLog: 'ログを実行します',
@@ -3994,10 +3996,10 @@ const message = {
noFail: 'マウント失敗はシステム起動に影響しません',
},
xpack: {
expiresTrialAlert:
'ご注意: あなたの商用版トライアルは{0}日後に終了しすべての商用版機能が使用できなくなります適時に更新またはフルバージョンにアップグレードしてください',
expiresAlert:
'ご注意: あなたの商用版ライセンスは{0}日後に終了しすべての商用版機能が使用できなくなります継続的な使用のために速やかに更新してください',
expiresEnterpriseAlert:
'ご注意: あなたのEnterprise Editionライセンスは{0}日後に終了しすべてのEnterprise Edition機能が使用できなくなります継続的な使用のために速やかに更新してください',
expiresProAlert:
'ご注意: あなたのPro Editionライセンスは{0}日後に終了しすべてのPro Edition機能が使用できなくなります継続的な使用のために速やかに更新してください',
menu: 'Рro',
upage: 'AIウェブサイトビルダー',
proAlert: 'この機能を使用するには商用版にアップグレードしてください',
+6 -4
View File
@@ -1982,6 +1982,8 @@ const message = {
nodes: '노드',
commands: '빠른 명령',
opsReport: '운영 보고서',
users: '사용자 관리',
auth: '로그인 인증',
},
websiteLog: '웹사이트 로그',
runLog: '실행 로그',
@@ -3906,10 +3908,10 @@ const message = {
noFail: '마운트 실패는 시스템 시작에 영향을 미치지 않습니다',
},
xpack: {
expiresTrialAlert:
'친절한 알림: 귀하의 상용 버전 체험판이 {0} 만료되며, 모든 상용 버전 기능에 이상 접근할 없습니다. 제때 갱신하거나 전체 버전으로 업그레이드하시기 바랍니다.',
expiresAlert:
'친절한 알림: 귀하의 상용 버전 라이선스가 {0} 만료되며, 모든 상용 버전 기능에 이상 접근할 없습니다. 지속적인 사용을 위해 신속하게 갱신하시기 바랍니다.',
expiresEnterpriseAlert:
'친절한 알림: 귀하의 Enterprise Edition 라이선스가 {0} 만료되며, 모든 Enterprise Edition 기능에 이상 접근할 없습니다. 지속적인 사용을 위해 신속하게 갱신하시기 바랍니다.',
expiresProAlert:
'친절한 알림: 귀하의 Pro Edition 라이선스가 {0} 만료되며, 모든 Pro Edition 기능에 이상 접근할 없습니다. 지속적인 사용을 위해 신속하게 갱신하시기 바랍니다.',
menu: 'Pro',
upage: 'AI 웹사이트 빌더',
proAlert: ' 기능을 사용하려면 상용 버전으로 업그레이드하세요',
+6 -4
View File
@@ -2051,6 +2051,8 @@ const message = {
nodes: 'nod',
commands: 'Perintah Pantas',
opsReport: 'Laporan Operasi',
users: 'Pengurusan pengguna',
auth: 'Pengesahan log masuk',
},
websiteLog: 'Log Laman Web',
runLog: 'Log Jalankan',
@@ -4049,10 +4051,10 @@ const message = {
mountPoint: 'Titik lekap',
},
xpack: {
expiresTrialAlert:
'Peringatan mesra: Percubaan Edisi Komersial anda akan tamat dalam {0} hari, dan semua ciri Edisi Komersial tidak lagi dapat diakses. Sila perbaharui atau naik taraf ke versi penuh tepat pada masanya.',
expiresAlert:
'Peringatan mesra: Lesen Edisi Komersial anda akan tamat dalam {0} hari, dan semua ciri Edisi Komersial tidak lagi dapat diakses. Sila perbaharui segera untuk memastikan penggunaan berterusan.',
expiresEnterpriseAlert:
'Peringatan mesra: Lesen Enterprise Edition anda akan tamat dalam {0} hari, dan semua ciri Enterprise Edition tidak lagi dapat diakses. Sila perbaharui segera untuk memastikan penggunaan berterusan.',
expiresProAlert:
'Peringatan mesra: Lesen Pro Edition anda akan tamat dalam {0} hari, dan semua ciri Pro Edition tidak lagi dapat diakses. Sila perbaharui segera untuk memastikan penggunaan berterusan.',
menu: 'Pro',
upage: 'Pembina Laman Web AI',
proAlert: 'Tingkatkan ke Edisi Komersial untuk menggunakan ciri ini',
+6 -4
View File
@@ -2165,6 +2165,8 @@ const message = {
nodes: 'nós',
commands: 'Comandos Rápidos',
opsReport: 'Relatório de Operações',
users: 'Gerenciamento de usuários',
auth: 'Autenticação de login',
},
websiteLog: 'Logs do website',
runLog: 'Logs de execução',
@@ -4188,10 +4190,10 @@ const message = {
noFail: 'Falha na montagem não afeta a inicialização do sistema',
},
xpack: {
expiresTrialAlert:
'Lembrete: Sua avaliação da edição comercial expira em {0} dias. Depois disso, todos os recursos da edição comercial deixarão de ficar disponíveis. Renove ou faça upgrade em tempo hábil.',
expiresAlert:
'Lembrete: Sua licença da edição comercial expira em {0} dias. Depois disso, todos os recursos da edição comercial deixarão de ficar disponíveis. Renove a licença para manter o uso contínuo.',
expiresEnterpriseAlert:
'Lembrete: Sua licença da Enterprise Edition expira em {0} dias. Depois disso, todos os recursos da Enterprise Edition deixarão de ficar disponíveis. Renove a licença para manter o uso contínuo.',
expiresProAlert:
'Lembrete: Sua licença da Pro Edition expira em {0} dias. Depois disso, todos os recursos da Pro Edition deixarão de ficar disponíveis. Renove a licença para manter o uso contínuo.',
menu: 'Pro',
upage: 'Construtor de Sites com IA',
proAlert: 'Atualize para comercial para usar este recurso',
+6 -4
View File
@@ -2038,6 +2038,8 @@ const message = {
nodes: 'ноды',
commands: 'Быстрые команды',
opsReport: 'Операционный отчет',
users: 'Управление пользователями',
auth: 'Аутентификация входа',
},
websiteLog: 'Логи веб-сайта',
runLog: 'Логи выполнения',
@@ -4040,10 +4042,10 @@ const message = {
noFail: 'Сбой монтирования не влияет на запуск системы',
},
xpack: {
expiresTrialAlert:
'Дружеское напоминание: ваша пробная коммерческая версия истечет через {0} дней, и все функции коммерческой версии станут недоступны. Пожалуйста, своевременно продлите или обновите до коммерческой версии.',
expiresAlert:
'Дружеское напоминание: ваша лицензия коммерческой версии истечет через {0} дней, и все функции коммерческой версии станут недоступны. Пожалуйста, продлите лицензию вовремя, чтобы обеспечить дальнейшее использование.',
expiresEnterpriseAlert:
'Дружеское напоминание: ваша лицензия Enterprise Edition истечет через {0} дней, и все функции Enterprise Edition станут недоступны. Пожалуйста, продлите лицензию вовремя, чтобы обеспечить дальнейшее использование.',
expiresProAlert:
'Дружеское напоминание: ваша лицензия Pro Edition истечет через {0} дней, и все функции Pro Edition станут недоступны. Пожалуйста, продлите лицензию вовремя, чтобы обеспечить дальнейшее использование.',
menu: 'Рro',
upage: 'AI Конструктор сайтов',
proAlert: 'Обновитесь до коммерческой версии, чтобы использовать эту функцию',
+6 -4
View File
@@ -2047,6 +2047,8 @@ const message = {
nodes: 'Düğüm',
commands: 'Hızlı Komutlar',
opsReport: 'Operasyon Raporu',
users: 'Kullanıcı yönetimi',
auth: 'Giriş kimlik doğrulaması',
},
websiteLog: 'Website logları',
runLog: 'Çalıştırma logları',
@@ -4041,10 +4043,10 @@ const message = {
noFail: 'Bağlama hatası sistem başlangıcını etkilemez',
},
xpack: {
expiresTrialAlert:
'Nazik hatırlatma: Ticari sürüm denemeniz {0} gün içinde sona erecek ve tüm Ticari sürüm özellikleri kullanılamaz hale gelecektir. Lütfen zamanında yenileyin veya tam sürüme yükseltin.',
expiresAlert:
'Nazik hatırlatma: Ticari sürüm lisansınız {0} gün içinde sona erecek ve tüm Ticari sürüm özellikleri kullanılamaz hale gelecektir. Lütfen devam eden kullanım için zamanında yenileyin.',
expiresEnterpriseAlert:
'Nazik hatırlatma: Enterprise Edition lisansınız {0} gün içinde sona erecek ve tüm Enterprise Edition özellikleri kullanılamaz hale gelecektir. Lütfen devam eden kullanım için zamanında yenileyin.',
expiresProAlert:
'Nazik hatırlatma: Pro Edition lisansınız {0} gün içinde sona erecek ve tüm Pro Edition özellikleri kullanılamaz hale gelecektir. Lütfen devam eden kullanım için zamanında yenileyin.',
menu: 'Pro',
upage: 'AI Web Sitesi Oluşturucu',
proAlert: 'Bu özelliği kullanmak için Ticari sürüme yükseltin',
+4 -1
View File
@@ -1892,6 +1892,8 @@ const message = {
nodes: '節點',
commands: '快速指令',
opsReport: '運維報表',
users: '使用者管理',
auth: '登入認證',
},
websiteLog: '網站日誌',
runLog: '執行日誌',
@@ -3681,7 +3683,8 @@ const message = {
noFail: '掛載失敗不影響系統啟動',
},
xpack: {
expiresAlert: '溫馨提醒您的業版許可證將在 {0} 天後到期屆時所有業版功能將無法繼續使用',
expiresEnterpriseAlert: '溫馨提醒您的業版許可證將在 {0} 天後到期屆時所有業版功能將無法繼續使用',
expiresProAlert: '溫馨提醒您的專業版許可證將在 {0} 天後到期屆時所有專業版功能將無法繼續使用',
name: '商業版',
menu: '進階功能',
upage: 'AI 建站',
+4 -1
View File
@@ -1883,6 +1883,8 @@ const message = {
nodes: '节点',
commands: '快速命令',
opsReport: '运维报表',
users: '用户管理',
auth: '登陆认证',
},
websiteLog: '网站日志',
runLog: '运行日志',
@@ -3670,7 +3672,8 @@ const message = {
noFail: '挂载失败不影响系统启动',
},
xpack: {
expiresAlert: '温馨提醒业版试用将于 [{0}] 天后到期届时将停止使用所有业版功能',
expiresEnterpriseAlert: '温馨提醒业版许可证将于 [{0}] 天后到期届时将停止使用所有业版功能',
expiresProAlert: '温馨提醒专业版许可证将于 [{0}] 天后到期届时将停止使用所有专业版功能',
menu: '高级功能',
upage: 'AI 建站',
proAlert: '升级商业版使用此功能',
+1 -1
View File
@@ -248,7 +248,7 @@ onMounted(async () => {
syncCustomAppstore.value = res.data.status === 'Enable';
}
}
if (isOffline.value) {
if (isOffline.value && !isEnterprise.value) {
syncCustomAppstore.value = true;
}
mainHeight.value = window.innerHeight - 380;