From 4c9c3992abe5ffbf98ad1e44e8e9d4b899af6594 Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 25 Feb 2026 03:40:54 +0300 Subject: [PATCH] fix: RBAC frontend type mismatches and translations - Move RBAC translations from banSystem to admin namespace in all 4 locales - Fix AccessPolicy type: action (string) -> actions (string[]) - Fix AuditLogEntry type to match backend: add status, request_method, request_path, resource_type, user info - Fix AuditLogFilters: resource -> resource_type, add status filter - Handle null details safely in audit log expanded view - Fix policy form submission and display to use actions array --- src/api/rbac.ts | 24 +- src/locales/en.json | 630 +++++++++++++++++------------------ src/locales/fa.json | 537 +++++++++++++++--------------- src/locales/ru.json | 647 ++++++++++++++++++------------------ src/locales/zh.json | 537 +++++++++++++++--------------- src/pages/AdminAuditLog.tsx | 60 ++-- src/pages/AdminPolicies.tsx | 21 +- 7 files changed, 1218 insertions(+), 1238 deletions(-) diff --git a/src/api/rbac.ts b/src/api/rbac.ts index b504064..a0af13b 100644 --- a/src/api/rbac.ts +++ b/src/api/rbac.ts @@ -60,33 +60,37 @@ export interface AccessPolicy { name: string; description: string | null; resource: string; - action: string; + actions: string[]; effect: 'allow' | 'deny'; conditions: Record; priority: number; + role_id: number | null; + role_name: string | null; is_active: boolean; + created_by: number | null; created_at: string; - updated_at: string; } export interface CreatePolicyPayload { name: string; description?: string | null; resource: string; - action: string; + actions: string[]; effect: 'allow' | 'deny'; conditions?: Record; priority?: number; + role_id?: number | null; } export interface UpdatePolicyPayload { name?: string; description?: string | null; resource?: string; - action?: string; + actions?: string[]; effect?: 'allow' | 'deny'; conditions?: Record; priority?: number; + role_id?: number | null; is_active?: boolean; } @@ -94,20 +98,24 @@ export interface AuditLogEntry { id: number; user_id: number; action: string; - resource: string; + resource_type: string | null; resource_id: string | null; - details: Record; + details: Record | null; ip_address: string | null; user_agent: string | null; + status: string; + request_method: string | null; + request_path: string | null; created_at: string; - user_email: string | null; user_first_name: string | null; + user_email: string | null; } export interface AuditLogFilters { user_id?: number; action?: string; - resource?: string; + resource_type?: string; + status?: string; date_from?: string; date_to?: string; offset?: number; diff --git a/src/locales/en.json b/src/locales/en.json index a88e3d6..d0758ab 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -299,7 +299,10 @@ "extraDevicesIncluded_other": "Incl. {{count}} extra dev.", "baseTariff": "Tariff", "perMonth": "/mo", - "summary": "Summary", + "summary": { + "period": "Period: {{label}}", + "traffic": "Traffic: {{gb}} GB" + }, "total": "Total", "purchase": "Purchase", "step": "Step {{current}} of {{total}}", @@ -507,10 +510,6 @@ "label": "Traffic", "selectVolume": "Select traffic volume", "default": "Default: {{label}}" - }, - "summary": { - "period": "Period: {{label}}", - "traffic": "Traffic: {{gb}} GB" } }, "balance": { @@ -520,7 +519,41 @@ "topUpBalance": "Top Up Balance", "enterAmount": "Enter amount", "paymentMethod": "Payment Method", - "paymentMethods": "Payment Methods", + "paymentMethods": { + "yookassa": { + "description": "Pay via YooKassa" + }, + "cryptobot": { + "description": "Pay with cryptocurrency via CryptoBot" + }, + "telegram_stars": { + "description": "Pay with Telegram Stars" + }, + "heleket": { + "description": "Pay with cryptocurrency via Heleket" + }, + "mulenpay": { + "description": "Pay via MulenPay" + }, + "pal24": { + "description": "Pay via PAL24" + }, + "platega": { + "description": "Pay via Platega" + }, + "wata": { + "description": "Pay via Wata" + }, + "cloudpayments": { + "description": "Pay with bank card via CloudPayments" + }, + "freekassa": { + "description": "Pay via FreeKassa" + }, + "tribute": { + "description": "Pay with bank card via Tribute" + } + }, "transactionHistory": "Transaction History", "noTransactions": "No transactions", "date": "Date", @@ -562,41 +595,6 @@ "topUpToComplete": "Top up your balance to complete the purchase", "missing": "Missing", "noPaymentMethods": "No payment methods available", - "paymentMethods": { - "yookassa": { - "description": "Pay via YooKassa" - }, - "cryptobot": { - "description": "Pay with cryptocurrency via CryptoBot" - }, - "telegram_stars": { - "description": "Pay with Telegram Stars" - }, - "heleket": { - "description": "Pay with cryptocurrency via Heleket" - }, - "mulenpay": { - "description": "Pay via MulenPay" - }, - "pal24": { - "description": "Pay via PAL24" - }, - "platega": { - "description": "Pay via Platega" - }, - "wata": { - "description": "Pay via Wata" - }, - "cloudpayments": { - "description": "Pay with bank card via CloudPayments" - }, - "freekassa": { - "description": "Pay via FreeKassa" - }, - "tribute": { - "description": "Pay with bank card via Tribute" - } - }, "pendingPayments": { "title": "Pending Payments", "pay": "Pay", @@ -916,7 +914,7 @@ "total": "Total", "noData": "No traffic data", "loading": "Loading traffic data...", - "noTariff": "\u2014", + "noTariff": "—", "search": "Search by name or username", "allTariffs": "All tariffs", "nodes": "Nodes", @@ -1647,7 +1645,7 @@ "periodsTabHint": "Add periods and prices for the tariff. Users will be able to choose from the added periods.", "addPeriodTitle": "Add period", "daysLabel": "Days", - "priceLabel": "Price (\u20bd)", + "priceLabel": "Price (₽)", "addButton": "Add", "noPeriodsHint": "No added periods. Add at least one period.", "daysShort": "d.", @@ -1697,7 +1695,7 @@ "dailyType": "Daily", "periodType": "Period-based", "dailyPriceLabel": "Price per day", - "currencyPerDay": "\u20bd/day", + "currencyPerDay": "₽/day", "dailyDeductionDesc": "Charged daily from user's balance", "dailyPriceRequired": "Enter a price per day greater than 0", "cannotSave": "Cannot save tariff:", @@ -2535,6 +2533,278 @@ "purchaseDiscount": "Purchase discount", "purchaseDiscountDesc": "Discount for new users" } + }, + "roles": { + "title": "Roles & Permissions", + "subtitle": "Manage admin roles and their permissions", + "createRole": "Create Role", + "noRoles": "No roles found", + "systemBadge": "System", + "inactiveBadge": "Inactive", + "levelLabel": "Level", + "usersCount_one": "{{count}} user", + "usersCount_other": "{{count}} users", + "permissionsCount_one": "{{count}} permission", + "permissionsCount_other": "{{count}} permissions", + "stats": { + "totalRoles": "Total roles", + "active": "Active", + "system": "System" + }, + "actions": { + "edit": "Edit", + "delete": "Delete" + }, + "modal": { + "createTitle": "Create Role", + "editTitle": "Edit Role", + "close": "Close" + }, + "form": { + "name": "Role name", + "namePlaceholder": "e.g. Moderator", + "description": "Description", + "descriptionPlaceholder": "What this role is for...", + "level": "Priority level", + "levelValue": "Level value", + "levelHint": "Higher level = more authority. Users can only manage roles with a lower level than their own.", + "color": "Color", + "presets": "Permission presets", + "permissions": "Permissions", + "toggleSection": "Toggle all {{section}} permissions", + "selectedPermissions_one": "{{count}} permission selected", + "selectedPermissions_other": "{{count}} permissions selected", + "cancel": "Cancel", + "saving": "Saving...", + "save": "Save" + }, + "presets": { + "moderator": "Moderator", + "marketer": "Marketer", + "support": "Support" + }, + "confirm": { + "title": "Delete role?", + "text": "This role will be permanently deleted. Users with this role will lose its permissions.", + "cancel": "Cancel", + "delete": "Delete", + "deleting": "Deleting..." + }, + "errors": { + "loadFailed": "Failed to load roles", + "createFailed": "Failed to create role", + "updateFailed": "Failed to update role", + "nameRequired": "Role name is required", + "deleteFailed": "Failed to delete role" + } + }, + "roleAssign": { + "title": "Role Assignment", + "subtitle": "Assign and manage user roles", + "assignSection": "Assign Role", + "userLabel": "User", + "searchPlaceholder": "Search by name, username, Telegram ID, or email...", + "clearUser": "Clear selected user", + "noUsersFound": "No users found", + "roleLabel": "Role", + "selectRole": "Select a role...", + "expiresLabel": "Expires at (optional)", + "expiresHint": "Leave empty for permanent assignment", + "assignButton": "Assign", + "assigning": "Assigning...", + "assignSuccess": "Role assigned successfully", + "currentAssignments": "Current Assignments", + "totalAssignments_one": "{{count}} assignment total", + "totalAssignments_other": "{{count}} assignments total", + "noAssignments": "No role assignments found", + "unknownUser": "Unknown user", + "system": "System", + "permanent": "Permanent", + "expired": "expired", + "revoke": "Revoke", + "revokeAriaLabel": "Revoke role {{role}} from {{user}}", + "table": { + "user": "User", + "role": "Role", + "assignedBy": "Assigned by", + "assignedAt": "Assigned at", + "expires": "Expires", + "actions": "Actions" + }, + "pagination": { + "showing": "{{from}}-{{to}} of {{total}}", + "prev": "Previous page", + "next": "Next page" + }, + "confirm": { + "title": "Revoke role?", + "text": "This user will immediately lose all permissions associated with this role.", + "cancel": "Cancel", + "revoke": "Revoke", + "revoking": "Revoking..." + }, + "errors": { + "userRequired": "Please select a user", + "roleRequired": "Please select a role", + "assignFailed": "Failed to assign role", + "revokeFailed": "Failed to revoke role" + } + }, + "policies": { + "title": "Access Policies", + "subtitle": "Manage ABAC access control policies", + "createPolicy": "Create Policy", + "noPolicies": "No policies found", + "inactiveBadge": "Inactive", + "effectAllow": "Allow", + "effectDeny": "Deny", + "global": "Global", + "unknownRole": "Unknown role", + "roleLabel": "Role", + "priorityLabel": "Priority", + "stats": { + "total": "Total policies", + "allow": "Allow", + "deny": "Deny", + "active": "Active" + }, + "actions": { + "edit": "Edit", + "delete": "Delete" + }, + "modal": { + "createTitle": "Create Policy", + "editTitle": "Edit Policy", + "close": "Close" + }, + "form": { + "name": "Policy name", + "namePlaceholder": "e.g. Block night access", + "description": "Description", + "descriptionPlaceholder": "What this policy does...", + "effect": "Effect", + "resource": "Resource", + "selectResource": "Select a resource...", + "actions": "Actions", + "role": "Role (optional)", + "globalOption": "Global (all roles)", + "roleHint": "Leave as Global to apply the policy to all roles.", + "priority": "Priority", + "priorityHint": "Higher priority policies are evaluated first. Deny overrides Allow at equal priority.", + "conditions": "Conditions", + "cancel": "Cancel", + "saving": "Saving...", + "save": "Save" + }, + "conditions": { + "timeRange": "Time range", + "timeStart": "Start time", + "timeEnd": "End time", + "ipWhitelist": "IP whitelist", + "ipPlaceholder": "Enter IP and press Enter...", + "removeIp": "Remove {{ip}}", + "ipCount_one": "{{count}} IP", + "ipCount_other": "{{count}} IPs", + "rateLimit": "Rate limit", + "rateLimitValue": "Rate limit value", + "rateValue": "{{count}}/hr", + "perHour": "actions per hour" + }, + "confirm": { + "title": "Delete policy?", + "text": "This policy will be permanently deleted. Access control rules will be recalculated.", + "cancel": "Cancel", + "delete": "Delete", + "deleting": "Deleting..." + }, + "errors": { + "loadFailed": "Failed to load policies", + "createFailed": "Failed to create policy", + "updateFailed": "Failed to update policy", + "nameRequired": "Policy name is required", + "resourceRequired": "Please select a resource", + "actionsRequired": "Please select at least one action", + "deleteFailed": "Failed to delete policy" + } + }, + "auditLog": { + "title": "Audit Log", + "subtitle": "Review system activity and access history", + "back": "Back", + "exportCsv": "Export CSV", + "exportError": "Failed to export", + "exporting": "Exporting...", + "refresh": "Refresh", + "unknownUser": "Unknown user", + "noEntries": "No audit log entries found", + "totalEntries_one": "{{count}} entry", + "totalEntries_other": "{{count}} entries", + "autoRefresh": { + "label": "Auto-refresh", + "tooltip": "Auto-refresh every 30 seconds" + }, + "filters": { + "title": "Filters", + "active": "Active", + "action": "Action", + "actionPlaceholder": "Search by action...", + "resource": "Resource type", + "allResources": "All resources", + "status": "Status", + "allStatuses": "All statuses", + "dateFrom": "Date from", + "dateTo": "Date to", + "apply": "Apply", + "clear": "Clear" + }, + "status": { + "success": "Success", + "denied": "Denied", + "error": "Error" + }, + "resourceTypes": { + "users": "Users", + "tickets": "Tickets", + "roles": "Roles", + "policies": "Policies", + "settings": "Settings", + "broadcasts": "Broadcasts", + "tariffs": "Tariffs", + "servers": "Servers", + "payments": "Payments", + "campaigns": "Campaigns", + "promocodes": "Promo Codes" + }, + "details": { + "userAgent": "User Agent", + "requestPath": "Request Path", + "ipAddress": "IP Address", + "timestamp": "Timestamp", + "before": "Before", + "after": "After", + "fullDetails": "Full Details" + }, + "time": { + "justNow": "Just now", + "minutesAgo_one": "{{count}} min ago", + "minutesAgo_other": "{{count}} min ago", + "hoursAgo_one": "{{count}} hr ago", + "hoursAgo_other": "{{count}} hrs ago", + "daysAgo_one": "{{count}} day ago", + "daysAgo_other": "{{count}} days ago" + }, + "pagination": { + "pageSize": "Per page:", + "pageOf": "{{current}} / {{total}}", + "first": "First page", + "previous": "Previous page", + "next": "Next page", + "last": "Last page" + }, + "errors": { + "loadFailed": "Failed to load audit log", + "retry": "Try again" + } } }, "adminUpdates": { @@ -2929,278 +3199,6 @@ "unhealthy": "Unhealthy", "components": "Components", "uptime": "Uptime" - }, - "roles": { - "title": "Roles & Permissions", - "subtitle": "Manage admin roles and their permissions", - "createRole": "Create Role", - "noRoles": "No roles found", - "systemBadge": "System", - "inactiveBadge": "Inactive", - "levelLabel": "Level", - "usersCount_one": "{{count}} user", - "usersCount_other": "{{count}} users", - "permissionsCount_one": "{{count}} permission", - "permissionsCount_other": "{{count}} permissions", - "stats": { - "totalRoles": "Total roles", - "active": "Active", - "system": "System" - }, - "actions": { - "edit": "Edit", - "delete": "Delete" - }, - "modal": { - "createTitle": "Create Role", - "editTitle": "Edit Role", - "close": "Close" - }, - "form": { - "name": "Role name", - "namePlaceholder": "e.g. Moderator", - "description": "Description", - "descriptionPlaceholder": "What this role is for...", - "level": "Priority level", - "levelValue": "Level value", - "levelHint": "Higher level = more authority. Users can only manage roles with a lower level than their own.", - "color": "Color", - "presets": "Permission presets", - "permissions": "Permissions", - "toggleSection": "Toggle all {{section}} permissions", - "selectedPermissions_one": "{{count}} permission selected", - "selectedPermissions_other": "{{count}} permissions selected", - "cancel": "Cancel", - "saving": "Saving...", - "save": "Save" - }, - "presets": { - "moderator": "Moderator", - "marketer": "Marketer", - "support": "Support" - }, - "confirm": { - "title": "Delete role?", - "text": "This role will be permanently deleted. Users with this role will lose its permissions.", - "cancel": "Cancel", - "delete": "Delete", - "deleting": "Deleting..." - }, - "errors": { - "loadFailed": "Failed to load roles", - "createFailed": "Failed to create role", - "updateFailed": "Failed to update role", - "nameRequired": "Role name is required", - "deleteFailed": "Failed to delete role" - } - }, - "roleAssign": { - "title": "Role Assignment", - "subtitle": "Assign and manage user roles", - "assignSection": "Assign Role", - "userLabel": "User", - "searchPlaceholder": "Search by name, username, Telegram ID, or email...", - "clearUser": "Clear selected user", - "noUsersFound": "No users found", - "roleLabel": "Role", - "selectRole": "Select a role...", - "expiresLabel": "Expires at (optional)", - "expiresHint": "Leave empty for permanent assignment", - "assignButton": "Assign", - "assigning": "Assigning...", - "assignSuccess": "Role assigned successfully", - "currentAssignments": "Current Assignments", - "totalAssignments_one": "{{count}} assignment total", - "totalAssignments_other": "{{count}} assignments total", - "noAssignments": "No role assignments found", - "unknownUser": "Unknown user", - "system": "System", - "permanent": "Permanent", - "expired": "expired", - "revoke": "Revoke", - "revokeAriaLabel": "Revoke role {{role}} from {{user}}", - "table": { - "user": "User", - "role": "Role", - "assignedBy": "Assigned by", - "assignedAt": "Assigned at", - "expires": "Expires", - "actions": "Actions" - }, - "pagination": { - "showing": "{{from}}-{{to}} of {{total}}", - "prev": "Previous page", - "next": "Next page" - }, - "confirm": { - "title": "Revoke role?", - "text": "This user will immediately lose all permissions associated with this role.", - "cancel": "Cancel", - "revoke": "Revoke", - "revoking": "Revoking..." - }, - "errors": { - "userRequired": "Please select a user", - "roleRequired": "Please select a role", - "assignFailed": "Failed to assign role", - "revokeFailed": "Failed to revoke role" - } - }, - "policies": { - "title": "Access Policies", - "subtitle": "Manage ABAC access control policies", - "createPolicy": "Create Policy", - "noPolicies": "No policies found", - "inactiveBadge": "Inactive", - "effectAllow": "Allow", - "effectDeny": "Deny", - "global": "Global", - "unknownRole": "Unknown role", - "roleLabel": "Role", - "priorityLabel": "Priority", - "stats": { - "total": "Total policies", - "allow": "Allow", - "deny": "Deny", - "active": "Active" - }, - "actions": { - "edit": "Edit", - "delete": "Delete" - }, - "modal": { - "createTitle": "Create Policy", - "editTitle": "Edit Policy", - "close": "Close" - }, - "form": { - "name": "Policy name", - "namePlaceholder": "e.g. Block night access", - "description": "Description", - "descriptionPlaceholder": "What this policy does...", - "effect": "Effect", - "resource": "Resource", - "selectResource": "Select a resource...", - "actions": "Actions", - "role": "Role (optional)", - "globalOption": "Global (all roles)", - "roleHint": "Leave as Global to apply the policy to all roles.", - "priority": "Priority", - "priorityHint": "Higher priority policies are evaluated first. Deny overrides Allow at equal priority.", - "conditions": "Conditions", - "cancel": "Cancel", - "saving": "Saving...", - "save": "Save" - }, - "conditions": { - "timeRange": "Time range", - "timeStart": "Start time", - "timeEnd": "End time", - "ipWhitelist": "IP whitelist", - "ipPlaceholder": "Enter IP and press Enter...", - "removeIp": "Remove {{ip}}", - "ipCount_one": "{{count}} IP", - "ipCount_other": "{{count}} IPs", - "rateLimit": "Rate limit", - "rateLimitValue": "Rate limit value", - "rateValue": "{{count}}/hr", - "perHour": "actions per hour" - }, - "confirm": { - "title": "Delete policy?", - "text": "This policy will be permanently deleted. Access control rules will be recalculated.", - "cancel": "Cancel", - "delete": "Delete", - "deleting": "Deleting..." - }, - "errors": { - "loadFailed": "Failed to load policies", - "createFailed": "Failed to create policy", - "updateFailed": "Failed to update policy", - "nameRequired": "Policy name is required", - "resourceRequired": "Please select a resource", - "actionsRequired": "Please select at least one action", - "deleteFailed": "Failed to delete policy" - } - }, - "auditLog": { - "title": "Audit Log", - "subtitle": "Review system activity and access history", - "back": "Back", - "exportCsv": "Export CSV", - "exportError": "Failed to export", - "exporting": "Exporting...", - "refresh": "Refresh", - "unknownUser": "Unknown user", - "noEntries": "No audit log entries found", - "totalEntries_one": "{{count}} entry", - "totalEntries_other": "{{count}} entries", - "autoRefresh": { - "label": "Auto-refresh", - "tooltip": "Auto-refresh every 30 seconds" - }, - "filters": { - "title": "Filters", - "active": "Active", - "action": "Action", - "actionPlaceholder": "Search by action...", - "resource": "Resource type", - "allResources": "All resources", - "status": "Status", - "allStatuses": "All statuses", - "dateFrom": "Date from", - "dateTo": "Date to", - "apply": "Apply", - "clear": "Clear" - }, - "status": { - "success": "Success", - "denied": "Denied", - "error": "Error" - }, - "resourceTypes": { - "users": "Users", - "tickets": "Tickets", - "roles": "Roles", - "policies": "Policies", - "settings": "Settings", - "broadcasts": "Broadcasts", - "tariffs": "Tariffs", - "servers": "Servers", - "payments": "Payments", - "campaigns": "Campaigns", - "promocodes": "Promo Codes" - }, - "details": { - "userAgent": "User Agent", - "requestPath": "Request Path", - "ipAddress": "IP Address", - "timestamp": "Timestamp", - "before": "Before", - "after": "After", - "fullDetails": "Full Details" - }, - "time": { - "justNow": "Just now", - "minutesAgo_one": "{{count}} min ago", - "minutesAgo_other": "{{count}} min ago", - "hoursAgo_one": "{{count}} hr ago", - "hoursAgo_other": "{{count}} hrs ago", - "daysAgo_one": "{{count}} day ago", - "daysAgo_other": "{{count}} days ago" - }, - "pagination": { - "pageSize": "Per page:", - "pageOf": "{{current}} / {{total}}", - "first": "First page", - "previous": "Previous page", - "next": "Next page", - "last": "Last page" - }, - "errors": { - "loadFailed": "Failed to load audit log", - "retry": "Try again" - } } }, "profile": { diff --git a/src/locales/fa.json b/src/locales/fa.json index 42f86c5..b0cc09d 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -280,7 +280,10 @@ "selectServers": "انتخاب سرورها", "selectDevices": "انتخاب دستگاه‌ها", "perDevice": "/ دستگاه", - "summary": "خلاصه", + "summary": { + "period": "دوره: {{label}}", + "traffic": "ترافیک: {{gb}} GB" + }, "total": "جمع کل", "purchase": "خرید", "step": "مرحله {{current}} از {{total}}", @@ -392,10 +395,6 @@ "selectVolume": "انتخاب حجم ترافیک", "default": "پیش‌فرض: {{label}}" }, - "summary": { - "period": "دوره: {{label}}", - "traffic": "ترافیک: {{gb}} GB" - }, "confirmDeleteAllDevices": "همه دستگاه‌ها حذف شوند؟", "confirmDeleteDevice": "این دستگاه حذف شود؟", "currentTariff": "فعلی", @@ -795,7 +794,7 @@ "total": "کل", "noData": "داده ترافیکی موجود نیست", "loading": "در حال بارگذاری داده‌های ترافیک...", - "noTariff": "\u2014", + "noTariff": "—", "search": "جستجو بر اساس نام یا نام کاربری", "allTariffs": "همه تعرفه‌ها", "nodes": "نودها", @@ -2325,6 +2324,269 @@ "lightness": "روشنایی", "quickPresets": "پیش‌تنظیم‌های سریع", "saturation": "اشباع" + }, + "roles": { + "title": "نقش‌ها و مجوزها", + "subtitle": "مدیریت نقش‌های مدیران و مجوزهای آن‌ها", + "createRole": "ایجاد نقش", + "noRoles": "نقشی یافت نشد", + "systemBadge": "سیستمی", + "inactiveBadge": "غیرفعال", + "levelLabel": "سطح", + "usersCount_other": "{{count}} کاربر", + "permissionsCount_other": "{{count}} مجوز", + "stats": { + "totalRoles": "کل نقش‌ها", + "active": "فعال", + "system": "سیستمی" + }, + "actions": { + "edit": "ویرایش", + "delete": "حذف" + }, + "modal": { + "createTitle": "ایجاد نقش", + "editTitle": "ویرایش نقش", + "close": "بستن" + }, + "form": { + "name": "نام نقش", + "namePlaceholder": "مثلاً مدیر محتوا", + "description": "توضیحات", + "descriptionPlaceholder": "کاربرد این نقش...", + "level": "سطح اولویت", + "levelValue": "مقدار سطح", + "levelHint": "سطح بالاتر = اختیارات بیشتر. کاربران فقط می‌توانند نقش‌هایی با سطح پایین‌تر از خود را مدیریت کنند.", + "color": "رنگ", + "presets": "الگوهای مجوز", + "permissions": "مجوزها", + "toggleSection": "تغییر وضعیت تمام مجوزهای {{section}}", + "selectedPermissions_other": "{{count}} مجوز انتخاب شده", + "cancel": "لغو", + "saving": "در حال ذخیره...", + "save": "ذخیره" + }, + "presets": { + "moderator": "مدیر محتوا", + "marketer": "بازاریاب", + "support": "پشتیبانی" + }, + "confirm": { + "title": "حذف نقش؟", + "text": "این نقش برای همیشه حذف خواهد شد. کاربران دارای این نقش مجوزهای آن را از دست خواهند داد.", + "cancel": "لغو", + "delete": "حذف", + "deleting": "در حال حذف..." + }, + "errors": { + "loadFailed": "بارگذاری نقش‌ها ناموفق بود", + "createFailed": "ایجاد نقش ناموفق بود", + "updateFailed": "به‌روزرسانی نقش ناموفق بود", + "nameRequired": "نام نقش الزامی است", + "deleteFailed": "حذف نقش ناموفق بود" + } + }, + "roleAssign": { + "title": "تخصیص نقش", + "subtitle": "تخصیص و مدیریت نقش‌های کاربران", + "assignSection": "تخصیص نقش", + "userLabel": "کاربر", + "searchPlaceholder": "جستجو بر اساس نام، نام کاربری، شناسه تلگرام یا ایمیل...", + "clearUser": "پاک کردن کاربر انتخاب‌شده", + "noUsersFound": "کاربری یافت نشد", + "roleLabel": "نقش", + "selectRole": "یک نقش انتخاب کنید...", + "expiresLabel": "تاریخ انقضا (اختیاری)", + "expiresHint": "برای تخصیص دائمی خالی بگذارید", + "assignButton": "تخصیص", + "assigning": "در حال تخصیص...", + "assignSuccess": "نقش با موفقیت تخصیص داده شد", + "currentAssignments": "تخصیص‌های فعلی", + "totalAssignments_other": "{{count}} تخصیص", + "noAssignments": "تخصیص نقشی یافت نشد", + "unknownUser": "کاربر ناشناخته", + "system": "سیستم", + "permanent": "دائمی", + "expired": "منقضی شده", + "revoke": "لغو", + "revokeAriaLabel": "لغو نقش {{role}} از {{user}}", + "table": { + "user": "کاربر", + "role": "نقش", + "assignedBy": "تخصیص‌دهنده", + "assignedAt": "تاریخ تخصیص", + "expires": "انقضا", + "actions": "عملیات" + }, + "pagination": { + "showing": "{{from}}-{{to}} از {{total}}", + "prev": "صفحه قبل", + "next": "صفحه بعد" + }, + "confirm": { + "title": "لغو نقش؟", + "text": "این کاربر فوراً تمام مجوزهای مرتبط با این نقش را از دست خواهد داد.", + "cancel": "لغو", + "revoke": "لغو نقش", + "revoking": "در حال لغو..." + }, + "errors": { + "userRequired": "لطفاً یک کاربر انتخاب کنید", + "roleRequired": "لطفاً یک نقش انتخاب کنید", + "assignFailed": "تخصیص نقش ناموفق بود", + "revokeFailed": "لغو نقش ناموفق بود" + } + }, + "policies": { + "title": "سیاست‌های دسترسی", + "subtitle": "مدیریت سیاست‌های کنترل دسترسی ABAC", + "createPolicy": "ایجاد سیاست", + "noPolicies": "سیاستی یافت نشد", + "inactiveBadge": "غیرفعال", + "effectAllow": "اجازه", + "effectDeny": "رد", + "global": "سراسری", + "unknownRole": "نقش ناشناخته", + "roleLabel": "نقش", + "priorityLabel": "اولویت", + "stats": { + "total": "کل سیاست‌ها", + "allow": "اجازه", + "deny": "رد", + "active": "فعال" + }, + "actions": { + "edit": "ویرایش", + "delete": "حذف" + }, + "modal": { + "createTitle": "ایجاد سیاست", + "editTitle": "ویرایش سیاست", + "close": "بستن" + }, + "form": { + "name": "نام سیاست", + "namePlaceholder": "مثلاً مسدودسازی دسترسی شبانه", + "description": "توضیحات", + "descriptionPlaceholder": "عملکرد این سیاست...", + "effect": "اثر", + "resource": "منبع", + "selectResource": "یک منبع انتخاب کنید...", + "actions": "عملیات", + "role": "نقش (اختیاری)", + "globalOption": "سراسری (همه نقش‌ها)", + "roleHint": "سراسری بگذارید تا سیاست برای همه نقش‌ها اعمال شود.", + "priority": "اولویت", + "priorityHint": "سیاست‌های با اولویت بالاتر ابتدا ارزیابی می‌شوند. رد در اولویت مساوی بر اجازه غلبه می‌کند.", + "conditions": "شرایط", + "cancel": "لغو", + "saving": "در حال ذخیره...", + "save": "ذخیره" + }, + "conditions": { + "timeRange": "بازه زمانی", + "timeStart": "زمان شروع", + "timeEnd": "زمان پایان", + "ipWhitelist": "لیست سفید IP", + "ipPlaceholder": "IP را وارد کنید و Enter بزنید...", + "removeIp": "حذف {{ip}}", + "ipCount_other": "{{count}} IP", + "rateLimit": "محدودیت نرخ", + "rateLimitValue": "مقدار محدودیت نرخ", + "rateValue": "{{count}}/ساعت", + "perHour": "عملیات در ساعت" + }, + "confirm": { + "title": "حذف سیاست؟", + "text": "این سیاست برای همیشه حذف خواهد شد. قوانین کنترل دسترسی مجدداً محاسبه خواهند شد.", + "cancel": "لغو", + "delete": "حذف", + "deleting": "در حال حذف..." + }, + "errors": { + "loadFailed": "بارگذاری سیاست‌ها ناموفق بود", + "createFailed": "ایجاد سیاست ناموفق بود", + "updateFailed": "به‌روزرسانی سیاست ناموفق بود", + "nameRequired": "نام سیاست الزامی است", + "resourceRequired": "لطفاً یک منبع انتخاب کنید", + "actionsRequired": "لطفاً حداقل یک عملیات انتخاب کنید", + "deleteFailed": "حذف سیاست ناموفق بود" + } + }, + "auditLog": { + "title": "گزارش بازرسی", + "subtitle": "بررسی فعالیت سیستم و تاریخچه دسترسی", + "back": "بازگشت", + "exportCsv": "خروجی CSV", + "exportError": "خطا در خروجی", + "exporting": "در حال خروجی...", + "refresh": "بازخوانی", + "unknownUser": "کاربر ناشناخته", + "noEntries": "رکوردی در گزارش بازرسی یافت نشد", + "totalEntries_other": "{{count}} رکورد", + "autoRefresh": { + "label": "بازخوانی خودکار", + "tooltip": "بازخوانی خودکار هر ۳۰ ثانیه" + }, + "filters": { + "title": "فیلترها", + "active": "فعال", + "action": "عملیات", + "actionPlaceholder": "جستجو بر اساس عملیات...", + "resource": "نوع منبع", + "allResources": "همه منابع", + "status": "وضعیت", + "allStatuses": "همه وضعیت‌ها", + "dateFrom": "از تاریخ", + "dateTo": "تا تاریخ", + "apply": "اعمال", + "clear": "پاک کردن" + }, + "status": { + "success": "موفق", + "denied": "رد شده", + "error": "خطا" + }, + "resourceTypes": { + "users": "کاربران", + "tickets": "تیکت‌ها", + "roles": "نقش‌ها", + "policies": "سیاست‌ها", + "settings": "تنظیمات", + "broadcasts": "پیام‌رسانی", + "tariffs": "تعرفه‌ها", + "servers": "سرورها", + "payments": "پرداخت‌ها", + "campaigns": "کمپین‌ها", + "promocodes": "کدهای تخفیف" + }, + "details": { + "userAgent": "عامل کاربر", + "requestPath": "مسیر درخواست", + "ipAddress": "آدرس IP", + "timestamp": "زمان", + "before": "قبل", + "after": "بعد", + "fullDetails": "جزئیات کامل" + }, + "time": { + "justNow": "همین الان", + "minutesAgo_other": "{{count}} دقیقه پیش", + "hoursAgo_other": "{{count}} ساعت پیش", + "daysAgo_other": "{{count}} روز پیش" + }, + "pagination": { + "pageSize": "در هر صفحه:", + "pageOf": "{{current}} / {{total}}", + "first": "صفحه اول", + "previous": "صفحه قبل", + "next": "صفحه بعد", + "last": "صفحه آخر" + }, + "errors": { + "loadFailed": "بارگذاری گزارش بازرسی ناموفق بود", + "retry": "تلاش مجدد" + } } }, "adminUpdates": { @@ -2792,269 +3054,6 @@ "node": "گره", "requests": "درخواست‌ها", "ipHistory": "تاریخچه IP" - }, - "roles": { - "title": "نقش‌ها و مجوزها", - "subtitle": "مدیریت نقش‌های مدیران و مجوزهای آن‌ها", - "createRole": "ایجاد نقش", - "noRoles": "نقشی یافت نشد", - "systemBadge": "سیستمی", - "inactiveBadge": "غیرفعال", - "levelLabel": "سطح", - "usersCount_other": "{{count}} کاربر", - "permissionsCount_other": "{{count}} مجوز", - "stats": { - "totalRoles": "کل نقش‌ها", - "active": "فعال", - "system": "سیستمی" - }, - "actions": { - "edit": "ویرایش", - "delete": "حذف" - }, - "modal": { - "createTitle": "ایجاد نقش", - "editTitle": "ویرایش نقش", - "close": "بستن" - }, - "form": { - "name": "نام نقش", - "namePlaceholder": "مثلاً مدیر محتوا", - "description": "توضیحات", - "descriptionPlaceholder": "کاربرد این نقش...", - "level": "سطح اولویت", - "levelValue": "مقدار سطح", - "levelHint": "سطح بالاتر = اختیارات بیشتر. کاربران فقط می‌توانند نقش‌هایی با سطح پایین‌تر از خود را مدیریت کنند.", - "color": "رنگ", - "presets": "الگوهای مجوز", - "permissions": "مجوزها", - "toggleSection": "تغییر وضعیت تمام مجوزهای {{section}}", - "selectedPermissions_other": "{{count}} مجوز انتخاب شده", - "cancel": "لغو", - "saving": "در حال ذخیره...", - "save": "ذخیره" - }, - "presets": { - "moderator": "مدیر محتوا", - "marketer": "بازاریاب", - "support": "پشتیبانی" - }, - "confirm": { - "title": "حذف نقش؟", - "text": "این نقش برای همیشه حذف خواهد شد. کاربران دارای این نقش مجوزهای آن را از دست خواهند داد.", - "cancel": "لغو", - "delete": "حذف", - "deleting": "در حال حذف..." - }, - "errors": { - "loadFailed": "بارگذاری نقش‌ها ناموفق بود", - "createFailed": "ایجاد نقش ناموفق بود", - "updateFailed": "به‌روزرسانی نقش ناموفق بود", - "nameRequired": "نام نقش الزامی است", - "deleteFailed": "حذف نقش ناموفق بود" - } - }, - "roleAssign": { - "title": "تخصیص نقش", - "subtitle": "تخصیص و مدیریت نقش‌های کاربران", - "assignSection": "تخصیص نقش", - "userLabel": "کاربر", - "searchPlaceholder": "جستجو بر اساس نام، نام کاربری، شناسه تلگرام یا ایمیل...", - "clearUser": "پاک کردن کاربر انتخاب‌شده", - "noUsersFound": "کاربری یافت نشد", - "roleLabel": "نقش", - "selectRole": "یک نقش انتخاب کنید...", - "expiresLabel": "تاریخ انقضا (اختیاری)", - "expiresHint": "برای تخصیص دائمی خالی بگذارید", - "assignButton": "تخصیص", - "assigning": "در حال تخصیص...", - "assignSuccess": "نقش با موفقیت تخصیص داده شد", - "currentAssignments": "تخصیص‌های فعلی", - "totalAssignments_other": "{{count}} تخصیص", - "noAssignments": "تخصیص نقشی یافت نشد", - "unknownUser": "کاربر ناشناخته", - "system": "سیستم", - "permanent": "دائمی", - "expired": "منقضی شده", - "revoke": "لغو", - "revokeAriaLabel": "لغو نقش {{role}} از {{user}}", - "table": { - "user": "کاربر", - "role": "نقش", - "assignedBy": "تخصیص‌دهنده", - "assignedAt": "تاریخ تخصیص", - "expires": "انقضا", - "actions": "عملیات" - }, - "pagination": { - "showing": "{{from}}-{{to}} از {{total}}", - "prev": "صفحه قبل", - "next": "صفحه بعد" - }, - "confirm": { - "title": "لغو نقش؟", - "text": "این کاربر فوراً تمام مجوزهای مرتبط با این نقش را از دست خواهد داد.", - "cancel": "لغو", - "revoke": "لغو نقش", - "revoking": "در حال لغو..." - }, - "errors": { - "userRequired": "لطفاً یک کاربر انتخاب کنید", - "roleRequired": "لطفاً یک نقش انتخاب کنید", - "assignFailed": "تخصیص نقش ناموفق بود", - "revokeFailed": "لغو نقش ناموفق بود" - } - }, - "policies": { - "title": "سیاست‌های دسترسی", - "subtitle": "مدیریت سیاست‌های کنترل دسترسی ABAC", - "createPolicy": "ایجاد سیاست", - "noPolicies": "سیاستی یافت نشد", - "inactiveBadge": "غیرفعال", - "effectAllow": "اجازه", - "effectDeny": "رد", - "global": "سراسری", - "unknownRole": "نقش ناشناخته", - "roleLabel": "نقش", - "priorityLabel": "اولویت", - "stats": { - "total": "کل سیاست‌ها", - "allow": "اجازه", - "deny": "رد", - "active": "فعال" - }, - "actions": { - "edit": "ویرایش", - "delete": "حذف" - }, - "modal": { - "createTitle": "ایجاد سیاست", - "editTitle": "ویرایش سیاست", - "close": "بستن" - }, - "form": { - "name": "نام سیاست", - "namePlaceholder": "مثلاً مسدودسازی دسترسی شبانه", - "description": "توضیحات", - "descriptionPlaceholder": "عملکرد این سیاست...", - "effect": "اثر", - "resource": "منبع", - "selectResource": "یک منبع انتخاب کنید...", - "actions": "عملیات", - "role": "نقش (اختیاری)", - "globalOption": "سراسری (همه نقش‌ها)", - "roleHint": "سراسری بگذارید تا سیاست برای همه نقش‌ها اعمال شود.", - "priority": "اولویت", - "priorityHint": "سیاست‌های با اولویت بالاتر ابتدا ارزیابی می‌شوند. رد در اولویت مساوی بر اجازه غلبه می‌کند.", - "conditions": "شرایط", - "cancel": "لغو", - "saving": "در حال ذخیره...", - "save": "ذخیره" - }, - "conditions": { - "timeRange": "بازه زمانی", - "timeStart": "زمان شروع", - "timeEnd": "زمان پایان", - "ipWhitelist": "لیست سفید IP", - "ipPlaceholder": "IP را وارد کنید و Enter بزنید...", - "removeIp": "حذف {{ip}}", - "ipCount_other": "{{count}} IP", - "rateLimit": "محدودیت نرخ", - "rateLimitValue": "مقدار محدودیت نرخ", - "rateValue": "{{count}}/ساعت", - "perHour": "عملیات در ساعت" - }, - "confirm": { - "title": "حذف سیاست؟", - "text": "این سیاست برای همیشه حذف خواهد شد. قوانین کنترل دسترسی مجدداً محاسبه خواهند شد.", - "cancel": "لغو", - "delete": "حذف", - "deleting": "در حال حذف..." - }, - "errors": { - "loadFailed": "بارگذاری سیاست‌ها ناموفق بود", - "createFailed": "ایجاد سیاست ناموفق بود", - "updateFailed": "به‌روزرسانی سیاست ناموفق بود", - "nameRequired": "نام سیاست الزامی است", - "resourceRequired": "لطفاً یک منبع انتخاب کنید", - "actionsRequired": "لطفاً حداقل یک عملیات انتخاب کنید", - "deleteFailed": "حذف سیاست ناموفق بود" - } - }, - "auditLog": { - "title": "گزارش بازرسی", - "subtitle": "بررسی فعالیت سیستم و تاریخچه دسترسی", - "back": "بازگشت", - "exportCsv": "خروجی CSV", - "exportError": "خطا در خروجی", - "exporting": "در حال خروجی...", - "refresh": "بازخوانی", - "unknownUser": "کاربر ناشناخته", - "noEntries": "رکوردی در گزارش بازرسی یافت نشد", - "totalEntries_other": "{{count}} رکورد", - "autoRefresh": { - "label": "بازخوانی خودکار", - "tooltip": "بازخوانی خودکار هر ۳۰ ثانیه" - }, - "filters": { - "title": "فیلترها", - "active": "فعال", - "action": "عملیات", - "actionPlaceholder": "جستجو بر اساس عملیات...", - "resource": "نوع منبع", - "allResources": "همه منابع", - "status": "وضعیت", - "allStatuses": "همه وضعیت‌ها", - "dateFrom": "از تاریخ", - "dateTo": "تا تاریخ", - "apply": "اعمال", - "clear": "پاک کردن" - }, - "status": { - "success": "موفق", - "denied": "رد شده", - "error": "خطا" - }, - "resourceTypes": { - "users": "کاربران", - "tickets": "تیکت‌ها", - "roles": "نقش‌ها", - "policies": "سیاست‌ها", - "settings": "تنظیمات", - "broadcasts": "پیام‌رسانی", - "tariffs": "تعرفه‌ها", - "servers": "سرورها", - "payments": "پرداخت‌ها", - "campaigns": "کمپین‌ها", - "promocodes": "کدهای تخفیف" - }, - "details": { - "userAgent": "عامل کاربر", - "requestPath": "مسیر درخواست", - "ipAddress": "آدرس IP", - "timestamp": "زمان", - "before": "قبل", - "after": "بعد", - "fullDetails": "جزئیات کامل" - }, - "time": { - "justNow": "همین الان", - "minutesAgo_other": "{{count}} دقیقه پیش", - "hoursAgo_other": "{{count}} ساعت پیش", - "daysAgo_other": "{{count}} روز پیش" - }, - "pagination": { - "pageSize": "در هر صفحه:", - "pageOf": "{{current}} / {{total}}", - "first": "صفحه اول", - "previous": "صفحه قبل", - "next": "صفحه بعد", - "last": "صفحه آخر" - }, - "errors": { - "loadFailed": "بارگذاری گزارش بازرسی ناموفق بود", - "retry": "تلاش مجدد" - } } }, "blocking": { diff --git a/src/locales/ru.json b/src/locales/ru.json index 54cf9a8..d4f3a12 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -320,7 +320,10 @@ "extraDevicesIncluded_many": "Вкл. {{count}} доп. устр.", "baseTariff": "Тариф", "perMonth": "/мес", - "summary": "Итого", + "summary": { + "period": "Период: {{label}}", + "traffic": "Трафик: {{gb}} ГБ" + }, "total": "К оплате", "purchase": "Купить", "step": "Шаг {{current}} из {{total}}", @@ -535,10 +538,6 @@ "label": "Трафик", "selectVolume": "Выбрать объём трафика", "default": "По умолчанию: {{label}}" - }, - "summary": { - "period": "Период: {{label}}", - "traffic": "Трафик: {{gb}} ГБ" } }, "balance": { @@ -548,7 +547,41 @@ "topUpBalance": "Пополнение баланса", "enterAmount": "Введите сумму", "paymentMethod": "Способ оплаты", - "paymentMethods": "Способы оплаты", + "paymentMethods": { + "yookassa": { + "description": "Оплата через ЮKassa" + }, + "cryptobot": { + "description": "Оплата криптовалютой через CryptoBot" + }, + "telegram_stars": { + "description": "Оплата через Telegram Stars" + }, + "heleket": { + "description": "Оплата криптовалютой через Heleket" + }, + "mulenpay": { + "description": "Оплата через MulenPay" + }, + "pal24": { + "description": "Оплата через PAL24" + }, + "platega": { + "description": "Оплата через Platega" + }, + "wata": { + "description": "Оплата через Wata" + }, + "cloudpayments": { + "description": "Оплата банковской картой через CloudPayments" + }, + "freekassa": { + "description": "Оплата через FreeKassa" + }, + "tribute": { + "description": "Оплата банковской картой через Tribute" + } + }, "transactionHistory": "История операций", "noTransactions": "Нет операций", "date": "Дата", @@ -590,41 +623,6 @@ "topUpToComplete": "Пополните баланс для завершения покупки", "missing": "Не хватает", "noPaymentMethods": "Способы оплаты недоступны", - "paymentMethods": { - "yookassa": { - "description": "Оплата через ЮKassa" - }, - "cryptobot": { - "description": "Оплата криптовалютой через CryptoBot" - }, - "telegram_stars": { - "description": "Оплата через Telegram Stars" - }, - "heleket": { - "description": "Оплата криптовалютой через Heleket" - }, - "mulenpay": { - "description": "Оплата через MulenPay" - }, - "pal24": { - "description": "Оплата через PAL24" - }, - "platega": { - "description": "Оплата через Platega" - }, - "wata": { - "description": "Оплата через Wata" - }, - "cloudpayments": { - "description": "Оплата банковской картой через CloudPayments" - }, - "freekassa": { - "description": "Оплата через FreeKassa" - }, - "tribute": { - "description": "Оплата банковской картой через Tribute" - } - }, "pendingPayments": { "title": "Ожидающие платежи", "pay": "Оплатить", @@ -937,7 +935,7 @@ "total": "Всего", "noData": "Нет данных о трафике", "loading": "Загрузка данных о трафике...", - "noTariff": "\u2014", + "noTariff": "—", "search": "Поиск по имени или юзернейму", "allTariffs": "Все тарифы", "nodes": "Ноды", @@ -2087,7 +2085,7 @@ "name": "Название", "namePlaceholder": "Введите название тарифа", "description": "Описание", - "descriptionPlaceholder": "Введите описание тарифа", + "descriptionPlaceholder": "Краткое описание тарифа", "trafficLimit": "Лимит трафика", "trafficHint": "0 = безлимитный трафик", "deviceLimit": "Лимит устройств", @@ -2155,7 +2153,6 @@ "nameExampleDaily": "Например: Суточный", "nameHint": "От 2 до 50 символов", "descriptionLabel": "Описание", - "descriptionPlaceholder": "Краткое описание тарифа", "trafficLimitLabel": "Лимит трафика", "gbUnit": "ГБ", "trafficLimitHint": "0 = безлимитный трафик", @@ -3082,6 +3079,287 @@ "lightness": "Яркость", "quickPresets": "Быстрые пресеты", "saturation": "Насыщенность" + }, + "roles": { + "title": "Роли и права", + "subtitle": "Управление ролями администраторов и их правами", + "createRole": "Создать роль", + "noRoles": "Роли не найдены", + "systemBadge": "Системная", + "inactiveBadge": "Неактивна", + "levelLabel": "Уровень", + "usersCount_one": "{{count}} пользователь", + "usersCount_few": "{{count}} пользователя", + "usersCount_many": "{{count}} пользователей", + "permissionsCount_one": "{{count}} разрешение", + "permissionsCount_few": "{{count}} разрешения", + "permissionsCount_many": "{{count}} разрешений", + "stats": { + "totalRoles": "Всего ролей", + "active": "Активные", + "system": "Системные" + }, + "actions": { + "edit": "Редактировать", + "delete": "Удалить" + }, + "modal": { + "createTitle": "Создание роли", + "editTitle": "Редактирование роли", + "close": "Закрыть" + }, + "form": { + "name": "Название роли", + "namePlaceholder": "например, Модератор", + "description": "Описание", + "descriptionPlaceholder": "Для чего нужна эта роль...", + "level": "Уровень приоритета", + "levelValue": "Значение уровня", + "levelHint": "Чем выше уровень, тем больше полномочий. Пользователи могут управлять только ролями с уровнем ниже своего.", + "color": "Цвет", + "presets": "Шаблоны прав", + "permissions": "Разрешения", + "toggleSection": "Переключить все права {{section}}", + "selectedPermissions_one": "{{count}} разрешение выбрано", + "selectedPermissions_few": "{{count}} разрешения выбрано", + "selectedPermissions_many": "{{count}} разрешений выбрано", + "cancel": "Отмена", + "saving": "Сохранение...", + "save": "Сохранить" + }, + "presets": { + "moderator": "Модератор", + "marketer": "Маркетолог", + "support": "Поддержка" + }, + "confirm": { + "title": "Удалить роль?", + "text": "Роль будет безвозвратно удалена. Пользователи с этой ролью потеряют её права.", + "cancel": "Отмена", + "delete": "Удалить", + "deleting": "Удаление..." + }, + "errors": { + "loadFailed": "Не удалось загрузить роли", + "createFailed": "Не удалось создать роль", + "updateFailed": "Не удалось обновить роль", + "nameRequired": "Укажите название роли", + "deleteFailed": "Не удалось удалить роль" + } + }, + "roleAssign": { + "title": "Назначение ролей", + "subtitle": "Назначение и управление ролями пользователей", + "assignSection": "Назначить роль", + "userLabel": "Пользователь", + "searchPlaceholder": "Поиск по имени, username, Telegram ID или email...", + "clearUser": "Очистить выбранного пользователя", + "noUsersFound": "Пользователи не найдены", + "roleLabel": "Роль", + "selectRole": "Выберите роль...", + "expiresLabel": "Истекает (необязательно)", + "expiresHint": "Оставьте пустым для бессрочного назначения", + "assignButton": "Назначить", + "assigning": "Назначение...", + "assignSuccess": "Роль успешно назначена", + "currentAssignments": "Текущие назначения", + "totalAssignments_one": "{{count}} назначение", + "totalAssignments_few": "{{count}} назначения", + "totalAssignments_many": "{{count}} назначений", + "noAssignments": "Назначения ролей не найдены", + "unknownUser": "Неизвестный пользователь", + "system": "Система", + "permanent": "Бессрочно", + "expired": "истекло", + "revoke": "Отозвать", + "revokeAriaLabel": "Отозвать роль {{role}} у {{user}}", + "table": { + "user": "Пользователь", + "role": "Роль", + "assignedBy": "Назначил", + "assignedAt": "Дата назначения", + "expires": "Истекает", + "actions": "Действия" + }, + "pagination": { + "showing": "{{from}}-{{to}} из {{total}}", + "prev": "Предыдущая страница", + "next": "Следующая страница" + }, + "confirm": { + "title": "Отозвать роль?", + "text": "Пользователь немедленно потеряет все права, связанные с этой ролью.", + "cancel": "Отмена", + "revoke": "Отозвать", + "revoking": "Отзыв..." + }, + "errors": { + "userRequired": "Выберите пользователя", + "roleRequired": "Выберите роль", + "assignFailed": "Не удалось назначить роль", + "revokeFailed": "Не удалось отозвать роль" + } + }, + "policies": { + "title": "Политики доступа", + "subtitle": "Управление политиками контроля доступа ABAC", + "createPolicy": "Создать политику", + "noPolicies": "Политики не найдены", + "inactiveBadge": "Неактивна", + "effectAllow": "Разрешить", + "effectDeny": "Запретить", + "global": "Глобальная", + "unknownRole": "Неизвестная роль", + "roleLabel": "Роль", + "priorityLabel": "Приоритет", + "stats": { + "total": "Всего политик", + "allow": "Разрешающие", + "deny": "Запрещающие", + "active": "Активные" + }, + "actions": { + "edit": "Редактировать", + "delete": "Удалить" + }, + "modal": { + "createTitle": "Создание политики", + "editTitle": "Редактирование политики", + "close": "Закрыть" + }, + "form": { + "name": "Название политики", + "namePlaceholder": "например, Блокировка ночного доступа", + "description": "Описание", + "descriptionPlaceholder": "Что делает эта политика...", + "effect": "Эффект", + "resource": "Ресурс", + "selectResource": "Выберите ресурс...", + "actions": "Действия", + "role": "Роль (необязательно)", + "globalOption": "Глобальная (все роли)", + "roleHint": "Оставьте Глобальная, чтобы политика применялась ко всем ролям.", + "priority": "Приоритет", + "priorityHint": "Политики с более высоким приоритетом проверяются первыми. Запрет перекрывает Разрешение при равном приоритете.", + "conditions": "Условия", + "cancel": "Отмена", + "saving": "Сохранение...", + "save": "Сохранить" + }, + "conditions": { + "timeRange": "Временной диапазон", + "timeStart": "Время начала", + "timeEnd": "Время окончания", + "ipWhitelist": "Белый список IP", + "ipPlaceholder": "Введите IP и нажмите Enter...", + "removeIp": "Удалить {{ip}}", + "ipCount_one": "{{count}} IP", + "ipCount_few": "{{count}} IP", + "ipCount_many": "{{count}} IP", + "rateLimit": "Лимит запросов", + "rateLimitValue": "Значение лимита запросов", + "rateValue": "{{count}}/ч", + "perHour": "действий в час" + }, + "confirm": { + "title": "Удалить политику?", + "text": "Политика будет безвозвратно удалена. Правила контроля доступа будут пересчитаны.", + "cancel": "Отмена", + "delete": "Удалить", + "deleting": "Удаление..." + }, + "errors": { + "loadFailed": "Не удалось загрузить политики", + "createFailed": "Не удалось создать политику", + "updateFailed": "Не удалось обновить политику", + "nameRequired": "Укажите название политики", + "resourceRequired": "Выберите ресурс", + "actionsRequired": "Выберите хотя бы одно действие", + "deleteFailed": "Не удалось удалить политику" + } + }, + "auditLog": { + "title": "Журнал аудита", + "subtitle": "Просмотр активности системы и истории доступа", + "back": "Назад", + "exportCsv": "Экспорт CSV", + "exportError": "Ошибка экспорта", + "exporting": "Экспорт...", + "refresh": "Обновить", + "unknownUser": "Неизвестный пользователь", + "noEntries": "Записи журнала аудита не найдены", + "totalEntries_one": "{{count}} запись", + "totalEntries_few": "{{count}} записи", + "totalEntries_many": "{{count}} записей", + "autoRefresh": { + "label": "Автообновление", + "tooltip": "Автообновление каждые 30 секунд" + }, + "filters": { + "title": "Фильтры", + "active": "Активны", + "action": "Действие", + "actionPlaceholder": "Поиск по действию...", + "resource": "Тип ресурса", + "allResources": "Все ресурсы", + "status": "Статус", + "allStatuses": "Все статусы", + "dateFrom": "Дата от", + "dateTo": "Дата до", + "apply": "Применить", + "clear": "Сбросить" + }, + "status": { + "success": "Успешно", + "denied": "Отказано", + "error": "Ошибка" + }, + "resourceTypes": { + "users": "Пользователи", + "tickets": "Тикеты", + "roles": "Роли", + "policies": "Политики", + "settings": "Настройки", + "broadcasts": "Рассылки", + "tariffs": "Тарифы", + "servers": "Серверы", + "payments": "Платежи", + "campaigns": "Кампании", + "promocodes": "Промокоды" + }, + "details": { + "userAgent": "User Agent", + "requestPath": "Путь запроса", + "ipAddress": "IP-адрес", + "timestamp": "Время", + "before": "До", + "after": "После", + "fullDetails": "Полные данные" + }, + "time": { + "justNow": "Только что", + "minutesAgo_one": "{{count}} мин назад", + "minutesAgo_few": "{{count}} мин назад", + "minutesAgo_many": "{{count}} мин назад", + "hoursAgo_one": "{{count}} ч назад", + "hoursAgo_few": "{{count}} ч назад", + "hoursAgo_many": "{{count}} ч назад", + "daysAgo_one": "{{count}} день назад", + "daysAgo_few": "{{count}} дня назад", + "daysAgo_many": "{{count}} дней назад" + }, + "pagination": { + "pageSize": "На странице:", + "pageOf": "{{current}} / {{total}}", + "first": "Первая страница", + "previous": "Предыдущая страница", + "next": "Следующая страница", + "last": "Последняя страница" + }, + "errors": { + "loadFailed": "Не удалось загрузить журнал аудита", + "retry": "Попробовать снова" + } } }, "adminUpdates": { @@ -3481,287 +3759,6 @@ "unhealthy": "Проблема", "components": "Компоненты", "uptime": "Аптайм" - }, - "roles": { - "title": "Роли и права", - "subtitle": "Управление ролями администраторов и их правами", - "createRole": "Создать роль", - "noRoles": "Роли не найдены", - "systemBadge": "Системная", - "inactiveBadge": "Неактивна", - "levelLabel": "Уровень", - "usersCount_one": "{{count}} пользователь", - "usersCount_few": "{{count}} пользователя", - "usersCount_many": "{{count}} пользователей", - "permissionsCount_one": "{{count}} разрешение", - "permissionsCount_few": "{{count}} разрешения", - "permissionsCount_many": "{{count}} разрешений", - "stats": { - "totalRoles": "Всего ролей", - "active": "Активные", - "system": "Системные" - }, - "actions": { - "edit": "Редактировать", - "delete": "Удалить" - }, - "modal": { - "createTitle": "Создание роли", - "editTitle": "Редактирование роли", - "close": "Закрыть" - }, - "form": { - "name": "Название роли", - "namePlaceholder": "например, Модератор", - "description": "Описание", - "descriptionPlaceholder": "Для чего нужна эта роль...", - "level": "Уровень приоритета", - "levelValue": "Значение уровня", - "levelHint": "Чем выше уровень, тем больше полномочий. Пользователи могут управлять только ролями с уровнем ниже своего.", - "color": "Цвет", - "presets": "Шаблоны прав", - "permissions": "Разрешения", - "toggleSection": "Переключить все права {{section}}", - "selectedPermissions_one": "{{count}} разрешение выбрано", - "selectedPermissions_few": "{{count}} разрешения выбрано", - "selectedPermissions_many": "{{count}} разрешений выбрано", - "cancel": "Отмена", - "saving": "Сохранение...", - "save": "Сохранить" - }, - "presets": { - "moderator": "Модератор", - "marketer": "Маркетолог", - "support": "Поддержка" - }, - "confirm": { - "title": "Удалить роль?", - "text": "Роль будет безвозвратно удалена. Пользователи с этой ролью потеряют её права.", - "cancel": "Отмена", - "delete": "Удалить", - "deleting": "Удаление..." - }, - "errors": { - "loadFailed": "Не удалось загрузить роли", - "createFailed": "Не удалось создать роль", - "updateFailed": "Не удалось обновить роль", - "nameRequired": "Укажите название роли", - "deleteFailed": "Не удалось удалить роль" - } - }, - "roleAssign": { - "title": "Назначение ролей", - "subtitle": "Назначение и управление ролями пользователей", - "assignSection": "Назначить роль", - "userLabel": "Пользователь", - "searchPlaceholder": "Поиск по имени, username, Telegram ID или email...", - "clearUser": "Очистить выбранного пользователя", - "noUsersFound": "Пользователи не найдены", - "roleLabel": "Роль", - "selectRole": "Выберите роль...", - "expiresLabel": "Истекает (необязательно)", - "expiresHint": "Оставьте пустым для бессрочного назначения", - "assignButton": "Назначить", - "assigning": "Назначение...", - "assignSuccess": "Роль успешно назначена", - "currentAssignments": "Текущие назначения", - "totalAssignments_one": "{{count}} назначение", - "totalAssignments_few": "{{count}} назначения", - "totalAssignments_many": "{{count}} назначений", - "noAssignments": "Назначения ролей не найдены", - "unknownUser": "Неизвестный пользователь", - "system": "Система", - "permanent": "Бессрочно", - "expired": "истекло", - "revoke": "Отозвать", - "revokeAriaLabel": "Отозвать роль {{role}} у {{user}}", - "table": { - "user": "Пользователь", - "role": "Роль", - "assignedBy": "Назначил", - "assignedAt": "Дата назначения", - "expires": "Истекает", - "actions": "Действия" - }, - "pagination": { - "showing": "{{from}}-{{to}} из {{total}}", - "prev": "Предыдущая страница", - "next": "Следующая страница" - }, - "confirm": { - "title": "Отозвать роль?", - "text": "Пользователь немедленно потеряет все права, связанные с этой ролью.", - "cancel": "Отмена", - "revoke": "Отозвать", - "revoking": "Отзыв..." - }, - "errors": { - "userRequired": "Выберите пользователя", - "roleRequired": "Выберите роль", - "assignFailed": "Не удалось назначить роль", - "revokeFailed": "Не удалось отозвать роль" - } - }, - "policies": { - "title": "Политики доступа", - "subtitle": "Управление политиками контроля доступа ABAC", - "createPolicy": "Создать политику", - "noPolicies": "Политики не найдены", - "inactiveBadge": "Неактивна", - "effectAllow": "Разрешить", - "effectDeny": "Запретить", - "global": "Глобальная", - "unknownRole": "Неизвестная роль", - "roleLabel": "Роль", - "priorityLabel": "Приоритет", - "stats": { - "total": "Всего политик", - "allow": "Разрешающие", - "deny": "Запрещающие", - "active": "Активные" - }, - "actions": { - "edit": "Редактировать", - "delete": "Удалить" - }, - "modal": { - "createTitle": "Создание политики", - "editTitle": "Редактирование политики", - "close": "Закрыть" - }, - "form": { - "name": "Название политики", - "namePlaceholder": "например, Блокировка ночного доступа", - "description": "Описание", - "descriptionPlaceholder": "Что делает эта политика...", - "effect": "Эффект", - "resource": "Ресурс", - "selectResource": "Выберите ресурс...", - "actions": "Действия", - "role": "Роль (необязательно)", - "globalOption": "Глобальная (все роли)", - "roleHint": "Оставьте Глобальная, чтобы политика применялась ко всем ролям.", - "priority": "Приоритет", - "priorityHint": "Политики с более высоким приоритетом проверяются первыми. Запрет перекрывает Разрешение при равном приоритете.", - "conditions": "Условия", - "cancel": "Отмена", - "saving": "Сохранение...", - "save": "Сохранить" - }, - "conditions": { - "timeRange": "Временной диапазон", - "timeStart": "Время начала", - "timeEnd": "Время окончания", - "ipWhitelist": "Белый список IP", - "ipPlaceholder": "Введите IP и нажмите Enter...", - "removeIp": "Удалить {{ip}}", - "ipCount_one": "{{count}} IP", - "ipCount_few": "{{count}} IP", - "ipCount_many": "{{count}} IP", - "rateLimit": "Лимит запросов", - "rateLimitValue": "Значение лимита запросов", - "rateValue": "{{count}}/ч", - "perHour": "действий в час" - }, - "confirm": { - "title": "Удалить политику?", - "text": "Политика будет безвозвратно удалена. Правила контроля доступа будут пересчитаны.", - "cancel": "Отмена", - "delete": "Удалить", - "deleting": "Удаление..." - }, - "errors": { - "loadFailed": "Не удалось загрузить политики", - "createFailed": "Не удалось создать политику", - "updateFailed": "Не удалось обновить политику", - "nameRequired": "Укажите название политики", - "resourceRequired": "Выберите ресурс", - "actionsRequired": "Выберите хотя бы одно действие", - "deleteFailed": "Не удалось удалить политику" - } - }, - "auditLog": { - "title": "Журнал аудита", - "subtitle": "Просмотр активности системы и истории доступа", - "back": "Назад", - "exportCsv": "Экспорт CSV", - "exportError": "Ошибка экспорта", - "exporting": "Экспорт...", - "refresh": "Обновить", - "unknownUser": "Неизвестный пользователь", - "noEntries": "Записи журнала аудита не найдены", - "totalEntries_one": "{{count}} запись", - "totalEntries_few": "{{count}} записи", - "totalEntries_many": "{{count}} записей", - "autoRefresh": { - "label": "Автообновление", - "tooltip": "Автообновление каждые 30 секунд" - }, - "filters": { - "title": "Фильтры", - "active": "Активны", - "action": "Действие", - "actionPlaceholder": "Поиск по действию...", - "resource": "Тип ресурса", - "allResources": "Все ресурсы", - "status": "Статус", - "allStatuses": "Все статусы", - "dateFrom": "Дата от", - "dateTo": "Дата до", - "apply": "Применить", - "clear": "Сбросить" - }, - "status": { - "success": "Успешно", - "denied": "Отказано", - "error": "Ошибка" - }, - "resourceTypes": { - "users": "Пользователи", - "tickets": "Тикеты", - "roles": "Роли", - "policies": "Политики", - "settings": "Настройки", - "broadcasts": "Рассылки", - "tariffs": "Тарифы", - "servers": "Серверы", - "payments": "Платежи", - "campaigns": "Кампании", - "promocodes": "Промокоды" - }, - "details": { - "userAgent": "User Agent", - "requestPath": "Путь запроса", - "ipAddress": "IP-адрес", - "timestamp": "Время", - "before": "До", - "after": "После", - "fullDetails": "Полные данные" - }, - "time": { - "justNow": "Только что", - "minutesAgo_one": "{{count}} мин назад", - "minutesAgo_few": "{{count}} мин назад", - "minutesAgo_many": "{{count}} мин назад", - "hoursAgo_one": "{{count}} ч назад", - "hoursAgo_few": "{{count}} ч назад", - "hoursAgo_many": "{{count}} ч назад", - "daysAgo_one": "{{count}} день назад", - "daysAgo_few": "{{count}} дня назад", - "daysAgo_many": "{{count}} дней назад" - }, - "pagination": { - "pageSize": "На странице:", - "pageOf": "{{current}} / {{total}}", - "first": "Первая страница", - "previous": "Предыдущая страница", - "next": "Следующая страница", - "last": "Последняя страница" - }, - "errors": { - "loadFailed": "Не удалось загрузить журнал аудита", - "retry": "Попробовать снова" - } } }, "profile": { diff --git a/src/locales/zh.json b/src/locales/zh.json index 5d2bb4e..a290ac6 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -280,7 +280,10 @@ "selectServers": "选择服务器", "selectDevices": "选择设备", "perDevice": "/ 设备", - "summary": "摘要", + "summary": { + "period": "周期:{{label}}", + "traffic": "流量:{{gb}} GB" + }, "total": "总计", "purchase": "购买", "step": "第 {{current}} 步,共 {{total}} 步", @@ -427,10 +430,6 @@ "resumeBtn": "恢复", "title": "暂停订阅", "willBeCharged": "将扣费" - }, - "summary": { - "period": "周期:{{label}}", - "traffic": "流量:{{gb}} GB" } }, "balance": { @@ -795,7 +794,7 @@ "total": "总计", "noData": "无流量数据", "loading": "正在加载流量数据...", - "noTariff": "\u2014", + "noTariff": "—", "search": "按名称或用户名搜索", "allTariffs": "所有套餐", "nodes": "节点", @@ -2324,6 +2323,269 @@ "toPanel": "到面板", "toPanelDesc": "从机器人导出用户到RemnaWave" } + }, + "roles": { + "title": "角色与权限", + "subtitle": "管理管理员角色及其权限", + "createRole": "创建角色", + "noRoles": "未找到角色", + "systemBadge": "系统", + "inactiveBadge": "未激活", + "levelLabel": "级别", + "usersCount_other": "{{count}} 个用户", + "permissionsCount_other": "{{count}} 个权限", + "stats": { + "totalRoles": "角色总数", + "active": "活跃", + "system": "系统" + }, + "actions": { + "edit": "编辑", + "delete": "删除" + }, + "modal": { + "createTitle": "创建角色", + "editTitle": "编辑角色", + "close": "关闭" + }, + "form": { + "name": "角色名称", + "namePlaceholder": "例如:版主", + "description": "描述", + "descriptionPlaceholder": "此角色的用途...", + "level": "优先级", + "levelValue": "级别值", + "levelHint": "级别越高,权限越大。用户只能管理级别低于自己的角色。", + "color": "颜色", + "presets": "权限预设", + "permissions": "权限", + "toggleSection": "切换所有 {{section}} 权限", + "selectedPermissions_other": "已选择 {{count}} 个权限", + "cancel": "取消", + "saving": "保存中...", + "save": "保存" + }, + "presets": { + "moderator": "版主", + "marketer": "营销员", + "support": "客服" + }, + "confirm": { + "title": "删除角色?", + "text": "此角色将被永久删除。拥有此角色的用户将失去其权限。", + "cancel": "取消", + "delete": "删除", + "deleting": "删除中..." + }, + "errors": { + "loadFailed": "加载角色失败", + "createFailed": "创建角色失败", + "updateFailed": "更新角色失败", + "nameRequired": "请输入角色名称", + "deleteFailed": "删除角色失败" + } + }, + "roleAssign": { + "title": "角色分配", + "subtitle": "分配和管理用户角色", + "assignSection": "分配角色", + "userLabel": "用户", + "searchPlaceholder": "按名称、用户名、Telegram ID或邮箱搜索...", + "clearUser": "清除已选用户", + "noUsersFound": "未找到用户", + "roleLabel": "角色", + "selectRole": "选择角色...", + "expiresLabel": "到期时间(可选)", + "expiresHint": "留空表示永久分配", + "assignButton": "分配", + "assigning": "分配中...", + "assignSuccess": "角色分配成功", + "currentAssignments": "当前分配", + "totalAssignments_other": "共 {{count}} 个分配", + "noAssignments": "未找到角色分配", + "unknownUser": "未知用户", + "system": "系统", + "permanent": "永久", + "expired": "已过期", + "revoke": "撤销", + "revokeAriaLabel": "撤销 {{user}} 的角色 {{role}}", + "table": { + "user": "用户", + "role": "角色", + "assignedBy": "分配者", + "assignedAt": "分配时间", + "expires": "到期", + "actions": "操作" + }, + "pagination": { + "showing": "{{from}}-{{to}} / {{total}}", + "prev": "上一页", + "next": "下一页" + }, + "confirm": { + "title": "撤销角色?", + "text": "该用户将立即失去与此角色相关的所有权限。", + "cancel": "取消", + "revoke": "撤销", + "revoking": "撤销中..." + }, + "errors": { + "userRequired": "请选择用户", + "roleRequired": "请选择角色", + "assignFailed": "角色分配失败", + "revokeFailed": "角色撤销失败" + } + }, + "policies": { + "title": "访问策略", + "subtitle": "管理ABAC访问控制策略", + "createPolicy": "创建策略", + "noPolicies": "未找到策略", + "inactiveBadge": "未激活", + "effectAllow": "允许", + "effectDeny": "拒绝", + "global": "全局", + "unknownRole": "未知角色", + "roleLabel": "角色", + "priorityLabel": "优先级", + "stats": { + "total": "策略总数", + "allow": "允许", + "deny": "拒绝", + "active": "活跃" + }, + "actions": { + "edit": "编辑", + "delete": "删除" + }, + "modal": { + "createTitle": "创建策略", + "editTitle": "编辑策略", + "close": "关闭" + }, + "form": { + "name": "策略名称", + "namePlaceholder": "例如:阻止夜间访问", + "description": "描述", + "descriptionPlaceholder": "此策略的作用...", + "effect": "效果", + "resource": "资源", + "selectResource": "选择资源...", + "actions": "操作", + "role": "角色(可选)", + "globalOption": "全局(所有角色)", + "roleHint": "保留全局以将策略应用于所有角色。", + "priority": "优先级", + "priorityHint": "优先级高的策略先被评估。同等优先级下,拒绝覆盖允许。", + "conditions": "条件", + "cancel": "取消", + "saving": "保存中...", + "save": "保存" + }, + "conditions": { + "timeRange": "时间范围", + "timeStart": "开始时间", + "timeEnd": "结束时间", + "ipWhitelist": "IP白名单", + "ipPlaceholder": "输入IP并按回车...", + "removeIp": "移除 {{ip}}", + "ipCount_other": "{{count}} 个IP", + "rateLimit": "速率限制", + "rateLimitValue": "速率限制值", + "rateValue": "{{count}}/小时", + "perHour": "每小时操作数" + }, + "confirm": { + "title": "删除策略?", + "text": "此策略将被永久删除。访问控制规则将被重新计算。", + "cancel": "取消", + "delete": "删除", + "deleting": "删除中..." + }, + "errors": { + "loadFailed": "加载策略失败", + "createFailed": "创建策略失败", + "updateFailed": "更新策略失败", + "nameRequired": "请输入策略名称", + "resourceRequired": "请选择资源", + "actionsRequired": "请至少选择一个操作", + "deleteFailed": "删除策略失败" + } + }, + "auditLog": { + "title": "审计日志", + "subtitle": "查看系统活动和访问历史", + "back": "返回", + "exportCsv": "导出CSV", + "exportError": "导出失败", + "exporting": "导出中...", + "refresh": "刷新", + "unknownUser": "未知用户", + "noEntries": "未找到审计日志条目", + "totalEntries_other": "{{count}} 条记录", + "autoRefresh": { + "label": "自动刷新", + "tooltip": "每30秒自动刷新" + }, + "filters": { + "title": "筛选", + "active": "已激活", + "action": "操作", + "actionPlaceholder": "按操作搜索...", + "resource": "资源类型", + "allResources": "所有资源", + "status": "状态", + "allStatuses": "所有状态", + "dateFrom": "开始日期", + "dateTo": "结束日期", + "apply": "应用", + "clear": "清除" + }, + "status": { + "success": "成功", + "denied": "拒绝", + "error": "错误" + }, + "resourceTypes": { + "users": "用户", + "tickets": "工单", + "roles": "角色", + "policies": "策略", + "settings": "设置", + "broadcasts": "群发", + "tariffs": "套餐", + "servers": "服务器", + "payments": "支付", + "campaigns": "活动", + "promocodes": "促销码" + }, + "details": { + "userAgent": "用户代理", + "requestPath": "请求路径", + "ipAddress": "IP地址", + "timestamp": "时间戳", + "before": "之前", + "after": "之后", + "fullDetails": "完整详情" + }, + "time": { + "justNow": "刚刚", + "minutesAgo_other": "{{count}} 分钟前", + "hoursAgo_other": "{{count}} 小时前", + "daysAgo_other": "{{count}} 天前" + }, + "pagination": { + "pageSize": "每页:", + "pageOf": "{{current}} / {{total}}", + "first": "第一页", + "previous": "上一页", + "next": "下一页", + "last": "最后一页" + }, + "errors": { + "loadFailed": "加载审计日志失败", + "retry": "重试" + } } }, "adminUpdates": { @@ -2818,269 +3080,6 @@ "node": "节点", "requests": "请求数", "ipHistory": "IP历史" - }, - "roles": { - "title": "角色与权限", - "subtitle": "管理管理员角色及其权限", - "createRole": "创建角色", - "noRoles": "未找到角色", - "systemBadge": "系统", - "inactiveBadge": "未激活", - "levelLabel": "级别", - "usersCount_other": "{{count}} 个用户", - "permissionsCount_other": "{{count}} 个权限", - "stats": { - "totalRoles": "角色总数", - "active": "活跃", - "system": "系统" - }, - "actions": { - "edit": "编辑", - "delete": "删除" - }, - "modal": { - "createTitle": "创建角色", - "editTitle": "编辑角色", - "close": "关闭" - }, - "form": { - "name": "角色名称", - "namePlaceholder": "例如:版主", - "description": "描述", - "descriptionPlaceholder": "此角色的用途...", - "level": "优先级", - "levelValue": "级别值", - "levelHint": "级别越高,权限越大。用户只能管理级别低于自己的角色。", - "color": "颜色", - "presets": "权限预设", - "permissions": "权限", - "toggleSection": "切换所有 {{section}} 权限", - "selectedPermissions_other": "已选择 {{count}} 个权限", - "cancel": "取消", - "saving": "保存中...", - "save": "保存" - }, - "presets": { - "moderator": "版主", - "marketer": "营销员", - "support": "客服" - }, - "confirm": { - "title": "删除角色?", - "text": "此角色将被永久删除。拥有此角色的用户将失去其权限。", - "cancel": "取消", - "delete": "删除", - "deleting": "删除中..." - }, - "errors": { - "loadFailed": "加载角色失败", - "createFailed": "创建角色失败", - "updateFailed": "更新角色失败", - "nameRequired": "请输入角色名称", - "deleteFailed": "删除角色失败" - } - }, - "roleAssign": { - "title": "角色分配", - "subtitle": "分配和管理用户角色", - "assignSection": "分配角色", - "userLabel": "用户", - "searchPlaceholder": "按名称、用户名、Telegram ID或邮箱搜索...", - "clearUser": "清除已选用户", - "noUsersFound": "未找到用户", - "roleLabel": "角色", - "selectRole": "选择角色...", - "expiresLabel": "到期时间(可选)", - "expiresHint": "留空表示永久分配", - "assignButton": "分配", - "assigning": "分配中...", - "assignSuccess": "角色分配成功", - "currentAssignments": "当前分配", - "totalAssignments_other": "共 {{count}} 个分配", - "noAssignments": "未找到角色分配", - "unknownUser": "未知用户", - "system": "系统", - "permanent": "永久", - "expired": "已过期", - "revoke": "撤销", - "revokeAriaLabel": "撤销 {{user}} 的角色 {{role}}", - "table": { - "user": "用户", - "role": "角色", - "assignedBy": "分配者", - "assignedAt": "分配时间", - "expires": "到期", - "actions": "操作" - }, - "pagination": { - "showing": "{{from}}-{{to}} / {{total}}", - "prev": "上一页", - "next": "下一页" - }, - "confirm": { - "title": "撤销角色?", - "text": "该用户将立即失去与此角色相关的所有权限。", - "cancel": "取消", - "revoke": "撤销", - "revoking": "撤销中..." - }, - "errors": { - "userRequired": "请选择用户", - "roleRequired": "请选择角色", - "assignFailed": "角色分配失败", - "revokeFailed": "角色撤销失败" - } - }, - "policies": { - "title": "访问策略", - "subtitle": "管理ABAC访问控制策略", - "createPolicy": "创建策略", - "noPolicies": "未找到策略", - "inactiveBadge": "未激活", - "effectAllow": "允许", - "effectDeny": "拒绝", - "global": "全局", - "unknownRole": "未知角色", - "roleLabel": "角色", - "priorityLabel": "优先级", - "stats": { - "total": "策略总数", - "allow": "允许", - "deny": "拒绝", - "active": "活跃" - }, - "actions": { - "edit": "编辑", - "delete": "删除" - }, - "modal": { - "createTitle": "创建策略", - "editTitle": "编辑策略", - "close": "关闭" - }, - "form": { - "name": "策略名称", - "namePlaceholder": "例如:阻止夜间访问", - "description": "描述", - "descriptionPlaceholder": "此策略的作用...", - "effect": "效果", - "resource": "资源", - "selectResource": "选择资源...", - "actions": "操作", - "role": "角色(可选)", - "globalOption": "全局(所有角色)", - "roleHint": "保留全局以将策略应用于所有角色。", - "priority": "优先级", - "priorityHint": "优先级高的策略先被评估。同等优先级下,拒绝覆盖允许。", - "conditions": "条件", - "cancel": "取消", - "saving": "保存中...", - "save": "保存" - }, - "conditions": { - "timeRange": "时间范围", - "timeStart": "开始时间", - "timeEnd": "结束时间", - "ipWhitelist": "IP白名单", - "ipPlaceholder": "输入IP并按回车...", - "removeIp": "移除 {{ip}}", - "ipCount_other": "{{count}} 个IP", - "rateLimit": "速率限制", - "rateLimitValue": "速率限制值", - "rateValue": "{{count}}/小时", - "perHour": "每小时操作数" - }, - "confirm": { - "title": "删除策略?", - "text": "此策略将被永久删除。访问控制规则将被重新计算。", - "cancel": "取消", - "delete": "删除", - "deleting": "删除中..." - }, - "errors": { - "loadFailed": "加载策略失败", - "createFailed": "创建策略失败", - "updateFailed": "更新策略失败", - "nameRequired": "请输入策略名称", - "resourceRequired": "请选择资源", - "actionsRequired": "请至少选择一个操作", - "deleteFailed": "删除策略失败" - } - }, - "auditLog": { - "title": "审计日志", - "subtitle": "查看系统活动和访问历史", - "back": "返回", - "exportCsv": "导出CSV", - "exportError": "导出失败", - "exporting": "导出中...", - "refresh": "刷新", - "unknownUser": "未知用户", - "noEntries": "未找到审计日志条目", - "totalEntries_other": "{{count}} 条记录", - "autoRefresh": { - "label": "自动刷新", - "tooltip": "每30秒自动刷新" - }, - "filters": { - "title": "筛选", - "active": "已激活", - "action": "操作", - "actionPlaceholder": "按操作搜索...", - "resource": "资源类型", - "allResources": "所有资源", - "status": "状态", - "allStatuses": "所有状态", - "dateFrom": "开始日期", - "dateTo": "结束日期", - "apply": "应用", - "clear": "清除" - }, - "status": { - "success": "成功", - "denied": "拒绝", - "error": "错误" - }, - "resourceTypes": { - "users": "用户", - "tickets": "工单", - "roles": "角色", - "policies": "策略", - "settings": "设置", - "broadcasts": "群发", - "tariffs": "套餐", - "servers": "服务器", - "payments": "支付", - "campaigns": "活动", - "promocodes": "促销码" - }, - "details": { - "userAgent": "用户代理", - "requestPath": "请求路径", - "ipAddress": "IP地址", - "timestamp": "时间戳", - "before": "之前", - "after": "之后", - "fullDetails": "完整详情" - }, - "time": { - "justNow": "刚刚", - "minutesAgo_other": "{{count}} 分钟前", - "hoursAgo_other": "{{count}} 小时前", - "daysAgo_other": "{{count}} 天前" - }, - "pagination": { - "pageSize": "每页:", - "pageOf": "{{current}} / {{total}}", - "first": "第一页", - "previous": "上一页", - "next": "下一页", - "last": "最后一页" - }, - "errors": { - "loadFailed": "加载审计日志失败", - "retry": "重试" - } } } } diff --git a/src/pages/AdminAuditLog.tsx b/src/pages/AdminAuditLog.tsx index c77149f..7f165e7 100644 --- a/src/pages/AdminAuditLog.tsx +++ b/src/pages/AdminAuditLog.tsx @@ -118,27 +118,6 @@ const INITIAL_FILTERS: FiltersState = { // === Utility functions === -function getStatusFromEntry(entry: AuditLogEntry): string { - const details = entry.details; - if (typeof details.status === 'string') return details.status; - if (typeof details.error === 'string') return 'error'; - if (details.denied === true) return 'denied'; - return 'success'; -} - -function getMethodFromEntry(entry: AuditLogEntry): string | null { - const details = entry.details; - if (typeof details.method === 'string') return details.method.toUpperCase(); - return null; -} - -function getRequestPathFromEntry(entry: AuditLogEntry): string | null { - const details = entry.details; - if (typeof details.path === 'string') return details.path; - if (typeof details.request_path === 'string') return details.request_path; - return null; -} - function formatRelativeTime( dateString: string, t: (key: string, opts?: Record) => string, @@ -216,9 +195,9 @@ interface LogEntryCardProps { function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) { const { t } = useTranslation(); - const status = getStatusFromEntry(entry); - const method = getMethodFromEntry(entry); - const requestPath = getRequestPathFromEntry(entry); + const status = entry.status; + const method = entry.request_method?.toUpperCase() ?? null; + const requestPath = entry.request_path; const userName = entry.user_first_name || entry.user_email || t('admin.auditLog.unknownUser'); return ( @@ -254,7 +233,7 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) { {/* Resource */}
- {entry.resource} + {entry.resource_type} {entry.resource_id && ( #{entry.resource_id} @@ -323,7 +302,7 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {
{/* Before/after diff */} - {'before' in entry.details && entry.details.before != null && ( + {entry.details && 'before' in entry.details && entry.details.before != null && (

{t('admin.auditLog.details.before')} @@ -334,7 +313,7 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {

)} - {'after' in entry.details && entry.details.after != null && ( + {entry.details && 'after' in entry.details && entry.details.after != null && (

{t('admin.auditLog.details.after')} @@ -347,14 +326,16 @@ function LogEntryCard({ entry, isExpanded, onToggle }: LogEntryCardProps) {

{/* Full details JSON */} -
-

- {t('admin.auditLog.details.fullDetails')} -

-
-              {JSON.stringify(entry.details, null, 2)}
-            
-
+ {entry.details && ( +
+

+ {t('admin.auditLog.details.fullDetails')} +

+
+                {JSON.stringify(entry.details, null, 2)}
+              
+
+ )} )} @@ -397,7 +378,10 @@ export default function AdminAuditLog() { params.action = appliedFilters.action.trim(); } if (appliedFilters.resource) { - params.resource = appliedFilters.resource; + params.resource_type = appliedFilters.resource; + } + if (appliedFilters.status) { + params.status = appliedFilters.status; } if (appliedFilters.dateFrom) { params.date_from = appliedFilters.dateFrom; @@ -467,7 +451,7 @@ export default function AdminAuditLog() { try { const exportParams: AuditLogFilters = {}; if (appliedFilters.action.trim()) exportParams.action = appliedFilters.action.trim(); - if (appliedFilters.resource) exportParams.resource = appliedFilters.resource; + if (appliedFilters.resource) exportParams.resource_type = appliedFilters.resource; if (appliedFilters.dateFrom) exportParams.date_from = appliedFilters.dateFrom; if (appliedFilters.dateTo) exportParams.date_to = appliedFilters.dateTo; @@ -495,7 +479,7 @@ export default function AdminAuditLog() { // Filter status entries client-side (status is derived from details, not a backend field) const filteredEntries = useMemo(() => { if (!appliedFilters.status) return entries; - return entries.filter((entry) => getStatusFromEntry(entry) === appliedFilters.status); + return entries.filter((entry) => entry.status === appliedFilters.status); }, [entries, appliedFilters.status]); const hasActiveFilters = useMemo(() => { diff --git a/src/pages/AdminPolicies.tsx b/src/pages/AdminPolicies.tsx index 3e11a37..1b92e9b 100644 --- a/src/pages/AdminPolicies.tsx +++ b/src/pages/AdminPolicies.tsx @@ -433,14 +433,7 @@ export default function AdminPolicies() { // Determine the role_id from conditions if present const roleId = typeof policy.conditions.role_id === 'number' ? policy.conditions.role_id : null; - // Parse actions — the API stores a single `action` string which may be comma-separated or - // a single action. Normalize to an array. - const actions = policy.action - ? policy.action - .split(',') - .map((a) => a.trim()) - .filter(Boolean) - : []; + const actions = Array.isArray(policy.actions) ? policy.actions : []; setEditingPolicy(policy); setFormData({ @@ -511,17 +504,16 @@ export default function AdminPolicies() { conditionsPayload.role_id = formData.role_id; } - const actionString = formData.actions.join(','); - if (editingPolicy) { const payload: UpdatePolicyPayload = { name: formData.name.trim(), description: formData.description.trim() || null, effect: formData.effect, resource: formData.resource, - action: actionString, + actions: formData.actions, conditions: conditionsPayload, priority: formData.priority, + role_id: formData.role_id, }; updateMutation.mutate({ id: editingPolicy.id, payload }); } else { @@ -530,9 +522,10 @@ export default function AdminPolicies() { description: formData.description.trim() || null, effect: formData.effect, resource: formData.resource, - action: actionString, + actions: formData.actions, conditions: conditionsPayload, priority: formData.priority, + role_id: formData.role_id, }; createMutation.mutate(payload); } @@ -705,7 +698,9 @@ export default function AdminPolicies() { {policy.resource} : - {policy.action} + + {(policy.actions ?? []).join(', ')} + {/* Info row */}