From 3bb1a61c3fabb2427a44d556317021df3f19c85a Mon Sep 17 00:00:00 2001 From: sakuradairong Date: Wed, 24 Jun 2026 07:44:15 +0000 Subject: [PATCH] i18n: localize Peers, Activity, Setup Keys modules - PeerActionCell.tsx: replace ~20 hardcoded strings with t() calls (approve, bypass compliance, session expiration, SSH, delete actions) - Activity module: internationalize 5 files including ActivityDescription.tsx with ~104 activity event description templates using t.rich() - Setup Keys: replace ~18 hardcoded strings in SetupKeyActionCell and SetupKeyGroupsCell with translation keys - Add Chinese translations with natural grammar and consistent terminology --- package-lock.json | 136 +++- src/i18n/messages/en.ts | 313 ++++++++ src/i18n/messages/zh.ts | 312 ++++++++ src/modules/activity/ActivityDescription.tsx | 696 +++++++++++++----- src/modules/activity/ActivityEntryRow.tsx | 6 +- .../activity/ActivityEventCodeSelector.tsx | 10 +- src/modules/activity/ActivityTable.tsx | 2 +- src/modules/activity/ActivityTypePicker.tsx | 9 +- .../activity/UsersDropdownSelector.tsx | 20 +- src/modules/peers/PeerActionCell.tsx | 108 +-- src/modules/setup-keys/SetupKeyActionCell.tsx | 43 +- src/modules/setup-keys/SetupKeyGroupsCell.tsx | 16 +- 12 files changed, 1359 insertions(+), 312 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5dc3c73..2aba9a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "@tanstack/react-table": "^8.10.7", "@types/crypto-js": "^4.2.2", "@types/d3": "^7.4.3", - "@types/lodash": "^4.14.200", + "@types/lodash": "4.17.24", "@types/node": "20.10.6", "@types/react": "^19", "@types/react-dom": "^19", @@ -47,6 +47,7 @@ "classnames": "^2.5.1", "clsx": "^2.0.0", "cmdk": "^1.1.1", + "cross-env": "^7.0.3", "crypto-js": "^4.2.0", "d3": "^7.9.0", "date-fns": "^2.30.0", @@ -55,16 +56,18 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-simple-import-sort": "^10.0.0", "framer-motion": "^12.29.2", - "ip-address": "^10.1.0", + "ip-address": "^10.2.0", "ip-cidr": "^3.1.0", - "js-cookie": "^3.0.5", - "lodash": "^4.17.23", + "js-cookie": "^3.0.7", + "lodash": "4.18.1", "lucide-react": "^0.566.0", "next": "16.1.7", "next-intl": "^4.13.0", "next-themes": "^0.2.1", "punycode": "^2.3.1", "react": "^19.2.4", + "react-chartjs-2": "^5.3.0", + "react-confetti-explosion": "^3.0.3", "react-day-picker": "^9.13.0", "react-dom": "^19.2.4", "react-ga4": "^2.1.0", @@ -84,6 +87,7 @@ }, "devDependencies": { "@faker-js/faker": "^9.5.1", + "@playwright/test": "^1.52.0", "@types/chroma-js": "^3.1.1", "@types/js-cookie": "^3.0.6", "eslint": "^9.39.1", @@ -1738,6 +1742,22 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -3562,9 +3582,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", "license": "MIT" }, "node_modules/@types/node": { @@ -4850,6 +4870,24 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6760,9 +6798,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -7258,13 +7296,10 @@ } }, "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "license": "MIT", - "engines": { - "node": ">=14" - } + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -8084,6 +8119,53 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/po-parser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz", @@ -8330,6 +8412,26 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", + "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-confetti-explosion": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-confetti-explosion/-/react-confetti-explosion-3.0.3.tgz", + "integrity": "sha512-ow5ns/1ttzXsIlbbfJmWJNiyQK8lTHBL6lRSUXGaK44K/3NIMngR57Ja96l+D6txTeFhfe0BfXGvORMxhtRDng==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-day-picker": { "version": "9.13.0", "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz", diff --git a/src/i18n/messages/en.ts b/src/i18n/messages/en.ts index 78bb026..7fed6a0 100644 --- a/src/i18n/messages/en.ts +++ b/src/i18n/messages/en.ts @@ -472,6 +472,25 @@ export default { "Access other networks without installing NetBird on every resource.", remoteJobsDesc: "Remotely trigger actions such as debug bundles or other tasks on this peer, without requiring CLI access.", + revoke: "Revoke", + bypassCompliance: "Bypass Compliance", + bypassComplianceConfirmTitle: "Bypass compliance for '{name}'?", + bypassComplianceConfirmDescription: + "This will override the compliance check and allow this peer to connect. The bypass will be automatically removed if the device becomes compliant.", + bypassComplianceSuccess: "Compliance bypassed for {name}", + bypassComplianceSuccessDescription: + "This peer can now connect to other peers.", + bypassComplianceLoading: "Bypassing compliance...", + revokeBypass: "Revoke Bypass", + revokeBypassConfirmTitle: "Revoke compliance bypass for '{name}'?", + revokeBypassConfirmDescription: + "This peer will be subject to normal compliance validation. If still non-compliant, it will lose network access.", + revokeBypassSuccess: "Compliance bypass revoked", + revokeBypassSuccessDescription: + "Peer {name} is now subject to normal compliance validation.", + revokeBypassLoading: "Revoking compliance bypass...", + bypassTooltip: + "Bypass {integrationName} compliance check and allow this peer to connect. The bypass is automatically removed when the device becomes compliant.", }, policies: { title: "Policies", @@ -1552,6 +1571,25 @@ export default { groups: "Groups", usage: "Usage", lastUsedOn: "Last used on", + // SetupKeyActionCell + revoke: "Revoke", + openActionsMenu: "Open actions menu", + revokeConfirmTitle: "Revoke '{name}'?", + revokeConfirmDescription: + "Are you sure you want to revoke the setup key? This action cannot be undone.", + revokeSuccessDescription: "Setup key was successfully revoked", + revokeLoading: "Revoking the setup key...", + deleteConfirmTitle: "Delete '{name}'?", + deleteConfirmDescription: + "Are you sure you want to delete the setup key? This action cannot be undone.", + deleteSuccessDescription: "Setup key was successfully deleted", + deleteLoading: "Deleting the setup key...", + // SetupKeyGroupsCell + autoAssignedGroups: "Auto-assigned Groups", + autoAssignedGroupsDescription: + "These groups will be automatically assigned to peers enrolled with this key", + groupsSavedDescription: "Groups of the setup key were successfully saved", + groupsSaving: "Saving the groups of the setup key...", }, activity: { title: "Activity", @@ -1569,6 +1607,281 @@ export default { ipAddress: "IP Address", details: "Details", code: "Code", + + // UI strings used across activity components + allEventTypes: "All Event Types", + allUsers: "All Users", + includeAllUsers: "Include all users", + searchEvent: "Search event...", + searchUser: "Search user...", + noUsersAvailable: "No users available to select.", + noUsersMatching: "There are no users matching your search.", + system: "System", + external: "External", + serviceUser: "Service User", + typeCount: "{count} types", + eventCount: "{count} Event(s)", + activityCode: "Activity Code", + meta: "Meta", + from: "from", + unknown: "Unknown", + + // Activity event descriptions (used by ActivityDescription.tsx) + + // Setup Key + desc_setupkey_revoke: + "Setup-Key {name} with key {key} was revoked", + desc_setupkey_delete: + "Setup-Key {name} with key {key} was deleted", + desc_setupkey_add: + "Setup-Key {name} with key {key} was created", + desc_peer_setupkey_add: + "Peer {name} from was added with the NetBird IP {ip} using the setup key {setup_key_name}", + desc_setupkey_group_delete: + "Group {group} was removed from the {setupkey} setup key", + desc_setupkey_group_add: + "Group {group} was added to the {setupkey} setup key", + + // Dashboard + desc_dashboard_login: + "{username} logged in to the dashboard", + + // Policy + desc_policy_update: "Policy {name} has been updated", + desc_policy_delete: "Policy {name} was deleted", + desc_policy_add: "Policy {name} was created", + + // Route + desc_route_delete_domains: + "Route {name} with the domain(s) {domains} was deleted", + desc_route_delete_range: + "Route {name} with the range {network_range} was deleted", + desc_route_update_domains: + "Route {name} with the domain(s) {domains} was updated", + desc_route_update_range: + "Route {name} with the range {network_range} was updated", + desc_route_add_domains: + "Route {name} with the domain(s) {domains} was created", + desc_route_add_range: + "Route {name} with the range {network_range} was created", + + // User / Peer + desc_user_peer_delete: + "Peer {name} from with NetBird IP {ip} was deleted", + desc_user_peer_add: + "Peer {name} from was added with the NetBird IP {ip}", + desc_user_peer_update: + "Peer {name} from with NetBird IP {ip} was updated", + desc_user_join: "User {username} joined NetBird", + desc_user_invite: + "{username} {email} was invited.", + desc_user_create: + "{username} {email} was created by {initiator}", + desc_user_group_add: + "Group {group} was added to user {username}", + desc_user_block: + "User {username} {email} was blocked", + desc_user_unblock: + "User {username} {email} was unblocked", + desc_user_delete: + "User {username} {email} was deleted", + desc_user_group_delete: + "Group {group} was removed from user {username} {email}", + desc_user_role_update: + "Role {role} was updated of user {username} {email}", + desc_user_approve: + "User {username} {email} was approved", + desc_user_reject: + "User {username} {email} was rejected", + desc_user_password_change: + "Password was changed for user {username} {email}", + + // Invite Link + desc_user_invite_link_create: + "Invite link was created for {username} {email}", + desc_user_invite_link_accept: + "Invite link was accepted by {username} {email}", + desc_user_invite_link_regenerate: + "Invite link was regenerated for {username} {email}", + desc_user_invite_link_delete: + "Invite link was deleted for {username} {email}", + + // Service User + desc_service_user_create: "Service user {name} was created", + desc_service_user_delete: "Service user {name} was deleted", + + // Peer + desc_peer_group_delete: + "Group {group} was removed from the peer with the NetBird IP {peer_ip}", + desc_peer_group_add: + "Group {group} was added to the peer with the NetBird IP {peer_ip}", + desc_peer_login_expire: + "Login of the peer {name} is expired", + desc_peer_ssh_disable: + "SSH Server of peer {name} was disabled", + desc_peer_ssh_enable: + "SSH Server of peer {name} was enabled", + desc_peer_login_expiration_disable: + "Login expiration of peer {name} was disabled", + desc_peer_login_expiration_enable: + "Login expiration of peer {name} was enabled", + desc_peer_rename: + "Peer with the NetBird IP {ip} was renamed to {name}", + desc_peer_approve: + "Peer with the NetBird IP {ip} was approved", + desc_peer_ip_update: + "Peer {name} IP address was updated from {old_ip} to {ip}", + desc_peer_user_add: + "Peer {name} from was added with the NetBird IP {ip}", + + // Group + desc_group_add: "Group {name} was created", + desc_group_delete: "Group {name} was deleted", + desc_group_update: + "Group {old_name} was renamed to {new_name}", + + // Account + desc_account_create: "{initiator} created an account", + desc_account_setting_peer_login_expiration_update: + "Global login expiration was updated", + desc_account_setting_peer_login_expiration_enable: + "Global login expiration was enabled", + desc_account_setting_peer_login_expiration_disable: + "Global login expiration was disabled", + desc_account_network_range_update: + "Account network range was updated from {old_network_range} to {new_network_range}", + + // Nameserver + desc_nameserver_group_add: "Nameserver {name} was added", + desc_nameserver_group_delete: "Nameserver {name} was deleted", + desc_nameserver_group_update: "Nameserver {name} was updated", + + // Personal Access Token + desc_personal_access_token_create: + "Access token {name} for user {username} was created", + desc_personal_access_token_delete: + "Access token {name} for user {username} was deleted", + + // Integration + desc_integration_create_platform: + "{platform} integration created", + desc_integration_create: "Integration created", + desc_integration_delete_platform: + "{platform} integration deleted", + desc_integration_delete: "Integration deleted", + desc_integration_update_platform: + "{platform} integration updated", + desc_integration_update: "Integration updated", + + // DNS + desc_dns_setting_disabled_management_group_add: + "Group {group} was added to disabled DNS group setting", + desc_dns_setting_disabled_management_group_delete: + "Group {group} was removed from disabled DNS group setting", + + // Posture Checks + desc_posture_check_updated: + "Posture check {name} was updated", + desc_posture_check_created: + "Posture check {name} was created", + desc_posture_check_deleted: + "Posture check {name} was deleted", + desc_transferred_owner_role: "Owner role was transferred", + + // EDR / Integrated Validator + desc_integrated_validator_api_created: + "{platform} integration created", + desc_integrated_validator_api_updated: + "{platform} integration updated", + desc_integrated_validator_api_deleted: + "{platform} integration deleted", + desc_integrated_validator_host_check_approved: + "Peer approved by {platform} integration", + desc_integrated_validator_host_check_denied: + "Peer rejected by {platform} integration", + desc_integrated_validator_peer_compliance_bypassed: + "Peer {name} with the NetBird IP {ip} compliance bypassed for {platform} integration{original_reason}", + desc_integrated_validator_peer_compliance_bypass_revoked: + "Peer {name} with the NetBird IP {ip} compliance bypass revoked for {platform} integration", + desc_compliance_original_reason: + " (original non-compliant reason: {reason})", + + // Resource + desc_resource_group_add: + "Group {resource_name} added to resource {name}", + desc_resource_group_delete: + "Group {resource_name} removed from resource {name}", + + // Reverse Proxy (peer expose) + desc_service_peer_expose: + "Peer {peer_name} exposed service {domain} with auth {auth}", + desc_service_peer_unexpose: + "Peer {peer_name} unexposed service {domain}", + desc_service_peer_expose_expire: + "Service {domain} exposed by peer {peer_name} was removed due to renewal expiration", + + // Networks + desc_network_resource_create: + "Resource {name} created for network {network_name}", + desc_network_resource_update: + "Resource {name} updated for network {network_name}", + desc_network_resource_delete: + "Resource {name} deleted from network {network_name}", + desc_network_router_create: + "Routing peer created for network {network_name}", + desc_network_router_delete: + "Routing peer deleted from network {network_name}", + desc_network_router_update: + "Routing peer updated from network {network_name}", + desc_network_create: + "Network with name {name} created", + desc_network_delete: + "Network with name {name} deleted", + desc_network_update: + "Network with name {name} updated", + + // Jobs + desc_peer_job_create: + "Remote job {job_type} created for peer {for_peer_name}", + + // Flow Settings + desc_account_settings_extra_flow_group_remove: + "Limit traffic event group {group_name} removed", + desc_account_settings_extra_flow_group_add: + "Limit traffic event group {group_name} added", + + // Identity Provider + desc_identityprovider_create: + "Identity provider {name} was created", + desc_identityprovider_update: + "Identity provider {name} was updated", + desc_identityprovider_delete: + "Identity provider {name} was deleted", + + // Service (proxy cluster) + desc_service_create: + "Service {domain} in cluster {proxy_cluster} was created with authentication {auth}", + desc_service_update: + "Service {domain} in cluster {proxy_cluster} was updated with authentication {auth}", + desc_service_delete: + "Service {domain} in cluster {proxy_cluster} was deleted", + + // Reseller / Distributor + desc_reseller_msp_created: + "Customer {msp_name} with domain {msp_domain} was created", + desc_reseller_activated: "Distributor account was activated", + desc_reseller_msp_deleted: + "Customer {msp_name} with domain {msp_domain} was deleted", + desc_reseller_msp_unlinked: + "Customer {msp_name} with domain {msp_domain} was unlinked", + desc_reseller_msp_invite_requested: + "Invite requested for customer {msp_name} with domain {msp_domain}", + desc_reseller_msp_invite_accepted: + "Invite accepted by customer {msp_name} with domain {msp_domain}", + desc_reseller_msp_invite_declined: + "Invite declined by customer {msp_name} with domain {msp_domain}", + desc_reseller_msp_updated: + "Customer {msp_name} with domain {msp_domain} was updated", }, controlCenter: { title: "Control Center", diff --git a/src/i18n/messages/zh.ts b/src/i18n/messages/zh.ts index ca7a331..5df8356 100644 --- a/src/i18n/messages/zh.ts +++ b/src/i18n/messages/zh.ts @@ -456,6 +456,24 @@ export default { networkRoutesDesc: "无需在每个资源上安装 NetBird 即可访问其他网络。", remoteJobsDesc: "远程触发此节点上的操作,如调试包或其他任务,无需 CLI 访问。", + revoke: "撤销", + bypassCompliance: "绕过合规检查", + bypassComplianceConfirmTitle: "绕过节点 '{name}' 的合规检查?", + bypassComplianceConfirmDescription: + "此操作将覆盖合规检查,允许此节点进行连接。当设备恢复合规时,绕过将自动移除。", + bypassComplianceSuccess: "已为 {name} 绕过合规检查", + bypassComplianceSuccessDescription: "此节点现在可以连接到其他节点。", + bypassComplianceLoading: "正在绕过合规检查...", + revokeBypass: "撤销绕过", + revokeBypassConfirmTitle: "撤销节点 '{name}' 的合规绕过?", + revokeBypassConfirmDescription: + "此节点将接受正常的合规验证。如果仍然不合规,它将失去网络访问权限。", + revokeBypassSuccess: "合规绕过已撤销", + revokeBypassSuccessDescription: + "节点 {name} 现在接受正常的合规验证。", + revokeBypassLoading: "正在撤销合规绕过...", + bypassTooltip: + "绕过 {integrationName} 合规检查并允许此节点连接。当设备恢复合规时,绕过将自动移除。", }, policies: { title: "策略", @@ -1459,6 +1477,25 @@ export default { groups: "组", usage: "使用情况", lastUsedOn: "上次使用于", + // SetupKeyActionCell + revoke: "撤销", + openActionsMenu: "打开操作菜单", + revokeConfirmTitle: "撤销安装密钥「{name}」?", + revokeConfirmDescription: + "确定要撤销此安装密钥吗?此操作无法撤销。", + revokeSuccessDescription: "安装密钥已成功撤销", + revokeLoading: "正在撤销安装密钥...", + deleteConfirmTitle: "删除安装密钥「{name}」?", + deleteConfirmDescription: + "确定要删除此安装密钥吗?此操作无法撤销。", + deleteSuccessDescription: "安装密钥已成功删除", + deleteLoading: "正在删除安装密钥...", + // SetupKeyGroupsCell + autoAssignedGroups: "自动分配的组", + autoAssignedGroupsDescription: + "使用此密钥注册的节点将自动分配这些组", + groupsSavedDescription: "安装密钥的组已成功保存", + groupsSaving: "正在保存安装密钥的组...", }, activity: { title: "活动", @@ -1476,6 +1513,281 @@ export default { ipAddress: "IP 地址", details: "详情", code: "代码", + + // UI strings used across activity components + allEventTypes: "所有事件类型", + allUsers: "所有用户", + includeAllUsers: "包含所有用户", + searchEvent: "搜索事件...", + searchUser: "搜索用户...", + noUsersAvailable: "没有可选择的用户。", + noUsersMatching: "没有符合条件的用户。", + system: "系统", + external: "外部", + serviceUser: "服务用户", + typeCount: "{count} 种类型", + eventCount: "{count} 个事件", + activityCode: "活动代码", + meta: "元数据", + from: "来自", + unknown: "未知", + + // Activity event descriptions (used by ActivityDescription.tsx) + + // Setup Key + desc_setupkey_revoke: + "安装密钥 {name}(密钥:{key})已被撤销", + desc_setupkey_delete: + "安装密钥 {name}(密钥:{key})已被删除", + desc_setupkey_add: + "安装密钥 {name}(密钥:{key})已创建", + desc_peer_setupkey_add: + "节点 {name}已通过 NetBird IP {ip} 添加,使用安装密钥 {setup_key_name}", + desc_setupkey_group_delete: + "组 {group} 已从安装密钥 {setupkey} 中移除", + desc_setupkey_group_add: + "组 {group} 已添加到安装密钥 {setupkey}", + + // Dashboard + desc_dashboard_login: + "{username} 登录到仪表板", + + // Policy + desc_policy_update: "策略 {name} 已更新", + desc_policy_delete: "策略 {name} 已被删除", + desc_policy_add: "策略 {name} 已创建", + + // Route + desc_route_delete_domains: + "路由 {name}(域名:{domains})已被删除", + desc_route_delete_range: + "路由 {name}(范围:{network_range})已被删除", + desc_route_update_domains: + "路由 {name}(域名:{domains})已更新", + desc_route_update_range: + "路由 {name}(范围:{network_range})已更新", + desc_route_add_domains: + "路由 {name}(域名:{domains})已创建", + desc_route_add_range: + "路由 {name}(范围:{network_range})已创建", + + // User / Peer + desc_user_peer_delete: + "节点 {name}(NetBird IP:{ip})已被删除", + desc_user_peer_add: + "节点 {name}已通过 NetBird IP {ip} 添加", + desc_user_peer_update: + "节点 {name}(NetBird IP:{ip})已更新", + desc_user_join: "用户 {username} 加入了 NetBird", + desc_user_invite: + "{username} {email} 已被邀请。", + desc_user_create: + "{username} {email} 已由 {initiator} 创建", + desc_user_group_add: + "组 {group} 已添加到用户 {username}", + desc_user_block: + "用户 {username} {email} 已被阻止", + desc_user_unblock: + "用户 {username} {email} 已解除阻止", + desc_user_delete: + "用户 {username} {email} 已被删除", + desc_user_group_delete: + "组 {group} 已从用户 {username} {email} 中移除", + desc_user_role_update: + "用户 {username} {email} 的角色已更新为 {role}", + desc_user_approve: + "用户 {username} {email} 已获批准", + desc_user_reject: + "用户 {username} {email} 已被拒绝", + desc_user_password_change: + "用户 {username} {email} 的密码已更改", + + // Invite Link + desc_user_invite_link_create: + "已为 {username} {email} 创建邀请链接", + desc_user_invite_link_accept: + "邀请链接已被 {username} {email} 接受", + desc_user_invite_link_regenerate: + "已为 {username} {email} 重新生成邀请链接", + desc_user_invite_link_delete: + "已删除 {username} {email} 的邀请链接", + + // Service User + desc_service_user_create: "服务用户 {name} 已创建", + desc_service_user_delete: "服务用户 {name} 已被删除", + + // Peer + desc_peer_group_delete: + "组 {group} 已从 NetBird IP 为 {peer_ip} 的节点中移除", + desc_peer_group_add: + "组 {group} 已添加到 NetBird IP 为 {peer_ip} 的节点", + desc_peer_login_expire: + "节点 {name} 的登录已过期", + desc_peer_ssh_disable: + "节点 {name} 的 SSH 服务器已禁用", + desc_peer_ssh_enable: + "节点 {name} 的 SSH 服务器已启用", + desc_peer_login_expiration_disable: + "节点 {name} 的登录过期已禁用", + desc_peer_login_expiration_enable: + "节点 {name} 的登录过期已启用", + desc_peer_rename: + "NetBird IP 为 {ip} 的节点已重命名为 {name}", + desc_peer_approve: + "NetBird IP 为 {ip} 的节点已获批准", + desc_peer_ip_update: + "节点 {name} 的 IP 地址已从 {old_ip} 更新为 {ip}", + desc_peer_user_add: + "节点 {name}已通过 NetBird IP {ip} 添加", + + // Group + desc_group_add: "组 {name} 已创建", + desc_group_delete: "组 {name} 已被删除", + desc_group_update: + "组 {old_name} 已重命名为 {new_name}", + + // Account + desc_account_create: "{initiator} 创建了账户", + desc_account_setting_peer_login_expiration_update: + "全局登录过期已更新", + desc_account_setting_peer_login_expiration_enable: + "全局登录过期已启用", + desc_account_setting_peer_login_expiration_disable: + "全局登录过期已禁用", + desc_account_network_range_update: + "账户网络范围已从 {old_network_range} 更新为 {new_network_range}", + + // Nameserver + desc_nameserver_group_add: "名称服务器 {name} 已添加", + desc_nameserver_group_delete: "名称服务器 {name} 已被删除", + desc_nameserver_group_update: "名称服务器 {name} 已更新", + + // Personal Access Token + desc_personal_access_token_create: + "用户 {username} 的访问令牌 {name} 已创建", + desc_personal_access_token_delete: + "用户 {username} 的访问令牌 {name} 已被删除", + + // Integration + desc_integration_create_platform: + "{platform} 集成已创建", + desc_integration_create: "集成已创建", + desc_integration_delete_platform: + "{platform} 集成已被删除", + desc_integration_delete: "集成已被删除", + desc_integration_update_platform: + "{platform} 集成已更新", + desc_integration_update: "集成已更新", + + // DNS + desc_dns_setting_disabled_management_group_add: + "组 {group} 已添加到禁用的 DNS 组设置", + desc_dns_setting_disabled_management_group_delete: + "组 {group} 已从禁用的 DNS 组设置中移除", + + // Posture Checks + desc_posture_check_updated: + "姿态检查 {name} 已更新", + desc_posture_check_created: + "姿态检查 {name} 已创建", + desc_posture_check_deleted: + "姿态检查 {name} 已被删除", + desc_transferred_owner_role: "所有者角色已转让", + + // EDR / Integrated Validator + desc_integrated_validator_api_created: + "{platform} 集成已创建", + desc_integrated_validator_api_updated: + "{platform} 集成已更新", + desc_integrated_validator_api_deleted: + "{platform} 集成已被删除", + desc_integrated_validator_host_check_approved: + "节点已通过 {platform} 集成批准", + desc_integrated_validator_host_check_denied: + "节点已被 {platform} 集成拒绝", + desc_integrated_validator_peer_compliance_bypassed: + "NetBird IP 为 {ip} 的节点 {name} 已绕过 {platform} 的合规检查{original_reason}", + desc_integrated_validator_peer_compliance_bypass_revoked: + "NetBird IP 为 {ip} 的节点 {name} 的合规绕过已对 {platform} 撤销", + desc_compliance_original_reason: + "(原始不合规原因:{reason})", + + // Resource + desc_resource_group_add: + "组 {resource_name} 已添加到资源 {name}", + desc_resource_group_delete: + "组 {resource_name} 已从资源 {name} 中移除", + + // Reverse Proxy (peer expose) + desc_service_peer_expose: + "节点 {peer_name} 暴露了服务 {domain},认证状态:{auth}", + desc_service_peer_unexpose: + "节点 {peer_name} 取消了服务 {domain} 的暴露", + desc_service_peer_expose_expire: + "节点 {peer_name} 暴露的服务 {domain} 因续期过期已被移除", + + // Networks + desc_network_resource_create: + "资源 {name} 已为网络 {network_name} 创建", + desc_network_resource_update: + "资源 {name} 已为网络 {network_name} 更新", + desc_network_resource_delete: + "资源 {name} 已从网络 {network_name} 中删除", + desc_network_router_create: + "已为网络 {network_name} 创建路由节点", + desc_network_router_delete: + "已从网络 {network_name} 中删除路由节点", + desc_network_router_update: + "已更新网络 {network_name} 的路由节点", + desc_network_create: + "名为 {name} 的网络已创建", + desc_network_delete: + "名为 {name} 的网络已被删除", + desc_network_update: + "名为 {name} 的网络已更新", + + // Jobs + desc_peer_job_create: + "已为节点 {for_peer_name} 创建远程任务 {job_type}", + + // Flow Settings + desc_account_settings_extra_flow_group_remove: + "流量事件限制组 {group_name} 已移除", + desc_account_settings_extra_flow_group_add: + "流量事件限制组 {group_name} 已添加", + + // Identity Provider + desc_identityprovider_create: + "身份提供者 {name} 已创建", + desc_identityprovider_update: + "身份提供者 {name} 已更新", + desc_identityprovider_delete: + "身份提供者 {name} 已被删除", + + // Service (proxy cluster) + desc_service_create: + "集群 {proxy_cluster} 中的服务 {domain} 已创建,认证方式:{auth}", + desc_service_update: + "集群 {proxy_cluster} 中的服务 {domain} 已更新,认证方式:{auth}", + desc_service_delete: + "集群 {proxy_cluster} 中的服务 {domain} 已被删除", + + // Reseller / Distributor + desc_reseller_msp_created: + "客户 {msp_name}(域名:{msp_domain})已创建", + desc_reseller_activated: "分销商账户已激活", + desc_reseller_msp_deleted: + "客户 {msp_name}(域名:{msp_domain})已被删除", + desc_reseller_msp_unlinked: + "客户 {msp_name}(域名:{msp_domain})已取消关联", + desc_reseller_msp_invite_requested: + "已为客户 {msp_name}(域名:{msp_domain})请求邀请", + desc_reseller_msp_invite_accepted: + "客户 {msp_name}(域名:{msp_domain})已接受邀请", + desc_reseller_msp_invite_declined: + "客户 {msp_name}(域名:{msp_domain})已拒绝邀请", + desc_reseller_msp_updated: + "客户 {msp_name}(域名:{msp_domain})已更新", }, controlCenter: { title: "控制中心", diff --git a/src/modules/activity/ActivityDescription.tsx b/src/modules/activity/ActivityDescription.tsx index d4f3b86..6e2df5c 100644 --- a/src/modules/activity/ActivityDescription.tsx +++ b/src/modules/activity/ActivityDescription.tsx @@ -5,6 +5,7 @@ import { cn } from "@utils/helpers"; import { isLocalDev, isProduction } from "@utils/netbird"; import { isEmpty } from "lodash"; import { GlobeIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; import React, { useMemo } from "react"; import RoundedFlag from "@/assets/countries/RoundedFlag"; import { useCountries } from "@/contexts/CountryProvider"; @@ -15,6 +16,8 @@ type Props = { }; export default function ActivityDescription({ event }: Props) { + const t = useTranslations("activity"); + const tCommon = useTranslations("common"); const m = event.meta; const meta = useMemo(() => { if (event.meta) { @@ -42,49 +45,68 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "setupkey.revoke") return (
- Setup-Key {m.name} with key {m.key} was - revoked + {t.rich("desc_setupkey_revoke", { + name: m.name, + key: m.key, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "setupkey.delete") return (
- Setup-Key {m.name} with key {m.key} was - deleted + {t.rich("desc_setupkey_delete", { + name: m.name, + key: m.key, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "setupkey.add") return (
- Setup-Key {m.name} with key {m.key} was - created + {t.rich("desc_setupkey_add", { + name: m.name, + key: m.key, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.setupkey.add") return (
- Peer {m.name} was added - with the NetBird IP {m.ip} using the setup key{" "} - {m.setup_key_name} + {t.rich("desc_peer_setupkey_add", { + name: m.name, + ip: m.ip, + setup_key_name: m.setup_key_name, + peerConnectionInfo: () => , + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "setupkey.group.delete") return (
- Group {m.group} was removed from the{" "} - {m.setupkey} setup key + {t.rich("desc_setupkey_group_delete", { + group: m.group, + setupkey: m.setupkey, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "setupkey.group.add") return (
- Group {m.group} was added to the{" "} - {m.setupkey} setup key + {t.rich("desc_setupkey_group_add", { + group: m.group, + setupkey: m.setupkey, + Value: (chunks) => {chunks}, + })}
); @@ -94,7 +116,10 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "dashboard.login") return (
- {m.username} logged in to the dashboard + {t.rich("desc_dashboard_login", { + username: m.username, + Value: (chunks) => {chunks}, + })}
); @@ -105,21 +130,30 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "policy.update") return (
- Policy {m.name} has been updated + {t.rich("desc_policy_update", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "policy.delete") return (
- Policy {m.name} was deleted + {t.rich("desc_policy_delete", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "policy.add") return (
- Policy {m.name} was created + {t.rich("desc_policy_add", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); @@ -129,33 +163,45 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "route.delete") { let hasDomains = m?.domains && m?.domains.length > 0; + let descKey = hasDomains ? "desc_route_delete_domains" : "desc_route_delete_range"; return (
- Route {m.name} with the {hasDomains ? "domain(s)" : ""}{" "} - {hasDomains ? m?.domains : m.network_range}{" "} - {hasDomains ? "" : "range"} was deleted + {t.rich(descKey, { + name: m.name, + domains: m?.domains, + network_range: m.network_range, + Value: (chunks) => {chunks}, + })}
); } if (event.activity_code == "route.update") { let hasDomains = m?.domains && m?.domains.length > 0; + let descKey = hasDomains ? "desc_route_update_domains" : "desc_route_update_range"; return (
- Route {m.name} with the {hasDomains ? "domain(s)" : ""}{" "} - {hasDomains ? m?.domains : m.network_range}{" "} - {hasDomains ? "" : "range"} was updated + {t.rich(descKey, { + name: m.name, + domains: m?.domains, + network_range: m.network_range, + Value: (chunks) => {chunks}, + })}
); } if (event.activity_code == "route.add") { let hasDomains = m?.domains && m?.domains.length > 0; + let descKey = hasDomains ? "desc_route_add_domains" : "desc_route_add_range"; return (
- Route {m.name} with the {hasDomains ? "domain(s)" : ""}{" "} - {hasDomains ? m?.domains : m.network_range}{" "} - {hasDomains ? "" : "range"} was created + {t.rich(descKey, { + name: m.name, + domains: m?.domains, + network_range: m.network_range, + Value: (chunks) => {chunks}, + })}
); } @@ -167,121 +213,170 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "user.peer.delete") return (
- Peer {m.name} with - NetBird IP {m.ip} was deleted + {t.rich("desc_user_peer_delete", { + name: m.name, + ip: m.ip, + peerConnectionInfo: () => , + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.peer.add") return (
- Peer {m.name} was added - with the NetBird IP {m.ip} + {t.rich("desc_user_peer_add", { + name: m.name, + ip: m.ip, + peerConnectionInfo: () => , + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.peer.update") return (
- Peer {m.name} with - NetBird IP {m.ip} was updated + {t.rich("desc_user_peer_update", { + name: m.name, + ip: m.ip, + peerConnectionInfo: () => , + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.join") return (
- User {m.username} joined NetBird + {t.rich("desc_user_join", { + username: m.username, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.invite") return (
- {event.meta.username} {event.meta.email}{" "} - was invited. + {t.rich("desc_user_invite", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.create") return (
- {event.meta.username} {event.meta.email}{" "} - was created by {event?.initiator_name || "NetBird"} + {t.rich("desc_user_create", { + username: event.meta.username, + email: event.meta.email, + initiator: event?.initiator_name || "NetBird", + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.group.add") return (
- Group {event.meta.group} was added to user{" "} - {event.meta.username} + {t.rich("desc_user_group_add", { + group: event.meta.group, + username: event.meta.username, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.block") return (
- User {event.meta.username}{" "} - {event.meta.email} - was blocked + {t.rich("desc_user_block", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.unblock") return (
- User {event.meta.username}{" "} - {event.meta.email} - was unblocked + {t.rich("desc_user_unblock", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.delete") return (
- User {event.meta.username}{" "} - {event.meta.email} was deleted + {t.rich("desc_user_delete", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.group.delete") return (
- Group {event.meta.group} was removed from user{" "} - {event.meta.username} {event.meta.email} + {t.rich("desc_user_group_delete", { + group: event.meta.group, + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.role.update") return (
- Role {event.meta.role} was updated of user{" "} - {event.meta.username} {event.meta.email} + {t.rich("desc_user_role_update", { + role: event.meta.role, + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.approve") return (
- User {event.meta.username}{" "} - {event.meta.email} was approved + {t.rich("desc_user_approve", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.reject") return (
- User {event.meta.username}{" "} - {event.meta.email} was rejected + {t.rich("desc_user_reject", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.password.change") return (
- Password was changed for user {event.meta.username}{" "} - {event.meta.email} + {t.rich("desc_user_password_change", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); @@ -292,32 +387,44 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "user.invite.link.create") return (
- Invite link was created for {event.meta.username}{" "} - {event.meta.email} + {t.rich("desc_user_invite_link_create", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.invite.link.accept") return (
- Invite link was accepted by {event.meta.username}{" "} - {event.meta.email} + {t.rich("desc_user_invite_link_accept", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.invite.link.regenerate") return (
- Invite link was regenerated for {event.meta.username}{" "} - {event.meta.email} + {t.rich("desc_user_invite_link_regenerate", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "user.invite.link.delete") return (
- Invite link was deleted for {event.meta.username}{" "} - {event.meta.email} + {t.rich("desc_user_invite_link_delete", { + username: event.meta.username, + email: event.meta.email, + Value: (chunks) => {chunks}, + })}
); @@ -328,14 +435,20 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "service.user.create") return (
- Service user {event.meta.name} was created + {t.rich("desc_service_user_create", { + name: event.meta.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "service.user.delete") return (
- Service user {event.meta.name} was deleted + {t.rich("desc_service_user_delete", { + name: event.meta.name, + Value: (chunks) => {chunks}, + })}
); @@ -346,82 +459,117 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "peer.group.delete") return (
- Group {m.group} was removed from the peer with the - NetBird IP {m.peer_ip} + {t.rich("desc_peer_group_delete", { + group: m.group, + peer_ip: m.peer_ip, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.group.add") return (
- Group {m.group} was added to the peer with the NetBird IP{" "} - {m.peer_ip} + {t.rich("desc_peer_group_add", { + group: m.group, + peer_ip: m.peer_ip, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.login.expire") return (
- Login of the peer {m.name} is expired + {t.rich("desc_peer_login_expire", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.ssh.disable") return (
- SSH Server of peer {m.name} was disabled + {t.rich("desc_peer_ssh_disable", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.ssh.enable") return (
- SSH Server of peer {m.name} was enabled + {t.rich("desc_peer_ssh_enable", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.login.expiration.disable") return (
- Login expiration of peer {m.name} was disabled + {t.rich("desc_peer_login_expiration_disable", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.login.expiration.enable") return (
- Login expiration of peer {m.name} was enabled + {t.rich("desc_peer_login_expiration_enable", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.rename") return (
- Peer with the NetBird IP {m.ip} was renamed to{" "} - {m.name} + {t.rich("desc_peer_rename", { + ip: m.ip, + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.approve") return (
- Peer with the NetBird IP {m.ip} was approved + {t.rich("desc_peer_approve", { + ip: m.ip, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.ip.update") return (
- Peer {m.name} IP address was updated from{" "} - {m.old_ip} to {m.ip} + {t.rich("desc_peer_ip_update", { + name: m.name, + old_ip: m.old_ip, + ip: m.ip, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "peer.user.add") return (
- Peer {m.name} was added - with the NetBird IP {m.ip} + {t.rich("desc_peer_user_add", { + name: m.name, + ip: m.ip, + peerConnectionInfo: () => , + Value: (chunks) => {chunks}, + })}
); @@ -432,22 +580,31 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "group.add") return (
- Group {m.name} was created + {t.rich("desc_group_add", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "group.delete") return (
- Group {event.meta.name} was deleted + {t.rich("desc_group_delete", { + name: event.meta.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "group.update") return (
- Group {event.meta.old_name} was renamed to{" "} - {event.meta.new_name} + {t.rich("desc_group_update", { + old_name: event.meta.old_name, + new_name: event.meta.new_name, + Value: (chunks) => {chunks}, + })}
); @@ -458,25 +615,30 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "account.create") return (
- {event.initiator_name} created an account + {t.rich("desc_account_create", { + initiator: event.initiator_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "account.setting.peer.login.expiration.update") - return
Global login expiration was updated
; + return
{t("desc_account_setting_peer_login_expiration_update")}
; if (event.activity_code == "account.setting.peer.login.expiration.enable") - return
Global login expiration was enabled
; + return
{t("desc_account_setting_peer_login_expiration_enable")}
; if (event.activity_code == "account.setting.peer.login.expiration.disable") - return
Global login expiration was disabled
; + return
{t("desc_account_setting_peer_login_expiration_disable")}
; if (event.activity_code == "account.network.range.update") return (
- Account network range was updated from{" "} - {m.old_network_range} to{" "} - {m.new_network_range} + {t.rich("desc_account_network_range_update", { + old_network_range: m.old_network_range, + new_network_range: m.new_network_range, + Value: (chunks) => {chunks}, + })}
); @@ -487,21 +649,30 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "nameserver.group.add") return (
- Nameserver {event.meta.name} was added + {t.rich("desc_nameserver_group_add", { + name: event.meta.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "nameserver.group.delete") return (
- Nameserver {event.meta.name} was deleted + {t.rich("desc_nameserver_group_delete", { + name: event.meta.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "nameserver.group.update") return (
- Nameserver {event.meta.name} was updated + {t.rich("desc_nameserver_group_update", { + name: event.meta.name, + Value: (chunks) => {chunks}, + })}
); @@ -512,16 +683,22 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "personal.access.token.create") return (
- Access token {event.meta.name} for user{" "} - {event.meta.username} was created + {t.rich("desc_personal_access_token_create", { + name: event.meta.name, + username: event.meta.username, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "personal.access.token.delete") return (
- Access token {event.meta.name} for user{" "} - {event.meta.username} was deleted + {t.rich("desc_personal_access_token_delete", { + name: event.meta.name, + username: event.meta.username, + Value: (chunks) => {chunks}, + })}
); @@ -530,31 +707,37 @@ export default function ActivityDescription({ event }: Props) { */ if (event.activity_code == "integration.create") { - if (!event.meta.platform) return "Integration created"; + if (!event.meta.platform) return
{t("desc_integration_create")}
; return (
- {event.meta.platform}{" "} - integration created + {t.rich("desc_integration_create_platform", { + platform: event.meta.platform, + Value: (chunks) => {chunks}, + })}
); } if (event.activity_code == "integration.delete") { - if (!event.meta.platform) return "Integration deleted"; + if (!event.meta.platform) return
{t("desc_integration_delete")}
; return (
- {event.meta.platform}{" "} - integration deleted + {t.rich("desc_integration_delete_platform", { + platform: event.meta.platform, + Value: (chunks) => {chunks}, + })}
); } if (event.activity_code == "integration.update") { - if (!event.meta.platform) return "Integration updated"; + if (!event.meta.platform) return
{t("desc_integration_update")}
; return (
- {event.meta.platform}{" "} - integration updated + {t.rich("desc_integration_update_platform", { + platform: event.meta.platform, + Value: (chunks) => {chunks}, + })}
); } @@ -566,16 +749,20 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "dns.setting.disabled.management.group.add") return (
- Group {event.meta.group} was added to disabled DNS group - setting + {t.rich("desc_dns_setting_disabled_management_group_add", { + group: event.meta.group, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "dns.setting.disabled.management.group.delete") return (
- Group {event.meta.group} was removed from disabled DNS - group setting + {t.rich("desc_dns_setting_disabled_management_group_delete", { + group: event.meta.group, + Value: (chunks) => {chunks}, + })}
); @@ -586,26 +773,35 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "posture.check.updated") return (
- Posture check {m.name} was updated + {t.rich("desc_posture_check_updated", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "posture.check.created") return (
- Posture check {m.name} was created + {t.rich("desc_posture_check_created", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "posture.check.deleted") return (
- Posture check {m.name} was deleted + {t.rich("desc_posture_check_deleted", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "transferred.owner.role") - return
Owner role was transferred
; + return
{t("desc_transferred_owner_role")}
; /** * EDR @@ -613,49 +809,68 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "integrated-validator.api.created") return (
- {m?.platform} integration created + {t.rich("desc_integrated_validator_api_created", { + platform: m?.platform, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "integrated-validator.api.updated") return (
- {m?.platform} integration updated + {t.rich("desc_integrated_validator_api_updated", { + platform: m?.platform, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "integrated-validator.api.deleted") return (
- {m?.platform} integration deleted + {t.rich("desc_integrated_validator_api_deleted", { + platform: m?.platform, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "integrated-validator.host-check.approved") return (
- Peer approved by {m?.platform} integration + {t.rich("desc_integrated_validator_host_check_approved", { + platform: m?.platform, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "integrated-validator.host-check.denied") return (
- Peer rejected by {m?.platform} integration + {t.rich("desc_integrated_validator_host_check_denied", { + platform: m?.platform, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "integrated-validator.peer.compliance-bypassed") return (
- Peer {m?.name} with the NetBird IP {m?.ip}{" "} - compliance bypassed for {m?.platform} integration - {m?.original_reason && ( - <> - {" "} - (original non-compliant reason: {m?.original_reason}) - - )} + {t.rich("desc_integrated_validator_peer_compliance_bypassed", { + name: m?.name, + ip: m?.ip, + platform: m?.platform, + original_reason: m?.original_reason + ? t.rich("desc_compliance_original_reason", { + reason: m?.original_reason, + Value: (chunks) => {chunks}, + }) + : "", + Value: (chunks) => {chunks}, + })}
); @@ -664,8 +879,12 @@ export default function ActivityDescription({ event }: Props) { ) return (
- Peer {m?.name} with the NetBird IP {m?.ip}{" "} - compliance bypass revoked for {m?.platform} integration + {t.rich("desc_integrated_validator_peer_compliance_bypass_revoked", { + name: m?.name, + ip: m?.ip, + platform: m?.platform, + Value: (chunks) => {chunks}, + })}
); @@ -675,16 +894,22 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "resource.group.add") return (
- Group {m.resource_name} added to resource{" "} - {m.name} + {t.rich("desc_resource_group_add", { + resource_name: m.resource_name, + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "resource.group.delete") return (
- Group {m.resource_name} removed from resource{" "} - {m.name} + {t.rich("desc_resource_group_delete", { + resource_name: m.resource_name, + name: m.name, + Value: (chunks) => {chunks}, + })}
); @@ -695,25 +920,34 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "service.peer.expose") return (
- Peer {m.peer_name} exposed service{" "} - {m.domain} with auth{" "} - {m.auth ? "Enabled" : "Disabled"} + {t.rich("desc_service_peer_expose", { + peer_name: m.peer_name, + domain: m.domain, + auth: m.auth ? tCommon("enabled") : tCommon("disabled"), + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "service.peer.unexpose") return (
- Peer {m.peer_name} unexposed service{" "} - {m.domain} + {t.rich("desc_service_peer_unexpose", { + peer_name: m.peer_name, + domain: m.domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "service.peer.expose.expire") return (
- Service {m.domain} exposed by peer{" "} - {m.peer_name} was removed due to renewal expiration + {t.rich("desc_service_peer_expose_expire", { + domain: m.domain, + peer_name: m.peer_name, + Value: (chunks) => {chunks}, + })}
); @@ -724,69 +958,93 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "network.resource.create") return (
- Resource {m.name} created for network{" "} - {m.network_name} + {t.rich("desc_network_resource_create", { + name: m.name, + network_name: m.network_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.resource.update") return (
- Resource {m.name} updated for network{" "} - {m.network_name} + {t.rich("desc_network_resource_update", { + name: m.name, + network_name: m.network_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.resource.delete") return (
- Resource {m.name} deleted from network{" "} - {m.network_name} + {t.rich("desc_network_resource_delete", { + name: m.name, + network_name: m.network_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.router.create") return (
- Routing peer created for network{" "} - {m.network_name} + {t.rich("desc_network_router_create", { + network_name: m.network_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.router.delete") return (
- Routing peer deleted from network{" "} - {m.network_name} + {t.rich("desc_network_router_delete", { + network_name: m.network_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.router.update") return (
- Routing peer updated from network{" "} - {m.network_name} + {t.rich("desc_network_router_update", { + network_name: m.network_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.create") return (
- Network with name {m.name} created + {t.rich("desc_network_create", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.delete") return (
- Network with name {m.name} deleted + {t.rich("desc_network_delete", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "network.update") return (
- Network with name {m.name} updated + {t.rich("desc_network_update", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); @@ -797,8 +1055,11 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "peer.job.create") return (
- Remote job {m.job_type} created for peer{" "} - {m.for_peer_name} + {t.rich("desc_peer_job_create", { + job_type: m.job_type, + for_peer_name: m.for_peer_name, + Value: (chunks) => {chunks}, + })}
); @@ -809,14 +1070,20 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "account.settings.extra.flow.group.remove") return (
- Limit traffic event group {m.group_name} removed + {t.rich("desc_account_settings_extra_flow_group_remove", { + group_name: m.group_name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "account.settings.extra.flow.group.add") return (
- Limit traffic event group {m.group_name} added + {t.rich("desc_account_settings_extra_flow_group_add", { + group_name: m.group_name, + Value: (chunks) => {chunks}, + })}
); @@ -827,21 +1094,30 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "identityprovider.create") return (
- Identity provider {m.name} was created + {t.rich("desc_identityprovider_create", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "identityprovider.update") return (
- Identity provider {m.name} was updated + {t.rich("desc_identityprovider_update", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "identityprovider.delete") return (
- Identity provider {m.name} was deleted + {t.rich("desc_identityprovider_delete", { + name: m.name, + Value: (chunks) => {chunks}, + })}
); @@ -852,26 +1128,35 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "service.create") return (
- Service {m.domain} in cluster{" "} - {m.proxy_cluster} was created with authentication{" "} - {m.auth ? "Enabled" : "Disabled"} + {t.rich("desc_service_create", { + domain: m.domain, + proxy_cluster: m.proxy_cluster, + auth: m.auth ? tCommon("enabled") : tCommon("disabled"), + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "service.update") return (
- Service {m.domain} in cluster{" "} - {m.proxy_cluster} was updated with authentication{" "} - {m.auth ? "Enabled" : "Disabled"} + {t.rich("desc_service_update", { + domain: m.domain, + proxy_cluster: m.proxy_cluster, + auth: m.auth ? tCommon("enabled") : tCommon("disabled"), + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "service.delete") return (
- Service {m.domain} in cluster{" "} - {m.proxy_cluster} was deleted + {t.rich("desc_service_delete", { + domain: m.domain, + proxy_cluster: m.proxy_cluster, + Value: (chunks) => {chunks}, + })}
); @@ -882,59 +1167,80 @@ export default function ActivityDescription({ event }: Props) { if (event.activity_code == "reseller.msp.created") return (
- Customer {m.msp_name} with domain{" "} - {m.msp_domain} was created + {t.rich("desc_reseller_msp_created", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "reseller.activated") - return
Distributor account was activated
; + return
{t("desc_reseller_activated")}
; if (event.activity_code == "reseller.msp.deleted") return (
- Customer {m.msp_name} with domain{" "} - {m.msp_domain} was deleted + {t.rich("desc_reseller_msp_deleted", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "reseller.msp.unlinked") return (
- Customer {m.msp_name} with domain{" "} - {m.msp_domain} was unlinked + {t.rich("desc_reseller_msp_unlinked", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "reseller.msp.invite.requested") return (
- Invite requested for customer {m.msp_name} with domain{" "} - {m.msp_domain} + {t.rich("desc_reseller_msp_invite_requested", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "reseller.msp.invite.accepted") return (
- Invite accepted by customer {m.msp_name} with domain{" "} - {m.msp_domain} + {t.rich("desc_reseller_msp_invite_accepted", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "reseller.msp.invite.declined") return (
- Invite declined by customer {m.msp_name} with domain{" "} - {m.msp_domain} + {t.rich("desc_reseller_msp_invite_declined", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); if (event.activity_code == "reseller.msp.updated") return (
- Customer {m.msp_name} with domain{" "} - {m.msp_domain} was updated + {t.rich("desc_reseller_msp_updated", { + msp_name: m.msp_name, + msp_domain: m.msp_domain, + Value: (chunks) => {chunks}, + })}
); @@ -946,9 +1252,9 @@ export default function ActivityDescription({ event }: Props) { - + {event.activity_code} - + {meta && meta.map((item) => ( @@ -989,25 +1295,25 @@ function Value({ } function PeerConnectionInfo({ meta }: { meta: any }) { + const t = useTranslations("activity"); const hasMeta = !isEmpty(meta?.location_country_code) || !isEmpty(meta?.location_connection_ip); const { countries } = useCountries(); const countryText = useMemo(() => { - if (!countries) return "Unknown"; + if (!countries) return t("unknown"); const country = countries.find( (c) => c.country_code === meta?.location_country_code, ); - if (!country) return "Unknown"; + if (!country) return t("unknown"); if (!meta?.location_city_name) return country.country_name; return `${country.country_name}, ${meta?.location_city_name}`; - }, [countries, meta]); + }, [countries, meta, t]); return hasMeta ? ( <> {" "} - from{" "} {meta?.location_connection_ip && ( {meta?.location_connection_ip} )}{" "} diff --git a/src/modules/activity/ActivityEntryRow.tsx b/src/modules/activity/ActivityEntryRow.tsx index 5a10a04..7bead0b 100644 --- a/src/modules/activity/ActivityEntryRow.tsx +++ b/src/modules/activity/ActivityEntryRow.tsx @@ -4,6 +4,7 @@ import TextWithTooltip from "@components/ui/TextWithTooltip"; import { cn, generateColorFromUser } from "@utils/helpers"; import dayjs from "dayjs"; import { AlertCircle, ArrowUpRight, Cog, PlusIcon, XIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; import React, { useMemo } from "react"; import { useUsers } from "@/contexts/UsersProvider"; import { ActivityEvent } from "@/interfaces/ActivityEvent"; @@ -23,6 +24,7 @@ const ActionIcons: Record = { export const ActivityEntryRow = ({ event }: { event: ActivityEvent }) => { const { users } = useUsers(); + const t = useTranslations("activity"); const getActivityUser = () => { let user; @@ -95,7 +97,7 @@ export const ActivityEntryRow = ({ event }: { event: ActivityEvent }) => { @@ -105,7 +107,7 @@ export const ActivityEntryRow = ({ event }: { event: ActivityEvent }) => { {isExternal && ( (null); const [inputRef, { width }] = useElementSize(); const [search, setSearch] = useState(""); @@ -50,7 +52,7 @@ export function ActivityEventCodeSelector({ activity_code: event.activity_code, activity: event.activity, group: event.activity_code.startsWith("service.user") - ? "Service User" + ? t("serviceUser") : event.activity_code.split(".")[0], }; }); @@ -81,9 +83,9 @@ export function ActivityEventCodeSelector({
{values.length > 0 ? ( -
{values.length} Event(s)
+
{t("eventCount", { count: values.length })}
) : ( - "All Event Types" + t("allEventTypes") )}
@@ -122,7 +124,7 @@ export function ActivityEventCodeSelector({ ref={searchRef} value={search} onValueChange={setSearch} - placeholder={"Search event..."} + placeholder={t("searchEvent")} />
), - formatChip: (v) => formatActivityTypeChip(v as string[] | undefined), + formatChip: (v) => formatActivityTypeChip(v as string[] | undefined, t), }, { id: "initiator_email", diff --git a/src/modules/activity/ActivityTypePicker.tsx b/src/modules/activity/ActivityTypePicker.tsx index e42f699..21cf8f5 100644 --- a/src/modules/activity/ActivityTypePicker.tsx +++ b/src/modules/activity/ActivityTypePicker.tsx @@ -7,6 +7,7 @@ import { cn } from "@utils/helpers"; import { Command, CommandGroup, CommandInput, CommandList } from "cmdk"; import { trim, uniqBy } from "lodash"; import { SearchIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; import * as React from "react"; import { useMemo, useRef } from "react"; import { ActivityEvent } from "@/interfaces/ActivityEvent"; @@ -34,6 +35,7 @@ export function ActivityTypePicker({ onChange, events, }: Readonly) { + const t = useTranslations("activity"); const searchRef = useRef(null); const selected = value ?? []; @@ -43,7 +45,7 @@ export function ActivityTypePicker({ activity_code: event.activity_code, activity: event.activity, group: event.activity_code.startsWith("service.user") - ? "Service User" + ? t("serviceUser") : event.activity_code.split(".")[0], })); return items.reduce>((acc, item) => { @@ -80,7 +82,7 @@ export function ActivityTypePicker({ "dark:placeholder:text-nb-gray-400 font-light placeholder:text-neutral-500 pl-9", )} ref={searchRef} - placeholder={"Search event..."} + placeholder={t("searchEvent")} />
) => string, ): string | null { if (!value || value.length === 0) return null; if (value.length === 1) return value[0]; - return `${value.length} types`; + return t ? t("typeCount", { count: value.length }) : `${value.length} types`; } diff --git a/src/modules/activity/UsersDropdownSelector.tsx b/src/modules/activity/UsersDropdownSelector.tsx index 48e57a5..b837f4d 100644 --- a/src/modules/activity/UsersDropdownSelector.tsx +++ b/src/modules/activity/UsersDropdownSelector.tsx @@ -9,6 +9,7 @@ import { useSearch } from "@hooks/useSearch"; import { generateColorFromString } from "@utils/helpers"; import { sortBy, uniqBy } from "lodash"; import { ChevronsUpDown, Cog, UserCircle2 } from "lucide-react"; +import { useTranslations } from "next-intl"; import * as React from "react"; import { useMemo, useState } from "react"; import { useElementSize } from "@/hooks/useElementSize"; @@ -48,11 +49,12 @@ export function UsersDropdownSelector({ popoverWidth = 250, options, }: Readonly) { + const t = useTranslations("activity"); const [filteredItems, search, setSearch] = useSearch( options.concat({ id: "all-users", - name: "All Users", - email: "Include all users", + name: t("allUsers"), + email: t("includeAllUsers"), }), searchPredicate, { filter: true, debounce: 150 }, @@ -107,7 +109,7 @@ export function UsersDropdownSelector({ {!selectedUser ? ( - All Users + {t("allUsers")} ) : ( @@ -136,7 +138,7 @@ export function UsersDropdownSelector({ {options.length == 0 && !search && (
- {"No users available to select."} + {t("noUsersAvailable")}
)} @@ -180,7 +182,7 @@ export function UsersDropdownSelector({ {filteredItems.length == 0 && search != "" && (
- There are no users matching your search. + {t("noUsersMatching")}
)} @@ -227,7 +229,7 @@ export function UsersDropdownSelector({ > @@ -246,7 +248,7 @@ export function UsersDropdownSelector({ {user.external && ( { const choice = await confirm({ - title: `Approve peer '${peer.name}'?`, - description: "Are you sure you want to approve this peer?", - confirmText: "Approve", - cancelText: "Cancel", + title: t("confirmApprove", { name: peer.name }), + description: t("confirmApproveDescription"), + confirmText: t("approve"), + cancelText: tCommon("cancel"), type: "default", }); if (!choice) return; notify({ - title: `Peer ${peer.name} approved`, - description: `This peer was approved and can now connect to other peers.`, + title: t("approveSuccess", { name: peer.name }), + description: t("approveSuccessDescription"), promise: update({ name: peer.name, ssh: peer.ssh_enabled, @@ -74,45 +79,41 @@ export default function PeerActionCell() { mutate("/peers"); mutate("/groups"); }), - loadingMessage: "Approving peer...", + loadingMessage: t("approveLoading"), }); }; const handleBypassCompliance = async () => { const choice = await confirm({ - title: `Bypass compliance for '${peer.name}'?`, - description: - "This will override the compliance check and allow this peer to connect. " + - "The bypass will be automatically removed if the device becomes compliant.", - confirmText: "Bypass Compliance", - cancelText: "Cancel", + title: t("bypassComplianceConfirmTitle", { name: peer.name }), + description: t("bypassComplianceConfirmDescription"), + confirmText: t("bypassCompliance"), + cancelText: tCommon("cancel"), type: "warning", }); if (!choice || !peer.id) return; notify({ - title: `Compliance bypassed for ${peer.name}`, - description: `This peer can now connect to other peers.`, + title: t("bypassComplianceSuccess", { name: peer.name }), + description: t("bypassComplianceSuccessDescription"), promise: bypassCompliance(peer.id), - loadingMessage: "Bypassing compliance...", + loadingMessage: t("bypassComplianceLoading"), }); }; const handleRevokeBypass = async () => { const choice = await confirm({ - title: `Revoke compliance bypass for '${peer.name}'?`, - description: - "This peer will be subject to normal compliance validation. " + - "If still non-compliant, it will lose network access.", - confirmText: "Revoke", - cancelText: "Cancel", + title: t("revokeBypassConfirmTitle", { name: peer.name }), + description: t("revokeBypassConfirmDescription"), + confirmText: t("revoke"), + cancelText: tCommon("cancel"), type: "warning", }); if (!choice || !peer.id) return; notify({ - title: `Compliance bypass revoked`, - description: `Peer ${peer.name} is now subject to normal compliance validation.`, + title: t("revokeBypassSuccess"), + description: t("revokeBypassSuccessDescription", { name: peer.name }), promise: revokeBypass(peer.id), - loadingMessage: "Revoking compliance bypass...", + loadingMessage: t("revokeBypassLoading"), }); }; @@ -143,11 +144,16 @@ export default function PeerActionCell() { const showRemoteAccessItems = !isMobile && !!peer.connected; const toggleLoginExpiration = async () => { - const text = peer.login_expiration_enabled ? "disabled" : "enabled"; + const state = peer.login_expiration_enabled + ? tCommon("disabled") + : tCommon("enabled"); const disableLoginExpiration = peer.login_expiration_enabled; notify({ - title: `Session expiration is ${text}`, - description: `Session expiration for peer ${peer.name} was successfully ${text}.`, + title: t("loginExpirationUpdated", { state }), + description: t("loginExpirationUpdateDescription", { + name: peer.name, + state, + }), promise: update({ loginExpiration: !peer.login_expiration_enabled, inactivityExpiration: disableLoginExpiration @@ -157,31 +163,28 @@ export default function PeerActionCell() { mutate("/peers"); mutate("/groups"); }), - loadingMessage: "Updating session expiration...", + loadingMessage: t("loginExpirationUpdating"), }); }; const disableDashboardSSH = async () => { const choice = await confirm({ - title: `Disable SSH Access?`, + title: t("disableSSHConfirmation"), description: (
- Starting from NetBird v0.61.0, once SSH access is disabled, you cannot - re-enable it again from the dashboard. You'll need to create an - explicit access control policy and update your NetBird client to - restore SSH functionality.{" "} + {t("disableSSHDescription")}{" "} e.stopPropagation()} > - Learn more + {tCommon("learnMore")}
), - confirmText: "Disable", - cancelText: "Cancel", + confirmText: tCommon("disable"), + cancelText: tCommon("cancel"), type: "warning", maxWidthClass: "max-w-xl", }); @@ -210,7 +213,7 @@ export default function PeerActionCell() { >
- View Details + {t("viewDetails")}
@@ -221,7 +224,7 @@ export default function PeerActionCell() {
- Approve + {t("approve")}
)} @@ -230,16 +233,16 @@ export default function PeerActionCell() { className={"w-full block"} content={
- Bypass {activeIntegrationName} compliance check and - allow this peer to connect. The bypass is automatically - removed when the device becomes compliant. + {t("bypassTooltip", { + integrationName: activeIntegrationName, + })}
} >
- Bypass Compliance + {t("bypassCompliance")}
@@ -248,7 +251,7 @@ export default function PeerActionCell() {
- Revoke Bypass + {t("revokeBypass")}
)} @@ -270,9 +273,7 @@ export default function PeerActionCell() { className={"flex gap-2 items-center !text-nb-gray-300 text-xs"} > - - Expiration is disabled for all peers added with an setup-key. - + {t("expirationDisabledTooltip")}
} className={"w-full block"} @@ -284,8 +285,9 @@ export default function PeerActionCell() { >
- {peer.login_expiration_enabled ? "Disable" : "Enable"} Session - Expiration + {peer.login_expiration_enabled + ? t("disableLoginExpiration") + : t("enableLoginExpiration")}
@@ -302,7 +304,7 @@ export default function PeerActionCell() {
- {peer.ssh_enabled ? "Disable" : "Enable"} SSH Access + {peer.ssh_enabled ? t("disableSSH") : t("enableSSH")}
@@ -319,7 +321,7 @@ export default function PeerActionCell() { >
- Delete + {tCommon("delete")}
diff --git a/src/modules/setup-keys/SetupKeyActionCell.tsx b/src/modules/setup-keys/SetupKeyActionCell.tsx index fd1b533..d36df00 100644 --- a/src/modules/setup-keys/SetupKeyActionCell.tsx +++ b/src/modules/setup-keys/SetupKeyActionCell.tsx @@ -9,6 +9,7 @@ import { import { notify } from "@components/Notification"; import { useApiCall } from "@utils/api"; import { MoreVertical, Trash2, Undo2Icon } from "lucide-react"; +import { useTranslations } from "next-intl"; import * as React from "react"; import { useSWRConfig } from "swr"; import { useDialog } from "@/contexts/DialogProvider"; @@ -19,6 +20,8 @@ type Props = { setupKey: SetupKey; }; export default function SetupKeyActionCell({ setupKey }: Readonly) { + const t = useTranslations("setupKeys"); + const tCommon = useTranslations("common"); const { confirm } = useDialog(); const request = useApiCall("/setup-keys/" + setupKey.id); const { mutate } = useSWRConfig(); @@ -28,23 +31,24 @@ export default function SetupKeyActionCell({ setupKey }: Readonly) { !setupKey.revoked && setupKey.valid && permission.setup_keys.update; const canDelete = permission.setup_keys.delete; + const keyName = setupKey?.name || t("key"); + const handleRevoke = async () => { const choice = await confirm({ - title: `Revoke '${setupKey?.name || "Setup Key"}'?`, - description: - "Are you sure you want to revoke the setup key? This action cannot be undone.", - confirmText: "Revoke", - cancelText: "Cancel", + title: t("revokeConfirmTitle", { name: keyName }), + description: t("revokeConfirmDescription"), + confirmText: t("revoke"), + cancelText: tCommon("cancel"), type: "danger", }); if (!choice) return; notify({ - title: setupKey?.name || "Setup Key", - description: "Setup key was successfully revoked", + title: keyName, + description: t("revokeSuccessDescription"), promise: request .put({ - name: setupKey?.name || "Setup Key", + name: keyName, type: setupKey.type, expires_in: setupKey.expires_in, revoked: true, @@ -57,29 +61,28 @@ export default function SetupKeyActionCell({ setupKey }: Readonly) { mutate("/setup-keys"); mutate("/groups"); }), - loadingMessage: "Revoking the setup key...", + loadingMessage: t("revokeLoading"), }); }; const handleDelete = async () => { const choice = await confirm({ - title: `Delete '${setupKey?.name || "Setup Key"}'?`, - description: - "Are you sure you want to delete the setup key? This action cannot be undone.", - confirmText: "Delete", - cancelText: "Cancel", + title: t("deleteConfirmTitle", { name: keyName }), + description: t("deleteConfirmDescription"), + confirmText: tCommon("delete"), + cancelText: tCommon("cancel"), type: "danger", }); if (!choice) return; notify({ - title: setupKey?.name || "Setup Key", - description: "Setup key was successfully deleted", + title: keyName, + description: t("deleteSuccessDescription"), promise: request.del().then(() => { mutate("/setup-keys"); mutate("/groups"); }), - loadingMessage: "Deleting the setup key...", + loadingMessage: t("deleteLoading"), }); }; @@ -96,7 +99,7 @@ export default function SetupKeyActionCell({ setupKey }: Readonly) {