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) {
- Activity Code
+ {t("activityCode")}
{event.activity_code}
- Meta
+ {t("meta")}
{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) {
@@ -111,7 +114,7 @@ export default function SetupKeyActionCell({ setupKey }: Readonly) {
>
- Revoke
+ {t("revoke")}
@@ -123,7 +126,7 @@ export default function SetupKeyActionCell({ setupKey }: Readonly) {
>
- Delete
+ {tCommon("delete")}
diff --git a/src/modules/setup-keys/SetupKeyGroupsCell.tsx b/src/modules/setup-keys/SetupKeyGroupsCell.tsx
index ce3b170..1b6f7b3 100644
--- a/src/modules/setup-keys/SetupKeyGroupsCell.tsx
+++ b/src/modules/setup-keys/SetupKeyGroupsCell.tsx
@@ -1,5 +1,6 @@
import { notify } from "@components/Notification";
import { useApiCall } from "@utils/api";
+import { useTranslations } from "next-intl";
import { useState } from "react";
import { useSWRConfig } from "swr";
import { usePermissions } from "@/contexts/PermissionsProvider";
@@ -11,6 +12,7 @@ type Props = {
setupKey: SetupKey;
};
export default function SetupKeyGroupsCell({ setupKey }: Readonly) {
+ const t = useTranslations("setupKeys");
const [modal, setModal] = useState(false);
const { permission } = usePermissions();
const request = useApiCall("/setup-keys/" + setupKey.id);
@@ -19,11 +21,11 @@ export default function SetupKeyGroupsCell({ setupKey }: Readonly) {
const groups = await Promise.all(promises);
notify({
- title: setupKey?.name || "Setup Key",
- description: "Groups of the setup key were successfully saved",
+ title: setupKey?.name || t("key"),
+ description: t("groupsSavedDescription"),
promise: request
.put({
- name: setupKey?.name || "Setup Key",
+ name: setupKey?.name || t("key"),
type: setupKey.type,
expires_in: setupKey.expires_in,
revoked: setupKey.revoked,
@@ -37,17 +39,15 @@ export default function SetupKeyGroupsCell({ setupKey }: Readonly) {
mutate("/setup-keys");
mutate("/groups");
}),
- loadingMessage: "Saving the groups of the setup key...",
+ loadingMessage: t("groupsSaving"),
});
};
return (
permission.groups.read && (