Remove new useTranslate usage
Signed-off-by: Jonathan <contact@grafikart.fr>
This commit is contained in:
@@ -1170,7 +1170,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"common": { "error": "Error", "success": "Success", "create": "Create" },
|
||||
"common": { "error": "Error", "success": "Success", "create": "Create", "actions": { "back": "Back", "close": "Close", "continue": "Continue", "copy": "Copy", "tryAgain": "Try again" }, "messages": { "copied": "Copied", "copiedToClipboard": "Copied to clipboard" } },
|
||||
"mutation": {
|
||||
"messages": { "completed": "Operation completed successfully" },
|
||||
"errors": { "commit": "Failed to commit this operation" }
|
||||
@@ -1331,10 +1331,10 @@
|
||||
"membershipsDropdown": { "searchPlaceholder": "Search organizations...", "loading": "Loading organizations...", "addOrganization": "Add organization" },
|
||||
"membershipsDropdownInvitingItem": { "title": "Check your email to accept the invitation" },
|
||||
"membershipsDropdownMenu": { "pendingInvitations": "Pending invitations" },
|
||||
"sidebar": { "context": "Context", "tasks": "Tasks", "measures": "Measures", "risks": "Risks", "frameworks": "Frameworks", "people": "People", "thirdParties": "Third parties", "documents": "Documents", "assets": "Assets", "data": "Data", "audits": "Audits", "findings": "Findings", "obligations": "Obligations", "processingActivities": "Processing Activities", "statementsOfApplicability": "Statements of Applicability", "rightsRequests": "Rights Requests", "accessReviews": "Access Reviews", "compliancePage": "Compliance Page", "cookieBanners": "Cookie Banners", "settings": "Settings" },
|
||||
"sidebar": { "context": "Context", "tasks": "Tasks", "measures": "Measures", "risks": "Risks", "frameworks": "Frameworks", "people": "People", "thirdParties": "Third parties", "documents": "Documents", "assets": "Assets", "devices": "Devices", "data": "Data", "audits": "Audits", "findings": "Findings", "obligations": "Obligations", "processingActivities": "Processing Activities", "statementsOfApplicability": "Statements of Applicability", "rightsRequests": "Rights Requests", "accessReviews": "Access Reviews", "compliancePage": "Compliance Page", "cookieBanners": "Cookie Banners", "settings": "Settings" },
|
||||
"viewerMembershipDropdown": {
|
||||
"errors": { "requestFailed": "Request failed", "cannotSignOut": "Cannot sign out" },
|
||||
"actions": { "apiKeys": "API Keys", "oauthTokens": "OAuth tokens", "mySignatures": "My Signatures", "help": "Help", "logout": "Logout" }
|
||||
"actions": { "apiKeys": "API Keys", "oauthTokens": "OAuth tokens", "mySignatures": "My Signatures", "employeePortal": "Employee Portal", "help": "Help", "logout": "Logout" }
|
||||
},
|
||||
"peoplePage": { "title": "People", "actions": { "add": "Add Person" } },
|
||||
"personPage": {
|
||||
@@ -2461,6 +2461,11 @@
|
||||
"magicLinkExpired": { "title": "Link Expired", "description": "This magic link has expired. Magic links are only valid for 15 minutes. Please request a new one." },
|
||||
"magicLinkForm": { "errors": { "send": "Cannot send magic link" }, "messages": { "sent": "Magic link sent!" }, "fields": { "email": "Email" }, "sentDescription": "Magic link sent! Check your email and use the link to continue.", "actions": { "resend": "Resend Link", "resendIn": "Resend Link in {{count}}s", "send": "Send Magic Link" } },
|
||||
"auth": { "actions": { "signIn": "Sign in" } },
|
||||
"authError": { "enterpriseAccountRequired": { "title": "Enterprise account required", "description": "Personal Google and Microsoft accounts cannot be used to sign in. Please use your work or school account instead." }, "emailNotVerified": { "title": "Email not verified", "description": "Your email address is not verified with the identity provider. Please verify it, then try signing in again." }, "signInSessionExpired": { "title": "Sign-in session expired", "description": "This sign-in attempt is no longer valid. Please start again from the sign-in page." }, "magicLinkExpired": { "title": "Link Expired", "description": "This magic link has expired. Magic links are only valid for 15 minutes. Please request a new one." }, "magicLinkAlreadyUsed": { "title": "Link Already Used", "description": "This magic link has already been used. Please request a new one." }, "invalidLink": { "title": "Invalid link", "description": "This magic link is invalid. Please request a new one." }, "default": { "title": "Authentication failed", "description": "We could not complete your sign-in. Please try again." } },
|
||||
"accessReviewSource": { "documentation": "Documentation", "regions": { "label": "Region", "placeholder": "Select a region", "unitedStates": "United States", "europe": "Europe" } },
|
||||
"devices": { "title": "Devices", "description": "Manage computers enrolled with the Probo posture agent.", "empty": "No devices enrolled yet", "actions": { "add": "Add device", "new": "New device", "reassign": "Re-assign", "revoke": "Revoke" }, "fields": { "organization": "Organization", "hostname": "Hostname", "owner": "Owner", "state": "State", "platform": "Platform", "osVersion": "OS version", "hardwareUuid": "Hardware UUID", "serialNumber": "Serial number", "agentVersion": "Agent version", "enrolledAt": "Enrolled at", "lastSeen": "Last seen" }, "values": { "pending": "(pending)", "never": "Never", "unassigned": "Unassigned" }, "messages": { "ownerUpdated": "Device owner updated", "revoked": "Device revoked" }, "errors": { "create": "Failed to create device", "reassign": "Failed to re-assign device", "revoke": "Failed to revoke device" }, "confirmations": { "revoke": "Revoke device \"{{hostname}}\"? The agent on the device will stop reporting and must be re-enrolled." }, "postures": { "title": "Posture", "navigation": "Postures", "summaryHelp": "How posture summary is calculated", "summaryDescription": "Shown as pass / fail / total.", "empty": "No posture checks recorded", "columns": { "check": "Check", "status": "Status", "observedAt": "Observed at" }, "status": { "pass": "Pass", "fail": "Fail", "unknown": "Unknown", "notApplicable": "Not applicable" }, "checks": { "diskEncryption": "Disk encryption", "screenLock": "Screen lock", "firewallEnabled": "Firewall enabled", "timeSync": "Time sync", "osVersion": "OS version", "autoUpdate": "Auto update", "passwordPolicy": "Password policy", "remoteLogin": "Remote login", "malwareProtection": "Malware protection" } } },
|
||||
"employeeDevices": { "title": "Your devices" },
|
||||
"deviceEnrollment": { "pageTitle": "Enroll device", "title": "Device enrollment", "setup": "Setup", "stepProgress": "Step {{current}} of {{total}}", "steps": { "privacy": { "title": "Privacy", "description": "Review collected data" }, "organization": { "title": "Organization", "description": "Choose destination workspace" }, "enroll": { "title": "Open and wait", "description": "Finish setup in the desktop agent" } }, "unavailable": { "title": "Enrollment unavailable", "description": "You do not have permission to enroll devices in any organization." }, "intro": { "title": "Before you start", "description": "Probo collects the following device metadata for inventory and posture reporting:" }, "privacy": { "identity": "Device identity: hardware UUID, hostname, and serial number (when available).", "systemDetails": "System details: platform, OS version, and Probo agent version.", "activitySignals": "Activity signals: enrollment time, heartbeats, and posture check results." }, "organization": { "title": "Choose organization", "description": "Pick which organization will own and manage this device." }, "openAgent": { "title": "Open the Probo agent", "description": "Open the desktop agent to finish setup, then keep this page open until enrollment is confirmed." }, "actions": { "backToOrganizations": "Back to organizations", "enrollNew": "Enroll new device", "openAgent": "Open Probo agent", "preparing": "Preparing…" }, "status": { "enrolledWithHostname": "{{hostname}} is enrolled.", "enrolled": "This device is enrolled.", "closeWindow": "You can close this window.", "waitingForCheckIn": "Waiting for the agent's first check-in…", "timedOut": "We haven't heard from the agent yet. Make sure the desktop agent is installed and running, then try again." }, "manual": { "cannotEnroll": "Can't enroll new device?", "tryCreating": "Try creating it manually", "title": "Manual enrollment", "creating": "Creating device…" }, "token": { "title": "Enrollment token generated", "description": "Share this enrollment token only with the device owner through a secure channel. It can be used once and expires after seven days.", "manualInstall": "Manual install (CLI / MDM)", "installUnix": "Install on macOS or Linux (run from a shell with sudo access)", "installWindows": "Install on Windows (run from an elevated PowerShell session)", "securityNotice": "The token is passed as a CLI flag (not via curl-piped-to-shell or sudo env vars). Once installed, the agent self-updates from GitHub Releases with cosign signature verification." }, "messages": { "created": "Device created. Copy the enrollment token now — it will not be shown again." }, "errors": { "copyToClipboard": "Failed to copy to clipboard" } },
|
||||
"thirdPartyRiskAssessmentRow": {
|
||||
"expired": "Expired",
|
||||
"notes": "Notes"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@probo/ui";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
@@ -29,63 +29,49 @@ type AuthErrorContent = {
|
||||
};
|
||||
|
||||
function useAuthErrorContent(code: string | null): AuthErrorContent {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
switch (code) {
|
||||
case "personal_account_not_allowed":
|
||||
return {
|
||||
title: __("Enterprise account required"),
|
||||
description: __(
|
||||
"Personal Google and Microsoft accounts cannot be used to sign in. Please use your work or school account instead.",
|
||||
),
|
||||
title: t("authError.enterpriseAccountRequired.title"),
|
||||
description: t("authError.enterpriseAccountRequired.description"),
|
||||
};
|
||||
case "email_not_verified":
|
||||
return {
|
||||
title: __("Email not verified"),
|
||||
description: __(
|
||||
"Your email address is not verified with the identity provider. Please verify it, then try signing in again.",
|
||||
),
|
||||
title: t("authError.emailNotVerified.title"),
|
||||
description: t("authError.emailNotVerified.description"),
|
||||
};
|
||||
case "invalid_state":
|
||||
return {
|
||||
title: __("Sign-in session expired"),
|
||||
description: __(
|
||||
"This sign-in attempt is no longer valid. Please start again from the sign-in page.",
|
||||
),
|
||||
title: t("authError.signInSessionExpired.title"),
|
||||
description: t("authError.signInSessionExpired.description"),
|
||||
};
|
||||
case "magic_link_expired":
|
||||
return {
|
||||
title: __("Link Expired"),
|
||||
description: __(
|
||||
"This magic link has expired. Magic links are only valid for 15 minutes. Please request a new one.",
|
||||
),
|
||||
title: t("authError.magicLinkExpired.title"),
|
||||
description: t("authError.magicLinkExpired.description"),
|
||||
};
|
||||
case "magic_link_already_used":
|
||||
return {
|
||||
title: __("Link Already Used"),
|
||||
description: __(
|
||||
"This magic link has already been used. Please request a new one.",
|
||||
),
|
||||
title: t("authError.magicLinkAlreadyUsed.title"),
|
||||
description: t("authError.magicLinkAlreadyUsed.description"),
|
||||
};
|
||||
case "magic_link_invalid":
|
||||
return {
|
||||
title: __("Invalid link"),
|
||||
description: __(
|
||||
"This magic link is invalid. Please request a new one.",
|
||||
),
|
||||
title: t("authError.invalidLink.title"),
|
||||
description: t("authError.invalidLink.description"),
|
||||
};
|
||||
default:
|
||||
return {
|
||||
title: __("Authentication failed"),
|
||||
description: __(
|
||||
"We could not complete your sign-in. Please try again.",
|
||||
),
|
||||
title: t("authError.default.title"),
|
||||
description: t("authError.default.description"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function AuthErrorPage() {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const content = useAuthErrorContent(searchParams.get("error"));
|
||||
|
||||
@@ -98,7 +84,7 @@ export default function AuthErrorPage() {
|
||||
<p className="text-txt-tertiary">{content.description}</p>
|
||||
</div>
|
||||
<Button className="w-full h-10" to="/auth/login">
|
||||
{__("Sign in")}
|
||||
{t("auth.actions.signIn")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { LaptopIcon } from "@phosphor-icons/react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Card } from "@probo/ui";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
@@ -65,8 +64,8 @@ interface EnrollDevicePageProps {
|
||||
}
|
||||
|
||||
export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
const { __ } = useTranslate();
|
||||
usePageTitle(__("Enroll device"));
|
||||
const { t } = useTranslation();
|
||||
usePageTitle(t("deviceEnrollment.pageTitle"));
|
||||
|
||||
const { viewer } = usePreloadedQuery<EnrollDevicePageQuery>(
|
||||
enrollDevicePageQuery,
|
||||
@@ -125,18 +124,18 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
const steps = [
|
||||
{
|
||||
key: "intro",
|
||||
title: __("Privacy"),
|
||||
description: __("Review collected data"),
|
||||
title: t("deviceEnrollment.steps.privacy.title"),
|
||||
description: t("deviceEnrollment.steps.privacy.description"),
|
||||
},
|
||||
{
|
||||
key: "organization",
|
||||
title: __("Organization"),
|
||||
description: __("Choose destination workspace"),
|
||||
title: t("devices.fields.organization"),
|
||||
description: t("deviceEnrollment.steps.organization.description"),
|
||||
},
|
||||
{
|
||||
key: "enroll",
|
||||
title: __("Open and wait"),
|
||||
description: __("Finish setup in the desktop agent"),
|
||||
title: t("deviceEnrollment.steps.enroll.title"),
|
||||
description: t("deviceEnrollment.steps.enroll.description"),
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -147,16 +146,14 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
<Card className="space-y-4 border-border-low p-6">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-lg font-medium">
|
||||
{__("Enrollment unavailable")}
|
||||
{t("deviceEnrollment.unavailable.title")}
|
||||
</h2>
|
||||
<p className="text-sm text-txt-secondary">
|
||||
{__(
|
||||
"You do not have permission to enroll devices in any organization.",
|
||||
)}
|
||||
{t("deviceEnrollment.unavailable.description")}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="secondary" asChild>
|
||||
<Link to="/">{__("Back to organizations")}</Link>
|
||||
<Link to="/">{t("deviceEnrollment.actions.backToOrganizations")}</Link>
|
||||
</Button>
|
||||
</Card>
|
||||
)
|
||||
@@ -169,8 +166,8 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
<LaptopIcon size={18} weight="duotone" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-txt-primary">{__("Device enrollment")}</p>
|
||||
<p className="text-xs text-txt-secondary">{__("Setup")}</p>
|
||||
<p className="text-sm font-semibold text-txt-primary">{t("deviceEnrollment.title")}</p>
|
||||
<p className="text-xs text-txt-secondary">{t("deviceEnrollment.setup")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -211,7 +208,7 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
|
||||
<div className="mt-8 md:mt-auto">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.12em] text-txt-secondary">
|
||||
{sprintf(__("Step %s of %s"), activeStepIndex, steps.length)}
|
||||
{t("deviceEnrollment.stepProgress", { current: activeStepIndex, total: steps.length })}
|
||||
</p>
|
||||
<div className="mt-3 flex gap-2">
|
||||
{steps.map((item, index) => (
|
||||
@@ -231,21 +228,19 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
{step === "intro" && (
|
||||
<section className="space-y-5">
|
||||
<header className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{__("Before you start")}</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t("deviceEnrollment.intro.title")}</h1>
|
||||
<p className="text-sm leading-6 text-txt-secondary">
|
||||
{__(
|
||||
"Probo collects the following device metadata for inventory and posture reporting:",
|
||||
)}
|
||||
{t("deviceEnrollment.intro.description")}
|
||||
</p>
|
||||
</header>
|
||||
<ul className="list-disc space-y-1.5 pl-5 text-sm text-txt-secondary">
|
||||
<li>{__("Device identity: hardware UUID, hostname, and serial number (when available).")}</li>
|
||||
<li>{__("System details: platform, OS version, and Probo agent version.")}</li>
|
||||
<li>{__("Activity signals: enrollment time, heartbeats, and posture check results.")}</li>
|
||||
<li>{t("deviceEnrollment.privacy.identity")}</li>
|
||||
<li>{t("deviceEnrollment.privacy.systemDetails")}</li>
|
||||
<li>{t("deviceEnrollment.privacy.activitySignals")}</li>
|
||||
</ul>
|
||||
<div className="flex flex-wrap gap-3 pt-2">
|
||||
<Button onClick={() => setStep("organization")}>
|
||||
{__("Continue")}
|
||||
{t("common.actions.continue")}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
@@ -254,11 +249,9 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
{step === "organization" && (
|
||||
<section className="space-y-5">
|
||||
<header className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{__("Choose organization")}</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t("deviceEnrollment.organization.title")}</h1>
|
||||
<p className="text-sm leading-6 text-txt-secondary">
|
||||
{__(
|
||||
"Pick which organization will own and manage this device.",
|
||||
)}
|
||||
{t("deviceEnrollment.organization.description")}
|
||||
</p>
|
||||
</header>
|
||||
<EnrollOrganizationPicker
|
||||
@@ -274,10 +267,10 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
onClick={() => setStep("enroll")}
|
||||
disabled={selectedOrganizationId == null}
|
||||
>
|
||||
{__("Continue")}
|
||||
{t("common.actions.continue")}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => setStep("intro")}>
|
||||
{__("Back")}
|
||||
{t("common.actions.back")}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
@@ -286,11 +279,9 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
{step === "enroll" && (
|
||||
<section className="space-y-5">
|
||||
<header className="space-y-2">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{__("Open the Probo agent")}</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t("deviceEnrollment.openAgent.title")}</h1>
|
||||
<p className="text-sm leading-6 text-txt-secondary">
|
||||
{__(
|
||||
"Open the desktop agent to finish setup, then keep this page open until enrollment is confirmed.",
|
||||
)}
|
||||
{t("deviceEnrollment.openAgent.description")}
|
||||
</p>
|
||||
</header>
|
||||
<CoreRelayProvider>
|
||||
@@ -304,7 +295,7 @@ export function EnrollDevicePage({ queryRef }: EnrollDevicePageProps) {
|
||||
{!isEnrollmentComplete && (
|
||||
<div>
|
||||
<Button variant="secondary" onClick={() => setStep("organization")}>
|
||||
{__("Back")}
|
||||
{t("common.actions.back")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface OrganizationOption {
|
||||
id: string;
|
||||
@@ -38,12 +38,12 @@ export function EnrollOrganizationPicker(
|
||||
onChange,
|
||||
}: EnrollOrganizationPickerProps,
|
||||
) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<label htmlFor="organization-select" className="space-y-2 text-xs font-medium text-txt-secondary">
|
||||
<span>{__("Organization")}</span>
|
||||
<span>{t("devices.fields.organization")}</span>
|
||||
<select
|
||||
id="organization-select"
|
||||
value={selectedOrganizationId ?? ""}
|
||||
|
||||
@@ -271,15 +271,15 @@ export function APIKeyConnectorDialog({
|
||||
if (provider.provider === "SEGMENT") {
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium">{__("Region")}</label>
|
||||
<label className="text-sm font-medium">{t("accessReviewSource.regions.label")}</label>
|
||||
<Select
|
||||
value={extraSettingValues.region ?? ""}
|
||||
onValueChange={(val: string) =>
|
||||
setExtraSettingValues(prev => ({ ...prev, region: val }))}
|
||||
placeholder={__("Select a region")}
|
||||
placeholder={t("accessReviewSource.regions.placeholder")}
|
||||
>
|
||||
<Option value="US">{__("United States")}</Option>
|
||||
<Option value="EU">{__("Europe")}</Option>
|
||||
<Option value="US">{t("accessReviewSource.regions.unitedStates")}</Option>
|
||||
<Option value="EU">{t("accessReviewSource.regions.europe")}</Option>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, IconArrowLink } from "@probo/ui";
|
||||
|
||||
type Props = {
|
||||
@@ -33,7 +33,7 @@ type Props = {
|
||||
// the provider has no documentation URL. Shared by the provider card and the
|
||||
// connect dialogs so the URL/label/target markup lives in one place.
|
||||
export function ConnectorDocumentationLink({ url, variant = "link" }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!url) {
|
||||
return null;
|
||||
@@ -45,7 +45,7 @@ export function ConnectorDocumentationLink({ url, variant = "link" }: Props) {
|
||||
return (
|
||||
<Button variant="secondary" asChild>
|
||||
<a href={url} target="_blank" rel="noopener noreferrer">
|
||||
{__("Documentation")}
|
||||
{t("accessReviewSource.documentation")}
|
||||
<IconArrowLink size={16} />
|
||||
</a>
|
||||
</Button>
|
||||
@@ -59,7 +59,7 @@ export function ConnectorDocumentationLink({ url, variant = "link" }: Props) {
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-xs text-txt-tertiary underline hover:no-underline"
|
||||
>
|
||||
{__("Documentation")}
|
||||
{t("accessReviewSource.documentation")}
|
||||
<IconArrowLink size={12} />
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
@@ -67,9 +67,9 @@ interface DeviceLayoutProps {
|
||||
}
|
||||
|
||||
export function DeviceLayout({ queryRef }: DeviceLayoutProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const organizationId = useOrganizationId();
|
||||
const pendingLabel = __("(pending)");
|
||||
const pendingLabel = t("devices.values.pending");
|
||||
|
||||
const { device, organization } = usePreloadedQuery<DeviceLayoutQuery>(
|
||||
deviceLayoutQuery,
|
||||
@@ -96,7 +96,7 @@ export function DeviceLayout({ queryRef }: DeviceLayoutProps) {
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
label: __("Devices"),
|
||||
label: t("devices.title"),
|
||||
to: `/organizations/${organizationId}/devices`,
|
||||
},
|
||||
{ label: hostnameLabel },
|
||||
@@ -113,7 +113,7 @@ export function DeviceLayout({ queryRef }: DeviceLayoutProps) {
|
||||
confirmRevoke({ id: device.id, hostname: device.hostname })}
|
||||
disabled={isRevoking}
|
||||
>
|
||||
{__("Revoke")}
|
||||
{t("devices.actions.revoke")}
|
||||
</Button>
|
||||
)}
|
||||
</PageHeader>
|
||||
@@ -124,7 +124,7 @@ export function DeviceLayout({ queryRef }: DeviceLayoutProps) {
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/devices/${device.id}/postures`}
|
||||
>
|
||||
{__("Postures")}
|
||||
{t("devices.postures.navigation")}
|
||||
</TabLink>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, IconPlusLarge, PageHeader, Tbody, Th, Thead, Tr } from "@probo/ui";
|
||||
import type { ComponentProps } from "react";
|
||||
import {
|
||||
@@ -89,10 +89,10 @@ interface DevicesPageProps {
|
||||
}
|
||||
|
||||
export function DevicesPage({ queryRef }: DevicesPageProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const organizationId = useOrganizationId();
|
||||
|
||||
usePageTitle(__("Devices"));
|
||||
usePageTitle(t("devices.title"));
|
||||
|
||||
const { organization } = usePreloadedQuery<DevicesPageQuery>(
|
||||
devicesPageQuery,
|
||||
@@ -112,10 +112,8 @@ export function DevicesPage({ queryRef }: DevicesPageProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title={__("Devices")}
|
||||
description={__(
|
||||
"Manage computers enrolled with the Probo posture agent.",
|
||||
)}
|
||||
title={t("devices.title")}
|
||||
description={t("devices.description")}
|
||||
>
|
||||
{organization.canCreateDevice && (
|
||||
<CreateDeviceDialog
|
||||
@@ -124,7 +122,7 @@ export function DevicesPage({ queryRef }: DevicesPageProps) {
|
||||
pagination.refetch({}, { fetchPolicy: "store-and-network" });
|
||||
}}
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add device")}</Button>
|
||||
<Button icon={IconPlusLarge}>{t("devices.actions.add")}</Button>
|
||||
</CreateDeviceDialog>
|
||||
)}
|
||||
</PageHeader>
|
||||
@@ -137,12 +135,12 @@ export function DevicesPage({ queryRef }: DevicesPageProps) {
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<SortableTh field="HOSTNAME">{__("Hostname")}</SortableTh>
|
||||
<Th>{__("Owner")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
<Th>{__("Platform")}</Th>
|
||||
<Th>{__("OS version")}</Th>
|
||||
<SortableTh field="LAST_SEEN_AT">{__("Last seen")}</SortableTh>
|
||||
<SortableTh field="HOSTNAME">{t("devices.fields.hostname")}</SortableTh>
|
||||
<Th>{t("devices.fields.owner")}</Th>
|
||||
<Th>{t("devices.fields.state")}</Th>
|
||||
<Th>{t("devices.fields.platform")}</Th>
|
||||
<Th>{t("devices.fields.osVersion")}</Th>
|
||||
<SortableTh field="LAST_SEEN_AT">{t("devices.fields.lastSeen")}</SortableTh>
|
||||
<Th>
|
||||
<PostureColumnHeader />
|
||||
</Th>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatDate } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Badge, Card } from "@probo/ui";
|
||||
import type { ReactNode } from "react";
|
||||
import { useFragment } from "react-relay";
|
||||
@@ -48,52 +48,52 @@ const deviceFragment = graphql`
|
||||
export function DeviceDetailsCard(props: {
|
||||
deviceFragmentRef: DeviceDetailsCard_deviceFragment$key;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const pendingLabel = __("(pending)");
|
||||
const { t } = useTranslation();
|
||||
const pendingLabel = t("devices.values.pending");
|
||||
const device = useFragment(deviceFragment, props.deviceFragmentRef);
|
||||
|
||||
return (
|
||||
<Card className="space-y-4" padded>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<DetailField
|
||||
label={__("State")}
|
||||
label={t("devices.fields.state")}
|
||||
value={
|
||||
<Badge variant={stateVariant(device.state)}>{device.state}</Badge>
|
||||
}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Owner")}
|
||||
value={device.owner?.fullName ?? __("Unassigned")}
|
||||
label={t("devices.fields.owner")}
|
||||
value={device.owner?.fullName ?? t("devices.values.unassigned")}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Hardware UUID")}
|
||||
label={t("devices.fields.hardwareUuid")}
|
||||
value={displayValue(device.hardwareUuid, pendingLabel)}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Serial number")}
|
||||
label={t("devices.fields.serialNumber")}
|
||||
value={displayValue(device.serialNumber, pendingLabel)}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Platform")}
|
||||
label={t("devices.fields.platform")}
|
||||
value={displayValue(device.platform, pendingLabel)}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("OS version")}
|
||||
label={t("devices.fields.osVersion")}
|
||||
value={displayValue(device.osVersion, pendingLabel)}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Agent version")}
|
||||
label={t("devices.fields.agentVersion")}
|
||||
value={displayValue(device.agentVersion, pendingLabel)}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Enrolled at")}
|
||||
label={t("devices.fields.enrolledAt")}
|
||||
value={
|
||||
device.enrolledAt ? formatDate(device.enrolledAt) : pendingLabel
|
||||
}
|
||||
/>
|
||||
<DetailField
|
||||
label={__("Last seen")}
|
||||
value={device.lastSeenAt ? formatDate(device.lastSeenAt) : __("Never")}
|
||||
label={t("devices.fields.lastSeen")}
|
||||
value={device.lastSeenAt ? formatDate(device.lastSeenAt) : t("devices.values.never")}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatDate } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActionDropdown,
|
||||
Badge,
|
||||
@@ -67,10 +67,10 @@ interface DeviceRowProps {
|
||||
}
|
||||
|
||||
export function DeviceRow({ canAssignDevice, canRevoke, fKey }: DeviceRowProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const organizationId = useOrganizationId();
|
||||
const reassignDialogRef = useDialogRef();
|
||||
const pendingLabel = __("(pending)");
|
||||
const pendingLabel = t("devices.values.pending");
|
||||
|
||||
const device = useFragment(deviceRowFragment, fKey);
|
||||
|
||||
@@ -89,13 +89,13 @@ export function DeviceRow({ canAssignDevice, canRevoke, fKey }: DeviceRowProps)
|
||||
/>
|
||||
<Tr to={`/organizations/${organizationId}/devices/${device.id}`}>
|
||||
<Td>{displayValue(device.hostname, pendingLabel)}</Td>
|
||||
<Td>{device.owner?.fullName ?? __("Unassigned")}</Td>
|
||||
<Td>{device.owner?.fullName ?? t("devices.values.unassigned")}</Td>
|
||||
<Td>
|
||||
<Badge variant={stateVariant(device.state)}>{device.state}</Badge>
|
||||
</Td>
|
||||
<Td>{displayValue(device.platform, pendingLabel)}</Td>
|
||||
<Td>{displayValue(device.osVersion, pendingLabel)}</Td>
|
||||
<Td>{device.lastSeenAt ? formatDate(device.lastSeenAt) : __("Never")}</Td>
|
||||
<Td>{device.lastSeenAt ? formatDate(device.lastSeenAt) : t("devices.values.never")}</Td>
|
||||
<Td>
|
||||
<span className="text-txt-success">{summary.pass}</span>
|
||||
{" / "}
|
||||
@@ -113,7 +113,7 @@ export function DeviceRow({ canAssignDevice, canRevoke, fKey }: DeviceRowProps)
|
||||
icon={IconUser}
|
||||
onClick={() => reassignDialogRef.current?.open()}
|
||||
>
|
||||
{__("Re-assign")}
|
||||
{t("devices.actions.reassign")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{canRevoke && (
|
||||
@@ -124,7 +124,7 @@ export function DeviceRow({ canAssignDevice, canRevoke, fKey }: DeviceRowProps)
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Revoke")}
|
||||
{t("devices.actions.revoke")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
</ActionDropdown>
|
||||
|
||||
@@ -18,22 +18,22 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconCircleInfo } from "@probo/ui";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
|
||||
export function PostureColumnHeader() {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
{__("Posture")}
|
||||
{t("devices.postures.title")}
|
||||
<Popover.Root>
|
||||
<Popover.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex text-txt-tertiary hover:text-txt-secondary"
|
||||
aria-label={__("How posture summary is calculated")}
|
||||
aria-label={t("devices.postures.summaryHelp")}
|
||||
>
|
||||
<IconCircleInfo size={14} />
|
||||
</button>
|
||||
@@ -45,9 +45,7 @@ export function PostureColumnHeader() {
|
||||
align="start"
|
||||
>
|
||||
<p>
|
||||
{__(
|
||||
"Shown as pass / fail / total.",
|
||||
)}
|
||||
{t("devices.postures.summaryDescription")}
|
||||
</p>
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatError, sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useConfirm, useToast } from "@probo/ui";
|
||||
import { useCallback } from "react";
|
||||
import { useMutation } from "react-relay";
|
||||
@@ -48,10 +48,10 @@ interface RevokeDeviceInput {
|
||||
}
|
||||
|
||||
export function useRevokeDevice() {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const confirm = useConfirm();
|
||||
const pendingLabel = __("(pending)");
|
||||
const pendingLabel = t("devices.values.pending");
|
||||
|
||||
const [revokeDevice, isRevoking] = useMutation<useRevokeDeviceMutation>(
|
||||
revokeDeviceMutation,
|
||||
@@ -67,14 +67,14 @@ export function useRevokeDevice() {
|
||||
onCompleted(_, errors) {
|
||||
if (errors?.length) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: errors[0].message,
|
||||
variant: "error",
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __("Device revoked"),
|
||||
title: t("common.success"),
|
||||
description: t("devices.messages.revoked"),
|
||||
variant: "success",
|
||||
});
|
||||
}
|
||||
@@ -82,9 +82,9 @@ export function useRevokeDevice() {
|
||||
},
|
||||
onError(error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: formatError(
|
||||
__("Failed to revoke device"),
|
||||
t("devices.errors.revoke"),
|
||||
error,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -94,18 +94,13 @@ export function useRevokeDevice() {
|
||||
});
|
||||
}),
|
||||
{
|
||||
message: sprintf(
|
||||
__(
|
||||
"Revoke device \"%s\"? The agent on the device will stop reporting and must be re-enrolled.",
|
||||
),
|
||||
displayValue(device.hostname, pendingLabel),
|
||||
),
|
||||
message: t("devices.confirmations.revoke", { hostname: displayValue(device.hostname, pendingLabel) }),
|
||||
variant: "danger",
|
||||
label: __("Revoke"),
|
||||
label: t("devices.actions.revoke"),
|
||||
},
|
||||
);
|
||||
},
|
||||
[__, confirm, pendingLabel, revokeDevice, toast],
|
||||
[t, confirm, pendingLabel, revokeDevice, toast],
|
||||
);
|
||||
|
||||
return [confirmRevoke, isRevoking] as const;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
@@ -67,7 +67,7 @@ export function CreateDeviceDialog({
|
||||
organizationId,
|
||||
onCreated,
|
||||
}: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const dialogRef = useDialogRef();
|
||||
const [enrollment, setEnrollment] = useState<{
|
||||
@@ -115,7 +115,7 @@ export function CreateDeviceDialog({
|
||||
onCompleted(response, errors) {
|
||||
if (errors?.length) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: errors[0].message,
|
||||
variant: "error",
|
||||
});
|
||||
@@ -127,18 +127,16 @@ export function CreateDeviceDialog({
|
||||
serverUrl: response.createDevice.serverUrl,
|
||||
});
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __(
|
||||
"Device created. Copy the enrollment token now — it will not be shown again.",
|
||||
),
|
||||
title: t("common.success"),
|
||||
description: t("deviceEnrollment.messages.created"),
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
onError(error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: formatError(
|
||||
__("Failed to create device"),
|
||||
t("devices.errors.create"),
|
||||
error,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -153,7 +151,7 @@ export function CreateDeviceDialog({
|
||||
trigger={children}
|
||||
onClose={handleClose}
|
||||
closable={!isCreating}
|
||||
title={<Breadcrumb items={[__("Devices"), __("New device")]} />}
|
||||
title={<Breadcrumb items={[t("devices.title"), t("devices.actions.new")]} />}
|
||||
>
|
||||
<form onSubmit={e => void onSubmit(e)} className="space-y-4">
|
||||
<DialogContent padded className="space-y-4">
|
||||
@@ -169,7 +167,7 @@ export function CreateDeviceDialog({
|
||||
organizationId={organizationId}
|
||||
control={control}
|
||||
name="ownerId"
|
||||
label={__("Owner")}
|
||||
label={t("devices.fields.owner")}
|
||||
optional
|
||||
/>
|
||||
)}
|
||||
@@ -181,14 +179,14 @@ export function CreateDeviceDialog({
|
||||
type="button"
|
||||
onClick={closeDialog}
|
||||
>
|
||||
{__("Close")}
|
||||
{t("common.actions.close")}
|
||||
</Button>
|
||||
</footer>
|
||||
)
|
||||
: (
|
||||
<DialogFooter>
|
||||
<Button disabled={formState.isSubmitting || isCreating} type="submit">
|
||||
{__("Create")}
|
||||
{t("common.create")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
)}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
@@ -77,7 +77,7 @@ export function ReassignDeviceDialog({
|
||||
organizationId,
|
||||
ref: refProps,
|
||||
}: ReassignDeviceDialogProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const dialogRef = useDialogRef();
|
||||
const ref = refProps ?? dialogRef;
|
||||
@@ -121,7 +121,7 @@ export function ReassignDeviceDialog({
|
||||
onCompleted(_, errors) {
|
||||
if (errors?.length) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: errors[0].message,
|
||||
variant: "error",
|
||||
});
|
||||
@@ -129,8 +129,8 @@ export function ReassignDeviceDialog({
|
||||
}
|
||||
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __("Device owner updated"),
|
||||
title: t("common.success"),
|
||||
description: t("devices.messages.ownerUpdated"),
|
||||
variant: "success",
|
||||
});
|
||||
handleClose();
|
||||
@@ -138,9 +138,9 @@ export function ReassignDeviceDialog({
|
||||
},
|
||||
onError(error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: formatError(
|
||||
__("Failed to re-assign device"),
|
||||
t("devices.errors.reassign"),
|
||||
error,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -154,7 +154,7 @@ export function ReassignDeviceDialog({
|
||||
ref={ref}
|
||||
onClose={handleClose}
|
||||
onOpenChange={setOpen}
|
||||
title={<Breadcrumb items={[__("Devices"), __("Re-assign")]} />}
|
||||
title={<Breadcrumb items={[t("devices.title"), t("devices.actions.reassign")]} />}
|
||||
>
|
||||
<form onSubmit={e => void handleSubmit(onSubmit)(e)} className="space-y-4">
|
||||
<DialogContent padded className="space-y-4">
|
||||
@@ -163,14 +163,14 @@ export function ReassignDeviceDialog({
|
||||
organizationId={organizationId}
|
||||
control={control}
|
||||
name="ownerId"
|
||||
label={__("Owner")}
|
||||
label={t("devices.fields.owner")}
|
||||
optional
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button disabled={formState.isSubmitting || isInFlight} type="submit">
|
||||
{__("Re-assign")}
|
||||
{t("devices.actions.reassign")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui";
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
@@ -41,23 +41,23 @@ interface DevicePostureListProps {
|
||||
}
|
||||
|
||||
export function DevicePostureList({ deviceFragmentRef }: DevicePostureListProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const device = useFragment(deviceFragment, deviceFragmentRef);
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Check")}</Th>
|
||||
<Th>{__("Status")}</Th>
|
||||
<Th className="text-end">{__("Observed at")}</Th>
|
||||
<Th>{t("devices.postures.columns.check")}</Th>
|
||||
<Th>{t("devices.postures.columns.status")}</Th>
|
||||
<Th className="text-end">{t("devices.postures.columns.observedAt")}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{device.latestPostures.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={3} className="text-center text-txt-secondary">
|
||||
{__("No posture checks recorded")}
|
||||
{t("devices.postures.empty")}
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Badge, Td, Tr } from "@probo/ui";
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
@@ -42,19 +42,19 @@ interface DevicePostureListItemProps {
|
||||
}
|
||||
|
||||
export function DevicePostureListItem({ postureKey }: DevicePostureListItemProps) {
|
||||
const { __, dateTimeFormat } = useTranslate();
|
||||
const { i18n, t } = useTranslation();
|
||||
const posture = useFragment(postureFragment, postureKey);
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td>{getPostureCheckLabel(__, posture.checkKey)}</Td>
|
||||
<Td>{getPostureCheckLabel(t, posture.checkKey)}</Td>
|
||||
<Td>
|
||||
<Badge variant={statusVariant(posture.status)}>
|
||||
{getPostureStatusLabel(__, posture.status)}
|
||||
{getPostureStatusLabel(t, posture.status)}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td className="text-end whitespace-nowrap">
|
||||
{dateTimeFormat(posture.observedAt, {
|
||||
{new Intl.DateTimeFormat(i18n.language, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
@@ -62,7 +62,7 @@ export function DevicePostureListItem({ postureKey }: DevicePostureListItemProps
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
})}
|
||||
}).format(posture.observedAt)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
||||
@@ -18,21 +18,21 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
type Translator = (s: string) => string;
|
||||
type Translator = (key: string) => string;
|
||||
|
||||
const checkKeyLabels: Record<string, string> = {
|
||||
DISK_ENCRYPTION: "Disk encryption",
|
||||
SCREEN_LOCK: "Screen lock",
|
||||
FIREWALL_ENABLED: "Firewall enabled",
|
||||
TIME_SYNC: "Time sync",
|
||||
OS_VERSION: "OS version",
|
||||
AUTO_UPDATE: "Auto update",
|
||||
PASSWORD_POLICY: "Password policy",
|
||||
REMOTE_LOGIN: "Remote login",
|
||||
MALWARE_PROTECTION: "Malware protection",
|
||||
DISK_ENCRYPTION: "devices.postures.checks.diskEncryption",
|
||||
SCREEN_LOCK: "devices.postures.checks.screenLock",
|
||||
FIREWALL_ENABLED: "devices.postures.checks.firewallEnabled",
|
||||
TIME_SYNC: "devices.postures.checks.timeSync",
|
||||
OS_VERSION: "devices.postures.checks.osVersion",
|
||||
AUTO_UPDATE: "devices.postures.checks.autoUpdate",
|
||||
PASSWORD_POLICY: "devices.postures.checks.passwordPolicy",
|
||||
REMOTE_LOGIN: "devices.postures.checks.remoteLogin",
|
||||
MALWARE_PROTECTION: "devices.postures.checks.malwareProtection",
|
||||
};
|
||||
|
||||
export function getPostureCheckLabel(__: Translator, checkKey: string) {
|
||||
export function getPostureCheckLabel(t: Translator, checkKey: string) {
|
||||
const label = checkKeyLabels[checkKey];
|
||||
return label ? __(label) : checkKey;
|
||||
return label ? t(label) : checkKey;
|
||||
}
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
type Translator = (s: string) => string;
|
||||
type Translator = (key: string) => string;
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
PASS: "Pass",
|
||||
FAIL: "Fail",
|
||||
UNKNOWN: "Unknown",
|
||||
NOT_APPLICABLE: "Not applicable",
|
||||
PASS: "devices.postures.status.pass",
|
||||
FAIL: "devices.postures.status.fail",
|
||||
UNKNOWN: "devices.postures.status.unknown",
|
||||
NOT_APPLICABLE: "devices.postures.status.notApplicable",
|
||||
};
|
||||
|
||||
export function getPostureStatusLabel(__: Translator, status: string) {
|
||||
export function getPostureStatusLabel(t: Translator, status: string) {
|
||||
const label = statusLabels[status];
|
||||
return label ? __(label) : status;
|
||||
return label ? t(label) : status;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Card, IconPlusLarge, Tbody, Th, Thead, Tr } from "@probo/ui";
|
||||
import { useTransition } from "react";
|
||||
import {
|
||||
@@ -74,9 +74,9 @@ interface EmployeeDevicesPageProps {
|
||||
}
|
||||
|
||||
export function EmployeeDevicesPage({ queryRef }: EmployeeDevicesPageProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
usePageTitle(__("Devices"));
|
||||
usePageTitle(t("devices.title"));
|
||||
|
||||
const { viewer, organization } = usePreloadedQuery<EmployeeDevicesPageQuery>(
|
||||
employeeDevicesPageQuery,
|
||||
@@ -105,10 +105,10 @@ export function EmployeeDevicesPage({ queryRef }: EmployeeDevicesPageProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<header className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-semibold">{__("Your devices")}</h1>
|
||||
<h1 className="text-2xl font-semibold">{t("employeeDevices.title")}</h1>
|
||||
{canEnrollDevice && (
|
||||
<Button to="/enroll" icon={IconPlusLarge}>
|
||||
{__("Enroll new device")}
|
||||
{t("deviceEnrollment.actions.enrollNew")}
|
||||
</Button>
|
||||
)}
|
||||
</header>
|
||||
@@ -119,11 +119,11 @@ export function EmployeeDevicesPage({ queryRef }: EmployeeDevicesPageProps) {
|
||||
<table className="w-full table-fixed">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th className="text-left">{__("Hostname")}</Th>
|
||||
<Th className="w-32 text-left">{__("State")}</Th>
|
||||
<Th className="w-32 text-left">{__("Platform")}</Th>
|
||||
<Th className="w-40 text-left">{__("OS version")}</Th>
|
||||
<Th className="w-40 text-left">{__("Last seen")}</Th>
|
||||
<Th className="text-left">{t("devices.fields.hostname")}</Th>
|
||||
<Th className="w-32 text-left">{t("devices.fields.state")}</Th>
|
||||
<Th className="w-32 text-left">{t("devices.fields.platform")}</Th>
|
||||
<Th className="w-40 text-left">{t("devices.fields.osVersion")}</Th>
|
||||
<Th className="w-40 text-left">{t("devices.fields.lastSeen")}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -139,7 +139,7 @@ export function EmployeeDevicesPage({ queryRef }: EmployeeDevicesPageProps) {
|
||||
: (
|
||||
<div className="px-4 py-12 text-center">
|
||||
<h3 className="text-lg font-semibold">
|
||||
{__("No devices enrolled yet")}
|
||||
{t("devices.empty")}
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function EmployeeTabsLayout() {
|
||||
{t("employeeTabsLayout.tabs.approvals")}
|
||||
</TabLink>
|
||||
<TabLink to="devices" end>
|
||||
{__("Devices")}
|
||||
{t("devices.title")}
|
||||
</TabLink>
|
||||
</Tabs>
|
||||
<Outlet />
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -53,7 +53,7 @@ interface CreateDeviceFormProps {
|
||||
}
|
||||
|
||||
export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const dialogRef = useDialogRef();
|
||||
|
||||
@@ -90,7 +90,7 @@ export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
onCompleted(response, errors) {
|
||||
if (errors?.length) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: errors[0].message,
|
||||
variant: "error",
|
||||
});
|
||||
@@ -104,18 +104,16 @@ export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
});
|
||||
dialogRef.current?.open();
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __(
|
||||
"Device created. Copy the enrollment token now — it will not be shown again.",
|
||||
),
|
||||
title: t("common.success"),
|
||||
description: t("deviceEnrollment.messages.created"),
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
onError(error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: formatError(
|
||||
__("Failed to create device"),
|
||||
t("devices.errors.create"),
|
||||
error,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -135,7 +133,7 @@ export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
return (
|
||||
<>
|
||||
<p className="text-center text-xs text-txt-secondary">
|
||||
{__("Can't enroll new device?")}
|
||||
{t("deviceEnrollment.manual.cannotEnroll")}
|
||||
{" "}
|
||||
<button
|
||||
type="button"
|
||||
@@ -143,7 +141,7 @@ export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
disabled={isCreating}
|
||||
className="text-txt-primary underline hover:no-underline disabled:opacity-60"
|
||||
>
|
||||
{__("Try creating it manually")}
|
||||
{t("deviceEnrollment.manual.tryCreating")}
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@@ -151,11 +149,11 @@ export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
ref={dialogRef}
|
||||
onClose={handleClose}
|
||||
closable={!(isCreating && !enrollment)}
|
||||
title={__("Manual enrollment")}
|
||||
title={t("deviceEnrollment.manual.title")}
|
||||
>
|
||||
<DialogContent padded className="space-y-4">
|
||||
{isCreating && !enrollment
|
||||
? <p>{__("Creating device…")}</p>
|
||||
? <p>{t("deviceEnrollment.manual.creating")}</p>
|
||||
: null}
|
||||
{enrollment
|
||||
? (
|
||||
@@ -170,7 +168,7 @@ export function CreateDeviceForm({ onDeviceCreated }: CreateDeviceFormProps) {
|
||||
? (
|
||||
<footer className="flex items-center justify-end gap-2 border-t border-t-border-low p-3">
|
||||
<Button type="button" onClick={closeDialog}>
|
||||
{__("Close")}
|
||||
{t("common.actions.close")}
|
||||
</Button>
|
||||
</footer>
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatDate } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Badge, Td, Tr } from "@probo/ui";
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
@@ -54,8 +54,8 @@ function stateVariant(state: string): "success" | "warning" | "info" {
|
||||
}
|
||||
|
||||
export function EmployeeDeviceListItem({ deviceKey }: EmployeeDeviceListItemProps) {
|
||||
const { __ } = useTranslate();
|
||||
const pendingLabel = __("(pending)");
|
||||
const { t } = useTranslation();
|
||||
const pendingLabel = t("devices.values.pending");
|
||||
|
||||
const device = useFragment(employeeDeviceListItemFragment, deviceKey);
|
||||
|
||||
@@ -67,7 +67,7 @@ export function EmployeeDeviceListItem({ deviceKey }: EmployeeDeviceListItemProp
|
||||
</Td>
|
||||
<Td>{displayValue(device.platform, pendingLabel)}</Td>
|
||||
<Td>{displayValue(device.osVersion, pendingLabel)}</Td>
|
||||
<Td>{device.lastSeenAt ? formatDate(device.lastSeenAt) : __("Never")}</Td>
|
||||
<Td>{device.lastSeenAt ? formatDate(device.lastSeenAt) : t("devices.values.never")}</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Card, useToast } from "@probo/ui";
|
||||
|
||||
const RELEASE_BASE_URL
|
||||
@@ -32,7 +32,7 @@ interface EnrollmentInstructionsProps {
|
||||
export function EnrollmentInstructions(
|
||||
{ enrollmentToken, serverUrl }: EnrollmentInstructionsProps,
|
||||
) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const unixCommand = `# 1. Download and install the probo-agent binary
|
||||
OS=$(uname -s); ARCH=$(uname -m | sed 's/aarch64/arm64/; s/amd64/x86_64/')
|
||||
@@ -65,42 +65,34 @@ Remove-Item -Recurse -Force $zip, "$env:TEMP\\$name"
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="font-medium">{__("Enrollment token generated")}</h2>
|
||||
<h2 className="font-medium">{t("deviceEnrollment.token.title")}</h2>
|
||||
<p className="text-sm text-txt-secondary">
|
||||
{__(
|
||||
"Share this enrollment token only with the device owner through a secure channel. It can be used once and expires after seven days.",
|
||||
)}
|
||||
{t("deviceEnrollment.token.description")}
|
||||
</p>
|
||||
<CopyableCodeBlock code={enrollmentToken} />
|
||||
|
||||
<details>
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
{__("Manual install (CLI / MDM)")}
|
||||
{t("deviceEnrollment.token.manualInstall")}
|
||||
</summary>
|
||||
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">
|
||||
{__(
|
||||
"Install on macOS or Linux (run from a shell with sudo access)",
|
||||
)}
|
||||
{t("deviceEnrollment.token.installUnix")}
|
||||
</h3>
|
||||
<CopyableCodeBlock code={unixCommand} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">
|
||||
{__(
|
||||
"Install on Windows (run from an elevated PowerShell session)",
|
||||
)}
|
||||
{t("deviceEnrollment.token.installWindows")}
|
||||
</h3>
|
||||
<CopyableCodeBlock code={windowsCommand} />
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-txt-secondary">
|
||||
{__(
|
||||
"The token is passed as a CLI flag (not via curl-piped-to-shell or sudo env vars). Once installed, the agent self-updates from GitHub Releases with cosign signature verification.",
|
||||
)}
|
||||
{t("deviceEnrollment.token.securityNotice")}
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
@@ -109,22 +101,22 @@ Remove-Item -Recurse -Force $zip, "$env:TEMP\\$name"
|
||||
}
|
||||
|
||||
function CopyableCodeBlock({ code }: { code: string }) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(code).then(
|
||||
() => {
|
||||
toast({
|
||||
title: __("Copied"),
|
||||
description: __("Copied to clipboard"),
|
||||
title: t("common.messages.copied"),
|
||||
description: t("common.messages.copiedToClipboard"),
|
||||
variant: "success",
|
||||
});
|
||||
},
|
||||
() => {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: __("Failed to copy to clipboard"),
|
||||
title: t("common.error"),
|
||||
description: t("deviceEnrollment.errors.copyToClipboard"),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
@@ -135,7 +127,7 @@ function CopyableCodeBlock({ code }: { code: string }) {
|
||||
<Card className="rounded-lg border">
|
||||
<div className="flex items-center justify-end border-b border-border-low px-1 py-1">
|
||||
<Button type="button" variant="secondary" onClick={handleCopy}>
|
||||
{__("Copy")}
|
||||
{t("common.actions.copy")}
|
||||
</Button>
|
||||
</div>
|
||||
<pre className="overflow-x-auto whitespace-pre p-4 text-sm font-mono rounded-b-lg text-invert bg-accent">
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { formatError, sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { formatError } from "@probo/helpers";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
IconArrowsClockwise,
|
||||
@@ -80,7 +80,7 @@ function EnrollDeviceButtonContent(
|
||||
onComplete,
|
||||
}: EnrollDeviceButtonProps,
|
||||
) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const environment = useRelayEnvironment();
|
||||
const matchingSession
|
||||
@@ -212,7 +212,7 @@ function EnrollDeviceButtonContent(
|
||||
onCompleted(response, errors) {
|
||||
if (errors?.length) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: errors[0].message,
|
||||
variant: "error",
|
||||
});
|
||||
@@ -232,9 +232,9 @@ function EnrollDeviceButtonContent(
|
||||
},
|
||||
onError(error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
title: t("common.error"),
|
||||
description: formatError(
|
||||
__("Failed to create device"),
|
||||
t("devices.errors.create"),
|
||||
error,
|
||||
),
|
||||
variant: "error",
|
||||
@@ -249,11 +249,11 @@ function EnrollDeviceButtonContent(
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-txt-success">
|
||||
<IconCircleCheck size={18} />
|
||||
{deviceHostname
|
||||
? sprintf(__("%s is enrolled."), deviceHostname)
|
||||
: __("This device is enrolled.")}
|
||||
? t("deviceEnrollment.status.enrolledWithHostname", { hostname: deviceHostname })
|
||||
: t("deviceEnrollment.status.enrolled")}
|
||||
</div>
|
||||
<p className="text-sm text-txt-secondary">
|
||||
{__("You can close this window.")}
|
||||
{t("deviceEnrollment.status.closeWindow")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -263,7 +263,7 @@ function EnrollDeviceButtonContent(
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-txt-secondary">
|
||||
<IconArrowsClockwise size={18} className="animate-spin" />
|
||||
{__("Waiting for the agent's first check-in…")}
|
||||
{t("deviceEnrollment.status.waitingForCheckIn")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -272,9 +272,7 @@ function EnrollDeviceButtonContent(
|
||||
<div className="space-y-2">
|
||||
{hasTimedOut && (
|
||||
<p className="text-sm text-txt-secondary">
|
||||
{__(
|
||||
"We haven't heard from the agent yet. Make sure the desktop agent is installed and running, then try again.",
|
||||
)}
|
||||
{t("deviceEnrollment.status.timedOut")}
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
@@ -282,10 +280,10 @@ function EnrollDeviceButtonContent(
|
||||
disabled={isCreating || organizationId == null}
|
||||
>
|
||||
{isCreating
|
||||
? __("Preparing…")
|
||||
? t("deviceEnrollment.actions.preparing")
|
||||
: hasTimedOut
|
||||
? __("Try again")
|
||||
: __("Open Probo agent")}
|
||||
? t("common.actions.tryAgain")
|
||||
: t("deviceEnrollment.actions.openAgent")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user