From 8b43e69bfeb5e30ea634f6313ff8c4c5a8e296ba Mon Sep 17 00:00:00 2001 From: "web@ppanel" Date: Wed, 2 Apr 2025 00:12:02 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(admin):=20Add=20application=20?= =?UTF-8?q?and=20rule=20management=20entries=20to=20localization=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{subscribe/app => application}/config.tsx | 0 .../{subscribe/app => application}/form.tsx | 0 .../app/table.tsx => application/page.tsx} | 0 .../app/dashboard/rules/import-yaml-rules.tsx | 215 + apps/admin/app/dashboard/rules/page.tsx | 212 + apps/admin/app/dashboard/rules/rule-form.tsx | 232 + apps/admin/app/dashboard/subscribe/page.tsx | 5 - apps/admin/config/navs.ts | 20 +- apps/admin/locales/cs-CZ/menu.json | 2 + apps/admin/locales/cs-CZ/rules.json | 43 + apps/admin/locales/de-DE/menu.json | 2 + apps/admin/locales/de-DE/rules.json | 43 + apps/admin/locales/en-US/menu.json | 2 + apps/admin/locales/en-US/rules.json | 43 + apps/admin/locales/es-ES/menu.json | 2 + apps/admin/locales/es-ES/rules.json | 43 + apps/admin/locales/es-MX/menu.json | 2 + apps/admin/locales/es-MX/rules.json | 43 + apps/admin/locales/fa-IR/menu.json | 2 + apps/admin/locales/fa-IR/rules.json | 43 + apps/admin/locales/fi-FI/menu.json | 2 + apps/admin/locales/fi-FI/rules.json | 43 + apps/admin/locales/fr-FR/menu.json | 2 + apps/admin/locales/fr-FR/rules.json | 43 + apps/admin/locales/hi-IN/menu.json | 2 + apps/admin/locales/hi-IN/rules.json | 43 + apps/admin/locales/hu-HU/menu.json | 2 + apps/admin/locales/hu-HU/rules.json | 43 + apps/admin/locales/ja-JP/menu.json | 2 + apps/admin/locales/ja-JP/rules.json | 43 + apps/admin/locales/ko-KR/menu.json | 2 + apps/admin/locales/ko-KR/rules.json | 43 + apps/admin/locales/no-NO/menu.json | 2 + apps/admin/locales/no-NO/rules.json | 43 + apps/admin/locales/pl-PL/menu.json | 2 + apps/admin/locales/pl-PL/rules.json | 43 + apps/admin/locales/pt-BR/menu.json | 2 + apps/admin/locales/pt-BR/rules.json | 43 + apps/admin/locales/request.ts | 1 + apps/admin/locales/ro-RO/menu.json | 2 + apps/admin/locales/ro-RO/rules.json | 43 + apps/admin/locales/ru-RU/menu.json | 2 + apps/admin/locales/ru-RU/rules.json | 43 + apps/admin/locales/th-TH/menu.json | 2 + apps/admin/locales/th-TH/rules.json | 43 + apps/admin/locales/tr-TR/menu.json | 2 + apps/admin/locales/tr-TR/rules.json | 43 + apps/admin/locales/uk-UA/menu.json | 2 + apps/admin/locales/uk-UA/rules.json | 43 + apps/admin/locales/vi-VN/menu.json | 2 + apps/admin/locales/vi-VN/rules.json | 43 + apps/admin/locales/zh-CN/menu.json | 2 + apps/admin/locales/zh-CN/rules.json | 43 + apps/admin/locales/zh-HK/menu.json | 2 + apps/admin/locales/zh-HK/rules.json | 43 + apps/admin/package.json | 2 + apps/admin/public/template/rules.yml | 9971 +++++++++++++++++ apps/admin/services/admin/server.ts | 11 + apps/admin/services/admin/typings.d.ts | 17 +- apps/admin/services/common/typings.d.ts | 5 +- apps/user/services/common/typings.d.ts | 5 +- apps/user/services/user/typings.d.ts | 5 +- bun.lockb | Bin 615672 -> 615968 bytes .../ui/src/custom-components/upload-image.tsx | 15 + 64 files changed, 11730 insertions(+), 21 deletions(-) rename apps/admin/app/dashboard/{subscribe/app => application}/config.tsx (100%) rename apps/admin/app/dashboard/{subscribe/app => application}/form.tsx (100%) rename apps/admin/app/dashboard/{subscribe/app/table.tsx => application/page.tsx} (100%) create mode 100644 apps/admin/app/dashboard/rules/import-yaml-rules.tsx create mode 100644 apps/admin/app/dashboard/rules/page.tsx create mode 100644 apps/admin/app/dashboard/rules/rule-form.tsx create mode 100644 apps/admin/locales/cs-CZ/rules.json create mode 100644 apps/admin/locales/de-DE/rules.json create mode 100644 apps/admin/locales/en-US/rules.json create mode 100644 apps/admin/locales/es-ES/rules.json create mode 100644 apps/admin/locales/es-MX/rules.json create mode 100644 apps/admin/locales/fa-IR/rules.json create mode 100644 apps/admin/locales/fi-FI/rules.json create mode 100644 apps/admin/locales/fr-FR/rules.json create mode 100644 apps/admin/locales/hi-IN/rules.json create mode 100644 apps/admin/locales/hu-HU/rules.json create mode 100644 apps/admin/locales/ja-JP/rules.json create mode 100644 apps/admin/locales/ko-KR/rules.json create mode 100644 apps/admin/locales/no-NO/rules.json create mode 100644 apps/admin/locales/pl-PL/rules.json create mode 100644 apps/admin/locales/pt-BR/rules.json create mode 100644 apps/admin/locales/ro-RO/rules.json create mode 100644 apps/admin/locales/ru-RU/rules.json create mode 100644 apps/admin/locales/th-TH/rules.json create mode 100644 apps/admin/locales/tr-TR/rules.json create mode 100644 apps/admin/locales/uk-UA/rules.json create mode 100644 apps/admin/locales/vi-VN/rules.json create mode 100644 apps/admin/locales/zh-CN/rules.json create mode 100644 apps/admin/locales/zh-HK/rules.json create mode 100644 apps/admin/public/template/rules.yml diff --git a/apps/admin/app/dashboard/subscribe/app/config.tsx b/apps/admin/app/dashboard/application/config.tsx similarity index 100% rename from apps/admin/app/dashboard/subscribe/app/config.tsx rename to apps/admin/app/dashboard/application/config.tsx diff --git a/apps/admin/app/dashboard/subscribe/app/form.tsx b/apps/admin/app/dashboard/application/form.tsx similarity index 100% rename from apps/admin/app/dashboard/subscribe/app/form.tsx rename to apps/admin/app/dashboard/application/form.tsx diff --git a/apps/admin/app/dashboard/subscribe/app/table.tsx b/apps/admin/app/dashboard/application/page.tsx similarity index 100% rename from apps/admin/app/dashboard/subscribe/app/table.tsx rename to apps/admin/app/dashboard/application/page.tsx diff --git a/apps/admin/app/dashboard/rules/import-yaml-rules.tsx b/apps/admin/app/dashboard/rules/import-yaml-rules.tsx new file mode 100644 index 0000000..7ac6434 --- /dev/null +++ b/apps/admin/app/dashboard/rules/import-yaml-rules.tsx @@ -0,0 +1,215 @@ +'use client'; + +import { createRuleGroup } from '@/services/admin/server'; +import { Button } from '@workspace/ui/components/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@workspace/ui/components/dialog'; +import { Label } from '@workspace/ui/components/label'; +import { Progress } from '@workspace/ui/components/progress'; +import { Textarea } from '@workspace/ui/components/textarea'; +import { Icon } from '@workspace/ui/custom-components/icon'; +import yaml from 'js-yaml'; +import { useTranslations } from 'next-intl'; +import { useRef, useState } from 'react'; +import { toast } from 'sonner'; + +interface ImportYamlRulesProps { + onImportSuccess?: () => void; +} + +interface RuleGroup { + name: string; + rules: string[]; +} + +export default function ImportYamlRules({ onImportSuccess }: ImportYamlRulesProps) { + const t = useTranslations('rules'); + const [open, setOpen] = useState(false); + const [loading, setLoading] = useState(false); + const [yamlContent, setYamlContent] = useState(''); + const [importProgress, setImportProgress] = useState(0); + const [importTotal, setImportTotal] = useState(0); + const [analyzing, setAnalyzing] = useState(false); + const fileInputRef = useRef(null); + + const handleFileUpload = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (event) => { + const content = event.target?.result as string; + setYamlContent(content); + setOpen(true); + }; + reader.readAsText(file); + + e.target.value = ''; + }; + + const processRule = (rule: string): { policyGroup: string; cleanRule: string } | null => { + const parts = rule.split(','); + + if (parts.length === 1) { + return null; + } + + let policyGroup = 'default'; + let cleanRule = rule; + + if (parts.length >= 3) { + const thirdPart = parts[2]?.trim(); + if (thirdPart) { + policyGroup = thirdPart; + } + } + + cleanRule = parts.slice(0, 2).join(','); + + return { policyGroup, cleanRule }; + }; + + const parseRulesIntoGroups = (rules: string[]): Record => { + const groups: Record = {}; + + for (const rule of rules) { + if (!rule.trim()) continue; + + const result = processRule(rule); + if (result === null) continue; + + const { policyGroup, cleanRule } = result; + if (!groups[policyGroup]) { + groups[policyGroup] = []; + } + groups[policyGroup].push(cleanRule); + } + + return groups; + }; + + const handleImport = async () => { + if (!yamlContent) { + toast.error(t('pleaseUploadFile')); + return; + } + + setLoading(true); + setAnalyzing(true); + try { + const parsedYaml = yaml.load(yamlContent) as any; + + if (!parsedYaml || !parsedYaml.rules) { + throw new Error(t('invalidYamlFormat')); + } + + let allRules: string[] = []; + if (Array.isArray(parsedYaml.rules)) { + allRules = parsedYaml.rules.filter((rule: string) => rule.trim()); + } + + if (allRules.length === 0) { + throw new Error(t('noValidRules')); + } + + const ruleGroups = parseRulesIntoGroups(allRules); + const groups = Object.entries(ruleGroups).map(([name, rules]) => ({ + name, + rules, + })); + + setImportTotal(groups.length); + setAnalyzing(false); + + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + if (!group?.name || !group?.rules.length) continue; + await createRuleGroup({ + name: group.name, + rules: group?.rules.join('\n'), + enable: false, + tags: [], + icon: '', + }); + setImportProgress(i + 1); + } + + toast.success(t('importSuccess')); + setOpen(false); + setYamlContent(''); + setImportProgress(0); + setImportTotal(0); + onImportSuccess?.(); + } catch (error) { + console.error('Import error:', error); + toast.error(error instanceof Error ? error.message : t('importFailed')); + } finally { + setLoading(false); + setAnalyzing(false); + } + }; + + return ( + <> + + + + + + + {t('importYamlRules')} + {t('importYamlDescription')} + +
+ {yamlContent && ( +
+ +