"""
-assert P3_OLD in content, "right-col v-if not found"
-content = content.replace(P3_OLD, P3_NEW)
-print("✓ right-col (schedule) gate applied")
-
-# ============= PATCH 4: Add per-card click handlers + 2 section click handlers after triggerEeUpgrade =============
-P4_OLD = """// 工作流统计 6 卡片区块:EE 跳转工作流列表 / CE 弹升级提醒
-const handleWfSectionClick = (e: MouseEvent) => {
- if (hasFeature('WORKFLOW_DAG_ENGINE')) return;
- // 由 ee-gate-section::after 捕获点击,e.target 始终是父 div
- e.stopPropagation();
- triggerEeUpgrade('WORKFLOW_DAG_ENGINE');
-};"""
-P4_NEW = P4_OLD + """
-
-// ---------- Home Approval Section 5 cards - individual gates ----------
-function _guardFeature(code: string, goFn: () => void, evt?: MouseEvent): void {
- if (hasFeature(code)) {
- goFn();
- return;
- }
- if (evt) evt.stopPropagation();
- triggerEeUpgrade(code);
-}
-const onApprovalScriptCardClick = (e?: MouseEvent) => _guardFeature('SCRIPT_APPROVAL_FLOW', goApprovalScript, e);
-const onApprovalWfCardClick = (e?: MouseEvent) => _guardFeature('WORKFLOW_APPROVAL_FLOW', goApprovalWf, e);
-const onApprovalScriptExecCardClick = (e?: MouseEvent) => _guardFeature('OPS_EXECUTION_APPROVAL', goApprovalScriptExec, e);
-const onApprovalCommandExecCardClick = (e?: MouseEvent) => _guardFeature('OPS_EXECUTION_APPROVAL', goApprovalCommandExec, e);
-const onApprovalWfExecCardClick = (e?: MouseEvent) => _guardFeature('OPS_EXECUTION_APPROVAL', goApprovalWfExec, e);
-// Outer approval section background click — only triggers if user clicks the gap
-const handleApprovalSectionClick = (e: MouseEvent) => {
- if (hasFeature('SCRIPT_APPROVAL_FLOW') || hasFeature('WORKFLOW_APPROVAL_FLOW') || hasFeature('OPS_EXECUTION_APPROVAL')) return;
- // CE: 所有审批都没有 → 弹工作流审批 code 做兜底升级提示
- e.stopPropagation();
- triggerEeUpgrade('WORKFLOW_APPROVAL_FLOW');
-};
-
-// ---------- Home 我的定时任务 right-col gate ----------
-const goSchedule = () => router.push('/ops/schedule');
-const handleScheduleColClick = (e: MouseEvent) => {
- if (hasFeature('SCRIPT_TASK_UNIFIED')) return;
- e.stopPropagation();
- triggerEeUpgrade('SCRIPT_TASK_UNIFIED');
-};"""
-
-assert P4_OLD in content, "handleWfSectionClick anchor for new handlers not found"
-content = content.replace(P4_OLD, P4_NEW)
-print("✓ added 5 per-card approval handlers + schedule col handler")
-
-# ============= PATCH 5: CSS — extend existing .ee-gate-section rule to cover .approval-card inside =============
-# Already have `.ee-gate-section` in file; also make per-card .ee-gate-card.is-ee-gate for
-# standalone cards (single approval card inside a section with mixed CE/EE).
-# Append just BEFORE the closing tag of style block (find last .chart-container line as anchor)
-P5_OLD = """}
-}
-
-// ======== 统一 EE 功能区块置灰样式"""
-P5_NEW = """}
-}
-
-// ======== EE Gate 单个卡片置灰(用于 grid-row 内部的独立卡片被 v-if 移除场景,不依赖父 section)========
-.ee-gate-card.is-ee-gate {
- cursor: not-allowed;
- position: relative;
- filter: grayscale(90%) opacity(0.65);
- background: repeating-linear-gradient(45deg, #fafafa, #fafafa 8px, #f4f4f5 8px, #f4f4f5 16px) !important;
- box-shadow: none !important;
-
- &:hover { transform: none !important; box-shadow: 0 2px 8px rgba(0,0,0,0.04) !important; }
- * { user-select: none; }
-}
-
-// ======== 统一 EE 功能区块置灰"""
-assert P5_OLD in content, "CSS anchor for ee-gate-card rule insertion not found"
-content = content.replace(P5_OLD, P5_NEW)
-print("✓ appended .ee-gate-card.is-ee-gate CSS rule")
-
-HOME.write_text(content, encoding='utf-8')
-print(f"\n✓ Home file written, size={len(content)} bytes")
\ No newline at end of file
diff --git a/_patch_home_i18n_2.py b/_patch_home_i18n_2.py
deleted file mode 100644
index a2dd192..0000000
--- a/_patch_home_i18n_2.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-"""Verify i18n paths for the new workflow EE placeholder card.
-
-The home/index.vue uses T = t('message.pages.home.' + key).
-But:
- - charts.workflowStatus / charts.workflowEETip are under
- { message: { pages: { home: { charts: {...} } } } } → ✓ works with T()
- - edition.enterpriseOnlyTag is under { message: { pages: { edition: {...} } } }
- → using T('edition.enterpriseOnlyTag') resolves to
- t('message.pages.home.edition.enterpriseOnlyTag') which does NOT exist.
-
-Fix in template: replace T('edition.enterpriseOnlyTag') with the direct
-t('message.pages.edition.enterpriseOnlyTag') call.
-
-Also fix handleWfCardClick: uses require('element-plus') which breaks in Vite ESM.
-Replace with direct import usage.
-"""
-
-import pathlib
-
-FILE = pathlib.Path("/media/jwj/TOSHIBA EXT/dev/taurus-stack-c/taurus-web/src/views/system/home/index.vue")
-content = FILE.read_text(encoding="utf-8")
-
-# Patch 1: template - T('edition.enterpriseOnlyTag') → t('message.pages.edition.enterpriseOnlyTag')
-OLD_TAG = """{{ T('edition.enterpriseOnlyTag') }}"""
-NEW_TAG = """{{ t('message.pages.edition.enterpriseOnlyTag') }}"""
-assert OLD_TAG in content, "edition tag in template not found"
-content = content.replace(OLD_TAG, NEW_TAG)
-print("✓ Patch 1: template tag uses correct t() path")
-
-# Patch 2: handleWfCardClick - remove require('element-plus') — it was already imported implicitly via
-# the installed app; but safer to import at top. Actually ElMessageBox IS globally available in Element Plus
-# apps, so use (window as any).ElMessageBox fallback, OR reference via direct symbol.
-# Simpler: because the template already uses ElMessageBox elsewhere, let's just reference a
-# getGlobalProperties-style call. But the most robust ESM-compatible way in a setup script
-# is to import at top. Let's verify it's not already imported, and inline the dialog using
-# the same pattern as featureDirective.ts (direct `from 'element-plus'` at top of file).
-#
-# First check if ElMessageBox is already imported in the file top.
-if "import { ElMessageBox } from 'element-plus'" not in content:
- # add import near top
- content = content.replace(
- "import { Box, Select, Loading, Warning, Timer, Close, Remove, Document, Setting, Operation, Share, Connection, CircleCheck } from '@element-plus/icons-vue';",
- "import { Box, Select, Loading, Warning, Timer, Close, Remove, Document, Setting, Operation, Share, Connection, CircleCheck } from '@element-plus/icons-vue';\nimport { ElMessageBox } from 'element-plus';"
- )
- print("✓ Patch 2a: added ElMessageBox top-level import")
-
-OLD_BLOCK = """ const tKey = (key: string, fallback: string) => {
- try {
- const v = t(`message.pages.edition.${key}`);
- if (typeof v === 'string' && v && v !== `message.pages.edition.${key}`) return v;
- } catch (_e) { /* noop */ }
- return fallback;
- };
- const { ElMessageBox } = require('element-plus');
- ElMessageBox.confirm("""
-NEW_BLOCK = """ const tKey = (key: string, fallback: string) => {
- try {
- const v = t(`message.pages.edition.${key}`);
- if (typeof v === 'string' && v && v !== `message.pages.edition.${key}`) return v;
- } catch (_e) { /* noop */ }
- return fallback;
- };
- ElMessageBox.confirm("""
-assert OLD_BLOCK in content, "handleWfCardClick ElMessageBox require block not found"
-content = content.replace(OLD_BLOCK, NEW_BLOCK)
-print("✓ Patch 2b: removed require('element-plus') in favor of top-level import")
-
-FILE.write_text(content, encoding="utf-8")
-print(f"\n✓ File written: {FILE} size={len(content)} bytes")
\ No newline at end of file
diff --git a/_patch_pilot_canary_final.py b/_patch_pilot_canary_final.py
deleted file mode 100644
index e8bfcff..0000000
--- a/_patch_pilot_canary_final.py
+++ /dev/null
@@ -1,216 +0,0 @@
-#!/usr/bin/env python3
-"""Fix OPS_PILOT_CANARY (灰度执行) gate — 3 files."""
-
-import pathlib
-
-ROOT = pathlib.Path("/media/jwj/TOSHIBA EXT/dev/taurus-stack-c/taurus-web/src/views/taurus/ops")
-
-# ============ File 1: ops/script/index.vue — 用户截图的快速执行弹窗 ============
-SCRIPT_INDEX = ROOT / "script" / "index.vue"
-content = SCRIPT_INDEX.read_text(encoding='utf-8')
-
-P1_OLD = """
- {{ t('message.pages.opsScript.strategyPilotTitle') }}
- {{ t('message.pages.opsScript.strategyPilotDesc') }}
- """
-assert P1_OLD in content
-P1_NEW = """
-
-
- {{ t('message.pages.opsScript.strategyPilotTitle') }}
- EE
-
- {{ t('message.pages.opsScript.strategyPilotDesc') }}
-
-
"""
-content = content.replace(P1_OLD, P1_NEW)
-print("✓ script/index.vue: v-if pilot radio → ee-gate wrapper")
-
-# Add helpers + handler after `const hasFeature = (code: string) => editionStore.hasFeature(code);`
-P1b_OLD = "const hasFeature = (code: string) => editionStore.hasFeature(code);\nimport CustomDialog"
-assert P1b_OLD in content
-P1b_NEW = """const hasFeature = (code: string) => editionStore.hasFeature(code);
-
-// ---------- EE 升级拦截通用 helpers ----------
-const eeT = (key: string, fallback: string) => {
-\ttry {
-\t\tconst v = t(`message.pages.edition.${key}`);
-\t\tif (typeof v === 'string' && v && v !== `message.pages.edition.${key}`) return v;
-\t} catch (_e) { /* noop */ }
-\treturn fallback;
-};
-const triggerEeUpgrade = (code: string, customDesc?: string) => {
-\tElMessageBox.confirm(
-\t\tcustomDesc || eeT('enterpriseOnlyDesc', '该功能仅在 Taurus Ops 企业版中提供。升级到企业版即可解锁灰度执行、审批流、通知等全部高级能力。'),
-\t\teeT('enterpriseOnlyTitle', '企业版专属功能'),
-\t\t{ confirmButtonText: eeT('upgradeAction', '立即升级'), cancelButtonText: eeT('dismiss', '稍后再说'), type: 'info', showCancelButton: true, closeOnClickModal: true }
-\t).then(() => window.dispatchEvent(new CustomEvent('taurus:edition-upgrade', { detail: { code } }))).catch(() => {});
-};
-const onPilotRadioClick = (e?: MouseEvent) => {
-\tif (hasFeature('OPS_PILOT_CANARY')) return;
-\tif (e) e.stopPropagation();
-\ttriggerEeUpgrade('OPS_PILOT_CANARY');
-};
-
-import CustomDialog"""
-content = content.replace(P1b_OLD, P1b_NEW)
-print("✓ script/index.vue: added eeT() + triggerEeUpgrade() + onPilotRadioClick handler")
-
-# Also wrap pilot-params template block with the gate guard —
-# only when the form actually has exec_mode === 'pilot' AND feature OK, show the params form.
-# (No change to this — current v-if already OK as secondary conditional, because in CE
-# the pilot radio wrapper never sets the value, so pilot params template never activated.
-# Extra safety: keep as-is because ExecutorPanel.vue watch already resets pilot→parallel.)
-
-SCRIPT_INDEX.write_text(content, encoding='utf-8')
-print(f" → script/index.vue size={SCRIPT_INDEX.stat().st_size}")
-
-# ============ File 2: components/AdvancedOptionsPanel.vue ============
-AOP = ROOT / "components" / "AdvancedOptionsPanel.vue"
-content = AOP.read_text(encoding='utf-8')
-
-P2a_OLD = """
{{ t('message.pages.opsExecution.advanced.pilot') }}"""
-assert P2a_OLD in content
-P2a_NEW = """
-
- {{ t('message.pages.opsExecution.advanced.pilot') }}
- EE
-
- """
-content = content.replace(P2a_OLD, P2a_NEW)
-print("✓ AdvancedOptionsPanel.vue: pilot radio → ee-gate wrapper")
-
-# pilot-params section — just keep it's already conditional on hasFeature. It won't ever show in CE since radio can't be selected. No change.
-
-# Find the script setup block of AdvancedOptionsPanel
-P2b_OLD = """
@@ -176,4 +181,4 @@ const goUpgrade = () => {
justify-content: center;
gap: 12px;
}
-
+
\ No newline at end of file
diff --git a/src/i18n/pages/edition/en.ts b/src/i18n/pages/edition/en.ts
index 79fb1da..acefb33 100644
--- a/src/i18n/pages/edition/en.ts
+++ b/src/i18n/pages/edition/en.ts
@@ -13,6 +13,34 @@ export default {
workflowQuotaBadge: 'Workflows {current}/{limit}',
workflowQuotaUpgradeHint: 'Quota full. Click to view upgrade options.',
+ // === EditionLockedPage (full-page lock overlay) ===
+ lockedSubtitle: 'This feature is available only in Taurus Stack Enterprise Edition',
+ lockedBackBtn: 'Go Back',
+ lockedUpgradeBtn: '🚀 Upgrade to Enterprise',
+ lockedDefaultLabel: 'This page',
+ lockedHighlight1: 'Unlock enterprise-grade core capabilities',
+ lockedHighlight2: 'Enjoy professional technical support',
+ lockedHighlight3: 'Ensure large-scale operations compliance',
+ lockedUpgradeToast: 'Upgrade guide dialog will appear shortly…',
+
+ // === EditionLockedPage page titles (for label prop) ===
+ lockedPageLabels: {
+ ticketCenter: 'Ticket Center',
+ inspectionCenter: 'Inspection Center',
+ knowledgeBase: 'Knowledge Base',
+ taskCenter: 'Task Management Center',
+ toolsCenter: 'Ops Toolbox',
+ workflowApproveList: 'Workflow Approval Center',
+ workflowApprovalInstance: 'Workflow Approval Center',
+ workflowApprovalRule: 'Workflow Approval Rules',
+ programInstallTemplate: 'Enterprise Program Install Template',
+ programInstallConfig: 'Enterprise Program Install Config',
+ programInstallPolicy: 'Enterprise Program Install Policy',
+ scriptCheckRule: 'Script Security Scan Rules',
+ scriptApproval: 'Script Approval Center',
+ executionApproval: 'Execution Approval Center',
+ },
+
// === Upgrade banner ===
ceBannerTitle: 'Using Community Edition?',
ceBannerSubtitle: 'Unlock all Enterprise capabilities',
diff --git a/src/i18n/pages/edition/zh-cn.ts b/src/i18n/pages/edition/zh-cn.ts
index 3fb3a77..57ab986 100644
--- a/src/i18n/pages/edition/zh-cn.ts
+++ b/src/i18n/pages/edition/zh-cn.ts
@@ -13,6 +13,34 @@ export default {
workflowQuotaBadge: '工作流 {current}/{limit}',
workflowQuotaUpgradeHint: '配额已满,点击查看升级方案',
+ // === EditionLockedPage(整页锁遮罩)===
+ lockedSubtitle: '此功能仅 Taurus Stack 企业版(Enterprise Edition)提供',
+ lockedBackBtn: '返回上一页',
+ lockedUpgradeBtn: '🚀 升级到企业版',
+ lockedDefaultLabel: '该页面',
+ lockedHighlight1: '解锁企业级核心能力',
+ lockedHighlight2: '享受专业技术支持',
+ lockedHighlight3: '保障大规模运维合规',
+ lockedUpgradeToast: '升级引导弹窗即将显示…',
+
+ // === EditionLockedPage 各页面标题(label prop 用)===
+ lockedPageLabels: {
+ ticketCenter: '工单中心',
+ inspectionCenter: '巡检中心',
+ knowledgeBase: '知识库',
+ taskCenter: '任务管理中心',
+ toolsCenter: '运维工具箱',
+ workflowApproveList: '作业审批中心',
+ workflowApprovalInstance: '工作流审批中心',
+ workflowApprovalRule: '作业审批规则',
+ programInstallTemplate: '企业程序安装模板',
+ programInstallConfig: '企业程序安装配置',
+ programInstallPolicy: '企业程序安装策略',
+ scriptCheckRule: '脚本安全扫描规则',
+ scriptApproval: '脚本审批中心',
+ executionApproval: '执行审批中心',
+ },
+
// === Upgrade banner ===
ceBannerTitle: '正在使用社区版?',
ceBannerSubtitle: '解锁企业版全部高级能力',
diff --git a/src/i18n/pages/edition/zh-tw.ts b/src/i18n/pages/edition/zh-tw.ts
index bee0450..fb2a6c0 100644
--- a/src/i18n/pages/edition/zh-tw.ts
+++ b/src/i18n/pages/edition/zh-tw.ts
@@ -13,6 +13,34 @@ export default {
workflowQuotaBadge: '工作流 {current}/{limit}',
workflowQuotaUpgradeHint: '配額已滿,點擊查看升級方案',
+ // === EditionLockedPage(整頁鎖遮罩)===
+ lockedSubtitle: '此功能僅 Taurus Stack 企業版(Enterprise Edition)提供',
+ lockedBackBtn: '返回上一頁',
+ lockedUpgradeBtn: '🚀 升級到企業版',
+ lockedDefaultLabel: '該頁面',
+ lockedHighlight1: '解鎖企業級核心能力',
+ lockedHighlight2: '享受專業技術支援',
+ lockedHighlight3: '保障大規模維運合規',
+ lockedUpgradeToast: '升級引導彈窗即將顯示…',
+
+ // === EditionLockedPage 各頁面標題(label prop 用)===
+ lockedPageLabels: {
+ ticketCenter: '工單中心',
+ inspectionCenter: '巡檢中心',
+ knowledgeBase: '知識庫',
+ taskCenter: '任務管理中心',
+ toolsCenter: '維運工具箱',
+ workflowApproveList: '作業審批中心',
+ workflowApprovalInstance: '工作流審批中心',
+ workflowApprovalRule: '作業審批規則',
+ programInstallTemplate: '企業程式安裝範本',
+ programInstallConfig: '企業程式安裝設定',
+ programInstallPolicy: '企業程式安裝策略',
+ scriptCheckRule: '指令碼安全掃描規則',
+ scriptApproval: '指令碼審批中心',
+ executionApproval: '執行審批中心',
+ },
+
// === Upgrade banner ===
ceBannerTitle: '正在使用社群版?',
ceBannerSubtitle: '解鎖企業版全部高級能力',
diff --git a/src/views/taurus/inspection/InspectionCenter.vue b/src/views/taurus/inspection/InspectionCenter.vue
index 872238b..98a2d9f 100644
--- a/src/views/taurus/inspection/InspectionCenter.vue
+++ b/src/views/taurus/inspection/InspectionCenter.vue
@@ -1,5 +1,6 @@
-
+
+
-
+
+
@@ -501,4 +503,4 @@ onMounted(() => {
}
}
}
-
+
\ No newline at end of file
diff --git a/src/views/taurus/supervisor/program-install-config/index.vue b/src/views/taurus/supervisor/program-install-config/index.vue
index cbcfe90..66c12d2 100644
--- a/src/views/taurus/supervisor/program-install-config/index.vue
+++ b/src/views/taurus/supervisor/program-install-config/index.vue
@@ -1,5 +1,5 @@
-
+
@@ -12,10 +12,16 @@ import { ref, onMounted } from 'vue';
import { useFs } from '@fast-crud/fast-crud';
import EditionLockedPage from '/@/components/EditionLockedPage.vue';
import { createCrudOptions } from './crud';
+import { i18n } from '/@/i18n';
+import { useEditionStore } from '/@/editions/index';
+
+// @ts-ignore — vue-i18n type inference chain too deep, runtime is fine
+const t = i18n.global.t;
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
onMounted(() => {
+ if (!useEditionStore().hasFeature('PROGRAM_INSTALL_CONFIG')) return;
crudExpose.doRefresh();
});
-
+
\ No newline at end of file
diff --git a/src/views/taurus/supervisor/program-install-policy/index.vue b/src/views/taurus/supervisor/program-install-policy/index.vue
index bf52157..cff6e95 100644
--- a/src/views/taurus/supervisor/program-install-policy/index.vue
+++ b/src/views/taurus/supervisor/program-install-policy/index.vue
@@ -1,5 +1,5 @@
-
+
@@ -12,10 +12,16 @@ import { ref, onMounted } from 'vue';
import { useFs } from '@fast-crud/fast-crud';
import EditionLockedPage from '/@/components/EditionLockedPage.vue';
import { createCrudOptions } from './crud';
+import { i18n } from '/@/i18n';
+import { useEditionStore } from '/@/editions/index';
+
+// @ts-ignore — vue-i18n type inference chain too deep, runtime is fine
+const t = i18n.global.t;
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
onMounted(() => {
+ if (!useEditionStore().hasFeature('PROGRAM_INSTALL_POLICY')) return;
crudExpose.doRefresh();
});
-
+
\ No newline at end of file
diff --git a/src/views/taurus/supervisor/program-install-template/index.vue b/src/views/taurus/supervisor/program-install-template/index.vue
index dd6bded..4bb46c4 100644
--- a/src/views/taurus/supervisor/program-install-template/index.vue
+++ b/src/views/taurus/supervisor/program-install-template/index.vue
@@ -1,5 +1,5 @@
-
+
@@ -18,6 +18,7 @@ import { createCrudOptions } from './crud';
import ApplyToHosts from './components/ApplyToHosts.vue';
import { ElMessage } from 'element-plus';
import { i18n } from '/@/i18n';
+import { useEditionStore } from '/@/editions/index';
// @ts-ignore — vue-i18n type inference chain too deep, runtime is fine
const t = i18n.global.t;
@@ -41,11 +42,14 @@ const handleApplyToHostsOpen = async (event: any) => {
};
onMounted(() => {
- crudExpose.doRefresh();
+ const store = useEditionStore();
+ if (store.hasFeature('PROGRAM_INSTALL_TEMPLATE')) {
+ crudExpose.doRefresh();
+ }
window.addEventListener('apply-to-hosts-open', handleApplyToHostsOpen);
});
onBeforeUnmount(() => {
window.removeEventListener('apply-to-hosts-open', handleApplyToHostsOpen);
});
-
+
\ No newline at end of file
diff --git a/src/views/taurus/task-center/index.vue b/src/views/taurus/task-center/index.vue
index 301f085..aeb8b32 100644
--- a/src/views/taurus/task-center/index.vue
+++ b/src/views/taurus/task-center/index.vue
@@ -1,5 +1,6 @@
-
+
+
+
diff --git a/src/views/taurus/ticket/TicketCenter.vue b/src/views/taurus/ticket/TicketCenter.vue
index 9e276ed..61ab05a 100644
--- a/src/views/taurus/ticket/TicketCenter.vue
+++ b/src/views/taurus/ticket/TicketCenter.vue
@@ -1,5 +1,6 @@
-