mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-30 14:50:45 +00:00
Compare commits
162 Commits
msg-delive
...
crowdin_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8f81145df | ||
|
|
61525dcf70 | ||
|
|
7c8f58e593 | ||
|
|
74c75e64d3 | ||
|
|
7d961ebf8b | ||
|
|
59c19fa25c | ||
|
|
45add21051 | ||
|
|
aa4d62c93b | ||
|
|
405b18e2c0 | ||
|
|
5ebbd8f3c2 | ||
|
|
f981687f42 | ||
|
|
4333993e3d | ||
|
|
6fd0de3192 | ||
|
|
88b6587d6f | ||
|
|
db0f7cfbae | ||
|
|
1724885371 | ||
|
|
a97e9ea8b1 | ||
|
|
9d30e97526 | ||
|
|
b91330a27a | ||
|
|
744bc9ebe9 | ||
|
|
89ed9e6d7f | ||
|
|
b007e7f54a | ||
|
|
6651a6df42 | ||
|
|
3f29b165aa | ||
|
|
b13b91face | ||
|
|
63c14fe2d5 | ||
|
|
14e74ed02d | ||
|
|
7e30750618 | ||
|
|
4d1dd16be5 | ||
|
|
fa49cf5eba | ||
|
|
26b39fc1c6 | ||
|
|
0d36e368ea | ||
|
|
859f265c68 | ||
|
|
3219f520ba | ||
|
|
97e27b6caf | ||
|
|
09da83a72b | ||
|
|
d13b210e2f | ||
|
|
09fb672718 | ||
|
|
9797ad0e17 | ||
|
|
8b3d61ac36 | ||
|
|
7161c9547a | ||
|
|
60d4362a87 | ||
|
|
1836e0c8fc | ||
|
|
d3344aeb34 | ||
|
|
cfeb093fa6 | ||
|
|
a469b3ffcc | ||
|
|
14b3a3fdd8 | ||
|
|
94367ce387 | ||
|
|
5be518aa50 | ||
|
|
d059a8da9e | ||
|
|
1dcacbef7a | ||
|
|
a25edeccf7 | ||
|
|
315f73c77d | ||
|
|
666288fccc | ||
|
|
0ccf61c2a9 | ||
|
|
c16b1b27a3 | ||
|
|
ed9ba60be6 | ||
|
|
24d047e3d8 | ||
|
|
9671079ffb | ||
|
|
688892523c | ||
|
|
b02c341f62 | ||
|
|
3e9bcada1e | ||
|
|
93d4bd6438 | ||
|
|
5146498b33 | ||
|
|
72da4f39a8 | ||
|
|
a2b2fb804b | ||
|
|
3eac80e666 | ||
|
|
718d2122a4 | ||
|
|
310c6c90a3 | ||
|
|
9d80f62d58 | ||
|
|
77032fc989 | ||
|
|
64e6086f0c | ||
|
|
3aa58fdc8f | ||
|
|
93bc6ba615 | ||
|
|
36690d63cb | ||
|
|
9896e9799a | ||
|
|
27afc82b79 | ||
|
|
1c8f01ce7b | ||
|
|
4038ccff0d | ||
|
|
5b41bc2f59 | ||
|
|
014ba760b5 | ||
|
|
96a91ccf09 | ||
|
|
347fbd2a48 | ||
|
|
29723052ab | ||
|
|
86415d675b | ||
|
|
8fc4a0dc48 | ||
|
|
e14670cdda | ||
|
|
4d73488f0c | ||
|
|
46e62b24cf | ||
|
|
17c3041fe9 | ||
|
|
d5ae381528 | ||
|
|
e2e09527ec | ||
|
|
3ce1afbcc9 | ||
|
|
1f077d7ec2 | ||
|
|
adf3d0347b | ||
|
|
7ed8b16a53 | ||
|
|
9f7c162107 | ||
|
|
fb15f8cde6 | ||
|
|
45ecfcc6bb | ||
|
|
c6f947e470 | ||
|
|
adf5caf18a | ||
|
|
0b8068e13d | ||
|
|
f143d2e214 | ||
|
|
2e802301ae | ||
|
|
7305c721a6 | ||
|
|
b299f3d6aa | ||
|
|
e09cd6c16c | ||
|
|
b7df8b7319 | ||
|
|
c92b5942fc | ||
|
|
fe729ec762 | ||
|
|
915673798e | ||
|
|
9527fe4f26 | ||
|
|
e8a8b3f664 | ||
|
|
d6a829abc2 | ||
|
|
1a36cd0317 | ||
|
|
75005ccf81 | ||
|
|
fd6c600531 | ||
|
|
6996c2501e | ||
|
|
efbd9bdb56 | ||
|
|
0d34213647 | ||
|
|
870b85d71b | ||
|
|
86ba6b6f86 | ||
|
|
02be3cd0c4 | ||
|
|
1b756ef9a0 | ||
|
|
ceda06f9ae | ||
|
|
068eba015b | ||
|
|
7ae6b2df05 | ||
|
|
6765d5ad26 | ||
|
|
35cfd6bec9 | ||
|
|
90f66baf85 | ||
|
|
5edfed78f2 | ||
|
|
fd6a3e5a17 | ||
|
|
14a4b1b4b4 | ||
|
|
5743c0bb72 | ||
|
|
acca1b6a91 | ||
|
|
355265cd1e | ||
|
|
6ec8d143fa | ||
|
|
8ae327e8f5 | ||
|
|
c03a61f613 | ||
|
|
89928c753c | ||
|
|
a56fcc0fba | ||
|
|
43c60bcdbc | ||
|
|
a3fa12f0e4 | ||
|
|
d696556097 | ||
|
|
6a45151741 | ||
|
|
34e2fbefb9 | ||
|
|
f7cede4713 | ||
|
|
610b20c1ff | ||
|
|
fb19e10cdc | ||
|
|
2f1756ccf2 | ||
|
|
ce632a25cf | ||
|
|
ec10c37468 | ||
|
|
5ee3e140ed | ||
|
|
888f5f8bb6 | ||
|
|
9114dd5992 | ||
|
|
a126494c12 | ||
|
|
79ba804c88 | ||
|
|
e2cbe11a5f | ||
|
|
f4496bb23a | ||
|
|
c93766bb48 | ||
|
|
1065004fa3 | ||
|
|
8e1fd4474f |
6
.github/workflows/cicd.yml
vendored
6
.github/workflows/cicd.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
uses: aws-actions/configure-aws-credentials@v5
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
@@ -264,7 +264,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.24
|
go-version: 1.24
|
||||||
|
|
||||||
@@ -513,7 +513,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
uses: aws-actions/configure-aws-credentials@v5
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
|
|||||||
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/saas.yml
vendored
6
.github/workflows/saas.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
uses: aws-actions/configure-aws-credentials@v5
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
uses: aws-actions/configure-aws-credentials@v5
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.aws_account_id }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.aws_account_id }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
uses: aws-actions/configure-aws-credentials@v5
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
|||||||
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
||||||
|
|
||||||
COPY server/db/names.json ./dist/names.json
|
COPY server/db/names.json ./dist/names.json
|
||||||
|
COPY server/db/ios_models.json ./dist/ios_models.json
|
||||||
|
COPY server/db/mac_models.json ./dist/mac_models.json
|
||||||
COPY public ./public
|
COPY public ./public
|
||||||
|
|
||||||
# OCI Image Labels
|
# OCI Image Labels
|
||||||
|
|||||||
36
cli/commands/clearLicenseKeys.ts
Normal file
36
cli/commands/clearLicenseKeys.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { CommandModule } from "yargs";
|
||||||
|
import { db, licenseKey } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
type ClearLicenseKeysArgs = { };
|
||||||
|
|
||||||
|
export const clearLicenseKeys: CommandModule<
|
||||||
|
{},
|
||||||
|
ClearLicenseKeysArgs
|
||||||
|
> = {
|
||||||
|
command: "clear-license-keys",
|
||||||
|
describe:
|
||||||
|
"Clear all license keys from the database",
|
||||||
|
// no args
|
||||||
|
builder: (yargs) => {
|
||||||
|
return yargs;
|
||||||
|
},
|
||||||
|
handler: async (argv: {}) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
console.log(`Clearing all license keys from the database`);
|
||||||
|
|
||||||
|
// Delete all license keys
|
||||||
|
const deletedCount = await db
|
||||||
|
.delete(licenseKey)
|
||||||
|
.where(eq(licenseKey.licenseKeyId, licenseKey.licenseKeyId)) .returning();; // delete all
|
||||||
|
|
||||||
|
console.log(`Deleted ${deletedCount.length} license key(s) from the database`);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
123
cli/commands/deleteClient.ts
Normal file
123
cli/commands/deleteClient.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { CommandModule } from "yargs";
|
||||||
|
import { db, clients, olms, currentFingerprint, userClients, approvals } from "@server/db";
|
||||||
|
import { eq, and, inArray } from "drizzle-orm";
|
||||||
|
|
||||||
|
type DeleteClientArgs = {
|
||||||
|
orgId: string;
|
||||||
|
niceId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteClient: CommandModule<{}, DeleteClientArgs> = {
|
||||||
|
command: "delete-client",
|
||||||
|
describe:
|
||||||
|
"Delete a client and all associated data (OLMs, current fingerprint, userClients, approvals). Snapshots are preserved.",
|
||||||
|
builder: (yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("orgId", {
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
describe: "The organization ID"
|
||||||
|
})
|
||||||
|
.option("niceId", {
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
describe: "The client niceId (identifier)"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (argv: { orgId: string; niceId: string }) => {
|
||||||
|
try {
|
||||||
|
const { orgId, niceId } = argv;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Deleting client with orgId: ${orgId}, niceId: ${niceId}...`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find the client
|
||||||
|
const [client] = await db
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(and(eq(clients.orgId, orgId), eq(clients.niceId, niceId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
console.error(
|
||||||
|
`Error: Client with orgId "${orgId}" and niceId "${niceId}" not found.`
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientId = client.clientId;
|
||||||
|
console.log(`Found client with clientId: ${clientId}`);
|
||||||
|
|
||||||
|
// Find all OLMs associated with this client
|
||||||
|
const associatedOlms = await db
|
||||||
|
.select()
|
||||||
|
.from(olms)
|
||||||
|
.where(eq(olms.clientId, clientId));
|
||||||
|
|
||||||
|
console.log(`Found ${associatedOlms.length} OLM(s) associated with this client`);
|
||||||
|
|
||||||
|
// Delete in a transaction to ensure atomicity
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
// Delete currentFingerprint entries for the associated OLMs
|
||||||
|
// Note: We delete these explicitly before deleting OLMs to ensure
|
||||||
|
// we have control, even though cascade would handle it
|
||||||
|
let fingerprintCount = 0;
|
||||||
|
if (associatedOlms.length > 0) {
|
||||||
|
const olmIds = associatedOlms.map((olm) => olm.olmId);
|
||||||
|
const deletedFingerprints = await trx
|
||||||
|
.delete(currentFingerprint)
|
||||||
|
.where(inArray(currentFingerprint.olmId, olmIds))
|
||||||
|
.returning();
|
||||||
|
fingerprintCount = deletedFingerprints.length;
|
||||||
|
}
|
||||||
|
console.log(`Deleted ${fingerprintCount} current fingerprint(s)`);
|
||||||
|
|
||||||
|
// Delete OLMs
|
||||||
|
// Note: OLMs have onDelete: "set null" for clientId, so we need to delete them explicitly
|
||||||
|
const deletedOlms = await trx
|
||||||
|
.delete(olms)
|
||||||
|
.where(eq(olms.clientId, clientId))
|
||||||
|
.returning();
|
||||||
|
console.log(`Deleted ${deletedOlms.length} OLM(s)`);
|
||||||
|
|
||||||
|
// Delete approvals
|
||||||
|
// Note: Approvals have onDelete: "cascade" but we delete explicitly for clarity
|
||||||
|
const deletedApprovals = await trx
|
||||||
|
.delete(approvals)
|
||||||
|
.where(eq(approvals.clientId, clientId))
|
||||||
|
.returning();
|
||||||
|
console.log(`Deleted ${deletedApprovals.length} approval(s)`);
|
||||||
|
|
||||||
|
// Delete userClients
|
||||||
|
// Note: userClients have onDelete: "cascade" but we delete explicitly for clarity
|
||||||
|
const deletedUserClients = await trx
|
||||||
|
.delete(userClients)
|
||||||
|
.where(eq(userClients.clientId, clientId))
|
||||||
|
.returning();
|
||||||
|
console.log(`Deleted ${deletedUserClients.length} userClient association(s)`);
|
||||||
|
|
||||||
|
// Finally, delete the client itself
|
||||||
|
const deletedClients = await trx
|
||||||
|
.delete(clients)
|
||||||
|
.where(eq(clients.clientId, clientId))
|
||||||
|
.returning();
|
||||||
|
console.log(`Deleted client: ${deletedClients[0]?.name || niceId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("\nClient deletion completed successfully!");
|
||||||
|
console.log("\nSummary:");
|
||||||
|
console.log(` - Client: ${niceId} (clientId: ${clientId})`);
|
||||||
|
console.log(` - Olm(s): ${associatedOlms.length}`);
|
||||||
|
console.log(` - Current fingerprints: deleted`);
|
||||||
|
console.log(` - Approvals: deleted`);
|
||||||
|
console.log(` - UserClients: deleted`);
|
||||||
|
console.log(` - Snapshots: preserved (not deleted)`);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting client:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -6,6 +6,8 @@ import { setAdminCredentials } from "@cli/commands/setAdminCredentials";
|
|||||||
import { resetUserSecurityKeys } from "@cli/commands/resetUserSecurityKeys";
|
import { resetUserSecurityKeys } from "@cli/commands/resetUserSecurityKeys";
|
||||||
import { clearExitNodes } from "./commands/clearExitNodes";
|
import { clearExitNodes } from "./commands/clearExitNodes";
|
||||||
import { rotateServerSecret } from "./commands/rotateServerSecret";
|
import { rotateServerSecret } from "./commands/rotateServerSecret";
|
||||||
|
import { clearLicenseKeys } from "./commands/clearLicenseKeys";
|
||||||
|
import { deleteClient } from "./commands/deleteClient";
|
||||||
|
|
||||||
yargs(hideBin(process.argv))
|
yargs(hideBin(process.argv))
|
||||||
.scriptName("pangctl")
|
.scriptName("pangctl")
|
||||||
@@ -13,5 +15,7 @@ yargs(hideBin(process.argv))
|
|||||||
.command(resetUserSecurityKeys)
|
.command(resetUserSecurityKeys)
|
||||||
.command(clearExitNodes)
|
.command(clearExitNodes)
|
||||||
.command(rotateServerSecret)
|
.command(rotateServerSecret)
|
||||||
|
.command(clearLicenseKeys)
|
||||||
|
.command(deleteClient)
|
||||||
.demandCommand()
|
.demandCommand()
|
||||||
.help().argv;
|
.help().argv;
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
# To see all available options, please visit the docs:
|
# To see all available options, please visit the docs:
|
||||||
# https://docs.pangolin.net/self-host/advanced/config-file
|
# https://docs.pangolin.net/
|
||||||
|
|
||||||
app:
|
|
||||||
dashboard_url: http://localhost:3002
|
|
||||||
log_level: debug
|
|
||||||
|
|
||||||
domains:
|
|
||||||
domain1:
|
|
||||||
base_domain: example.com
|
|
||||||
|
|
||||||
server:
|
|
||||||
secret: my_secret_key
|
|
||||||
|
|
||||||
gerbil:
|
gerbil:
|
||||||
base_endpoint: example.com
|
start_port: 51820
|
||||||
|
base_endpoint: "{{.DashboardDomain}}"
|
||||||
|
|
||||||
orgs:
|
app:
|
||||||
block_size: 24
|
dashboard_url: "https://{{.DashboardDomain}}"
|
||||||
subnet_group: 100.90.137.0/20
|
log_level: "info"
|
||||||
|
telemetry:
|
||||||
|
anonymous_usage: true
|
||||||
|
|
||||||
|
domains:
|
||||||
|
domain1:
|
||||||
|
base_domain: "{{.BaseDomain}}"
|
||||||
|
|
||||||
|
server:
|
||||||
|
secret: "{{.Secret}}"
|
||||||
|
cors:
|
||||||
|
origins: ["https://{{.DashboardDomain}}"]
|
||||||
|
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
||||||
|
allowed_headers: ["X-CSRF-Token", "Content-Type"]
|
||||||
|
credentials: false
|
||||||
|
|
||||||
flags:
|
flags:
|
||||||
require_email_verification: false
|
require_email_verification: false
|
||||||
disable_signup_without_invite: true
|
disable_signup_without_invite: true
|
||||||
disable_user_create_org: true
|
disable_user_create_org: false
|
||||||
allow_raw_resources: true
|
allow_raw_resources: true
|
||||||
enable_integration_api: true
|
|
||||||
|
|||||||
@@ -21,9 +21,8 @@ http:
|
|||||||
|
|
||||||
# Next.js router (handles everything except API and WebSocket paths)
|
# Next.js router (handles everything except API and WebSocket paths)
|
||||||
next-router:
|
next-router:
|
||||||
rule: "Host(`{{.DashboardDomain}}`)"
|
rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)"
|
||||||
service: next-service
|
service: next-service
|
||||||
priority: 10
|
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
middlewares:
|
middlewares:
|
||||||
@@ -35,7 +34,6 @@ http:
|
|||||||
api-router:
|
api-router:
|
||||||
rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)"
|
rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)"
|
||||||
service: api-service
|
service: api-service
|
||||||
priority: 100
|
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
middlewares:
|
middlewares:
|
||||||
@@ -53,3 +51,12 @@ http:
|
|||||||
loadBalancer:
|
loadBalancer:
|
||||||
servers:
|
servers:
|
||||||
- url: "http://pangolin:3000" # API/WebSocket server
|
- url: "http://pangolin:3000" # API/WebSocket server
|
||||||
|
|
||||||
|
tcp:
|
||||||
|
serversTransports:
|
||||||
|
pp-transport-v1:
|
||||||
|
proxyProtocol:
|
||||||
|
version: 1
|
||||||
|
pp-transport-v2:
|
||||||
|
proxyProtocol:
|
||||||
|
version: 2
|
||||||
|
|||||||
@@ -3,32 +3,52 @@ api:
|
|||||||
dashboard: true
|
dashboard: true
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
|
http:
|
||||||
|
endpoint: "http://pangolin:3001/api/v1/traefik-config"
|
||||||
|
pollInterval: "5s"
|
||||||
file:
|
file:
|
||||||
directory: "/var/dynamic"
|
filename: "/etc/traefik/dynamic_config.yml"
|
||||||
watch: true
|
|
||||||
|
|
||||||
experimental:
|
experimental:
|
||||||
plugins:
|
plugins:
|
||||||
badger:
|
badger:
|
||||||
moduleName: "github.com/fosrl/badger"
|
moduleName: "github.com/fosrl/badger"
|
||||||
version: "v1.3.0"
|
version: "{{.BadgerVersion}}"
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: "DEBUG"
|
level: "INFO"
|
||||||
format: "common"
|
format: "common"
|
||||||
maxSize: 100
|
maxSize: 100
|
||||||
maxBackups: 3
|
maxBackups: 3
|
||||||
maxAge: 3
|
maxAge: 3
|
||||||
compress: true
|
compress: true
|
||||||
|
|
||||||
|
certificatesResolvers:
|
||||||
|
letsencrypt:
|
||||||
|
acme:
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: web
|
||||||
|
email: "{{.LetsEncryptEmail}}"
|
||||||
|
storage: "/letsencrypt/acme.json"
|
||||||
|
caServer: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
entryPoints:
|
entryPoints:
|
||||||
web:
|
web:
|
||||||
address: ":80"
|
address: ":80"
|
||||||
websecure:
|
websecure:
|
||||||
address: ":9443"
|
address: ":443"
|
||||||
transport:
|
transport:
|
||||||
respondingTimeouts:
|
respondingTimeouts:
|
||||||
readTimeout: "30m"
|
readTimeout: "30m"
|
||||||
|
http:
|
||||||
|
tls:
|
||||||
|
certResolver: "letsencrypt"
|
||||||
|
encodedCharacters:
|
||||||
|
allowEncodedSlash: true
|
||||||
|
allowEncodedQuestionMark: true
|
||||||
|
|
||||||
serversTransport:
|
serversTransport:
|
||||||
insecureSkipVerify: true
|
insecureSkipVerify: true
|
||||||
|
|
||||||
|
ping:
|
||||||
|
entryPoint: "web"
|
||||||
|
|||||||
@@ -210,6 +210,47 @@ func isDockerRunning() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPodmanRunning() bool {
|
||||||
|
cmd := exec.Command("podman", "info")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectContainerType detects whether the system is currently using Docker or Podman
|
||||||
|
// by checking which container runtime is running and has containers
|
||||||
|
func detectContainerType() SupportedContainer {
|
||||||
|
// Check if we have running containers with podman
|
||||||
|
if isPodmanRunning() {
|
||||||
|
cmd := exec.Command("podman", "ps", "-q")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err == nil && len(strings.TrimSpace(string(output))) > 0 {
|
||||||
|
return Podman
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have running containers with docker
|
||||||
|
if isDockerRunning() {
|
||||||
|
cmd := exec.Command("docker", "ps", "-q")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err == nil && len(strings.TrimSpace(string(output))) > 0 {
|
||||||
|
return Docker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no containers are running, check which one is installed and running
|
||||||
|
if isPodmanRunning() && isPodmanInstalled() {
|
||||||
|
return Podman
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDockerRunning() && isDockerInstalled() {
|
||||||
|
return Docker
|
||||||
|
}
|
||||||
|
|
||||||
|
return Undefined
|
||||||
|
}
|
||||||
|
|
||||||
// executeDockerComposeCommandWithArgs executes the appropriate docker command with arguments supplied
|
// executeDockerComposeCommandWithArgs executes the appropriate docker command with arguments supplied
|
||||||
func executeDockerComposeCommandWithArgs(args ...string) error {
|
func executeDockerComposeCommandWithArgs(args ...string) error {
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func installCrowdsec(config Config) error {
|
|||||||
|
|
||||||
if checkIfTextInFile("config/traefik/dynamic_config.yml", "PUT_YOUR_BOUNCER_KEY_HERE_OR_IT_WILL_NOT_WORK") {
|
if checkIfTextInFile("config/traefik/dynamic_config.yml", "PUT_YOUR_BOUNCER_KEY_HERE_OR_IT_WILL_NOT_WORK") {
|
||||||
fmt.Println("Failed to replace bouncer key! Please retrieve the key and replace it in the config/traefik/dynamic_config.yml file using the following command:")
|
fmt.Println("Failed to replace bouncer key! Please retrieve the key and replace it in the config/traefik/dynamic_config.yml file using the following command:")
|
||||||
fmt.Println(" docker exec crowdsec cscli bouncers add traefik-bouncer")
|
fmt.Printf(" %s exec crowdsec cscli bouncers add traefik-bouncer\n", config.InstallationContainerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -117,7 +117,7 @@ func GetCrowdSecAPIKey(containerType SupportedContainer) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute the command to get the API key
|
// Execute the command to get the API key
|
||||||
cmd := exec.Command("docker", "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer", "-o", "raw")
|
cmd := exec.Command(string(containerType), "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer", "-o", "raw")
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ module installer
|
|||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/term v0.38.0
|
golang.org/x/term v0.39.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.39.0 // indirect
|
require golang.org/x/sys v0.40.0 // indirect
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -229,7 +229,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.InstallationContainerType = podmanOrDocker(reader)
|
// Try to detect container type from existing installation
|
||||||
|
detectedType := detectContainerType()
|
||||||
|
if detectedType == Undefined {
|
||||||
|
// If detection fails, prompt the user
|
||||||
|
fmt.Println("Unable to detect container type from existing installation.")
|
||||||
|
config.InstallationContainerType = podmanOrDocker(reader)
|
||||||
|
} else {
|
||||||
|
config.InstallationContainerType = detectedType
|
||||||
|
fmt.Printf("Detected container type: %s\n", config.InstallationContainerType)
|
||||||
|
}
|
||||||
|
|
||||||
config.DoCrowdsecInstall = true
|
config.DoCrowdsecInstall = true
|
||||||
err := installCrowdsec(config)
|
err := installCrowdsec(config)
|
||||||
@@ -286,10 +295,10 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := exec.Command("bash", "-c", "cat /etc/sysctl.conf | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
|
if err := exec.Command("bash", "-c", "cat /etc/sysctl.d/99-podman.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start=' || cat /etc/sysctl.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
|
||||||
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
|
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
|
||||||
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
|
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
|
||||||
approved := readBool(reader, "The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' >> /etc/sysctl.conf && sysctl -p\". Approve?", true)
|
approved := readBool(reader, "The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system\". Approve?", true)
|
||||||
if approved {
|
if approved {
|
||||||
if os.Geteuid() != 0 {
|
if os.Geteuid() != 0 {
|
||||||
fmt.Println("You need to run the installer as root for such a configuration.")
|
fmt.Println("You need to run the installer as root for such a configuration.")
|
||||||
@@ -300,7 +309,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
// container low-range ports as unprivileged ports.
|
// container low-range ports as unprivileged ports.
|
||||||
// Linux only.
|
// Linux only.
|
||||||
|
|
||||||
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' >> /etc/sysctl.conf && sysctl -p"); err != nil {
|
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system"); err != nil {
|
||||||
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
|
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Свържете се с мрежа.",
|
"sitesBannerTitle": "Свържете се с мрежа.",
|
||||||
"sitesBannerDescription": "Сайтът е връзка с отдалечена мрежа, която позволява на Pangolin да предоставя достъп до ресурси, било то публични или частни, на потребители навсякъде. Инсталирайте мрежовия конектор на сайта (Newt) навсякъде, където можете да стартирате бинарен или контейнер, за да създадете връзката.",
|
"sitesBannerDescription": "Сайтът е връзка с отдалечена мрежа, която позволява на Pangolin да предоставя достъп до ресурси, било то публични или частни, на потребители навсякъде. Инсталирайте мрежовия конектор на сайта (Newt) навсякъде, където можете да стартирате бинарен или контейнер, за да създадете връзката.",
|
||||||
"sitesBannerButtonText": "Инсталиране на сайт.",
|
"sitesBannerButtonText": "Инсталиране на сайт.",
|
||||||
|
"approvalsBannerTitle": "Одобрете или откажете достъп до устройство",
|
||||||
|
"approvalsBannerDescription": "Прегледайте и одобрите или откажете искания за достъп до устройства от потребители. Когато се изисква одобрение на устройства, потребителите трябва да получат администраторско одобрение, преди техните устройства да могат да се свържат с ресурсите на вашата организация.",
|
||||||
|
"approvalsBannerButtonText": "Научете повече",
|
||||||
"siteCreate": "Създайте сайт",
|
"siteCreate": "Създайте сайт",
|
||||||
"siteCreateDescription2": "Следвайте стъпките по-долу, за да създадете и свържете нов сайт",
|
"siteCreateDescription2": "Следвайте стъпките по-долу, за да създадете и свържете нов сайт",
|
||||||
"siteCreateDescription": "Създайте нов сайт, за да започнете да свързвате ресурси",
|
"siteCreateDescription": "Създайте нов сайт, за да започнете да свързвате ресурси",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Търсене на роли...",
|
"accessRolesSearch": "Търсене на роли...",
|
||||||
"accessRolesAdd": "Добавете роля",
|
"accessRolesAdd": "Добавете роля",
|
||||||
"accessRoleDelete": "Изтриване на роля",
|
"accessRoleDelete": "Изтриване на роля",
|
||||||
|
"accessApprovalsManage": "Управление на одобрения",
|
||||||
|
"accessApprovalsDescription": "Прегледайте и управлявайте чакащи одобрения за достъп до тази организация",
|
||||||
"description": "Описание",
|
"description": "Описание",
|
||||||
"inviteTitle": "Отворени покани",
|
"inviteTitle": "Отворени покани",
|
||||||
"inviteDescription": "Управлявайте покани за други потребители да се присъединят към организацията",
|
"inviteDescription": "Управлявайте покани за други потребители да се присъединят към организацията",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Изберете продължителност",
|
"selectDuration": "Изберете продължителност",
|
||||||
"selectResource": "Изберете Ресурс",
|
"selectResource": "Изберете Ресурс",
|
||||||
"filterByResource": "Филтрирай По Ресурс",
|
"filterByResource": "Филтрирай По Ресурс",
|
||||||
|
"selectApprovalState": "Изберете състояние на одобрение",
|
||||||
|
"filterByApprovalState": "Филтрирайте по състояние на одобрение",
|
||||||
|
"approvalListEmpty": "Няма одобрения",
|
||||||
|
"approvalState": "Състояние на одобрение",
|
||||||
|
"approve": "Одобряване",
|
||||||
|
"approved": "Одобрен",
|
||||||
|
"denied": "Отказан",
|
||||||
|
"deniedApproval": "Одобрение е отказано",
|
||||||
|
"all": "Всички",
|
||||||
|
"deny": "Откажете",
|
||||||
|
"viewDetails": "Разгледай подробности",
|
||||||
|
"requestingNewDeviceApproval": "поискана нова устройство",
|
||||||
"resetFilters": "Нулиране на Филтрите",
|
"resetFilters": "Нулиране на Филтрите",
|
||||||
"totalBlocked": "Заявки Блокирани От Pangolin",
|
"totalBlocked": "Заявки Блокирани От Pangolin",
|
||||||
"totalRequests": "Общо Заявки",
|
"totalRequests": "Общо Заявки",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Държави",
|
"countries": "Държави",
|
||||||
"accessRoleCreate": "Създайте роля",
|
"accessRoleCreate": "Създайте роля",
|
||||||
"accessRoleCreateDescription": "Създайте нова роля за групиране на потребители и управление на техните разрешения.",
|
"accessRoleCreateDescription": "Създайте нова роля за групиране на потребители и управление на техните разрешения.",
|
||||||
|
"accessRoleEdit": "Редактиране на роля",
|
||||||
|
"accessRoleEditDescription": "Редактирайте информацията за ролята.",
|
||||||
"accessRoleCreateSubmit": "Създайте роля",
|
"accessRoleCreateSubmit": "Създайте роля",
|
||||||
"accessRoleCreated": "Ролята е създадена",
|
"accessRoleCreated": "Ролята е създадена",
|
||||||
"accessRoleCreatedDescription": "Ролята беше успешно създадена.",
|
"accessRoleCreatedDescription": "Ролята беше успешно създадена.",
|
||||||
"accessRoleErrorCreate": "Неуспешно създаване на роля",
|
"accessRoleErrorCreate": "Неуспешно създаване на роля",
|
||||||
"accessRoleErrorCreateDescription": "Възникна грешка при създаването на ролята.",
|
"accessRoleErrorCreateDescription": "Възникна грешка при създаването на ролята.",
|
||||||
|
"accessRoleUpdateSubmit": "Обновете роля",
|
||||||
|
"accessRoleUpdated": "Ролята е актуализирана",
|
||||||
|
"accessRoleUpdatedDescription": "Ролята беше успешно актуализирана.",
|
||||||
|
"accessApprovalUpdated": "Одобрението е обработено",
|
||||||
|
"accessApprovalApprovedDescription": "Задайте решение на заявка за одобрение да бъде одобрено.",
|
||||||
|
"accessApprovalDeniedDescription": "Задайте решение на заявка за одобрение да бъде отказано.",
|
||||||
|
"accessRoleErrorUpdate": "Неуспешно актуализиране на ролята",
|
||||||
|
"accessRoleErrorUpdateDescription": "Възникна грешка при актуализиране на ролята.",
|
||||||
|
"accessApprovalErrorUpdate": "Неуспешно обработване на одобрение",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Възникна грешка при обработване на одобрението.",
|
||||||
"accessRoleErrorNewRequired": "Нова роля е необходима",
|
"accessRoleErrorNewRequired": "Нова роля е необходима",
|
||||||
"accessRoleErrorRemove": "Неуспешно премахване на роля",
|
"accessRoleErrorRemove": "Неуспешно премахване на роля",
|
||||||
"accessRoleErrorRemoveDescription": "Възникна грешка при премахването на роля.",
|
"accessRoleErrorRemoveDescription": "Възникна грешка при премахването на роля.",
|
||||||
"accessRoleName": "Име на роля",
|
"accessRoleName": "Име на роля",
|
||||||
"accessRoleQuestionRemove": "Ще изтриете ролята {name}. Не можете да отмените това действие.",
|
"accessRoleQuestionRemove": "Ще изтриете ролята `{name}`. Не можете да отмените това действие.",
|
||||||
"accessRoleRemove": "Премахни роля",
|
"accessRoleRemove": "Премахни роля",
|
||||||
"accessRoleRemoveDescription": "Премахни роля от организацията",
|
"accessRoleRemoveDescription": "Премахни роля от организацията",
|
||||||
"accessRoleRemoveSubmit": "Премахни роля",
|
"accessRoleRemoveSubmit": "Премахни роля",
|
||||||
@@ -874,7 +903,7 @@
|
|||||||
"inviteAlready": "Изглежда, че сте били поканени!",
|
"inviteAlready": "Изглежда, че сте били поканени!",
|
||||||
"inviteAlreadyDescription": "За да приемете поканата, трябва да влезете или да създадете акаунт.",
|
"inviteAlreadyDescription": "За да приемете поканата, трябва да влезете или да създадете акаунт.",
|
||||||
"signupQuestion": "Вече имате акаунт?",
|
"signupQuestion": "Вече имате акаунт?",
|
||||||
"login": "Влизане",
|
"login": "Вход",
|
||||||
"resourceNotFound": "Ресурсът не е намерен",
|
"resourceNotFound": "Ресурсът не е намерен",
|
||||||
"resourceNotFoundDescription": "Ресурсът, който се опитвате да достъпите, не съществува.",
|
"resourceNotFoundDescription": "Ресурсът, който се опитвате да достъпите, не съществува.",
|
||||||
"pincodeRequirementsLength": "ПИН трябва да бъде точно 6 цифри",
|
"pincodeRequirementsLength": "ПИН трябва да бъде точно 6 цифри",
|
||||||
@@ -954,13 +983,13 @@
|
|||||||
"passwordExpiryDescription": "Тази организация изисква да сменяте паролата си на всеки {maxDays} дни.",
|
"passwordExpiryDescription": "Тази организация изисква да сменяте паролата си на всеки {maxDays} дни.",
|
||||||
"changePasswordNow": "Сменете паролата сега",
|
"changePasswordNow": "Сменете паролата сега",
|
||||||
"pincodeAuth": "Код на удостоверителя",
|
"pincodeAuth": "Код на удостоверителя",
|
||||||
"pincodeSubmit2": "Изпрати код",
|
"pincodeSubmit2": "Изпратете кода",
|
||||||
"passwordResetSubmit": "Заявка за нулиране",
|
"passwordResetSubmit": "Заявка за нулиране",
|
||||||
"passwordResetAlreadyHaveCode": "Въведете код.",
|
"passwordResetAlreadyHaveCode": "Въведете код.",
|
||||||
"passwordResetSmtpRequired": "Моля, свържете се с вашия администратор",
|
"passwordResetSmtpRequired": "Моля, свържете се с вашия администратор",
|
||||||
"passwordResetSmtpRequiredDescription": "Кодът за нулиране на парола е задължителен за нулиране на паролата ви. Моля, свържете се с вашия администратор за помощ.",
|
"passwordResetSmtpRequiredDescription": "Кодът за нулиране на парола е задължителен за нулиране на паролата ви. Моля, свържете се с вашия администратор за помощ.",
|
||||||
"passwordBack": "Назад към Парола",
|
"passwordBack": "Назад към Парола",
|
||||||
"loginBack": "Връщане към вход",
|
"loginBack": "Върнете се на главната страница за вход",
|
||||||
"signup": "Регистрация",
|
"signup": "Регистрация",
|
||||||
"loginStart": "Влезте, за да започнете",
|
"loginStart": "Влезте, за да започнете",
|
||||||
"idpOidcTokenValidating": "Валидиране на OIDC токен",
|
"idpOidcTokenValidating": "Валидиране на OIDC токен",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Актуализиране на IdP организация",
|
"actionUpdateIdpOrg": "Актуализиране на IdP организация",
|
||||||
"actionCreateClient": "Създаване на клиент",
|
"actionCreateClient": "Създаване на клиент",
|
||||||
"actionDeleteClient": "Изтриване на клиент",
|
"actionDeleteClient": "Изтриване на клиент",
|
||||||
|
"actionArchiveClient": "Архивиране на клиента",
|
||||||
|
"actionUnarchiveClient": "Разархивиране на клиента",
|
||||||
|
"actionBlockClient": "Блокиране на клиента",
|
||||||
|
"actionUnblockClient": "Деблокиране на клиента",
|
||||||
"actionUpdateClient": "Актуализиране на клиент",
|
"actionUpdateClient": "Актуализиране на клиент",
|
||||||
"actionListClients": "Списък с клиенти",
|
"actionListClients": "Списък с клиенти",
|
||||||
"actionGetClient": "Получаване на клиент",
|
"actionGetClient": "Получаване на клиент",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Търсене...",
|
"searchProgress": "Търсене...",
|
||||||
"create": "Създаване",
|
"create": "Създаване",
|
||||||
"orgs": "Организации",
|
"orgs": "Организации",
|
||||||
"loginError": "Възникна грешка при влизане",
|
"loginError": "Възникна неочаквана грешка. Моля, опитайте отново.",
|
||||||
"loginRequiredForDevice": "Необходим е вход за удостоверяване на вашето устройство.",
|
"loginRequiredForDevice": "Необходим е вход за вашето устройство.",
|
||||||
"passwordForgot": "Забравена парола?",
|
"passwordForgot": "Забравена парола?",
|
||||||
"otpAuth": "Двуфакторно удостоверяване",
|
"otpAuth": "Двуфакторно удостоверяване",
|
||||||
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
|
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
|
||||||
"otpAuthSubmit": "Изпрати код",
|
"otpAuthSubmit": "Изпрати код",
|
||||||
"idpContinue": "Или продължете със",
|
"idpContinue": "Или продължете със",
|
||||||
"otpAuthBack": "Назад към Вход",
|
"otpAuthBack": "Назад към парола",
|
||||||
"navbar": "Навигационно меню",
|
"navbar": "Навигационно меню",
|
||||||
"navbarDescription": "Главно навигационно меню за приложението",
|
"navbarDescription": "Главно навигационно меню за приложението",
|
||||||
"navbarDocsLink": "Документация",
|
"navbarDocsLink": "Документация",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Общ преглед",
|
"sidebarOverview": "Общ преглед",
|
||||||
"sidebarHome": "Начало",
|
"sidebarHome": "Начало",
|
||||||
"sidebarSites": "Сайтове",
|
"sidebarSites": "Сайтове",
|
||||||
|
"sidebarApprovals": "Заявки за одобрение",
|
||||||
"sidebarResources": "Ресурси",
|
"sidebarResources": "Ресурси",
|
||||||
"sidebarProxyResources": "Публично",
|
"sidebarProxyResources": "Публично",
|
||||||
"sidebarClientResources": "Частно",
|
"sidebarClientResources": "Частно",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Идентификационни доставчици",
|
"sidebarIdentityProviders": "Идентификационни доставчици",
|
||||||
"sidebarLicense": "Лиценз",
|
"sidebarLicense": "Лиценз",
|
||||||
"sidebarClients": "Клиенти",
|
"sidebarClients": "Клиенти",
|
||||||
"sidebarUserDevices": "Потребители",
|
"sidebarUserDevices": "Устройства на потребителя",
|
||||||
"sidebarMachineClients": "Машини",
|
"sidebarMachineClients": "Машини",
|
||||||
"sidebarDomains": "Домейни",
|
"sidebarDomains": "Домейни",
|
||||||
"sidebarGeneral": "Управление.",
|
"sidebarGeneral": "Управление.",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Възникна грешка при създаване на админ акаунт.",
|
"setupErrorCreateAdmin": "Възникна грешка при създаване на админ акаунт.",
|
||||||
"certificateStatus": "Статус на сертификата",
|
"certificateStatus": "Статус на сертификата",
|
||||||
"loading": "Зареждане",
|
"loading": "Зареждане",
|
||||||
|
"loadingAnalytics": "Зареждане на анализи",
|
||||||
"restart": "Рестарт",
|
"restart": "Рестарт",
|
||||||
"domains": "Домейни",
|
"domains": "Домейни",
|
||||||
"domainsDescription": "Създайте и управлявайте наличните домейни в организацията",
|
"domainsDescription": "Създайте и управлявайте наличните домейни в организацията",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Неуспешно обновяване на данни",
|
"refreshError": "Неуспешно обновяване на данни",
|
||||||
"verified": "Потвърдено",
|
"verified": "Потвърдено",
|
||||||
"pending": "Чакащо",
|
"pending": "Чакащо",
|
||||||
|
"pendingApproval": "Очаква одобрение",
|
||||||
"sidebarBilling": "Фактуриране",
|
"sidebarBilling": "Фактуриране",
|
||||||
"billing": "Фактуриране",
|
"billing": "Фактуриране",
|
||||||
"orgBillingDescription": "Управлявайте информацията за плащане и абонаментите",
|
"orgBillingDescription": "Управлявайте информацията за плащане и абонаментите",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Ключът за защита е премахнат успешно",
|
"securityKeyRemoveSuccess": "Ключът за защита е премахнат успешно",
|
||||||
"securityKeyRemoveError": "Неуспешно премахване на ключ за защита",
|
"securityKeyRemoveError": "Неуспешно премахване на ключ за защита",
|
||||||
"securityKeyLoadError": "Неуспешно зареждане на ключове за защита",
|
"securityKeyLoadError": "Неуспешно зареждане на ключове за защита",
|
||||||
"securityKeyLogin": "Продължете с ключа за сигурност",
|
"securityKeyLogin": "Използвайте ключ за защита",
|
||||||
"securityKeyAuthError": "Неуспешно удостоверяване с ключ за сигурност",
|
"securityKeyAuthError": "Неуспешно удостоверяване с ключ за сигурност",
|
||||||
"securityKeyRecommendation": "Регистрирайте резервен ключ за безопасност на друго устройство, за да сте сигурни, че винаги ще имате достъп до профила си",
|
"securityKeyRecommendation": "Регистрирайте резервен ключ за безопасност на друго устройство, за да сте сигурни, че винаги ще имате достъп до профила си",
|
||||||
"registering": "Регистрация...",
|
"registering": "Регистрация...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Интервал за здраве",
|
"IntervalSeconds": "Интервал за здраве",
|
||||||
"timeoutSeconds": "Време за изчакване (сек)",
|
"timeoutSeconds": "Време за изчакване (сек)",
|
||||||
"timeIsInSeconds": "Времето е в секунди",
|
"timeIsInSeconds": "Времето е в секунди",
|
||||||
|
"requireDeviceApproval": "Изискват одобрение на устройства",
|
||||||
|
"requireDeviceApprovalDescription": "Потребители с тази роля трябва да имат нови устройства одобрени от администратор преди да могат да се свържат и да имат достъп до ресурси.",
|
||||||
"retryAttempts": "Опити за повторно",
|
"retryAttempts": "Опити за повторно",
|
||||||
"expectedResponseCodes": "Очаквани кодове за отговор",
|
"expectedResponseCodes": "Очаквани кодове за отговор",
|
||||||
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
|
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Не са намерени вътрешни ресурси.",
|
"resourcesTableNoInternalResourcesFound": "Не са намерени вътрешни ресурси.",
|
||||||
"resourcesTableDestination": "Дестинация",
|
"resourcesTableDestination": "Дестинация",
|
||||||
"resourcesTableAlias": "Псевдоним",
|
"resourcesTableAlias": "Псевдоним",
|
||||||
|
"resourcesTableAliasAddress": "Адрес на псевдоним.",
|
||||||
|
"resourcesTableAliasAddressInfo": "Този адрес е част от подсистемата на организацията. Използва се за разрешаване на псевдонимни записи чрез вътрешно DNS разрешаване.",
|
||||||
"resourcesTableClients": "Клиенти",
|
"resourcesTableClients": "Клиенти",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "и са достъпни само вътрешно при свързване с клиент.",
|
"resourcesTableAndOnlyAccessibleInternally": "и са достъпни само вътрешно при свързване с клиент.",
|
||||||
"resourcesTableNoTargets": "Без цели",
|
"resourcesTableNoTargets": "Без цели",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Изберете своя доставчик на идентичност, за да продължите",
|
"orgAuthChooseIdpDescription": "Изберете своя доставчик на идентичност, за да продължите",
|
||||||
"orgAuthNoIdpConfigured": "Тази организация няма конфигурирани доставчици на идентичност. Можете да влезете с вашата Pangolin идентичност.",
|
"orgAuthNoIdpConfigured": "Тази организация няма конфигурирани доставчици на идентичност. Можете да влезете с вашата Pangolin идентичност.",
|
||||||
"orgAuthSignInWithPangolin": "Впишете се с Pangolin",
|
"orgAuthSignInWithPangolin": "Впишете се с Pangolin",
|
||||||
"orgAuthSignInToOrg": "Влезте в организация.",
|
"orgAuthSignInToOrg": "Влезте в организация",
|
||||||
"orgAuthSelectOrgTitle": "Вход в организация.",
|
"orgAuthSelectOrgTitle": "Вход в организация.",
|
||||||
"orgAuthSelectOrgDescription": "Въведете идентификатора на вашата организация, за да продължите.",
|
"orgAuthSelectOrgDescription": "Въведете идентификатора на вашата организация, за да продължите.",
|
||||||
"orgAuthOrgIdPlaceholder": "вашата-организация",
|
"orgAuthOrgIdPlaceholder": "вашата-организация",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Кодът трябва да бъде 9 символа (напр. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Кодът трябва да бъде 9 символа (напр. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Невалиден или изтекъл код",
|
"deviceCodeInvalidOrExpired": "Невалиден или изтекъл код",
|
||||||
"deviceCodeVerifyFailed": "Неуспешна проверка на кода на устройството",
|
"deviceCodeVerifyFailed": "Неуспешна проверка на кода на устройството",
|
||||||
|
"deviceCodeValidating": "Валидиране на кода на устройството...",
|
||||||
|
"deviceCodeVerifying": "Проверка на оторизацията на устройството...",
|
||||||
"signedInAs": "Вписан като",
|
"signedInAs": "Вписан като",
|
||||||
"deviceCodeEnterPrompt": "Въведете кода, показан на устройството",
|
"deviceCodeEnterPrompt": "Въведете кода, показан на устройството",
|
||||||
"continue": "Продължете",
|
"continue": "Продължете",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Достъп до всички организации, до които има достъп акаунтът ви",
|
"deviceOrganizationsAccess": "Достъп до всички организации, до които има достъп акаунтът ви",
|
||||||
"deviceAuthorize": "Разрешете {applicationName}",
|
"deviceAuthorize": "Разрешете {applicationName}",
|
||||||
"deviceConnected": "Устройството е свързано!",
|
"deviceConnected": "Устройството е свързано!",
|
||||||
"deviceAuthorizedMessage": "Устройството е разрешено да има достъп до вашия акаунт.",
|
"deviceAuthorizedMessage": "Устройството е оторизирано да има достъп до акаунта ви. Моля, върнете се към клиентското приложение.",
|
||||||
"pangolinCloud": "Pangolin Cloud",
|
"pangolinCloud": "Pangolin Cloud",
|
||||||
"viewDevices": "Преглед на устройствата",
|
"viewDevices": "Преглед на устройствата",
|
||||||
"viewDevicesDescription": "Управлявайте свързаните си устройства",
|
"viewDevicesDescription": "Управлявайте свързаните си устройства",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Идентификатор",
|
"identifier": "Идентификатор",
|
||||||
"deviceLoginUseDifferentAccount": "Не сте вие? Използвайте друг акаунт.",
|
"deviceLoginUseDifferentAccount": "Не сте вие? Използвайте друг акаунт.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запитващо достъп до този акаунт.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запитващо достъп до този акаунт.",
|
||||||
|
"loginSelectAuthenticationMethod": "Изберете метод на удостоверяване, за да продължите.",
|
||||||
"noData": "Няма Данни",
|
"noData": "Няма Данни",
|
||||||
"machineClients": "Машинни клиенти",
|
"machineClients": "Машинни клиенти",
|
||||||
"install": "Инсталирай",
|
"install": "Инсталирай",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Услугата временно недостъпна.",
|
"maintenanceScreenTitle": "Услугата временно недостъпна.",
|
||||||
"maintenanceScreenMessage": "В момента срещаме технически затруднения. Моля, проверете отново скоро.",
|
"maintenanceScreenMessage": "В момента срещаме технически затруднения. Моля, проверете отново скоро.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Прогнозно завършване:",
|
"maintenanceScreenEstimatedCompletion": "Прогнозно завършване:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна."
|
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна.",
|
||||||
|
"available": "Налично",
|
||||||
|
"archived": "Архивирано",
|
||||||
|
"noArchivedDevices": "Не са намерени архивирани устройства.",
|
||||||
|
"deviceArchived": "Устройството е архивирано.",
|
||||||
|
"deviceArchivedDescription": "Устройството беше успешно архивирано.",
|
||||||
|
"errorArchivingDevice": "Грешка при архивиране на устройството.",
|
||||||
|
"failedToArchiveDevice": "Неуспех при архивиране на устройството.",
|
||||||
|
"deviceQuestionArchive": "Сигурни ли сте, че искате да архивирате това устройство?",
|
||||||
|
"deviceMessageArchive": "Устройството ще бъде архивирано и премахнато от вашия списък с активни устройства.",
|
||||||
|
"deviceArchiveConfirm": "Архивиране на устройството",
|
||||||
|
"archiveDevice": "Архивиране на устройство",
|
||||||
|
"archive": "Архив",
|
||||||
|
"deviceUnarchived": "Устройството е разархивирано.",
|
||||||
|
"deviceUnarchivedDescription": "Устройството беше успешно разархивирано.",
|
||||||
|
"errorUnarchivingDevice": "Грешка при разархивиране на устройството.",
|
||||||
|
"failedToUnarchiveDevice": "Неуспешно разархивиране на устройството.",
|
||||||
|
"unarchive": "Разархивиране",
|
||||||
|
"archiveClient": "Архивиране на клиента",
|
||||||
|
"archiveClientQuestion": "Сигурни ли сте, че искате да архивирате този клиент?",
|
||||||
|
"archiveClientMessage": "Клиентът ще бъде архивиран и премахнат от вашия списък с активни клиенти.",
|
||||||
|
"archiveClientConfirm": "Архивиране на клиента",
|
||||||
|
"blockClient": "Блокиране на клиента",
|
||||||
|
"blockClientQuestion": "Сигурни ли сте, че искате да блокирате този клиент?",
|
||||||
|
"blockClientMessage": "Устройството ще бъде принудено да прекъсне, ако е в момента свързано. Можете да го отблокирате по-късно.",
|
||||||
|
"blockClientConfirm": "Блокиране на клиента",
|
||||||
|
"active": "Активно",
|
||||||
|
"usernameOrEmail": "Потребителско име или имейл",
|
||||||
|
"selectYourOrganization": "Изберете вашата организация",
|
||||||
|
"signInTo": "Влезте в",
|
||||||
|
"signInWithPassword": "Продължете с парола",
|
||||||
|
"noAuthMethodsAvailable": "Няма налични методи за удостоверяване за тази организация.",
|
||||||
|
"enterPassword": "Въведете вашата парола",
|
||||||
|
"enterMfaCode": "Въведете кода от вашето приложение за удостоверяване",
|
||||||
|
"securityKeyRequired": "Моля, използвайте ключа за сигурност, за да влезете.",
|
||||||
|
"needToUseAnotherAccount": "Трябва ли да използвате различен акаунт?",
|
||||||
|
"loginLegalDisclaimer": "С натискането на бутоните по-долу, потвърждавате, че сте прочели, разбирате и се съгласявате с <termsOfService>Условията за ползване</termsOfService> и <privacyPolicy>Политиката за поверителност</privacyPolicy>.",
|
||||||
|
"termsOfService": "Условия за ползване",
|
||||||
|
"privacyPolicy": "Политика за поверителност",
|
||||||
|
"userNotFoundWithUsername": "Не е намерен потребител с това потребителско име.",
|
||||||
|
"verify": "Потвърждение",
|
||||||
|
"signIn": "Вход",
|
||||||
|
"forgotPassword": "Забравена парола?",
|
||||||
|
"orgSignInTip": "Ако сте влизали преди, можете да въведете вашето потребителско име или имейл по-горе, за да се удостовери с идентификатора на вашата организация. Лесно е!",
|
||||||
|
"continueAnyway": "Продължете въпреки това",
|
||||||
|
"dontShowAgain": "Не показвайте повече",
|
||||||
|
"orgSignInNotice": "Знаете ли?",
|
||||||
|
"signupOrgNotice": "Опитвате се да влезете?",
|
||||||
|
"signupOrgTip": "Опитвате ли се да влезете чрез идентификационния доставчик на вашата организация?",
|
||||||
|
"signupOrgLink": "Влезте или се регистрирайте с вашата организация вместо това.",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Използвайте различен акаунт",
|
||||||
|
"logIn": "Вход",
|
||||||
|
"deviceInformation": "Информация за устройството",
|
||||||
|
"deviceInformationDescription": "Информация за устройството и агента",
|
||||||
|
"deviceSecurity": "Защита на устройството.",
|
||||||
|
"deviceSecurityDescription": "Информация за състоянието на защитата на устройството.",
|
||||||
|
"platform": "Платформа",
|
||||||
|
"macosVersion": "Версия на macOS",
|
||||||
|
"windowsVersion": "Версия на Windows",
|
||||||
|
"iosVersion": "Версия на iOS",
|
||||||
|
"androidVersion": "Версия на Android",
|
||||||
|
"osVersion": "Версия на ОС",
|
||||||
|
"kernelVersion": "Версия на ядрото",
|
||||||
|
"deviceModel": "Модел на устройството",
|
||||||
|
"serialNumber": "Сериен номер",
|
||||||
|
"hostname": "Име на хост",
|
||||||
|
"firstSeen": "Видян за първи път",
|
||||||
|
"lastSeen": "Последно видян",
|
||||||
|
"biometricsEnabled": "Активирани биометрични данни.",
|
||||||
|
"diskEncrypted": "Криптиран диск.",
|
||||||
|
"firewallEnabled": "Активирана защитна стена.",
|
||||||
|
"autoUpdatesEnabled": "Активирани автоматични актуализации.",
|
||||||
|
"tpmAvailable": "TPM е на разположение.",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Protection на системната цялост (SIP).",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper.",
|
||||||
|
"macosFirewallStealthMode": "Скрит режим на защитната стена.",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor.",
|
||||||
|
"linuxSELinuxEnabled": "SELinux.",
|
||||||
|
"deviceSettingsDescription": "Разгледайте информация и настройки на устройството",
|
||||||
|
"devicePendingApprovalDescription": "Това устройство чака одобрение",
|
||||||
|
"deviceBlockedDescription": "Това устройство е в момента блокирано. Няма да може да се свърже с никакви ресурси, освен ако не бъде деблокирано.",
|
||||||
|
"unblockClient": "Деблокирайте клиента",
|
||||||
|
"unblockClientDescription": "Устройството е деблокирано",
|
||||||
|
"unarchiveClient": "Разархивиране на клиента",
|
||||||
|
"unarchiveClientDescription": "Устройството е разархивирано",
|
||||||
|
"block": "Блокирането",
|
||||||
|
"unblock": "Деблокиране",
|
||||||
|
"deviceActions": "Действия с устройствата",
|
||||||
|
"deviceActionsDescription": "Управлявайте състоянието и достъпа на устройството",
|
||||||
|
"devicePendingApprovalBannerDescription": "Това устройство чака одобрение. Няма да може да се свърже с ресурси, докато не бъде одобрено.",
|
||||||
|
"connected": "Свързан",
|
||||||
|
"disconnected": "Прекъснат",
|
||||||
|
"approvalsEmptyStateTitle": "Одобрения на устройство не са активирани",
|
||||||
|
"approvalsEmptyStateDescription": "Активирайте одобрения на устройства за роли, така че да изискват администраторско одобрение, преди потребителите да могат да свързват нови устройства.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Отидете на роли",
|
||||||
|
"approvalsEmptyStateStep1Description": "Навигирайте до настройките на ролите на вашата организация, за да конфигурирате одобренията на устройства.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Активирайте одобрения на устройства",
|
||||||
|
"approvalsEmptyStateStep2Description": "Редактирайте ролята и активирайте опцията 'Изискване на одобрения за устройства'. Потребители с тази роля ще трябва администраторско одобрение за нови устройства.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Преглед: Когато е активирано, чакащите заявки за устройства ще се появят тук за преглед",
|
||||||
|
"approvalsEmptyStateButtonText": "Управлявайте роли"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Připojit jakoukoli síť",
|
"sitesBannerTitle": "Připojit jakoukoli síť",
|
||||||
"sitesBannerDescription": "Lokalita je připojení k vzdálené síti, která umožňuje Pangolinu poskytovat přístup k prostředkům, ať už veřejným nebo soukromým, uživatelům kdekoli. Nainstalujte síťový konektor (Newt) kamkoli, kam můžete spustit binární soubor nebo kontejner, aby bylo možné připojení navázat.",
|
"sitesBannerDescription": "Lokalita je připojení k vzdálené síti, která umožňuje Pangolinu poskytovat přístup k prostředkům, ať už veřejným nebo soukromým, uživatelům kdekoli. Nainstalujte síťový konektor (Newt) kamkoli, kam můžete spustit binární soubor nebo kontejner, aby bylo možné připojení navázat.",
|
||||||
"sitesBannerButtonText": "Nainstalovat lokalitu",
|
"sitesBannerButtonText": "Nainstalovat lokalitu",
|
||||||
|
"approvalsBannerTitle": "Schválit nebo zakázat přístup k zařízení",
|
||||||
|
"approvalsBannerDescription": "Zkontrolovat a schválit nebo zakázat žádosti uživatelů o přístup k zařízení. Pokud jsou vyžadována schválení zařízení, musí být uživatelé oprávněni před tím, než se jejich zařízení mohou připojit k zdrojům vaší organizace.",
|
||||||
|
"approvalsBannerButtonText": "Zjistit více",
|
||||||
"siteCreate": "Vytvořit lokalitu",
|
"siteCreate": "Vytvořit lokalitu",
|
||||||
"siteCreateDescription2": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili novou lokalitu",
|
"siteCreateDescription2": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili novou lokalitu",
|
||||||
"siteCreateDescription": "Vytvořit nový web pro zahájení připojování zdrojů",
|
"siteCreateDescription": "Vytvořit nový web pro zahájení připojování zdrojů",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Hledat role...",
|
"accessRolesSearch": "Hledat role...",
|
||||||
"accessRolesAdd": "Přidat roli",
|
"accessRolesAdd": "Přidat roli",
|
||||||
"accessRoleDelete": "Odstranit roli",
|
"accessRoleDelete": "Odstranit roli",
|
||||||
|
"accessApprovalsManage": "Spravovat schválení",
|
||||||
|
"accessApprovalsDescription": "Zobrazit a spravovat čekající oprávnění pro přístup k této organizaci",
|
||||||
"description": "L 343, 22.12.2009, s. 1).",
|
"description": "L 343, 22.12.2009, s. 1).",
|
||||||
"inviteTitle": "Otevřít pozvánky",
|
"inviteTitle": "Otevřít pozvánky",
|
||||||
"inviteDescription": "Spravovat pozvánky pro ostatní uživatele do organizace",
|
"inviteDescription": "Spravovat pozvánky pro ostatní uživatele do organizace",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Vyberte dobu trvání",
|
"selectDuration": "Vyberte dobu trvání",
|
||||||
"selectResource": "Vybrat dokument",
|
"selectResource": "Vybrat dokument",
|
||||||
"filterByResource": "Filtrovat podle zdroje",
|
"filterByResource": "Filtrovat podle zdroje",
|
||||||
|
"selectApprovalState": "Vyberte stát schválení",
|
||||||
|
"filterByApprovalState": "Filtrovat podle státu schválení",
|
||||||
|
"approvalListEmpty": "Žádná schválení",
|
||||||
|
"approvalState": "Země schválení",
|
||||||
|
"approve": "Schválit",
|
||||||
|
"approved": "Schváleno",
|
||||||
|
"denied": "Zamítnuto",
|
||||||
|
"deniedApproval": "Odmítnuto schválení",
|
||||||
|
"all": "Vše",
|
||||||
|
"deny": "Zamítnout",
|
||||||
|
"viewDetails": "Zobrazit detaily",
|
||||||
|
"requestingNewDeviceApproval": "vyžádal si nové zařízení",
|
||||||
"resetFilters": "Resetovat filtry",
|
"resetFilters": "Resetovat filtry",
|
||||||
"totalBlocked": "Požadavky blokovány Pangolinem",
|
"totalBlocked": "Požadavky blokovány Pangolinem",
|
||||||
"totalRequests": "Celkem požadavků",
|
"totalRequests": "Celkem požadavků",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Země",
|
"countries": "Země",
|
||||||
"accessRoleCreate": "Vytvořit roli",
|
"accessRoleCreate": "Vytvořit roli",
|
||||||
"accessRoleCreateDescription": "Vytvořte novou roli pro seskupení uživatelů a spravujte jejich oprávnění.",
|
"accessRoleCreateDescription": "Vytvořte novou roli pro seskupení uživatelů a spravujte jejich oprávnění.",
|
||||||
|
"accessRoleEdit": "Upravit roli",
|
||||||
|
"accessRoleEditDescription": "Upravit informace o roli.",
|
||||||
"accessRoleCreateSubmit": "Vytvořit roli",
|
"accessRoleCreateSubmit": "Vytvořit roli",
|
||||||
"accessRoleCreated": "Role vytvořena",
|
"accessRoleCreated": "Role vytvořena",
|
||||||
"accessRoleCreatedDescription": "Role byla úspěšně vytvořena.",
|
"accessRoleCreatedDescription": "Role byla úspěšně vytvořena.",
|
||||||
"accessRoleErrorCreate": "Nepodařilo se vytvořit roli",
|
"accessRoleErrorCreate": "Nepodařilo se vytvořit roli",
|
||||||
"accessRoleErrorCreateDescription": "Došlo k chybě při vytváření role.",
|
"accessRoleErrorCreateDescription": "Došlo k chybě při vytváření role.",
|
||||||
|
"accessRoleUpdateSubmit": "Aktualizovat roli",
|
||||||
|
"accessRoleUpdated": "Role aktualizována",
|
||||||
|
"accessRoleUpdatedDescription": "Role byla úspěšně aktualizována.",
|
||||||
|
"accessApprovalUpdated": "Zpracovaná schválení",
|
||||||
|
"accessApprovalApprovedDescription": "Nastavit rozhodnutí o schválení žádosti o schválení.",
|
||||||
|
"accessApprovalDeniedDescription": "Nastavit žádost o schválení rozhodnutí o zamítnutí.",
|
||||||
|
"accessRoleErrorUpdate": "Nepodařilo se aktualizovat roli",
|
||||||
|
"accessRoleErrorUpdateDescription": "Došlo k chybě při aktualizaci role.",
|
||||||
|
"accessApprovalErrorUpdate": "Zpracování schválení se nezdařilo",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Při zpracování schválení došlo k chybě.",
|
||||||
"accessRoleErrorNewRequired": "Je vyžadována nová role",
|
"accessRoleErrorNewRequired": "Je vyžadována nová role",
|
||||||
"accessRoleErrorRemove": "Nepodařilo se odstranit roli",
|
"accessRoleErrorRemove": "Nepodařilo se odstranit roli",
|
||||||
"accessRoleErrorRemoveDescription": "Došlo k chybě při odstraňování role.",
|
"accessRoleErrorRemoveDescription": "Došlo k chybě při odstraňování role.",
|
||||||
"accessRoleName": "Název role",
|
"accessRoleName": "Název role",
|
||||||
"accessRoleQuestionRemove": "Chystáte se odstranit {name} roli. Tuto akci nelze vrátit zpět.",
|
"accessRoleQuestionRemove": "Chystáte se odstranit roli `{name}`. Tuto akci nelze vrátit zpět.",
|
||||||
"accessRoleRemove": "Odstranit roli",
|
"accessRoleRemove": "Odstranit roli",
|
||||||
"accessRoleRemoveDescription": "Odebrat roli z organizace",
|
"accessRoleRemoveDescription": "Odebrat roli z organizace",
|
||||||
"accessRoleRemoveSubmit": "Odstranit roli",
|
"accessRoleRemoveSubmit": "Odstranit roli",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "Obraťte se na správce",
|
"passwordResetSmtpRequired": "Obraťte se na správce",
|
||||||
"passwordResetSmtpRequiredDescription": "Pro obnovení hesla je vyžadován kód pro obnovení hesla. Kontaktujte prosím svého administrátora.",
|
"passwordResetSmtpRequiredDescription": "Pro obnovení hesla je vyžadován kód pro obnovení hesla. Kontaktujte prosím svého administrátora.",
|
||||||
"passwordBack": "Zpět na heslo",
|
"passwordBack": "Zpět na heslo",
|
||||||
"loginBack": "Přejít zpět na přihlášení",
|
"loginBack": "Přejít zpět na hlavní přihlašovací stránku",
|
||||||
"signup": "Zaregistrovat se",
|
"signup": "Zaregistrovat se",
|
||||||
"loginStart": "Přihlaste se a začněte",
|
"loginStart": "Přihlaste se a začněte",
|
||||||
"idpOidcTokenValidating": "Ověřování OIDC tokenu",
|
"idpOidcTokenValidating": "Ověřování OIDC tokenu",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Aktualizovat IDP Org",
|
"actionUpdateIdpOrg": "Aktualizovat IDP Org",
|
||||||
"actionCreateClient": "Vytvořit klienta",
|
"actionCreateClient": "Vytvořit klienta",
|
||||||
"actionDeleteClient": "Odstranit klienta",
|
"actionDeleteClient": "Odstranit klienta",
|
||||||
|
"actionArchiveClient": "Archivovat klienta",
|
||||||
|
"actionUnarchiveClient": "Zrušit archiv klienta",
|
||||||
|
"actionBlockClient": "Blokovat klienta",
|
||||||
|
"actionUnblockClient": "Odblokovat klienta",
|
||||||
"actionUpdateClient": "Aktualizovat klienta",
|
"actionUpdateClient": "Aktualizovat klienta",
|
||||||
"actionListClients": "Seznam klientů",
|
"actionListClients": "Seznam klientů",
|
||||||
"actionGetClient": "Získat klienta",
|
"actionGetClient": "Získat klienta",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Hledat...",
|
"searchProgress": "Hledat...",
|
||||||
"create": "Vytvořit",
|
"create": "Vytvořit",
|
||||||
"orgs": "Organizace",
|
"orgs": "Organizace",
|
||||||
"loginError": "Při přihlášení došlo k chybě",
|
"loginError": "Došlo k neočekávané chybě. Zkuste to prosím znovu.",
|
||||||
"loginRequiredForDevice": "Pro ověření vašeho zařízení je nutné se přihlásit.",
|
"loginRequiredForDevice": "Přihlášení je vyžadováno pro vaše zařízení.",
|
||||||
"passwordForgot": "Zapomněli jste heslo?",
|
"passwordForgot": "Zapomněli jste heslo?",
|
||||||
"otpAuth": "Dvoufaktorové ověření",
|
"otpAuth": "Dvoufaktorové ověření",
|
||||||
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
|
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
|
||||||
"otpAuthSubmit": "Odeslat kód",
|
"otpAuthSubmit": "Odeslat kód",
|
||||||
"idpContinue": "Nebo pokračovat s",
|
"idpContinue": "Nebo pokračovat s",
|
||||||
"otpAuthBack": "Zpět na přihlášení",
|
"otpAuthBack": "Zpět na heslo",
|
||||||
"navbar": "Navigation Menu",
|
"navbar": "Navigation Menu",
|
||||||
"navbarDescription": "Hlavní navigační menu aplikace",
|
"navbarDescription": "Hlavní navigační menu aplikace",
|
||||||
"navbarDocsLink": "Dokumentace",
|
"navbarDocsLink": "Dokumentace",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Přehled",
|
"sidebarOverview": "Přehled",
|
||||||
"sidebarHome": "Domů",
|
"sidebarHome": "Domů",
|
||||||
"sidebarSites": "Stránky",
|
"sidebarSites": "Stránky",
|
||||||
|
"sidebarApprovals": "Žádosti o schválení",
|
||||||
"sidebarResources": "Zdroje",
|
"sidebarResources": "Zdroje",
|
||||||
"sidebarProxyResources": "Veřejnost",
|
"sidebarProxyResources": "Veřejnost",
|
||||||
"sidebarClientResources": "Soukromé",
|
"sidebarClientResources": "Soukromé",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Poskytovatelé identity",
|
"sidebarIdentityProviders": "Poskytovatelé identity",
|
||||||
"sidebarLicense": "Licence",
|
"sidebarLicense": "Licence",
|
||||||
"sidebarClients": "Klienti",
|
"sidebarClients": "Klienti",
|
||||||
"sidebarUserDevices": "Uživatelé",
|
"sidebarUserDevices": "Uživatelská zařízení",
|
||||||
"sidebarMachineClients": "Stroje a přístroje",
|
"sidebarMachineClients": "Stroje a přístroje",
|
||||||
"sidebarDomains": "Domény",
|
"sidebarDomains": "Domény",
|
||||||
"sidebarGeneral": "Spravovat",
|
"sidebarGeneral": "Spravovat",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Došlo k chybě při vytváření účtu správce serveru.",
|
"setupErrorCreateAdmin": "Došlo k chybě při vytváření účtu správce serveru.",
|
||||||
"certificateStatus": "Stav certifikátu",
|
"certificateStatus": "Stav certifikátu",
|
||||||
"loading": "Načítání",
|
"loading": "Načítání",
|
||||||
|
"loadingAnalytics": "Načítání analytiky",
|
||||||
"restart": "Restartovat",
|
"restart": "Restartovat",
|
||||||
"domains": "Domény",
|
"domains": "Domény",
|
||||||
"domainsDescription": "Vytvořit a spravovat domény dostupné v organizaci",
|
"domainsDescription": "Vytvořit a spravovat domény dostupné v organizaci",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Obnovení dat se nezdařilo",
|
"refreshError": "Obnovení dat se nezdařilo",
|
||||||
"verified": "Ověřeno",
|
"verified": "Ověřeno",
|
||||||
"pending": "Nevyřízeno",
|
"pending": "Nevyřízeno",
|
||||||
|
"pendingApproval": "Čeká na schválení",
|
||||||
"sidebarBilling": "Fakturace",
|
"sidebarBilling": "Fakturace",
|
||||||
"billing": "Fakturace",
|
"billing": "Fakturace",
|
||||||
"orgBillingDescription": "Spravovat fakturační informace a předplatné",
|
"orgBillingDescription": "Spravovat fakturační informace a předplatné",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Bezpečnostní klíč byl úspěšně odstraněn",
|
"securityKeyRemoveSuccess": "Bezpečnostní klíč byl úspěšně odstraněn",
|
||||||
"securityKeyRemoveError": "Odstranění bezpečnostního klíče se nezdařilo",
|
"securityKeyRemoveError": "Odstranění bezpečnostního klíče se nezdařilo",
|
||||||
"securityKeyLoadError": "Nepodařilo se načíst bezpečnostní klíče",
|
"securityKeyLoadError": "Nepodařilo se načíst bezpečnostní klíče",
|
||||||
"securityKeyLogin": "Pokračovat s bezpečnostním klíčem",
|
"securityKeyLogin": "Použít bezpečnostní klíč",
|
||||||
"securityKeyAuthError": "Ověření bezpečnostním klíčem se nezdařilo",
|
"securityKeyAuthError": "Ověření bezpečnostním klíčem se nezdařilo",
|
||||||
"securityKeyRecommendation": "Registrujte záložní bezpečnostní klíč na jiném zařízení, abyste zajistili, že budete mít vždy přístup ke svému účtu.",
|
"securityKeyRecommendation": "Registrujte záložní bezpečnostní klíč na jiném zařízení, abyste zajistili, že budete mít vždy přístup ke svému účtu.",
|
||||||
"registering": "Registrace...",
|
"registering": "Registrace...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Interval zdraví",
|
"IntervalSeconds": "Interval zdraví",
|
||||||
"timeoutSeconds": "Časový limit (sek)",
|
"timeoutSeconds": "Časový limit (sek)",
|
||||||
"timeIsInSeconds": "Čas je v sekundách",
|
"timeIsInSeconds": "Čas je v sekundách",
|
||||||
|
"requireDeviceApproval": "Vyžadovat schválení zařízení",
|
||||||
|
"requireDeviceApprovalDescription": "Uživatelé s touto rolí potřebují nová zařízení schválená správcem, než se mohou připojit a přistupovat ke zdrojům.",
|
||||||
"retryAttempts": "Opakovat pokusy",
|
"retryAttempts": "Opakovat pokusy",
|
||||||
"expectedResponseCodes": "Očekávané kódy odezvy",
|
"expectedResponseCodes": "Očekávané kódy odezvy",
|
||||||
"expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.",
|
"expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Nebyly nalezeny žádné vnitřní zdroje.",
|
"resourcesTableNoInternalResourcesFound": "Nebyly nalezeny žádné vnitřní zdroje.",
|
||||||
"resourcesTableDestination": "Místo určení",
|
"resourcesTableDestination": "Místo určení",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Adresa aliasu",
|
||||||
|
"resourcesTableAliasAddressInfo": "Tato adresa je součástí subsítě veřejných služeb organizace. Používá se k řešení záznamů aliasů pomocí interního rozlišení DNS.",
|
||||||
"resourcesTableClients": "Klienti",
|
"resourcesTableClients": "Klienti",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "a jsou interně přístupné pouze v případě, že jsou propojeni s klientem.",
|
"resourcesTableAndOnlyAccessibleInternally": "a jsou interně přístupné pouze v případě, že jsou propojeni s klientem.",
|
||||||
"resourcesTableNoTargets": "Žádné cíle",
|
"resourcesTableNoTargets": "Žádné cíle",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Chcete-li pokračovat, vyberte svého poskytovatele identity",
|
"orgAuthChooseIdpDescription": "Chcete-li pokračovat, vyberte svého poskytovatele identity",
|
||||||
"orgAuthNoIdpConfigured": "Tato organizace nemá nakonfigurovány žádné poskytovatele identity. Místo toho se můžete přihlásit s vaší Pangolinovou identitou.",
|
"orgAuthNoIdpConfigured": "Tato organizace nemá nakonfigurovány žádné poskytovatele identity. Místo toho se můžete přihlásit s vaší Pangolinovou identitou.",
|
||||||
"orgAuthSignInWithPangolin": "Přihlásit se pomocí Pangolinu",
|
"orgAuthSignInWithPangolin": "Přihlásit se pomocí Pangolinu",
|
||||||
"orgAuthSignInToOrg": "Přihlaste se do organizace",
|
"orgAuthSignInToOrg": "Přihlásit se do organizace",
|
||||||
"orgAuthSelectOrgTitle": "Přihlášení do organizace",
|
"orgAuthSelectOrgTitle": "Přihlášení do organizace",
|
||||||
"orgAuthSelectOrgDescription": "Zadejte ID vaší organizace pro pokračování",
|
"orgAuthSelectOrgDescription": "Zadejte ID vaší organizace pro pokračování",
|
||||||
"orgAuthOrgIdPlaceholder": "vaše-organizace",
|
"orgAuthOrgIdPlaceholder": "vaše-organizace",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Kód musí být 9 znaků (např. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Kód musí být 9 znaků (např. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Neplatný nebo prošlý kód",
|
"deviceCodeInvalidOrExpired": "Neplatný nebo prošlý kód",
|
||||||
"deviceCodeVerifyFailed": "Ověření kódu zařízení se nezdařilo",
|
"deviceCodeVerifyFailed": "Ověření kódu zařízení se nezdařilo",
|
||||||
|
"deviceCodeValidating": "Ověřování kódu zařízení...",
|
||||||
|
"deviceCodeVerifying": "Ověřování autorizace zařízení...",
|
||||||
"signedInAs": "Přihlášen jako",
|
"signedInAs": "Přihlášen jako",
|
||||||
"deviceCodeEnterPrompt": "Zadejte kód zobrazený na zařízení",
|
"deviceCodeEnterPrompt": "Zadejte kód zobrazený na zařízení",
|
||||||
"continue": "Pokračovat",
|
"continue": "Pokračovat",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Přístup ke všem organizacím má přístup k vašemu účtu",
|
"deviceOrganizationsAccess": "Přístup ke všem organizacím má přístup k vašemu účtu",
|
||||||
"deviceAuthorize": "Autorizovat {applicationName}",
|
"deviceAuthorize": "Autorizovat {applicationName}",
|
||||||
"deviceConnected": "Zařízení připojeno!",
|
"deviceConnected": "Zařízení připojeno!",
|
||||||
"deviceAuthorizedMessage": "Zařízení má oprávnění k přístupu k vašemu účtu.",
|
"deviceAuthorizedMessage": "Zařízení má oprávnění k přístupu k vašemu účtu. Vraťte se prosím do klientské aplikace.",
|
||||||
"pangolinCloud": "Pangolin Cloud",
|
"pangolinCloud": "Pangolin Cloud",
|
||||||
"viewDevices": "Zobrazit zařízení",
|
"viewDevices": "Zobrazit zařízení",
|
||||||
"viewDevicesDescription": "Spravovat připojená zařízení",
|
"viewDevicesDescription": "Spravovat připojená zařízení",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Nejste vy? Použijte jiný účet.",
|
"deviceLoginUseDifferentAccount": "Nejste vy? Použijte jiný účet.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Zařízení žádá o přístup k tomuto účtu.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Zařízení žádá o přístup k tomuto účtu.",
|
||||||
|
"loginSelectAuthenticationMethod": "Chcete-li pokračovat, vyberte metodu ověřování.",
|
||||||
"noData": "Žádná data",
|
"noData": "Žádná data",
|
||||||
"machineClients": "Strojoví klienti",
|
"machineClients": "Strojoví klienti",
|
||||||
"install": "Instalovat",
|
"install": "Instalovat",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Služba dočasně nedostupná",
|
"maintenanceScreenTitle": "Služba dočasně nedostupná",
|
||||||
"maintenanceScreenMessage": "Momentálně máme technické potíže. Zkontrolujte později.",
|
"maintenanceScreenMessage": "Momentálně máme technické potíže. Zkontrolujte později.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Odhadované dokončení:",
|
"maintenanceScreenEstimatedCompletion": "Odhadované dokončení:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Cíl je povinný"
|
"createInternalResourceDialogDestinationRequired": "Cíl je povinný",
|
||||||
|
"available": "Dostupné",
|
||||||
|
"archived": "Archivováno",
|
||||||
|
"noArchivedDevices": "Nebyla nalezena žádná archivovaná zařízení",
|
||||||
|
"deviceArchived": "Zařízení archivováno",
|
||||||
|
"deviceArchivedDescription": "Zařízení bylo úspěšně archivováno.",
|
||||||
|
"errorArchivingDevice": "Chyba při archivaci zařízení",
|
||||||
|
"failedToArchiveDevice": "Archivace zařízení se nezdařila",
|
||||||
|
"deviceQuestionArchive": "Opravdu chcete archivovat toto zařízení?",
|
||||||
|
"deviceMessageArchive": "Zařízení bude archivováno a odebráno ze seznamu aktivních zařízení.",
|
||||||
|
"deviceArchiveConfirm": "Archivovat zařízení",
|
||||||
|
"archiveDevice": "Archivovat zařízení",
|
||||||
|
"archive": "Archiv",
|
||||||
|
"deviceUnarchived": "Zařízení bylo odarchivováno",
|
||||||
|
"deviceUnarchivedDescription": "Zařízení bylo úspěšně odarchivováno.",
|
||||||
|
"errorUnarchivingDevice": "Chyba při odarchivování zařízení",
|
||||||
|
"failedToUnarchiveDevice": "Nepodařilo se odarchivovat zařízení",
|
||||||
|
"unarchive": "Zrušit archiv",
|
||||||
|
"archiveClient": "Archivovat klienta",
|
||||||
|
"archiveClientQuestion": "Jste si jisti, že chcete archivovat tohoto klienta?",
|
||||||
|
"archiveClientMessage": "Klient bude archivován a odstraněn z vašeho aktivního seznamu klientů.",
|
||||||
|
"archiveClientConfirm": "Archivovat klienta",
|
||||||
|
"blockClient": "Blokovat klienta",
|
||||||
|
"blockClientQuestion": "Jste si jisti, že chcete zablokovat tohoto klienta?",
|
||||||
|
"blockClientMessage": "Zařízení bude nuceno odpojit, pokud je připojeno. Zařízení můžete později odblokovat.",
|
||||||
|
"blockClientConfirm": "Blokovat klienta",
|
||||||
|
"active": "Aktivní",
|
||||||
|
"usernameOrEmail": "Uživatelské jméno nebo e-mail",
|
||||||
|
"selectYourOrganization": "Vyberte vaši organizaci",
|
||||||
|
"signInTo": "Přihlásit se do",
|
||||||
|
"signInWithPassword": "Pokračovat s heslem",
|
||||||
|
"noAuthMethodsAvailable": "Pro tuto organizaci nejsou k dispozici žádné metody ověřování.",
|
||||||
|
"enterPassword": "Zadejte své heslo",
|
||||||
|
"enterMfaCode": "Zadejte kód z vaší ověřovací aplikace",
|
||||||
|
"securityKeyRequired": "Pro přihlášení použijte svůj bezpečnostní klíč.",
|
||||||
|
"needToUseAnotherAccount": "Potřebujete použít jiný účet?",
|
||||||
|
"loginLegalDisclaimer": "Kliknutím na tlačítka níže potvrzujete, že jste si přečetli, chápali, a souhlasím s <termsOfService>obchodními podmínkami</termsOfService> a <privacyPolicy>Zásadami ochrany osobních údajů</privacyPolicy>.",
|
||||||
|
"termsOfService": "Podmínky služby",
|
||||||
|
"privacyPolicy": "Ochrana osobních údajů",
|
||||||
|
"userNotFoundWithUsername": "Nebyl nalezen žádný uživatel s tímto uživatelským jménem.",
|
||||||
|
"verify": "Ověřit",
|
||||||
|
"signIn": "Přihlásit se",
|
||||||
|
"forgotPassword": "Zapomněli jste heslo?",
|
||||||
|
"orgSignInTip": "Pokud jste se přihlásili dříve, můžete místo toho zadat své uživatelské jméno nebo e-mail výše pro ověření u poskytovatele identity vaší organizace. Je to jednodušší!",
|
||||||
|
"continueAnyway": "Přesto pokračovat",
|
||||||
|
"dontShowAgain": "Znovu nezobrazovat",
|
||||||
|
"orgSignInNotice": "Věděli jste, že?",
|
||||||
|
"signupOrgNotice": "Chcete se přihlásit?",
|
||||||
|
"signupOrgTip": "Snažíte se přihlásit prostřednictvím poskytovatele identity vaší organizace?",
|
||||||
|
"signupOrgLink": "Namísto toho se přihlaste nebo se zaregistrujte pomocí své organizace",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Použít jiný účet",
|
||||||
|
"logIn": "Přihlásit se",
|
||||||
|
"deviceInformation": "Informace o zařízení",
|
||||||
|
"deviceInformationDescription": "Informace o zařízení a agentovi",
|
||||||
|
"deviceSecurity": "Zabezpečení zařízení",
|
||||||
|
"deviceSecurityDescription": "Informace o bezpečnostní pozici zařízení",
|
||||||
|
"platform": "Platforma",
|
||||||
|
"macosVersion": "macOS verze",
|
||||||
|
"windowsVersion": "Verze Windows",
|
||||||
|
"iosVersion": "Verze iOS",
|
||||||
|
"androidVersion": "Verze Androidu",
|
||||||
|
"osVersion": "Verze OS",
|
||||||
|
"kernelVersion": "Verze jádra",
|
||||||
|
"deviceModel": "Model zařízení",
|
||||||
|
"serialNumber": "Pořadové číslo",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "První vidění",
|
||||||
|
"lastSeen": "Naposledy viděno",
|
||||||
|
"biometricsEnabled": "Biometrie povolena",
|
||||||
|
"diskEncrypted": "Šifrovaný disk",
|
||||||
|
"firewallEnabled": "Firewall povolen",
|
||||||
|
"autoUpdatesEnabled": "Automatické aktualizace povoleny",
|
||||||
|
"tpmAvailable": "TPM k dispozici",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
||||||
|
"linuxAppArmorEnabled": "Pancíř aplikace",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Zobrazit informace o zařízení a nastavení",
|
||||||
|
"devicePendingApprovalDescription": "Toto zařízení čeká na schválení",
|
||||||
|
"deviceBlockedDescription": "Toto zařízení je momentálně blokováno. Nebude se moci připojit k žádným zdrojům, dokud nebude odblokováno.",
|
||||||
|
"unblockClient": "Odblokovat klienta",
|
||||||
|
"unblockClientDescription": "Zařízení bylo odblokováno",
|
||||||
|
"unarchiveClient": "Zrušit archiv klienta",
|
||||||
|
"unarchiveClientDescription": "Zařízení bylo odarchivováno",
|
||||||
|
"block": "Blokovat",
|
||||||
|
"unblock": "Odblokovat",
|
||||||
|
"deviceActions": "Akce zařízení",
|
||||||
|
"deviceActionsDescription": "Spravovat stav zařízení a přístup",
|
||||||
|
"devicePendingApprovalBannerDescription": "Toto zařízení čeká na schválení. Nebude se moci připojit ke zdrojům, dokud nebude schváleno.",
|
||||||
|
"connected": "Připojeno",
|
||||||
|
"disconnected": "Odpojeno",
|
||||||
|
"approvalsEmptyStateTitle": "Schvalování zařízení není povoleno",
|
||||||
|
"approvalsEmptyStateDescription": "Povolte oprávnění oprávnění pro role správce před připojením nových zařízení.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Přejít na role",
|
||||||
|
"approvalsEmptyStateStep1Description": "Přejděte do nastavení rolí vaší organizace pro konfiguraci schválení zařízení.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Povolit schválení zařízení",
|
||||||
|
"approvalsEmptyStateStep2Description": "Upravte roli a povolte možnost 'Vyžadovat schválení zařízení'. Uživatelé s touto rolí budou potřebovat schválení pro nová zařízení správce.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Náhled: Pokud je povoleno, čekající na zařízení se zde zobrazí žádosti o recenzi",
|
||||||
|
"approvalsEmptyStateButtonText": "Spravovat role"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Verbinde ein beliebiges Netzwerk",
|
"sitesBannerTitle": "Verbinde ein beliebiges Netzwerk",
|
||||||
"sitesBannerDescription": "Ein Standort ist eine Verbindung zu einem Remote-Netzwerk, die es Pangolin ermöglicht, Zugriff auf öffentliche oder private Ressourcen für Benutzer überall zu gewähren. Installieren Sie den Site Netzwerk Connector (Newt) wo auch immer Sie eine Binärdatei oder einen Container starten können, um die Verbindung herzustellen.",
|
"sitesBannerDescription": "Ein Standort ist eine Verbindung zu einem Remote-Netzwerk, die es Pangolin ermöglicht, Zugriff auf öffentliche oder private Ressourcen für Benutzer überall zu gewähren. Installieren Sie den Site Netzwerk Connector (Newt) wo auch immer Sie eine Binärdatei oder einen Container starten können, um die Verbindung herzustellen.",
|
||||||
"sitesBannerButtonText": "Standort installieren",
|
"sitesBannerButtonText": "Standort installieren",
|
||||||
|
"approvalsBannerTitle": "Gerätezugriff genehmigen oder verweigern",
|
||||||
|
"approvalsBannerDescription": "Überprüfen und genehmigen oder verweigern Gerätezugriffsanfragen von Benutzern. Wenn Gerätegenehmigungen erforderlich sind, müssen Benutzer eine Administratorgenehmigung erhalten, bevor ihre Geräte sich mit den Ressourcen Ihrer Organisation verbinden können.",
|
||||||
|
"approvalsBannerButtonText": "Mehr erfahren",
|
||||||
"siteCreate": "Standort erstellen",
|
"siteCreate": "Standort erstellen",
|
||||||
"siteCreateDescription2": "Folge den nachfolgenden Schritten, um einen neuen Standort zu erstellen und zu verbinden",
|
"siteCreateDescription2": "Folge den nachfolgenden Schritten, um einen neuen Standort zu erstellen und zu verbinden",
|
||||||
"siteCreateDescription": "Erstellen Sie einen neuen Standort, um Ressourcen zu verbinden",
|
"siteCreateDescription": "Erstellen Sie einen neuen Standort, um Ressourcen zu verbinden",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Rollen suchen...",
|
"accessRolesSearch": "Rollen suchen...",
|
||||||
"accessRolesAdd": "Rolle hinzufügen",
|
"accessRolesAdd": "Rolle hinzufügen",
|
||||||
"accessRoleDelete": "Rolle löschen",
|
"accessRoleDelete": "Rolle löschen",
|
||||||
|
"accessApprovalsManage": "Genehmigungen verwalten",
|
||||||
|
"accessApprovalsDescription": "Zeige und verwalte ausstehende Genehmigungen für den Zugriff auf diese Organisation",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"inviteTitle": "Einladungen öffnen",
|
"inviteTitle": "Einladungen öffnen",
|
||||||
"inviteDescription": "Einladungen für andere Benutzer verwalten, der Organisation beizutreten",
|
"inviteDescription": "Einladungen für andere Benutzer verwalten, der Organisation beizutreten",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Dauer auswählen",
|
"selectDuration": "Dauer auswählen",
|
||||||
"selectResource": "Ressource auswählen",
|
"selectResource": "Ressource auswählen",
|
||||||
"filterByResource": "Nach Ressource filtern",
|
"filterByResource": "Nach Ressource filtern",
|
||||||
|
"selectApprovalState": "Genehmigungsstatus auswählen",
|
||||||
|
"filterByApprovalState": "Filtern nach Genehmigungsstatus",
|
||||||
|
"approvalListEmpty": "Keine Genehmigungen",
|
||||||
|
"approvalState": "Genehmigungsstatus",
|
||||||
|
"approve": "Bestätigen",
|
||||||
|
"approved": "Genehmigt",
|
||||||
|
"denied": "Verweigert",
|
||||||
|
"deniedApproval": "Genehmigung verweigert",
|
||||||
|
"all": "Alle",
|
||||||
|
"deny": "Leugnen",
|
||||||
|
"viewDetails": "Details anzeigen",
|
||||||
|
"requestingNewDeviceApproval": "hat ein neues Gerät angefordert",
|
||||||
"resetFilters": "Filter zurücksetzen",
|
"resetFilters": "Filter zurücksetzen",
|
||||||
"totalBlocked": "Anfragen blockiert von Pangolin",
|
"totalBlocked": "Anfragen blockiert von Pangolin",
|
||||||
"totalRequests": "Gesamte Anfragen",
|
"totalRequests": "Gesamte Anfragen",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Länder",
|
"countries": "Länder",
|
||||||
"accessRoleCreate": "Rolle erstellen",
|
"accessRoleCreate": "Rolle erstellen",
|
||||||
"accessRoleCreateDescription": "Erstellen Sie eine neue Rolle, um Benutzer zu gruppieren und ihre Berechtigungen zu verwalten.",
|
"accessRoleCreateDescription": "Erstellen Sie eine neue Rolle, um Benutzer zu gruppieren und ihre Berechtigungen zu verwalten.",
|
||||||
|
"accessRoleEdit": "Rolle bearbeiten",
|
||||||
|
"accessRoleEditDescription": "Rolleninformationen bearbeiten.",
|
||||||
"accessRoleCreateSubmit": "Rolle erstellen",
|
"accessRoleCreateSubmit": "Rolle erstellen",
|
||||||
"accessRoleCreated": "Rolle erstellt",
|
"accessRoleCreated": "Rolle erstellt",
|
||||||
"accessRoleCreatedDescription": "Die Rolle wurde erfolgreich erstellt.",
|
"accessRoleCreatedDescription": "Die Rolle wurde erfolgreich erstellt.",
|
||||||
"accessRoleErrorCreate": "Fehler beim Erstellen der Rolle",
|
"accessRoleErrorCreate": "Fehler beim Erstellen der Rolle",
|
||||||
"accessRoleErrorCreateDescription": "Beim Erstellen der Rolle ist ein Fehler aufgetreten.",
|
"accessRoleErrorCreateDescription": "Beim Erstellen der Rolle ist ein Fehler aufgetreten.",
|
||||||
|
"accessRoleUpdateSubmit": "Rolle aktualisieren",
|
||||||
|
"accessRoleUpdated": "Rolle aktualisiert",
|
||||||
|
"accessRoleUpdatedDescription": "Die Rolle wurde erfolgreich aktualisiert.",
|
||||||
|
"accessApprovalUpdated": "Genehmigung bearbeitet",
|
||||||
|
"accessApprovalApprovedDescription": "Entscheidung für Genehmigungsanfrage setzen.",
|
||||||
|
"accessApprovalDeniedDescription": "Entscheidung für Genehmigungsanfrage ablehnen.",
|
||||||
|
"accessRoleErrorUpdate": "Fehler beim Aktualisieren der Rolle",
|
||||||
|
"accessRoleErrorUpdateDescription": "Beim Aktualisieren der Rolle ist ein Fehler aufgetreten.",
|
||||||
|
"accessApprovalErrorUpdate": "Genehmigung konnte nicht verarbeitet werden",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Bei der Bearbeitung der Genehmigung ist ein Fehler aufgetreten.",
|
||||||
"accessRoleErrorNewRequired": "Neue Rolle ist erforderlich",
|
"accessRoleErrorNewRequired": "Neue Rolle ist erforderlich",
|
||||||
"accessRoleErrorRemove": "Fehler beim Entfernen der Rolle",
|
"accessRoleErrorRemove": "Fehler beim Entfernen der Rolle",
|
||||||
"accessRoleErrorRemoveDescription": "Beim Entfernen der Rolle ist ein Fehler aufgetreten.",
|
"accessRoleErrorRemoveDescription": "Beim Entfernen der Rolle ist ein Fehler aufgetreten.",
|
||||||
"accessRoleName": "Rollenname",
|
"accessRoleName": "Rollenname",
|
||||||
"accessRoleQuestionRemove": "Sie sind dabei, die Rolle {name} zu löschen. Diese Aktion kann nicht rückgängig gemacht werden.",
|
"accessRoleQuestionRemove": "Du bist dabei die Rolle `{name}` zu löschen. Du kannst diese Aktion nicht rückgängig machen.",
|
||||||
"accessRoleRemove": "Rolle entfernen",
|
"accessRoleRemove": "Rolle entfernen",
|
||||||
"accessRoleRemoveDescription": "Eine Rolle aus der Organisation entfernen",
|
"accessRoleRemoveDescription": "Eine Rolle aus der Organisation entfernen",
|
||||||
"accessRoleRemoveSubmit": "Rolle entfernen",
|
"accessRoleRemoveSubmit": "Rolle entfernen",
|
||||||
@@ -954,13 +983,13 @@
|
|||||||
"passwordExpiryDescription": "Diese Organisation erfordert, dass Sie Ihr Passwort alle {maxDays} Tage ändern.",
|
"passwordExpiryDescription": "Diese Organisation erfordert, dass Sie Ihr Passwort alle {maxDays} Tage ändern.",
|
||||||
"changePasswordNow": "Passwort jetzt ändern",
|
"changePasswordNow": "Passwort jetzt ändern",
|
||||||
"pincodeAuth": "Authentifizierungscode",
|
"pincodeAuth": "Authentifizierungscode",
|
||||||
"pincodeSubmit2": "Code absenden",
|
"pincodeSubmit2": "Code einreichen",
|
||||||
"passwordResetSubmit": "Zurücksetzung anfordern",
|
"passwordResetSubmit": "Zurücksetzung anfordern",
|
||||||
"passwordResetAlreadyHaveCode": "Code eingeben",
|
"passwordResetAlreadyHaveCode": "Code eingeben",
|
||||||
"passwordResetSmtpRequired": "Bitte kontaktieren Sie Ihren Administrator",
|
"passwordResetSmtpRequired": "Bitte kontaktieren Sie Ihren Administrator",
|
||||||
"passwordResetSmtpRequiredDescription": "Zum Zurücksetzen Ihres Passworts ist ein Passwort erforderlich. Bitte wenden Sie sich an Ihren Administrator.",
|
"passwordResetSmtpRequiredDescription": "Zum Zurücksetzen Ihres Passworts ist ein Passwort erforderlich. Bitte wenden Sie sich an Ihren Administrator.",
|
||||||
"passwordBack": "Zurück zum Passwort",
|
"passwordBack": "Zurück zum Passwort",
|
||||||
"loginBack": "Zurück zur Anmeldung",
|
"loginBack": "Zurück zur Haupt-Login-Seite",
|
||||||
"signup": "Registrieren",
|
"signup": "Registrieren",
|
||||||
"loginStart": "Melden Sie sich an, um zu beginnen",
|
"loginStart": "Melden Sie sich an, um zu beginnen",
|
||||||
"idpOidcTokenValidating": "OIDC-Token wird validiert",
|
"idpOidcTokenValidating": "OIDC-Token wird validiert",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
|
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
|
||||||
"actionCreateClient": "Client erstellen",
|
"actionCreateClient": "Client erstellen",
|
||||||
"actionDeleteClient": "Client löschen",
|
"actionDeleteClient": "Client löschen",
|
||||||
|
"actionArchiveClient": "Kunde archivieren",
|
||||||
|
"actionUnarchiveClient": "Client dearchivieren",
|
||||||
|
"actionBlockClient": "Klient sperren",
|
||||||
|
"actionUnblockClient": "Client entsperren",
|
||||||
"actionUpdateClient": "Client aktualisieren",
|
"actionUpdateClient": "Client aktualisieren",
|
||||||
"actionListClients": "Clients auflisten",
|
"actionListClients": "Clients auflisten",
|
||||||
"actionGetClient": "Clients abrufen",
|
"actionGetClient": "Clients abrufen",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Suche...",
|
"searchProgress": "Suche...",
|
||||||
"create": "Erstellen",
|
"create": "Erstellen",
|
||||||
"orgs": "Organisationen",
|
"orgs": "Organisationen",
|
||||||
"loginError": "Beim Anmelden ist ein Fehler aufgetreten",
|
"loginError": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
|
||||||
"loginRequiredForDevice": "Zur Authentifizierung Ihres Geräts ist eine Anmeldung erforderlich",
|
"loginRequiredForDevice": "Anmeldung ist für Ihr Gerät erforderlich.",
|
||||||
"passwordForgot": "Passwort vergessen?",
|
"passwordForgot": "Passwort vergessen?",
|
||||||
"otpAuth": "Zwei-Faktor-Authentifizierung",
|
"otpAuth": "Zwei-Faktor-Authentifizierung",
|
||||||
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
|
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
|
||||||
"otpAuthSubmit": "Code absenden",
|
"otpAuthSubmit": "Code absenden",
|
||||||
"idpContinue": "Oder weiter mit",
|
"idpContinue": "Oder weiter mit",
|
||||||
"otpAuthBack": "Zurück zur Anmeldung",
|
"otpAuthBack": "Zurück zum Passwort",
|
||||||
"navbar": "Navigationsmenü",
|
"navbar": "Navigationsmenü",
|
||||||
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
|
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
|
||||||
"navbarDocsLink": "Dokumentation",
|
"navbarDocsLink": "Dokumentation",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Übersicht",
|
"sidebarOverview": "Übersicht",
|
||||||
"sidebarHome": "Zuhause",
|
"sidebarHome": "Zuhause",
|
||||||
"sidebarSites": "Standorte",
|
"sidebarSites": "Standorte",
|
||||||
|
"sidebarApprovals": "Genehmigungsanfragen",
|
||||||
"sidebarResources": "Ressourcen",
|
"sidebarResources": "Ressourcen",
|
||||||
"sidebarProxyResources": "Öffentlich",
|
"sidebarProxyResources": "Öffentlich",
|
||||||
"sidebarClientResources": "Privat",
|
"sidebarClientResources": "Privat",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Identitätsanbieter",
|
"sidebarIdentityProviders": "Identitätsanbieter",
|
||||||
"sidebarLicense": "Lizenz",
|
"sidebarLicense": "Lizenz",
|
||||||
"sidebarClients": "Clients",
|
"sidebarClients": "Clients",
|
||||||
"sidebarUserDevices": "Benutzergeräte",
|
"sidebarUserDevices": "Benutzer-Geräte",
|
||||||
"sidebarMachineClients": "Maschinen",
|
"sidebarMachineClients": "Maschinen",
|
||||||
"sidebarDomains": "Domänen",
|
"sidebarDomains": "Domänen",
|
||||||
"sidebarGeneral": "Verwalten",
|
"sidebarGeneral": "Verwalten",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Beim Erstellen des Server-Admin-Kontos ist ein Fehler aufgetreten.",
|
"setupErrorCreateAdmin": "Beim Erstellen des Server-Admin-Kontos ist ein Fehler aufgetreten.",
|
||||||
"certificateStatus": "Zertifikatsstatus",
|
"certificateStatus": "Zertifikatsstatus",
|
||||||
"loading": "Laden",
|
"loading": "Laden",
|
||||||
|
"loadingAnalytics": "Analytik wird geladen",
|
||||||
"restart": "Neustart",
|
"restart": "Neustart",
|
||||||
"domains": "Domänen",
|
"domains": "Domänen",
|
||||||
"domainsDescription": "Erstellen und verwalten der in der Organisation verfügbaren Domänen",
|
"domainsDescription": "Erstellen und verwalten der in der Organisation verfügbaren Domänen",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Datenaktualisierung fehlgeschlagen",
|
"refreshError": "Datenaktualisierung fehlgeschlagen",
|
||||||
"verified": "Verifiziert",
|
"verified": "Verifiziert",
|
||||||
"pending": "Ausstehend",
|
"pending": "Ausstehend",
|
||||||
|
"pendingApproval": "Ausstehende Genehmigung",
|
||||||
"sidebarBilling": "Abrechnung",
|
"sidebarBilling": "Abrechnung",
|
||||||
"billing": "Abrechnung",
|
"billing": "Abrechnung",
|
||||||
"orgBillingDescription": "Zahlungsinformationen und Abonnements verwalten",
|
"orgBillingDescription": "Zahlungsinformationen und Abonnements verwalten",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Sicherheitsschlüssel erfolgreich entfernt",
|
"securityKeyRemoveSuccess": "Sicherheitsschlüssel erfolgreich entfernt",
|
||||||
"securityKeyRemoveError": "Fehler beim Entfernen des Sicherheitsschlüssels",
|
"securityKeyRemoveError": "Fehler beim Entfernen des Sicherheitsschlüssels",
|
||||||
"securityKeyLoadError": "Fehler beim Laden der Sicherheitsschlüssel",
|
"securityKeyLoadError": "Fehler beim Laden der Sicherheitsschlüssel",
|
||||||
"securityKeyLogin": "Mit dem Sicherheitsschlüssel fortfahren",
|
"securityKeyLogin": "Sicherheitsschlüssel verwenden",
|
||||||
"securityKeyAuthError": "Fehler bei der Authentifizierung mit Sicherheitsschlüssel",
|
"securityKeyAuthError": "Fehler bei der Authentifizierung mit Sicherheitsschlüssel",
|
||||||
"securityKeyRecommendation": "Erwägen Sie die Registrierung eines weiteren Sicherheitsschlüssels auf einem anderen Gerät, um sicherzustellen, dass Sie sich nicht aus Ihrem Konto aussperren.",
|
"securityKeyRecommendation": "Erwägen Sie die Registrierung eines weiteren Sicherheitsschlüssels auf einem anderen Gerät, um sicherzustellen, dass Sie sich nicht aus Ihrem Konto aussperren.",
|
||||||
"registering": "Registrierung...",
|
"registering": "Registrierung...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Gesunder Intervall",
|
"IntervalSeconds": "Gesunder Intervall",
|
||||||
"timeoutSeconds": "Timeout (Sek.)",
|
"timeoutSeconds": "Timeout (Sek.)",
|
||||||
"timeIsInSeconds": "Zeit ist in Sekunden",
|
"timeIsInSeconds": "Zeit ist in Sekunden",
|
||||||
|
"requireDeviceApproval": "Gerätegenehmigungen erforderlich",
|
||||||
|
"requireDeviceApprovalDescription": "Benutzer mit dieser Rolle benötigen neue Geräte, die von einem Administrator genehmigt wurden, bevor sie sich verbinden und auf Ressourcen zugreifen können.",
|
||||||
"retryAttempts": "Wiederholungsversuche",
|
"retryAttempts": "Wiederholungsversuche",
|
||||||
"expectedResponseCodes": "Erwartete Antwortcodes",
|
"expectedResponseCodes": "Erwartete Antwortcodes",
|
||||||
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
|
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Keine internen Ressourcen gefunden.",
|
"resourcesTableNoInternalResourcesFound": "Keine internen Ressourcen gefunden.",
|
||||||
"resourcesTableDestination": "Ziel",
|
"resourcesTableDestination": "Ziel",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Alias-Adresse",
|
||||||
|
"resourcesTableAliasAddressInfo": "Diese Adresse ist Teil des Utility-Subnetzes der Organisation. Sie wird verwendet, um Alias-Einträge mit interner DNS-Auflösung aufzulösen.",
|
||||||
"resourcesTableClients": "Clients",
|
"resourcesTableClients": "Clients",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "und sind nur intern zugänglich, wenn mit einem Client verbunden.",
|
"resourcesTableAndOnlyAccessibleInternally": "und sind nur intern zugänglich, wenn mit einem Client verbunden.",
|
||||||
"resourcesTableNoTargets": "Keine Ziele",
|
"resourcesTableNoTargets": "Keine Ziele",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Code muss 9 Zeichen lang sein (z.B. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Code muss 9 Zeichen lang sein (z.B. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Ungültiger oder abgelaufener Code",
|
"deviceCodeInvalidOrExpired": "Ungültiger oder abgelaufener Code",
|
||||||
"deviceCodeVerifyFailed": "Fehler beim Überprüfen des Gerätecodes",
|
"deviceCodeVerifyFailed": "Fehler beim Überprüfen des Gerätecodes",
|
||||||
|
"deviceCodeValidating": "Überprüfe Gerätecode...",
|
||||||
|
"deviceCodeVerifying": "Geräteautorisierung wird überprüft...",
|
||||||
"signedInAs": "Angemeldet als",
|
"signedInAs": "Angemeldet als",
|
||||||
"deviceCodeEnterPrompt": "Geben Sie den auf dem Gerät angezeigten Code ein",
|
"deviceCodeEnterPrompt": "Geben Sie den auf dem Gerät angezeigten Code ein",
|
||||||
"continue": "Weiter",
|
"continue": "Weiter",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Zugriff auf alle Organisationen, auf die Ihr Konto Zugriff hat",
|
"deviceOrganizationsAccess": "Zugriff auf alle Organisationen, auf die Ihr Konto Zugriff hat",
|
||||||
"deviceAuthorize": "{applicationName} autorisieren",
|
"deviceAuthorize": "{applicationName} autorisieren",
|
||||||
"deviceConnected": "Gerät verbunden!",
|
"deviceConnected": "Gerät verbunden!",
|
||||||
"deviceAuthorizedMessage": "Gerät ist berechtigt, auf Ihr Konto zuzugreifen.",
|
"deviceAuthorizedMessage": "Gerät ist berechtigt, auf Ihr Konto zuzugreifen. Bitte kehren Sie zur Client-Anwendung zurück.",
|
||||||
"pangolinCloud": "Pangolin Cloud",
|
"pangolinCloud": "Pangolin Cloud",
|
||||||
"viewDevices": "Geräte anzeigen",
|
"viewDevices": "Geräte anzeigen",
|
||||||
"viewDevicesDescription": "Verwalten Sie Ihre verbundenen Geräte",
|
"viewDevicesDescription": "Verwalten Sie Ihre verbundenen Geräte",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Nicht du? Verwenden Sie ein anderes Konto.",
|
"deviceLoginUseDifferentAccount": "Nicht du? Verwenden Sie ein anderes Konto.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Ein Gerät fordert Zugriff auf dieses Konto an.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Ein Gerät fordert Zugriff auf dieses Konto an.",
|
||||||
|
"loginSelectAuthenticationMethod": "Wählen Sie eine Authentifizierungsmethode aus, um fortzufahren.",
|
||||||
"noData": "Keine Daten",
|
"noData": "Keine Daten",
|
||||||
"machineClients": "Maschinen-Clients",
|
"machineClients": "Maschinen-Clients",
|
||||||
"install": "Installieren",
|
"install": "Installieren",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Dienst vorübergehend nicht verfügbar",
|
"maintenanceScreenTitle": "Dienst vorübergehend nicht verfügbar",
|
||||||
"maintenanceScreenMessage": "Wir haben derzeit technische Schwierigkeiten. Bitte schauen Sie bald noch einmal vorbei.",
|
"maintenanceScreenMessage": "Wir haben derzeit technische Schwierigkeiten. Bitte schauen Sie bald noch einmal vorbei.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Geschätzter Abschluss:",
|
"maintenanceScreenEstimatedCompletion": "Geschätzter Abschluss:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich"
|
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich",
|
||||||
|
"available": "Verfügbar",
|
||||||
|
"archived": "Archiviert",
|
||||||
|
"noArchivedDevices": "Keine archivierten Geräte gefunden",
|
||||||
|
"deviceArchived": "Gerät archiviert",
|
||||||
|
"deviceArchivedDescription": "Das Gerät wurde erfolgreich archiviert.",
|
||||||
|
"errorArchivingDevice": "Fehler beim Archivieren des Geräts",
|
||||||
|
"failedToArchiveDevice": "Archivierung des Geräts fehlgeschlagen",
|
||||||
|
"deviceQuestionArchive": "Sind Sie sicher, dass Sie dieses Gerät archivieren möchten?",
|
||||||
|
"deviceMessageArchive": "Das Gerät wird archiviert und aus Ihrer Liste der aktiven Geräte entfernt.",
|
||||||
|
"deviceArchiveConfirm": "Gerät archivieren",
|
||||||
|
"archiveDevice": "Gerät archivieren",
|
||||||
|
"archive": "Archiv",
|
||||||
|
"deviceUnarchived": "Gerät nicht archiviert",
|
||||||
|
"deviceUnarchivedDescription": "Das Gerät wurde erfolgreich deinstalliert.",
|
||||||
|
"errorUnarchivingDevice": "Fehler beim Entarchivieren des Geräts",
|
||||||
|
"failedToUnarchiveDevice": "Fehler beim Entfernen des Geräts",
|
||||||
|
"unarchive": "Archivieren",
|
||||||
|
"archiveClient": "Kunde archivieren",
|
||||||
|
"archiveClientQuestion": "Sind Sie sicher, dass Sie diesen Client archivieren möchten?",
|
||||||
|
"archiveClientMessage": "Der Client wird archiviert und aus der Liste Ihrer aktiven Clients entfernt.",
|
||||||
|
"archiveClientConfirm": "Kunde archivieren",
|
||||||
|
"blockClient": "Klient sperren",
|
||||||
|
"blockClientQuestion": "Sind Sie sicher, dass Sie diesen Client blockieren möchten?",
|
||||||
|
"blockClientMessage": "Das Gerät wird gezwungen, die Verbindung zu trennen, wenn es gerade verbunden ist. Sie können das Gerät später entsperren.",
|
||||||
|
"blockClientConfirm": "Klient sperren",
|
||||||
|
"active": "Aktiv",
|
||||||
|
"usernameOrEmail": "Benutzername oder E-Mail",
|
||||||
|
"selectYourOrganization": "Wählen Sie Ihre Organisation",
|
||||||
|
"signInTo": "Einloggen in",
|
||||||
|
"signInWithPassword": "Mit Passwort fortfahren",
|
||||||
|
"noAuthMethodsAvailable": "Keine Authentifizierungsmethoden für diese Organisation verfügbar.",
|
||||||
|
"enterPassword": "Geben Sie Ihr Passwort ein",
|
||||||
|
"enterMfaCode": "Geben Sie den Code aus Ihrer Authentifizierungs-App ein",
|
||||||
|
"securityKeyRequired": "Bitte verwenden Sie Ihren Sicherheitsschlüssel zum Anmelden.",
|
||||||
|
"needToUseAnotherAccount": "Benötigen Sie ein anderes Konto?",
|
||||||
|
"loginLegalDisclaimer": "Indem Sie auf die Buttons unten klicken, bestätigen Sie, dass Sie gelesen haben, verstehen, und stimmen den <termsOfService>Nutzungsbedingungen</termsOfService> und <privacyPolicy>Datenschutzrichtlinien</privacyPolicy> zu.",
|
||||||
|
"termsOfService": "Nutzungsbedingungen",
|
||||||
|
"privacyPolicy": "Datenschutzerklärung",
|
||||||
|
"userNotFoundWithUsername": "Kein Benutzer mit diesem Benutzernamen gefunden.",
|
||||||
|
"verify": "Überprüfen",
|
||||||
|
"signIn": "Anmelden",
|
||||||
|
"forgotPassword": "Passwort vergessen?",
|
||||||
|
"orgSignInTip": "Wenn Sie sich vorher angemeldet haben, können Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse eingeben, um sich stattdessen beim Identifikationsprovider Ihrer Organisation zu authentifizieren. Es ist einfacher!",
|
||||||
|
"continueAnyway": "Trotzdem fortfahren",
|
||||||
|
"dontShowAgain": "Nicht mehr anzeigen",
|
||||||
|
"orgSignInNotice": "Wussten Sie schon?",
|
||||||
|
"signupOrgNotice": "Versucht sich anzumelden?",
|
||||||
|
"signupOrgTip": "Versuchen Sie, sich über den Identitätsanbieter Ihrer Organisation anzumelden?",
|
||||||
|
"signupOrgLink": "Melden Sie sich an oder melden Sie sich stattdessen bei Ihrer Organisation an",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Anderes Konto verwenden",
|
||||||
|
"logIn": "Anmelden",
|
||||||
|
"deviceInformation": "Geräteinformationen",
|
||||||
|
"deviceInformationDescription": "Informationen über das Gerät und den Agent",
|
||||||
|
"deviceSecurity": "Gerätesicherheit",
|
||||||
|
"deviceSecurityDescription": "Informationen zur Gerätesicherheit",
|
||||||
|
"platform": "Plattform",
|
||||||
|
"macosVersion": "macOS-Version",
|
||||||
|
"windowsVersion": "Windows-Version",
|
||||||
|
"iosVersion": "iOS-Version",
|
||||||
|
"androidVersion": "Android-Version",
|
||||||
|
"osVersion": "OS-Version",
|
||||||
|
"kernelVersion": "Kernel-Version",
|
||||||
|
"deviceModel": "Gerätemodell",
|
||||||
|
"serialNumber": "Seriennummer",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Erster Blick",
|
||||||
|
"lastSeen": "Zuletzt gesehen",
|
||||||
|
"biometricsEnabled": "Biometrie aktiviert",
|
||||||
|
"diskEncrypted": "Festplatte verschlüsselt",
|
||||||
|
"firewallEnabled": "Firewall aktiviert",
|
||||||
|
"autoUpdatesEnabled": "Automatische Updates aktiviert",
|
||||||
|
"tpmAvailable": "TPM verfügbar",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Schutz der Systemintegrität (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Firewall Stealth-Modus",
|
||||||
|
"linuxAppArmorEnabled": "AppRüstung",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Geräteinformationen und -einstellungen anzeigen",
|
||||||
|
"devicePendingApprovalDescription": "Dieses Gerät wartet auf Freigabe",
|
||||||
|
"deviceBlockedDescription": "Dieses Gerät ist derzeit gesperrt. Es kann keine Verbindung zu anderen Ressourcen herstellen, es sei denn, es entsperrt.",
|
||||||
|
"unblockClient": "Client entsperren",
|
||||||
|
"unblockClientDescription": "Das Gerät wurde entsperrt",
|
||||||
|
"unarchiveClient": "Client dearchivieren",
|
||||||
|
"unarchiveClientDescription": "Das Gerät wurde nicht archiviert",
|
||||||
|
"block": "Blockieren",
|
||||||
|
"unblock": "Entsperren",
|
||||||
|
"deviceActions": "Geräte-Aktionen",
|
||||||
|
"deviceActionsDescription": "Gerätestatus und Zugriff verwalten",
|
||||||
|
"devicePendingApprovalBannerDescription": "Dieses Gerät wartet auf Genehmigung. Es kann sich erst mit Ressourcen verbinden.",
|
||||||
|
"connected": "Verbunden",
|
||||||
|
"disconnected": "Verbindung getrennt",
|
||||||
|
"approvalsEmptyStateTitle": "Gerätezulassungen nicht aktiviert",
|
||||||
|
"approvalsEmptyStateDescription": "Aktiviere Gerätegenehmigungen für Rollen, um Administratorgenehmigungen zu benötigen, bevor Benutzer neue Geräte verbinden können.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Gehe zu Rollen",
|
||||||
|
"approvalsEmptyStateStep1Description": "Navigieren Sie zu den Rolleneinstellungen Ihrer Organisation, um die Gerätefreigaben zu konfigurieren.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Gerätegenehmigungen aktivieren",
|
||||||
|
"approvalsEmptyStateStep2Description": "Bearbeite eine Rolle und aktiviere die Option 'Gerätegenehmigung erforderlich'. Benutzer mit dieser Rolle benötigen Administrator-Genehmigung für neue Geräte.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Vorschau: Wenn aktiviert, werden ausstehende Geräteanfragen hier zur Überprüfung angezeigt",
|
||||||
|
"approvalsEmptyStateButtonText": "Rollen verwalten"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Connect Any Network",
|
"sitesBannerTitle": "Connect Any Network",
|
||||||
"sitesBannerDescription": "A site is a connection to a remote network that allows Pangolin to provide access to resources, whether public or private, to users anywhere. Install the site network connector (Newt) anywhere you can run a binary or container to establish the connection.",
|
"sitesBannerDescription": "A site is a connection to a remote network that allows Pangolin to provide access to resources, whether public or private, to users anywhere. Install the site network connector (Newt) anywhere you can run a binary or container to establish the connection.",
|
||||||
"sitesBannerButtonText": "Install Site",
|
"sitesBannerButtonText": "Install Site",
|
||||||
|
"approvalsBannerTitle": "Approve or Deny Device Access",
|
||||||
|
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
|
||||||
|
"approvalsBannerButtonText": "Learn More",
|
||||||
"siteCreate": "Create Site",
|
"siteCreate": "Create Site",
|
||||||
"siteCreateDescription2": "Follow the steps below to create and connect a new site",
|
"siteCreateDescription2": "Follow the steps below to create and connect a new site",
|
||||||
"siteCreateDescription": "Create a new site to start connecting resources",
|
"siteCreateDescription": "Create a new site to start connecting resources",
|
||||||
@@ -258,7 +261,7 @@
|
|||||||
"accessRolesAdd": "Add Role",
|
"accessRolesAdd": "Add Role",
|
||||||
"accessRoleDelete": "Delete Role",
|
"accessRoleDelete": "Delete Role",
|
||||||
"accessApprovalsManage": "Manage Approvals",
|
"accessApprovalsManage": "Manage Approvals",
|
||||||
"accessApprovalsDescription": "Manage approval requests in the organization",
|
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"inviteTitle": "Open Invitations",
|
"inviteTitle": "Open Invitations",
|
||||||
"inviteDescription": "Manage invitations for other users to join the organization",
|
"inviteDescription": "Manage invitations for other users to join the organization",
|
||||||
@@ -1308,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "An error occurred while creating the server admin account.",
|
"setupErrorCreateAdmin": "An error occurred while creating the server admin account.",
|
||||||
"certificateStatus": "Certificate Status",
|
"certificateStatus": "Certificate Status",
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
|
"loadingAnalytics": "Loading Analytics",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
"domainsDescription": "Create and manage domains available in the organization",
|
"domainsDescription": "Create and manage domains available in the organization",
|
||||||
@@ -1580,7 +1584,7 @@
|
|||||||
"timeoutSeconds": "Timeout (sec)",
|
"timeoutSeconds": "Timeout (sec)",
|
||||||
"timeIsInSeconds": "Time is in seconds",
|
"timeIsInSeconds": "Time is in seconds",
|
||||||
"requireDeviceApproval": "Require Device Approvals",
|
"requireDeviceApproval": "Require Device Approvals",
|
||||||
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
|
||||||
"retryAttempts": "Retry Attempts",
|
"retryAttempts": "Retry Attempts",
|
||||||
"expectedResponseCodes": "Expected Response Codes",
|
"expectedResponseCodes": "Expected Response Codes",
|
||||||
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
||||||
@@ -1621,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "No internal resources found.",
|
"resourcesTableNoInternalResourcesFound": "No internal resources found.",
|
||||||
"resourcesTableDestination": "Destination",
|
"resourcesTableDestination": "Destination",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Alias Address",
|
||||||
|
"resourcesTableAliasAddressInfo": "This address is part of the organization's utility subnet. It's used to resolve alias records using internal DNS resolution.",
|
||||||
"resourcesTableClients": "Clients",
|
"resourcesTableClients": "Clients",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "and are only accessible internally when connected with a client.",
|
"resourcesTableAndOnlyAccessibleInternally": "and are only accessible internally when connected with a client.",
|
||||||
"resourcesTableNoTargets": "No targets",
|
"resourcesTableNoTargets": "No targets",
|
||||||
@@ -2482,5 +2488,54 @@
|
|||||||
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
|
||||||
"signupOrgLink": "Sign in or sign up with your organization instead",
|
"signupOrgLink": "Sign in or sign up with your organization instead",
|
||||||
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
|
||||||
"logIn": "Log In"
|
"logIn": "Log In",
|
||||||
|
"deviceInformation": "Device Information",
|
||||||
|
"deviceInformationDescription": "Information about the device and agent",
|
||||||
|
"deviceSecurity": "Device Security",
|
||||||
|
"deviceSecurityDescription": "Device security posture information",
|
||||||
|
"platform": "Platform",
|
||||||
|
"macosVersion": "macOS Version",
|
||||||
|
"windowsVersion": "Windows Version",
|
||||||
|
"iosVersion": "iOS Version",
|
||||||
|
"androidVersion": "Android Version",
|
||||||
|
"osVersion": "OS Version",
|
||||||
|
"kernelVersion": "Kernel Version",
|
||||||
|
"deviceModel": "Device Model",
|
||||||
|
"serialNumber": "Serial Number",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "First Seen",
|
||||||
|
"lastSeen": "Last Seen",
|
||||||
|
"biometricsEnabled": "Biometrics Enabled",
|
||||||
|
"diskEncrypted": "Disk Encrypted",
|
||||||
|
"firewallEnabled": "Firewall Enabled",
|
||||||
|
"autoUpdatesEnabled": "Auto Updates Enabled",
|
||||||
|
"tpmAvailable": "TPM Available",
|
||||||
|
"windowsDefenderEnabled": "Windows Defender Enabled",
|
||||||
|
"macosSipEnabled": "System Integrity Protection (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Firewall Stealth Mode",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "View device information and settings",
|
||||||
|
"devicePendingApprovalDescription": "This device is waiting for approval",
|
||||||
|
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
|
||||||
|
"unblockClient": "Unblock Client",
|
||||||
|
"unblockClientDescription": "The device has been unblocked",
|
||||||
|
"unarchiveClient": "Unarchive Client",
|
||||||
|
"unarchiveClientDescription": "The device has been unarchived",
|
||||||
|
"block": "Block",
|
||||||
|
"unblock": "Unblock",
|
||||||
|
"deviceActions": "Device Actions",
|
||||||
|
"deviceActionsDescription": "Manage device status and access",
|
||||||
|
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
|
||||||
|
"connected": "Connected",
|
||||||
|
"disconnected": "Disconnected",
|
||||||
|
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
|
||||||
|
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Go to Roles",
|
||||||
|
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
|
||||||
|
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
|
||||||
|
"approvalsEmptyStateButtonText": "Manage Roles"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Conectar cualquier red",
|
"sitesBannerTitle": "Conectar cualquier red",
|
||||||
"sitesBannerDescription": "Un sitio es una conexión a una red remota que permite a Pangolin proporcionar acceso a recursos, públicos o privados, a usuarios en cualquier lugar. Instale el conector de red del sitio (Newt) en cualquier lugar donde pueda ejecutar un binario o contenedor para establecer la conexión.",
|
"sitesBannerDescription": "Un sitio es una conexión a una red remota que permite a Pangolin proporcionar acceso a recursos, públicos o privados, a usuarios en cualquier lugar. Instale el conector de red del sitio (Newt) en cualquier lugar donde pueda ejecutar un binario o contenedor para establecer la conexión.",
|
||||||
"sitesBannerButtonText": "Instalar sitio",
|
"sitesBannerButtonText": "Instalar sitio",
|
||||||
|
"approvalsBannerTitle": "Aprobar o denegar el acceso al dispositivo",
|
||||||
|
"approvalsBannerDescription": "Revisar y aprobar o denegar las solicitudes de acceso al dispositivo de los usuarios. Cuando se requieren aprobaciones de dispositivos, los usuarios deben obtener la aprobación del administrador antes de que sus dispositivos puedan conectarse a los recursos de su organización.",
|
||||||
|
"approvalsBannerButtonText": "Saber más",
|
||||||
"siteCreate": "Crear sitio",
|
"siteCreate": "Crear sitio",
|
||||||
"siteCreateDescription2": "Siga los pasos siguientes para crear y conectar un nuevo sitio",
|
"siteCreateDescription2": "Siga los pasos siguientes para crear y conectar un nuevo sitio",
|
||||||
"siteCreateDescription": "Crear un nuevo sitio para empezar a conectar recursos",
|
"siteCreateDescription": "Crear un nuevo sitio para empezar a conectar recursos",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Buscar roles...",
|
"accessRolesSearch": "Buscar roles...",
|
||||||
"accessRolesAdd": "Añadir rol",
|
"accessRolesAdd": "Añadir rol",
|
||||||
"accessRoleDelete": "Eliminar rol",
|
"accessRoleDelete": "Eliminar rol",
|
||||||
|
"accessApprovalsManage": "Administrar aprobaciones",
|
||||||
|
"accessApprovalsDescription": "Ver y administrar aprobaciones pendientes para el acceso a esta organización",
|
||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"inviteTitle": "Invitaciones abiertas",
|
"inviteTitle": "Invitaciones abiertas",
|
||||||
"inviteDescription": "Administrar invitaciones para que otros usuarios se unan a la organización",
|
"inviteDescription": "Administrar invitaciones para que otros usuarios se unan a la organización",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Seleccionar duración",
|
"selectDuration": "Seleccionar duración",
|
||||||
"selectResource": "Seleccionar Recurso",
|
"selectResource": "Seleccionar Recurso",
|
||||||
"filterByResource": "Filtrar por Recurso",
|
"filterByResource": "Filtrar por Recurso",
|
||||||
|
"selectApprovalState": "Seleccionar Estado de Aprobación",
|
||||||
|
"filterByApprovalState": "Filtrar por estado de aprobación",
|
||||||
|
"approvalListEmpty": "No hay aprobaciones",
|
||||||
|
"approvalState": "Estado de aprobación",
|
||||||
|
"approve": "Aprobar",
|
||||||
|
"approved": "Aprobado",
|
||||||
|
"denied": "Denegado",
|
||||||
|
"deniedApproval": "Aprobación denegada",
|
||||||
|
"all": "Todo",
|
||||||
|
"deny": "Denegar",
|
||||||
|
"viewDetails": "Ver detalles",
|
||||||
|
"requestingNewDeviceApproval": "solicitó un nuevo dispositivo",
|
||||||
"resetFilters": "Reiniciar filtros",
|
"resetFilters": "Reiniciar filtros",
|
||||||
"totalBlocked": "Solicitudes bloqueadas por Pangolin",
|
"totalBlocked": "Solicitudes bloqueadas por Pangolin",
|
||||||
"totalRequests": "Solicitudes totales",
|
"totalRequests": "Solicitudes totales",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Países",
|
"countries": "Países",
|
||||||
"accessRoleCreate": "Crear rol",
|
"accessRoleCreate": "Crear rol",
|
||||||
"accessRoleCreateDescription": "Crear un nuevo rol para agrupar usuarios y administrar sus permisos.",
|
"accessRoleCreateDescription": "Crear un nuevo rol para agrupar usuarios y administrar sus permisos.",
|
||||||
|
"accessRoleEdit": "Editar rol",
|
||||||
|
"accessRoleEditDescription": "Editar información de rol.",
|
||||||
"accessRoleCreateSubmit": "Crear rol",
|
"accessRoleCreateSubmit": "Crear rol",
|
||||||
"accessRoleCreated": "Rol creado",
|
"accessRoleCreated": "Rol creado",
|
||||||
"accessRoleCreatedDescription": "El rol se ha creado correctamente.",
|
"accessRoleCreatedDescription": "El rol se ha creado correctamente.",
|
||||||
"accessRoleErrorCreate": "Error al crear el rol",
|
"accessRoleErrorCreate": "Error al crear el rol",
|
||||||
"accessRoleErrorCreateDescription": "Se ha producido un error al crear el rol.",
|
"accessRoleErrorCreateDescription": "Se ha producido un error al crear el rol.",
|
||||||
|
"accessRoleUpdateSubmit": "Actualizar rol",
|
||||||
|
"accessRoleUpdated": "Rol actualizado",
|
||||||
|
"accessRoleUpdatedDescription": "El rol se ha actualizado correctamente.",
|
||||||
|
"accessApprovalUpdated": "Aprobación procesada",
|
||||||
|
"accessApprovalApprovedDescription": "Establezca la decisión de Solicitud de Aprobación a aprobar.",
|
||||||
|
"accessApprovalDeniedDescription": "Define la decisión de Solicitud de Aprobación a denegar.",
|
||||||
|
"accessRoleErrorUpdate": "Error al actualizar el rol",
|
||||||
|
"accessRoleErrorUpdateDescription": "Se ha producido un error al actualizar el rol.",
|
||||||
|
"accessApprovalErrorUpdate": "Error al procesar la aprobación",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Se ha producido un error al procesar la aprobación.",
|
||||||
"accessRoleErrorNewRequired": "Se requiere un nuevo rol",
|
"accessRoleErrorNewRequired": "Se requiere un nuevo rol",
|
||||||
"accessRoleErrorRemove": "Error al eliminar el rol",
|
"accessRoleErrorRemove": "Error al eliminar el rol",
|
||||||
"accessRoleErrorRemoveDescription": "Ocurrió un error mientras se eliminaba el rol.",
|
"accessRoleErrorRemoveDescription": "Ocurrió un error mientras se eliminaba el rol.",
|
||||||
"accessRoleName": "Nombre del Rol",
|
"accessRoleName": "Nombre del Rol",
|
||||||
"accessRoleQuestionRemove": "Estás a punto de eliminar el rol {name} . No puedes deshacer esta acción.",
|
"accessRoleQuestionRemove": "Estás a punto de eliminar el rol `{name}`. No puedes deshacer esta acción.",
|
||||||
"accessRoleRemove": "Quitar rol",
|
"accessRoleRemove": "Quitar rol",
|
||||||
"accessRoleRemoveDescription": "Eliminar un rol de la organización",
|
"accessRoleRemoveDescription": "Eliminar un rol de la organización",
|
||||||
"accessRoleRemoveSubmit": "Quitar rol",
|
"accessRoleRemoveSubmit": "Quitar rol",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "Póngase en contacto con su administrador",
|
"passwordResetSmtpRequired": "Póngase en contacto con su administrador",
|
||||||
"passwordResetSmtpRequiredDescription": "Se requiere un código de restablecimiento de contraseña para restablecer su contraseña. Póngase en contacto con su administrador para obtener asistencia.",
|
"passwordResetSmtpRequiredDescription": "Se requiere un código de restablecimiento de contraseña para restablecer su contraseña. Póngase en contacto con su administrador para obtener asistencia.",
|
||||||
"passwordBack": "Volver a la contraseña",
|
"passwordBack": "Volver a la contraseña",
|
||||||
"loginBack": "Volver a iniciar sesión",
|
"loginBack": "Volver a la página principal de acceso",
|
||||||
"signup": "Regístrate",
|
"signup": "Regístrate",
|
||||||
"loginStart": "Inicia sesión para empezar",
|
"loginStart": "Inicia sesión para empezar",
|
||||||
"idpOidcTokenValidating": "Validando token OIDC",
|
"idpOidcTokenValidating": "Validando token OIDC",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Actualizar IDP Org",
|
"actionUpdateIdpOrg": "Actualizar IDP Org",
|
||||||
"actionCreateClient": "Crear cliente",
|
"actionCreateClient": "Crear cliente",
|
||||||
"actionDeleteClient": "Eliminar cliente",
|
"actionDeleteClient": "Eliminar cliente",
|
||||||
|
"actionArchiveClient": "Archivar cliente",
|
||||||
|
"actionUnarchiveClient": "Desarchivar cliente",
|
||||||
|
"actionBlockClient": "Bloquear cliente",
|
||||||
|
"actionUnblockClient": "Desbloquear cliente",
|
||||||
"actionUpdateClient": "Actualizar cliente",
|
"actionUpdateClient": "Actualizar cliente",
|
||||||
"actionListClients": "Listar clientes",
|
"actionListClients": "Listar clientes",
|
||||||
"actionGetClient": "Obtener cliente",
|
"actionGetClient": "Obtener cliente",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Buscar...",
|
"searchProgress": "Buscar...",
|
||||||
"create": "Crear",
|
"create": "Crear",
|
||||||
"orgs": "Organizaciones",
|
"orgs": "Organizaciones",
|
||||||
"loginError": "Se ha producido un error al iniciar sesión",
|
"loginError": "Ocurrió un error inesperado. Por favor, inténtelo de nuevo.",
|
||||||
"loginRequiredForDevice": "Es necesario iniciar sesión para autenticar tu dispositivo.",
|
"loginRequiredForDevice": "Es necesario iniciar sesión para tu dispositivo.",
|
||||||
"passwordForgot": "¿Olvidaste tu contraseña?",
|
"passwordForgot": "¿Olvidaste tu contraseña?",
|
||||||
"otpAuth": "Autenticación de dos factores",
|
"otpAuth": "Autenticación de dos factores",
|
||||||
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
|
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
|
||||||
"otpAuthSubmit": "Enviar código",
|
"otpAuthSubmit": "Enviar código",
|
||||||
"idpContinue": "O continuar con",
|
"idpContinue": "O continuar con",
|
||||||
"otpAuthBack": "Volver a iniciar sesión",
|
"otpAuthBack": "Volver a la contraseña",
|
||||||
"navbar": "Menú de navegación",
|
"navbar": "Menú de navegación",
|
||||||
"navbarDescription": "Menú de navegación principal para la aplicación",
|
"navbarDescription": "Menú de navegación principal para la aplicación",
|
||||||
"navbarDocsLink": "Documentación",
|
"navbarDocsLink": "Documentación",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Resumen",
|
"sidebarOverview": "Resumen",
|
||||||
"sidebarHome": "Inicio",
|
"sidebarHome": "Inicio",
|
||||||
"sidebarSites": "Sitios",
|
"sidebarSites": "Sitios",
|
||||||
|
"sidebarApprovals": "Solicitudes de aprobación",
|
||||||
"sidebarResources": "Recursos",
|
"sidebarResources": "Recursos",
|
||||||
"sidebarProxyResources": "Público",
|
"sidebarProxyResources": "Público",
|
||||||
"sidebarClientResources": "Privado",
|
"sidebarClientResources": "Privado",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Proveedores de identidad",
|
"sidebarIdentityProviders": "Proveedores de identidad",
|
||||||
"sidebarLicense": "Licencia",
|
"sidebarLicense": "Licencia",
|
||||||
"sidebarClients": "Clientes",
|
"sidebarClients": "Clientes",
|
||||||
"sidebarUserDevices": "Usuarios",
|
"sidebarUserDevices": "Dispositivos de usuario",
|
||||||
"sidebarMachineClients": "Máquinas",
|
"sidebarMachineClients": "Máquinas",
|
||||||
"sidebarDomains": "Dominios",
|
"sidebarDomains": "Dominios",
|
||||||
"sidebarGeneral": "Gestionar",
|
"sidebarGeneral": "Gestionar",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Se produjo un error al crear la cuenta de administrador del servidor.",
|
"setupErrorCreateAdmin": "Se produjo un error al crear la cuenta de administrador del servidor.",
|
||||||
"certificateStatus": "Estado del certificado",
|
"certificateStatus": "Estado del certificado",
|
||||||
"loading": "Cargando",
|
"loading": "Cargando",
|
||||||
|
"loadingAnalytics": "Cargando analíticas",
|
||||||
"restart": "Reiniciar",
|
"restart": "Reiniciar",
|
||||||
"domains": "Dominios",
|
"domains": "Dominios",
|
||||||
"domainsDescription": "Crear y administrar dominios disponibles en la organización",
|
"domainsDescription": "Crear y administrar dominios disponibles en la organización",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Error al actualizar datos",
|
"refreshError": "Error al actualizar datos",
|
||||||
"verified": "Verificado",
|
"verified": "Verificado",
|
||||||
"pending": "Pendiente",
|
"pending": "Pendiente",
|
||||||
|
"pendingApproval": "Pendientes de aprobación",
|
||||||
"sidebarBilling": "Facturación",
|
"sidebarBilling": "Facturación",
|
||||||
"billing": "Facturación",
|
"billing": "Facturación",
|
||||||
"orgBillingDescription": "Administrar información de facturación y suscripciones",
|
"orgBillingDescription": "Administrar información de facturación y suscripciones",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Llave de seguridad eliminada exitosamente",
|
"securityKeyRemoveSuccess": "Llave de seguridad eliminada exitosamente",
|
||||||
"securityKeyRemoveError": "Error al eliminar la llave de seguridad",
|
"securityKeyRemoveError": "Error al eliminar la llave de seguridad",
|
||||||
"securityKeyLoadError": "Error al cargar las llaves de seguridad",
|
"securityKeyLoadError": "Error al cargar las llaves de seguridad",
|
||||||
"securityKeyLogin": "Continuar con clave de seguridad",
|
"securityKeyLogin": "Usar clave de seguridad",
|
||||||
"securityKeyAuthError": "Error al autenticar con llave de seguridad",
|
"securityKeyAuthError": "Error al autenticar con llave de seguridad",
|
||||||
"securityKeyRecommendation": "Considere registrar otra llave de seguridad en un dispositivo diferente para asegurarse de no quedar bloqueado de su cuenta.",
|
"securityKeyRecommendation": "Considere registrar otra llave de seguridad en un dispositivo diferente para asegurarse de no quedar bloqueado de su cuenta.",
|
||||||
"registering": "Registrando...",
|
"registering": "Registrando...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Intervalo Saludable",
|
"IntervalSeconds": "Intervalo Saludable",
|
||||||
"timeoutSeconds": "Tiempo agotado (seg)",
|
"timeoutSeconds": "Tiempo agotado (seg)",
|
||||||
"timeIsInSeconds": "El tiempo está en segundos",
|
"timeIsInSeconds": "El tiempo está en segundos",
|
||||||
|
"requireDeviceApproval": "Requiere aprobaciones del dispositivo",
|
||||||
|
"requireDeviceApprovalDescription": "Los usuarios con este rol necesitan nuevos dispositivos aprobados por un administrador antes de poder conectarse y acceder a los recursos.",
|
||||||
"retryAttempts": "Intentos de Reintento",
|
"retryAttempts": "Intentos de Reintento",
|
||||||
"expectedResponseCodes": "Códigos de respuesta esperados",
|
"expectedResponseCodes": "Códigos de respuesta esperados",
|
||||||
"expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.",
|
"expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "No se encontraron recursos internos.",
|
"resourcesTableNoInternalResourcesFound": "No se encontraron recursos internos.",
|
||||||
"resourcesTableDestination": "Destino",
|
"resourcesTableDestination": "Destino",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Dirección del alias",
|
||||||
|
"resourcesTableAliasAddressInfo": "Esta dirección es parte de la subred de utilidad de la organización. Se utiliza para resolver registros de alias usando resolución DNS interna.",
|
||||||
"resourcesTableClients": "Clientes",
|
"resourcesTableClients": "Clientes",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "y solo son accesibles internamente cuando se conectan con un cliente.",
|
"resourcesTableAndOnlyAccessibleInternally": "y solo son accesibles internamente cuando se conectan con un cliente.",
|
||||||
"resourcesTableNoTargets": "Sin objetivos",
|
"resourcesTableNoTargets": "Sin objetivos",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "El código debe tener 9 caracteres (por ejemplo, A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "El código debe tener 9 caracteres (por ejemplo, A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Código no válido o caducado",
|
"deviceCodeInvalidOrExpired": "Código no válido o caducado",
|
||||||
"deviceCodeVerifyFailed": "Error al verificar el código del dispositivo",
|
"deviceCodeVerifyFailed": "Error al verificar el código del dispositivo",
|
||||||
|
"deviceCodeValidating": "Validando código de dispositivo...",
|
||||||
|
"deviceCodeVerifying": "Verificando autorización del dispositivo...",
|
||||||
"signedInAs": "Conectado como",
|
"signedInAs": "Conectado como",
|
||||||
"deviceCodeEnterPrompt": "Introduzca el código mostrado en el dispositivo",
|
"deviceCodeEnterPrompt": "Introduzca el código mostrado en el dispositivo",
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Acceso a todas las organizaciones a las que su cuenta tiene acceso",
|
"deviceOrganizationsAccess": "Acceso a todas las organizaciones a las que su cuenta tiene acceso",
|
||||||
"deviceAuthorize": "Autorizar a {applicationName}",
|
"deviceAuthorize": "Autorizar a {applicationName}",
|
||||||
"deviceConnected": "¡Dispositivo conectado!",
|
"deviceConnected": "¡Dispositivo conectado!",
|
||||||
"deviceAuthorizedMessage": "El dispositivo está autorizado para acceder a su cuenta.",
|
"deviceAuthorizedMessage": "El dispositivo está autorizado para acceder a su cuenta. Por favor, vuelva a la aplicación cliente.",
|
||||||
"pangolinCloud": "Nube de Pangolin",
|
"pangolinCloud": "Nube de Pangolin",
|
||||||
"viewDevices": "Ver dispositivos",
|
"viewDevices": "Ver dispositivos",
|
||||||
"viewDevicesDescription": "Administra tus dispositivos conectados",
|
"viewDevicesDescription": "Administra tus dispositivos conectados",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "¿No tú? Utilice una cuenta diferente.",
|
"deviceLoginUseDifferentAccount": "¿No tú? Utilice una cuenta diferente.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo está solicitando acceso a esta cuenta.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo está solicitando acceso a esta cuenta.",
|
||||||
|
"loginSelectAuthenticationMethod": "Seleccione un método de autenticación para continuar.",
|
||||||
"noData": "Sin datos",
|
"noData": "Sin datos",
|
||||||
"machineClients": "Clientes de la máquina",
|
"machineClients": "Clientes de la máquina",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Servicio temporalmente no disponible",
|
"maintenanceScreenTitle": "Servicio temporalmente no disponible",
|
||||||
"maintenanceScreenMessage": "Actualmente estamos experimentando dificultades técnicas. Por favor regrese pronto.",
|
"maintenanceScreenMessage": "Actualmente estamos experimentando dificultades técnicas. Por favor regrese pronto.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Estimado completado:",
|
"maintenanceScreenEstimatedCompletion": "Estimado completado:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Se requiere destino"
|
"createInternalResourceDialogDestinationRequired": "Se requiere destino",
|
||||||
|
"available": "Disponible",
|
||||||
|
"archived": "Archivado",
|
||||||
|
"noArchivedDevices": "No se encontraron dispositivos archivados",
|
||||||
|
"deviceArchived": "Dispositivo archivado",
|
||||||
|
"deviceArchivedDescription": "El dispositivo se ha archivado correctamente.",
|
||||||
|
"errorArchivingDevice": "Error al archivar dispositivo",
|
||||||
|
"failedToArchiveDevice": "Error al archivar el dispositivo",
|
||||||
|
"deviceQuestionArchive": "¿Está seguro que desea archivar este dispositivo?",
|
||||||
|
"deviceMessageArchive": "El dispositivo será archivado y eliminado de su lista de dispositivos activos.",
|
||||||
|
"deviceArchiveConfirm": "Archivar dispositivo",
|
||||||
|
"archiveDevice": "Archivar dispositivo",
|
||||||
|
"archive": "Archivar",
|
||||||
|
"deviceUnarchived": "Dispositivo desarchivado",
|
||||||
|
"deviceUnarchivedDescription": "El dispositivo se ha desarchivado correctamente.",
|
||||||
|
"errorUnarchivingDevice": "Error al desarchivar dispositivo",
|
||||||
|
"failedToUnarchiveDevice": "Error al desarchivar el dispositivo",
|
||||||
|
"unarchive": "Desarchivar",
|
||||||
|
"archiveClient": "Archivar cliente",
|
||||||
|
"archiveClientQuestion": "¿Está seguro que desea archivar este cliente?",
|
||||||
|
"archiveClientMessage": "El cliente será archivado y eliminado de su lista de clientes activos.",
|
||||||
|
"archiveClientConfirm": "Archivar cliente",
|
||||||
|
"blockClient": "Bloquear cliente",
|
||||||
|
"blockClientQuestion": "¿Estás seguro de que quieres bloquear a este cliente?",
|
||||||
|
"blockClientMessage": "El dispositivo será forzado a desconectarse si está conectado actualmente. Puede desbloquear el dispositivo más tarde.",
|
||||||
|
"blockClientConfirm": "Bloquear cliente",
|
||||||
|
"active": "Activo",
|
||||||
|
"usernameOrEmail": "Nombre de usuario o email",
|
||||||
|
"selectYourOrganization": "Seleccione su organización",
|
||||||
|
"signInTo": "Iniciar sesión en",
|
||||||
|
"signInWithPassword": "Continuar con la contraseña",
|
||||||
|
"noAuthMethodsAvailable": "No hay métodos de autenticación disponibles para esta organización.",
|
||||||
|
"enterPassword": "Introduzca su contraseña",
|
||||||
|
"enterMfaCode": "Introduzca el código de su aplicación de autenticación",
|
||||||
|
"securityKeyRequired": "Utilice su clave de seguridad para iniciar sesión.",
|
||||||
|
"needToUseAnotherAccount": "¿Necesitas usar una cuenta diferente?",
|
||||||
|
"loginLegalDisclaimer": "Al hacer clic en los botones de abajo, reconoces que has leído, comprendido, y acepta los <termsOfService>Términos de Servicio</termsOfService> y <privacyPolicy>Política de Privacidad</privacyPolicy>.",
|
||||||
|
"termsOfService": "Términos de Servicio",
|
||||||
|
"privacyPolicy": "Política de privacidad",
|
||||||
|
"userNotFoundWithUsername": "Ningún usuario encontrado con ese nombre de usuario.",
|
||||||
|
"verify": "Verificar",
|
||||||
|
"signIn": "Iniciar sesión",
|
||||||
|
"forgotPassword": "¿Olvidaste la contraseña?",
|
||||||
|
"orgSignInTip": "Si has iniciado sesión antes, puedes introducir tu nombre de usuario o correo electrónico arriba para autenticarte con el proveedor de identidad de tu organización. ¡Es más fácil!",
|
||||||
|
"continueAnyway": "Continuar de todos modos",
|
||||||
|
"dontShowAgain": "No volver a mostrar",
|
||||||
|
"orgSignInNotice": "¿Sabía usted?",
|
||||||
|
"signupOrgNotice": "¿Intentando iniciar sesión?",
|
||||||
|
"signupOrgTip": "¿Estás intentando iniciar sesión a través del proveedor de identidad de tu organización?",
|
||||||
|
"signupOrgLink": "Inicia sesión o regístrate con tu organización",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Usar una cuenta diferente",
|
||||||
|
"logIn": "Iniciar sesión",
|
||||||
|
"deviceInformation": "Información del dispositivo",
|
||||||
|
"deviceInformationDescription": "Información sobre el dispositivo y el agente",
|
||||||
|
"deviceSecurity": "Seguridad del dispositivo",
|
||||||
|
"deviceSecurityDescription": "Información de postura de seguridad del dispositivo",
|
||||||
|
"platform": "Plataforma",
|
||||||
|
"macosVersion": "versión macOS",
|
||||||
|
"windowsVersion": "Versión de Windows",
|
||||||
|
"iosVersion": "Versión de iOS",
|
||||||
|
"androidVersion": "Versión de Android",
|
||||||
|
"osVersion": "Versión del SO",
|
||||||
|
"kernelVersion": "Versión de Kernel",
|
||||||
|
"deviceModel": "Modelo de dispositivo",
|
||||||
|
"serialNumber": "Número Serial",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Primer detectado",
|
||||||
|
"lastSeen": "Último Visto",
|
||||||
|
"biometricsEnabled": "Biometría habilitada",
|
||||||
|
"diskEncrypted": "Disco cifrado",
|
||||||
|
"firewallEnabled": "Cortafuegos activado",
|
||||||
|
"autoUpdatesEnabled": "Actualizaciones automáticas habilitadas",
|
||||||
|
"tpmAvailable": "TPM disponible",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Protección de integridad del sistema (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Modo Sigilo Firewall",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Ver información y ajustes del dispositivo",
|
||||||
|
"devicePendingApprovalDescription": "Este dispositivo está esperando su aprobación",
|
||||||
|
"deviceBlockedDescription": "Este dispositivo está actualmente bloqueado. No podrá conectarse a ningún recurso a menos que sea desbloqueado.",
|
||||||
|
"unblockClient": "Desbloquear cliente",
|
||||||
|
"unblockClientDescription": "El dispositivo ha sido desbloqueado",
|
||||||
|
"unarchiveClient": "Desarchivar cliente",
|
||||||
|
"unarchiveClientDescription": "El dispositivo ha sido desarchivado",
|
||||||
|
"block": "Bloque",
|
||||||
|
"unblock": "Desbloquear",
|
||||||
|
"deviceActions": "Acciones del dispositivo",
|
||||||
|
"deviceActionsDescription": "Administrar estado y acceso al dispositivo",
|
||||||
|
"devicePendingApprovalBannerDescription": "Este dispositivo está pendiente de aprobación. No podrá conectarse a recursos hasta que sea aprobado.",
|
||||||
|
"connected": "Conectado",
|
||||||
|
"disconnected": "Desconectado",
|
||||||
|
"approvalsEmptyStateTitle": "Aprobaciones de dispositivo no habilitadas",
|
||||||
|
"approvalsEmptyStateDescription": "Habilita las aprobaciones de dispositivos para que los roles requieran aprobación del administrador antes de que los usuarios puedan conectar nuevos dispositivos.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Ir a roles",
|
||||||
|
"approvalsEmptyStateStep1Description": "Navega a la configuración de roles de tu organización para configurar las aprobaciones de dispositivos.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Habilitar aprobaciones de dispositivo",
|
||||||
|
"approvalsEmptyStateStep2Description": "Editar un rol y habilitar la opción 'Requerir aprobaciones de dispositivos'. Los usuarios con este rol necesitarán la aprobación del administrador para nuevos dispositivos.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Vista previa: Cuando está habilitado, las solicitudes de dispositivo pendientes aparecerán aquí para su revisión",
|
||||||
|
"approvalsEmptyStateButtonText": "Administrar roles"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Se connecter à n'importe quel réseau",
|
"sitesBannerTitle": "Se connecter à n'importe quel réseau",
|
||||||
"sitesBannerDescription": "Un site est une connexion à un réseau distant qui permet à Pangolin de fournir aux utilisateurs l'accès à des ressources, publiques ou privées, n'importe où. Installez le connecteur de réseau du site (Newt) partout où vous pouvez exécuter un binaire ou un conteneur pour établir la connexion.",
|
"sitesBannerDescription": "Un site est une connexion à un réseau distant qui permet à Pangolin de fournir aux utilisateurs l'accès à des ressources, publiques ou privées, n'importe où. Installez le connecteur de réseau du site (Newt) partout où vous pouvez exécuter un binaire ou un conteneur pour établir la connexion.",
|
||||||
"sitesBannerButtonText": "Installer le site",
|
"sitesBannerButtonText": "Installer le site",
|
||||||
|
"approvalsBannerTitle": "Approuver ou refuser l'accès à l'appareil",
|
||||||
|
"approvalsBannerDescription": "Examinez et approuvez ou refusez les demandes d'accès à l'appareil des utilisateurs. Lorsque les autorisations de l'appareil sont requises, les utilisateurs doivent obtenir l'approbation de l'administrateur avant que leurs appareils puissent se connecter aux ressources de votre organisation.",
|
||||||
|
"approvalsBannerButtonText": "En savoir plus",
|
||||||
"siteCreate": "Créer un nœud",
|
"siteCreate": "Créer un nœud",
|
||||||
"siteCreateDescription2": "Suivez les étapes ci-dessous pour créer et connecter un nouveau nœud",
|
"siteCreateDescription2": "Suivez les étapes ci-dessous pour créer et connecter un nouveau nœud",
|
||||||
"siteCreateDescription": "Créer un nouveau site pour commencer à connecter des ressources",
|
"siteCreateDescription": "Créer un nouveau site pour commencer à connecter des ressources",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Chercher des rôles...",
|
"accessRolesSearch": "Chercher des rôles...",
|
||||||
"accessRolesAdd": "Ajouter un rôle",
|
"accessRolesAdd": "Ajouter un rôle",
|
||||||
"accessRoleDelete": "Supprimer le rôle",
|
"accessRoleDelete": "Supprimer le rôle",
|
||||||
|
"accessApprovalsManage": "Gérer les approbations",
|
||||||
|
"accessApprovalsDescription": "Voir et gérer les approbations en attente pour accéder à cette organisation",
|
||||||
"description": "Libellé",
|
"description": "Libellé",
|
||||||
"inviteTitle": "Invitations actives",
|
"inviteTitle": "Invitations actives",
|
||||||
"inviteDescription": "Gérer les invitations des autres utilisateurs à rejoindre l'organisation",
|
"inviteDescription": "Gérer les invitations des autres utilisateurs à rejoindre l'organisation",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Sélectionner la durée",
|
"selectDuration": "Sélectionner la durée",
|
||||||
"selectResource": "Sélectionner une ressource",
|
"selectResource": "Sélectionner une ressource",
|
||||||
"filterByResource": "Filtrer par ressource",
|
"filterByResource": "Filtrer par ressource",
|
||||||
|
"selectApprovalState": "Sélectionnez l'État d'Approbation",
|
||||||
|
"filterByApprovalState": "Filtrer par État d'Approbation",
|
||||||
|
"approvalListEmpty": "Aucune approbation",
|
||||||
|
"approvalState": "État d'approbation",
|
||||||
|
"approve": "Approuver",
|
||||||
|
"approved": "Approuvé",
|
||||||
|
"denied": "Refusé",
|
||||||
|
"deniedApproval": "Approbation refusée",
|
||||||
|
"all": "Tous",
|
||||||
|
"deny": "Refuser",
|
||||||
|
"viewDetails": "Voir les détails",
|
||||||
|
"requestingNewDeviceApproval": "a demandé un nouvel appareil",
|
||||||
"resetFilters": "Réinitialiser les filtres",
|
"resetFilters": "Réinitialiser les filtres",
|
||||||
"totalBlocked": "Demandes bloquées par le Pangolin",
|
"totalBlocked": "Demandes bloquées par le Pangolin",
|
||||||
"totalRequests": "Total des demandes",
|
"totalRequests": "Total des demandes",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Pays",
|
"countries": "Pays",
|
||||||
"accessRoleCreate": "Créer un rôle",
|
"accessRoleCreate": "Créer un rôle",
|
||||||
"accessRoleCreateDescription": "Créer un nouveau rôle pour regrouper les utilisateurs et gérer leurs permissions.",
|
"accessRoleCreateDescription": "Créer un nouveau rôle pour regrouper les utilisateurs et gérer leurs permissions.",
|
||||||
|
"accessRoleEdit": "Modifier le rôle",
|
||||||
|
"accessRoleEditDescription": "Modifier les informations du rôle.",
|
||||||
"accessRoleCreateSubmit": "Créer un rôle",
|
"accessRoleCreateSubmit": "Créer un rôle",
|
||||||
"accessRoleCreated": "Rôle créé",
|
"accessRoleCreated": "Rôle créé",
|
||||||
"accessRoleCreatedDescription": "Le rôle a été créé avec succès.",
|
"accessRoleCreatedDescription": "Le rôle a été créé avec succès.",
|
||||||
"accessRoleErrorCreate": "Échec de la création du rôle",
|
"accessRoleErrorCreate": "Échec de la création du rôle",
|
||||||
"accessRoleErrorCreateDescription": "Une erreur s'est produite lors de la création du rôle.",
|
"accessRoleErrorCreateDescription": "Une erreur s'est produite lors de la création du rôle.",
|
||||||
|
"accessRoleUpdateSubmit": "Mettre à jour un rôle",
|
||||||
|
"accessRoleUpdated": "Rôle mis à jour",
|
||||||
|
"accessRoleUpdatedDescription": "Le rôle a été mis à jour avec succès.",
|
||||||
|
"accessApprovalUpdated": "Approbation traitée",
|
||||||
|
"accessApprovalApprovedDescription": "Définir la décision de la demande d'approbation à approuver.",
|
||||||
|
"accessApprovalDeniedDescription": "Définir la décision de la demande d'approbation comme refusée.",
|
||||||
|
"accessRoleErrorUpdate": "Impossible de mettre à jour le rôle",
|
||||||
|
"accessRoleErrorUpdateDescription": "Une erreur s'est produite lors de la mise à jour du rôle.",
|
||||||
|
"accessApprovalErrorUpdate": "Impossible de traiter l'approbation",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Une erreur s'est produite lors du traitement de l'approbation.",
|
||||||
"accessRoleErrorNewRequired": "Un nouveau rôle est requis",
|
"accessRoleErrorNewRequired": "Un nouveau rôle est requis",
|
||||||
"accessRoleErrorRemove": "Échec de la suppression du rôle",
|
"accessRoleErrorRemove": "Échec de la suppression du rôle",
|
||||||
"accessRoleErrorRemoveDescription": "Une erreur s'est produite lors de la suppression du rôle.",
|
"accessRoleErrorRemoveDescription": "Une erreur s'est produite lors de la suppression du rôle.",
|
||||||
"accessRoleName": "Nom du rôle",
|
"accessRoleName": "Nom du rôle",
|
||||||
"accessRoleQuestionRemove": "Vous êtes sur le point de supprimer le rôle {name}. Cette action est irréversible.",
|
"accessRoleQuestionRemove": "Vous êtes sur le point de supprimer le rôle `{name}`. Vous ne pouvez pas annuler cette action.",
|
||||||
"accessRoleRemove": "Supprimer le rôle",
|
"accessRoleRemove": "Supprimer le rôle",
|
||||||
"accessRoleRemoveDescription": "Retirer un rôle de l'organisation",
|
"accessRoleRemoveDescription": "Retirer un rôle de l'organisation",
|
||||||
"accessRoleRemoveSubmit": "Supprimer le rôle",
|
"accessRoleRemoveSubmit": "Supprimer le rôle",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "Veuillez contacter votre administrateur",
|
"passwordResetSmtpRequired": "Veuillez contacter votre administrateur",
|
||||||
"passwordResetSmtpRequiredDescription": "Un code de réinitialisation du mot de passe est requis pour réinitialiser votre mot de passe. Veuillez contacter votre administrateur pour obtenir de l'aide.",
|
"passwordResetSmtpRequiredDescription": "Un code de réinitialisation du mot de passe est requis pour réinitialiser votre mot de passe. Veuillez contacter votre administrateur pour obtenir de l'aide.",
|
||||||
"passwordBack": "Retour au mot de passe",
|
"passwordBack": "Retour au mot de passe",
|
||||||
"loginBack": "Retour à la connexion",
|
"loginBack": "Revenir à la page de connexion principale",
|
||||||
"signup": "S'inscrire",
|
"signup": "S'inscrire",
|
||||||
"loginStart": "Connectez-vous pour commencer",
|
"loginStart": "Connectez-vous pour commencer",
|
||||||
"idpOidcTokenValidating": "Validation du jeton OIDC",
|
"idpOidcTokenValidating": "Validation du jeton OIDC",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Mettre à jour une organisation IDP",
|
"actionUpdateIdpOrg": "Mettre à jour une organisation IDP",
|
||||||
"actionCreateClient": "Créer un client",
|
"actionCreateClient": "Créer un client",
|
||||||
"actionDeleteClient": "Supprimer le client",
|
"actionDeleteClient": "Supprimer le client",
|
||||||
|
"actionArchiveClient": "Archiver le client",
|
||||||
|
"actionUnarchiveClient": "Désarchiver le client",
|
||||||
|
"actionBlockClient": "Bloquer le client",
|
||||||
|
"actionUnblockClient": "Débloquer le client",
|
||||||
"actionUpdateClient": "Mettre à jour le client",
|
"actionUpdateClient": "Mettre à jour le client",
|
||||||
"actionListClients": "Liste des clients",
|
"actionListClients": "Liste des clients",
|
||||||
"actionGetClient": "Obtenir le client",
|
"actionGetClient": "Obtenir le client",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Rechercher...",
|
"searchProgress": "Rechercher...",
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
"orgs": "Organisations",
|
"orgs": "Organisations",
|
||||||
"loginError": "Une erreur s'est produite lors de la connexion",
|
"loginError": "Une erreur inattendue s'est produite. Veuillez réessayer.",
|
||||||
"loginRequiredForDevice": "La connexion est requise pour authentifier votre appareil.",
|
"loginRequiredForDevice": "La connexion est requise pour votre appareil.",
|
||||||
"passwordForgot": "Mot de passe oublié ?",
|
"passwordForgot": "Mot de passe oublié ?",
|
||||||
"otpAuth": "Authentification à deux facteurs",
|
"otpAuth": "Authentification à deux facteurs",
|
||||||
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
|
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
|
||||||
"otpAuthSubmit": "Soumettre le code",
|
"otpAuthSubmit": "Soumettre le code",
|
||||||
"idpContinue": "Ou continuer avec",
|
"idpContinue": "Ou continuer avec",
|
||||||
"otpAuthBack": "Retour à la connexion",
|
"otpAuthBack": "Retour au mot de passe",
|
||||||
"navbar": "Menu de navigation",
|
"navbar": "Menu de navigation",
|
||||||
"navbarDescription": "Menu de navigation principal de l'application",
|
"navbarDescription": "Menu de navigation principal de l'application",
|
||||||
"navbarDocsLink": "Documentation",
|
"navbarDocsLink": "Documentation",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Aperçu",
|
"sidebarOverview": "Aperçu",
|
||||||
"sidebarHome": "Domicile",
|
"sidebarHome": "Domicile",
|
||||||
"sidebarSites": "Nœuds",
|
"sidebarSites": "Nœuds",
|
||||||
|
"sidebarApprovals": "Demandes d'approbation",
|
||||||
"sidebarResources": "Ressource",
|
"sidebarResources": "Ressource",
|
||||||
"sidebarProxyResources": "Publique",
|
"sidebarProxyResources": "Publique",
|
||||||
"sidebarClientResources": "Privé",
|
"sidebarClientResources": "Privé",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Fournisseurs d'identité",
|
"sidebarIdentityProviders": "Fournisseurs d'identité",
|
||||||
"sidebarLicense": "Licence",
|
"sidebarLicense": "Licence",
|
||||||
"sidebarClients": "Clients",
|
"sidebarClients": "Clients",
|
||||||
"sidebarUserDevices": "Utilisateurs",
|
"sidebarUserDevices": "Périphériques utilisateur",
|
||||||
"sidebarMachineClients": "Machines",
|
"sidebarMachineClients": "Machines",
|
||||||
"sidebarDomains": "Domaines",
|
"sidebarDomains": "Domaines",
|
||||||
"sidebarGeneral": "Gérer",
|
"sidebarGeneral": "Gérer",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Une erreur s'est produite lors de la création du compte administrateur du serveur.",
|
"setupErrorCreateAdmin": "Une erreur s'est produite lors de la création du compte administrateur du serveur.",
|
||||||
"certificateStatus": "Statut du certificat",
|
"certificateStatus": "Statut du certificat",
|
||||||
"loading": "Chargement",
|
"loading": "Chargement",
|
||||||
|
"loadingAnalytics": "Chargement de l'analyse",
|
||||||
"restart": "Redémarrer",
|
"restart": "Redémarrer",
|
||||||
"domains": "Domaines",
|
"domains": "Domaines",
|
||||||
"domainsDescription": "Créer et gérer les domaines disponibles dans l'organisation",
|
"domainsDescription": "Créer et gérer les domaines disponibles dans l'organisation",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Échec de l'actualisation des données",
|
"refreshError": "Échec de l'actualisation des données",
|
||||||
"verified": "Vérifié",
|
"verified": "Vérifié",
|
||||||
"pending": "En attente",
|
"pending": "En attente",
|
||||||
|
"pendingApproval": "En attente d'approbation",
|
||||||
"sidebarBilling": "Facturation",
|
"sidebarBilling": "Facturation",
|
||||||
"billing": "Facturation",
|
"billing": "Facturation",
|
||||||
"orgBillingDescription": "Gérer les informations de facturation et les abonnements",
|
"orgBillingDescription": "Gérer les informations de facturation et les abonnements",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Clé de sécurité supprimée avec succès",
|
"securityKeyRemoveSuccess": "Clé de sécurité supprimée avec succès",
|
||||||
"securityKeyRemoveError": "Échec de la suppression de la clé de sécurité",
|
"securityKeyRemoveError": "Échec de la suppression de la clé de sécurité",
|
||||||
"securityKeyLoadError": "Échec du chargement des clés de sécurité",
|
"securityKeyLoadError": "Échec du chargement des clés de sécurité",
|
||||||
"securityKeyLogin": "Continuer avec une clé de sécurité",
|
"securityKeyLogin": "Utiliser la clé de sécurité",
|
||||||
"securityKeyAuthError": "Échec de l'authentification avec la clé de sécurité",
|
"securityKeyAuthError": "Échec de l'authentification avec la clé de sécurité",
|
||||||
"securityKeyRecommendation": "Envisagez d'enregistrer une autre clé de sécurité sur un appareil différent pour vous assurer de ne pas être bloqué de votre compte.",
|
"securityKeyRecommendation": "Envisagez d'enregistrer une autre clé de sécurité sur un appareil différent pour vous assurer de ne pas être bloqué de votre compte.",
|
||||||
"registering": "Enregistrement...",
|
"registering": "Enregistrement...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Intervalle sain",
|
"IntervalSeconds": "Intervalle sain",
|
||||||
"timeoutSeconds": "Délai d'attente (sec)",
|
"timeoutSeconds": "Délai d'attente (sec)",
|
||||||
"timeIsInSeconds": "Le temps est exprimé en secondes",
|
"timeIsInSeconds": "Le temps est exprimé en secondes",
|
||||||
|
"requireDeviceApproval": "Exiger les autorisations de l'appareil",
|
||||||
|
"requireDeviceApprovalDescription": "Les utilisateurs ayant ce rôle ont besoin de nouveaux périphériques approuvés par un administrateur avant de pouvoir se connecter et accéder aux ressources.",
|
||||||
"retryAttempts": "Tentatives de réessai",
|
"retryAttempts": "Tentatives de réessai",
|
||||||
"expectedResponseCodes": "Codes de réponse attendus",
|
"expectedResponseCodes": "Codes de réponse attendus",
|
||||||
"expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.",
|
"expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Aucune ressource interne trouvée.",
|
"resourcesTableNoInternalResourcesFound": "Aucune ressource interne trouvée.",
|
||||||
"resourcesTableDestination": "Destination",
|
"resourcesTableDestination": "Destination",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Adresse de l'alias",
|
||||||
|
"resourcesTableAliasAddressInfo": "Cette adresse fait partie du sous-réseau utilitaire de l'organisation. Elle est utilisée pour résoudre les enregistrements d'alias en utilisant une résolution DNS interne.",
|
||||||
"resourcesTableClients": "Clients",
|
"resourcesTableClients": "Clients",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "et sont uniquement accessibles en interne lorsqu'elles sont connectées avec un client.",
|
"resourcesTableAndOnlyAccessibleInternally": "et sont uniquement accessibles en interne lorsqu'elles sont connectées avec un client.",
|
||||||
"resourcesTableNoTargets": "Aucune cible",
|
"resourcesTableNoTargets": "Aucune cible",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Choisissez votre fournisseur d'identité pour continuer",
|
"orgAuthChooseIdpDescription": "Choisissez votre fournisseur d'identité pour continuer",
|
||||||
"orgAuthNoIdpConfigured": "Cette organisation n'a aucun fournisseur d'identité configuré. Vous pouvez vous connecter avec votre identité Pangolin à la place.",
|
"orgAuthNoIdpConfigured": "Cette organisation n'a aucun fournisseur d'identité configuré. Vous pouvez vous connecter avec votre identité Pangolin à la place.",
|
||||||
"orgAuthSignInWithPangolin": "Se connecter avec Pangolin",
|
"orgAuthSignInWithPangolin": "Se connecter avec Pangolin",
|
||||||
"orgAuthSignInToOrg": "Connectez-vous à une organisation",
|
"orgAuthSignInToOrg": "Se connecter à une organisation",
|
||||||
"orgAuthSelectOrgTitle": "Connexion à l'organisation",
|
"orgAuthSelectOrgTitle": "Connexion à l'organisation",
|
||||||
"orgAuthSelectOrgDescription": "Entrez votre identifiant d'organisation pour continuer",
|
"orgAuthSelectOrgDescription": "Entrez votre identifiant d'organisation pour continuer",
|
||||||
"orgAuthOrgIdPlaceholder": "votre-organisation",
|
"orgAuthOrgIdPlaceholder": "votre-organisation",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Le code doit contenir 9 caractères (par exemple, A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Le code doit contenir 9 caractères (par exemple, A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Code invalide ou expiré",
|
"deviceCodeInvalidOrExpired": "Code invalide ou expiré",
|
||||||
"deviceCodeVerifyFailed": "Impossible de vérifier le code de l'appareil",
|
"deviceCodeVerifyFailed": "Impossible de vérifier le code de l'appareil",
|
||||||
|
"deviceCodeValidating": "Validation du code de l'appareil...",
|
||||||
|
"deviceCodeVerifying": "Vérification de l'autorisation de l'appareil...",
|
||||||
"signedInAs": "Connecté en tant que",
|
"signedInAs": "Connecté en tant que",
|
||||||
"deviceCodeEnterPrompt": "Entrez le code affiché sur l'appareil",
|
"deviceCodeEnterPrompt": "Entrez le code affiché sur l'appareil",
|
||||||
"continue": "Continuer",
|
"continue": "Continuer",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Accès à toutes les organisations auxquelles votre compte a accès",
|
"deviceOrganizationsAccess": "Accès à toutes les organisations auxquelles votre compte a accès",
|
||||||
"deviceAuthorize": "Autoriser {applicationName}",
|
"deviceAuthorize": "Autoriser {applicationName}",
|
||||||
"deviceConnected": "Appareil connecté !",
|
"deviceConnected": "Appareil connecté !",
|
||||||
"deviceAuthorizedMessage": "L'appareil est autorisé à accéder à votre compte.",
|
"deviceAuthorizedMessage": "L'appareil est autorisé à accéder à votre compte. Veuillez retourner à l'application client.",
|
||||||
"pangolinCloud": "Nuage de Pangolin",
|
"pangolinCloud": "Nuage de Pangolin",
|
||||||
"viewDevices": "Voir les appareils",
|
"viewDevices": "Voir les appareils",
|
||||||
"viewDevicesDescription": "Gérer vos appareils connectés",
|
"viewDevicesDescription": "Gérer vos appareils connectés",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifiant",
|
"identifier": "Identifiant",
|
||||||
"deviceLoginUseDifferentAccount": "Pas vous ? Utilisez un autre compte.",
|
"deviceLoginUseDifferentAccount": "Pas vous ? Utilisez un autre compte.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Un appareil demande l'accès à ce compte.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Un appareil demande l'accès à ce compte.",
|
||||||
|
"loginSelectAuthenticationMethod": "Sélectionnez une méthode d'authentification pour continuer.",
|
||||||
"noData": "Aucune donnée",
|
"noData": "Aucune donnée",
|
||||||
"machineClients": "Clients Machines",
|
"machineClients": "Clients Machines",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Service temporairement indisponible",
|
"maintenanceScreenTitle": "Service temporairement indisponible",
|
||||||
"maintenanceScreenMessage": "Nous rencontrons actuellement des difficultés techniques. Veuillez vérifier ultérieurement.",
|
"maintenanceScreenMessage": "Nous rencontrons actuellement des difficultés techniques. Veuillez vérifier ultérieurement.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Achèvement estimé :",
|
"maintenanceScreenEstimatedCompletion": "Achèvement estimé :",
|
||||||
"createInternalResourceDialogDestinationRequired": "La destination est requise"
|
"createInternalResourceDialogDestinationRequired": "La destination est requise",
|
||||||
|
"available": "Disponible",
|
||||||
|
"archived": "Archivé",
|
||||||
|
"noArchivedDevices": "Aucun périphérique archivé trouvé",
|
||||||
|
"deviceArchived": "Appareil archivé",
|
||||||
|
"deviceArchivedDescription": "L'appareil a été archivé avec succès.",
|
||||||
|
"errorArchivingDevice": "Erreur lors de l'archivage du périphérique",
|
||||||
|
"failedToArchiveDevice": "Impossible d'archiver l'appareil",
|
||||||
|
"deviceQuestionArchive": "Êtes-vous sûr de vouloir archiver cet appareil ?",
|
||||||
|
"deviceMessageArchive": "Le périphérique sera archivé et retiré de la liste des périphériques actifs.",
|
||||||
|
"deviceArchiveConfirm": "Dispositif d'archivage",
|
||||||
|
"archiveDevice": "Dispositif d'archivage",
|
||||||
|
"archive": "Archive",
|
||||||
|
"deviceUnarchived": "Appareil désarchivé",
|
||||||
|
"deviceUnarchivedDescription": "L'appareil a été désarchivé avec succès.",
|
||||||
|
"errorUnarchivingDevice": "Erreur lors de la désarchivage du périphérique",
|
||||||
|
"failedToUnarchiveDevice": "Échec de la désarchivage de l'appareil",
|
||||||
|
"unarchive": "Désarchiver",
|
||||||
|
"archiveClient": "Archiver le client",
|
||||||
|
"archiveClientQuestion": "Êtes-vous sûr de vouloir archiver ce client?",
|
||||||
|
"archiveClientMessage": "Le client sera archivé et retiré de votre liste de clients actifs.",
|
||||||
|
"archiveClientConfirm": "Archiver le client",
|
||||||
|
"blockClient": "Bloquer le client",
|
||||||
|
"blockClientQuestion": "Êtes-vous sûr de vouloir bloquer ce client?",
|
||||||
|
"blockClientMessage": "L'appareil sera forcé de se déconnecter si vous êtes actuellement connecté. Vous pourrez débloquer l'appareil plus tard.",
|
||||||
|
"blockClientConfirm": "Bloquer le client",
|
||||||
|
"active": "Actif",
|
||||||
|
"usernameOrEmail": "Nom d'utilisateur ou email",
|
||||||
|
"selectYourOrganization": "Sélectionnez votre organisation",
|
||||||
|
"signInTo": "Se connecter à",
|
||||||
|
"signInWithPassword": "Continuer avec le mot de passe",
|
||||||
|
"noAuthMethodsAvailable": "Aucune méthode d'authentification disponible pour cette organisation.",
|
||||||
|
"enterPassword": "Entrez votre mot de passe",
|
||||||
|
"enterMfaCode": "Entrez le code de votre application d'authentification",
|
||||||
|
"securityKeyRequired": "Veuillez utiliser votre clé de sécurité pour vous connecter.",
|
||||||
|
"needToUseAnotherAccount": "Besoin d'un autre compte ?",
|
||||||
|
"loginLegalDisclaimer": "En cliquant sur les boutons ci-dessous, vous reconnaissez avoir lu, compris et accepté les <termsOfService>Conditions d'utilisation</termsOfService> et la <privacyPolicy>Politique de confidentialité</privacyPolicy>.",
|
||||||
|
"termsOfService": "Conditions d'utilisation",
|
||||||
|
"privacyPolicy": "Politique de confidentialité",
|
||||||
|
"userNotFoundWithUsername": "Aucun utilisateur trouvé avec ce nom d'utilisateur.",
|
||||||
|
"verify": "Vérifier",
|
||||||
|
"signIn": "Se connecter",
|
||||||
|
"forgotPassword": "Mot de passe oublié ?",
|
||||||
|
"orgSignInTip": "Si vous vous êtes déjà connecté, vous pouvez entrer votre nom d'utilisateur ou votre e-mail ci-dessus pour vous authentifier auprès du fournisseur d'identité de votre organisation. C'est plus facile !",
|
||||||
|
"continueAnyway": "Continuer quand même",
|
||||||
|
"dontShowAgain": "Ne plus afficher",
|
||||||
|
"orgSignInNotice": "Le saviez-vous ?",
|
||||||
|
"signupOrgNotice": "Vous essayez de vous connecter ?",
|
||||||
|
"signupOrgTip": "Essayez-vous de vous connecter par l'intermédiaire du fournisseur d'identité de votre organisme?",
|
||||||
|
"signupOrgLink": "Connectez-vous ou inscrivez-vous avec votre organisation à la place",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Utiliser un compte différent",
|
||||||
|
"logIn": "Se connecter",
|
||||||
|
"deviceInformation": "Informations sur l'appareil",
|
||||||
|
"deviceInformationDescription": "Informations sur l'appareil et l'agent",
|
||||||
|
"deviceSecurity": "Sécurité de l'appareil",
|
||||||
|
"deviceSecurityDescription": "Informations sur la posture de sécurité de l'appareil",
|
||||||
|
"platform": "Plateforme",
|
||||||
|
"macosVersion": "Version macOS",
|
||||||
|
"windowsVersion": "Version de Windows",
|
||||||
|
"iosVersion": "Version iOS",
|
||||||
|
"androidVersion": "Version d'Android",
|
||||||
|
"osVersion": "Version du système d'exploitation",
|
||||||
|
"kernelVersion": "Version du noyau",
|
||||||
|
"deviceModel": "Modèle de l'appareil",
|
||||||
|
"serialNumber": "Numéro de série",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Première vue",
|
||||||
|
"lastSeen": "Dernière vue",
|
||||||
|
"biometricsEnabled": "biométrique activée",
|
||||||
|
"diskEncrypted": "Disque chiffré",
|
||||||
|
"firewallEnabled": "Pare-feu activé",
|
||||||
|
"autoUpdatesEnabled": "Mises à jour automatiques activées",
|
||||||
|
"tpmAvailable": "TPM disponible",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Protection contre l'intégrité du système (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Mode furtif du pare-feu",
|
||||||
|
"linuxAppArmorEnabled": "Armure d'application",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Afficher les informations et les paramètres de l'appareil",
|
||||||
|
"devicePendingApprovalDescription": "Cet appareil est en attente d'approbation",
|
||||||
|
"deviceBlockedDescription": "Cet appareil est actuellement bloqué. Il ne pourra se connecter à aucune ressource à moins d'être débloqué.",
|
||||||
|
"unblockClient": "Débloquer le client",
|
||||||
|
"unblockClientDescription": "L'appareil a été débloqué",
|
||||||
|
"unarchiveClient": "Désarchiver le client",
|
||||||
|
"unarchiveClientDescription": "L'appareil a été désarchivé",
|
||||||
|
"block": "Bloquer",
|
||||||
|
"unblock": "Débloquer",
|
||||||
|
"deviceActions": "Actions de l'appareil",
|
||||||
|
"deviceActionsDescription": "Gérer le statut et l'accès de l'appareil",
|
||||||
|
"devicePendingApprovalBannerDescription": "Cet appareil est en attente d'approbation. Il ne sera pas en mesure de se connecter aux ressources jusqu'à ce qu'il soit approuvé.",
|
||||||
|
"connected": "Connecté",
|
||||||
|
"disconnected": "Déconnecté",
|
||||||
|
"approvalsEmptyStateTitle": "Approbations de l'appareil non activées",
|
||||||
|
"approvalsEmptyStateDescription": "Activer les autorisations de l'appareil pour les rôles qui nécessitent l'approbation de l'administrateur avant que les utilisateurs puissent connecter de nouveaux appareils.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Aller aux Rôles",
|
||||||
|
"approvalsEmptyStateStep1Description": "Accédez aux paramètres de rôles de votre organisation pour configurer les autorisations de l'appareil.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Activer les autorisations de l'appareil",
|
||||||
|
"approvalsEmptyStateStep2Description": "Modifier un rôle et activer l'option 'Exiger les autorisations de l'appareil'. Les utilisateurs avec ce rôle auront besoin de l'approbation de l'administrateur pour les nouveaux appareils.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Aperçu: Lorsque cette option est activée, les demandes de périphérique en attente apparaîtront ici pour vérification",
|
||||||
|
"approvalsEmptyStateButtonText": "Gérer les rôles"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Connetti Qualsiasi Rete",
|
"sitesBannerTitle": "Connetti Qualsiasi Rete",
|
||||||
"sitesBannerDescription": "Un sito è una connessione a una rete remota che consente a Pangolin di fornire accesso alle risorse, pubbliche o private, agli utenti ovunque. Installa il connettore di rete del sito (Newt) ovunque tu possa eseguire un binario o un container per stabilire la connessione.",
|
"sitesBannerDescription": "Un sito è una connessione a una rete remota che consente a Pangolin di fornire accesso alle risorse, pubbliche o private, agli utenti ovunque. Installa il connettore di rete del sito (Newt) ovunque tu possa eseguire un binario o un container per stabilire la connessione.",
|
||||||
"sitesBannerButtonText": "Installa Sito",
|
"sitesBannerButtonText": "Installa Sito",
|
||||||
|
"approvalsBannerTitle": "Approva o nega l'accesso al dispositivo",
|
||||||
|
"approvalsBannerDescription": "Controlla e approva o nega le richieste di accesso al dispositivo da parte degli utenti. Quando le approvazioni del dispositivo sono richieste, gli utenti devono ottenere l'approvazione dell'amministratore prima che i loro dispositivi possano connettersi alle risorse della vostra organizzazione.",
|
||||||
|
"approvalsBannerButtonText": "Scopri di più",
|
||||||
"siteCreate": "Crea Sito",
|
"siteCreate": "Crea Sito",
|
||||||
"siteCreateDescription2": "Segui i passaggi qui sotto per creare e collegare un nuovo sito",
|
"siteCreateDescription2": "Segui i passaggi qui sotto per creare e collegare un nuovo sito",
|
||||||
"siteCreateDescription": "Crea un nuovo sito per iniziare a connettere le risorse",
|
"siteCreateDescription": "Crea un nuovo sito per iniziare a connettere le risorse",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Ricerca ruoli...",
|
"accessRolesSearch": "Ricerca ruoli...",
|
||||||
"accessRolesAdd": "Aggiungi Ruolo",
|
"accessRolesAdd": "Aggiungi Ruolo",
|
||||||
"accessRoleDelete": "Elimina Ruolo",
|
"accessRoleDelete": "Elimina Ruolo",
|
||||||
|
"accessApprovalsManage": "Gestisci Approvazioni",
|
||||||
|
"accessApprovalsDescription": "Visualizza e gestisci le approvazioni in attesa per accedere a questa organizzazione",
|
||||||
"description": "Descrizione",
|
"description": "Descrizione",
|
||||||
"inviteTitle": "Inviti Aperti",
|
"inviteTitle": "Inviti Aperti",
|
||||||
"inviteDescription": "Gestisci gli inviti per gli altri utenti a unirsi all'organizzazione",
|
"inviteDescription": "Gestisci gli inviti per gli altri utenti a unirsi all'organizzazione",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Seleziona durata",
|
"selectDuration": "Seleziona durata",
|
||||||
"selectResource": "Seleziona Risorsa",
|
"selectResource": "Seleziona Risorsa",
|
||||||
"filterByResource": "Filtra Per Risorsa",
|
"filterByResource": "Filtra Per Risorsa",
|
||||||
|
"selectApprovalState": "Seleziona Stato Di Approvazione",
|
||||||
|
"filterByApprovalState": "Filtra Per Stato Di Approvazione",
|
||||||
|
"approvalListEmpty": "Nessuna approvazione",
|
||||||
|
"approvalState": "Stato Di Approvazione",
|
||||||
|
"approve": "Approva",
|
||||||
|
"approved": "Approvato",
|
||||||
|
"denied": "Negato",
|
||||||
|
"deniedApproval": "Omologazione Negata",
|
||||||
|
"all": "Tutti",
|
||||||
|
"deny": "Nega",
|
||||||
|
"viewDetails": "Visualizza Dettagli",
|
||||||
|
"requestingNewDeviceApproval": "ha richiesto un nuovo dispositivo",
|
||||||
"resetFilters": "Ripristina Filtri",
|
"resetFilters": "Ripristina Filtri",
|
||||||
"totalBlocked": "Richieste Bloccate Da Pangolino",
|
"totalBlocked": "Richieste Bloccate Da Pangolino",
|
||||||
"totalRequests": "Totale Richieste",
|
"totalRequests": "Totale Richieste",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Paesi",
|
"countries": "Paesi",
|
||||||
"accessRoleCreate": "Crea Ruolo",
|
"accessRoleCreate": "Crea Ruolo",
|
||||||
"accessRoleCreateDescription": "Crea un nuovo ruolo per raggruppare gli utenti e gestire i loro permessi.",
|
"accessRoleCreateDescription": "Crea un nuovo ruolo per raggruppare gli utenti e gestire i loro permessi.",
|
||||||
|
"accessRoleEdit": "Modifica Ruolo",
|
||||||
|
"accessRoleEditDescription": "Modifica informazioni sul ruolo.",
|
||||||
"accessRoleCreateSubmit": "Crea Ruolo",
|
"accessRoleCreateSubmit": "Crea Ruolo",
|
||||||
"accessRoleCreated": "Ruolo creato",
|
"accessRoleCreated": "Ruolo creato",
|
||||||
"accessRoleCreatedDescription": "Il ruolo è stato creato con successo.",
|
"accessRoleCreatedDescription": "Il ruolo è stato creato con successo.",
|
||||||
"accessRoleErrorCreate": "Impossibile creare il ruolo",
|
"accessRoleErrorCreate": "Impossibile creare il ruolo",
|
||||||
"accessRoleErrorCreateDescription": "Si è verificato un errore durante la creazione del ruolo.",
|
"accessRoleErrorCreateDescription": "Si è verificato un errore durante la creazione del ruolo.",
|
||||||
|
"accessRoleUpdateSubmit": "Aggiorna Ruolo",
|
||||||
|
"accessRoleUpdated": "Ruolo aggiornato",
|
||||||
|
"accessRoleUpdatedDescription": "Il ruolo è stato aggiornato con successo.",
|
||||||
|
"accessApprovalUpdated": "Approvazione trattata",
|
||||||
|
"accessApprovalApprovedDescription": "Impostare la decisione di richiesta di approvazione da approvare.",
|
||||||
|
"accessApprovalDeniedDescription": "Imposta la decisione di richiesta di approvazione negata.",
|
||||||
|
"accessRoleErrorUpdate": "Impossibile aggiornare il ruolo",
|
||||||
|
"accessRoleErrorUpdateDescription": "Si è verificato un errore nell'aggiornamento del ruolo.",
|
||||||
|
"accessApprovalErrorUpdate": "Impossibile elaborare l'approvazione",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Si è verificato un errore durante l'elaborazione dell'approvazione.",
|
||||||
"accessRoleErrorNewRequired": "Nuovo ruolo richiesto",
|
"accessRoleErrorNewRequired": "Nuovo ruolo richiesto",
|
||||||
"accessRoleErrorRemove": "Impossibile rimuovere il ruolo",
|
"accessRoleErrorRemove": "Impossibile rimuovere il ruolo",
|
||||||
"accessRoleErrorRemoveDescription": "Si è verificato un errore durante la rimozione del ruolo.",
|
"accessRoleErrorRemoveDescription": "Si è verificato un errore durante la rimozione del ruolo.",
|
||||||
"accessRoleName": "Nome Del Ruolo",
|
"accessRoleName": "Nome Del Ruolo",
|
||||||
"accessRoleQuestionRemove": "Stai per eliminare il ruolo {name}. Non puoi annullare questa azione.",
|
"accessRoleQuestionRemove": "Stai per eliminare il ruolo `{name}`. Non puoi annullare questa azione.",
|
||||||
"accessRoleRemove": "Rimuovi Ruolo",
|
"accessRoleRemove": "Rimuovi Ruolo",
|
||||||
"accessRoleRemoveDescription": "Rimuovi un ruolo dall'organizzazione",
|
"accessRoleRemoveDescription": "Rimuovi un ruolo dall'organizzazione",
|
||||||
"accessRoleRemoveSubmit": "Rimuovi Ruolo",
|
"accessRoleRemoveSubmit": "Rimuovi Ruolo",
|
||||||
@@ -874,7 +903,7 @@
|
|||||||
"inviteAlready": "Sembra che sei stato invitato!",
|
"inviteAlready": "Sembra che sei stato invitato!",
|
||||||
"inviteAlreadyDescription": "Per accettare l'invito, devi accedere o creare un account.",
|
"inviteAlreadyDescription": "Per accettare l'invito, devi accedere o creare un account.",
|
||||||
"signupQuestion": "Hai già un account?",
|
"signupQuestion": "Hai già un account?",
|
||||||
"login": "Accedi",
|
"login": "Log In",
|
||||||
"resourceNotFound": "Risorsa Non Trovata",
|
"resourceNotFound": "Risorsa Non Trovata",
|
||||||
"resourceNotFoundDescription": "La risorsa che stai cercando di accedere non esiste.",
|
"resourceNotFoundDescription": "La risorsa che stai cercando di accedere non esiste.",
|
||||||
"pincodeRequirementsLength": "Il PIN deve essere esattamente di 6 cifre",
|
"pincodeRequirementsLength": "Il PIN deve essere esattamente di 6 cifre",
|
||||||
@@ -954,13 +983,13 @@
|
|||||||
"passwordExpiryDescription": "Questa organizzazione richiede di cambiare la password ogni {maxDays} giorni.",
|
"passwordExpiryDescription": "Questa organizzazione richiede di cambiare la password ogni {maxDays} giorni.",
|
||||||
"changePasswordNow": "Cambia Password Ora",
|
"changePasswordNow": "Cambia Password Ora",
|
||||||
"pincodeAuth": "Codice Autenticatore",
|
"pincodeAuth": "Codice Autenticatore",
|
||||||
"pincodeSubmit2": "Invia Codice",
|
"pincodeSubmit2": "Invia codice",
|
||||||
"passwordResetSubmit": "Richiedi Reset",
|
"passwordResetSubmit": "Richiedi Reset",
|
||||||
"passwordResetAlreadyHaveCode": "Inserisci Codice",
|
"passwordResetAlreadyHaveCode": "Inserisci Codice",
|
||||||
"passwordResetSmtpRequired": "Si prega di contattare l'amministratore",
|
"passwordResetSmtpRequired": "Si prega di contattare l'amministratore",
|
||||||
"passwordResetSmtpRequiredDescription": "Per reimpostare la password è necessario un codice di reimpostazione della password. Si prega di contattare l'amministratore per assistenza.",
|
"passwordResetSmtpRequiredDescription": "Per reimpostare la password è necessario un codice di reimpostazione della password. Si prega di contattare l'amministratore per assistenza.",
|
||||||
"passwordBack": "Torna alla Password",
|
"passwordBack": "Torna alla Password",
|
||||||
"loginBack": "Torna al login",
|
"loginBack": "Torna alla pagina di accesso principale",
|
||||||
"signup": "Registrati",
|
"signup": "Registrati",
|
||||||
"loginStart": "Accedi per iniziare",
|
"loginStart": "Accedi per iniziare",
|
||||||
"idpOidcTokenValidating": "Convalida token OIDC",
|
"idpOidcTokenValidating": "Convalida token OIDC",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Aggiorna Org IDP",
|
"actionUpdateIdpOrg": "Aggiorna Org IDP",
|
||||||
"actionCreateClient": "Crea Client",
|
"actionCreateClient": "Crea Client",
|
||||||
"actionDeleteClient": "Elimina Client",
|
"actionDeleteClient": "Elimina Client",
|
||||||
|
"actionArchiveClient": "Archivia Client",
|
||||||
|
"actionUnarchiveClient": "Annulla Archiviazione Client",
|
||||||
|
"actionBlockClient": "Blocca Client",
|
||||||
|
"actionUnblockClient": "Sblocca Client",
|
||||||
"actionUpdateClient": "Aggiorna Client",
|
"actionUpdateClient": "Aggiorna Client",
|
||||||
"actionListClients": "Elenco Clienti",
|
"actionListClients": "Elenco Clienti",
|
||||||
"actionGetClient": "Ottieni Client",
|
"actionGetClient": "Ottieni Client",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Ricerca...",
|
"searchProgress": "Ricerca...",
|
||||||
"create": "Crea",
|
"create": "Crea",
|
||||||
"orgs": "Organizzazioni",
|
"orgs": "Organizzazioni",
|
||||||
"loginError": "Si è verificato un errore durante l'accesso",
|
"loginError": "Si è verificato un errore imprevisto. Riprova.",
|
||||||
"loginRequiredForDevice": "È richiesto il login per autenticare il dispositivo.",
|
"loginRequiredForDevice": "Il login è richiesto per il tuo dispositivo.",
|
||||||
"passwordForgot": "Password dimenticata?",
|
"passwordForgot": "Password dimenticata?",
|
||||||
"otpAuth": "Autenticazione a Due Fattori",
|
"otpAuth": "Autenticazione a Due Fattori",
|
||||||
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
|
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
|
||||||
"otpAuthSubmit": "Invia Codice",
|
"otpAuthSubmit": "Invia Codice",
|
||||||
"idpContinue": "O continua con",
|
"idpContinue": "O continua con",
|
||||||
"otpAuthBack": "Torna al Login",
|
"otpAuthBack": "Torna alla Password",
|
||||||
"navbar": "Menu di Navigazione",
|
"navbar": "Menu di Navigazione",
|
||||||
"navbarDescription": "Menu di navigazione principale dell'applicazione",
|
"navbarDescription": "Menu di navigazione principale dell'applicazione",
|
||||||
"navbarDocsLink": "Documentazione",
|
"navbarDocsLink": "Documentazione",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Panoramica",
|
"sidebarOverview": "Panoramica",
|
||||||
"sidebarHome": "Home",
|
"sidebarHome": "Home",
|
||||||
"sidebarSites": "Siti",
|
"sidebarSites": "Siti",
|
||||||
|
"sidebarApprovals": "Richieste Di Approvazione",
|
||||||
"sidebarResources": "Risorse",
|
"sidebarResources": "Risorse",
|
||||||
"sidebarProxyResources": "Pubblico",
|
"sidebarProxyResources": "Pubblico",
|
||||||
"sidebarClientResources": "Privato",
|
"sidebarClientResources": "Privato",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Fornitori Di Identità",
|
"sidebarIdentityProviders": "Fornitori Di Identità",
|
||||||
"sidebarLicense": "Licenza",
|
"sidebarLicense": "Licenza",
|
||||||
"sidebarClients": "Client",
|
"sidebarClients": "Client",
|
||||||
"sidebarUserDevices": "Utenti",
|
"sidebarUserDevices": "Dispositivi Utente",
|
||||||
"sidebarMachineClients": "Macchine",
|
"sidebarMachineClients": "Macchine",
|
||||||
"sidebarDomains": "Domini",
|
"sidebarDomains": "Domini",
|
||||||
"sidebarGeneral": "Gestisci",
|
"sidebarGeneral": "Gestisci",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Si è verificato un errore durante la creazione dell'account amministratore del server.",
|
"setupErrorCreateAdmin": "Si è verificato un errore durante la creazione dell'account amministratore del server.",
|
||||||
"certificateStatus": "Stato del Certificato",
|
"certificateStatus": "Stato del Certificato",
|
||||||
"loading": "Caricamento",
|
"loading": "Caricamento",
|
||||||
|
"loadingAnalytics": "Caricamento Delle Analisi",
|
||||||
"restart": "Riavvia",
|
"restart": "Riavvia",
|
||||||
"domains": "Domini",
|
"domains": "Domini",
|
||||||
"domainsDescription": "Creare e gestire i domini disponibili nell'organizzazione",
|
"domainsDescription": "Creare e gestire i domini disponibili nell'organizzazione",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Impossibile aggiornare i dati",
|
"refreshError": "Impossibile aggiornare i dati",
|
||||||
"verified": "Verificato",
|
"verified": "Verificato",
|
||||||
"pending": "In attesa",
|
"pending": "In attesa",
|
||||||
|
"pendingApproval": "Approvazione In Attesa",
|
||||||
"sidebarBilling": "Fatturazione",
|
"sidebarBilling": "Fatturazione",
|
||||||
"billing": "Fatturazione",
|
"billing": "Fatturazione",
|
||||||
"orgBillingDescription": "Gestisci le informazioni di fatturazione e gli abbonamenti",
|
"orgBillingDescription": "Gestisci le informazioni di fatturazione e gli abbonamenti",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Chiave di sicurezza rimossa con successo",
|
"securityKeyRemoveSuccess": "Chiave di sicurezza rimossa con successo",
|
||||||
"securityKeyRemoveError": "Errore durante la rimozione della chiave di sicurezza",
|
"securityKeyRemoveError": "Errore durante la rimozione della chiave di sicurezza",
|
||||||
"securityKeyLoadError": "Errore durante il caricamento delle chiavi di sicurezza",
|
"securityKeyLoadError": "Errore durante il caricamento delle chiavi di sicurezza",
|
||||||
"securityKeyLogin": "Continua con la chiave di sicurezza",
|
"securityKeyLogin": "Usa Chiave Di Sicurezza",
|
||||||
"securityKeyAuthError": "Errore durante l'autenticazione con chiave di sicurezza",
|
"securityKeyAuthError": "Errore durante l'autenticazione con chiave di sicurezza",
|
||||||
"securityKeyRecommendation": "Considera di registrare un'altra chiave di sicurezza su un dispositivo diverso per assicurarti di non rimanere bloccato fuori dal tuo account.",
|
"securityKeyRecommendation": "Considera di registrare un'altra chiave di sicurezza su un dispositivo diverso per assicurarti di non rimanere bloccato fuori dal tuo account.",
|
||||||
"registering": "Registrazione in corso...",
|
"registering": "Registrazione in corso...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Intervallo Sano",
|
"IntervalSeconds": "Intervallo Sano",
|
||||||
"timeoutSeconds": "Timeout (sec)",
|
"timeoutSeconds": "Timeout (sec)",
|
||||||
"timeIsInSeconds": "Il tempo è in secondi",
|
"timeIsInSeconds": "Il tempo è in secondi",
|
||||||
|
"requireDeviceApproval": "Richiede Approvazioni Dispositivo",
|
||||||
|
"requireDeviceApprovalDescription": "Gli utenti con questo ruolo hanno bisogno di nuovi dispositivi approvati da un amministratore prima di poter connettersi e accedere alle risorse.",
|
||||||
"retryAttempts": "Tentativi di Riprova",
|
"retryAttempts": "Tentativi di Riprova",
|
||||||
"expectedResponseCodes": "Codici di Risposta Attesi",
|
"expectedResponseCodes": "Codici di Risposta Attesi",
|
||||||
"expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.",
|
"expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Nessuna risorsa interna trovata.",
|
"resourcesTableNoInternalResourcesFound": "Nessuna risorsa interna trovata.",
|
||||||
"resourcesTableDestination": "Destinazione",
|
"resourcesTableDestination": "Destinazione",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Indirizzo Alias",
|
||||||
|
"resourcesTableAliasAddressInfo": "Questo indirizzo fa parte della subnet di utilità dell'organizzazione. È usato per risolvere i record alias usando la risoluzione DNS interna.",
|
||||||
"resourcesTableClients": "Client",
|
"resourcesTableClients": "Client",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "e sono accessibili solo internamente quando connessi con un client.",
|
"resourcesTableAndOnlyAccessibleInternally": "e sono accessibili solo internamente quando connessi con un client.",
|
||||||
"resourcesTableNoTargets": "Nessun obiettivo",
|
"resourcesTableNoTargets": "Nessun obiettivo",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Il codice deve contenere 9 caratteri (es. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Il codice deve contenere 9 caratteri (es. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Codice non valido o scaduto",
|
"deviceCodeInvalidOrExpired": "Codice non valido o scaduto",
|
||||||
"deviceCodeVerifyFailed": "Impossibile verificare il codice del dispositivo",
|
"deviceCodeVerifyFailed": "Impossibile verificare il codice del dispositivo",
|
||||||
|
"deviceCodeValidating": "Convalida codice dispositivo...",
|
||||||
|
"deviceCodeVerifying": "Verifica autorizzazione dispositivo...",
|
||||||
"signedInAs": "Accesso come",
|
"signedInAs": "Accesso come",
|
||||||
"deviceCodeEnterPrompt": "Inserisci il codice visualizzato sul dispositivo",
|
"deviceCodeEnterPrompt": "Inserisci il codice visualizzato sul dispositivo",
|
||||||
"continue": "Continua",
|
"continue": "Continua",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Accesso a tutte le organizzazioni a cui il tuo account ha accesso",
|
"deviceOrganizationsAccess": "Accesso a tutte le organizzazioni a cui il tuo account ha accesso",
|
||||||
"deviceAuthorize": "Autorizza {applicationName}",
|
"deviceAuthorize": "Autorizza {applicationName}",
|
||||||
"deviceConnected": "Dispositivo Connesso!",
|
"deviceConnected": "Dispositivo Connesso!",
|
||||||
"deviceAuthorizedMessage": "Il dispositivo è autorizzato ad accedere al tuo account.",
|
"deviceAuthorizedMessage": "Il dispositivo è autorizzato ad accedere al tuo account. Ritorna all'applicazione client.",
|
||||||
"pangolinCloud": "Pangolin Cloud",
|
"pangolinCloud": "Pangolin Cloud",
|
||||||
"viewDevices": "Visualizza Dispositivi",
|
"viewDevices": "Visualizza Dispositivi",
|
||||||
"viewDevicesDescription": "Gestisci i tuoi dispositivi connessi",
|
"viewDevicesDescription": "Gestisci i tuoi dispositivi connessi",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Non tu? Usa un account diverso.",
|
"deviceLoginUseDifferentAccount": "Non tu? Usa un account diverso.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo sta richiedendo l'accesso a questo account.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo sta richiedendo l'accesso a questo account.",
|
||||||
|
"loginSelectAuthenticationMethod": "Selezionare un metodo di autenticazione per continuare.",
|
||||||
"noData": "Nessun Dato",
|
"noData": "Nessun Dato",
|
||||||
"machineClients": "Machine Clients",
|
"machineClients": "Machine Clients",
|
||||||
"install": "Installa",
|
"install": "Installa",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Servizio Temporaneamente Non Disponibile",
|
"maintenanceScreenTitle": "Servizio Temporaneamente Non Disponibile",
|
||||||
"maintenanceScreenMessage": "Stiamo attualmente riscontrando difficoltà tecniche. Si prega di ricontrollare a breve.",
|
"maintenanceScreenMessage": "Stiamo attualmente riscontrando difficoltà tecniche. Si prega di ricontrollare a breve.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Completamento Stimato:",
|
"maintenanceScreenEstimatedCompletion": "Completamento Stimato:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta"
|
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta",
|
||||||
|
"available": "Disponibile",
|
||||||
|
"archived": "Archiviato",
|
||||||
|
"noArchivedDevices": "Nessun dispositivo archiviato trovato",
|
||||||
|
"deviceArchived": "Dispositivo archiviato",
|
||||||
|
"deviceArchivedDescription": "Il dispositivo è stato archiviato con successo.",
|
||||||
|
"errorArchivingDevice": "Errore nell'archiviazione del dispositivo",
|
||||||
|
"failedToArchiveDevice": "Impossibile archiviare il dispositivo",
|
||||||
|
"deviceQuestionArchive": "È sicuro di voler archiviare questo dispositivo?",
|
||||||
|
"deviceMessageArchive": "Il dispositivo verrà archiviato e rimosso dalla lista dei dispositivi attivi.",
|
||||||
|
"deviceArchiveConfirm": "Archivia Dispositivo",
|
||||||
|
"archiveDevice": "Archivia Dispositivo",
|
||||||
|
"archive": "Archivio",
|
||||||
|
"deviceUnarchived": "Dispositivo non archiviato",
|
||||||
|
"deviceUnarchivedDescription": "Il dispositivo è stato disarchiviato con successo.",
|
||||||
|
"errorUnarchivingDevice": "Errore nel disarchiviare il dispositivo",
|
||||||
|
"failedToUnarchiveDevice": "Disarchiviazione del dispositivo non riuscita",
|
||||||
|
"unarchive": "Disarchivia",
|
||||||
|
"archiveClient": "Archivia Client",
|
||||||
|
"archiveClientQuestion": "È sicuro di voler archiviare questo client?",
|
||||||
|
"archiveClientMessage": "Il client verrà archiviato e rimosso dalla lista dei client attivi.",
|
||||||
|
"archiveClientConfirm": "Archivia Client",
|
||||||
|
"blockClient": "Blocca Client",
|
||||||
|
"blockClientQuestion": "Sei sicuro di voler bloccare questo client?",
|
||||||
|
"blockClientMessage": "Il dispositivo sarà forzato a disconnettersi se attualmente connesso. Puoi sbloccare il dispositivo più tardi.",
|
||||||
|
"blockClientConfirm": "Blocca Client",
|
||||||
|
"active": "Attivo",
|
||||||
|
"usernameOrEmail": "Nome utente o Email",
|
||||||
|
"selectYourOrganization": "Seleziona la tua organizzazione",
|
||||||
|
"signInTo": "Accedi a",
|
||||||
|
"signInWithPassword": "Continua con la password",
|
||||||
|
"noAuthMethodsAvailable": "Nessun metodo di autenticazione disponibile per questa organizzazione.",
|
||||||
|
"enterPassword": "Inserisci la tua password",
|
||||||
|
"enterMfaCode": "Inserisci il codice dalla tua app di autenticazione",
|
||||||
|
"securityKeyRequired": "Utilizza la tua chiave di sicurezza per accedere.",
|
||||||
|
"needToUseAnotherAccount": "Hai bisogno di utilizzare un account diverso?",
|
||||||
|
"loginLegalDisclaimer": "Facendo clic sui pulsanti qui sotto, si riconosce di aver letto, capire, e accettare i Termini di Servizio <termsOfService></termsOfService> e <privacyPolicy>Privacy Policy</privacyPolicy>.",
|
||||||
|
"termsOfService": "Termini di servizio",
|
||||||
|
"privacyPolicy": "Politica Sulla Privacy",
|
||||||
|
"userNotFoundWithUsername": "Nessun utente trovato con questo nome utente.",
|
||||||
|
"verify": "Verifica",
|
||||||
|
"signIn": "Accedi",
|
||||||
|
"forgotPassword": "Password dimenticata?",
|
||||||
|
"orgSignInTip": "Se hai effettuato l'accesso prima, puoi inserire il tuo nome utente o email qui sopra per autenticarti con il provider di identità della tua organizzazione. È più facile!",
|
||||||
|
"continueAnyway": "Continua comunque",
|
||||||
|
"dontShowAgain": "Non mostrare più",
|
||||||
|
"orgSignInNotice": "Lo sapevate?",
|
||||||
|
"signupOrgNotice": "Cercando di accedere?",
|
||||||
|
"signupOrgTip": "Stai cercando di accedere tramite il provider di identità della tua organizzazione?",
|
||||||
|
"signupOrgLink": "Accedi o registrati con la tua organizzazione",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Usa un account diverso",
|
||||||
|
"logIn": "Log In",
|
||||||
|
"deviceInformation": "Informazioni Sul Dispositivo",
|
||||||
|
"deviceInformationDescription": "Informazioni sul dispositivo e sull'agente",
|
||||||
|
"deviceSecurity": "Sicurezza Del Dispositivo",
|
||||||
|
"deviceSecurityDescription": "Informazioni postura sicurezza dispositivo",
|
||||||
|
"platform": "Piattaforma",
|
||||||
|
"macosVersion": "versione macOS",
|
||||||
|
"windowsVersion": "Versione Windows",
|
||||||
|
"iosVersion": "Versione iOS",
|
||||||
|
"androidVersion": "Versione Android",
|
||||||
|
"osVersion": "Versione OS",
|
||||||
|
"kernelVersion": "Versione Del Kernel",
|
||||||
|
"deviceModel": "Modello Di Dispositivo",
|
||||||
|
"serialNumber": "Numero D'Ordine",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Prima Visto",
|
||||||
|
"lastSeen": "Visto L'Ultima",
|
||||||
|
"biometricsEnabled": "Biometria Abilitata",
|
||||||
|
"diskEncrypted": "Cifratura Del Disco",
|
||||||
|
"firewallEnabled": "Firewall Abilitato",
|
||||||
|
"autoUpdatesEnabled": "Aggiornamenti Automatici Abilitati",
|
||||||
|
"tpmAvailable": "TPM Disponibile",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Protezione Dell'Integrità Del Sistema (Sip)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Modo Furtivo Del Firewall",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Visualizza informazioni e impostazioni del dispositivo",
|
||||||
|
"devicePendingApprovalDescription": "Questo dispositivo è in attesa di approvazione",
|
||||||
|
"deviceBlockedDescription": "Questo dispositivo è attualmente bloccato. Non sarà in grado di connettersi a nessuna risorsa a meno che non sia sbloccato.",
|
||||||
|
"unblockClient": "Sblocca Client",
|
||||||
|
"unblockClientDescription": "Il dispositivo è stato sbloccato",
|
||||||
|
"unarchiveClient": "Annulla Archiviazione Client",
|
||||||
|
"unarchiveClientDescription": "Il dispositivo è stato disarchiviato",
|
||||||
|
"block": "Blocca",
|
||||||
|
"unblock": "Sblocca",
|
||||||
|
"deviceActions": "Azioni Dispositivo",
|
||||||
|
"deviceActionsDescription": "Gestisci lo stato del dispositivo e l'accesso",
|
||||||
|
"devicePendingApprovalBannerDescription": "Questo dispositivo è in attesa di approvazione. Non sarà in grado di connettersi alle risorse fino all'approvazione.",
|
||||||
|
"connected": "Connesso",
|
||||||
|
"disconnected": "Disconnesso",
|
||||||
|
"approvalsEmptyStateTitle": "Approvazioni Dispositivo Non Abilitato",
|
||||||
|
"approvalsEmptyStateDescription": "Abilita le approvazioni del dispositivo per i ruoli per richiedere l'approvazione dell'amministratore prima che gli utenti possano collegare nuovi dispositivi.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Vai ai ruoli",
|
||||||
|
"approvalsEmptyStateStep1Description": "Vai alle impostazioni dei ruoli della tua organizzazione per configurare le approvazioni del dispositivo.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Abilita Approvazioni Dispositivo",
|
||||||
|
"approvalsEmptyStateStep2Description": "Modifica un ruolo e abilita l'opzione 'Richiedi l'approvazione del dispositivo'. Gli utenti con questo ruolo avranno bisogno dell'approvazione dell'amministratore per i nuovi dispositivi.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Anteprima: quando abilitato, le richieste di dispositivo in attesa appariranno qui per la revisione",
|
||||||
|
"approvalsEmptyStateButtonText": "Gestisci Ruoli"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "모든 네트워크 연결",
|
"sitesBannerTitle": "모든 네트워크 연결",
|
||||||
"sitesBannerDescription": "사이트는 원격 네트워크와의 연결로 Pangolin이 어디서나 사용자에게 공공 및 개인 리소스에 대한 접근을 제공할 수 있게 해 줍니다. 연결을 설정하려면 바이너리 또는 컨테이너로 실행할 수 있는 어디서든 사이트 네트워크 커넥터(Newt)를 설치하세요.",
|
"sitesBannerDescription": "사이트는 원격 네트워크와의 연결로 Pangolin이 어디서나 사용자에게 공공 및 개인 리소스에 대한 접근을 제공할 수 있게 해 줍니다. 연결을 설정하려면 바이너리 또는 컨테이너로 실행할 수 있는 어디서든 사이트 네트워크 커넥터(Newt)를 설치하세요.",
|
||||||
"sitesBannerButtonText": "사이트 설치",
|
"sitesBannerButtonText": "사이트 설치",
|
||||||
|
"approvalsBannerTitle": "장치 접근 승인 또는 거부",
|
||||||
|
"approvalsBannerDescription": "사용자의 장치 접근 요청을 검토하고 승인하거나 거부하세요. 장치 승인 요구 시, 관리자의 승인이 필요합니다.",
|
||||||
|
"approvalsBannerButtonText": "자세히 알아보기",
|
||||||
"siteCreate": "사이트 생성",
|
"siteCreate": "사이트 생성",
|
||||||
"siteCreateDescription2": "아래 단계를 따라 새 사이트를 생성하고 연결하십시오",
|
"siteCreateDescription2": "아래 단계를 따라 새 사이트를 생성하고 연결하십시오",
|
||||||
"siteCreateDescription": "리소스를 연결하기 위해 새 사이트를 생성하세요.",
|
"siteCreateDescription": "리소스를 연결하기 위해 새 사이트를 생성하세요.",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "역할 검색...",
|
"accessRolesSearch": "역할 검색...",
|
||||||
"accessRolesAdd": "역할 추가",
|
"accessRolesAdd": "역할 추가",
|
||||||
"accessRoleDelete": "역할 삭제",
|
"accessRoleDelete": "역할 삭제",
|
||||||
|
"accessApprovalsManage": "승인 관리",
|
||||||
|
"accessApprovalsDescription": "이 조직의 접근 승인 대기를 보고 관리하세요.",
|
||||||
"description": "설명",
|
"description": "설명",
|
||||||
"inviteTitle": "열린 초대",
|
"inviteTitle": "열린 초대",
|
||||||
"inviteDescription": "다른 사용자가 조직에 참여하도록 초대장을 관리합니다.",
|
"inviteDescription": "다른 사용자가 조직에 참여하도록 초대장을 관리합니다.",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "지속 시간 선택",
|
"selectDuration": "지속 시간 선택",
|
||||||
"selectResource": "리소스 선택",
|
"selectResource": "리소스 선택",
|
||||||
"filterByResource": "리소스별 필터",
|
"filterByResource": "리소스별 필터",
|
||||||
|
"selectApprovalState": "승인 상태 선택",
|
||||||
|
"filterByApprovalState": "승인 상태로 필터링",
|
||||||
|
"approvalListEmpty": "승인이 없습니다.",
|
||||||
|
"approvalState": "승인 상태",
|
||||||
|
"approve": "승인",
|
||||||
|
"approved": "승인됨",
|
||||||
|
"denied": "거부됨",
|
||||||
|
"deniedApproval": "승인 거부됨",
|
||||||
|
"all": "모두",
|
||||||
|
"deny": "거부",
|
||||||
|
"viewDetails": "세부 정보 보기",
|
||||||
|
"requestingNewDeviceApproval": "새 장치를 요청함",
|
||||||
"resetFilters": "필터 재설정",
|
"resetFilters": "필터 재설정",
|
||||||
"totalBlocked": "Pangolin으로 차단된 요청",
|
"totalBlocked": "Pangolin으로 차단된 요청",
|
||||||
"totalRequests": "총 요청 수",
|
"totalRequests": "총 요청 수",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "국가",
|
"countries": "국가",
|
||||||
"accessRoleCreate": "역할 생성",
|
"accessRoleCreate": "역할 생성",
|
||||||
"accessRoleCreateDescription": "사용자를 그룹화하고 권한을 관리하기 위해 새 역할을 생성하세요.",
|
"accessRoleCreateDescription": "사용자를 그룹화하고 권한을 관리하기 위해 새 역할을 생성하세요.",
|
||||||
|
"accessRoleEdit": "역할 편집",
|
||||||
|
"accessRoleEditDescription": "역할 정보 편집.",
|
||||||
"accessRoleCreateSubmit": "역할 생성",
|
"accessRoleCreateSubmit": "역할 생성",
|
||||||
"accessRoleCreated": "역할이 생성되었습니다.",
|
"accessRoleCreated": "역할이 생성되었습니다.",
|
||||||
"accessRoleCreatedDescription": "역할이 성공적으로 생성되었습니다.",
|
"accessRoleCreatedDescription": "역할이 성공적으로 생성되었습니다.",
|
||||||
"accessRoleErrorCreate": "역할 생성 실패",
|
"accessRoleErrorCreate": "역할 생성 실패",
|
||||||
"accessRoleErrorCreateDescription": "역할 생성 중 오류가 발생했습니다.",
|
"accessRoleErrorCreateDescription": "역할 생성 중 오류가 발생했습니다.",
|
||||||
|
"accessRoleUpdateSubmit": "역할 업데이트",
|
||||||
|
"accessRoleUpdated": "역할 업데이트됨",
|
||||||
|
"accessRoleUpdatedDescription": "역할이 성공적으로 업데이트되었습니다.",
|
||||||
|
"accessApprovalUpdated": "승인 처리됨",
|
||||||
|
"accessApprovalApprovedDescription": "승인 요청을 승인으로 설정.",
|
||||||
|
"accessApprovalDeniedDescription": "승인 요청을 거부로 설정.",
|
||||||
|
"accessRoleErrorUpdate": "역할 업데이트 실패",
|
||||||
|
"accessRoleErrorUpdateDescription": "역할 업데이트 중 오류 발생.",
|
||||||
|
"accessApprovalErrorUpdate": "승인 처리 실패",
|
||||||
|
"accessApprovalErrorUpdateDescription": "승인 처리 중 오류가 발생했습니다.",
|
||||||
"accessRoleErrorNewRequired": "새 역할이 필요합니다.",
|
"accessRoleErrorNewRequired": "새 역할이 필요합니다.",
|
||||||
"accessRoleErrorRemove": "역할 제거에 실패했습니다.",
|
"accessRoleErrorRemove": "역할 제거에 실패했습니다.",
|
||||||
"accessRoleErrorRemoveDescription": "역할을 제거하는 동안 오류가 발생했습니다.",
|
"accessRoleErrorRemoveDescription": "역할을 제거하는 동안 오류가 발생했습니다.",
|
||||||
"accessRoleName": "역할 이름",
|
"accessRoleName": "역할 이름",
|
||||||
"accessRoleQuestionRemove": "{name} 역할을 삭제하려고 합니다. 이 작업은 취소할 수 없습니다.",
|
"accessRoleQuestionRemove": "`{name}` 역할을 삭제하려고 합니다. 이 작업은 되돌릴 수 없습니다.",
|
||||||
"accessRoleRemove": "역할 제거",
|
"accessRoleRemove": "역할 제거",
|
||||||
"accessRoleRemoveDescription": "조직에서 역할 제거",
|
"accessRoleRemoveDescription": "조직에서 역할 제거",
|
||||||
"accessRoleRemoveSubmit": "역할 제거",
|
"accessRoleRemoveSubmit": "역할 제거",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "관리자에게 문의하십시오",
|
"passwordResetSmtpRequired": "관리자에게 문의하십시오",
|
||||||
"passwordResetSmtpRequiredDescription": "비밀번호를 재설정하려면 비밀번호 초기화 코드가 필요합니다. 지원을 받으려면 관리자에게 문의하십시오.",
|
"passwordResetSmtpRequiredDescription": "비밀번호를 재설정하려면 비밀번호 초기화 코드가 필요합니다. 지원을 받으려면 관리자에게 문의하십시오.",
|
||||||
"passwordBack": "비밀번호로 돌아가기",
|
"passwordBack": "비밀번호로 돌아가기",
|
||||||
"loginBack": "로그인으로 돌아가기",
|
"loginBack": "메인 로그인 페이지로 돌아갑니다.",
|
||||||
"signup": "가입하기",
|
"signup": "가입하기",
|
||||||
"loginStart": "시작하려면 로그인하세요.",
|
"loginStart": "시작하려면 로그인하세요.",
|
||||||
"idpOidcTokenValidating": "OIDC 토큰 검증 중",
|
"idpOidcTokenValidating": "OIDC 토큰 검증 중",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "IDP 조직 업데이트",
|
"actionUpdateIdpOrg": "IDP 조직 업데이트",
|
||||||
"actionCreateClient": "클라이언트 생성",
|
"actionCreateClient": "클라이언트 생성",
|
||||||
"actionDeleteClient": "클라이언트 삭제",
|
"actionDeleteClient": "클라이언트 삭제",
|
||||||
|
"actionArchiveClient": "클라이언트 보관",
|
||||||
|
"actionUnarchiveClient": "클라이언트 보관 취소",
|
||||||
|
"actionBlockClient": "클라이언트 차단",
|
||||||
|
"actionUnblockClient": "클라이언트 차단 해제",
|
||||||
"actionUpdateClient": "클라이언트 업데이트",
|
"actionUpdateClient": "클라이언트 업데이트",
|
||||||
"actionListClients": "클라이언트 목록",
|
"actionListClients": "클라이언트 목록",
|
||||||
"actionGetClient": "클라이언트 가져오기",
|
"actionGetClient": "클라이언트 가져오기",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "검색...",
|
"searchProgress": "검색...",
|
||||||
"create": "생성",
|
"create": "생성",
|
||||||
"orgs": "조직",
|
"orgs": "조직",
|
||||||
"loginError": "로그인 중 오류가 발생했습니다",
|
"loginError": "예기치 않은 오류가 발생했습니다. 다시 시도해주세요.",
|
||||||
"loginRequiredForDevice": "장치를 인증하려면 로그인이 필요합니다.",
|
"loginRequiredForDevice": "로그인이 필요합니다.",
|
||||||
"passwordForgot": "비밀번호를 잊으셨나요?",
|
"passwordForgot": "비밀번호를 잊으셨나요?",
|
||||||
"otpAuth": "이중 인증",
|
"otpAuth": "이중 인증",
|
||||||
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
|
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
|
||||||
"otpAuthSubmit": "코드 제출",
|
"otpAuthSubmit": "코드 제출",
|
||||||
"idpContinue": "또는 계속 진행하십시오.",
|
"idpContinue": "또는 계속 진행하십시오.",
|
||||||
"otpAuthBack": "로그인으로 돌아가기",
|
"otpAuthBack": "비밀번호로 돌아가기",
|
||||||
"navbar": "탐색 메뉴",
|
"navbar": "탐색 메뉴",
|
||||||
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
|
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
|
||||||
"navbarDocsLink": "문서",
|
"navbarDocsLink": "문서",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "개요",
|
"sidebarOverview": "개요",
|
||||||
"sidebarHome": "홈",
|
"sidebarHome": "홈",
|
||||||
"sidebarSites": "사이트",
|
"sidebarSites": "사이트",
|
||||||
|
"sidebarApprovals": "승인 요청",
|
||||||
"sidebarResources": "리소스",
|
"sidebarResources": "리소스",
|
||||||
"sidebarProxyResources": "공유",
|
"sidebarProxyResources": "공유",
|
||||||
"sidebarClientResources": "비공개",
|
"sidebarClientResources": "비공개",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "신원 공급자",
|
"sidebarIdentityProviders": "신원 공급자",
|
||||||
"sidebarLicense": "라이선스",
|
"sidebarLicense": "라이선스",
|
||||||
"sidebarClients": "클라이언트",
|
"sidebarClients": "클라이언트",
|
||||||
"sidebarUserDevices": "사용자",
|
"sidebarUserDevices": "사용자 장치",
|
||||||
"sidebarMachineClients": "기계",
|
"sidebarMachineClients": "기계",
|
||||||
"sidebarDomains": "도메인",
|
"sidebarDomains": "도메인",
|
||||||
"sidebarGeneral": "관리",
|
"sidebarGeneral": "관리",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "서버 관리자 계정을 생성하는 동안 오류가 발생했습니다.",
|
"setupErrorCreateAdmin": "서버 관리자 계정을 생성하는 동안 오류가 발생했습니다.",
|
||||||
"certificateStatus": "인증서 상태",
|
"certificateStatus": "인증서 상태",
|
||||||
"loading": "로딩 중",
|
"loading": "로딩 중",
|
||||||
|
"loadingAnalytics": "분석 로딩 중",
|
||||||
"restart": "재시작",
|
"restart": "재시작",
|
||||||
"domains": "도메인",
|
"domains": "도메인",
|
||||||
"domainsDescription": "조직에서 사용 가능한 도메인 생성 및 관리",
|
"domainsDescription": "조직에서 사용 가능한 도메인 생성 및 관리",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "데이터 새로고침 실패",
|
"refreshError": "데이터 새로고침 실패",
|
||||||
"verified": "검증됨",
|
"verified": "검증됨",
|
||||||
"pending": "대기 중",
|
"pending": "대기 중",
|
||||||
|
"pendingApproval": "승인 대기 중",
|
||||||
"sidebarBilling": "청구",
|
"sidebarBilling": "청구",
|
||||||
"billing": "청구",
|
"billing": "청구",
|
||||||
"orgBillingDescription": "청구 정보 및 구독을 관리하세요",
|
"orgBillingDescription": "청구 정보 및 구독을 관리하세요",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "보안 키가 성공적으로 제거되었습니다",
|
"securityKeyRemoveSuccess": "보안 키가 성공적으로 제거되었습니다",
|
||||||
"securityKeyRemoveError": "보안 키 제거 실패",
|
"securityKeyRemoveError": "보안 키 제거 실패",
|
||||||
"securityKeyLoadError": "보안 키를 불러오는 데 실패했습니다",
|
"securityKeyLoadError": "보안 키를 불러오는 데 실패했습니다",
|
||||||
"securityKeyLogin": "보안 키로 계속하기",
|
"securityKeyLogin": "보안 키 사용",
|
||||||
"securityKeyAuthError": "보안 키를 사용한 인증 실패",
|
"securityKeyAuthError": "보안 키를 사용한 인증 실패",
|
||||||
"securityKeyRecommendation": "항상 계정에 액세스할 수 있도록 다른 장치에 백업 보안 키를 등록하세요.",
|
"securityKeyRecommendation": "항상 계정에 액세스할 수 있도록 다른 장치에 백업 보안 키를 등록하세요.",
|
||||||
"registering": "등록 중...",
|
"registering": "등록 중...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "정상 간격",
|
"IntervalSeconds": "정상 간격",
|
||||||
"timeoutSeconds": "타임아웃(초)",
|
"timeoutSeconds": "타임아웃(초)",
|
||||||
"timeIsInSeconds": "시간은 초 단위입니다",
|
"timeIsInSeconds": "시간은 초 단위입니다",
|
||||||
|
"requireDeviceApproval": "장치 승인 요구",
|
||||||
|
"requireDeviceApprovalDescription": "이 역할을 가진 사용자는 장치가 연결되기 전에 관리자의 승인이 필요합니다.",
|
||||||
"retryAttempts": "재시도 횟수",
|
"retryAttempts": "재시도 횟수",
|
||||||
"expectedResponseCodes": "예상 응답 코드",
|
"expectedResponseCodes": "예상 응답 코드",
|
||||||
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
|
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "내부 리소스를 찾을 수 없습니다.",
|
"resourcesTableNoInternalResourcesFound": "내부 리소스를 찾을 수 없습니다.",
|
||||||
"resourcesTableDestination": "대상지",
|
"resourcesTableDestination": "대상지",
|
||||||
"resourcesTableAlias": "별칭",
|
"resourcesTableAlias": "별칭",
|
||||||
|
"resourcesTableAliasAddress": "별칭 주소",
|
||||||
|
"resourcesTableAliasAddressInfo": "이 주소는 조직의 유틸리티 서브넷의 일부로, 내부 DNS 해석을 사용하여 별칭 레코드를 해석하는 데 사용됩니다.",
|
||||||
"resourcesTableClients": "클라이언트",
|
"resourcesTableClients": "클라이언트",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "클라이언트와 연결되었을 때만 내부적으로 접근 가능합니다.",
|
"resourcesTableAndOnlyAccessibleInternally": "클라이언트와 연결되었을 때만 내부적으로 접근 가능합니다.",
|
||||||
"resourcesTableNoTargets": "대상 없음",
|
"resourcesTableNoTargets": "대상 없음",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "계속하려면 신원 공급자를 선택하세요.",
|
"orgAuthChooseIdpDescription": "계속하려면 신원 공급자를 선택하세요.",
|
||||||
"orgAuthNoIdpConfigured": "이 조직은 구성된 신원 공급자가 없습니다. 대신 Pangolin 아이덴티티로 로그인할 수 있습니다.",
|
"orgAuthNoIdpConfigured": "이 조직은 구성된 신원 공급자가 없습니다. 대신 Pangolin 아이덴티티로 로그인할 수 있습니다.",
|
||||||
"orgAuthSignInWithPangolin": "Pangolin으로 로그인",
|
"orgAuthSignInWithPangolin": "Pangolin으로 로그인",
|
||||||
"orgAuthSignInToOrg": "조직에 로그인합니다.",
|
"orgAuthSignInToOrg": "조직에 로그인",
|
||||||
"orgAuthSelectOrgTitle": "조직 로그인",
|
"orgAuthSelectOrgTitle": "조직 로그인",
|
||||||
"orgAuthSelectOrgDescription": "계속하려면 조직 ID를 입력하십시오.",
|
"orgAuthSelectOrgDescription": "계속하려면 조직 ID를 입력하십시오.",
|
||||||
"orgAuthOrgIdPlaceholder": "your-organization",
|
"orgAuthOrgIdPlaceholder": "your-organization",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "코드는 9자리여야 합니다 (예: A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "코드는 9자리여야 합니다 (예: A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "무효하거나 만료된 코드",
|
"deviceCodeInvalidOrExpired": "무효하거나 만료된 코드",
|
||||||
"deviceCodeVerifyFailed": "이메일 확인에 실패했습니다:",
|
"deviceCodeVerifyFailed": "이메일 확인에 실패했습니다:",
|
||||||
|
"deviceCodeValidating": "장치 코드 검증 중...",
|
||||||
|
"deviceCodeVerifying": "장치 권한 검증 중...",
|
||||||
"signedInAs": "로그인한 사용자",
|
"signedInAs": "로그인한 사용자",
|
||||||
"deviceCodeEnterPrompt": "기기에 표시된 코드를 입력하세요",
|
"deviceCodeEnterPrompt": "기기에 표시된 코드를 입력하세요",
|
||||||
"continue": "계속 진행하기",
|
"continue": "계속 진행하기",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "계정이 접근할 수 있는 모든 조직에 대한 접근",
|
"deviceOrganizationsAccess": "계정이 접근할 수 있는 모든 조직에 대한 접근",
|
||||||
"deviceAuthorize": "{applicationName} 권한 부여",
|
"deviceAuthorize": "{applicationName} 권한 부여",
|
||||||
"deviceConnected": "장치가 연결되었습니다!",
|
"deviceConnected": "장치가 연결되었습니다!",
|
||||||
"deviceAuthorizedMessage": "장치가 계정에 액세스할 수 있도록 승인되었습니다.",
|
"deviceAuthorizedMessage": "장치가 계정 접속을 승인받았습니다. 클라이언트 응용프로그램으로 돌아가세요.",
|
||||||
"pangolinCloud": "판골린 클라우드",
|
"pangolinCloud": "판골린 클라우드",
|
||||||
"viewDevices": "장치 보기",
|
"viewDevices": "장치 보기",
|
||||||
"viewDevicesDescription": "연결된 장치를 관리하십시오",
|
"viewDevicesDescription": "연결된 장치를 관리하십시오",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "식별자",
|
"identifier": "식별자",
|
||||||
"deviceLoginUseDifferentAccount": "본인이 아닙니까? 다른 계정을 사용하세요.",
|
"deviceLoginUseDifferentAccount": "본인이 아닙니까? 다른 계정을 사용하세요.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "장치가 이 계정에 접근하려고 합니다.",
|
"deviceLoginDeviceRequestingAccessToAccount": "장치가 이 계정에 접근하려고 합니다.",
|
||||||
|
"loginSelectAuthenticationMethod": "계속하려면 인증 방법을 선택하세요.",
|
||||||
"noData": "데이터 없음",
|
"noData": "데이터 없음",
|
||||||
"machineClients": "기계 클라이언트",
|
"machineClients": "기계 클라이언트",
|
||||||
"install": "설치",
|
"install": "설치",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "서비스 일시 중단",
|
"maintenanceScreenTitle": "서비스 일시 중단",
|
||||||
"maintenanceScreenMessage": "현재 기술적 문제를 겪고 있습니다. 곧 다시 확인하십시오.",
|
"maintenanceScreenMessage": "현재 기술적 문제를 겪고 있습니다. 곧 다시 확인하십시오.",
|
||||||
"maintenanceScreenEstimatedCompletion": "예상 완료:",
|
"maintenanceScreenEstimatedCompletion": "예상 완료:",
|
||||||
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다."
|
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다.",
|
||||||
|
"available": "사용 가능",
|
||||||
|
"archived": "보관된",
|
||||||
|
"noArchivedDevices": "보관된 장치가 없습니다.",
|
||||||
|
"deviceArchived": "장치가 보관되었습니다.",
|
||||||
|
"deviceArchivedDescription": "장치가 성공적으로 보관되었습니다.",
|
||||||
|
"errorArchivingDevice": "장치를 보관하는 동안 오류가 발생했습니다.",
|
||||||
|
"failedToArchiveDevice": "장치를 보관하는 데 실패했습니다.",
|
||||||
|
"deviceQuestionArchive": "이 장치를 보관하시겠습니까?",
|
||||||
|
"deviceMessageArchive": "장치가 보관되며 당신의 활성 장치 목록에서 제거됩니다.",
|
||||||
|
"deviceArchiveConfirm": "장치 보관",
|
||||||
|
"archiveDevice": "장치 보관",
|
||||||
|
"archive": "보관",
|
||||||
|
"deviceUnarchived": "장치의 보관이 취소되었습니다.",
|
||||||
|
"deviceUnarchivedDescription": "장치의 보관이 성공적으로 취소되었습니다.",
|
||||||
|
"errorUnarchivingDevice": "장치 보관 해제 중 오류가 발생했습니다.",
|
||||||
|
"failedToUnarchiveDevice": "장치 보관 해제 실패",
|
||||||
|
"unarchive": "보관 해제",
|
||||||
|
"archiveClient": "클라이언트 보관",
|
||||||
|
"archiveClientQuestion": "이 클라이언트를 보관하시겠습니까?",
|
||||||
|
"archiveClientMessage": "클라이언트가 보관되며 당신의 활성 클라이언트 목록에서 제거됩니다.",
|
||||||
|
"archiveClientConfirm": "클라이언트 보관 확인",
|
||||||
|
"blockClient": "클라이언트 차단",
|
||||||
|
"blockClientQuestion": "이 클라이언트를 차단하시겠습니까?",
|
||||||
|
"blockClientMessage": "장치가 현재 연결되어 있는 경우 강제로 연결이 해제됩니다. 이후에도 차단 해제가 가능합니다.",
|
||||||
|
"blockClientConfirm": "클라이언트 차단 확인",
|
||||||
|
"active": "활성",
|
||||||
|
"usernameOrEmail": "사용자 이름 또는 이메일",
|
||||||
|
"selectYourOrganization": "조직 선택",
|
||||||
|
"signInTo": "로그인 중",
|
||||||
|
"signInWithPassword": "비밀번호로 계속",
|
||||||
|
"noAuthMethodsAvailable": "이 조직에는 사용할 수 있는 인증 방법이 없습니다.",
|
||||||
|
"enterPassword": "비밀번호를 입력하세요.",
|
||||||
|
"enterMfaCode": "인증 앱에서 제공한 코드를 입력하세요.",
|
||||||
|
"securityKeyRequired": "보안 키를 사용해 로그인하세요.",
|
||||||
|
"needToUseAnotherAccount": "다른 계정을 사용해야 합니까?",
|
||||||
|
"loginLegalDisclaimer": "아래 버튼을 클릭하여 <termsOfService>서비스 약관</termsOfService>과 <privacyPolicy>개인 정보 보호 정책</privacyPolicy>을 읽고 이해했으며 동의함을 인정합니다.",
|
||||||
|
"termsOfService": "서비스 약관",
|
||||||
|
"privacyPolicy": "개인 정보 보호 정책",
|
||||||
|
"userNotFoundWithUsername": "해당 사용자 이름으로 사용자를 찾지 못했습니다.",
|
||||||
|
"verify": "확인",
|
||||||
|
"signIn": "로그인",
|
||||||
|
"forgotPassword": "비밀번호를 잊으셨나요?",
|
||||||
|
"orgSignInTip": "이전에 로그인한 적이 있다면, 위의 사용자 이름 또는 이메일을 입력하여 조직의 ID 공급자로 인증할 수 있습니다. 더 쉬워요!",
|
||||||
|
"continueAnyway": "계속하기",
|
||||||
|
"dontShowAgain": "다시 보기 않습니다.",
|
||||||
|
"orgSignInNotice": "아셨나요?",
|
||||||
|
"signupOrgNotice": "로그인 중이신가요?",
|
||||||
|
"signupOrgTip": "조직의 ID 공급자를 통해 로그인하려고 하십니까?",
|
||||||
|
"signupOrgLink": "대신 조직을 사용하여 로그인 또는 가입",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "다른 계정 사용",
|
||||||
|
"logIn": "로그인",
|
||||||
|
"deviceInformation": "장치 정보",
|
||||||
|
"deviceInformationDescription": "장치와 에이전트 정보",
|
||||||
|
"deviceSecurity": "디바이스 보안",
|
||||||
|
"deviceSecurityDescription": "디바이스 보안 상태 정보",
|
||||||
|
"platform": "플랫폼",
|
||||||
|
"macosVersion": "macOS 버전",
|
||||||
|
"windowsVersion": "Windows 버전",
|
||||||
|
"iosVersion": "iOS 버전",
|
||||||
|
"androidVersion": "Android 버전",
|
||||||
|
"osVersion": "OS 버전",
|
||||||
|
"kernelVersion": "커널 버전",
|
||||||
|
"deviceModel": "장치 모델",
|
||||||
|
"serialNumber": "일련 번호",
|
||||||
|
"hostname": "호스트 이름",
|
||||||
|
"firstSeen": "처음 발견됨",
|
||||||
|
"lastSeen": "마지막으로 발견됨",
|
||||||
|
"biometricsEnabled": "생체 인식 활성화",
|
||||||
|
"diskEncrypted": "디스크 암호화됨",
|
||||||
|
"firewallEnabled": "방화벽 활성화",
|
||||||
|
"autoUpdatesEnabled": "자동 업데이트 활성화",
|
||||||
|
"tpmAvailable": "TPM 사용 가능",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "시스템 무결성 보호 (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "방화벽 스텔스 모드",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "장치 정보 및 설정 보기",
|
||||||
|
"devicePendingApprovalDescription": "이 장치는 승인을 기다리고 있습니다.",
|
||||||
|
"deviceBlockedDescription": "이 장치는 현재 차단되었습니다. 차단이 해제되지 않으면 리소스에 연결할 수 없습니다.",
|
||||||
|
"unblockClient": "클라이언트 차단 해제",
|
||||||
|
"unblockClientDescription": "장치가 차단 해제되었습니다.",
|
||||||
|
"unarchiveClient": "클라이언트 보관 취소",
|
||||||
|
"unarchiveClientDescription": "장치가 보관 해제되었습니다.",
|
||||||
|
"block": "차단",
|
||||||
|
"unblock": "차단 해제",
|
||||||
|
"deviceActions": "장치 작업",
|
||||||
|
"deviceActionsDescription": "장치 상태 및 접근 관리",
|
||||||
|
"devicePendingApprovalBannerDescription": "이 장치는 승인 대기 중입니다. 승인될 때까지 리소스에 연결할 수 없습니다.",
|
||||||
|
"connected": "연결됨",
|
||||||
|
"disconnected": "연결 해제됨",
|
||||||
|
"approvalsEmptyStateTitle": "장치 승인 비활성화됨",
|
||||||
|
"approvalsEmptyStateDescription": "사용자가 새 장치를 연결하기 전에 관리자의 승인을 필요로 하도록 역할에 대해 장치 승인을 활성화하세요.",
|
||||||
|
"approvalsEmptyStateStep1Title": "역할로 이동",
|
||||||
|
"approvalsEmptyStateStep1Description": "조직의 역할 설정으로 이동하여 장치 승인을 구성하십시오.",
|
||||||
|
"approvalsEmptyStateStep2Title": "장치 승인 활성화",
|
||||||
|
"approvalsEmptyStateStep2Description": "역할을 편집하고 '장치 승인 요구' 옵션을 활성화하세요. 이 역할을 가진 사용자는 새 장치에 대해 관리자의 승인이 필요합니다.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "미리 보기: 활성화된 경우, 승인 대기 중인 장치 요청이 검토용으로 여기에 표시됩니다.",
|
||||||
|
"approvalsEmptyStateButtonText": "역할 관리"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Koble til alle nettverk",
|
"sitesBannerTitle": "Koble til alle nettverk",
|
||||||
"sitesBannerDescription": "Et nettverk er en tilkobling til et eksternt nettverk som tillater Pangolin å gi tilgang til ressurser, enten offentlige eller private, til brukere hvor som helst. Installer nettverkskontaktet (Newt) hvor som helst du kan kjøre en binærfil eller container for å opprette forbindelsen.",
|
"sitesBannerDescription": "Et nettverk er en tilkobling til et eksternt nettverk som tillater Pangolin å gi tilgang til ressurser, enten offentlige eller private, til brukere hvor som helst. Installer nettverkskontaktet (Newt) hvor som helst du kan kjøre en binærfil eller container for å opprette forbindelsen.",
|
||||||
"sitesBannerButtonText": "Installer nettsted",
|
"sitesBannerButtonText": "Installer nettsted",
|
||||||
|
"approvalsBannerTitle": "Godkjenn eller avslå tilgang til enhet",
|
||||||
|
"approvalsBannerDescription": "Gjennomgå og godkjenne eller avslå forespørsler om tilgang fra brukere. Når enhetsgodkjenninger er nødvendig, må brukere få admingodkjenning før enhetene kan koble seg til organisasjonens ressurser.",
|
||||||
|
"approvalsBannerButtonText": "Lær mer",
|
||||||
"siteCreate": "Opprett område",
|
"siteCreate": "Opprett område",
|
||||||
"siteCreateDescription2": "Følg trinnene nedenfor for å opprette og koble til et nytt område",
|
"siteCreateDescription2": "Følg trinnene nedenfor for å opprette og koble til et nytt område",
|
||||||
"siteCreateDescription": "Opprett et nytt nettsted for å koble til ressurser",
|
"siteCreateDescription": "Opprett et nytt nettsted for å koble til ressurser",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Søk etter roller...",
|
"accessRolesSearch": "Søk etter roller...",
|
||||||
"accessRolesAdd": "Legg til rolle",
|
"accessRolesAdd": "Legg til rolle",
|
||||||
"accessRoleDelete": "Slett rolle",
|
"accessRoleDelete": "Slett rolle",
|
||||||
|
"accessApprovalsManage": "Behandle godkjenninger",
|
||||||
|
"accessApprovalsDescription": "Se og administrer ventende godkjenninger for tilgang til denne organisasjonen",
|
||||||
"description": "Beskrivelse",
|
"description": "Beskrivelse",
|
||||||
"inviteTitle": "Åpne invitasjoner",
|
"inviteTitle": "Åpne invitasjoner",
|
||||||
"inviteDescription": "Administrer invitasjoner til andre brukere for å bli med i organisasjonen",
|
"inviteDescription": "Administrer invitasjoner til andre brukere for å bli med i organisasjonen",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Velg varighet",
|
"selectDuration": "Velg varighet",
|
||||||
"selectResource": "Velg ressurs",
|
"selectResource": "Velg ressurs",
|
||||||
"filterByResource": "Filtrer etter ressurser",
|
"filterByResource": "Filtrer etter ressurser",
|
||||||
|
"selectApprovalState": "Velg godkjenningsstatus",
|
||||||
|
"filterByApprovalState": "Filtrer etter godkjenningsstatus",
|
||||||
|
"approvalListEmpty": "Ingen godkjenninger",
|
||||||
|
"approvalState": "Godkjennings tilstand",
|
||||||
|
"approve": "Godkjenn",
|
||||||
|
"approved": "Godkjent",
|
||||||
|
"denied": "Avvist",
|
||||||
|
"deniedApproval": "Avslått godkjenning",
|
||||||
|
"all": "Alle",
|
||||||
|
"deny": "Avslå",
|
||||||
|
"viewDetails": "Vis detaljer",
|
||||||
|
"requestingNewDeviceApproval": "forespurt en ny enhet",
|
||||||
"resetFilters": "Tilbakestill filtre",
|
"resetFilters": "Tilbakestill filtre",
|
||||||
"totalBlocked": "Forespørsler blokkert av Pangolin",
|
"totalBlocked": "Forespørsler blokkert av Pangolin",
|
||||||
"totalRequests": "Totalt antall forespørsler",
|
"totalRequests": "Totalt antall forespørsler",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Land",
|
"countries": "Land",
|
||||||
"accessRoleCreate": "Opprett rolle",
|
"accessRoleCreate": "Opprett rolle",
|
||||||
"accessRoleCreateDescription": "Opprett en ny rolle for å gruppere brukere og administrere deres tillatelser.",
|
"accessRoleCreateDescription": "Opprett en ny rolle for å gruppere brukere og administrere deres tillatelser.",
|
||||||
|
"accessRoleEdit": "Rediger rolle",
|
||||||
|
"accessRoleEditDescription": "Rediger rolleinformasjon.",
|
||||||
"accessRoleCreateSubmit": "Opprett rolle",
|
"accessRoleCreateSubmit": "Opprett rolle",
|
||||||
"accessRoleCreated": "Rolle opprettet",
|
"accessRoleCreated": "Rolle opprettet",
|
||||||
"accessRoleCreatedDescription": "Rollen er vellykket opprettet.",
|
"accessRoleCreatedDescription": "Rollen er vellykket opprettet.",
|
||||||
"accessRoleErrorCreate": "Klarte ikke å opprette rolle",
|
"accessRoleErrorCreate": "Klarte ikke å opprette rolle",
|
||||||
"accessRoleErrorCreateDescription": "Det oppstod en feil under opprettelse av rollen.",
|
"accessRoleErrorCreateDescription": "Det oppstod en feil under opprettelse av rollen.",
|
||||||
|
"accessRoleUpdateSubmit": "Oppdater rolle",
|
||||||
|
"accessRoleUpdated": "Rollen oppdatert",
|
||||||
|
"accessRoleUpdatedDescription": "Rollen har blitt oppdatert.",
|
||||||
|
"accessApprovalUpdated": "Godkjenning behandlet",
|
||||||
|
"accessApprovalApprovedDescription": "Sett godkjenningsforespørsel om å godta.",
|
||||||
|
"accessApprovalDeniedDescription": "Sett godkjenningsforespørsel om å nekte.",
|
||||||
|
"accessRoleErrorUpdate": "Kunne ikke oppdatere rolle",
|
||||||
|
"accessRoleErrorUpdateDescription": "Det oppstod en feil under oppdatering av rollen.",
|
||||||
|
"accessApprovalErrorUpdate": "Kunne ikke behandle godkjenning",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Det oppstod en feil under behandling av godkjenningen.",
|
||||||
"accessRoleErrorNewRequired": "Ny rolle kreves",
|
"accessRoleErrorNewRequired": "Ny rolle kreves",
|
||||||
"accessRoleErrorRemove": "Kunne ikke fjerne rolle",
|
"accessRoleErrorRemove": "Kunne ikke fjerne rolle",
|
||||||
"accessRoleErrorRemoveDescription": "Det oppstod en feil under fjerning av rollen.",
|
"accessRoleErrorRemoveDescription": "Det oppstod en feil under fjerning av rollen.",
|
||||||
"accessRoleName": "Rollenavn",
|
"accessRoleName": "Rollenavn",
|
||||||
"accessRoleQuestionRemove": "Du er i ferd med å slette rollen {name}. Du kan ikke angre denne handlingen.",
|
"accessRoleQuestionRemove": "Du er ferd med å slette rollen `{name}. Du kan ikke angre denne handlingen.",
|
||||||
"accessRoleRemove": "Fjern Rolle",
|
"accessRoleRemove": "Fjern Rolle",
|
||||||
"accessRoleRemoveDescription": "Fjern en rolle fra organisasjonen",
|
"accessRoleRemoveDescription": "Fjern en rolle fra organisasjonen",
|
||||||
"accessRoleRemoveSubmit": "Fjern Rolle",
|
"accessRoleRemoveSubmit": "Fjern Rolle",
|
||||||
@@ -954,13 +983,13 @@
|
|||||||
"passwordExpiryDescription": "Denne organisasjonen krever at du bytter passord hver {maxDays} dag.",
|
"passwordExpiryDescription": "Denne organisasjonen krever at du bytter passord hver {maxDays} dag.",
|
||||||
"changePasswordNow": "Bytt passord nå",
|
"changePasswordNow": "Bytt passord nå",
|
||||||
"pincodeAuth": "Autentiseringskode",
|
"pincodeAuth": "Autentiseringskode",
|
||||||
"pincodeSubmit2": "Send inn kode",
|
"pincodeSubmit2": "Send kode",
|
||||||
"passwordResetSubmit": "Be om tilbakestilling",
|
"passwordResetSubmit": "Be om tilbakestilling",
|
||||||
"passwordResetAlreadyHaveCode": "Skriv inn koden",
|
"passwordResetAlreadyHaveCode": "Skriv inn koden",
|
||||||
"passwordResetSmtpRequired": "Kontakt din administrator",
|
"passwordResetSmtpRequired": "Kontakt din administrator",
|
||||||
"passwordResetSmtpRequiredDescription": "En passord tilbakestillingskode kreves for å tilbakestille passordet. Kontakt systemansvarlig for assistanse.",
|
"passwordResetSmtpRequiredDescription": "En passord tilbakestillingskode kreves for å tilbakestille passordet. Kontakt systemansvarlig for assistanse.",
|
||||||
"passwordBack": "Tilbake til passord",
|
"passwordBack": "Tilbake til passord",
|
||||||
"loginBack": "Gå tilbake til innlogging",
|
"loginBack": "Gå tilbake til innloggingssiden for hovedkontoen",
|
||||||
"signup": "Registrer deg",
|
"signup": "Registrer deg",
|
||||||
"loginStart": "Logg inn for å komme i gang",
|
"loginStart": "Logg inn for å komme i gang",
|
||||||
"idpOidcTokenValidating": "Validerer OIDC-token",
|
"idpOidcTokenValidating": "Validerer OIDC-token",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Oppdater IDP-organisasjon",
|
"actionUpdateIdpOrg": "Oppdater IDP-organisasjon",
|
||||||
"actionCreateClient": "Opprett Klient",
|
"actionCreateClient": "Opprett Klient",
|
||||||
"actionDeleteClient": "Slett klient",
|
"actionDeleteClient": "Slett klient",
|
||||||
|
"actionArchiveClient": "Arkiver klient",
|
||||||
|
"actionUnarchiveClient": "Fjern arkivering klient",
|
||||||
|
"actionBlockClient": "Blokker kunde",
|
||||||
|
"actionUnblockClient": "Avblokker klient",
|
||||||
"actionUpdateClient": "Oppdater klient",
|
"actionUpdateClient": "Oppdater klient",
|
||||||
"actionListClients": "List klienter",
|
"actionListClients": "List klienter",
|
||||||
"actionGetClient": "Hent klient",
|
"actionGetClient": "Hent klient",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Søker...",
|
"searchProgress": "Søker...",
|
||||||
"create": "Opprett",
|
"create": "Opprett",
|
||||||
"orgs": "Organisasjoner",
|
"orgs": "Organisasjoner",
|
||||||
"loginError": "En feil oppstod under innlogging",
|
"loginError": "En uventet feil oppstod. Vennligst prøv igjen.",
|
||||||
"loginRequiredForDevice": "Innlogging kreves for å godkjenne enheten.",
|
"loginRequiredForDevice": "Innlogging er nødvendig for enheten din.",
|
||||||
"passwordForgot": "Glemt passordet ditt?",
|
"passwordForgot": "Glemt passordet ditt?",
|
||||||
"otpAuth": "Tofaktorautentisering",
|
"otpAuth": "Tofaktorautentisering",
|
||||||
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
|
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
|
||||||
"otpAuthSubmit": "Send inn kode",
|
"otpAuthSubmit": "Send inn kode",
|
||||||
"idpContinue": "Eller fortsett med",
|
"idpContinue": "Eller fortsett med",
|
||||||
"otpAuthBack": "Tilbake til innlogging",
|
"otpAuthBack": "Tilbake til passord",
|
||||||
"navbar": "Navigasjonsmeny",
|
"navbar": "Navigasjonsmeny",
|
||||||
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
|
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
|
||||||
"navbarDocsLink": "Dokumentasjon",
|
"navbarDocsLink": "Dokumentasjon",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Oversikt",
|
"sidebarOverview": "Oversikt",
|
||||||
"sidebarHome": "Hjem",
|
"sidebarHome": "Hjem",
|
||||||
"sidebarSites": "Områder",
|
"sidebarSites": "Områder",
|
||||||
|
"sidebarApprovals": "Godkjenningsforespørsler",
|
||||||
"sidebarResources": "Ressurser",
|
"sidebarResources": "Ressurser",
|
||||||
"sidebarProxyResources": "Offentlig",
|
"sidebarProxyResources": "Offentlig",
|
||||||
"sidebarClientResources": "Privat",
|
"sidebarClientResources": "Privat",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Identitetsleverandører",
|
"sidebarIdentityProviders": "Identitetsleverandører",
|
||||||
"sidebarLicense": "Lisens",
|
"sidebarLicense": "Lisens",
|
||||||
"sidebarClients": "Klienter",
|
"sidebarClients": "Klienter",
|
||||||
"sidebarUserDevices": "Brukere",
|
"sidebarUserDevices": "Bruker Enheter",
|
||||||
"sidebarMachineClients": "Maskiner",
|
"sidebarMachineClients": "Maskiner",
|
||||||
"sidebarDomains": "Domener",
|
"sidebarDomains": "Domener",
|
||||||
"sidebarGeneral": "Administrer",
|
"sidebarGeneral": "Administrer",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "En feil oppstod under opprettelsen av serveradministratorkontoen.",
|
"setupErrorCreateAdmin": "En feil oppstod under opprettelsen av serveradministratorkontoen.",
|
||||||
"certificateStatus": "Sertifikatstatus",
|
"certificateStatus": "Sertifikatstatus",
|
||||||
"loading": "Laster inn",
|
"loading": "Laster inn",
|
||||||
|
"loadingAnalytics": "Laster inn analyser",
|
||||||
"restart": "Start på nytt",
|
"restart": "Start på nytt",
|
||||||
"domains": "Domener",
|
"domains": "Domener",
|
||||||
"domainsDescription": "Opprett og behandle domener som er tilgjengelige i organisasjonen",
|
"domainsDescription": "Opprett og behandle domener som er tilgjengelige i organisasjonen",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Klarte ikke å oppdatere data",
|
"refreshError": "Klarte ikke å oppdatere data",
|
||||||
"verified": "Verifisert",
|
"verified": "Verifisert",
|
||||||
"pending": "Venter",
|
"pending": "Venter",
|
||||||
|
"pendingApproval": "Venter på godkjenning",
|
||||||
"sidebarBilling": "Fakturering",
|
"sidebarBilling": "Fakturering",
|
||||||
"billing": "Fakturering",
|
"billing": "Fakturering",
|
||||||
"orgBillingDescription": "Administrer faktureringsinformasjon og abonnementer",
|
"orgBillingDescription": "Administrer faktureringsinformasjon og abonnementer",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Sikkerhetsnøkkel fjernet",
|
"securityKeyRemoveSuccess": "Sikkerhetsnøkkel fjernet",
|
||||||
"securityKeyRemoveError": "Klarte ikke å fjerne sikkerhetsnøkkel",
|
"securityKeyRemoveError": "Klarte ikke å fjerne sikkerhetsnøkkel",
|
||||||
"securityKeyLoadError": "Klarte ikke å laste inn sikkerhetsnøkler",
|
"securityKeyLoadError": "Klarte ikke å laste inn sikkerhetsnøkler",
|
||||||
"securityKeyLogin": "Fortsett med sikkerhetsnøkkel",
|
"securityKeyLogin": "Bruk sikkerhetsnøkkel",
|
||||||
"securityKeyAuthError": "Klarte ikke å autentisere med sikkerhetsnøkkel",
|
"securityKeyAuthError": "Klarte ikke å autentisere med sikkerhetsnøkkel",
|
||||||
"securityKeyRecommendation": "Registrer en reservesikkerhetsnøkkel på en annen enhet for å sikre at du alltid har tilgang til kontoen din.",
|
"securityKeyRecommendation": "Registrer en reservesikkerhetsnøkkel på en annen enhet for å sikre at du alltid har tilgang til kontoen din.",
|
||||||
"registering": "Registrerer...",
|
"registering": "Registrerer...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Sunt intervall",
|
"IntervalSeconds": "Sunt intervall",
|
||||||
"timeoutSeconds": "Tidsavbrudd (sek)",
|
"timeoutSeconds": "Tidsavbrudd (sek)",
|
||||||
"timeIsInSeconds": "Tid er i sekunder",
|
"timeIsInSeconds": "Tid er i sekunder",
|
||||||
|
"requireDeviceApproval": "Krev enhetsgodkjenning",
|
||||||
|
"requireDeviceApprovalDescription": "Brukere med denne rollen trenger nye enheter godkjent av en admin før de kan koble seg og få tilgang til ressurser.",
|
||||||
"retryAttempts": "Forsøk på nytt",
|
"retryAttempts": "Forsøk på nytt",
|
||||||
"expectedResponseCodes": "Forventede svarkoder",
|
"expectedResponseCodes": "Forventede svarkoder",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Ingen interne ressurser funnet.",
|
"resourcesTableNoInternalResourcesFound": "Ingen interne ressurser funnet.",
|
||||||
"resourcesTableDestination": "Destinasjon",
|
"resourcesTableDestination": "Destinasjon",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Alias adresse",
|
||||||
|
"resourcesTableAliasAddressInfo": "Denne adressen er en del av organisasjonens undernettverk. Den brukes til å løse aliasposter ved hjelp av intern DNS-oppløsning.",
|
||||||
"resourcesTableClients": "Klienter",
|
"resourcesTableClients": "Klienter",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "og er kun tilgjengelig internt når de er koblet til med en klient.",
|
"resourcesTableAndOnlyAccessibleInternally": "og er kun tilgjengelig internt når de er koblet til med en klient.",
|
||||||
"resourcesTableNoTargets": "Ingen mål",
|
"resourcesTableNoTargets": "Ingen mål",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Kode må inneholde 9 tegn (f.eks A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Kode må inneholde 9 tegn (f.eks A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Ugyldig eller utløpt kode",
|
"deviceCodeInvalidOrExpired": "Ugyldig eller utløpt kode",
|
||||||
"deviceCodeVerifyFailed": "Klarte ikke å bekrefte enhetskoden",
|
"deviceCodeVerifyFailed": "Klarte ikke å bekrefte enhetskoden",
|
||||||
|
"deviceCodeValidating": "Validerer enhetskode...",
|
||||||
|
"deviceCodeVerifying": "Bekrefter enhetens godkjennelse...",
|
||||||
"signedInAs": "Logget inn som",
|
"signedInAs": "Logget inn som",
|
||||||
"deviceCodeEnterPrompt": "Skriv inn koden som vises på enheten",
|
"deviceCodeEnterPrompt": "Skriv inn koden som vises på enheten",
|
||||||
"continue": "Fortsett",
|
"continue": "Fortsett",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Tilgang til alle organisasjoner din konto har tilgang til",
|
"deviceOrganizationsAccess": "Tilgang til alle organisasjoner din konto har tilgang til",
|
||||||
"deviceAuthorize": "Autoriser {applicationName}",
|
"deviceAuthorize": "Autoriser {applicationName}",
|
||||||
"deviceConnected": "Enhet tilkoblet!",
|
"deviceConnected": "Enhet tilkoblet!",
|
||||||
"deviceAuthorizedMessage": "Enhet er autorisert for tilgang til kontoen din.",
|
"deviceAuthorizedMessage": "Enheten er autorisert for tilgang til kontoen. Vennligst gå tilbake til klientapplikasjonen.",
|
||||||
"pangolinCloud": "Pangolin Sky",
|
"pangolinCloud": "Pangolin Sky",
|
||||||
"viewDevices": "Vis enheter",
|
"viewDevices": "Vis enheter",
|
||||||
"viewDevicesDescription": "Administrer tilkoblede enheter",
|
"viewDevicesDescription": "Administrer tilkoblede enheter",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
||||||
|
"loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.",
|
||||||
"noData": "Ingen data",
|
"noData": "Ingen data",
|
||||||
"machineClients": "Maskinklienter",
|
"machineClients": "Maskinklienter",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Tjenesten er midlertidig utilgjengelig",
|
"maintenanceScreenTitle": "Tjenesten er midlertidig utilgjengelig",
|
||||||
"maintenanceScreenMessage": "Vi opplever for øyeblikket tekniske problemer. Vennligst sjekk igjen snart.",
|
"maintenanceScreenMessage": "Vi opplever for øyeblikket tekniske problemer. Vennligst sjekk igjen snart.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Estimert ferdigstillelse:",
|
"maintenanceScreenEstimatedCompletion": "Estimert ferdigstillelse:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig"
|
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig",
|
||||||
|
"available": "Tilgjengelig",
|
||||||
|
"archived": "Arkivert",
|
||||||
|
"noArchivedDevices": "Ingen arkiverte enheter funnet",
|
||||||
|
"deviceArchived": "Enhet arkivert",
|
||||||
|
"deviceArchivedDescription": "Enheten er blitt arkivert.",
|
||||||
|
"errorArchivingDevice": "Feil ved arkivering av enhet",
|
||||||
|
"failedToArchiveDevice": "Kunne ikke arkivere enhet",
|
||||||
|
"deviceQuestionArchive": "Er du sikker på at du vil arkivere denne enheten?",
|
||||||
|
"deviceMessageArchive": "Enheten blir arkivert og fjernet fra listen over aktive enheter.",
|
||||||
|
"deviceArchiveConfirm": "Arkiver enhet",
|
||||||
|
"archiveDevice": "Arkiver enhet",
|
||||||
|
"archive": "Arkiv",
|
||||||
|
"deviceUnarchived": "Enheten er uarkivert",
|
||||||
|
"deviceUnarchivedDescription": "Enheten er blitt avarkivert.",
|
||||||
|
"errorUnarchivingDevice": "Feil ved arkivering av enhet",
|
||||||
|
"failedToUnarchiveDevice": "Kunne ikke fjerne arkivere enheten",
|
||||||
|
"unarchive": "Avarkiver",
|
||||||
|
"archiveClient": "Arkiver klient",
|
||||||
|
"archiveClientQuestion": "Er du sikker på at du vil arkivere denne klienten?",
|
||||||
|
"archiveClientMessage": "Klienten arkiveres og fjernes fra listen over aktive klienter.",
|
||||||
|
"archiveClientConfirm": "Arkiver klient",
|
||||||
|
"blockClient": "Blokker kunde",
|
||||||
|
"blockClientQuestion": "Er du sikker på at du vil blokkere denne klienten?",
|
||||||
|
"blockClientMessage": "Enheten blir tvunget til å koble fra hvis den er koblet til. Du kan fjerne blokkeringen av enheten senere.",
|
||||||
|
"blockClientConfirm": "Blokker kunde",
|
||||||
|
"active": "Aktiv",
|
||||||
|
"usernameOrEmail": "Brukernavn eller e-post",
|
||||||
|
"selectYourOrganization": "Velg din organisasjon",
|
||||||
|
"signInTo": "Logg inn på",
|
||||||
|
"signInWithPassword": "Fortsett med passord",
|
||||||
|
"noAuthMethodsAvailable": "Ingen autentiseringsmetoder er tilgjengelige for denne organisasjonen.",
|
||||||
|
"enterPassword": "Angi ditt passord",
|
||||||
|
"enterMfaCode": "Angi koden fra din autentiseringsapp",
|
||||||
|
"securityKeyRequired": "Vennligst bruk sikkerhetsnøkkelen til å logge på.",
|
||||||
|
"needToUseAnotherAccount": "Trenger du å bruke en annen konto?",
|
||||||
|
"loginLegalDisclaimer": "Ved å klikke på knappene nedenfor, erkjenner du at du har lest, forstår, og godtar <termsOfService>Vilkår for bruk</termsOfService> og <privacyPolicy>for Personvernerklæring</privacyPolicy>.",
|
||||||
|
"termsOfService": "Vilkår for bruk",
|
||||||
|
"privacyPolicy": "Retningslinjer for personvern",
|
||||||
|
"userNotFoundWithUsername": "Ingen bruker med det brukernavnet funnet.",
|
||||||
|
"verify": "Verifiser",
|
||||||
|
"signIn": "Logg inn",
|
||||||
|
"forgotPassword": "Glemt passord?",
|
||||||
|
"orgSignInTip": "Hvis du har logget inn før, kan du skrive inn brukernavnet eller e-postadressen ovenfor for å autentisere med organisasjonens identitetstjeneste i stedet. Det er enklere!",
|
||||||
|
"continueAnyway": "Fortsett likevel",
|
||||||
|
"dontShowAgain": "Ikke vis igjen",
|
||||||
|
"orgSignInNotice": "Visste du?",
|
||||||
|
"signupOrgNotice": "Prøver å logge inn?",
|
||||||
|
"signupOrgTip": "Prøver du å logge inn gjennom din organisasjons identitetsleverandør?",
|
||||||
|
"signupOrgLink": "Logg inn eller registrer deg med organisasjonen din i stedet",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Bruk en annen konto",
|
||||||
|
"logIn": "Logg inn",
|
||||||
|
"deviceInformation": "Enhetens informasjon",
|
||||||
|
"deviceInformationDescription": "Informasjon om enheten og agenten",
|
||||||
|
"deviceSecurity": "Enhetens sikkerhet",
|
||||||
|
"deviceSecurityDescription": "Sikkerhetsstillings informasjon om utstyr",
|
||||||
|
"platform": "Plattform",
|
||||||
|
"macosVersion": "macOS versjon",
|
||||||
|
"windowsVersion": "Windows versjon",
|
||||||
|
"iosVersion": "iOS Versjon",
|
||||||
|
"androidVersion": "Android versjon",
|
||||||
|
"osVersion": "OS versjon",
|
||||||
|
"kernelVersion": "Kjerne versjon",
|
||||||
|
"deviceModel": "Enhets modell",
|
||||||
|
"serialNumber": "Serienummer",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Først sett",
|
||||||
|
"lastSeen": "Sist sett",
|
||||||
|
"biometricsEnabled": "Biometri aktivert",
|
||||||
|
"diskEncrypted": "Disk kryptert",
|
||||||
|
"firewallEnabled": "Brannmur aktivert",
|
||||||
|
"autoUpdatesEnabled": "Automatiske oppdateringer aktivert",
|
||||||
|
"tpmAvailable": "TPM tilgjengelig",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "System Integritetsbeskyttelse (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Brannmur Usynlig Modus",
|
||||||
|
"linuxAppArmorEnabled": "Rustning",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Vis enhetsinformasjon og innstillinger",
|
||||||
|
"devicePendingApprovalDescription": "Denne enheten venter på godkjenning",
|
||||||
|
"deviceBlockedDescription": "Denne enheten er blokkert. Det kan ikke kobles til noen ressurser med mindre de ikke blir blokkert.",
|
||||||
|
"unblockClient": "Avblokker klient",
|
||||||
|
"unblockClientDescription": "Enheten har blitt blokkert",
|
||||||
|
"unarchiveClient": "Fjern arkivering klient",
|
||||||
|
"unarchiveClientDescription": "Enheten er arkivert",
|
||||||
|
"block": "Blokker",
|
||||||
|
"unblock": "Avblokker",
|
||||||
|
"deviceActions": "Enhetens handlinger",
|
||||||
|
"deviceActionsDescription": "Administrer enhetsstatus og tilgang",
|
||||||
|
"devicePendingApprovalBannerDescription": "Denne enheten venter på godkjenning. Den kan ikke koble til ressurser før den er godkjent.",
|
||||||
|
"connected": "Tilkoblet",
|
||||||
|
"disconnected": "Frakoblet",
|
||||||
|
"approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert",
|
||||||
|
"approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Gå til roller",
|
||||||
|
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
||||||
|
"approvalsEmptyStateStep2Description": "Rediger en rolle og aktiver alternativet 'Kreve enhetsgodkjenninger'. Brukere med denne rollen vil trenge administratorgodkjenning for nye enheter.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Forhåndsvisning: Når aktivert, ventende enhets forespørsler vil vises her for vurdering",
|
||||||
|
"approvalsEmptyStateButtonText": "Administrer Roller"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Verbind elk netwerk",
|
"sitesBannerTitle": "Verbind elk netwerk",
|
||||||
"sitesBannerDescription": "Een site is een verbinding met een extern netwerk waarmee Pangolin toegang biedt tot bronnen, zowel openbaar als privé, aan gebruikers overal. Installeer de sitedatacenterconnector (Newt) overal waar je een binaire of container kunt uitvoeren om de verbinding tot stand te brengen.",
|
"sitesBannerDescription": "Een site is een verbinding met een extern netwerk waarmee Pangolin toegang biedt tot bronnen, zowel openbaar als privé, aan gebruikers overal. Installeer de sitedatacenterconnector (Newt) overal waar je een binaire of container kunt uitvoeren om de verbinding tot stand te brengen.",
|
||||||
"sitesBannerButtonText": "Site installeren",
|
"sitesBannerButtonText": "Site installeren",
|
||||||
|
"approvalsBannerTitle": "Toegang tot het apparaat goedkeuren of weigeren",
|
||||||
|
"approvalsBannerDescription": "Bekijk en keur toestelverzoeken goed of weiger toegang van gebruikers. Wanneer apparaatgoedkeuringen vereist zijn, moeten gebruikers de goedkeuring van beheerders krijgen voordat hun apparaten verbinding kunnen maken met de bronnen van uw organisatie.",
|
||||||
|
"approvalsBannerButtonText": "Meer informatie",
|
||||||
"siteCreate": "Site maken",
|
"siteCreate": "Site maken",
|
||||||
"siteCreateDescription2": "Volg de onderstaande stappen om een nieuwe site aan te maken en te verbinden",
|
"siteCreateDescription2": "Volg de onderstaande stappen om een nieuwe site aan te maken en te verbinden",
|
||||||
"siteCreateDescription": "Maak een nieuwe site aan om bronnen te verbinden",
|
"siteCreateDescription": "Maak een nieuwe site aan om bronnen te verbinden",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Rollen zoeken...",
|
"accessRolesSearch": "Rollen zoeken...",
|
||||||
"accessRolesAdd": "Rol toevoegen",
|
"accessRolesAdd": "Rol toevoegen",
|
||||||
"accessRoleDelete": "Verwijder rol",
|
"accessRoleDelete": "Verwijder rol",
|
||||||
|
"accessApprovalsManage": "Goedkeuringen beheren",
|
||||||
|
"accessApprovalsDescription": "Bekijk en beheer openstaande goedkeuringen voor toegang tot deze organisatie",
|
||||||
"description": "Beschrijving",
|
"description": "Beschrijving",
|
||||||
"inviteTitle": "Open uitnodigingen",
|
"inviteTitle": "Open uitnodigingen",
|
||||||
"inviteDescription": "Beheer uitnodigingen voor andere gebruikers om deel te nemen aan de organisatie",
|
"inviteDescription": "Beheer uitnodigingen voor andere gebruikers om deel te nemen aan de organisatie",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Selecteer duur",
|
"selectDuration": "Selecteer duur",
|
||||||
"selectResource": "Selecteer Document",
|
"selectResource": "Selecteer Document",
|
||||||
"filterByResource": "Filter op pagina",
|
"filterByResource": "Filter op pagina",
|
||||||
|
"selectApprovalState": "Selecteer goedkeuringsstatus",
|
||||||
|
"filterByApprovalState": "Filter op goedkeuringsstatus",
|
||||||
|
"approvalListEmpty": "Geen goedkeuringen",
|
||||||
|
"approvalState": "Goedkeuring status",
|
||||||
|
"approve": "Goedkeuren",
|
||||||
|
"approved": "Goedgekeurd",
|
||||||
|
"denied": "Geweigerd",
|
||||||
|
"deniedApproval": "Geweigerde goedkeuring",
|
||||||
|
"all": "Alles",
|
||||||
|
"deny": "Weigeren",
|
||||||
|
"viewDetails": "Details bekijken",
|
||||||
|
"requestingNewDeviceApproval": "heeft een nieuw apparaat aangevraagd",
|
||||||
"resetFilters": "Filters resetten",
|
"resetFilters": "Filters resetten",
|
||||||
"totalBlocked": "Verzoeken geblokkeerd door Pangolin",
|
"totalBlocked": "Verzoeken geblokkeerd door Pangolin",
|
||||||
"totalRequests": "Totaal verzoeken",
|
"totalRequests": "Totaal verzoeken",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Landen",
|
"countries": "Landen",
|
||||||
"accessRoleCreate": "Rol aanmaken",
|
"accessRoleCreate": "Rol aanmaken",
|
||||||
"accessRoleCreateDescription": "Maak een nieuwe rol aan om gebruikers te groeperen en hun rechten te beheren.",
|
"accessRoleCreateDescription": "Maak een nieuwe rol aan om gebruikers te groeperen en hun rechten te beheren.",
|
||||||
|
"accessRoleEdit": "Rol bewerken",
|
||||||
|
"accessRoleEditDescription": "Bewerk rol informatie.",
|
||||||
"accessRoleCreateSubmit": "Rol aanmaken",
|
"accessRoleCreateSubmit": "Rol aanmaken",
|
||||||
"accessRoleCreated": "Rol aangemaakt",
|
"accessRoleCreated": "Rol aangemaakt",
|
||||||
"accessRoleCreatedDescription": "De rol is succesvol aangemaakt.",
|
"accessRoleCreatedDescription": "De rol is succesvol aangemaakt.",
|
||||||
"accessRoleErrorCreate": "Rol aanmaken mislukt",
|
"accessRoleErrorCreate": "Rol aanmaken mislukt",
|
||||||
"accessRoleErrorCreateDescription": "Fout opgetreden tijdens het aanmaken van de rol.",
|
"accessRoleErrorCreateDescription": "Fout opgetreden tijdens het aanmaken van de rol.",
|
||||||
|
"accessRoleUpdateSubmit": "Rol bijwerken",
|
||||||
|
"accessRoleUpdated": "Rol bijgewerkt",
|
||||||
|
"accessRoleUpdatedDescription": "De rol is succesvol bijgewerkt.",
|
||||||
|
"accessApprovalUpdated": "Afgewerkt met goedkeuring",
|
||||||
|
"accessApprovalApprovedDescription": "Stel het goedkeuringsverzoek in op goedkeuring.",
|
||||||
|
"accessApprovalDeniedDescription": "Stel de beslissing over het goedkeuringsverzoek in als geweigerd.",
|
||||||
|
"accessRoleErrorUpdate": "Bijwerken van rol mislukt",
|
||||||
|
"accessRoleErrorUpdateDescription": "Fout opgetreden tijdens het bijwerken van de rol.",
|
||||||
|
"accessApprovalErrorUpdate": "Kan goedkeuring niet verwerken",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Er is een fout opgetreden bij het verwerken van de goedkeuring.",
|
||||||
"accessRoleErrorNewRequired": "Nieuwe rol is vereist",
|
"accessRoleErrorNewRequired": "Nieuwe rol is vereist",
|
||||||
"accessRoleErrorRemove": "Rol verwijderen mislukt",
|
"accessRoleErrorRemove": "Rol verwijderen mislukt",
|
||||||
"accessRoleErrorRemoveDescription": "Er is een fout opgetreden tijdens het verwijderen van de rol.",
|
"accessRoleErrorRemoveDescription": "Er is een fout opgetreden tijdens het verwijderen van de rol.",
|
||||||
"accessRoleName": "Rol naam",
|
"accessRoleName": "Rol naam",
|
||||||
"accessRoleQuestionRemove": "U staat op het punt de {name} rol te verwijderen. U kunt deze actie niet ongedaan maken.",
|
"accessRoleQuestionRemove": "Je staat op het punt de `{name}` rol te verwijderen. Je kunt deze actie niet ongedaan maken.",
|
||||||
"accessRoleRemove": "Rol verwijderen",
|
"accessRoleRemove": "Rol verwijderen",
|
||||||
"accessRoleRemoveDescription": "Verwijder een rol van de organisatie",
|
"accessRoleRemoveDescription": "Verwijder een rol van de organisatie",
|
||||||
"accessRoleRemoveSubmit": "Rol verwijderen",
|
"accessRoleRemoveSubmit": "Rol verwijderen",
|
||||||
@@ -874,7 +903,7 @@
|
|||||||
"inviteAlready": "Het lijkt erop dat je bent uitgenodigd!",
|
"inviteAlready": "Het lijkt erop dat je bent uitgenodigd!",
|
||||||
"inviteAlreadyDescription": "Om de uitnodiging te accepteren, moet je inloggen of een account aanmaken.",
|
"inviteAlreadyDescription": "Om de uitnodiging te accepteren, moet je inloggen of een account aanmaken.",
|
||||||
"signupQuestion": "Heeft u al een account?",
|
"signupQuestion": "Heeft u al een account?",
|
||||||
"login": "Inloggen",
|
"login": "Log in",
|
||||||
"resourceNotFound": "Bron niet gevonden",
|
"resourceNotFound": "Bron niet gevonden",
|
||||||
"resourceNotFoundDescription": "De bron die u probeert te benaderen bestaat niet.",
|
"resourceNotFoundDescription": "De bron die u probeert te benaderen bestaat niet.",
|
||||||
"pincodeRequirementsLength": "Pincode moet precies 6 cijfers zijn",
|
"pincodeRequirementsLength": "Pincode moet precies 6 cijfers zijn",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "Neem contact op met uw beheerder",
|
"passwordResetSmtpRequired": "Neem contact op met uw beheerder",
|
||||||
"passwordResetSmtpRequiredDescription": "Er is een wachtwoord reset code nodig om uw wachtwoord opnieuw in te stellen. Neem contact op met uw beheerder voor hulp.",
|
"passwordResetSmtpRequiredDescription": "Er is een wachtwoord reset code nodig om uw wachtwoord opnieuw in te stellen. Neem contact op met uw beheerder voor hulp.",
|
||||||
"passwordBack": "Terug naar wachtwoord",
|
"passwordBack": "Terug naar wachtwoord",
|
||||||
"loginBack": "Ga terug naar login",
|
"loginBack": "Ga terug naar de hoofdinlogpagina",
|
||||||
"signup": "Registreer nu",
|
"signup": "Registreer nu",
|
||||||
"loginStart": "Log in om te beginnen",
|
"loginStart": "Log in om te beginnen",
|
||||||
"idpOidcTokenValidating": "Valideer OIDC-token",
|
"idpOidcTokenValidating": "Valideer OIDC-token",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "IDP-org bijwerken",
|
"actionUpdateIdpOrg": "IDP-org bijwerken",
|
||||||
"actionCreateClient": "Client aanmaken",
|
"actionCreateClient": "Client aanmaken",
|
||||||
"actionDeleteClient": "Verwijder klant",
|
"actionDeleteClient": "Verwijder klant",
|
||||||
|
"actionArchiveClient": "Archiveer client",
|
||||||
|
"actionUnarchiveClient": "Dearchiveer client",
|
||||||
|
"actionBlockClient": "Blokkeer klant",
|
||||||
|
"actionUnblockClient": "Deblokkeer client",
|
||||||
"actionUpdateClient": "Klant bijwerken",
|
"actionUpdateClient": "Klant bijwerken",
|
||||||
"actionListClients": "Lijst klanten",
|
"actionListClients": "Lijst klanten",
|
||||||
"actionGetClient": "Client ophalen",
|
"actionGetClient": "Client ophalen",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Zoeken...",
|
"searchProgress": "Zoeken...",
|
||||||
"create": "Aanmaken",
|
"create": "Aanmaken",
|
||||||
"orgs": "Organisaties",
|
"orgs": "Organisaties",
|
||||||
"loginError": "Er is een fout opgetreden tijdens het inloggen",
|
"loginError": "Er is een onverwachte fout opgetreden. Probeer het opnieuw.",
|
||||||
"loginRequiredForDevice": "Inloggen is vereist om je apparaat te verifiëren.",
|
"loginRequiredForDevice": "Inloggen is vereist voor je apparaat.",
|
||||||
"passwordForgot": "Wachtwoord vergeten?",
|
"passwordForgot": "Wachtwoord vergeten?",
|
||||||
"otpAuth": "Tweestapsverificatie verificatie",
|
"otpAuth": "Tweestapsverificatie verificatie",
|
||||||
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
|
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
|
||||||
"otpAuthSubmit": "Code indienen",
|
"otpAuthSubmit": "Code indienen",
|
||||||
"idpContinue": "Of ga verder met",
|
"idpContinue": "Of ga verder met",
|
||||||
"otpAuthBack": "Terug naar inloggen",
|
"otpAuthBack": "Terug naar wachtwoord",
|
||||||
"navbar": "Navigatiemenu",
|
"navbar": "Navigatiemenu",
|
||||||
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
|
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
|
||||||
"navbarDocsLink": "Documentatie",
|
"navbarDocsLink": "Documentatie",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Overzicht.",
|
"sidebarOverview": "Overzicht.",
|
||||||
"sidebarHome": "Startpagina",
|
"sidebarHome": "Startpagina",
|
||||||
"sidebarSites": "Werkruimtes",
|
"sidebarSites": "Werkruimtes",
|
||||||
|
"sidebarApprovals": "Goedkeuringsverzoeken",
|
||||||
"sidebarResources": "Bronnen",
|
"sidebarResources": "Bronnen",
|
||||||
"sidebarProxyResources": "Openbaar",
|
"sidebarProxyResources": "Openbaar",
|
||||||
"sidebarClientResources": "Privé",
|
"sidebarClientResources": "Privé",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Identiteit aanbieders",
|
"sidebarIdentityProviders": "Identiteit aanbieders",
|
||||||
"sidebarLicense": "Licentie",
|
"sidebarLicense": "Licentie",
|
||||||
"sidebarClients": "Clienten",
|
"sidebarClients": "Clienten",
|
||||||
"sidebarUserDevices": "Gebruikers",
|
"sidebarUserDevices": "Gebruiker Apparaten",
|
||||||
"sidebarMachineClients": "Machines",
|
"sidebarMachineClients": "Machines",
|
||||||
"sidebarDomains": "Domeinen",
|
"sidebarDomains": "Domeinen",
|
||||||
"sidebarGeneral": "Beheren",
|
"sidebarGeneral": "Beheren",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Er is een fout opgetreden bij het maken van het serverbeheerdersaccount.",
|
"setupErrorCreateAdmin": "Er is een fout opgetreden bij het maken van het serverbeheerdersaccount.",
|
||||||
"certificateStatus": "Certificaatstatus",
|
"certificateStatus": "Certificaatstatus",
|
||||||
"loading": "Bezig met laden",
|
"loading": "Bezig met laden",
|
||||||
|
"loadingAnalytics": "Laden van Analytics",
|
||||||
"restart": "Herstarten",
|
"restart": "Herstarten",
|
||||||
"domains": "Domeinen",
|
"domains": "Domeinen",
|
||||||
"domainsDescription": "Maak en beheer domeinen die beschikbaar zijn in de organisatie",
|
"domainsDescription": "Maak en beheer domeinen die beschikbaar zijn in de organisatie",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Het vernieuwen van gegevens is mislukt",
|
"refreshError": "Het vernieuwen van gegevens is mislukt",
|
||||||
"verified": "Gecontroleerd",
|
"verified": "Gecontroleerd",
|
||||||
"pending": "In afwachting",
|
"pending": "In afwachting",
|
||||||
|
"pendingApproval": "Wachten op goedkeuring",
|
||||||
"sidebarBilling": "Facturering",
|
"sidebarBilling": "Facturering",
|
||||||
"billing": "Facturering",
|
"billing": "Facturering",
|
||||||
"orgBillingDescription": "Beheer factureringsinformatie en abonnementen",
|
"orgBillingDescription": "Beheer factureringsinformatie en abonnementen",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Beveiligingssleutel succesvol verwijderd",
|
"securityKeyRemoveSuccess": "Beveiligingssleutel succesvol verwijderd",
|
||||||
"securityKeyRemoveError": "Fout bij verwijderen van beveiligingssleutel",
|
"securityKeyRemoveError": "Fout bij verwijderen van beveiligingssleutel",
|
||||||
"securityKeyLoadError": "Fout bij laden van beveiligingssleutels",
|
"securityKeyLoadError": "Fout bij laden van beveiligingssleutels",
|
||||||
"securityKeyLogin": "Doorgaan met beveiligingssleutel",
|
"securityKeyLogin": "Gebruik beveiligingssleutel",
|
||||||
"securityKeyAuthError": "Fout bij authenticatie met beveiligingssleutel",
|
"securityKeyAuthError": "Fout bij authenticatie met beveiligingssleutel",
|
||||||
"securityKeyRecommendation": "Overweeg om een andere beveiligingssleutel te registreren op een ander apparaat om ervoor te zorgen dat u niet buitengesloten raakt van uw account.",
|
"securityKeyRecommendation": "Overweeg om een andere beveiligingssleutel te registreren op een ander apparaat om ervoor te zorgen dat u niet buitengesloten raakt van uw account.",
|
||||||
"registering": "Registreren...",
|
"registering": "Registreren...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Gezonde Interval",
|
"IntervalSeconds": "Gezonde Interval",
|
||||||
"timeoutSeconds": "Timeout (sec)",
|
"timeoutSeconds": "Timeout (sec)",
|
||||||
"timeIsInSeconds": "Tijd is in seconden",
|
"timeIsInSeconds": "Tijd is in seconden",
|
||||||
|
"requireDeviceApproval": "Vereist goedkeuring van apparaat",
|
||||||
|
"requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
|
||||||
"retryAttempts": "Herhaal Pogingen",
|
"retryAttempts": "Herhaal Pogingen",
|
||||||
"expectedResponseCodes": "Verwachte Reactiecodes",
|
"expectedResponseCodes": "Verwachte Reactiecodes",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Geen interne bronnen gevonden.",
|
"resourcesTableNoInternalResourcesFound": "Geen interne bronnen gevonden.",
|
||||||
"resourcesTableDestination": "Bestemming",
|
"resourcesTableDestination": "Bestemming",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Alias adres",
|
||||||
|
"resourcesTableAliasAddressInfo": "Dit adres is onderdeel van het hulpprogramma subnet van de organisatie. Het wordt gebruikt om aliasrecords op te lossen met behulp van interne DNS-resolutie.",
|
||||||
"resourcesTableClients": "Clienten",
|
"resourcesTableClients": "Clienten",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "en zijn alleen intern toegankelijk wanneer verbonden met een client.",
|
"resourcesTableAndOnlyAccessibleInternally": "en zijn alleen intern toegankelijk wanneer verbonden met een client.",
|
||||||
"resourcesTableNoTargets": "Geen doelen",
|
"resourcesTableNoTargets": "Geen doelen",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Kies uw identiteitsprovider om door te gaan",
|
"orgAuthChooseIdpDescription": "Kies uw identiteitsprovider om door te gaan",
|
||||||
"orgAuthNoIdpConfigured": "Deze organisatie heeft geen identiteitsproviders geconfigureerd. Je kunt in plaats daarvan inloggen met je Pangolin-identiteit.",
|
"orgAuthNoIdpConfigured": "Deze organisatie heeft geen identiteitsproviders geconfigureerd. Je kunt in plaats daarvan inloggen met je Pangolin-identiteit.",
|
||||||
"orgAuthSignInWithPangolin": "Log in met Pangolin",
|
"orgAuthSignInWithPangolin": "Log in met Pangolin",
|
||||||
"orgAuthSignInToOrg": "Meld u aan bij een organisatie",
|
"orgAuthSignInToOrg": "Log in bij een organisatie",
|
||||||
"orgAuthSelectOrgTitle": "Organisatie Inloggen",
|
"orgAuthSelectOrgTitle": "Organisatie Inloggen",
|
||||||
"orgAuthSelectOrgDescription": "Voer je organisatie-ID in om verder te gaan",
|
"orgAuthSelectOrgDescription": "Voer je organisatie-ID in om verder te gaan",
|
||||||
"orgAuthOrgIdPlaceholder": "jouw-organisatie",
|
"orgAuthOrgIdPlaceholder": "jouw-organisatie",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Code moet 9 tekens bevatten (bijv. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Code moet 9 tekens bevatten (bijv. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Ongeldige of verlopen code",
|
"deviceCodeInvalidOrExpired": "Ongeldige of verlopen code",
|
||||||
"deviceCodeVerifyFailed": "Apparaatcode verifiëren mislukt",
|
"deviceCodeVerifyFailed": "Apparaatcode verifiëren mislukt",
|
||||||
|
"deviceCodeValidating": "Apparaatcode valideren...",
|
||||||
|
"deviceCodeVerifying": "Apparaatmachtiging verifiëren...",
|
||||||
"signedInAs": "Ingelogd als",
|
"signedInAs": "Ingelogd als",
|
||||||
"deviceCodeEnterPrompt": "Voer de op het apparaat weergegeven code in",
|
"deviceCodeEnterPrompt": "Voer de op het apparaat weergegeven code in",
|
||||||
"continue": "Doorgaan",
|
"continue": "Doorgaan",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Toegang tot alle organisaties waar uw account toegang tot heeft",
|
"deviceOrganizationsAccess": "Toegang tot alle organisaties waar uw account toegang tot heeft",
|
||||||
"deviceAuthorize": "Autoriseer {applicationName}",
|
"deviceAuthorize": "Autoriseer {applicationName}",
|
||||||
"deviceConnected": "Apparaat verbonden!",
|
"deviceConnected": "Apparaat verbonden!",
|
||||||
"deviceAuthorizedMessage": "Apparaat is gemachtigd om toegang te krijgen tot je account.",
|
"deviceAuthorizedMessage": "Apparaat is gemachtigd om toegang te krijgen tot je account. Ga terug naar de client applicatie.",
|
||||||
"pangolinCloud": "Pangoline Cloud",
|
"pangolinCloud": "Pangoline Cloud",
|
||||||
"viewDevices": "Bekijk apparaten",
|
"viewDevices": "Bekijk apparaten",
|
||||||
"viewDevicesDescription": "Beheer uw aangesloten apparaten",
|
"viewDevicesDescription": "Beheer uw aangesloten apparaten",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Niet u? Gebruik een ander account.",
|
"deviceLoginUseDifferentAccount": "Niet u? Gebruik een ander account.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Een apparaat vraagt om toegang tot dit account.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Een apparaat vraagt om toegang tot dit account.",
|
||||||
|
"loginSelectAuthenticationMethod": "Selecteer een verificatiemethode om door te gaan.",
|
||||||
"noData": "Geen gegevens",
|
"noData": "Geen gegevens",
|
||||||
"machineClients": "Machine Clienten",
|
"machineClients": "Machine Clienten",
|
||||||
"install": "Installeren",
|
"install": "Installeren",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Dienst tijdelijk niet beschikbaar",
|
"maintenanceScreenTitle": "Dienst tijdelijk niet beschikbaar",
|
||||||
"maintenanceScreenMessage": "We hebben momenteel technische problemen. Probeer het later opnieuw.",
|
"maintenanceScreenMessage": "We hebben momenteel technische problemen. Probeer het later opnieuw.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Geschatte voltooiing:",
|
"maintenanceScreenEstimatedCompletion": "Geschatte voltooiing:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist"
|
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist",
|
||||||
|
"available": "Beschikbaar",
|
||||||
|
"archived": "Gearchiveerd",
|
||||||
|
"noArchivedDevices": "Geen gearchiveerde apparaten gevonden",
|
||||||
|
"deviceArchived": "Apparaat gearchiveerd",
|
||||||
|
"deviceArchivedDescription": "Het apparaat is met succes gearchiveerd.",
|
||||||
|
"errorArchivingDevice": "Fout bij archiveren apparaat",
|
||||||
|
"failedToArchiveDevice": "Kan apparaat niet archiveren",
|
||||||
|
"deviceQuestionArchive": "Weet u zeker dat u dit apparaat wilt archiveren?",
|
||||||
|
"deviceMessageArchive": "Het apparaat wordt gearchiveerd en verwijderd uit de lijst met actieve apparaten.",
|
||||||
|
"deviceArchiveConfirm": "Archiveer apparaat",
|
||||||
|
"archiveDevice": "Archiveer apparaat",
|
||||||
|
"archive": "Archief",
|
||||||
|
"deviceUnarchived": "Apparaat niet gearchiveerd",
|
||||||
|
"deviceUnarchivedDescription": "Het apparaat is met succes gedearchiveerd.",
|
||||||
|
"errorUnarchivingDevice": "Fout bij dearchiveren van apparaat",
|
||||||
|
"failedToUnarchiveDevice": "Apparaat dearchiveren mislukt",
|
||||||
|
"unarchive": "Dearchiveren",
|
||||||
|
"archiveClient": "Archiveer client",
|
||||||
|
"archiveClientQuestion": "Weet u zeker dat u deze client wilt archiveren?",
|
||||||
|
"archiveClientMessage": "De klant zal worden gearchiveerd en verwijderd uit de lijst met actieve cliënten.",
|
||||||
|
"archiveClientConfirm": "Archiveer client",
|
||||||
|
"blockClient": "Blokkeer klant",
|
||||||
|
"blockClientQuestion": "Weet u zeker dat u deze cliënt wilt blokkeren?",
|
||||||
|
"blockClientMessage": "Het apparaat zal worden gedwongen de verbinding te verbreken als het momenteel is verbonden. U kunt het apparaat later deblokkeren.",
|
||||||
|
"blockClientConfirm": "Blokkeer klant",
|
||||||
|
"active": "actief",
|
||||||
|
"usernameOrEmail": "Gebruikersnaam of e-mailadres",
|
||||||
|
"selectYourOrganization": "Selecteer uw organisatie",
|
||||||
|
"signInTo": "Log in op",
|
||||||
|
"signInWithPassword": "Ga verder met wachtwoord",
|
||||||
|
"noAuthMethodsAvailable": "Geen verificatiemethoden beschikbaar voor deze organisatie.",
|
||||||
|
"enterPassword": "Voer je wachtwoord in",
|
||||||
|
"enterMfaCode": "Voer de code van je authenticator-app in",
|
||||||
|
"securityKeyRequired": "Gebruik uw beveiligingssleutel om in te loggen.",
|
||||||
|
"needToUseAnotherAccount": "Wilt u een ander account gebruiken?",
|
||||||
|
"loginLegalDisclaimer": "Door op de knoppen hieronder te klikken, erken je dat je gelezen en begrepen hebt en ga akkoord met de <termsOfService>Gebruiksvoorwaarden</termsOfService> en <privacyPolicy>Privacybeleid</privacyPolicy>.",
|
||||||
|
"termsOfService": "Algemene gebruiksvoorwaarden",
|
||||||
|
"privacyPolicy": "Privacy Beleid",
|
||||||
|
"userNotFoundWithUsername": "Geen gebruiker gevonden met die gebruikersnaam.",
|
||||||
|
"verify": "Verifiëren",
|
||||||
|
"signIn": "Log in",
|
||||||
|
"forgotPassword": "Wachtwoord vergeten?",
|
||||||
|
"orgSignInTip": "Als u eerder bent ingelogd, kunt u uw gebruikersnaam of e-mail hierboven invoeren om in plaats daarvan te verifiëren met de identiteitsprovider van uw organisatie! Het is makkelijk!",
|
||||||
|
"continueAnyway": "Toch doorgaan",
|
||||||
|
"dontShowAgain": "Niet meer weergeven",
|
||||||
|
"orgSignInNotice": "Wist u dat?",
|
||||||
|
"signupOrgNotice": "Proberen je aan te melden?",
|
||||||
|
"signupOrgTip": "Probeert u zich aan te melden via de identiteitsprovider van uw organisatie?",
|
||||||
|
"signupOrgLink": "Log in of meld je aan bij je organisatie",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Gebruik een ander account",
|
||||||
|
"logIn": "Log in",
|
||||||
|
"deviceInformation": "Apparaat informatie",
|
||||||
|
"deviceInformationDescription": "Informatie over het apparaat en de agent",
|
||||||
|
"deviceSecurity": "Apparaat beveiliging",
|
||||||
|
"deviceSecurityDescription": "Apparaat beveiligingsinformatie",
|
||||||
|
"platform": "Platform",
|
||||||
|
"macosVersion": "macOS versie",
|
||||||
|
"windowsVersion": "Windows versie",
|
||||||
|
"iosVersion": "iOS versie",
|
||||||
|
"androidVersion": "Android versie",
|
||||||
|
"osVersion": "OS versie",
|
||||||
|
"kernelVersion": "Kernel versie",
|
||||||
|
"deviceModel": "Apparaat model",
|
||||||
|
"serialNumber": "Serienummer",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Eerst gezien",
|
||||||
|
"lastSeen": "Laatst gezien op",
|
||||||
|
"biometricsEnabled": "Biometrie ingeschakeld",
|
||||||
|
"diskEncrypted": "Schijf versleuteld",
|
||||||
|
"firewallEnabled": "Firewall ingeschakeld",
|
||||||
|
"autoUpdatesEnabled": "Auto Updates Ingeschakeld",
|
||||||
|
"tpmAvailable": "TPM beschikbaar",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Systeemintegriteitsbescherming (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Firewall Verberg Modus",
|
||||||
|
"linuxAppArmorEnabled": "Appharnas",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Apparaatinformatie en -instellingen bekijken",
|
||||||
|
"devicePendingApprovalDescription": "Dit apparaat wacht op goedkeuring",
|
||||||
|
"deviceBlockedDescription": "Dit apparaat is momenteel geblokkeerd. Het kan geen verbinding maken met bronnen tenzij het wordt gedeblokkeerd.",
|
||||||
|
"unblockClient": "Deblokkeer client",
|
||||||
|
"unblockClientDescription": "Het apparaat is gedeblokkeerd",
|
||||||
|
"unarchiveClient": "Dearchiveer client",
|
||||||
|
"unarchiveClientDescription": "Het apparaat is gedearchiveerd",
|
||||||
|
"block": "Blokkeren",
|
||||||
|
"unblock": "Deblokkeer",
|
||||||
|
"deviceActions": "Apparaat Acties",
|
||||||
|
"deviceActionsDescription": "Apparaatstatus en toegang beheren",
|
||||||
|
"devicePendingApprovalBannerDescription": "Dit apparaat wacht op goedkeuring. Het zal niet in staat zijn verbinding te maken met bronnen totdat het is goedgekeurd.",
|
||||||
|
"connected": "Verbonden",
|
||||||
|
"disconnected": "Losgekoppeld",
|
||||||
|
"approvalsEmptyStateTitle": "Apparaat goedkeuringen niet ingeschakeld",
|
||||||
|
"approvalsEmptyStateDescription": "Apparaatgoedkeuringen voor rollen inschakelen om goedkeuring van de beheerder te vereisen voordat gebruikers nieuwe apparaten kunnen koppelen.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Ga naar rollen",
|
||||||
|
"approvalsEmptyStateStep1Description": "Navigeer naar de rolinstellingen van uw organisatie om apparaatgoedkeuringen te configureren.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
|
||||||
|
"approvalsEmptyStateStep2Description": "Bewerk een rol en schakel de optie 'Vereist Apparaat Goedkeuringen' in. Gebruikers met deze rol hebben admin goedkeuring nodig voor nieuwe apparaten.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Voorbeeld: Indien ingeschakeld, zullen in afwachting van apparaatverzoeken hier verschijnen om te beoordelen",
|
||||||
|
"approvalsEmptyStateButtonText": "Rollen beheren"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Połącz dowolną sieć",
|
"sitesBannerTitle": "Połącz dowolną sieć",
|
||||||
"sitesBannerDescription": "Witryna to połączenie z siecią zdalną, które umożliwia Pangolinowi zapewnienie dostępu do zasobów, publicznych lub prywatnych, użytkownikom w dowolnym miejscu. Zainstaluj łącznik sieci w witrynie (Newt) w dowolnym miejscu, w którym możesz uruchomić binarkę lub kontener, aby ustanowić połączenie.",
|
"sitesBannerDescription": "Witryna to połączenie z siecią zdalną, które umożliwia Pangolinowi zapewnienie dostępu do zasobów, publicznych lub prywatnych, użytkownikom w dowolnym miejscu. Zainstaluj łącznik sieci w witrynie (Newt) w dowolnym miejscu, w którym możesz uruchomić binarkę lub kontener, aby ustanowić połączenie.",
|
||||||
"sitesBannerButtonText": "Zainstaluj witrynę",
|
"sitesBannerButtonText": "Zainstaluj witrynę",
|
||||||
|
"approvalsBannerTitle": "Zatwierdź lub odmów dostępu do urządzenia",
|
||||||
|
"approvalsBannerDescription": "Przejrzyj i zatwierdzaj lub odmawiaj użytkownikom dostępu do urządzenia. Gdy wymagane jest zatwierdzenie urządzenia, użytkownicy muszą uzyskać zatwierdzenie administratora, zanim ich urządzenia będą mogły połączyć się z zasobami Twojej organizacji.",
|
||||||
|
"approvalsBannerButtonText": "Dowiedz się więcej",
|
||||||
"siteCreate": "Utwórz witrynę",
|
"siteCreate": "Utwórz witrynę",
|
||||||
"siteCreateDescription2": "Wykonaj poniższe kroki, aby utworzyć i połączyć nową witrynę",
|
"siteCreateDescription2": "Wykonaj poniższe kroki, aby utworzyć i połączyć nową witrynę",
|
||||||
"siteCreateDescription": "Utwórz nową witrynę, aby rozpocząć łączenie zasobów",
|
"siteCreateDescription": "Utwórz nową witrynę, aby rozpocząć łączenie zasobów",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Szukaj ról...",
|
"accessRolesSearch": "Szukaj ról...",
|
||||||
"accessRolesAdd": "Dodaj rolę",
|
"accessRolesAdd": "Dodaj rolę",
|
||||||
"accessRoleDelete": "Usuń rolę",
|
"accessRoleDelete": "Usuń rolę",
|
||||||
|
"accessApprovalsManage": "Zarządzaj zatwierdzaniem",
|
||||||
|
"accessApprovalsDescription": "Przeglądaj i zarządzaj oczekującymi zatwierdzeniami dostępu do tej organizacji",
|
||||||
"description": "Opis",
|
"description": "Opis",
|
||||||
"inviteTitle": "Otwórz zaproszenia",
|
"inviteTitle": "Otwórz zaproszenia",
|
||||||
"inviteDescription": "Zarządzaj zaproszeniami dla innych użytkowników do dołączenia do organizacji",
|
"inviteDescription": "Zarządzaj zaproszeniami dla innych użytkowników do dołączenia do organizacji",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Wybierz okres",
|
"selectDuration": "Wybierz okres",
|
||||||
"selectResource": "Wybierz zasób",
|
"selectResource": "Wybierz zasób",
|
||||||
"filterByResource": "Filtruj według zasobów",
|
"filterByResource": "Filtruj według zasobów",
|
||||||
|
"selectApprovalState": "Wybierz województwo zatwierdzające",
|
||||||
|
"filterByApprovalState": "Filtruj według państwa zatwierdzenia",
|
||||||
|
"approvalListEmpty": "Brak zatwierdzeń",
|
||||||
|
"approvalState": "Państwo zatwierdzające",
|
||||||
|
"approve": "Zatwierdź",
|
||||||
|
"approved": "Zatwierdzone",
|
||||||
|
"denied": "Odmowa",
|
||||||
|
"deniedApproval": "Odrzucono zatwierdzenie",
|
||||||
|
"all": "Wszystko",
|
||||||
|
"deny": "Odmowa",
|
||||||
|
"viewDetails": "Zobacz szczegóły",
|
||||||
|
"requestingNewDeviceApproval": "zażądano nowego urządzenia",
|
||||||
"resetFilters": "Resetuj filtry",
|
"resetFilters": "Resetuj filtry",
|
||||||
"totalBlocked": "Żądania zablokowane przez Pangolina",
|
"totalBlocked": "Żądania zablokowane przez Pangolina",
|
||||||
"totalRequests": "Wszystkich Żądań",
|
"totalRequests": "Wszystkich Żądań",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Kraje",
|
"countries": "Kraje",
|
||||||
"accessRoleCreate": "Utwórz rolę",
|
"accessRoleCreate": "Utwórz rolę",
|
||||||
"accessRoleCreateDescription": "Utwórz nową rolę aby zgrupować użytkowników i zarządzać ich uprawnieniami.",
|
"accessRoleCreateDescription": "Utwórz nową rolę aby zgrupować użytkowników i zarządzać ich uprawnieniami.",
|
||||||
|
"accessRoleEdit": "Edytuj rolę",
|
||||||
|
"accessRoleEditDescription": "Edytuj informacje o rolach.",
|
||||||
"accessRoleCreateSubmit": "Utwórz rolę",
|
"accessRoleCreateSubmit": "Utwórz rolę",
|
||||||
"accessRoleCreated": "Rola utworzona",
|
"accessRoleCreated": "Rola utworzona",
|
||||||
"accessRoleCreatedDescription": "Rola została pomyślnie utworzona.",
|
"accessRoleCreatedDescription": "Rola została pomyślnie utworzona.",
|
||||||
"accessRoleErrorCreate": "Nie udało się utworzyć roli",
|
"accessRoleErrorCreate": "Nie udało się utworzyć roli",
|
||||||
"accessRoleErrorCreateDescription": "Wystąpił błąd podczas tworzenia roli.",
|
"accessRoleErrorCreateDescription": "Wystąpił błąd podczas tworzenia roli.",
|
||||||
|
"accessRoleUpdateSubmit": "Aktualizuj rolę",
|
||||||
|
"accessRoleUpdated": "Rola zaktualizowana",
|
||||||
|
"accessRoleUpdatedDescription": "Rola została pomyślnie zaktualizowana.",
|
||||||
|
"accessApprovalUpdated": "Zatwierdzenie przetworzone",
|
||||||
|
"accessApprovalApprovedDescription": "Ustaw decyzję o zatwierdzeniu wniosku o zatwierdzenie.",
|
||||||
|
"accessApprovalDeniedDescription": "Ustaw decyzję o odrzuceniu wniosku o zatwierdzenie.",
|
||||||
|
"accessRoleErrorUpdate": "Nie udało się zaktualizować roli",
|
||||||
|
"accessRoleErrorUpdateDescription": "Wystąpił błąd podczas aktualizowania roli.",
|
||||||
|
"accessApprovalErrorUpdate": "Nie udało się przetworzyć zatwierdzenia",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Wystąpił błąd podczas przetwarzania zatwierdzenia.",
|
||||||
"accessRoleErrorNewRequired": "Nowa rola jest wymagana",
|
"accessRoleErrorNewRequired": "Nowa rola jest wymagana",
|
||||||
"accessRoleErrorRemove": "Nie udało się usunąć roli",
|
"accessRoleErrorRemove": "Nie udało się usunąć roli",
|
||||||
"accessRoleErrorRemoveDescription": "Wystąpił błąd podczas usuwania roli.",
|
"accessRoleErrorRemoveDescription": "Wystąpił błąd podczas usuwania roli.",
|
||||||
"accessRoleName": "Nazwa roli",
|
"accessRoleName": "Nazwa roli",
|
||||||
"accessRoleQuestionRemove": "Zamierzasz usunąć rolę {name}. Tej akcji nie można cofnąć.",
|
"accessRoleQuestionRemove": "Zamierzasz usunąć rolę `{name}`. Nie możesz cofnąć tej czynności.",
|
||||||
"accessRoleRemove": "Usuń rolę",
|
"accessRoleRemove": "Usuń rolę",
|
||||||
"accessRoleRemoveDescription": "Usuń rolę z organizacji",
|
"accessRoleRemoveDescription": "Usuń rolę z organizacji",
|
||||||
"accessRoleRemoveSubmit": "Usuń rolę",
|
"accessRoleRemoveSubmit": "Usuń rolę",
|
||||||
@@ -954,13 +983,13 @@
|
|||||||
"passwordExpiryDescription": "Organizacja wymaga zmiany hasła co {maxDays} dni.",
|
"passwordExpiryDescription": "Organizacja wymaga zmiany hasła co {maxDays} dni.",
|
||||||
"changePasswordNow": "Zmień hasło teraz",
|
"changePasswordNow": "Zmień hasło teraz",
|
||||||
"pincodeAuth": "Kod uwierzytelniający",
|
"pincodeAuth": "Kod uwierzytelniający",
|
||||||
"pincodeSubmit2": "Wyślij kod",
|
"pincodeSubmit2": "Prześlij kod",
|
||||||
"passwordResetSubmit": "Zażądaj resetowania",
|
"passwordResetSubmit": "Zażądaj resetowania",
|
||||||
"passwordResetAlreadyHaveCode": "Wprowadź kod",
|
"passwordResetAlreadyHaveCode": "Wprowadź kod",
|
||||||
"passwordResetSmtpRequired": "Skontaktuj się z administratorem",
|
"passwordResetSmtpRequired": "Skontaktuj się z administratorem",
|
||||||
"passwordResetSmtpRequiredDescription": "Aby zresetować hasło, wymagany jest kod resetowania hasła. Skontaktuj się z administratorem.",
|
"passwordResetSmtpRequiredDescription": "Aby zresetować hasło, wymagany jest kod resetowania hasła. Skontaktuj się z administratorem.",
|
||||||
"passwordBack": "Powrót do hasła",
|
"passwordBack": "Powrót do hasła",
|
||||||
"loginBack": "Wróć do logowania",
|
"loginBack": "Wróć do strony logowania głównego",
|
||||||
"signup": "Zarejestruj się",
|
"signup": "Zarejestruj się",
|
||||||
"loginStart": "Zaloguj się, aby rozpocząć",
|
"loginStart": "Zaloguj się, aby rozpocząć",
|
||||||
"idpOidcTokenValidating": "Walidacja tokena OIDC",
|
"idpOidcTokenValidating": "Walidacja tokena OIDC",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Aktualizuj organizację IDP",
|
"actionUpdateIdpOrg": "Aktualizuj organizację IDP",
|
||||||
"actionCreateClient": "Utwórz klienta",
|
"actionCreateClient": "Utwórz klienta",
|
||||||
"actionDeleteClient": "Usuń klienta",
|
"actionDeleteClient": "Usuń klienta",
|
||||||
|
"actionArchiveClient": "Zarchiwizuj klienta",
|
||||||
|
"actionUnarchiveClient": "Usuń archiwizację klienta",
|
||||||
|
"actionBlockClient": "Zablokuj klienta",
|
||||||
|
"actionUnblockClient": "Odblokuj klienta",
|
||||||
"actionUpdateClient": "Aktualizuj klienta",
|
"actionUpdateClient": "Aktualizuj klienta",
|
||||||
"actionListClients": "Lista klientów",
|
"actionListClients": "Lista klientów",
|
||||||
"actionGetClient": "Pobierz klienta",
|
"actionGetClient": "Pobierz klienta",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Szukaj...",
|
"searchProgress": "Szukaj...",
|
||||||
"create": "Utwórz",
|
"create": "Utwórz",
|
||||||
"orgs": "Organizacje",
|
"orgs": "Organizacje",
|
||||||
"loginError": "Wystąpił błąd podczas logowania",
|
"loginError": "Wystąpił nieoczekiwany błąd. Spróbuj ponownie.",
|
||||||
"loginRequiredForDevice": "Logowanie jest wymagane do uwierzytelnienia urządzenia.",
|
"loginRequiredForDevice": "Logowanie jest wymagane dla Twojego urządzenia.",
|
||||||
"passwordForgot": "Zapomniałeś hasła?",
|
"passwordForgot": "Zapomniałeś hasła?",
|
||||||
"otpAuth": "Uwierzytelnianie dwuskładnikowe",
|
"otpAuth": "Uwierzytelnianie dwuskładnikowe",
|
||||||
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
|
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
|
||||||
"otpAuthSubmit": "Wyślij kod",
|
"otpAuthSubmit": "Wyślij kod",
|
||||||
"idpContinue": "Lub kontynuuj z",
|
"idpContinue": "Lub kontynuuj z",
|
||||||
"otpAuthBack": "Powrót do logowania",
|
"otpAuthBack": "Powrót do hasła",
|
||||||
"navbar": "Menu nawigacyjne",
|
"navbar": "Menu nawigacyjne",
|
||||||
"navbarDescription": "Główne menu nawigacyjne aplikacji",
|
"navbarDescription": "Główne menu nawigacyjne aplikacji",
|
||||||
"navbarDocsLink": "Dokumentacja",
|
"navbarDocsLink": "Dokumentacja",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Przegląd",
|
"sidebarOverview": "Przegląd",
|
||||||
"sidebarHome": "Strona główna",
|
"sidebarHome": "Strona główna",
|
||||||
"sidebarSites": "Witryny",
|
"sidebarSites": "Witryny",
|
||||||
|
"sidebarApprovals": "Wnioski o zatwierdzenie",
|
||||||
"sidebarResources": "Zasoby",
|
"sidebarResources": "Zasoby",
|
||||||
"sidebarProxyResources": "Publiczne",
|
"sidebarProxyResources": "Publiczne",
|
||||||
"sidebarClientResources": "Prywatny",
|
"sidebarClientResources": "Prywatny",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Dostawcy tożsamości",
|
"sidebarIdentityProviders": "Dostawcy tożsamości",
|
||||||
"sidebarLicense": "Licencja",
|
"sidebarLicense": "Licencja",
|
||||||
"sidebarClients": "Klienty",
|
"sidebarClients": "Klienty",
|
||||||
"sidebarUserDevices": "Użytkownicy",
|
"sidebarUserDevices": "Urządzenia użytkownika",
|
||||||
"sidebarMachineClients": "Maszyny",
|
"sidebarMachineClients": "Maszyny",
|
||||||
"sidebarDomains": "Domeny",
|
"sidebarDomains": "Domeny",
|
||||||
"sidebarGeneral": "Zarządzaj",
|
"sidebarGeneral": "Zarządzaj",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Wystąpił błąd podczas tworzenia konta administratora serwera.",
|
"setupErrorCreateAdmin": "Wystąpił błąd podczas tworzenia konta administratora serwera.",
|
||||||
"certificateStatus": "Status certyfikatu",
|
"certificateStatus": "Status certyfikatu",
|
||||||
"loading": "Ładowanie",
|
"loading": "Ładowanie",
|
||||||
|
"loadingAnalytics": "Ładowanie Analityki",
|
||||||
"restart": "Uruchom ponownie",
|
"restart": "Uruchom ponownie",
|
||||||
"domains": "Domeny",
|
"domains": "Domeny",
|
||||||
"domainsDescription": "Tworzenie domen dostępnych w organizacji i zarządzanie nimi",
|
"domainsDescription": "Tworzenie domen dostępnych w organizacji i zarządzanie nimi",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Nie udało się odświeżyć danych",
|
"refreshError": "Nie udało się odświeżyć danych",
|
||||||
"verified": "Zatwierdzony",
|
"verified": "Zatwierdzony",
|
||||||
"pending": "Oczekuje",
|
"pending": "Oczekuje",
|
||||||
|
"pendingApproval": "Oczekujące na zatwierdzenie",
|
||||||
"sidebarBilling": "Fakturowanie",
|
"sidebarBilling": "Fakturowanie",
|
||||||
"billing": "Fakturowanie",
|
"billing": "Fakturowanie",
|
||||||
"orgBillingDescription": "Zarządzaj informacjami rozliczeniowymi i subskrypcjami",
|
"orgBillingDescription": "Zarządzaj informacjami rozliczeniowymi i subskrypcjami",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Klucz bezpieczeństwa został pomyślnie usunięty",
|
"securityKeyRemoveSuccess": "Klucz bezpieczeństwa został pomyślnie usunięty",
|
||||||
"securityKeyRemoveError": "Błąd podczas usuwania klucza bezpieczeństwa",
|
"securityKeyRemoveError": "Błąd podczas usuwania klucza bezpieczeństwa",
|
||||||
"securityKeyLoadError": "Błąd podczas ładowania kluczy bezpieczeństwa",
|
"securityKeyLoadError": "Błąd podczas ładowania kluczy bezpieczeństwa",
|
||||||
"securityKeyLogin": "Zaloguj się kluczem bezpieczeństwa",
|
"securityKeyLogin": "Użyj klucza bezpieczeństwa",
|
||||||
"securityKeyAuthError": "Błąd podczas uwierzytelniania kluczem bezpieczeństwa",
|
"securityKeyAuthError": "Błąd podczas uwierzytelniania kluczem bezpieczeństwa",
|
||||||
"securityKeyRecommendation": "Rozważ zarejestrowanie innego klucza bezpieczeństwa na innym urządzeniu, aby upewnić się, że nie zostaniesz zablokowany z dostępu do swojego konta.",
|
"securityKeyRecommendation": "Rozważ zarejestrowanie innego klucza bezpieczeństwa na innym urządzeniu, aby upewnić się, że nie zostaniesz zablokowany z dostępu do swojego konta.",
|
||||||
"registering": "Rejestracja...",
|
"registering": "Rejestracja...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Interwał Zdrowy",
|
"IntervalSeconds": "Interwał Zdrowy",
|
||||||
"timeoutSeconds": "Limit czasu (sek)",
|
"timeoutSeconds": "Limit czasu (sek)",
|
||||||
"timeIsInSeconds": "Czas w sekundach",
|
"timeIsInSeconds": "Czas w sekundach",
|
||||||
|
"requireDeviceApproval": "Wymagaj zatwierdzenia urządzenia",
|
||||||
|
"requireDeviceApprovalDescription": "Użytkownicy o tej roli potrzebują nowych urządzeń zatwierdzonych przez administratora, zanim będą mogli połączyć się i uzyskać dostęp do zasobów.",
|
||||||
"retryAttempts": "Próby Ponowienia",
|
"retryAttempts": "Próby Ponowienia",
|
||||||
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
|
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
|
||||||
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
|
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Nie znaleziono wewnętrznych zasobów.",
|
"resourcesTableNoInternalResourcesFound": "Nie znaleziono wewnętrznych zasobów.",
|
||||||
"resourcesTableDestination": "Miejsce docelowe",
|
"resourcesTableDestination": "Miejsce docelowe",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Adres aliasu",
|
||||||
|
"resourcesTableAliasAddressInfo": "Ten adres jest częścią podsieci użyteczności organizacji. Jest używany do rozwiązywania rekordów aliasu przy użyciu wewnętrznej rozdzielczości DNS.",
|
||||||
"resourcesTableClients": "Klientami",
|
"resourcesTableClients": "Klientami",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "i są dostępne tylko wewnętrznie po połączeniu z klientem.",
|
"resourcesTableAndOnlyAccessibleInternally": "i są dostępne tylko wewnętrznie po połączeniu z klientem.",
|
||||||
"resourcesTableNoTargets": "Brak celów",
|
"resourcesTableNoTargets": "Brak celów",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Kod musi mieć 9 znaków (np. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Kod musi mieć 9 znaków (np. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Nieprawidłowy lub wygasły kod",
|
"deviceCodeInvalidOrExpired": "Nieprawidłowy lub wygasły kod",
|
||||||
"deviceCodeVerifyFailed": "Nie udało się zweryfikować kodu urządzenia",
|
"deviceCodeVerifyFailed": "Nie udało się zweryfikować kodu urządzenia",
|
||||||
|
"deviceCodeValidating": "Sprawdzanie kodu urządzenia...",
|
||||||
|
"deviceCodeVerifying": "Weryfikowanie autoryzacji urządzenia...",
|
||||||
"signedInAs": "Zalogowany jako",
|
"signedInAs": "Zalogowany jako",
|
||||||
"deviceCodeEnterPrompt": "Wprowadź kod wyświetlany na urządzeniu",
|
"deviceCodeEnterPrompt": "Wprowadź kod wyświetlany na urządzeniu",
|
||||||
"continue": "Kontynuuj",
|
"continue": "Kontynuuj",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Dostęp do wszystkich organizacji, do których Twoje konto ma dostęp",
|
"deviceOrganizationsAccess": "Dostęp do wszystkich organizacji, do których Twoje konto ma dostęp",
|
||||||
"deviceAuthorize": "Autoryzuj {applicationName}",
|
"deviceAuthorize": "Autoryzuj {applicationName}",
|
||||||
"deviceConnected": "Urządzenie podłączone!",
|
"deviceConnected": "Urządzenie podłączone!",
|
||||||
"deviceAuthorizedMessage": "Urządzenie jest upoważnione do dostępu do Twojego konta.",
|
"deviceAuthorizedMessage": "Urządzenie jest autoryzowane do uzyskania dostępu do Twojego konta. Proszę wróć do aplikacji klienckiej.",
|
||||||
"pangolinCloud": "Chmura Pangolin",
|
"pangolinCloud": "Chmura Pangolin",
|
||||||
"viewDevices": "Zobacz urządzenia",
|
"viewDevices": "Zobacz urządzenia",
|
||||||
"viewDevicesDescription": "Zarządzaj podłączonymi urządzeniami",
|
"viewDevicesDescription": "Zarządzaj podłączonymi urządzeniami",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Nie ty? Użyj innego konta.",
|
"deviceLoginUseDifferentAccount": "Nie ty? Użyj innego konta.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Urządzenie żąda dostępu do tego konta.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Urządzenie żąda dostępu do tego konta.",
|
||||||
|
"loginSelectAuthenticationMethod": "Wybierz metodę uwierzytelniania aby kontynuować.",
|
||||||
"noData": "Brak danych",
|
"noData": "Brak danych",
|
||||||
"machineClients": "Klienci maszyn",
|
"machineClients": "Klienci maszyn",
|
||||||
"install": "Zainstaluj",
|
"install": "Zainstaluj",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Usługa chwilowo niedostępna",
|
"maintenanceScreenTitle": "Usługa chwilowo niedostępna",
|
||||||
"maintenanceScreenMessage": "Obecnie doświadczamy problemów technicznych. Proszę sprawdzić ponownie wkrótce.",
|
"maintenanceScreenMessage": "Obecnie doświadczamy problemów technicznych. Proszę sprawdzić ponownie wkrótce.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Szacowane zakończenie:",
|
"maintenanceScreenEstimatedCompletion": "Szacowane zakończenie:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane"
|
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane",
|
||||||
|
"available": "Dostępny",
|
||||||
|
"archived": "Zarchiwizowane",
|
||||||
|
"noArchivedDevices": "Nie znaleziono zarchiwizowanych urządzeń",
|
||||||
|
"deviceArchived": "Urządzenie zarchiwizowane",
|
||||||
|
"deviceArchivedDescription": "Urządzenie zostało pomyślnie zarchiwizowane.",
|
||||||
|
"errorArchivingDevice": "Błąd podczas archiwizacji urządzenia",
|
||||||
|
"failedToArchiveDevice": "Nie udało się zarchiwizować urządzenia",
|
||||||
|
"deviceQuestionArchive": "Czy na pewno chcesz zarchiwizować to urządzenie?",
|
||||||
|
"deviceMessageArchive": "Urządzenie zostanie zarchiwizowane i usunięte z listy aktywnych urządzeń.",
|
||||||
|
"deviceArchiveConfirm": "Archiwizuj urządzenie",
|
||||||
|
"archiveDevice": "Archiwizuj urządzenie",
|
||||||
|
"archive": "Archiwum",
|
||||||
|
"deviceUnarchived": "Urządzenie niezarchiwizowane",
|
||||||
|
"deviceUnarchivedDescription": "Urządzenie zostało pomyślnie usunięte.",
|
||||||
|
"errorUnarchivingDevice": "Błąd podczas usuwania archiwizacji urządzenia",
|
||||||
|
"failedToUnarchiveDevice": "Nie udało się odarchiwizować urządzenia",
|
||||||
|
"unarchive": "Usuń z archiwum",
|
||||||
|
"archiveClient": "Zarchiwizuj klienta",
|
||||||
|
"archiveClientQuestion": "Czy na pewno chcesz zarchiwizować tego klienta?",
|
||||||
|
"archiveClientMessage": "Klient zostanie zarchiwizowany i usunięty z listy aktywnych klientów.",
|
||||||
|
"archiveClientConfirm": "Zarchiwizuj klienta",
|
||||||
|
"blockClient": "Zablokuj klienta",
|
||||||
|
"blockClientQuestion": "Czy na pewno chcesz zablokować tego klienta?",
|
||||||
|
"blockClientMessage": "Urządzenie zostanie wymuszone do rozłączenia, jeśli jest obecnie podłączone. Możesz odblokować urządzenie później.",
|
||||||
|
"blockClientConfirm": "Zablokuj klienta",
|
||||||
|
"active": "Aktywne",
|
||||||
|
"usernameOrEmail": "Nazwa użytkownika lub e-mail",
|
||||||
|
"selectYourOrganization": "Wybierz swoją organizację",
|
||||||
|
"signInTo": "Zaloguj się do",
|
||||||
|
"signInWithPassword": "Kontynuuj z hasłem",
|
||||||
|
"noAuthMethodsAvailable": "Brak dostępnych metod uwierzytelniania dla tej organizacji.",
|
||||||
|
"enterPassword": "Wprowadź hasło",
|
||||||
|
"enterMfaCode": "Wprowadź kod z aplikacji uwierzytelniającej",
|
||||||
|
"securityKeyRequired": "Aby się zalogować, użyj klucza bezpieczeństwa.",
|
||||||
|
"needToUseAnotherAccount": "Potrzebujesz użyć innego konta?",
|
||||||
|
"loginLegalDisclaimer": "Klikając na przycisk poniżej, potwierdzasz, że przeczytałeś, rozumiesz, i zaakceptuj <termsOfService>Warunki świadczenia usługi</termsOfService> i <privacyPolicy>Polityka prywatności</privacyPolicy>.",
|
||||||
|
"termsOfService": "Warunki korzystania z usługi",
|
||||||
|
"privacyPolicy": "Polityka prywatności",
|
||||||
|
"userNotFoundWithUsername": "Nie znaleziono użytkownika o tej nazwie użytkownika.",
|
||||||
|
"verify": "Weryfikacja",
|
||||||
|
"signIn": "Zaloguj się",
|
||||||
|
"forgotPassword": "Zapomniałeś hasła?",
|
||||||
|
"orgSignInTip": "Jeśli zalogowałeś się wcześniej, możesz wprowadzić nazwę użytkownika lub e-mail powyżej, aby uwierzytelnić się z dostawcą tożsamości organizacji. To łatwiejsze!",
|
||||||
|
"continueAnyway": "Kontynuuj mimo to",
|
||||||
|
"dontShowAgain": "Nie pokazuj ponownie",
|
||||||
|
"orgSignInNotice": "Czy wiedziałeś?",
|
||||||
|
"signupOrgNotice": "Próbujesz się zalogować?",
|
||||||
|
"signupOrgTip": "Czy próbujesz zalogować się za pośrednictwem dostawcy tożsamości organizacji?",
|
||||||
|
"signupOrgLink": "Zamiast tego zaloguj się lub zarejestruj w swojej organizacji",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Użyj innego konta",
|
||||||
|
"logIn": "Zaloguj się",
|
||||||
|
"deviceInformation": "Informacje o urządzeniu",
|
||||||
|
"deviceInformationDescription": "Informacje o urządzeniu i agentach",
|
||||||
|
"deviceSecurity": "Bezpieczeństwo urządzenia",
|
||||||
|
"deviceSecurityDescription": "Informacje o bezpieczeństwie urządzenia",
|
||||||
|
"platform": "Platforma",
|
||||||
|
"macosVersion": "Wersja macOS",
|
||||||
|
"windowsVersion": "Wersja Windows",
|
||||||
|
"iosVersion": "Wersja iOS",
|
||||||
|
"androidVersion": "Wersja Androida",
|
||||||
|
"osVersion": "Wersja systemu operacyjnego",
|
||||||
|
"kernelVersion": "Wersja jądra",
|
||||||
|
"deviceModel": "Model urządzenia",
|
||||||
|
"serialNumber": "Numer seryjny",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Widziany po raz pierwszy",
|
||||||
|
"lastSeen": "Ostatnio widziane",
|
||||||
|
"biometricsEnabled": "Biometria włączona",
|
||||||
|
"diskEncrypted": "Dysk zaszyfrowany",
|
||||||
|
"firewallEnabled": "Zapora włączona",
|
||||||
|
"autoUpdatesEnabled": "Automatyczne aktualizacje włączone",
|
||||||
|
"tpmAvailable": "TPM dostępne",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Ochrona integralności systemu (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Tryb Stealth zapory",
|
||||||
|
"linuxAppArmorEnabled": "Zbroja aplikacji",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Wyświetl informacje o urządzeniu i ustawienia",
|
||||||
|
"devicePendingApprovalDescription": "To urządzenie czeka na zatwierdzenie",
|
||||||
|
"deviceBlockedDescription": "To urządzenie jest obecnie zablokowane. Nie będzie można połączyć się z żadnymi zasobami, chyba że zostanie odblokowane.",
|
||||||
|
"unblockClient": "Odblokuj klienta",
|
||||||
|
"unblockClientDescription": "Urządzenie zostało odblokowane",
|
||||||
|
"unarchiveClient": "Usuń archiwizację klienta",
|
||||||
|
"unarchiveClientDescription": "Urządzenie zostało odarchiwizowane",
|
||||||
|
"block": "Blok",
|
||||||
|
"unblock": "Odblokuj",
|
||||||
|
"deviceActions": "Akcje urządzenia",
|
||||||
|
"deviceActionsDescription": "Zarządzaj stanem urządzenia i dostępem",
|
||||||
|
"devicePendingApprovalBannerDescription": "To urządzenie oczekuje na zatwierdzenie. Nie będzie można połączyć się z zasobami, dopóki nie zostanie zatwierdzone.",
|
||||||
|
"connected": "Połączono",
|
||||||
|
"disconnected": "Rozłączony",
|
||||||
|
"approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone",
|
||||||
|
"approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Przejdź do ról",
|
||||||
|
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
||||||
|
"approvalsEmptyStateStep2Description": "Edytuj rolę i włącz opcję \"Wymagaj zatwierdzenia urządzenia\". Użytkownicy z tą rolą będą potrzebowali zatwierdzenia administratora dla nowych urządzeń.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Podgląd: Gdy włączone, oczekujące prośby o sprawdzenie pojawią się tutaj",
|
||||||
|
"approvalsEmptyStateButtonText": "Zarządzaj rolami"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Conectar a Qualquer Rede",
|
"sitesBannerTitle": "Conectar a Qualquer Rede",
|
||||||
"sitesBannerDescription": "Um site é uma conexão a uma rede remota que permite ao Pangolin fornecer acesso a recursos, sejam eles públicos ou privados, a usuários em qualquer lugar. Instale o conector de rede do site (Newt) em qualquer lugar onde você possa executar um binário ou contêiner para estabelecer a conexão.",
|
"sitesBannerDescription": "Um site é uma conexão a uma rede remota que permite ao Pangolin fornecer acesso a recursos, sejam eles públicos ou privados, a usuários em qualquer lugar. Instale o conector de rede do site (Newt) em qualquer lugar onde você possa executar um binário ou contêiner para estabelecer a conexão.",
|
||||||
"sitesBannerButtonText": "Instalar Site",
|
"sitesBannerButtonText": "Instalar Site",
|
||||||
|
"approvalsBannerTitle": "Aprovar ou negar acesso ao dispositivo",
|
||||||
|
"approvalsBannerDescription": "Revisar e aprovar ou negar solicitações de acesso ao dispositivo de usuários. Quando as aprovações do dispositivo são necessárias, os usuários devem obter a aprovação do administrador antes que seus dispositivos possam se conectar aos recursos da sua organização.",
|
||||||
|
"approvalsBannerButtonText": "Saiba mais",
|
||||||
"siteCreate": "Criar site",
|
"siteCreate": "Criar site",
|
||||||
"siteCreateDescription2": "Siga os passos abaixo para criar e conectar um novo site",
|
"siteCreateDescription2": "Siga os passos abaixo para criar e conectar um novo site",
|
||||||
"siteCreateDescription": "Crie um novo site para começar a conectar os recursos",
|
"siteCreateDescription": "Crie um novo site para começar a conectar os recursos",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Pesquisar funções...",
|
"accessRolesSearch": "Pesquisar funções...",
|
||||||
"accessRolesAdd": "Adicionar função",
|
"accessRolesAdd": "Adicionar função",
|
||||||
"accessRoleDelete": "Excluir Papel",
|
"accessRoleDelete": "Excluir Papel",
|
||||||
|
"accessApprovalsManage": "Gerenciar aprovações",
|
||||||
|
"accessApprovalsDescription": "Visualizar e gerenciar aprovações pendentes para acesso a esta organização",
|
||||||
"description": "Descrição:",
|
"description": "Descrição:",
|
||||||
"inviteTitle": "Convites Abertos",
|
"inviteTitle": "Convites Abertos",
|
||||||
"inviteDescription": "Gerenciar convites para outros usuários participarem da organização",
|
"inviteDescription": "Gerenciar convites para outros usuários participarem da organização",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Selecionar duração",
|
"selectDuration": "Selecionar duração",
|
||||||
"selectResource": "Selecionar Recurso",
|
"selectResource": "Selecionar Recurso",
|
||||||
"filterByResource": "Filtrar por Recurso",
|
"filterByResource": "Filtrar por Recurso",
|
||||||
|
"selectApprovalState": "Selecionar Estado de Aprovação",
|
||||||
|
"filterByApprovalState": "Filtrar por estado de aprovação",
|
||||||
|
"approvalListEmpty": "Sem aprovações",
|
||||||
|
"approvalState": "Estado de aprovação",
|
||||||
|
"approve": "Aprovar",
|
||||||
|
"approved": "Aceito",
|
||||||
|
"denied": "Negado",
|
||||||
|
"deniedApproval": "Aprovação Negada",
|
||||||
|
"all": "Todos",
|
||||||
|
"deny": "Recusar",
|
||||||
|
"viewDetails": "Visualizar Detalhes",
|
||||||
|
"requestingNewDeviceApproval": "solicitou um novo dispositivo",
|
||||||
"resetFilters": "Redefinir filtros",
|
"resetFilters": "Redefinir filtros",
|
||||||
"totalBlocked": "Solicitações bloqueadas pelo Pangolin",
|
"totalBlocked": "Solicitações bloqueadas pelo Pangolin",
|
||||||
"totalRequests": "Total de pedidos",
|
"totalRequests": "Total de pedidos",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Países",
|
"countries": "Países",
|
||||||
"accessRoleCreate": "Criar Função",
|
"accessRoleCreate": "Criar Função",
|
||||||
"accessRoleCreateDescription": "Crie uma nova função para agrupar utilizadores e gerir suas permissões.",
|
"accessRoleCreateDescription": "Crie uma nova função para agrupar utilizadores e gerir suas permissões.",
|
||||||
|
"accessRoleEdit": "Editar Permissão",
|
||||||
|
"accessRoleEditDescription": "Editar informações do papel.",
|
||||||
"accessRoleCreateSubmit": "Criar Função",
|
"accessRoleCreateSubmit": "Criar Função",
|
||||||
"accessRoleCreated": "Função criada",
|
"accessRoleCreated": "Função criada",
|
||||||
"accessRoleCreatedDescription": "A função foi criada com sucesso.",
|
"accessRoleCreatedDescription": "A função foi criada com sucesso.",
|
||||||
"accessRoleErrorCreate": "Falha ao criar função",
|
"accessRoleErrorCreate": "Falha ao criar função",
|
||||||
"accessRoleErrorCreateDescription": "Ocorreu um erro ao criar a função.",
|
"accessRoleErrorCreateDescription": "Ocorreu um erro ao criar a função.",
|
||||||
|
"accessRoleUpdateSubmit": "Atualizar Função",
|
||||||
|
"accessRoleUpdated": "Função atualizada",
|
||||||
|
"accessRoleUpdatedDescription": "A função foi atualizada com sucesso.",
|
||||||
|
"accessApprovalUpdated": "Aprovação processada",
|
||||||
|
"accessApprovalApprovedDescription": "Definir decisão de solicitação de aprovação para aprovada.",
|
||||||
|
"accessApprovalDeniedDescription": "Definir decisão de solicitação de aprovação para negada.",
|
||||||
|
"accessRoleErrorUpdate": "Falha ao atualizar papel",
|
||||||
|
"accessRoleErrorUpdateDescription": "Ocorreu um erro ao atualizar a função.",
|
||||||
|
"accessApprovalErrorUpdate": "Não foi possível processar a aprovação",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Ocorreu um erro ao processar a aprovação.",
|
||||||
"accessRoleErrorNewRequired": "Nova função é necessária",
|
"accessRoleErrorNewRequired": "Nova função é necessária",
|
||||||
"accessRoleErrorRemove": "Falha ao remover função",
|
"accessRoleErrorRemove": "Falha ao remover função",
|
||||||
"accessRoleErrorRemoveDescription": "Ocorreu um erro ao remover a função.",
|
"accessRoleErrorRemoveDescription": "Ocorreu um erro ao remover a função.",
|
||||||
"accessRoleName": "Nome da Função",
|
"accessRoleName": "Nome da Função",
|
||||||
"accessRoleQuestionRemove": "Você está prestes a apagar a função {name}. Você não pode desfazer esta ação.",
|
"accessRoleQuestionRemove": "Você está prestes a apagar o papel `{name}. Você não pode desfazer esta ação.",
|
||||||
"accessRoleRemove": "Remover Função",
|
"accessRoleRemove": "Remover Função",
|
||||||
"accessRoleRemoveDescription": "Remover uma função da organização",
|
"accessRoleRemoveDescription": "Remover uma função da organização",
|
||||||
"accessRoleRemoveSubmit": "Remover Função",
|
"accessRoleRemoveSubmit": "Remover Função",
|
||||||
@@ -954,13 +983,13 @@
|
|||||||
"passwordExpiryDescription": "Esta organização exige que você altere sua senha a cada {maxDays} dias.",
|
"passwordExpiryDescription": "Esta organização exige que você altere sua senha a cada {maxDays} dias.",
|
||||||
"changePasswordNow": "Alterar a senha agora",
|
"changePasswordNow": "Alterar a senha agora",
|
||||||
"pincodeAuth": "Código do Autenticador",
|
"pincodeAuth": "Código do Autenticador",
|
||||||
"pincodeSubmit2": "Submeter Código",
|
"pincodeSubmit2": "Enviar código",
|
||||||
"passwordResetSubmit": "Solicitar Redefinição",
|
"passwordResetSubmit": "Solicitar Redefinição",
|
||||||
"passwordResetAlreadyHaveCode": "Inserir Código",
|
"passwordResetAlreadyHaveCode": "Inserir Código",
|
||||||
"passwordResetSmtpRequired": "Por favor, contate o administrador",
|
"passwordResetSmtpRequired": "Por favor, contate o administrador",
|
||||||
"passwordResetSmtpRequiredDescription": "É necessário um código de redefinição de senha para redefinir sua senha. Por favor, contate o administrador para assistência.",
|
"passwordResetSmtpRequiredDescription": "É necessário um código de redefinição de senha para redefinir sua senha. Por favor, contate o administrador para assistência.",
|
||||||
"passwordBack": "Voltar à Palavra-passe",
|
"passwordBack": "Voltar à Palavra-passe",
|
||||||
"loginBack": "Voltar ao início de sessão",
|
"loginBack": "Voltar para a página principal de acesso",
|
||||||
"signup": "Registar",
|
"signup": "Registar",
|
||||||
"loginStart": "Inicie sessão para começar",
|
"loginStart": "Inicie sessão para começar",
|
||||||
"idpOidcTokenValidating": "A validar token OIDC",
|
"idpOidcTokenValidating": "A validar token OIDC",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Atualizar Organização IDP",
|
"actionUpdateIdpOrg": "Atualizar Organização IDP",
|
||||||
"actionCreateClient": "Criar Cliente",
|
"actionCreateClient": "Criar Cliente",
|
||||||
"actionDeleteClient": "Excluir Cliente",
|
"actionDeleteClient": "Excluir Cliente",
|
||||||
|
"actionArchiveClient": "Arquivar Cliente",
|
||||||
|
"actionUnarchiveClient": "Desarquivar Cliente",
|
||||||
|
"actionBlockClient": "Bloco do Cliente",
|
||||||
|
"actionUnblockClient": "Desbloquear Cliente",
|
||||||
"actionUpdateClient": "Atualizar Cliente",
|
"actionUpdateClient": "Atualizar Cliente",
|
||||||
"actionListClients": "Listar Clientes",
|
"actionListClients": "Listar Clientes",
|
||||||
"actionGetClient": "Obter Cliente",
|
"actionGetClient": "Obter Cliente",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Pesquisar...",
|
"searchProgress": "Pesquisar...",
|
||||||
"create": "Criar",
|
"create": "Criar",
|
||||||
"orgs": "Organizações",
|
"orgs": "Organizações",
|
||||||
"loginError": "Ocorreu um erro ao iniciar sessão",
|
"loginError": "Ocorreu um erro inesperado. Por favor, tente novamente.",
|
||||||
"loginRequiredForDevice": "É necessário entrar para autenticar seu dispositivo.",
|
"loginRequiredForDevice": "O login é necessário para seu dispositivo.",
|
||||||
"passwordForgot": "Esqueceu a sua palavra-passe?",
|
"passwordForgot": "Esqueceu a sua palavra-passe?",
|
||||||
"otpAuth": "Autenticação de Dois Fatores",
|
"otpAuth": "Autenticação de Dois Fatores",
|
||||||
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
|
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
|
||||||
"otpAuthSubmit": "Submeter Código",
|
"otpAuthSubmit": "Submeter Código",
|
||||||
"idpContinue": "Ou continuar com",
|
"idpContinue": "Ou continuar com",
|
||||||
"otpAuthBack": "Voltar ao Início de Sessão",
|
"otpAuthBack": "Voltar à Palavra-passe",
|
||||||
"navbar": "Menu de Navegação",
|
"navbar": "Menu de Navegação",
|
||||||
"navbarDescription": "Menu de navegação principal da aplicação",
|
"navbarDescription": "Menu de navegação principal da aplicação",
|
||||||
"navbarDocsLink": "Documentação",
|
"navbarDocsLink": "Documentação",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Geral",
|
"sidebarOverview": "Geral",
|
||||||
"sidebarHome": "Residencial",
|
"sidebarHome": "Residencial",
|
||||||
"sidebarSites": "sites",
|
"sidebarSites": "sites",
|
||||||
|
"sidebarApprovals": "Solicitações de aprovação",
|
||||||
"sidebarResources": "Recursos",
|
"sidebarResources": "Recursos",
|
||||||
"sidebarProxyResources": "Público",
|
"sidebarProxyResources": "Público",
|
||||||
"sidebarClientResources": "Privado",
|
"sidebarClientResources": "Privado",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Provedores de identidade",
|
"sidebarIdentityProviders": "Provedores de identidade",
|
||||||
"sidebarLicense": "Tipo:",
|
"sidebarLicense": "Tipo:",
|
||||||
"sidebarClients": "Clientes",
|
"sidebarClients": "Clientes",
|
||||||
"sidebarUserDevices": "Utilizadores",
|
"sidebarUserDevices": "Dispositivos do usuário",
|
||||||
"sidebarMachineClients": "Máquinas",
|
"sidebarMachineClients": "Máquinas",
|
||||||
"sidebarDomains": "Domínios",
|
"sidebarDomains": "Domínios",
|
||||||
"sidebarGeneral": "Gerir",
|
"sidebarGeneral": "Gerir",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Ocorreu um erro ao criar a conta de administrador do servidor.",
|
"setupErrorCreateAdmin": "Ocorreu um erro ao criar a conta de administrador do servidor.",
|
||||||
"certificateStatus": "Status do Certificado",
|
"certificateStatus": "Status do Certificado",
|
||||||
"loading": "Carregando",
|
"loading": "Carregando",
|
||||||
|
"loadingAnalytics": "Carregando Analytics",
|
||||||
"restart": "Reiniciar",
|
"restart": "Reiniciar",
|
||||||
"domains": "Domínios",
|
"domains": "Domínios",
|
||||||
"domainsDescription": "Criar e gerenciar domínios disponíveis na organização",
|
"domainsDescription": "Criar e gerenciar domínios disponíveis na organização",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Falha ao atualizar dados",
|
"refreshError": "Falha ao atualizar dados",
|
||||||
"verified": "Verificado",
|
"verified": "Verificado",
|
||||||
"pending": "Pendente",
|
"pending": "Pendente",
|
||||||
|
"pendingApproval": "Aprovação pendente",
|
||||||
"sidebarBilling": "Faturamento",
|
"sidebarBilling": "Faturamento",
|
||||||
"billing": "Faturamento",
|
"billing": "Faturamento",
|
||||||
"orgBillingDescription": "Gerenciar informações e assinaturas de cobrança",
|
"orgBillingDescription": "Gerenciar informações e assinaturas de cobrança",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Chave de segurança removida com sucesso",
|
"securityKeyRemoveSuccess": "Chave de segurança removida com sucesso",
|
||||||
"securityKeyRemoveError": "Erro ao remover chave de segurança",
|
"securityKeyRemoveError": "Erro ao remover chave de segurança",
|
||||||
"securityKeyLoadError": "Erro ao carregar chaves de segurança",
|
"securityKeyLoadError": "Erro ao carregar chaves de segurança",
|
||||||
"securityKeyLogin": "Continuar com a chave de segurança",
|
"securityKeyLogin": "Usar chave de segurança",
|
||||||
"securityKeyAuthError": "Erro ao autenticar com chave de segurança",
|
"securityKeyAuthError": "Erro ao autenticar com chave de segurança",
|
||||||
"securityKeyRecommendation": "Considere registrar outra chave de segurança em um dispositivo diferente para garantir que você não fique bloqueado da sua conta.",
|
"securityKeyRecommendation": "Considere registrar outra chave de segurança em um dispositivo diferente para garantir que você não fique bloqueado da sua conta.",
|
||||||
"registering": "Registrando...",
|
"registering": "Registrando...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Intervalo Saudável",
|
"IntervalSeconds": "Intervalo Saudável",
|
||||||
"timeoutSeconds": "Tempo limite (seg)",
|
"timeoutSeconds": "Tempo limite (seg)",
|
||||||
"timeIsInSeconds": "O tempo está em segundos",
|
"timeIsInSeconds": "O tempo está em segundos",
|
||||||
|
"requireDeviceApproval": "Exigir aprovação do dispositivo",
|
||||||
|
"requireDeviceApprovalDescription": "Usuários com esta função precisam de novos dispositivos aprovados por um administrador antes que eles possam se conectar e acessar recursos.",
|
||||||
"retryAttempts": "Tentativas de Repetição",
|
"retryAttempts": "Tentativas de Repetição",
|
||||||
"expectedResponseCodes": "Códigos de Resposta Esperados",
|
"expectedResponseCodes": "Códigos de Resposta Esperados",
|
||||||
"expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.",
|
"expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Nenhum recurso interno encontrado.",
|
"resourcesTableNoInternalResourcesFound": "Nenhum recurso interno encontrado.",
|
||||||
"resourcesTableDestination": "Destino",
|
"resourcesTableDestination": "Destino",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Endereço do Pseudônimo",
|
||||||
|
"resourcesTableAliasAddressInfo": "Este endereço faz parte da sub-rede de utilitários da organização. É usado para resolver registros de alias usando resolução de DNS interno.",
|
||||||
"resourcesTableClients": "Clientes",
|
"resourcesTableClients": "Clientes",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "e são acessíveis apenas internamente quando conectados com um cliente.",
|
"resourcesTableAndOnlyAccessibleInternally": "e são acessíveis apenas internamente quando conectados com um cliente.",
|
||||||
"resourcesTableNoTargets": "Nenhum alvo",
|
"resourcesTableNoTargets": "Nenhum alvo",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Escolha o seu provedor de identidade para continuar",
|
"orgAuthChooseIdpDescription": "Escolha o seu provedor de identidade para continuar",
|
||||||
"orgAuthNoIdpConfigured": "Esta organização não tem nenhum provedor de identidade configurado. Você pode entrar com a identidade do seu Pangolin.",
|
"orgAuthNoIdpConfigured": "Esta organização não tem nenhum provedor de identidade configurado. Você pode entrar com a identidade do seu Pangolin.",
|
||||||
"orgAuthSignInWithPangolin": "Entrar com o Pangolin",
|
"orgAuthSignInWithPangolin": "Entrar com o Pangolin",
|
||||||
"orgAuthSignInToOrg": "Entrar em uma organização",
|
"orgAuthSignInToOrg": "Fazer login em uma organização",
|
||||||
"orgAuthSelectOrgTitle": "Entrada da Organização",
|
"orgAuthSelectOrgTitle": "Entrada da Organização",
|
||||||
"orgAuthSelectOrgDescription": "Digite seu ID da organização para continuar",
|
"orgAuthSelectOrgDescription": "Digite seu ID da organização para continuar",
|
||||||
"orgAuthOrgIdPlaceholder": "sua-organização",
|
"orgAuthOrgIdPlaceholder": "sua-organização",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "O código deve ter 9 caracteres (ex.: A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "O código deve ter 9 caracteres (ex.: A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Código inválido ou expirado",
|
"deviceCodeInvalidOrExpired": "Código inválido ou expirado",
|
||||||
"deviceCodeVerifyFailed": "Falha ao verificar o código do dispositivo",
|
"deviceCodeVerifyFailed": "Falha ao verificar o código do dispositivo",
|
||||||
|
"deviceCodeValidating": "Validando código do dispositivo...",
|
||||||
|
"deviceCodeVerifying": "Verificando autorização do dispositivo...",
|
||||||
"signedInAs": "Sessão iniciada como",
|
"signedInAs": "Sessão iniciada como",
|
||||||
"deviceCodeEnterPrompt": "Digite o código exibido no dispositivo",
|
"deviceCodeEnterPrompt": "Digite o código exibido no dispositivo",
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Acesso a todas as organizações que sua conta tem acesso a",
|
"deviceOrganizationsAccess": "Acesso a todas as organizações que sua conta tem acesso a",
|
||||||
"deviceAuthorize": "Autorizar {applicationName}",
|
"deviceAuthorize": "Autorizar {applicationName}",
|
||||||
"deviceConnected": "Dispositivo Conectado!",
|
"deviceConnected": "Dispositivo Conectado!",
|
||||||
"deviceAuthorizedMessage": "O dispositivo está autorizado a acessar sua conta.",
|
"deviceAuthorizedMessage": "O dispositivo está autorizado a acessar sua conta. Por favor, retorne ao aplicativo cliente.",
|
||||||
"pangolinCloud": "Nuvem do Pangolin",
|
"pangolinCloud": "Nuvem do Pangolin",
|
||||||
"viewDevices": "Ver Dispositivos",
|
"viewDevices": "Ver Dispositivos",
|
||||||
"viewDevicesDescription": "Gerencie seus dispositivos conectados",
|
"viewDevicesDescription": "Gerencie seus dispositivos conectados",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Não é você? Use uma conta diferente.",
|
"deviceLoginUseDifferentAccount": "Não é você? Use uma conta diferente.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Um dispositivo está solicitando acesso a essa conta.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Um dispositivo está solicitando acesso a essa conta.",
|
||||||
|
"loginSelectAuthenticationMethod": "Selecione um método de autenticação para continuar.",
|
||||||
"noData": "Nenhum dado encontrado",
|
"noData": "Nenhum dado encontrado",
|
||||||
"machineClients": "Clientes de máquina",
|
"machineClients": "Clientes de máquina",
|
||||||
"install": "Instale",
|
"install": "Instale",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Serviço Temporariamente Indisponível",
|
"maintenanceScreenTitle": "Serviço Temporariamente Indisponível",
|
||||||
"maintenanceScreenMessage": "Estamos enfrentando dificuldades técnicas no momento. Por favor, volte em breve.",
|
"maintenanceScreenMessage": "Estamos enfrentando dificuldades técnicas no momento. Por favor, volte em breve.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Conclusão Estimada:",
|
"maintenanceScreenEstimatedCompletion": "Conclusão Estimada:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório"
|
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório",
|
||||||
|
"available": "Disponível",
|
||||||
|
"archived": "Arquivado",
|
||||||
|
"noArchivedDevices": "Nenhum dispositivo arquivado encontrado",
|
||||||
|
"deviceArchived": "Dispositivo arquivado",
|
||||||
|
"deviceArchivedDescription": "O dispositivo foi arquivado com sucesso.",
|
||||||
|
"errorArchivingDevice": "Erro ao arquivar dispositivo",
|
||||||
|
"failedToArchiveDevice": "Falha ao arquivar dispositivo",
|
||||||
|
"deviceQuestionArchive": "Tem certeza que deseja arquivar este dispositivo?",
|
||||||
|
"deviceMessageArchive": "O dispositivo será arquivado e removido da sua lista de dispositivos ativos.",
|
||||||
|
"deviceArchiveConfirm": "Arquivar dispositivo",
|
||||||
|
"archiveDevice": "Arquivar dispositivo",
|
||||||
|
"archive": "Arquivo",
|
||||||
|
"deviceUnarchived": "Dispositivo desarquivado",
|
||||||
|
"deviceUnarchivedDescription": "O dispositivo foi desarquivado com sucesso.",
|
||||||
|
"errorUnarchivingDevice": "Erro ao desarquivar dispositivo",
|
||||||
|
"failedToUnarchiveDevice": "Falha ao desarquivar dispositivo",
|
||||||
|
"unarchive": "Desarquivar",
|
||||||
|
"archiveClient": "Arquivar Cliente",
|
||||||
|
"archiveClientQuestion": "Tem certeza que deseja arquivar este cliente?",
|
||||||
|
"archiveClientMessage": "O cliente será arquivado e removido da sua lista de clientes ativos.",
|
||||||
|
"archiveClientConfirm": "Arquivar Cliente",
|
||||||
|
"blockClient": "Bloco do Cliente",
|
||||||
|
"blockClientQuestion": "Tem certeza que deseja bloquear este cliente?",
|
||||||
|
"blockClientMessage": "O dispositivo será forçado a desconectar se estiver conectado. Você pode desbloquear o dispositivo mais tarde.",
|
||||||
|
"blockClientConfirm": "Bloco do Cliente",
|
||||||
|
"active": "ativo",
|
||||||
|
"usernameOrEmail": "Usuário ou Email",
|
||||||
|
"selectYourOrganization": "Selecione sua organização",
|
||||||
|
"signInTo": "Iniciar sessão em",
|
||||||
|
"signInWithPassword": "Continuar com a senha",
|
||||||
|
"noAuthMethodsAvailable": "Nenhum método de autenticação disponível para esta organização.",
|
||||||
|
"enterPassword": "Digite sua senha",
|
||||||
|
"enterMfaCode": "Insira o código do seu aplicativo autenticador",
|
||||||
|
"securityKeyRequired": "Por favor, utilize sua chave de segurança para entrar.",
|
||||||
|
"needToUseAnotherAccount": "Precisa usar uma conta diferente?",
|
||||||
|
"loginLegalDisclaimer": "Ao clicar nos botões abaixo, você reconhece que leu, entende e concorda com os <termsOfService>Termos de Serviço</termsOfService> e a <privacyPolicy>Política de Privacidade</privacyPolicy>.",
|
||||||
|
"termsOfService": "Termos de Serviço",
|
||||||
|
"privacyPolicy": "Política de Privacidade",
|
||||||
|
"userNotFoundWithUsername": "Nenhum usuário encontrado com este nome de usuário.",
|
||||||
|
"verify": "Verificar",
|
||||||
|
"signIn": "Iniciar sessão",
|
||||||
|
"forgotPassword": "Esqueceu a senha?",
|
||||||
|
"orgSignInTip": "Se você já fez login antes, você pode digitar seu nome de usuário ou e-mail acima para autenticar com o provedor de identidade da sua organização. É mais fácil!",
|
||||||
|
"continueAnyway": "Continuar mesmo assim",
|
||||||
|
"dontShowAgain": "Não mostrar novamente",
|
||||||
|
"orgSignInNotice": "Você sabia?",
|
||||||
|
"signupOrgNotice": "Tentando fazer login?",
|
||||||
|
"signupOrgTip": "Você está tentando entrar através do provedor de identidade da sua organização?",
|
||||||
|
"signupOrgLink": "Faça login ou inscreva-se com sua organização em vez disso",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Use uma Conta Diferente",
|
||||||
|
"logIn": "Iniciar sessão",
|
||||||
|
"deviceInformation": "Informações do dispositivo",
|
||||||
|
"deviceInformationDescription": "Informações sobre o dispositivo e o agente",
|
||||||
|
"deviceSecurity": "Segurança do dispositivo",
|
||||||
|
"deviceSecurityDescription": "Informações sobre postagem de segurança",
|
||||||
|
"platform": "Plataforma",
|
||||||
|
"macosVersion": "Versão do macOS",
|
||||||
|
"windowsVersion": "Versão do Windows",
|
||||||
|
"iosVersion": "Versão para iOS",
|
||||||
|
"androidVersion": "Versão do Android",
|
||||||
|
"osVersion": "Versão do SO",
|
||||||
|
"kernelVersion": "Versão do Kernel",
|
||||||
|
"deviceModel": "Modelo do dispositivo",
|
||||||
|
"serialNumber": "Número de Série",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Visto primeiro",
|
||||||
|
"lastSeen": "Visto por último",
|
||||||
|
"biometricsEnabled": "Biometria habilitada",
|
||||||
|
"diskEncrypted": "Disco criptografado",
|
||||||
|
"firewallEnabled": "Firewall habilitado",
|
||||||
|
"autoUpdatesEnabled": "Atualizações Automáticas Habilitadas",
|
||||||
|
"tpmAvailable": "TPM disponível",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Proteção da Integridade do Sistema (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Modo Furtivo do Firewall",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Ver informações e configurações do dispositivo",
|
||||||
|
"devicePendingApprovalDescription": "Este dispositivo está aguardando aprovação",
|
||||||
|
"deviceBlockedDescription": "Este dispositivo está bloqueado no momento. Ele não será capaz de se conectar a qualquer recurso a menos que seja desbloqueado.",
|
||||||
|
"unblockClient": "Desbloquear Cliente",
|
||||||
|
"unblockClientDescription": "O dispositivo foi desbloqueado",
|
||||||
|
"unarchiveClient": "Desarquivar Cliente",
|
||||||
|
"unarchiveClientDescription": "O dispositivo foi desarquivado",
|
||||||
|
"block": "Bloquear",
|
||||||
|
"unblock": "Desbloquear",
|
||||||
|
"deviceActions": "Ações do dispositivo",
|
||||||
|
"deviceActionsDescription": "Gerenciar status e acesso do dispositivo",
|
||||||
|
"devicePendingApprovalBannerDescription": "Este dispositivo está pendente de aprovação. Não será possível conectar-se a recursos até ser aprovado.",
|
||||||
|
"connected": "Conectado",
|
||||||
|
"disconnected": "Desconectado",
|
||||||
|
"approvalsEmptyStateTitle": "Aprovações do dispositivo não habilitado",
|
||||||
|
"approvalsEmptyStateDescription": "Habilitar aprovações do dispositivo para cargos que exigem aprovação do administrador antes que os usuários possam conectar novos dispositivos.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Ir para Funções",
|
||||||
|
"approvalsEmptyStateStep1Description": "Navegue até as configurações dos papéis da sua organização para configurar as aprovações de dispositivo.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Habilitar Aprovações do Dispositivo",
|
||||||
|
"approvalsEmptyStateStep2Description": "Editar uma função e habilitar a opção 'Exigir aprovação de dispositivos'. Usuários com essa função precisarão de aprovação de administrador para novos dispositivos.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Pré-visualização: Quando ativado, solicitações de dispositivo pendentes aparecerão aqui para revisão",
|
||||||
|
"approvalsEmptyStateButtonText": "Gerir Funções"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Подключить любую сеть",
|
"sitesBannerTitle": "Подключить любую сеть",
|
||||||
"sitesBannerDescription": "Сайт — это соединение с удаленной сетью, которое позволяет Pangolin предоставлять доступ к ресурсам, будь они общедоступными или частными, пользователям в любом месте. Установите сетевой коннектор сайта (Newt) там, где можно запустить исполняемый файл или контейнер, чтобы установить соединение.",
|
"sitesBannerDescription": "Сайт — это соединение с удаленной сетью, которое позволяет Pangolin предоставлять доступ к ресурсам, будь они общедоступными или частными, пользователям в любом месте. Установите сетевой коннектор сайта (Newt) там, где можно запустить исполняемый файл или контейнер, чтобы установить соединение.",
|
||||||
"sitesBannerButtonText": "Установить сайт",
|
"sitesBannerButtonText": "Установить сайт",
|
||||||
|
"approvalsBannerTitle": "Одобрить или запретить доступ к устройству",
|
||||||
|
"approvalsBannerDescription": "Просмотрите и подтвердите или отклоните запросы на доступ к устройству от пользователей. Когда требуется подтверждение устройства, пользователи должны получить одобрение администратора, прежде чем их устройства смогут подключиться к ресурсам вашей организации.",
|
||||||
|
"approvalsBannerButtonText": "Узнать больше",
|
||||||
"siteCreate": "Создать сайт",
|
"siteCreate": "Создать сайт",
|
||||||
"siteCreateDescription2": "Следуйте инструкциям ниже для создания и подключения нового сайта",
|
"siteCreateDescription2": "Следуйте инструкциям ниже для создания и подключения нового сайта",
|
||||||
"siteCreateDescription": "Создайте новый сайт для начала подключения ресурсов",
|
"siteCreateDescription": "Создайте новый сайт для начала подключения ресурсов",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Поиск ролей...",
|
"accessRolesSearch": "Поиск ролей...",
|
||||||
"accessRolesAdd": "Добавить роль",
|
"accessRolesAdd": "Добавить роль",
|
||||||
"accessRoleDelete": "Удалить роль",
|
"accessRoleDelete": "Удалить роль",
|
||||||
|
"accessApprovalsManage": "Управление утверждениями",
|
||||||
|
"accessApprovalsDescription": "Просмотр и управление утверждениями в ожидании доступа к этой организации",
|
||||||
"description": "Описание",
|
"description": "Описание",
|
||||||
"inviteTitle": "Открытые приглашения",
|
"inviteTitle": "Открытые приглашения",
|
||||||
"inviteDescription": "Управление приглашениями для присоединения других пользователей к организации",
|
"inviteDescription": "Управление приглашениями для присоединения других пользователей к организации",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Укажите срок действия",
|
"selectDuration": "Укажите срок действия",
|
||||||
"selectResource": "Выберите ресурс",
|
"selectResource": "Выберите ресурс",
|
||||||
"filterByResource": "Фильтровать по ресурсам",
|
"filterByResource": "Фильтровать по ресурсам",
|
||||||
|
"selectApprovalState": "Выберите состояние одобрения",
|
||||||
|
"filterByApprovalState": "Фильтр по состоянию утверждения",
|
||||||
|
"approvalListEmpty": "Нет утверждений",
|
||||||
|
"approvalState": "Состояние одобрения",
|
||||||
|
"approve": "Одобрить",
|
||||||
|
"approved": "Одобрено",
|
||||||
|
"denied": "Отказано",
|
||||||
|
"deniedApproval": "Отказано в одобрении",
|
||||||
|
"all": "Все",
|
||||||
|
"deny": "Запретить",
|
||||||
|
"viewDetails": "Детали",
|
||||||
|
"requestingNewDeviceApproval": "запросил новое устройство",
|
||||||
"resetFilters": "Сбросить фильтры",
|
"resetFilters": "Сбросить фильтры",
|
||||||
"totalBlocked": "Запросы заблокированы Панголином",
|
"totalBlocked": "Запросы заблокированы Панголином",
|
||||||
"totalRequests": "Всего запросов",
|
"totalRequests": "Всего запросов",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "Страны",
|
"countries": "Страны",
|
||||||
"accessRoleCreate": "Создание роли",
|
"accessRoleCreate": "Создание роли",
|
||||||
"accessRoleCreateDescription": "Создайте новую роль для группы пользователей и выдавайте им разрешения.",
|
"accessRoleCreateDescription": "Создайте новую роль для группы пользователей и выдавайте им разрешения.",
|
||||||
|
"accessRoleEdit": "Изменить роль",
|
||||||
|
"accessRoleEditDescription": "Редактировать информацию о роли.",
|
||||||
"accessRoleCreateSubmit": "Создать роль",
|
"accessRoleCreateSubmit": "Создать роль",
|
||||||
"accessRoleCreated": "Роль создана",
|
"accessRoleCreated": "Роль создана",
|
||||||
"accessRoleCreatedDescription": "Роль была успешно создана.",
|
"accessRoleCreatedDescription": "Роль была успешно создана.",
|
||||||
"accessRoleErrorCreate": "Не удалось создать роль",
|
"accessRoleErrorCreate": "Не удалось создать роль",
|
||||||
"accessRoleErrorCreateDescription": "Произошла ошибка при создании роли.",
|
"accessRoleErrorCreateDescription": "Произошла ошибка при создании роли.",
|
||||||
|
"accessRoleUpdateSubmit": "Обновить роль",
|
||||||
|
"accessRoleUpdated": "Роль обновлена",
|
||||||
|
"accessRoleUpdatedDescription": "Роль была успешно обновлена.",
|
||||||
|
"accessApprovalUpdated": "Выполнено утверждение",
|
||||||
|
"accessApprovalApprovedDescription": "Принять решение об утверждении запроса.",
|
||||||
|
"accessApprovalDeniedDescription": "Отказано в запросе об утверждении.",
|
||||||
|
"accessRoleErrorUpdate": "Не удалось обновить роль",
|
||||||
|
"accessRoleErrorUpdateDescription": "Произошла ошибка при обновлении роли.",
|
||||||
|
"accessApprovalErrorUpdate": "Не удалось обработать подтверждение",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Произошла ошибка при обработке одобрения.",
|
||||||
"accessRoleErrorNewRequired": "Новая роль обязательна",
|
"accessRoleErrorNewRequired": "Новая роль обязательна",
|
||||||
"accessRoleErrorRemove": "Не удалось удалить роль",
|
"accessRoleErrorRemove": "Не удалось удалить роль",
|
||||||
"accessRoleErrorRemoveDescription": "Произошла ошибка при удалении роли.",
|
"accessRoleErrorRemoveDescription": "Произошла ошибка при удалении роли.",
|
||||||
"accessRoleName": "Название роли",
|
"accessRoleName": "Название роли",
|
||||||
"accessRoleQuestionRemove": "Вы собираетесь удалить роль {name}. Это действие нельзя отменить.",
|
"accessRoleQuestionRemove": "Вы собираетесь удалить `{name}` роль. Это действие нельзя отменить.",
|
||||||
"accessRoleRemove": "Удалить роль",
|
"accessRoleRemove": "Удалить роль",
|
||||||
"accessRoleRemoveDescription": "Удалить роль из организации",
|
"accessRoleRemoveDescription": "Удалить роль из организации",
|
||||||
"accessRoleRemoveSubmit": "Удалить роль",
|
"accessRoleRemoveSubmit": "Удалить роль",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "Пожалуйста, обратитесь к администратору",
|
"passwordResetSmtpRequired": "Пожалуйста, обратитесь к администратору",
|
||||||
"passwordResetSmtpRequiredDescription": "Для сброса пароля необходим код сброса пароля. Обратитесь к администратору за помощью.",
|
"passwordResetSmtpRequiredDescription": "Для сброса пароля необходим код сброса пароля. Обратитесь к администратору за помощью.",
|
||||||
"passwordBack": "Назад к паролю",
|
"passwordBack": "Назад к паролю",
|
||||||
"loginBack": "Вернуться к входу",
|
"loginBack": "Вернуться на главную страницу входа",
|
||||||
"signup": "Регистрация",
|
"signup": "Регистрация",
|
||||||
"loginStart": "Войдите для начала работы",
|
"loginStart": "Войдите для начала работы",
|
||||||
"idpOidcTokenValidating": "Проверка OIDC токена",
|
"idpOidcTokenValidating": "Проверка OIDC токена",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Обновить организацию IDP",
|
"actionUpdateIdpOrg": "Обновить организацию IDP",
|
||||||
"actionCreateClient": "Создать Клиента",
|
"actionCreateClient": "Создать Клиента",
|
||||||
"actionDeleteClient": "Удалить Клиента",
|
"actionDeleteClient": "Удалить Клиента",
|
||||||
|
"actionArchiveClient": "Архивировать клиента",
|
||||||
|
"actionUnarchiveClient": "Разархивировать клиента",
|
||||||
|
"actionBlockClient": "Блокировать клиента",
|
||||||
|
"actionUnblockClient": "Разблокировать клиента",
|
||||||
"actionUpdateClient": "Обновить Клиента",
|
"actionUpdateClient": "Обновить Клиента",
|
||||||
"actionListClients": "Список Клиентов",
|
"actionListClients": "Список Клиентов",
|
||||||
"actionGetClient": "Получить Клиента",
|
"actionGetClient": "Получить Клиента",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Поиск...",
|
"searchProgress": "Поиск...",
|
||||||
"create": "Создать",
|
"create": "Создать",
|
||||||
"orgs": "Организации",
|
"orgs": "Организации",
|
||||||
"loginError": "Произошла ошибка при входе",
|
"loginError": "Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз.",
|
||||||
"loginRequiredForDevice": "Для аутентификации устройства необходимо войти в систему.",
|
"loginRequiredForDevice": "Логин необходим для вашего устройства.",
|
||||||
"passwordForgot": "Забыли пароль?",
|
"passwordForgot": "Забыли пароль?",
|
||||||
"otpAuth": "Двухфакторная аутентификация",
|
"otpAuth": "Двухфакторная аутентификация",
|
||||||
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
|
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
|
||||||
"otpAuthSubmit": "Отправить код",
|
"otpAuthSubmit": "Отправить код",
|
||||||
"idpContinue": "Или продолжить с",
|
"idpContinue": "Или продолжить с",
|
||||||
"otpAuthBack": "Вернуться к входу",
|
"otpAuthBack": "Назад к паролю",
|
||||||
"navbar": "Навигационное меню",
|
"navbar": "Навигационное меню",
|
||||||
"navbarDescription": "Главное навигационное меню приложения",
|
"navbarDescription": "Главное навигационное меню приложения",
|
||||||
"navbarDocsLink": "Документация",
|
"navbarDocsLink": "Документация",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Обзор",
|
"sidebarOverview": "Обзор",
|
||||||
"sidebarHome": "Главная",
|
"sidebarHome": "Главная",
|
||||||
"sidebarSites": "Сайты",
|
"sidebarSites": "Сайты",
|
||||||
|
"sidebarApprovals": "Запросы на утверждение",
|
||||||
"sidebarResources": "Ресурсы",
|
"sidebarResources": "Ресурсы",
|
||||||
"sidebarProxyResources": "Публичный",
|
"sidebarProxyResources": "Публичный",
|
||||||
"sidebarClientResources": "Приватный",
|
"sidebarClientResources": "Приватный",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Поставщики удостоверений",
|
"sidebarIdentityProviders": "Поставщики удостоверений",
|
||||||
"sidebarLicense": "Лицензия",
|
"sidebarLicense": "Лицензия",
|
||||||
"sidebarClients": "Клиенты",
|
"sidebarClients": "Клиенты",
|
||||||
"sidebarUserDevices": "Пользователи",
|
"sidebarUserDevices": "Устройства пользователя",
|
||||||
"sidebarMachineClients": "Машины",
|
"sidebarMachineClients": "Машины",
|
||||||
"sidebarDomains": "Домены",
|
"sidebarDomains": "Домены",
|
||||||
"sidebarGeneral": "Управление",
|
"sidebarGeneral": "Управление",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Произошла ошибка при создании учётной записи администратора сервера.",
|
"setupErrorCreateAdmin": "Произошла ошибка при создании учётной записи администратора сервера.",
|
||||||
"certificateStatus": "Статус сертификата",
|
"certificateStatus": "Статус сертификата",
|
||||||
"loading": "Загрузка",
|
"loading": "Загрузка",
|
||||||
|
"loadingAnalytics": "Загрузка аналитики",
|
||||||
"restart": "Перезагрузка",
|
"restart": "Перезагрузка",
|
||||||
"domains": "Домены",
|
"domains": "Домены",
|
||||||
"domainsDescription": "Создание и управление доменами, доступными в организации",
|
"domainsDescription": "Создание и управление доменами, доступными в организации",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Не удалось обновить данные",
|
"refreshError": "Не удалось обновить данные",
|
||||||
"verified": "Подтверждено",
|
"verified": "Подтверждено",
|
||||||
"pending": "В ожидании",
|
"pending": "В ожидании",
|
||||||
|
"pendingApproval": "Ожидает утверждения",
|
||||||
"sidebarBilling": "Выставление счетов",
|
"sidebarBilling": "Выставление счетов",
|
||||||
"billing": "Выставление счетов",
|
"billing": "Выставление счетов",
|
||||||
"orgBillingDescription": "Управление платежной информацией и подписками",
|
"orgBillingDescription": "Управление платежной информацией и подписками",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Ключ безопасности успешно удален",
|
"securityKeyRemoveSuccess": "Ключ безопасности успешно удален",
|
||||||
"securityKeyRemoveError": "Не удалось удалить ключ безопасности",
|
"securityKeyRemoveError": "Не удалось удалить ключ безопасности",
|
||||||
"securityKeyLoadError": "Не удалось загрузить ключи безопасности",
|
"securityKeyLoadError": "Не удалось загрузить ключи безопасности",
|
||||||
"securityKeyLogin": "Продолжить с ключом безопасности",
|
"securityKeyLogin": "Использовать ключ безопасности",
|
||||||
"securityKeyAuthError": "Не удалось аутентифицироваться с ключом безопасности",
|
"securityKeyAuthError": "Не удалось аутентифицироваться с ключом безопасности",
|
||||||
"securityKeyRecommendation": "Зарегистрируйте резервный ключ безопасности на другом устройстве, чтобы всегда иметь доступ к вашему аккаунту.",
|
"securityKeyRecommendation": "Зарегистрируйте резервный ключ безопасности на другом устройстве, чтобы всегда иметь доступ к вашему аккаунту.",
|
||||||
"registering": "Регистрация...",
|
"registering": "Регистрация...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Интервал здоровых состояний",
|
"IntervalSeconds": "Интервал здоровых состояний",
|
||||||
"timeoutSeconds": "Таймаут (сек)",
|
"timeoutSeconds": "Таймаут (сек)",
|
||||||
"timeIsInSeconds": "Время указано в секундах",
|
"timeIsInSeconds": "Время указано в секундах",
|
||||||
|
"requireDeviceApproval": "Требовать подтверждения устройства",
|
||||||
|
"requireDeviceApprovalDescription": "Пользователям с этой ролью нужны новые устройства, одобренные администратором, прежде чем они смогут подключаться и получать доступ к ресурсам.",
|
||||||
"retryAttempts": "Количество попыток повторного запроса",
|
"retryAttempts": "Количество попыток повторного запроса",
|
||||||
"expectedResponseCodes": "Ожидаемые коды ответов",
|
"expectedResponseCodes": "Ожидаемые коды ответов",
|
||||||
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
|
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Внутренних ресурсов не найдено.",
|
"resourcesTableNoInternalResourcesFound": "Внутренних ресурсов не найдено.",
|
||||||
"resourcesTableDestination": "Пункт назначения",
|
"resourcesTableDestination": "Пункт назначения",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "Псевдоним адреса",
|
||||||
|
"resourcesTableAliasAddressInfo": "Этот адрес является частью вспомогательной подсети организации. Он используется для разрешения псевдонимов с использованием внутреннего разрешения DNS.",
|
||||||
"resourcesTableClients": "Клиенты",
|
"resourcesTableClients": "Клиенты",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "и доступны только внутренне при подключении с клиентом.",
|
"resourcesTableAndOnlyAccessibleInternally": "и доступны только внутренне при подключении с клиентом.",
|
||||||
"resourcesTableNoTargets": "Нет ярлыков",
|
"resourcesTableNoTargets": "Нет ярлыков",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Выберите своего поставщика удостоверений личности для продолжения",
|
"orgAuthChooseIdpDescription": "Выберите своего поставщика удостоверений личности для продолжения",
|
||||||
"orgAuthNoIdpConfigured": "Эта организация не имеет настроенных поставщиков идентификационных данных. Вместо этого вы можете войти в свой Pangolin.",
|
"orgAuthNoIdpConfigured": "Эта организация не имеет настроенных поставщиков идентификационных данных. Вместо этого вы можете войти в свой Pangolin.",
|
||||||
"orgAuthSignInWithPangolin": "Войти через Pangolin",
|
"orgAuthSignInWithPangolin": "Войти через Pangolin",
|
||||||
"orgAuthSignInToOrg": "Войдите в организацию",
|
"orgAuthSignInToOrg": "Войти в организацию",
|
||||||
"orgAuthSelectOrgTitle": "Вход в организацию",
|
"orgAuthSelectOrgTitle": "Вход в организацию",
|
||||||
"orgAuthSelectOrgDescription": "Введите ID вашей организации, чтобы продолжить",
|
"orgAuthSelectOrgDescription": "Введите ID вашей организации, чтобы продолжить",
|
||||||
"orgAuthOrgIdPlaceholder": "ваша-организация",
|
"orgAuthOrgIdPlaceholder": "ваша-организация",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Код должен быть 9 символов (например, A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Код должен быть 9 символов (например, A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Неверный или просроченный код",
|
"deviceCodeInvalidOrExpired": "Неверный или просроченный код",
|
||||||
"deviceCodeVerifyFailed": "Не удалось проверить код устройства",
|
"deviceCodeVerifyFailed": "Не удалось проверить код устройства",
|
||||||
|
"deviceCodeValidating": "Проверка кода устройства...",
|
||||||
|
"deviceCodeVerifying": "Проверка авторизации устройства...",
|
||||||
"signedInAs": "Вы вошли как",
|
"signedInAs": "Вы вошли как",
|
||||||
"deviceCodeEnterPrompt": "Введите код, отображаемый на устройстве",
|
"deviceCodeEnterPrompt": "Введите код, отображаемый на устройстве",
|
||||||
"continue": "Продолжить",
|
"continue": "Продолжить",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Доступ ко всем организациям, к которым ваш аккаунт имеет доступ",
|
"deviceOrganizationsAccess": "Доступ ко всем организациям, к которым ваш аккаунт имеет доступ",
|
||||||
"deviceAuthorize": "Авторизовать {applicationName}",
|
"deviceAuthorize": "Авторизовать {applicationName}",
|
||||||
"deviceConnected": "Устройство подключено!",
|
"deviceConnected": "Устройство подключено!",
|
||||||
"deviceAuthorizedMessage": "Устройство авторизовано для доступа к вашей учетной записи.",
|
"deviceAuthorizedMessage": "Устройство авторизовано для доступа к вашей учетной записи. Вернитесь в клиентское приложение.",
|
||||||
"pangolinCloud": "Облако Панголина",
|
"pangolinCloud": "Облако Панголина",
|
||||||
"viewDevices": "Просмотр устройств",
|
"viewDevices": "Просмотр устройств",
|
||||||
"viewDevicesDescription": "Управление подключенными устройствами",
|
"viewDevicesDescription": "Управление подключенными устройствами",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "Не вы? Используйте другую учетную запись.",
|
"deviceLoginUseDifferentAccount": "Не вы? Используйте другую учетную запись.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запрашивает доступ к этой учетной записи.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запрашивает доступ к этой учетной записи.",
|
||||||
|
"loginSelectAuthenticationMethod": "Выберите метод аутентификации для продолжения.",
|
||||||
"noData": "Нет данных",
|
"noData": "Нет данных",
|
||||||
"machineClients": "Машинные клиенты",
|
"machineClients": "Машинные клиенты",
|
||||||
"install": "Установить",
|
"install": "Установить",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Сервис временно недоступен",
|
"maintenanceScreenTitle": "Сервис временно недоступен",
|
||||||
"maintenanceScreenMessage": "В настоящее время мы испытываем технические трудности. Пожалуйста, зайдите позже.",
|
"maintenanceScreenMessage": "В настоящее время мы испытываем технические трудности. Пожалуйста, зайдите позже.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Предполагаемое завершение:",
|
"maintenanceScreenEstimatedCompletion": "Предполагаемое завершение:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес."
|
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес.",
|
||||||
|
"available": "Доступно",
|
||||||
|
"archived": "Архивировано",
|
||||||
|
"noArchivedDevices": "Архивные устройства не найдены",
|
||||||
|
"deviceArchived": "Устройство архивировано",
|
||||||
|
"deviceArchivedDescription": "Устройство успешно архивировано.",
|
||||||
|
"errorArchivingDevice": "Ошибка архивирования устройства",
|
||||||
|
"failedToArchiveDevice": "Не удалось архивировать устройство",
|
||||||
|
"deviceQuestionArchive": "Вы уверены, что хотите архивировать это устройство?",
|
||||||
|
"deviceMessageArchive": "Устройство будет архивировано и удалено из вашего списка активных устройств.",
|
||||||
|
"deviceArchiveConfirm": "Архивировать устройство",
|
||||||
|
"archiveDevice": "Архивировать устройство",
|
||||||
|
"archive": "Архивировать",
|
||||||
|
"deviceUnarchived": "Устройство разархивировано",
|
||||||
|
"deviceUnarchivedDescription": "Устройство было успешно разархивировано.",
|
||||||
|
"errorUnarchivingDevice": "Ошибка разархивирования устройства",
|
||||||
|
"failedToUnarchiveDevice": "Не удалось распаковать устройство",
|
||||||
|
"unarchive": "Разархивировать",
|
||||||
|
"archiveClient": "Архивировать клиента",
|
||||||
|
"archiveClientQuestion": "Вы уверены, что хотите архивировать этого клиента?",
|
||||||
|
"archiveClientMessage": "Клиент будет архивирован и удален из вашего активного списка клиентов.",
|
||||||
|
"archiveClientConfirm": "Архивировать клиента",
|
||||||
|
"blockClient": "Блокировать клиента",
|
||||||
|
"blockClientQuestion": "Вы уверены, что хотите заблокировать этого клиента?",
|
||||||
|
"blockClientMessage": "Устройство будет вынуждено отключиться, если подключено в данный момент. Вы можете разблокировать устройство позже.",
|
||||||
|
"blockClientConfirm": "Блокировать клиента",
|
||||||
|
"active": "Активный",
|
||||||
|
"usernameOrEmail": "Имя пользователя или Email",
|
||||||
|
"selectYourOrganization": "Выберите вашу организацию",
|
||||||
|
"signInTo": "Войти в",
|
||||||
|
"signInWithPassword": "Продолжить с паролем",
|
||||||
|
"noAuthMethodsAvailable": "Методы аутентификации для этой организации недоступны.",
|
||||||
|
"enterPassword": "Введите ваш пароль",
|
||||||
|
"enterMfaCode": "Введите код из вашего приложения-аутентификатора",
|
||||||
|
"securityKeyRequired": "Пожалуйста, используйте ваш защитный ключ для входа.",
|
||||||
|
"needToUseAnotherAccount": "Нужно использовать другой аккаунт?",
|
||||||
|
"loginLegalDisclaimer": "Нажимая на кнопки ниже, вы подтверждаете, что прочитали, поняли и согласны с <termsOfService>Условиями использования</termsOfService> и <privacyPolicy>Политикой конфиденциальности</privacyPolicy>.",
|
||||||
|
"termsOfService": "Условия предоставления услуг",
|
||||||
|
"privacyPolicy": "Политика конфиденциальности",
|
||||||
|
"userNotFoundWithUsername": "Пользователь с таким именем пользователя не найден.",
|
||||||
|
"verify": "Подтвердить",
|
||||||
|
"signIn": "Войти",
|
||||||
|
"forgotPassword": "Забыли пароль?",
|
||||||
|
"orgSignInTip": "Если вы вошли в систему ранее, вы можете ввести имя пользователя или адрес электронной почты, чтобы войти в систему с поставщиком идентификации вашей организации. Это проще!",
|
||||||
|
"continueAnyway": "Все равно продолжить",
|
||||||
|
"dontShowAgain": "Больше не показывать",
|
||||||
|
"orgSignInNotice": "Знаете ли вы?",
|
||||||
|
"signupOrgNotice": "Пытаетесь войти?",
|
||||||
|
"signupOrgTip": "Вы пытаетесь войти через оператора идентификации вашей организации?",
|
||||||
|
"signupOrgLink": "Войдите или зарегистрируйтесь через вашу организацию",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Использовать другую учетную запись",
|
||||||
|
"logIn": "Войти",
|
||||||
|
"deviceInformation": "Информация об устройстве",
|
||||||
|
"deviceInformationDescription": "Информация о устройстве и агенте",
|
||||||
|
"deviceSecurity": "Безопасность устройства",
|
||||||
|
"deviceSecurityDescription": "Информация о позе безопасности устройства",
|
||||||
|
"platform": "Платформа",
|
||||||
|
"macosVersion": "Версия macOS",
|
||||||
|
"windowsVersion": "Версия Windows",
|
||||||
|
"iosVersion": "Версия iOS",
|
||||||
|
"androidVersion": "Версия Android",
|
||||||
|
"osVersion": "Версия ОС",
|
||||||
|
"kernelVersion": "Версия ядра",
|
||||||
|
"deviceModel": "Модель устройства",
|
||||||
|
"serialNumber": "Серийный номер",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "Первый раз виден",
|
||||||
|
"lastSeen": "Последнее посещение",
|
||||||
|
"biometricsEnabled": "Включены биометрические данные",
|
||||||
|
"diskEncrypted": "Диск зашифрован",
|
||||||
|
"firewallEnabled": "Брандмауэр включен",
|
||||||
|
"autoUpdatesEnabled": "Автоматические обновления включены",
|
||||||
|
"tpmAvailable": "Доступно TPM",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Защита целостности системы (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Стилс-режим брандмауэра",
|
||||||
|
"linuxAppArmorEnabled": "Броня",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Просмотр информации и настроек устройства",
|
||||||
|
"devicePendingApprovalDescription": "Это устройство ожидает одобрения",
|
||||||
|
"deviceBlockedDescription": "Это устройство заблокировано. Оно не сможет подключаться к ресурсам, если не разблокировано.",
|
||||||
|
"unblockClient": "Разблокировать клиента",
|
||||||
|
"unblockClientDescription": "Устройство разблокировано",
|
||||||
|
"unarchiveClient": "Разархивировать клиента",
|
||||||
|
"unarchiveClientDescription": "Устройство было разархивировано",
|
||||||
|
"block": "Блок",
|
||||||
|
"unblock": "Разблокировать",
|
||||||
|
"deviceActions": "Действия устройства",
|
||||||
|
"deviceActionsDescription": "Управление статусом устройства и доступом",
|
||||||
|
"devicePendingApprovalBannerDescription": "Это устройство ожидает одобрения. Он не сможет подключиться к ресурсам до утверждения.",
|
||||||
|
"connected": "Подключено",
|
||||||
|
"disconnected": "Отключено",
|
||||||
|
"approvalsEmptyStateTitle": "Утверждения устройства не включены",
|
||||||
|
"approvalsEmptyStateDescription": "Включите одобрение ролей для того, чтобы пользователи могли подключать новые устройства.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Перейти к ролям",
|
||||||
|
"approvalsEmptyStateStep1Description": "Перейдите в настройки ролей вашей организации для настройки утверждений устройств.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Включить утверждения устройства",
|
||||||
|
"approvalsEmptyStateStep2Description": "Редактировать роль и включить опцию 'Требовать утверждения устройств'. Пользователям с этой ролью потребуется подтверждение администратора для новых устройств.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Предпросмотр: Если включено, ожидающие запросы на устройство появятся здесь для проверки",
|
||||||
|
"approvalsEmptyStateButtonText": "Управление ролями"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "Herhangi Bir Ağa Bağlan",
|
"sitesBannerTitle": "Herhangi Bir Ağa Bağlan",
|
||||||
"sitesBannerDescription": "Bir site, Pangolin'in kullanıcılara, halka açık veya özel kaynaklara, her yerden erişim sağlamak için uzak bir ağa bağlantı sunmasıdır. Site ağı bağlantısını (Newt) çalıştırabileceğiniz her yere kurarak bağlantıyı kurunuz.",
|
"sitesBannerDescription": "Bir site, Pangolin'in kullanıcılara, halka açık veya özel kaynaklara, her yerden erişim sağlamak için uzak bir ağa bağlantı sunmasıdır. Site ağı bağlantısını (Newt) çalıştırabileceğiniz her yere kurarak bağlantıyı kurunuz.",
|
||||||
"sitesBannerButtonText": "Site Kur",
|
"sitesBannerButtonText": "Site Kur",
|
||||||
|
"approvalsBannerTitle": "Cihaz Erişimini Onayla veya Reddet",
|
||||||
|
"approvalsBannerDescription": "Kullanıcılardan gelen cihaz erişim isteklerini gözden geçirin ve onaylayın veya reddedin. Cihaz onaylarının gerekli olduğu durumlarda, kullanıcıların cihazlarının kuruluşunuzun kaynaklarına bağlanabilmesi için yönetici onayı alması gerekecektir.",
|
||||||
|
"approvalsBannerButtonText": "Daha fazla bilgi",
|
||||||
"siteCreate": "Site Oluştur",
|
"siteCreate": "Site Oluştur",
|
||||||
"siteCreateDescription2": "Yeni bir site oluşturup bağlanmak için aşağıdaki adımları izleyin",
|
"siteCreateDescription2": "Yeni bir site oluşturup bağlanmak için aşağıdaki adımları izleyin",
|
||||||
"siteCreateDescription": "Kaynaklarınızı bağlamaya başlamak için yeni bir site oluşturun",
|
"siteCreateDescription": "Kaynaklarınızı bağlamaya başlamak için yeni bir site oluşturun",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "Rolleri ara...",
|
"accessRolesSearch": "Rolleri ara...",
|
||||||
"accessRolesAdd": "Rol Ekle",
|
"accessRolesAdd": "Rol Ekle",
|
||||||
"accessRoleDelete": "Rolü Sil",
|
"accessRoleDelete": "Rolü Sil",
|
||||||
|
"accessApprovalsManage": "Onayları Yönet",
|
||||||
|
"accessApprovalsDescription": "Bu kuruluşa erişim için bekleyen onayları görüntüleyin ve yönetin",
|
||||||
"description": "Açıklama",
|
"description": "Açıklama",
|
||||||
"inviteTitle": "Açık Davetiyeler",
|
"inviteTitle": "Açık Davetiyeler",
|
||||||
"inviteDescription": "Organizasyona katılmak için diğer kullanıcılar için davetleri yönetin",
|
"inviteDescription": "Organizasyona katılmak için diğer kullanıcılar için davetleri yönetin",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "Süreyi seçin",
|
"selectDuration": "Süreyi seçin",
|
||||||
"selectResource": "Kaynak Seçin",
|
"selectResource": "Kaynak Seçin",
|
||||||
"filterByResource": "Kaynağa Göre Filtrele",
|
"filterByResource": "Kaynağa Göre Filtrele",
|
||||||
|
"selectApprovalState": "Onay Durumunu Seçin",
|
||||||
|
"filterByApprovalState": "Onay Durumuna Göre Filtrele",
|
||||||
|
"approvalListEmpty": "Onay yok",
|
||||||
|
"approvalState": "Onay Durumu",
|
||||||
|
"approve": "Onayla",
|
||||||
|
"approved": "Onaylandı",
|
||||||
|
"denied": "Reddedildi",
|
||||||
|
"deniedApproval": "Reddedilen Onay",
|
||||||
|
"all": "Tümü",
|
||||||
|
"deny": "Reddet",
|
||||||
|
"viewDetails": "Ayrıntıları Gör",
|
||||||
|
"requestingNewDeviceApproval": "yeni bir cihaz talep etti",
|
||||||
"resetFilters": "Filtreleri Sıfırla",
|
"resetFilters": "Filtreleri Sıfırla",
|
||||||
"totalBlocked": "Pangolin Tarafından Engellenen İstekler",
|
"totalBlocked": "Pangolin Tarafından Engellenen İstekler",
|
||||||
"totalRequests": "Toplam İstekler",
|
"totalRequests": "Toplam İstekler",
|
||||||
@@ -729,11 +746,23 @@
|
|||||||
"countries": "Ülkeler",
|
"countries": "Ülkeler",
|
||||||
"accessRoleCreate": "Rol Oluştur",
|
"accessRoleCreate": "Rol Oluştur",
|
||||||
"accessRoleCreateDescription": "Kullanıcıları gruplamak ve izinlerini yönetmek için yeni bir rol oluşturun.",
|
"accessRoleCreateDescription": "Kullanıcıları gruplamak ve izinlerini yönetmek için yeni bir rol oluşturun.",
|
||||||
|
"accessRoleEdit": "Rol Düzenle",
|
||||||
|
"accessRoleEditDescription": "Rol bilgilerini düzenleyin.",
|
||||||
"accessRoleCreateSubmit": "Rol Oluştur",
|
"accessRoleCreateSubmit": "Rol Oluştur",
|
||||||
"accessRoleCreated": "Rol oluşturuldu",
|
"accessRoleCreated": "Rol oluşturuldu",
|
||||||
"accessRoleCreatedDescription": "Rol başarıyla oluşturuldu.",
|
"accessRoleCreatedDescription": "Rol başarıyla oluşturuldu.",
|
||||||
"accessRoleErrorCreate": "Rol oluşturulamadı",
|
"accessRoleErrorCreate": "Rol oluşturulamadı",
|
||||||
"accessRoleErrorCreateDescription": "Rol oluşturulurken bir hata oluştu.",
|
"accessRoleErrorCreateDescription": "Rol oluşturulurken bir hata oluştu.",
|
||||||
|
"accessRoleUpdateSubmit": "Rolü Güncelle",
|
||||||
|
"accessRoleUpdated": "Rol güncellendi",
|
||||||
|
"accessRoleUpdatedDescription": "Rol başarıyla güncellendi.",
|
||||||
|
"accessApprovalUpdated": "Onay işlendi",
|
||||||
|
"accessApprovalApprovedDescription": "Onay İsteği kararını onaylandı olarak ayarlayın.",
|
||||||
|
"accessApprovalDeniedDescription": "Onay İsteği kararını reddedildi olarak ayarlayın.",
|
||||||
|
"accessRoleErrorUpdate": "Rol güncellenemedi",
|
||||||
|
"accessRoleErrorUpdateDescription": "Rol güncellenirken bir hata oluştu.",
|
||||||
|
"accessApprovalErrorUpdate": "Onay işlenemedi",
|
||||||
|
"accessApprovalErrorUpdateDescription": "Onay işlenirken bir hata oluştu.",
|
||||||
"accessRoleErrorNewRequired": "Yeni rol gerekli",
|
"accessRoleErrorNewRequired": "Yeni rol gerekli",
|
||||||
"accessRoleErrorRemove": "Rol kaldırılamadı",
|
"accessRoleErrorRemove": "Rol kaldırılamadı",
|
||||||
"accessRoleErrorRemoveDescription": "Rol kaldırılırken bir hata oluştu.",
|
"accessRoleErrorRemoveDescription": "Rol kaldırılırken bir hata oluştu.",
|
||||||
@@ -874,7 +903,7 @@
|
|||||||
"inviteAlready": "Davetiye gönderilmiş gibi görünüyor!",
|
"inviteAlready": "Davetiye gönderilmiş gibi görünüyor!",
|
||||||
"inviteAlreadyDescription": "Daveti kabul etmek için giriş yapmalı veya bir hesap oluşturmalısınız.",
|
"inviteAlreadyDescription": "Daveti kabul etmek için giriş yapmalı veya bir hesap oluşturmalısınız.",
|
||||||
"signupQuestion": "Zaten bir hesabınız var mı?",
|
"signupQuestion": "Zaten bir hesabınız var mı?",
|
||||||
"login": "Giriş yap",
|
"login": "Giriş Yap",
|
||||||
"resourceNotFound": "No resources found",
|
"resourceNotFound": "No resources found",
|
||||||
"resourceNotFoundDescription": "Erişmeye çalıştığınız kaynak mevcut değil.",
|
"resourceNotFoundDescription": "Erişmeye çalıştığınız kaynak mevcut değil.",
|
||||||
"pincodeRequirementsLength": "PIN kesinlikle 6 haneli olmalıdır",
|
"pincodeRequirementsLength": "PIN kesinlikle 6 haneli olmalıdır",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "Yönetici ile iletişime geçin",
|
"passwordResetSmtpRequired": "Yönetici ile iletişime geçin",
|
||||||
"passwordResetSmtpRequiredDescription": "Parolanızı sıfırlamak için bir parola sıfırlama kodu gereklidir. Yardım için yönetici ile iletişime geçin.",
|
"passwordResetSmtpRequiredDescription": "Parolanızı sıfırlamak için bir parola sıfırlama kodu gereklidir. Yardım için yönetici ile iletişime geçin.",
|
||||||
"passwordBack": "Şifreye Geri Dön",
|
"passwordBack": "Şifreye Geri Dön",
|
||||||
"loginBack": "Girişe geri dön",
|
"loginBack": "Ana oturum açma sayfasına geri dön",
|
||||||
"signup": "Kaydol",
|
"signup": "Kaydol",
|
||||||
"loginStart": "Başlamak için giriş yapın",
|
"loginStart": "Başlamak için giriş yapın",
|
||||||
"idpOidcTokenValidating": "OIDC token'ı doğrulanıyor",
|
"idpOidcTokenValidating": "OIDC token'ı doğrulanıyor",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "Kimlik Sağlayıcı Organizasyonu Güncelle",
|
"actionUpdateIdpOrg": "Kimlik Sağlayıcı Organizasyonu Güncelle",
|
||||||
"actionCreateClient": "Müşteri Oluştur",
|
"actionCreateClient": "Müşteri Oluştur",
|
||||||
"actionDeleteClient": "Müşteri Sil",
|
"actionDeleteClient": "Müşteri Sil",
|
||||||
|
"actionArchiveClient": "İstemci Arşivle",
|
||||||
|
"actionUnarchiveClient": "İstemci Arşivini Kaldır",
|
||||||
|
"actionBlockClient": "İstemci Engelle",
|
||||||
|
"actionUnblockClient": "İstemci Engelini Kaldır",
|
||||||
"actionUpdateClient": "Müşteri Güncelle",
|
"actionUpdateClient": "Müşteri Güncelle",
|
||||||
"actionListClients": "Müşterileri Listele",
|
"actionListClients": "Müşterileri Listele",
|
||||||
"actionGetClient": "Müşteriyi Al",
|
"actionGetClient": "Müşteriyi Al",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "Ara...",
|
"searchProgress": "Ara...",
|
||||||
"create": "Oluştur",
|
"create": "Oluştur",
|
||||||
"orgs": "Organizasyonlar",
|
"orgs": "Organizasyonlar",
|
||||||
"loginError": "Giriş yaparken bir hata oluştu",
|
"loginError": "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin.",
|
||||||
"loginRequiredForDevice": "Cihazınızı kimlik doğrulamak için giriş yapılması gereklidir.",
|
"loginRequiredForDevice": "Cihazınız için oturum açmanız gerekiyor.",
|
||||||
"passwordForgot": "Şifrenizi mi unuttunuz?",
|
"passwordForgot": "Şifrenizi mi unuttunuz?",
|
||||||
"otpAuth": "İki Faktörlü Kimlik Doğrulama",
|
"otpAuth": "İki Faktörlü Kimlik Doğrulama",
|
||||||
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
|
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
|
||||||
"otpAuthSubmit": "Kodu Gönder",
|
"otpAuthSubmit": "Kodu Gönder",
|
||||||
"idpContinue": "Veya devam et:",
|
"idpContinue": "Veya devam et:",
|
||||||
"otpAuthBack": "Girişe Dön",
|
"otpAuthBack": "Şifreye Geri Dön",
|
||||||
"navbar": "Navigasyon Menüsü",
|
"navbar": "Navigasyon Menüsü",
|
||||||
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
|
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
|
||||||
"navbarDocsLink": "Dokümantasyon",
|
"navbarDocsLink": "Dokümantasyon",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "Genel Bakış",
|
"sidebarOverview": "Genel Bakış",
|
||||||
"sidebarHome": "Ana Sayfa",
|
"sidebarHome": "Ana Sayfa",
|
||||||
"sidebarSites": "Siteler",
|
"sidebarSites": "Siteler",
|
||||||
|
"sidebarApprovals": "Onay Talepleri",
|
||||||
"sidebarResources": "Kaynaklar",
|
"sidebarResources": "Kaynaklar",
|
||||||
"sidebarProxyResources": "Herkese Açık",
|
"sidebarProxyResources": "Herkese Açık",
|
||||||
"sidebarClientResources": "Özel",
|
"sidebarClientResources": "Özel",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "Kimlik Sağlayıcılar",
|
"sidebarIdentityProviders": "Kimlik Sağlayıcılar",
|
||||||
"sidebarLicense": "Lisans",
|
"sidebarLicense": "Lisans",
|
||||||
"sidebarClients": "İstemciler",
|
"sidebarClients": "İstemciler",
|
||||||
"sidebarUserDevices": "Kullanıcılar",
|
"sidebarUserDevices": "Kullanıcı Cihazları",
|
||||||
"sidebarMachineClients": "Makineler",
|
"sidebarMachineClients": "Makineler",
|
||||||
"sidebarDomains": "Alan Adları",
|
"sidebarDomains": "Alan Adları",
|
||||||
"sidebarGeneral": "Yönet",
|
"sidebarGeneral": "Yönet",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "Sunucu yönetici hesabı oluşturulurken bir hata oluştu.",
|
"setupErrorCreateAdmin": "Sunucu yönetici hesabı oluşturulurken bir hata oluştu.",
|
||||||
"certificateStatus": "Sertifika Durumu",
|
"certificateStatus": "Sertifika Durumu",
|
||||||
"loading": "Yükleniyor",
|
"loading": "Yükleniyor",
|
||||||
|
"loadingAnalytics": "Analiz Yükleniyor",
|
||||||
"restart": "Yeniden Başlat",
|
"restart": "Yeniden Başlat",
|
||||||
"domains": "Alan Adları",
|
"domains": "Alan Adları",
|
||||||
"domainsDescription": "Organizasyonda kullanılabilir alan adlarını oluşturun ve yönetin",
|
"domainsDescription": "Organizasyonda kullanılabilir alan adlarını oluşturun ve yönetin",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "Veriler yenilenemedi",
|
"refreshError": "Veriler yenilenemedi",
|
||||||
"verified": "Doğrulandı",
|
"verified": "Doğrulandı",
|
||||||
"pending": "Beklemede",
|
"pending": "Beklemede",
|
||||||
|
"pendingApproval": "Bekleyen Onay",
|
||||||
"sidebarBilling": "Faturalama",
|
"sidebarBilling": "Faturalama",
|
||||||
"billing": "Faturalama",
|
"billing": "Faturalama",
|
||||||
"orgBillingDescription": "Fatura bilgilerinizi ve aboneliklerinizi yönetin",
|
"orgBillingDescription": "Fatura bilgilerinizi ve aboneliklerinizi yönetin",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "Güvenlik anahtarı başarıyla kaldırıldı",
|
"securityKeyRemoveSuccess": "Güvenlik anahtarı başarıyla kaldırıldı",
|
||||||
"securityKeyRemoveError": "Güvenlik anahtarı kaldırılırken hata oluştu",
|
"securityKeyRemoveError": "Güvenlik anahtarı kaldırılırken hata oluştu",
|
||||||
"securityKeyLoadError": "Güvenlik anahtarları yüklenirken hata oluştu",
|
"securityKeyLoadError": "Güvenlik anahtarları yüklenirken hata oluştu",
|
||||||
"securityKeyLogin": "Güvenlik anahtarı ile devam edin",
|
"securityKeyLogin": "Güvenlik Anahtarı Kullan",
|
||||||
"securityKeyAuthError": "Güvenlik anahtarı ile kimlik doğrulama başarısız oldu",
|
"securityKeyAuthError": "Güvenlik anahtarı ile kimlik doğrulama başarısız oldu",
|
||||||
"securityKeyRecommendation": "Hesabınızdan kilitlenmediğinizden emin olmak için farklı bir cihazda başka bir güvenlik anahtarı kaydetmeyi düşünün.",
|
"securityKeyRecommendation": "Hesabınızdan kilitlenmediğinizden emin olmak için farklı bir cihazda başka bir güvenlik anahtarı kaydetmeyi düşünün.",
|
||||||
"registering": "Kaydediliyor...",
|
"registering": "Kaydediliyor...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "Sağlıklı Aralık",
|
"IntervalSeconds": "Sağlıklı Aralık",
|
||||||
"timeoutSeconds": "Zaman Aşımı (saniye)",
|
"timeoutSeconds": "Zaman Aşımı (saniye)",
|
||||||
"timeIsInSeconds": "Zaman saniye cinsindendir",
|
"timeIsInSeconds": "Zaman saniye cinsindendir",
|
||||||
|
"requireDeviceApproval": "Cihaz Onaylarını Gerektir",
|
||||||
|
"requireDeviceApprovalDescription": "Bu role sahip kullanıcıların yeni cihazlarının bağlanabilmesi ve kaynaklara erişebilmesi için bir yönetici tarafından onaylanması gerekiyor.",
|
||||||
"retryAttempts": "Tekrar Deneme Girişimleri",
|
"retryAttempts": "Tekrar Deneme Girişimleri",
|
||||||
"expectedResponseCodes": "Beklenen Yanıt Kodları",
|
"expectedResponseCodes": "Beklenen Yanıt Kodları",
|
||||||
"expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.",
|
"expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "Hiçbir dahili kaynak bulunamadı.",
|
"resourcesTableNoInternalResourcesFound": "Hiçbir dahili kaynak bulunamadı.",
|
||||||
"resourcesTableDestination": "Hedef",
|
"resourcesTableDestination": "Hedef",
|
||||||
"resourcesTableAlias": "Takma Ad",
|
"resourcesTableAlias": "Takma Ad",
|
||||||
|
"resourcesTableAliasAddress": "Alias Adresi",
|
||||||
|
"resourcesTableAliasAddressInfo": "Bu adres, kuruluşun yardımcı ağ alt bantının bir parçasıdır. Alias kayıtlarını çözümlemek için dahili DNS çözümlemesi kullanılır.",
|
||||||
"resourcesTableClients": "İstemciler",
|
"resourcesTableClients": "İstemciler",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "veyalnızca bir istemci ile bağlandığında dahili olarak erişilebilir.",
|
"resourcesTableAndOnlyAccessibleInternally": "veyalnızca bir istemci ile bağlandığında dahili olarak erişilebilir.",
|
||||||
"resourcesTableNoTargets": "Hedef yok",
|
"resourcesTableNoTargets": "Hedef yok",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "Kod 9 karakter olmalı (ör. A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "Kod 9 karakter olmalı (ör. A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "Geçersiz veya süresi dolmuş kod",
|
"deviceCodeInvalidOrExpired": "Geçersiz veya süresi dolmuş kod",
|
||||||
"deviceCodeVerifyFailed": "Cihaz kodu doğrulanamadı",
|
"deviceCodeVerifyFailed": "Cihaz kodu doğrulanamadı",
|
||||||
|
"deviceCodeValidating": "Cihaz kodu doğrulanıyor...",
|
||||||
|
"deviceCodeVerifying": "Cihaz yetkilendirme doğrulanıyor...",
|
||||||
"signedInAs": "Olarak giriş yapıldı",
|
"signedInAs": "Olarak giriş yapıldı",
|
||||||
"deviceCodeEnterPrompt": "Cihazda gösterilen kodu girin",
|
"deviceCodeEnterPrompt": "Cihazda gösterilen kodu girin",
|
||||||
"continue": "Devam Et",
|
"continue": "Devam Et",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "Hesabınızın erişim hakkına sahip olduğu tüm organizasyonlara erişim",
|
"deviceOrganizationsAccess": "Hesabınızın erişim hakkına sahip olduğu tüm organizasyonlara erişim",
|
||||||
"deviceAuthorize": "{uygulamaAdi} yetkilendir",
|
"deviceAuthorize": "{uygulamaAdi} yetkilendir",
|
||||||
"deviceConnected": "Cihaz Bağlandı!",
|
"deviceConnected": "Cihaz Bağlandı!",
|
||||||
"deviceAuthorizedMessage": "Cihazınız, hesabınıza erişim izni almıştır.",
|
"deviceAuthorizedMessage": "Cihaz hesabınıza erişim yetkisine sahiptir. Lütfen istemci uygulamasına geri dönün.",
|
||||||
"pangolinCloud": "Pangolin Cloud",
|
"pangolinCloud": "Pangolin Cloud",
|
||||||
"viewDevices": "Cihazları Görüntüle",
|
"viewDevices": "Cihazları Görüntüle",
|
||||||
"viewDevicesDescription": "Bağlantılı cihazlarınızı yönetin",
|
"viewDevicesDescription": "Bağlantılı cihazlarınızı yönetin",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Tanımlayıcı",
|
"identifier": "Tanımlayıcı",
|
||||||
"deviceLoginUseDifferentAccount": "Siz değil misiniz? Farklı bir hesap kullanın.",
|
"deviceLoginUseDifferentAccount": "Siz değil misiniz? Farklı bir hesap kullanın.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "Bir cihaz bu hesaba erişim talep ediyor.",
|
"deviceLoginDeviceRequestingAccessToAccount": "Bir cihaz bu hesaba erişim talep ediyor.",
|
||||||
|
"loginSelectAuthenticationMethod": "Devam etmek için bir kimlik doğrulama yöntemi seçin.",
|
||||||
"noData": "Veri Yok",
|
"noData": "Veri Yok",
|
||||||
"machineClients": "Makine İstemcileri",
|
"machineClients": "Makine İstemcileri",
|
||||||
"install": "Yükle",
|
"install": "Yükle",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "Servis Geçici Olarak Kullanılamıyor",
|
"maintenanceScreenTitle": "Servis Geçici Olarak Kullanılamıyor",
|
||||||
"maintenanceScreenMessage": "Şu anda teknik zorluklar yaşıyoruz. Lütfen yakında tekrar kontrol edin.",
|
"maintenanceScreenMessage": "Şu anda teknik zorluklar yaşıyoruz. Lütfen yakında tekrar kontrol edin.",
|
||||||
"maintenanceScreenEstimatedCompletion": "Tahmini Tamamlama:",
|
"maintenanceScreenEstimatedCompletion": "Tahmini Tamamlama:",
|
||||||
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir"
|
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir",
|
||||||
|
"available": "Mevcut",
|
||||||
|
"archived": "Arşivlenmiş",
|
||||||
|
"noArchivedDevices": "Arşivlenmiş cihaz bulunamadı",
|
||||||
|
"deviceArchived": "Cihaz arşivlendi",
|
||||||
|
"deviceArchivedDescription": "Cihaz başarıyla arşivlendi.",
|
||||||
|
"errorArchivingDevice": "Cihaz arşivleme hatası",
|
||||||
|
"failedToArchiveDevice": "Cihaz arşivlenemedi",
|
||||||
|
"deviceQuestionArchive": "Bu cihazı arşivlemek istediğinizden emin misiniz?",
|
||||||
|
"deviceMessageArchive": "Cihaz arşivlenecek ve aktif cihazlar listenizden kaldırılacak.",
|
||||||
|
"deviceArchiveConfirm": "Cihaz Arşivle",
|
||||||
|
"archiveDevice": "Cihaz Arşivle",
|
||||||
|
"archive": "Arşivle",
|
||||||
|
"deviceUnarchived": "Cihaz arşivden çıkarıldı",
|
||||||
|
"deviceUnarchivedDescription": "Cihaz başarıyla arşivden çıkarıldı.",
|
||||||
|
"errorUnarchivingDevice": "Cihaz arşivden çıkartılamadı",
|
||||||
|
"failedToUnarchiveDevice": "Cihaz arşivden çıkarılamadı",
|
||||||
|
"unarchive": "Arşivden Çıkart",
|
||||||
|
"archiveClient": "İstemci Arşivle",
|
||||||
|
"archiveClientQuestion": "Bu istemciyi arşivlemek istediğinizden emin misiniz?",
|
||||||
|
"archiveClientMessage": "İstemci arşivlenecek ve aktif istemciler listenizden çıkarılacak.",
|
||||||
|
"archiveClientConfirm": "İstemci Arşivle",
|
||||||
|
"blockClient": "İstemci Engelle",
|
||||||
|
"blockClientQuestion": "Bu istemciyi engellemek istediğinizden emin misiniz?",
|
||||||
|
"blockClientMessage": "Cihaz şu anda bağlıysa bağlantısı kesilecek. Cihazı daha sonra engelini kaldırabilirsiniz.",
|
||||||
|
"blockClientConfirm": "İstemci Engelle",
|
||||||
|
"active": "Aktif",
|
||||||
|
"usernameOrEmail": "Kullanıcı adı veya E-posta",
|
||||||
|
"selectYourOrganization": "Kuruluşunuzu seçin",
|
||||||
|
"signInTo": "Giriş yapın",
|
||||||
|
"signInWithPassword": "Şifre ile Devam Et",
|
||||||
|
"noAuthMethodsAvailable": "Bu kuruluş için kullanılabilir kimlik doğrulama yöntemleri yok.",
|
||||||
|
"enterPassword": "Şifrenizi girin",
|
||||||
|
"enterMfaCode": "Authenticator uygulamanızdan kodu girin",
|
||||||
|
"securityKeyRequired": "Giriş yapmak için güvenlik anahtarınızı kullanın.",
|
||||||
|
"needToUseAnotherAccount": "Farklı bir hesap kullanmanız mı gerekiyor?",
|
||||||
|
"loginLegalDisclaimer": "Aşağıdaki butonlara tıklayarak, <termsOfService>Hizmet Şartları</termsOfService> ve <privacyPolicy>Gizlilik Politikası</privacyPolicy> metinlerini okuduğunuzu ve anladığınızı kabul etmektesiniz.",
|
||||||
|
"termsOfService": "Hizmet Şartları",
|
||||||
|
"privacyPolicy": "Gizlilik Politikası",
|
||||||
|
"userNotFoundWithUsername": "Bu kullanıcı adıyla eşleşen kullanıcı bulunamadı.",
|
||||||
|
"verify": "Doğrula",
|
||||||
|
"signIn": "Giriş Yap",
|
||||||
|
"forgotPassword": "Şifreni mi unuttun?",
|
||||||
|
"orgSignInTip": "Daha önce giriş yaptıysanız, yukarıda kullanıcı adınızı veya e-posta adresinizi girerek kuruluşunuzun kimlik sağlayıcısıyla kimlik doğrulaması yapabilirsiniz. Daha kolay!",
|
||||||
|
"continueAnyway": "Yine de devam et",
|
||||||
|
"dontShowAgain": "Tekrar gösterme",
|
||||||
|
"orgSignInNotice": "Biliyor muydunuz?",
|
||||||
|
"signupOrgNotice": "Giriş yapmaya mı çalışıyorsunuz?",
|
||||||
|
"signupOrgTip": "Kuruluşunuzun kimlik sağlayıcısı aracılığıyla giriş yapmaya mı çalışıyorsunuz?",
|
||||||
|
"signupOrgLink": "Bunun yerine kuruluşunuzla giriş yapın veya kaydolun",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "Farklı Bir Hesap Kullan",
|
||||||
|
"logIn": "Giriş Yap",
|
||||||
|
"deviceInformation": "Cihaz Bilgisi",
|
||||||
|
"deviceInformationDescription": "Cihaz ve temsilci hakkında bilgi",
|
||||||
|
"deviceSecurity": "Cihaz Güvenliği",
|
||||||
|
"deviceSecurityDescription": "Cihaz güvenliği durumu bilgisi",
|
||||||
|
"platform": "Platform",
|
||||||
|
"macosVersion": "macOS Sürümü",
|
||||||
|
"windowsVersion": "Windows Sürümü",
|
||||||
|
"iosVersion": "iOS Sürümü",
|
||||||
|
"androidVersion": "Android Sürümü",
|
||||||
|
"osVersion": "İşletim Sistemi Sürümü",
|
||||||
|
"kernelVersion": "Çekirdek Sürümü",
|
||||||
|
"deviceModel": "Cihaz Modeli",
|
||||||
|
"serialNumber": "Seri Numarası",
|
||||||
|
"hostname": "Ana Makine Adı",
|
||||||
|
"firstSeen": "İlk Görüldü",
|
||||||
|
"lastSeen": "Son Görüldü",
|
||||||
|
"biometricsEnabled": "Biyometri Etkin",
|
||||||
|
"diskEncrypted": "Disk Şifrelenmiş",
|
||||||
|
"firewallEnabled": "Güvenlik Duvarı Etkin",
|
||||||
|
"autoUpdatesEnabled": "Otomatik Güncellemeler Etkin",
|
||||||
|
"tpmAvailable": "TPM Mevcut",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "Sistem Bütünlüğü Koruması (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Güvenlik Duvarı Gizlilik Modu",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "Cihaz bilgilerini ve ayarlarını görüntüleyin",
|
||||||
|
"devicePendingApprovalDescription": "Bu cihaz onay bekliyor",
|
||||||
|
"deviceBlockedDescription": "Bu cihaz şu anda engellidir. Engeli kaldırılmadığı sürece hiçbir kaynağa bağlanamayacaktır.",
|
||||||
|
"unblockClient": "İstemci Engeli Kaldır",
|
||||||
|
"unblockClientDescription": "Cihazın engeli kaldırıldı",
|
||||||
|
"unarchiveClient": "İstemci Arşivini Kaldır",
|
||||||
|
"unarchiveClientDescription": "Cihaz arşivden çıkarıldı",
|
||||||
|
"block": "Engelle",
|
||||||
|
"unblock": "Engelini Kaldır",
|
||||||
|
"deviceActions": "Cihaz İşlemleri",
|
||||||
|
"deviceActionsDescription": "Cihaz durumu ve erişimini yönetin",
|
||||||
|
"devicePendingApprovalBannerDescription": "Bu cihaz onay bekliyor. Onaylanana kadar kaynaklara bağlanamayacak.",
|
||||||
|
"connected": "Bağlandı",
|
||||||
|
"disconnected": "Bağlantı Kesildi",
|
||||||
|
"approvalsEmptyStateTitle": "Cihaz Onayları Etkin Değil",
|
||||||
|
"approvalsEmptyStateDescription": "Kullanıcıların yeni cihazlara bağlanabilmeleri için yönetici onayı gerektiren rol cihaz onaylarını etkinleştirin.",
|
||||||
|
"approvalsEmptyStateStep1Title": "Rollere Git",
|
||||||
|
"approvalsEmptyStateStep1Description": "Cihaz onaylarını yapılandırmak için kuruluşunuzun rol ayarlarına gidin.",
|
||||||
|
"approvalsEmptyStateStep2Title": "Cihaz Onaylarını Etkinleştir",
|
||||||
|
"approvalsEmptyStateStep2Description": "Bir rolü düzenleyin ve 'Cihaz Onaylarını Gerektir' seçeneğini etkinleştirin. Bu role sahip kullanıcıların yeni cihazlar için yönetici onayına ihtiyacı olacaktır.",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "Önizleme: Etkinleştirildiğinde, bekleyen cihaz talepleri incelenmek üzere burada görünecektir.",
|
||||||
|
"approvalsEmptyStateButtonText": "Rolleri Yönet"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@
|
|||||||
"sitesBannerTitle": "连接任何网络",
|
"sitesBannerTitle": "连接任何网络",
|
||||||
"sitesBannerDescription": "站点是连接到远程网络的链接,允许Pangolin为用户提供资源访问,无论是公共还是私人。可以在任何可以运行二进制文件或容器的地方安装站点网络连接器(Newt)以建立连接。",
|
"sitesBannerDescription": "站点是连接到远程网络的链接,允许Pangolin为用户提供资源访问,无论是公共还是私人。可以在任何可以运行二进制文件或容器的地方安装站点网络连接器(Newt)以建立连接。",
|
||||||
"sitesBannerButtonText": "安装站点",
|
"sitesBannerButtonText": "安装站点",
|
||||||
|
"approvalsBannerTitle": "批准或拒绝设备访问",
|
||||||
|
"approvalsBannerDescription": "审核、批准或拒绝用户的设备访问请求。 当需要设备批准时,用户必须先获得管理员批准,然后他们的设备才能连接到您的组织资源。",
|
||||||
|
"approvalsBannerButtonText": "了解更多",
|
||||||
"siteCreate": "创建站点",
|
"siteCreate": "创建站点",
|
||||||
"siteCreateDescription2": "按照下面的步骤创建和连接一个新站点",
|
"siteCreateDescription2": "按照下面的步骤创建和连接一个新站点",
|
||||||
"siteCreateDescription": "创建一个新站点开始连接资源",
|
"siteCreateDescription": "创建一个新站点开始连接资源",
|
||||||
@@ -257,6 +260,8 @@
|
|||||||
"accessRolesSearch": "搜索角色...",
|
"accessRolesSearch": "搜索角色...",
|
||||||
"accessRolesAdd": "添加角色",
|
"accessRolesAdd": "添加角色",
|
||||||
"accessRoleDelete": "删除角色",
|
"accessRoleDelete": "删除角色",
|
||||||
|
"accessApprovalsManage": "管理批准",
|
||||||
|
"accessApprovalsDescription": "查看和管理待审批的组织访问权限",
|
||||||
"description": "描述",
|
"description": "描述",
|
||||||
"inviteTitle": "打开邀请",
|
"inviteTitle": "打开邀请",
|
||||||
"inviteDescription": "管理其他用户加入机构的邀请",
|
"inviteDescription": "管理其他用户加入机构的邀请",
|
||||||
@@ -450,6 +455,18 @@
|
|||||||
"selectDuration": "选择持续时间",
|
"selectDuration": "选择持续时间",
|
||||||
"selectResource": "选择资源",
|
"selectResource": "选择资源",
|
||||||
"filterByResource": "按资源过滤",
|
"filterByResource": "按资源过滤",
|
||||||
|
"selectApprovalState": "选择审批状态",
|
||||||
|
"filterByApprovalState": "按批准状态过滤",
|
||||||
|
"approvalListEmpty": "无批准",
|
||||||
|
"approvalState": "审批状态",
|
||||||
|
"approve": "批准",
|
||||||
|
"approved": "已批准",
|
||||||
|
"denied": "被拒绝",
|
||||||
|
"deniedApproval": "拒绝批准",
|
||||||
|
"all": "所有",
|
||||||
|
"deny": "拒绝",
|
||||||
|
"viewDetails": "查看详情",
|
||||||
|
"requestingNewDeviceApproval": "请求了一个新设备",
|
||||||
"resetFilters": "重置过滤器",
|
"resetFilters": "重置过滤器",
|
||||||
"totalBlocked": "被Pangolin阻止的请求",
|
"totalBlocked": "被Pangolin阻止的请求",
|
||||||
"totalRequests": "总请求",
|
"totalRequests": "总请求",
|
||||||
@@ -729,16 +746,28 @@
|
|||||||
"countries": "国家",
|
"countries": "国家",
|
||||||
"accessRoleCreate": "创建角色",
|
"accessRoleCreate": "创建角色",
|
||||||
"accessRoleCreateDescription": "创建一个新角色来分组用户并管理他们的权限。",
|
"accessRoleCreateDescription": "创建一个新角色来分组用户并管理他们的权限。",
|
||||||
|
"accessRoleEdit": "编辑角色",
|
||||||
|
"accessRoleEditDescription": "编辑角色信息。",
|
||||||
"accessRoleCreateSubmit": "创建角色",
|
"accessRoleCreateSubmit": "创建角色",
|
||||||
"accessRoleCreated": "角色已创建",
|
"accessRoleCreated": "角色已创建",
|
||||||
"accessRoleCreatedDescription": "角色已成功创建。",
|
"accessRoleCreatedDescription": "角色已成功创建。",
|
||||||
"accessRoleErrorCreate": "创建角色失败",
|
"accessRoleErrorCreate": "创建角色失败",
|
||||||
"accessRoleErrorCreateDescription": "创建角色时出错。",
|
"accessRoleErrorCreateDescription": "创建角色时出错。",
|
||||||
|
"accessRoleUpdateSubmit": "更新角色",
|
||||||
|
"accessRoleUpdated": "角色已更新",
|
||||||
|
"accessRoleUpdatedDescription": "角色已成功更新。",
|
||||||
|
"accessApprovalUpdated": "审批已处理",
|
||||||
|
"accessApprovalApprovedDescription": "将审批请求决定设置为已批准。",
|
||||||
|
"accessApprovalDeniedDescription": "设置审批请求决定被拒绝。",
|
||||||
|
"accessRoleErrorUpdate": "更新角色失败",
|
||||||
|
"accessRoleErrorUpdateDescription": "更新角色时出错。",
|
||||||
|
"accessApprovalErrorUpdate": "处理审核失败",
|
||||||
|
"accessApprovalErrorUpdateDescription": "处理批准时出错。",
|
||||||
"accessRoleErrorNewRequired": "需要新角色",
|
"accessRoleErrorNewRequired": "需要新角色",
|
||||||
"accessRoleErrorRemove": "删除角色失败",
|
"accessRoleErrorRemove": "删除角色失败",
|
||||||
"accessRoleErrorRemoveDescription": "删除角色时出错。",
|
"accessRoleErrorRemoveDescription": "删除角色时出错。",
|
||||||
"accessRoleName": "角色名称",
|
"accessRoleName": "角色名称",
|
||||||
"accessRoleQuestionRemove": "您即将删除 {name} 角色。 此操作无法撤销。",
|
"accessRoleQuestionRemove": "您即将删除 `{name}` 角色。此操作无法撤销。",
|
||||||
"accessRoleRemove": "删除角色",
|
"accessRoleRemove": "删除角色",
|
||||||
"accessRoleRemoveDescription": "从组织中删除角色",
|
"accessRoleRemoveDescription": "从组织中删除角色",
|
||||||
"accessRoleRemoveSubmit": "删除角色",
|
"accessRoleRemoveSubmit": "删除角色",
|
||||||
@@ -960,7 +989,7 @@
|
|||||||
"passwordResetSmtpRequired": "请联系您的管理员",
|
"passwordResetSmtpRequired": "请联系您的管理员",
|
||||||
"passwordResetSmtpRequiredDescription": "需要密码重置密码。请联系您的管理员寻求帮助。",
|
"passwordResetSmtpRequiredDescription": "需要密码重置密码。请联系您的管理员寻求帮助。",
|
||||||
"passwordBack": "回到密码",
|
"passwordBack": "回到密码",
|
||||||
"loginBack": "返回登录",
|
"loginBack": "返回主登录页面",
|
||||||
"signup": "注册",
|
"signup": "注册",
|
||||||
"loginStart": "登录以开始",
|
"loginStart": "登录以开始",
|
||||||
"idpOidcTokenValidating": "正在验证 OIDC 令牌",
|
"idpOidcTokenValidating": "正在验证 OIDC 令牌",
|
||||||
@@ -1118,6 +1147,10 @@
|
|||||||
"actionUpdateIdpOrg": "更新 IDP组织",
|
"actionUpdateIdpOrg": "更新 IDP组织",
|
||||||
"actionCreateClient": "创建客户端",
|
"actionCreateClient": "创建客户端",
|
||||||
"actionDeleteClient": "删除客户端",
|
"actionDeleteClient": "删除客户端",
|
||||||
|
"actionArchiveClient": "归档客户端",
|
||||||
|
"actionUnarchiveClient": "取消归档客户端",
|
||||||
|
"actionBlockClient": "屏蔽客户端",
|
||||||
|
"actionUnblockClient": "解除屏蔽客户端",
|
||||||
"actionUpdateClient": "更新客户端",
|
"actionUpdateClient": "更新客户端",
|
||||||
"actionListClients": "列出客户端",
|
"actionListClients": "列出客户端",
|
||||||
"actionGetClient": "获取客户端",
|
"actionGetClient": "获取客户端",
|
||||||
@@ -1134,14 +1167,14 @@
|
|||||||
"searchProgress": "搜索中...",
|
"searchProgress": "搜索中...",
|
||||||
"create": "创建",
|
"create": "创建",
|
||||||
"orgs": "组织",
|
"orgs": "组织",
|
||||||
"loginError": "登录时出错",
|
"loginError": "发生意外错误。请重试。",
|
||||||
"loginRequiredForDevice": "需要登录才能验证您的设备。",
|
"loginRequiredForDevice": "您的设备需要登录。",
|
||||||
"passwordForgot": "忘记密码?",
|
"passwordForgot": "忘记密码?",
|
||||||
"otpAuth": "两步验证",
|
"otpAuth": "两步验证",
|
||||||
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
|
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
|
||||||
"otpAuthSubmit": "提交代码",
|
"otpAuthSubmit": "提交代码",
|
||||||
"idpContinue": "或者继续",
|
"idpContinue": "或者继续",
|
||||||
"otpAuthBack": "返回登录",
|
"otpAuthBack": "回到密码",
|
||||||
"navbar": "导航菜单",
|
"navbar": "导航菜单",
|
||||||
"navbarDescription": "应用程序的主导航菜单",
|
"navbarDescription": "应用程序的主导航菜单",
|
||||||
"navbarDocsLink": "文件",
|
"navbarDocsLink": "文件",
|
||||||
@@ -1189,6 +1222,7 @@
|
|||||||
"sidebarOverview": "概览",
|
"sidebarOverview": "概览",
|
||||||
"sidebarHome": "首页",
|
"sidebarHome": "首页",
|
||||||
"sidebarSites": "站点",
|
"sidebarSites": "站点",
|
||||||
|
"sidebarApprovals": "审批请求",
|
||||||
"sidebarResources": "资源",
|
"sidebarResources": "资源",
|
||||||
"sidebarProxyResources": "公开的",
|
"sidebarProxyResources": "公开的",
|
||||||
"sidebarClientResources": "非公开的",
|
"sidebarClientResources": "非公开的",
|
||||||
@@ -1205,7 +1239,7 @@
|
|||||||
"sidebarIdentityProviders": "身份提供商",
|
"sidebarIdentityProviders": "身份提供商",
|
||||||
"sidebarLicense": "证书",
|
"sidebarLicense": "证书",
|
||||||
"sidebarClients": "客户端",
|
"sidebarClients": "客户端",
|
||||||
"sidebarUserDevices": "用户",
|
"sidebarUserDevices": "用户设备",
|
||||||
"sidebarMachineClients": "机",
|
"sidebarMachineClients": "机",
|
||||||
"sidebarDomains": "域",
|
"sidebarDomains": "域",
|
||||||
"sidebarGeneral": "管理",
|
"sidebarGeneral": "管理",
|
||||||
@@ -1277,6 +1311,7 @@
|
|||||||
"setupErrorCreateAdmin": "创建服务器管理员账户时发生错误。",
|
"setupErrorCreateAdmin": "创建服务器管理员账户时发生错误。",
|
||||||
"certificateStatus": "证书状态",
|
"certificateStatus": "证书状态",
|
||||||
"loading": "加载中",
|
"loading": "加载中",
|
||||||
|
"loadingAnalytics": "加载分析",
|
||||||
"restart": "重启",
|
"restart": "重启",
|
||||||
"domains": "域",
|
"domains": "域",
|
||||||
"domainsDescription": "创建和管理组织中可用的域",
|
"domainsDescription": "创建和管理组织中可用的域",
|
||||||
@@ -1304,6 +1339,7 @@
|
|||||||
"refreshError": "刷新数据失败",
|
"refreshError": "刷新数据失败",
|
||||||
"verified": "已验证",
|
"verified": "已验证",
|
||||||
"pending": "待定",
|
"pending": "待定",
|
||||||
|
"pendingApproval": "等待批准",
|
||||||
"sidebarBilling": "计费",
|
"sidebarBilling": "计费",
|
||||||
"billing": "计费",
|
"billing": "计费",
|
||||||
"orgBillingDescription": "管理账单信息和订阅",
|
"orgBillingDescription": "管理账单信息和订阅",
|
||||||
@@ -1420,7 +1456,7 @@
|
|||||||
"securityKeyRemoveSuccess": "安全密钥删除成功",
|
"securityKeyRemoveSuccess": "安全密钥删除成功",
|
||||||
"securityKeyRemoveError": "删除安全密钥失败",
|
"securityKeyRemoveError": "删除安全密钥失败",
|
||||||
"securityKeyLoadError": "加载安全密钥失败",
|
"securityKeyLoadError": "加载安全密钥失败",
|
||||||
"securityKeyLogin": "使用安全密钥继续",
|
"securityKeyLogin": "使用安全密钥",
|
||||||
"securityKeyAuthError": "使用安全密钥认证失败",
|
"securityKeyAuthError": "使用安全密钥认证失败",
|
||||||
"securityKeyRecommendation": "考虑在其他设备上注册另一个安全密钥,以确保不会被锁定在您的账户之外。",
|
"securityKeyRecommendation": "考虑在其他设备上注册另一个安全密钥,以确保不会被锁定在您的账户之外。",
|
||||||
"registering": "注册中...",
|
"registering": "注册中...",
|
||||||
@@ -1547,6 +1583,8 @@
|
|||||||
"IntervalSeconds": "正常间隔",
|
"IntervalSeconds": "正常间隔",
|
||||||
"timeoutSeconds": "超时(秒)",
|
"timeoutSeconds": "超时(秒)",
|
||||||
"timeIsInSeconds": "时间以秒为单位",
|
"timeIsInSeconds": "时间以秒为单位",
|
||||||
|
"requireDeviceApproval": "需要设备批准",
|
||||||
|
"requireDeviceApprovalDescription": "具有此角色的用户需要管理员批准的新设备才能连接和访问资源。",
|
||||||
"retryAttempts": "重试次数",
|
"retryAttempts": "重试次数",
|
||||||
"expectedResponseCodes": "期望响应代码",
|
"expectedResponseCodes": "期望响应代码",
|
||||||
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
|
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
|
||||||
@@ -1587,6 +1625,8 @@
|
|||||||
"resourcesTableNoInternalResourcesFound": "未找到内部资源。",
|
"resourcesTableNoInternalResourcesFound": "未找到内部资源。",
|
||||||
"resourcesTableDestination": "目标",
|
"resourcesTableDestination": "目标",
|
||||||
"resourcesTableAlias": "Alias",
|
"resourcesTableAlias": "Alias",
|
||||||
|
"resourcesTableAliasAddress": "别名地址",
|
||||||
|
"resourcesTableAliasAddressInfo": "此地址是组织实用子网的一部分。它用来使用内部DNS解析来解析别名记录。",
|
||||||
"resourcesTableClients": "客户端",
|
"resourcesTableClients": "客户端",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "且仅在与客户端连接时可内部访问。",
|
"resourcesTableAndOnlyAccessibleInternally": "且仅在与客户端连接时可内部访问。",
|
||||||
"resourcesTableNoTargets": "没有目标",
|
"resourcesTableNoTargets": "没有目标",
|
||||||
@@ -1876,7 +1916,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "选择您的身份提供商以继续",
|
"orgAuthChooseIdpDescription": "选择您的身份提供商以继续",
|
||||||
"orgAuthNoIdpConfigured": "此机构没有配置任何身份提供者。您可以使用您的 Pangolin 身份登录。",
|
"orgAuthNoIdpConfigured": "此机构没有配置任何身份提供者。您可以使用您的 Pangolin 身份登录。",
|
||||||
"orgAuthSignInWithPangolin": "使用 Pangolin 登录",
|
"orgAuthSignInWithPangolin": "使用 Pangolin 登录",
|
||||||
"orgAuthSignInToOrg": "登录到一个组织",
|
"orgAuthSignInToOrg": "登录到组织",
|
||||||
"orgAuthSelectOrgTitle": "组织登录",
|
"orgAuthSelectOrgTitle": "组织登录",
|
||||||
"orgAuthSelectOrgDescription": "输入您的组织ID以继续",
|
"orgAuthSelectOrgDescription": "输入您的组织ID以继续",
|
||||||
"orgAuthOrgIdPlaceholder": "您的组织",
|
"orgAuthOrgIdPlaceholder": "您的组织",
|
||||||
@@ -2232,6 +2272,8 @@
|
|||||||
"deviceCodeInvalidFormat": "代码必须是9个字符(如A1AJ-N5JD)",
|
"deviceCodeInvalidFormat": "代码必须是9个字符(如A1AJ-N5JD)",
|
||||||
"deviceCodeInvalidOrExpired": "无效或过期的代码",
|
"deviceCodeInvalidOrExpired": "无效或过期的代码",
|
||||||
"deviceCodeVerifyFailed": "验证设备代码失败",
|
"deviceCodeVerifyFailed": "验证设备代码失败",
|
||||||
|
"deviceCodeValidating": "正在验证设备代码...",
|
||||||
|
"deviceCodeVerifying": "正在验证设备授权...",
|
||||||
"signedInAs": "登录为",
|
"signedInAs": "登录为",
|
||||||
"deviceCodeEnterPrompt": "输入设备上显示的代码",
|
"deviceCodeEnterPrompt": "输入设备上显示的代码",
|
||||||
"continue": "继续",
|
"continue": "继续",
|
||||||
@@ -2244,7 +2286,7 @@
|
|||||||
"deviceOrganizationsAccess": "访问您的帐户拥有访问权限的所有组织",
|
"deviceOrganizationsAccess": "访问您的帐户拥有访问权限的所有组织",
|
||||||
"deviceAuthorize": "授权{applicationName}",
|
"deviceAuthorize": "授权{applicationName}",
|
||||||
"deviceConnected": "设备已连接!",
|
"deviceConnected": "设备已连接!",
|
||||||
"deviceAuthorizedMessage": "设备被授权访问您的帐户。",
|
"deviceAuthorizedMessage": "设备被授权访问您的帐户。请返回客户端应用程序。",
|
||||||
"pangolinCloud": "邦戈林云",
|
"pangolinCloud": "邦戈林云",
|
||||||
"viewDevices": "查看设备",
|
"viewDevices": "查看设备",
|
||||||
"viewDevicesDescription": "管理您已连接的设备",
|
"viewDevicesDescription": "管理您已连接的设备",
|
||||||
@@ -2306,6 +2348,7 @@
|
|||||||
"identifier": "Identifier",
|
"identifier": "Identifier",
|
||||||
"deviceLoginUseDifferentAccount": "不是你?使用一个不同的帐户。",
|
"deviceLoginUseDifferentAccount": "不是你?使用一个不同的帐户。",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "设备正在请求访问此帐户。",
|
"deviceLoginDeviceRequestingAccessToAccount": "设备正在请求访问此帐户。",
|
||||||
|
"loginSelectAuthenticationMethod": "选择要继续的身份验证方法。",
|
||||||
"noData": "无数据",
|
"noData": "无数据",
|
||||||
"machineClients": "机器客户端",
|
"machineClients": "机器客户端",
|
||||||
"install": "安装",
|
"install": "安装",
|
||||||
@@ -2394,5 +2437,105 @@
|
|||||||
"maintenanceScreenTitle": "服务暂时不可用",
|
"maintenanceScreenTitle": "服务暂时不可用",
|
||||||
"maintenanceScreenMessage": "我们目前遇到技术问题。 请稍后再回来查看。",
|
"maintenanceScreenMessage": "我们目前遇到技术问题。 请稍后再回来查看。",
|
||||||
"maintenanceScreenEstimatedCompletion": "预计完成时间:",
|
"maintenanceScreenEstimatedCompletion": "预计完成时间:",
|
||||||
"createInternalResourceDialogDestinationRequired": "需要目标地址"
|
"createInternalResourceDialogDestinationRequired": "需要目标地址",
|
||||||
|
"available": "可用",
|
||||||
|
"archived": "已存档",
|
||||||
|
"noArchivedDevices": "未找到存档设备",
|
||||||
|
"deviceArchived": "设备已存档",
|
||||||
|
"deviceArchivedDescription": "设备已成功归档。",
|
||||||
|
"errorArchivingDevice": "错误存档设备",
|
||||||
|
"failedToArchiveDevice": "归档设备失败",
|
||||||
|
"deviceQuestionArchive": "您确定要存档此设备吗?",
|
||||||
|
"deviceMessageArchive": "设备将被存档并从活动设备列表中删除。",
|
||||||
|
"deviceArchiveConfirm": "归档设备",
|
||||||
|
"archiveDevice": "归档设备",
|
||||||
|
"archive": "存档",
|
||||||
|
"deviceUnarchived": "设备未存档",
|
||||||
|
"deviceUnarchivedDescription": "设备已成功解除归档。",
|
||||||
|
"errorUnarchivingDevice": "卸载设备时出错",
|
||||||
|
"failedToUnarchiveDevice": "取消归档设备失败",
|
||||||
|
"unarchive": "取消存档",
|
||||||
|
"archiveClient": "归档客户端",
|
||||||
|
"archiveClientQuestion": "您确定要存档此客户端吗?",
|
||||||
|
"archiveClientMessage": "客户端将被存档并从您活跃的客户端列表中删除。",
|
||||||
|
"archiveClientConfirm": "归档客户端",
|
||||||
|
"blockClient": "屏蔽客户端",
|
||||||
|
"blockClientQuestion": "您确定要屏蔽此客户端?",
|
||||||
|
"blockClientMessage": "如果当前连接,设备将被迫断开连接。您可以稍后取消屏蔽设备。",
|
||||||
|
"blockClientConfirm": "屏蔽客户端",
|
||||||
|
"active": "已启用",
|
||||||
|
"usernameOrEmail": "用户名或电子邮件",
|
||||||
|
"selectYourOrganization": "选择您的组织",
|
||||||
|
"signInTo": "登录到",
|
||||||
|
"signInWithPassword": "使用密码继续",
|
||||||
|
"noAuthMethodsAvailable": "该组织没有可用的身份验证方法。",
|
||||||
|
"enterPassword": "输入您的密码",
|
||||||
|
"enterMfaCode": "从您的身份验证程序中输入代码",
|
||||||
|
"securityKeyRequired": "请使用您的安全密钥登录。",
|
||||||
|
"needToUseAnotherAccount": "需要使用不同的帐户?",
|
||||||
|
"loginLegalDisclaimer": "点击下面的按钮,您确认您已经阅读了,理解, 并同意 <termsOfService>服务条款</termsOfService> 和 <privacyPolicy>隐私政策</privacyPolicy>。",
|
||||||
|
"termsOfService": "服务条款",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"userNotFoundWithUsername": "找不到该用户名。",
|
||||||
|
"verify": "验证",
|
||||||
|
"signIn": "登录",
|
||||||
|
"forgotPassword": "忘记密码?",
|
||||||
|
"orgSignInTip": "如果您以前已经登录,您可以在上面输入您的用户名或电子邮件来验证您的组织身份提供者。这很容易!",
|
||||||
|
"continueAnyway": "仍然继续",
|
||||||
|
"dontShowAgain": "不再显示",
|
||||||
|
"orgSignInNotice": "您知道吗?",
|
||||||
|
"signupOrgNotice": "试图登录?",
|
||||||
|
"signupOrgTip": "您是否试图通过您的组织的身份提供者登录?",
|
||||||
|
"signupOrgLink": "使用您的组织登录或注册",
|
||||||
|
"verifyEmailLogInWithDifferentAccount": "使用不同的帐户",
|
||||||
|
"logIn": "登录",
|
||||||
|
"deviceInformation": "设备信息",
|
||||||
|
"deviceInformationDescription": "关于设备和代理的信息",
|
||||||
|
"deviceSecurity": "设备安全",
|
||||||
|
"deviceSecurityDescription": "设备安全态势信息",
|
||||||
|
"platform": "平台",
|
||||||
|
"macosVersion": "macOS 版本",
|
||||||
|
"windowsVersion": "Windows 版本",
|
||||||
|
"iosVersion": "iOS 版本",
|
||||||
|
"androidVersion": "Android 版本",
|
||||||
|
"osVersion": "操作系统版本",
|
||||||
|
"kernelVersion": "内核版本",
|
||||||
|
"deviceModel": "设备模型",
|
||||||
|
"serialNumber": "序列号",
|
||||||
|
"hostname": "Hostname",
|
||||||
|
"firstSeen": "第一次查看",
|
||||||
|
"lastSeen": "上次查看时间",
|
||||||
|
"biometricsEnabled": "生物计已启用",
|
||||||
|
"diskEncrypted": "磁盘加密",
|
||||||
|
"firewallEnabled": "防火墙已启用",
|
||||||
|
"autoUpdatesEnabled": "启用自动更新",
|
||||||
|
"tpmAvailable": "TPM 可用",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||||
|
"macosSipEnabled": "系统完整性保护 (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "防火墙隐形模式",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
"deviceSettingsDescription": "查看设备信息和设置",
|
||||||
|
"devicePendingApprovalDescription": "此设备正在等待批准",
|
||||||
|
"deviceBlockedDescription": "此设备目前已被屏蔽。除非解除屏蔽,否则无法连接到任何资源。",
|
||||||
|
"unblockClient": "解除屏蔽客户端",
|
||||||
|
"unblockClientDescription": "设备已解除阻止",
|
||||||
|
"unarchiveClient": "取消归档客户端",
|
||||||
|
"unarchiveClientDescription": "设备已被取消存档",
|
||||||
|
"block": "封禁",
|
||||||
|
"unblock": "取消屏蔽",
|
||||||
|
"deviceActions": "设备操作",
|
||||||
|
"deviceActionsDescription": "管理设备状态和访问权限",
|
||||||
|
"devicePendingApprovalBannerDescription": "此设备正在等待批准。在批准之前,它将无法连接到资源。",
|
||||||
|
"connected": "已连接",
|
||||||
|
"disconnected": "断开连接",
|
||||||
|
"approvalsEmptyStateTitle": "设备批准未启用",
|
||||||
|
"approvalsEmptyStateDescription": "在用户连接新设备之前,允许设备批准角色,需要管理员批准。",
|
||||||
|
"approvalsEmptyStateStep1Title": "转到角色",
|
||||||
|
"approvalsEmptyStateStep1Description": "导航到您组织的角色设置来配置设备批准。",
|
||||||
|
"approvalsEmptyStateStep2Title": "启用设备批准",
|
||||||
|
"approvalsEmptyStateStep2Description": "编辑角色并启用“需要设备审批”选项。具有此角色的用户需要管理员批准新设备。",
|
||||||
|
"approvalsEmptyStatePreviewDescription": "预览:如果启用,待处理设备请求将出现在这里供审核",
|
||||||
|
"approvalsEmptyStateButtonText": "管理角色"
|
||||||
}
|
}
|
||||||
|
|||||||
4487
messages/zh-TW.json
4487
messages/zh-TW.json
File diff suppressed because it is too large
Load Diff
1776
package-lock.json
generated
1776
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -33,9 +33,9 @@
|
|||||||
"format": "prettier --write ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "8.2.0",
|
"@asteasolutions/zod-to-openapi": "8.4.0",
|
||||||
"@aws-sdk/client-s3": "3.955.0",
|
"@aws-sdk/client-s3": "3.971.0",
|
||||||
"@faker-js/faker": "10.1.0",
|
"@faker-js/faker": "10.2.0",
|
||||||
"@headlessui/react": "2.2.9",
|
"@headlessui/react": "2.2.9",
|
||||||
"@hookform/resolvers": "5.2.2",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@monaco-editor/react": "4.7.0",
|
"@monaco-editor/react": "4.7.0",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"http-errors": "2.0.1",
|
"http-errors": "2.0.1",
|
||||||
"i": "0.3.7",
|
"i": "0.3.7",
|
||||||
"input-otp": "1.4.2",
|
"input-otp": "1.4.2",
|
||||||
"ioredis": "5.8.2",
|
"ioredis": "5.9.2",
|
||||||
"jmespath": "0.16.0",
|
"jmespath": "0.16.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"jsonwebtoken": "9.0.3",
|
"jsonwebtoken": "9.0.3",
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
"maxmind": "5.0.1",
|
"maxmind": "5.0.1",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "15.5.9",
|
"next": "15.5.9",
|
||||||
"next-intl": "4.6.1",
|
"next-intl": "4.7.0",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"nextjs-toploader": "3.9.17",
|
"nextjs-toploader": "3.9.17",
|
||||||
"node-cache": "5.1.2",
|
"node-cache": "5.1.2",
|
||||||
@@ -109,21 +109,21 @@
|
|||||||
"npm": "11.7.0",
|
"npm": "11.7.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "8.16.3",
|
"pg": "8.17.1",
|
||||||
"posthog-node": "5.17.4",
|
"posthog-node": "5.23.0",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-day-picker": "9.13.0",
|
"react-day-picker": "9.13.0",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
"react-easy-sort": "1.8.0",
|
"react-easy-sort": "1.8.0",
|
||||||
"react-hook-form": "7.68.0",
|
"react-hook-form": "7.71.1",
|
||||||
"react-icons": "5.5.0",
|
"react-icons": "5.5.0",
|
||||||
"rebuild": "0.1.2",
|
"rebuild": "0.1.2",
|
||||||
"recharts": "2.15.4",
|
"recharts": "2.15.4",
|
||||||
"reodotdev": "1.0.0",
|
"reodotdev": "1.0.0",
|
||||||
"resend": "6.6.0",
|
"resend": "6.8.0",
|
||||||
"semver": "7.7.3",
|
"semver": "7.7.3",
|
||||||
"stripe": "20.1.0",
|
"stripe": "20.2.0",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"tailwind-merge": "3.4.0",
|
"tailwind-merge": "3.4.0",
|
||||||
"topojson-client": "3.1.0",
|
"topojson-client": "3.1.0",
|
||||||
@@ -133,10 +133,10 @@
|
|||||||
"visionscarto-world-atlas": "1.0.0",
|
"visionscarto-world-atlas": "1.0.0",
|
||||||
"winston": "3.19.0",
|
"winston": "3.19.0",
|
||||||
"winston-daily-rotate-file": "5.0.0",
|
"winston-daily-rotate-file": "5.0.0",
|
||||||
"ws": "8.18.3",
|
"ws": "8.19.0",
|
||||||
"yaml": "2.8.2",
|
"yaml": "2.8.2",
|
||||||
"yargs": "18.0.0",
|
"yargs": "18.0.0",
|
||||||
"zod": "4.2.1",
|
"zod": "4.3.5",
|
||||||
"zod-validation-error": "5.0.0"
|
"zod-validation-error": "5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -170,12 +170,12 @@
|
|||||||
"esbuild": "0.27.2",
|
"esbuild": "0.27.2",
|
||||||
"esbuild-node-externals": "1.20.1",
|
"esbuild-node-externals": "1.20.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"prettier": "3.7.4",
|
"prettier": "3.8.0",
|
||||||
"react-email": "5.0.7",
|
"react-email": "5.2.5",
|
||||||
"tailwindcss": "4.1.18",
|
"tailwindcss": "4.1.18",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.49.0"
|
"typescript-eslint": "8.53.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ export const loginPageOrg = pgTable("loginPageOrg", {
|
|||||||
|
|
||||||
export const loginPageBranding = pgTable("loginPageBranding", {
|
export const loginPageBranding = pgTable("loginPageBranding", {
|
||||||
loginPageBrandingId: serial("loginPageBrandingId").primaryKey(),
|
loginPageBrandingId: serial("loginPageBrandingId").primaryKey(),
|
||||||
logoUrl: text("logoUrl").notNull(),
|
logoUrl: text("logoUrl"),
|
||||||
logoWidth: integer("logoWidth").notNull(),
|
logoWidth: integer("logoWidth").notNull(),
|
||||||
logoHeight: integer("logoHeight").notNull(),
|
logoHeight: integer("logoHeight").notNull(),
|
||||||
primaryColor: text("primaryColor"),
|
primaryColor: text("primaryColor"),
|
||||||
|
|||||||
@@ -726,6 +726,100 @@ export const clientPostureSnapshots = pgTable("clientPostureSnapshots", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
collectedAt: integer("collectedAt").notNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const olms = pgTable("olms", {
|
||||||
|
olmId: varchar("id").primaryKey(),
|
||||||
|
secretHash: varchar("secretHash").notNull(),
|
||||||
|
dateCreated: varchar("dateCreated").notNull(),
|
||||||
|
version: text("version"),
|
||||||
|
agent: text("agent"),
|
||||||
|
name: varchar("name"),
|
||||||
|
clientId: integer("clientId").references(() => clients.clientId, {
|
||||||
|
// we will switch this depending on the current org it wants to connect to
|
||||||
|
onDelete: "set null"
|
||||||
|
}),
|
||||||
|
userId: text("userId").references(() => users.userId, {
|
||||||
|
// optionally tied to a user and in this case delete when the user deletes
|
||||||
|
onDelete: "cascade"
|
||||||
|
}),
|
||||||
|
archived: boolean("archived").notNull().default(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const currentFingerprint = pgTable("currentFingerprint", {
|
||||||
|
fingerprintId: serial("id").primaryKey(),
|
||||||
|
|
||||||
|
olmId: text("olmId")
|
||||||
|
.references(() => olms.olmId, { onDelete: "cascade" })
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
|
firstSeen: integer("firstSeen").notNull(),
|
||||||
|
lastSeen: integer("lastSeen").notNull(),
|
||||||
|
lastCollectedAt: integer("lastCollectedAt").notNull(),
|
||||||
|
|
||||||
|
username: text("username"),
|
||||||
|
hostname: text("hostname"),
|
||||||
|
platform: text("platform"),
|
||||||
|
osVersion: text("osVersion"),
|
||||||
|
kernelVersion: text("kernelVersion"),
|
||||||
|
arch: text("arch"),
|
||||||
|
deviceModel: text("deviceModel"),
|
||||||
|
serialNumber: text("serialNumber"),
|
||||||
|
platformFingerprint: varchar("platformFingerprint"),
|
||||||
|
|
||||||
|
// Platform-agnostic checks
|
||||||
|
|
||||||
|
biometricsEnabled: boolean("biometricsEnabled").notNull().default(false),
|
||||||
|
diskEncrypted: boolean("diskEncrypted").notNull().default(false),
|
||||||
|
firewallEnabled: boolean("firewallEnabled").notNull().default(false),
|
||||||
|
autoUpdatesEnabled: boolean("autoUpdatesEnabled").notNull().default(false),
|
||||||
|
tpmAvailable: boolean("tpmAvailable").notNull().default(false),
|
||||||
|
|
||||||
|
// Windows-specific posture check information
|
||||||
|
|
||||||
|
windowsDefenderEnabled: boolean("windowsDefenderEnabled")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
// macOS-specific posture check information
|
||||||
|
|
||||||
|
macosSipEnabled: boolean("macosSipEnabled").notNull().default(false),
|
||||||
|
macosGatekeeperEnabled: boolean("macosGatekeeperEnabled")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
macosFirewallStealthMode: boolean("macosFirewallStealthMode")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
// Linux-specific posture check information
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: boolean("linuxAppArmorEnabled")
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
linuxSELinuxEnabled: boolean("linuxSELinuxEnabled").notNull().default(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fingerprintSnapshots = pgTable("fingerprintSnapshots", {
|
||||||
|
snapshotId: serial("id").primaryKey(),
|
||||||
|
|
||||||
|
fingerprintId: integer("fingerprintId").references(
|
||||||
|
() => currentFingerprint.fingerprintId,
|
||||||
|
{
|
||||||
|
onDelete: "set null"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
username: text("username"),
|
||||||
|
hostname: text("hostname"),
|
||||||
|
platform: text("platform"),
|
||||||
|
osVersion: text("osVersion"),
|
||||||
|
kernelVersion: text("kernelVersion"),
|
||||||
|
arch: text("arch"),
|
||||||
|
deviceModel: text("deviceModel"),
|
||||||
|
serialNumber: text("serialNumber"),
|
||||||
|
platformFingerprint: varchar("platformFingerprint"),
|
||||||
|
|
||||||
// Platform-agnostic checks
|
// Platform-agnostic checks
|
||||||
|
|
||||||
biometricsEnabled: boolean("biometricsEnabled").notNull().default(false),
|
biometricsEnabled: boolean("biometricsEnabled").notNull().default(false),
|
||||||
@@ -759,48 +853,10 @@ export const clientPostureSnapshots = pgTable("clientPostureSnapshots", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.default(false),
|
.default(false),
|
||||||
|
|
||||||
|
hash: text("hash").notNull(),
|
||||||
collectedAt: integer("collectedAt").notNull()
|
collectedAt: integer("collectedAt").notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const olms = pgTable("olms", {
|
|
||||||
olmId: varchar("id").primaryKey(),
|
|
||||||
secretHash: varchar("secretHash").notNull(),
|
|
||||||
dateCreated: varchar("dateCreated").notNull(),
|
|
||||||
version: text("version"),
|
|
||||||
agent: text("agent"),
|
|
||||||
name: varchar("name"),
|
|
||||||
clientId: integer("clientId").references(() => clients.clientId, {
|
|
||||||
// we will switch this depending on the current org it wants to connect to
|
|
||||||
onDelete: "set null"
|
|
||||||
}),
|
|
||||||
userId: text("userId").references(() => users.userId, {
|
|
||||||
// optionally tied to a user and in this case delete when the user deletes
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
archived: boolean("archived").notNull().default(false)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fingerprints = pgTable("fingerprints", {
|
|
||||||
fingerprintId: serial("id").primaryKey(),
|
|
||||||
|
|
||||||
olmId: text("olmId")
|
|
||||||
.references(() => olms.olmId, { onDelete: "cascade" })
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
firstSeen: integer("firstSeen").notNull(),
|
|
||||||
lastSeen: integer("lastSeen").notNull(),
|
|
||||||
|
|
||||||
username: text("username"),
|
|
||||||
hostname: text("hostname"),
|
|
||||||
platform: text("platform"), // macos | windows | linux | ios | android | unknown
|
|
||||||
osVersion: text("osVersion"),
|
|
||||||
kernelVersion: text("kernelVersion"),
|
|
||||||
arch: text("arch"),
|
|
||||||
deviceModel: text("deviceModel"),
|
|
||||||
serialNumber: text("serialNumber"),
|
|
||||||
platformFingerprint: varchar("platformFingerprint")
|
|
||||||
});
|
|
||||||
|
|
||||||
export const olmSessions = pgTable("clientSession", {
|
export const olmSessions = pgTable("clientSession", {
|
||||||
sessionId: varchar("id").primaryKey(),
|
sessionId: varchar("id").primaryKey(),
|
||||||
olmId: varchar("olmId")
|
olmId: varchar("olmId")
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ export const loginPageBranding = sqliteTable("loginPageBranding", {
|
|||||||
loginPageBrandingId: integer("loginPageBrandingId").primaryKey({
|
loginPageBrandingId: integer("loginPageBrandingId").primaryKey({
|
||||||
autoIncrement: true
|
autoIncrement: true
|
||||||
}),
|
}),
|
||||||
logoUrl: text("logoUrl").notNull(),
|
logoUrl: text("logoUrl"),
|
||||||
logoWidth: integer("logoWidth").notNull(),
|
logoWidth: integer("logoWidth").notNull(),
|
||||||
logoHeight: integer("logoHeight").notNull(),
|
logoHeight: integer("logoHeight").notNull(),
|
||||||
primaryColor: text("primaryColor"),
|
primaryColor: text("primaryColor"),
|
||||||
|
|||||||
@@ -416,12 +416,118 @@ export const clientSiteResourcesAssociationsCache = sqliteTable(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const clientPostureSnapshots = sqliteTable("clientPostureSnapshots", {
|
export const olms = sqliteTable("olms", {
|
||||||
snapshotId: integer("snapshotId").primaryKey({ autoIncrement: true }),
|
olmId: text("id").primaryKey(),
|
||||||
|
secretHash: text("secretHash").notNull(),
|
||||||
|
dateCreated: text("dateCreated").notNull(),
|
||||||
|
version: text("version"),
|
||||||
|
agent: text("agent"),
|
||||||
|
name: text("name"),
|
||||||
clientId: integer("clientId").references(() => clients.clientId, {
|
clientId: integer("clientId").references(() => clients.clientId, {
|
||||||
|
// we will switch this depending on the current org it wants to connect to
|
||||||
|
onDelete: "set null"
|
||||||
|
}),
|
||||||
|
userId: text("userId").references(() => users.userId, {
|
||||||
|
// optionally tied to a user and in this case delete when the user deletes
|
||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}),
|
}),
|
||||||
|
archived: integer("archived", { mode: "boolean" }).notNull().default(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const currentFingerprint = sqliteTable("currentFingerprint", {
|
||||||
|
fingerprintId: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
|
|
||||||
|
olmId: text("olmId")
|
||||||
|
.references(() => olms.olmId, { onDelete: "cascade" })
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
|
firstSeen: integer("firstSeen").notNull(),
|
||||||
|
lastSeen: integer("lastSeen").notNull(),
|
||||||
|
lastCollectedAt: integer("lastCollectedAt").notNull(),
|
||||||
|
|
||||||
|
username: text("username"),
|
||||||
|
hostname: text("hostname"),
|
||||||
|
platform: text("platform"),
|
||||||
|
osVersion: text("osVersion"),
|
||||||
|
kernelVersion: text("kernelVersion"),
|
||||||
|
arch: text("arch"),
|
||||||
|
deviceModel: text("deviceModel"),
|
||||||
|
serialNumber: text("serialNumber"),
|
||||||
|
platformFingerprint: text("platformFingerprint"),
|
||||||
|
|
||||||
|
// Platform-agnostic checks
|
||||||
|
|
||||||
|
biometricsEnabled: integer("biometricsEnabled", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
diskEncrypted: integer("diskEncrypted", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
firewallEnabled: integer("firewallEnabled", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
autoUpdatesEnabled: integer("autoUpdatesEnabled", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
tpmAvailable: integer("tpmAvailable", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
// Windows-specific posture check information
|
||||||
|
|
||||||
|
windowsDefenderEnabled: integer("windowsDefenderEnabled", {
|
||||||
|
mode: "boolean"
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
// macOS-specific posture check information
|
||||||
|
|
||||||
|
macosSipEnabled: integer("macosSipEnabled", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
macosGatekeeperEnabled: integer("macosGatekeeperEnabled", {
|
||||||
|
mode: "boolean"
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
macosFirewallStealthMode: integer("macosFirewallStealthMode", {
|
||||||
|
mode: "boolean"
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
// Linux-specific posture check information
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: integer("linuxAppArmorEnabled", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
linuxSELinuxEnabled: integer("linuxSELinuxEnabled", {
|
||||||
|
mode: "boolean"
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.default(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fingerprintSnapshots = sqliteTable("fingerprintSnapshots", {
|
||||||
|
snapshotId: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
|
|
||||||
|
fingerprintId: integer("fingerprintId").references(
|
||||||
|
() => currentFingerprint.fingerprintId,
|
||||||
|
{
|
||||||
|
onDelete: "set null"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
username: text("username"),
|
||||||
|
hostname: text("hostname"),
|
||||||
|
platform: text("platform"),
|
||||||
|
osVersion: text("osVersion"),
|
||||||
|
kernelVersion: text("kernelVersion"),
|
||||||
|
arch: text("arch"),
|
||||||
|
deviceModel: text("deviceModel"),
|
||||||
|
serialNumber: text("serialNumber"),
|
||||||
|
platformFingerprint: text("platformFingerprint"),
|
||||||
|
|
||||||
// Platform-agnostic checks
|
// Platform-agnostic checks
|
||||||
|
|
||||||
@@ -476,48 +582,10 @@ export const clientPostureSnapshots = sqliteTable("clientPostureSnapshots", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.default(false),
|
.default(false),
|
||||||
|
|
||||||
|
hash: text("hash").notNull(),
|
||||||
collectedAt: integer("collectedAt").notNull()
|
collectedAt: integer("collectedAt").notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const olms = sqliteTable("olms", {
|
|
||||||
olmId: text("id").primaryKey(),
|
|
||||||
secretHash: text("secretHash").notNull(),
|
|
||||||
dateCreated: text("dateCreated").notNull(),
|
|
||||||
version: text("version"),
|
|
||||||
agent: text("agent"),
|
|
||||||
name: text("name"),
|
|
||||||
clientId: integer("clientId").references(() => clients.clientId, {
|
|
||||||
// we will switch this depending on the current org it wants to connect to
|
|
||||||
onDelete: "set null"
|
|
||||||
}),
|
|
||||||
userId: text("userId").references(() => users.userId, {
|
|
||||||
// optionally tied to a user and in this case delete when the user deletes
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
archived: integer("archived", { mode: "boolean" }).notNull().default(false)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fingerprints = sqliteTable("fingerprints", {
|
|
||||||
fingerprintId: integer("id").primaryKey({ autoIncrement: true }),
|
|
||||||
|
|
||||||
olmId: text("olmId")
|
|
||||||
.references(() => olms.olmId, { onDelete: "cascade" })
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
firstSeen: integer("firstSeen").notNull(),
|
|
||||||
lastSeen: integer("lastSeen").notNull(),
|
|
||||||
|
|
||||||
username: text("username"),
|
|
||||||
hostname: text("hostname"),
|
|
||||||
platform: text("platform"), // macos | windows | linux | ios | android | unknown
|
|
||||||
osVersion: text("osVersion"),
|
|
||||||
kernelVersion: text("kernelVersion"),
|
|
||||||
arch: text("arch"),
|
|
||||||
deviceModel: text("deviceModel"),
|
|
||||||
serialNumber: text("serialNumber"),
|
|
||||||
platformFingerprint: text("platformFingerprint")
|
|
||||||
});
|
|
||||||
|
|
||||||
export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
|
export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
|
||||||
codeId: integer("id").primaryKey({ autoIncrement: true }),
|
codeId: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
userId: text("userId")
|
userId: text("userId")
|
||||||
|
|||||||
@@ -587,13 +587,15 @@ export async function updateProxyResources(
|
|||||||
|
|
||||||
// Sync rules
|
// Sync rules
|
||||||
for (const [index, rule] of resourceData.rules?.entries() || []) {
|
for (const [index, rule] of resourceData.rules?.entries() || []) {
|
||||||
|
const intendedPriority = rule.priority ?? index + 1;
|
||||||
const existingRule = existingRules[index];
|
const existingRule = existingRules[index];
|
||||||
if (existingRule) {
|
if (existingRule) {
|
||||||
if (
|
if (
|
||||||
existingRule.action !== getRuleAction(rule.action) ||
|
existingRule.action !== getRuleAction(rule.action) ||
|
||||||
existingRule.match !== rule.match.toUpperCase() ||
|
existingRule.match !== rule.match.toUpperCase() ||
|
||||||
existingRule.value !==
|
existingRule.value !==
|
||||||
getRuleValue(rule.match.toUpperCase(), rule.value)
|
getRuleValue(rule.match.toUpperCase(), rule.value) ||
|
||||||
|
existingRule.priority !== intendedPriority
|
||||||
) {
|
) {
|
||||||
validateRule(rule);
|
validateRule(rule);
|
||||||
await trx
|
await trx
|
||||||
@@ -604,7 +606,8 @@ export async function updateProxyResources(
|
|||||||
value: getRuleValue(
|
value: getRuleValue(
|
||||||
rule.match.toUpperCase(),
|
rule.match.toUpperCase(),
|
||||||
rule.value
|
rule.value
|
||||||
)
|
),
|
||||||
|
priority: intendedPriority
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(resourceRules.ruleId, existingRule.ruleId)
|
eq(resourceRules.ruleId, existingRule.ruleId)
|
||||||
@@ -620,7 +623,7 @@ export async function updateProxyResources(
|
|||||||
rule.match.toUpperCase(),
|
rule.match.toUpperCase(),
|
||||||
rule.value
|
rule.value
|
||||||
),
|
),
|
||||||
priority: index + 1 // start priorities at 1
|
priority: intendedPriority
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -809,7 +812,7 @@ export async function updateProxyResources(
|
|||||||
action: getRuleAction(rule.action),
|
action: getRuleAction(rule.action),
|
||||||
match: rule.match.toUpperCase(),
|
match: rule.match.toUpperCase(),
|
||||||
value: getRuleValue(rule.match.toUpperCase(), rule.value),
|
value: getRuleValue(rule.match.toUpperCase(), rule.value),
|
||||||
priority: index + 1 // start priorities at 1
|
priority: rule.priority ?? index + 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ export const RuleSchema = z
|
|||||||
.object({
|
.object({
|
||||||
action: z.enum(["allow", "deny", "pass"]),
|
action: z.enum(["allow", "deny", "pass"]),
|
||||||
match: z.enum(["cidr", "path", "ip", "country", "asn"]),
|
match: z.enum(["cidr", "path", "ip", "country", "asn"]),
|
||||||
value: z.string()
|
value: z.string(),
|
||||||
|
priority: z.int().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(rule) => {
|
(rule) => {
|
||||||
@@ -268,6 +269,39 @@ export const ResourceSchema = z
|
|||||||
path: ["auth"],
|
path: ["auth"],
|
||||||
error: "When protocol is 'tcp' or 'udp', 'auth' must not be provided"
|
error: "When protocol is 'tcp' or 'udp', 'auth' must not be provided"
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(resource) => {
|
||||||
|
// Skip validation for targets-only resources
|
||||||
|
if (isTargetsOnlyResource(resource)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Skip validation if no rules are defined
|
||||||
|
if (!resource.rules || resource.rules.length === 0) return true;
|
||||||
|
|
||||||
|
const finalPriorities: number[] = [];
|
||||||
|
let priorityCounter = 1;
|
||||||
|
|
||||||
|
// Gather priorities, assigning auto-priorities where needed
|
||||||
|
// following the logic from the backend implementation where
|
||||||
|
// empty priorities are auto-assigned a value of 1 + index of rule
|
||||||
|
for (const rule of resource.rules) {
|
||||||
|
if (rule.priority !== undefined) {
|
||||||
|
finalPriorities.push(rule.priority);
|
||||||
|
} else {
|
||||||
|
finalPriorities.push(priorityCounter);
|
||||||
|
}
|
||||||
|
priorityCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate for duplicate priorities
|
||||||
|
return finalPriorities.length === new Set(finalPriorities).size;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ["rules"],
|
||||||
|
message:
|
||||||
|
"Rules have conflicting or invalid priorities (must be unique, including auto-assigned ones)"
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export function isTargetsOnlyResource(resource: any): boolean {
|
export function isTargetsOnlyResource(resource: any): boolean {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import logger from "@server/logger";
|
|||||||
import { sendTerminateClient } from "@server/routers/client/terminate";
|
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||||
import { and, eq, notInArray, type InferInsertModel } from "drizzle-orm";
|
import { and, eq, notInArray, type InferInsertModel } from "drizzle-orm";
|
||||||
import { rebuildClientAssociationsFromClient } from "./rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "./rebuildClientAssociations";
|
||||||
|
import { OlmErrorCodes } from "@server/routers/olm/error";
|
||||||
|
|
||||||
export async function calculateUserClientsForOrgs(
|
export async function calculateUserClientsForOrgs(
|
||||||
userId: string,
|
userId: string,
|
||||||
@@ -305,6 +306,7 @@ async function cleanupOrphanedClients(
|
|||||||
if (deletedClient.olmId) {
|
if (deletedClient.olmId) {
|
||||||
await sendTerminateClient(
|
await sendTerminateClient(
|
||||||
deletedClient.clientId,
|
deletedClient.clientId,
|
||||||
|
OlmErrorCodes.TERMINATED_DELETED,
|
||||||
deletedClient.olmId
|
deletedClient.olmId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { cleanUpOldLogs as cleanUpOldAccessLogs } from "#dynamic/lib/logAccessAu
|
|||||||
import { cleanUpOldLogs as cleanUpOldActionLogs } from "#dynamic/middlewares/logActionAudit";
|
import { cleanUpOldLogs as cleanUpOldActionLogs } from "#dynamic/middlewares/logActionAudit";
|
||||||
import { cleanUpOldLogs as cleanUpOldRequestLogs } from "@server/routers/badger/logRequestAudit";
|
import { cleanUpOldLogs as cleanUpOldRequestLogs } from "@server/routers/badger/logRequestAudit";
|
||||||
import { gt, or } from "drizzle-orm";
|
import { gt, or } from "drizzle-orm";
|
||||||
|
import { cleanUpOldFingerprintSnapshots } from "@server/routers/olm/fingerprintingUtils";
|
||||||
|
|
||||||
export function initLogCleanupInterval() {
|
export function initLogCleanupInterval() {
|
||||||
return setInterval(
|
return setInterval(
|
||||||
@@ -56,6 +57,8 @@ export function initLogCleanupInterval() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await cleanUpOldFingerprintSnapshots(365);
|
||||||
},
|
},
|
||||||
3 * 60 * 60 * 1000
|
3 * 60 * 60 * 1000
|
||||||
); // every 3 hours
|
); // every 3 hours
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// This is a placeholder value replaced by the build process
|
||||||
export const APP_VERSION = "1.14.0";
|
export const APP_VERSION = "1.15.0";
|
||||||
|
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|||||||
110
server/private/routers/approvals/countApprovals.ts
Normal file
110
server/private/routers/approvals/countApprovals.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
|
import type { Request, Response, NextFunction } from "express";
|
||||||
|
import { approvals, db, type Approval } from "@server/db";
|
||||||
|
import { eq, sql, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
|
||||||
|
const paramsSchema = z.strictObject({
|
||||||
|
orgId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
const querySchema = z.strictObject({
|
||||||
|
approvalState: z
|
||||||
|
.enum(["pending", "approved", "denied", "all"])
|
||||||
|
.optional()
|
||||||
|
.default("all")
|
||||||
|
.catch("all")
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CountApprovalsResponse = {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function countApprovals(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const parsedParams = paramsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedQuery = querySchema.safeParse(req.query);
|
||||||
|
if (!parsedQuery.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedQuery.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { approvalState } = parsedQuery.data;
|
||||||
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
|
let state: Array<Approval["decision"]> = [];
|
||||||
|
switch (approvalState) {
|
||||||
|
case "pending":
|
||||||
|
state = ["pending"];
|
||||||
|
break;
|
||||||
|
case "approved":
|
||||||
|
state = ["approved"];
|
||||||
|
break;
|
||||||
|
case "denied":
|
||||||
|
state = ["denied"];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state = ["approved", "denied", "pending"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [{ count }] = await db
|
||||||
|
.select({ count: sql<number>`count(*)` })
|
||||||
|
.from(approvals)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(approvals.orgId, orgId),
|
||||||
|
sql`${approvals.decision} in ${state}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return response<CountApprovalsResponse>(res, {
|
||||||
|
data: {
|
||||||
|
count
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Approval count retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,3 +13,4 @@
|
|||||||
|
|
||||||
export * from "./listApprovals";
|
export * from "./listApprovals";
|
||||||
export * from "./processPendingApproval";
|
export * from "./processPendingApproval";
|
||||||
|
export * from "./countApprovals";
|
||||||
|
|||||||
@@ -21,9 +21,18 @@ import type { Request, Response, NextFunction } from "express";
|
|||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { getOrgTierData } from "@server/lib/billing";
|
import { getOrgTierData } from "@server/lib/billing";
|
||||||
import { TierId } from "@server/lib/billing/tiers";
|
import { TierId } from "@server/lib/billing/tiers";
|
||||||
import { approvals, clients, db, users, type Approval } from "@server/db";
|
import {
|
||||||
|
approvals,
|
||||||
|
clients,
|
||||||
|
db,
|
||||||
|
users,
|
||||||
|
olms,
|
||||||
|
currentFingerprint,
|
||||||
|
type Approval
|
||||||
|
} from "@server/db";
|
||||||
import { eq, isNull, sql, not, and, desc } from "drizzle-orm";
|
import { eq, isNull, sql, not, and, desc } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
|
import { getUserDeviceName } from "@server/db/names";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -46,14 +55,20 @@ const querySchema = z.strictObject({
|
|||||||
.enum(["pending", "approved", "denied", "all"])
|
.enum(["pending", "approved", "denied", "all"])
|
||||||
.optional()
|
.optional()
|
||||||
.default("all")
|
.default("all")
|
||||||
.catch("all")
|
.catch("all"),
|
||||||
|
clientId: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => (val ? Number(val) : undefined))
|
||||||
|
.pipe(z.number().int().positive().optional())
|
||||||
});
|
});
|
||||||
|
|
||||||
async function queryApprovals(
|
async function queryApprovals(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
limit: number,
|
limit: number,
|
||||||
offset: number,
|
offset: number,
|
||||||
approvalState: z.infer<typeof querySchema>["approvalState"]
|
approvalState: z.infer<typeof querySchema>["approvalState"],
|
||||||
|
clientId?: number
|
||||||
) {
|
) {
|
||||||
let state: Array<Approval["decision"]> = [];
|
let state: Array<Approval["decision"]> = [];
|
||||||
switch (approvalState) {
|
switch (approvalState) {
|
||||||
@@ -80,8 +95,19 @@ async function queryApprovals(
|
|||||||
user: {
|
user: {
|
||||||
name: users.name,
|
name: users.name,
|
||||||
userId: users.userId,
|
userId: users.userId,
|
||||||
username: users.username
|
username: users.username,
|
||||||
}
|
email: users.email
|
||||||
|
},
|
||||||
|
clientName: clients.name,
|
||||||
|
niceId: clients.niceId,
|
||||||
|
deviceModel: currentFingerprint.deviceModel,
|
||||||
|
fingerprintPlatform: currentFingerprint.platform,
|
||||||
|
fingerprintOsVersion: currentFingerprint.osVersion,
|
||||||
|
fingerprintKernelVersion: currentFingerprint.kernelVersion,
|
||||||
|
fingerprintArch: currentFingerprint.arch,
|
||||||
|
fingerprintSerialNumber: currentFingerprint.serialNumber,
|
||||||
|
fingerprintUsername: currentFingerprint.username,
|
||||||
|
fingerprintHostname: currentFingerprint.hostname
|
||||||
})
|
})
|
||||||
.from(approvals)
|
.from(approvals)
|
||||||
.innerJoin(users, and(eq(approvals.userId, users.userId)))
|
.innerJoin(users, and(eq(approvals.userId, users.userId)))
|
||||||
@@ -92,10 +118,13 @@ async function queryApprovals(
|
|||||||
not(isNull(clients.userId)) // only user devices
|
not(isNull(clients.userId)) // only user devices
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
|
.leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(approvals.orgId, orgId),
|
eq(approvals.orgId, orgId),
|
||||||
sql`${approvals.decision} in ${state}`
|
sql`${approvals.decision} in ${state}`,
|
||||||
|
...(clientId ? [eq(approvals.clientId, clientId)] : [])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.orderBy(
|
.orderBy(
|
||||||
@@ -104,7 +133,58 @@ async function queryApprovals(
|
|||||||
)
|
)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
return res;
|
|
||||||
|
// Process results to format device names and build fingerprint objects
|
||||||
|
return res.map((approval) => {
|
||||||
|
const model = approval.deviceModel || null;
|
||||||
|
const deviceName = approval.clientName
|
||||||
|
? getUserDeviceName(model, approval.clientName)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Build fingerprint object if any fingerprint data exists
|
||||||
|
const hasFingerprintData =
|
||||||
|
approval.fingerprintPlatform ||
|
||||||
|
approval.fingerprintOsVersion ||
|
||||||
|
approval.fingerprintKernelVersion ||
|
||||||
|
approval.fingerprintArch ||
|
||||||
|
approval.fingerprintSerialNumber ||
|
||||||
|
approval.fingerprintUsername ||
|
||||||
|
approval.fingerprintHostname ||
|
||||||
|
approval.deviceModel;
|
||||||
|
|
||||||
|
const fingerprint = hasFingerprintData
|
||||||
|
? {
|
||||||
|
platform: approval.fingerprintPlatform || null,
|
||||||
|
osVersion: approval.fingerprintOsVersion || null,
|
||||||
|
kernelVersion: approval.fingerprintKernelVersion || null,
|
||||||
|
arch: approval.fingerprintArch || null,
|
||||||
|
deviceModel: approval.deviceModel || null,
|
||||||
|
serialNumber: approval.fingerprintSerialNumber || null,
|
||||||
|
username: approval.fingerprintUsername || null,
|
||||||
|
hostname: approval.fingerprintHostname || null
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const {
|
||||||
|
clientName,
|
||||||
|
deviceModel,
|
||||||
|
fingerprintPlatform,
|
||||||
|
fingerprintOsVersion,
|
||||||
|
fingerprintKernelVersion,
|
||||||
|
fingerprintArch,
|
||||||
|
fingerprintSerialNumber,
|
||||||
|
fingerprintUsername,
|
||||||
|
fingerprintHostname,
|
||||||
|
...rest
|
||||||
|
} = approval;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
deviceName,
|
||||||
|
fingerprint,
|
||||||
|
niceId: approval.niceId || null
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListApprovalsResponse = {
|
export type ListApprovalsResponse = {
|
||||||
@@ -137,7 +217,7 @@ export async function listApprovals(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { limit, offset, approvalState } = parsedQuery.data;
|
const { limit, offset, approvalState, clientId } = parsedQuery.data;
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
@@ -158,7 +238,8 @@ export async function listApprovals(
|
|||||||
orgId.toString(),
|
orgId.toString(),
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
approvalState
|
approvalState,
|
||||||
|
clientId
|
||||||
);
|
);
|
||||||
|
|
||||||
const [{ count }] = await db
|
const [{ count }] = await db
|
||||||
|
|||||||
@@ -321,6 +321,13 @@ authenticated.get(
|
|||||||
approval.listApprovals
|
approval.listApprovals
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/approvals/count",
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.listApprovals),
|
||||||
|
approval.countApprovals
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/org/:orgId/approvals/:approvalId",
|
"/org/:orgId/approvals/:approvalId",
|
||||||
verifyValidLicense,
|
verifyValidLicense,
|
||||||
|
|||||||
@@ -35,7 +35,29 @@ const paramsSchema = z.strictObject({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const bodySchema = z.strictObject({
|
const bodySchema = z.strictObject({
|
||||||
logoUrl: z.url(),
|
logoUrl: z
|
||||||
|
.union([
|
||||||
|
z.string().length(0),
|
||||||
|
z.url().refine(
|
||||||
|
async (url) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
return (
|
||||||
|
response.status === 200 &&
|
||||||
|
(
|
||||||
|
response.headers.get("content-type") ?? ""
|
||||||
|
).startsWith("image/")
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: "Invalid logo URL, must be a valid image URL"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
])
|
||||||
|
.optional(),
|
||||||
logoWidth: z.coerce.number<number>().min(1),
|
logoWidth: z.coerce.number<number>().min(1),
|
||||||
logoHeight: z.coerce.number<number>().min(1),
|
logoHeight: z.coerce.number<number>().min(1),
|
||||||
resourceTitle: z.string(),
|
resourceTitle: z.string(),
|
||||||
@@ -95,6 +117,10 @@ export async function upsertLoginPageBranding(
|
|||||||
typeof loginPageBranding
|
typeof loginPageBranding
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
if ((updateData.logoUrl ?? "").trim().length === 0) {
|
||||||
|
updateData.logoUrl = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
build !== "saas" &&
|
build !== "saas" &&
|
||||||
!config.getRawPrivateConfig().flags.use_org_only_idp
|
!config.getRawPrivateConfig().flags.use_org_only_idp
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
||||||
|
import { OlmErrorCodes, sendOlmError } from "@server/routers/olm/error";
|
||||||
|
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||||
|
|
||||||
const reGenerateSecretParamsSchema = z.strictObject({
|
const reGenerateSecretParamsSchema = z.strictObject({
|
||||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -117,12 +119,12 @@ export async function reGenerateClientSecret(
|
|||||||
|
|
||||||
// Only disconnect if explicitly requested
|
// Only disconnect if explicitly requested
|
||||||
if (disconnect) {
|
if (disconnect) {
|
||||||
const payload = {
|
|
||||||
type: `olm/terminate`,
|
|
||||||
data: {}
|
|
||||||
};
|
|
||||||
// Don't await this to prevent blocking the response
|
// Don't await this to prevent blocking the response
|
||||||
sendToClient(existingOlms[0].olmId, payload).catch((error) => {
|
sendTerminateClient(
|
||||||
|
clientId,
|
||||||
|
OlmErrorCodes.TERMINATED_REKEYED,
|
||||||
|
existingOlms[0].olmId
|
||||||
|
).catch((error) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to send termination message to olm:",
|
"Failed to send termination message to olm:",
|
||||||
error
|
error
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ const sendToClientLocal = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle config version
|
// Handle config version
|
||||||
let configVersion = await getClientConfigVersion(clientId);
|
const configVersion = await getClientConfigVersion(clientId);
|
||||||
|
|
||||||
// Add config version to message
|
// Add config version to message
|
||||||
const messageWithVersion = {
|
const messageWithVersion = {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
import { sendTerminateClient } from "./terminate";
|
import { sendTerminateClient } from "./terminate";
|
||||||
|
import { OlmErrorCodes } from "../olm/error";
|
||||||
|
|
||||||
const archiveClientSchema = z.strictObject({
|
const archiveClientSchema = z.strictObject({
|
||||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -79,11 +80,6 @@ export async function archiveClient(
|
|||||||
|
|
||||||
// Rebuild associations to clean up related data
|
// Rebuild associations to clean up related data
|
||||||
await rebuildClientAssociationsFromClient(client, trx);
|
await rebuildClientAssociationsFromClient(client, trx);
|
||||||
|
|
||||||
// Send terminate signal if there's an associated OLM
|
|
||||||
if (client.olmId) {
|
|
||||||
await sendTerminateClient(client.clientId, client.olmId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { sendTerminateClient } from "./terminate";
|
import { sendTerminateClient } from "./terminate";
|
||||||
|
import { OlmErrorCodes } from "../olm/error";
|
||||||
|
|
||||||
const blockClientSchema = z.strictObject({
|
const blockClientSchema = z.strictObject({
|
||||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -78,7 +79,7 @@ export async function blockClient(
|
|||||||
|
|
||||||
// Send terminate signal if there's an associated OLM and it's connected
|
// Send terminate signal if there's an associated OLM and it's connected
|
||||||
if (client.olmId && client.online) {
|
if (client.olmId && client.online) {
|
||||||
await sendTerminateClient(client.clientId, client.olmId);
|
await sendTerminateClient(client.clientId, OlmErrorCodes.TERMINATED_BLOCKED, client.olmId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
import { sendTerminateClient } from "./terminate";
|
import { sendTerminateClient } from "./terminate";
|
||||||
|
import { OlmErrorCodes } from "../olm/error";
|
||||||
|
|
||||||
const deleteClientSchema = z.strictObject({
|
const deleteClientSchema = z.strictObject({
|
||||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
clientId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -91,7 +92,7 @@ export async function deleteClient(
|
|||||||
await rebuildClientAssociationsFromClient(deletedClient, trx);
|
await rebuildClientAssociationsFromClient(deletedClient, trx);
|
||||||
|
|
||||||
if (olm) {
|
if (olm) {
|
||||||
await sendTerminateClient(deletedClient.clientId, olm.olmId); // the olmId needs to be provided because it cant look it up after deletion
|
await sendTerminateClient(deletedClient.clientId, OlmErrorCodes.TERMINATED_DELETED, olm.olmId); // the olmId needs to be provided because it cant look it up after deletion
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, olms } from "@server/db";
|
import { db, olms } from "@server/db";
|
||||||
import { clients, fingerprints } from "@server/db";
|
import { clients, currentFingerprint } from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -11,6 +11,7 @@ import stoi from "@server/lib/stoi";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { getUserDeviceName } from "@server/db/names";
|
import { getUserDeviceName } from "@server/db/names";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
const getClientSchema = z.strictObject({
|
const getClientSchema = z.strictObject({
|
||||||
clientId: z
|
clientId: z
|
||||||
@@ -30,7 +31,10 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
|
|||||||
.from(clients)
|
.from(clients)
|
||||||
.where(eq(clients.clientId, clientId))
|
.where(eq(clients.clientId, clientId))
|
||||||
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
.leftJoin(fingerprints, eq(olms.olmId, fingerprints.olmId))
|
.leftJoin(
|
||||||
|
currentFingerprint,
|
||||||
|
eq(olms.olmId, currentFingerprint.olmId)
|
||||||
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
return res;
|
return res;
|
||||||
} else if (niceId && orgId) {
|
} else if (niceId && orgId) {
|
||||||
@@ -39,16 +43,137 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
|
|||||||
.from(clients)
|
.from(clients)
|
||||||
.where(and(eq(clients.niceId, niceId), eq(clients.orgId, orgId)))
|
.where(and(eq(clients.niceId, niceId), eq(clients.orgId, orgId)))
|
||||||
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
.leftJoin(fingerprints, eq(olms.olmId, fingerprints.olmId))
|
.leftJoin(
|
||||||
|
currentFingerprint,
|
||||||
|
eq(olms.olmId, currentFingerprint.olmId)
|
||||||
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PostureData = {
|
||||||
|
biometricsEnabled?: boolean | null;
|
||||||
|
diskEncrypted?: boolean | null;
|
||||||
|
firewallEnabled?: boolean | null;
|
||||||
|
autoUpdatesEnabled?: boolean | null;
|
||||||
|
tpmAvailable?: boolean | null;
|
||||||
|
windowsDefenderEnabled?: boolean | null;
|
||||||
|
macosSipEnabled?: boolean | null;
|
||||||
|
macosGatekeeperEnabled?: boolean | null;
|
||||||
|
macosFirewallStealthMode?: boolean | null;
|
||||||
|
linuxAppArmorEnabled?: boolean | null;
|
||||||
|
linuxSELinuxEnabled?: boolean | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPlatformPostureData(
|
||||||
|
platform: string | null | undefined,
|
||||||
|
fingerprint: typeof currentFingerprint.$inferSelect | null
|
||||||
|
): PostureData | null {
|
||||||
|
if (!fingerprint) return null;
|
||||||
|
|
||||||
|
const normalizedPlatform = platform?.toLowerCase() || "unknown";
|
||||||
|
const posture: PostureData = {};
|
||||||
|
|
||||||
|
// Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Defender
|
||||||
|
if (normalizedPlatform === "windows") {
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) {
|
||||||
|
posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||||
|
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||||
|
}
|
||||||
|
if (fingerprint.windowsDefenderEnabled !== null && fingerprint.windowsDefenderEnabled !== undefined) {
|
||||||
|
posture.windowsDefenderEnabled = fingerprint.windowsDefenderEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode
|
||||||
|
else if (normalizedPlatform === "macos") {
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.macosSipEnabled !== null && fingerprint.macosSipEnabled !== undefined) {
|
||||||
|
posture.macosSipEnabled = fingerprint.macosSipEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.macosGatekeeperEnabled !== null && fingerprint.macosGatekeeperEnabled !== undefined) {
|
||||||
|
posture.macosGatekeeperEnabled = fingerprint.macosGatekeeperEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.macosFirewallStealthMode !== null && fingerprint.macosFirewallStealthMode !== undefined) {
|
||||||
|
posture.macosFirewallStealthMode = fingerprint.macosFirewallStealthMode;
|
||||||
|
}
|
||||||
|
if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) {
|
||||||
|
posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Linux: Hard drive encryption, Firewall, AppArmor, SELinux, TPM availability
|
||||||
|
else if (normalizedPlatform === "linux") {
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.linuxAppArmorEnabled !== null && fingerprint.linuxAppArmorEnabled !== undefined) {
|
||||||
|
posture.linuxAppArmorEnabled = fingerprint.linuxAppArmorEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.linuxSELinuxEnabled !== null && fingerprint.linuxSELinuxEnabled !== undefined) {
|
||||||
|
posture.linuxSELinuxEnabled = fingerprint.linuxSELinuxEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||||
|
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// iOS: Biometric configuration
|
||||||
|
else if (normalizedPlatform === "ios") {
|
||||||
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Android: Screen lock, Biometric configuration, Hard drive encryption
|
||||||
|
else if (normalizedPlatform === "android") {
|
||||||
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only return if we have at least one posture field
|
||||||
|
return Object.keys(posture).length > 0 ? posture : null;
|
||||||
|
}
|
||||||
|
|
||||||
export type GetClientResponse = NonNullable<
|
export type GetClientResponse = NonNullable<
|
||||||
Awaited<ReturnType<typeof query>>
|
Awaited<ReturnType<typeof query>>
|
||||||
>["clients"] & {
|
>["clients"] & {
|
||||||
olmId: string | null;
|
olmId: string | null;
|
||||||
|
agent: string | null;
|
||||||
|
olmVersion: string | null;
|
||||||
|
fingerprint: {
|
||||||
|
username: string | null;
|
||||||
|
hostname: string | null;
|
||||||
|
platform: string | null;
|
||||||
|
osVersion: string | null;
|
||||||
|
kernelVersion: string | null;
|
||||||
|
arch: string | null;
|
||||||
|
deviceModel: string | null;
|
||||||
|
serialNumber: string | null;
|
||||||
|
firstSeen: number | null;
|
||||||
|
lastSeen: number | null;
|
||||||
|
} | null;
|
||||||
|
posture: PostureData | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -111,14 +236,44 @@ export async function getClient(
|
|||||||
// Replace name with device name if OLM exists
|
// Replace name with device name if OLM exists
|
||||||
let clientName = client.clients.name;
|
let clientName = client.clients.name;
|
||||||
if (client.olms) {
|
if (client.olms) {
|
||||||
const model = client.fingerprints?.deviceModel || null;
|
const model = client.currentFingerprint?.deviceModel || null;
|
||||||
clientName = getUserDeviceName(model, client.clients.name);
|
clientName = getUserDeviceName(model, client.clients.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build fingerprint data if available
|
||||||
|
const fingerprintData = client.currentFingerprint
|
||||||
|
? {
|
||||||
|
username: client.currentFingerprint.username || null,
|
||||||
|
hostname: client.currentFingerprint.hostname || null,
|
||||||
|
platform: client.currentFingerprint.platform || null,
|
||||||
|
osVersion: client.currentFingerprint.osVersion || null,
|
||||||
|
kernelVersion:
|
||||||
|
client.currentFingerprint.kernelVersion || null,
|
||||||
|
arch: client.currentFingerprint.arch || null,
|
||||||
|
deviceModel: client.currentFingerprint.deviceModel || null,
|
||||||
|
serialNumber: client.currentFingerprint.serialNumber || null,
|
||||||
|
firstSeen: client.currentFingerprint.firstSeen || null,
|
||||||
|
lastSeen: client.currentFingerprint.lastSeen || null
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Build posture data if available (platform-specific)
|
||||||
|
let postureData: PostureData | null = null;
|
||||||
|
if (build !== "oss") {
|
||||||
|
postureData = getPlatformPostureData(
|
||||||
|
client.currentFingerprint?.platform || null,
|
||||||
|
client.currentFingerprint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const data: GetClientResponse = {
|
const data: GetClientResponse = {
|
||||||
...client.clients,
|
...client.clients,
|
||||||
name: clientName,
|
name: clientName,
|
||||||
olmId: client.olms ? client.olms.olmId : null
|
olmId: client.olms ? client.olms.olmId : null,
|
||||||
|
agent: client.olms?.agent || null,
|
||||||
|
olmVersion: client.olms?.version || null,
|
||||||
|
fingerprint: fingerprintData,
|
||||||
|
posture: postureData
|
||||||
};
|
};
|
||||||
|
|
||||||
return response<GetClientResponse>(res, {
|
return response<GetClientResponse>(res, {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
sites,
|
sites,
|
||||||
userClients,
|
userClients,
|
||||||
clientSitesAssociationsCache,
|
clientSitesAssociationsCache,
|
||||||
fingerprints
|
currentFingerprint
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -143,13 +143,20 @@ function queryClients(
|
|||||||
olmArchived: olms.archived,
|
olmArchived: olms.archived,
|
||||||
archived: clients.archived,
|
archived: clients.archived,
|
||||||
blocked: clients.blocked,
|
blocked: clients.blocked,
|
||||||
deviceModel: fingerprints.deviceModel
|
deviceModel: currentFingerprint.deviceModel,
|
||||||
|
fingerprintPlatform: currentFingerprint.platform,
|
||||||
|
fingerprintOsVersion: currentFingerprint.osVersion,
|
||||||
|
fingerprintKernelVersion: currentFingerprint.kernelVersion,
|
||||||
|
fingerprintArch: currentFingerprint.arch,
|
||||||
|
fingerprintSerialNumber: currentFingerprint.serialNumber,
|
||||||
|
fingerprintUsername: currentFingerprint.username,
|
||||||
|
fingerprintHostname: currentFingerprint.hostname
|
||||||
})
|
})
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
||||||
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
.leftJoin(users, eq(clients.userId, users.userId))
|
.leftJoin(users, eq(clients.userId, users.userId))
|
||||||
.leftJoin(fingerprints, eq(olms.olmId, fingerprints.olmId))
|
.leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId))
|
||||||
.where(and(...conditions));
|
.where(and(...conditions));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,10 +175,7 @@ async function getSiteAssociations(clientIds: number[]) {
|
|||||||
.where(inArray(clientSitesAssociationsCache.clientId, clientIds));
|
.where(inArray(clientSitesAssociationsCache.clientId, clientIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientWithSites = Omit<
|
type ClientWithSites = Awaited<ReturnType<typeof queryClients>>[0] & {
|
||||||
Awaited<ReturnType<typeof queryClients>>[0],
|
|
||||||
"deviceModel"
|
|
||||||
> & {
|
|
||||||
sites: Array<{
|
sites: Array<{
|
||||||
siteId: number;
|
siteId: number;
|
||||||
siteName: string | null;
|
siteName: string | null;
|
||||||
@@ -317,9 +321,8 @@ export async function listClients(
|
|||||||
const clientsWithSites = clientsList.map((client) => {
|
const clientsWithSites = clientsList.map((client) => {
|
||||||
const model = client.deviceModel || null;
|
const model = client.deviceModel || null;
|
||||||
const newName = getUserDeviceName(model, client.name);
|
const newName = getUserDeviceName(model, client.name);
|
||||||
const { deviceModel, ...clientWithoutDeviceModel } = client;
|
|
||||||
return {
|
return {
|
||||||
...clientWithoutDeviceModel,
|
...client,
|
||||||
name: newName,
|
name: newName,
|
||||||
sites: sitesByClient[client.clientId] || []
|
sites: sitesByClient[client.clientId] || []
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import { db, olms } from "@server/db";
|
import { db, olms } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import { OlmErrorCodes } from "../olm/error";
|
||||||
|
|
||||||
export async function sendTerminateClient(
|
export async function sendTerminateClient(
|
||||||
clientId: number,
|
clientId: number,
|
||||||
|
error: (typeof OlmErrorCodes)[keyof typeof OlmErrorCodes],
|
||||||
olmId?: string | null
|
olmId?: string | null
|
||||||
) {
|
) {
|
||||||
if (!olmId) {
|
if (!olmId) {
|
||||||
@@ -20,6 +22,9 @@ export async function sendTerminateClient(
|
|||||||
|
|
||||||
await sendToClient(olmId, {
|
await sendToClient(olmId, {
|
||||||
type: `olm/terminate`,
|
type: `olm/terminate`,
|
||||||
data: {}
|
data: {
|
||||||
|
code: error.code,
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import * as apiKeys from "./apiKeys";
|
|||||||
import * as logs from "./auditLogs";
|
import * as logs from "./auditLogs";
|
||||||
import * as newt from "./newt";
|
import * as newt from "./newt";
|
||||||
import * as olm from "./olm";
|
import * as olm from "./olm";
|
||||||
|
import * as serverInfo from "./serverInfo";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import {
|
import {
|
||||||
verifyAccessTokenAccess,
|
verifyAccessTokenAccess,
|
||||||
@@ -588,8 +589,8 @@ authenticated.get(
|
|||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/org/:orgId/role/:roleId",
|
"/role/:roleId",
|
||||||
verifyOrgAccess,
|
verifyRoleAccess,
|
||||||
verifyUserHasAction(ActionsEnum.updateRole),
|
verifyUserHasAction(ActionsEnum.updateRole),
|
||||||
logActionAudit(ActionsEnum.updateRole),
|
logActionAudit(ActionsEnum.updateRole),
|
||||||
role.updateRole
|
role.updateRole
|
||||||
@@ -712,6 +713,8 @@ authenticated.get(
|
|||||||
|
|
||||||
authenticated.get(`/org/:orgId/overview`, verifyOrgAccess, org.getOrgOverview);
|
authenticated.get(`/org/:orgId/overview`, verifyOrgAccess, org.getOrgOverview);
|
||||||
|
|
||||||
|
authenticated.get(`/server-info`, serverInfo.getServerInfo);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
`/supporter-key/validate`,
|
`/supporter-key/validate`,
|
||||||
supporterKey.validateSupporterKey
|
supporterKey.validateSupporterKey
|
||||||
|
|||||||
@@ -468,8 +468,8 @@ authenticated.put(
|
|||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/org/:orgId/role/:roleId",
|
"/role/:roleId",
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyRoleAccess,
|
||||||
verifyApiKeyHasAction(ActionsEnum.updateRole),
|
verifyApiKeyHasAction(ActionsEnum.updateRole),
|
||||||
logActionAudit(ActionsEnum.updateRole),
|
logActionAudit(ActionsEnum.updateRole),
|
||||||
role.updateRole
|
role.updateRole
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
import { sendTerminateClient } from "../client/terminate";
|
import { sendTerminateClient } from "../client/terminate";
|
||||||
|
import { OlmErrorCodes } from "./error";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -52,7 +53,7 @@ export async function archiveUserOlm(
|
|||||||
.where(eq(clients.clientId, client.clientId));
|
.where(eq(clients.clientId, client.clientId));
|
||||||
|
|
||||||
await rebuildClientAssociationsFromClient(client, trx);
|
await rebuildClientAssociationsFromClient(client, trx);
|
||||||
await sendTerminateClient(client.clientId, olmId);
|
await sendTerminateClient(client.clientId, OlmErrorCodes.TERMINATED_ARCHIVED, olmId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Archive the OLM (set archived to true)
|
// Archive the OLM (set archived to true)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import logger from "@server/logger";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
import { sendTerminateClient } from "../client/terminate";
|
import { sendTerminateClient } from "../client/terminate";
|
||||||
|
import { OlmErrorCodes } from "./error";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -76,6 +77,7 @@ export async function deleteUserOlm(
|
|||||||
if (olm) {
|
if (olm) {
|
||||||
await sendTerminateClient(
|
await sendTerminateClient(
|
||||||
deletedClient.clientId,
|
deletedClient.clientId,
|
||||||
|
OlmErrorCodes.TERMINATED_DELETED,
|
||||||
olm.olmId
|
olm.olmId
|
||||||
); // the olmId needs to be provided because it cant look it up after deletion
|
); // the olmId needs to be provided because it cant look it up after deletion
|
||||||
}
|
}
|
||||||
|
|||||||
104
server/routers/olm/error.ts
Normal file
104
server/routers/olm/error.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
|
// Error codes for registration failures
|
||||||
|
export const OlmErrorCodes = {
|
||||||
|
OLM_NOT_FOUND: {
|
||||||
|
code: "OLM_NOT_FOUND",
|
||||||
|
message: "The specified device could not be found."
|
||||||
|
},
|
||||||
|
CLIENT_ID_NOT_FOUND: {
|
||||||
|
code: "CLIENT_ID_NOT_FOUND",
|
||||||
|
message: "No client ID was provided in the request."
|
||||||
|
},
|
||||||
|
CLIENT_NOT_FOUND: {
|
||||||
|
code: "CLIENT_NOT_FOUND",
|
||||||
|
message: "The specified client does not exist."
|
||||||
|
},
|
||||||
|
CLIENT_BLOCKED: {
|
||||||
|
code: "CLIENT_BLOCKED",
|
||||||
|
message:
|
||||||
|
"This client has been blocked in this organization and cannot connect. Please contact your administrator."
|
||||||
|
},
|
||||||
|
CLIENT_PENDING: {
|
||||||
|
code: "CLIENT_PENDING",
|
||||||
|
message:
|
||||||
|
"This client is pending approval and cannot connect yet. Please contact your administrator."
|
||||||
|
},
|
||||||
|
ORG_NOT_FOUND: {
|
||||||
|
code: "ORG_NOT_FOUND",
|
||||||
|
message:
|
||||||
|
"The organization could not be found. Please select a valid organization."
|
||||||
|
},
|
||||||
|
USER_ID_NOT_FOUND: {
|
||||||
|
code: "USER_ID_NOT_FOUND",
|
||||||
|
message: "No user ID was provided in the request."
|
||||||
|
},
|
||||||
|
INVALID_USER_SESSION: {
|
||||||
|
code: "INVALID_USER_SESSION",
|
||||||
|
message:
|
||||||
|
"Your user session is invalid or has expired. Please log in again."
|
||||||
|
},
|
||||||
|
USER_ID_MISMATCH: {
|
||||||
|
code: "USER_ID_MISMATCH",
|
||||||
|
message: "The provided user ID does not match the session."
|
||||||
|
},
|
||||||
|
ORG_ACCESS_POLICY_DENIED: {
|
||||||
|
code: "ORG_ACCESS_POLICY_DENIED",
|
||||||
|
message:
|
||||||
|
"Access to this organization has been denied by policy. Please contact your administrator."
|
||||||
|
},
|
||||||
|
ORG_ACCESS_POLICY_PASSWORD_EXPIRED: {
|
||||||
|
code: "ORG_ACCESS_POLICY_PASSWORD_EXPIRED",
|
||||||
|
message:
|
||||||
|
"Access to this organization has been denied because your password has expired. Please visit this organization's dashboard to update your password."
|
||||||
|
},
|
||||||
|
ORG_ACCESS_POLICY_SESSION_EXPIRED: {
|
||||||
|
code: "ORG_ACCESS_POLICY_SESSION_EXPIRED",
|
||||||
|
message:
|
||||||
|
"Access to this organization has been denied because your session has expired. Please log in again to refresh the session."
|
||||||
|
},
|
||||||
|
ORG_ACCESS_POLICY_2FA_REQUIRED: {
|
||||||
|
code: "ORG_ACCESS_POLICY_2FA_REQUIRED",
|
||||||
|
message:
|
||||||
|
"Access to this organization requires two-factor authentication. Please visit this organization's dashboard to enable two-factor authentication."
|
||||||
|
},
|
||||||
|
TERMINATED_REKEYED: {
|
||||||
|
code: "TERMINATED_REKEYED",
|
||||||
|
message:
|
||||||
|
"This session was terminated because encryption keys were regenerated."
|
||||||
|
},
|
||||||
|
TERMINATED_ORG_DELETED: {
|
||||||
|
code: "TERMINATED_ORG_DELETED",
|
||||||
|
message:
|
||||||
|
"This session was terminated because the organization was deleted."
|
||||||
|
},
|
||||||
|
TERMINATED_INACTIVITY: {
|
||||||
|
code: "TERMINATED_INACTIVITY",
|
||||||
|
message: "This session was terminated due to inactivity."
|
||||||
|
},
|
||||||
|
TERMINATED_DELETED: {
|
||||||
|
code: "TERMINATED_DELETED",
|
||||||
|
message: "This session was terminated because it was deleted."
|
||||||
|
},
|
||||||
|
TERMINATED_ARCHIVED: {
|
||||||
|
code: "TERMINATED_ARCHIVED",
|
||||||
|
message: "This session was terminated because it was archived."
|
||||||
|
},
|
||||||
|
TERMINATED_BLOCKED: {
|
||||||
|
code: "TERMINATED_BLOCKED",
|
||||||
|
message: "This session was terminated because access was blocked."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Helper function to send registration error
|
||||||
|
export async function sendOlmError(
|
||||||
|
error: (typeof OlmErrorCodes)[keyof typeof OlmErrorCodes],
|
||||||
|
olmId: string
|
||||||
|
) {
|
||||||
|
sendToClient(olmId, {
|
||||||
|
type: "olm/error",
|
||||||
|
data: {
|
||||||
|
code: error.code,
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
224
server/routers/olm/fingerprintingUtils.ts
Normal file
224
server/routers/olm/fingerprintingUtils.ts
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||||
|
import { currentFingerprint, db, fingerprintSnapshots, Olm } from "@server/db";
|
||||||
|
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
||||||
|
import { desc, eq, lt } from "drizzle-orm";
|
||||||
|
|
||||||
|
function fingerprintSnapshotHash(fingerprint: any, postures: any): string {
|
||||||
|
const canonical = {
|
||||||
|
username: fingerprint.username ?? null,
|
||||||
|
hostname: fingerprint.hostname ?? null,
|
||||||
|
platform: fingerprint.platform ?? null,
|
||||||
|
osVersion: fingerprint.osVersion ?? null,
|
||||||
|
kernelVersion: fingerprint.kernelVersion ?? null,
|
||||||
|
arch: fingerprint.arch ?? null,
|
||||||
|
deviceModel: fingerprint.deviceModel ?? null,
|
||||||
|
serialNumber: fingerprint.serialNumber ?? null,
|
||||||
|
platformFingerprint: fingerprint.platformFingerprint ?? null,
|
||||||
|
|
||||||
|
biometricsEnabled: postures.biometricsEnabled ?? false,
|
||||||
|
diskEncrypted: postures.diskEncrypted ?? false,
|
||||||
|
firewallEnabled: postures.firewallEnabled ?? false,
|
||||||
|
autoUpdatesEnabled: postures.autoUpdatesEnabled ?? false,
|
||||||
|
tpmAvailable: postures.tpmAvailable ?? false,
|
||||||
|
|
||||||
|
windowsDefenderEnabled: postures.windowsDefenderEnabled ?? false,
|
||||||
|
|
||||||
|
macosSipEnabled: postures.macosSipEnabled ?? false,
|
||||||
|
macosGatekeeperEnabled: postures.macosGatekeeperEnabled ?? false,
|
||||||
|
macosFirewallStealthMode: postures.macosFirewallStealthMode ?? false,
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: postures.linuxAppArmorEnabled ?? false,
|
||||||
|
linuxSELinuxEnabled: postures.linuxSELinuxEnabled ?? false
|
||||||
|
};
|
||||||
|
|
||||||
|
return encodeHexLowerCase(
|
||||||
|
sha256(new TextEncoder().encode(JSON.stringify(canonical)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleFingerprintInsertion(
|
||||||
|
olm: Olm,
|
||||||
|
fingerprint: any,
|
||||||
|
postures: any
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!olm?.olmId ||
|
||||||
|
!fingerprint ||
|
||||||
|
!postures ||
|
||||||
|
Object.keys(fingerprint).length === 0 ||
|
||||||
|
Object.keys(postures).length === 0
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const hash = fingerprintSnapshotHash(fingerprint, postures);
|
||||||
|
|
||||||
|
const [current] = await db
|
||||||
|
.select()
|
||||||
|
.from(currentFingerprint)
|
||||||
|
.where(eq(currentFingerprint.olmId, olm.olmId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!current) {
|
||||||
|
const [inserted] = await db
|
||||||
|
.insert(currentFingerprint)
|
||||||
|
.values({
|
||||||
|
olmId: olm.olmId,
|
||||||
|
firstSeen: now,
|
||||||
|
lastSeen: now,
|
||||||
|
lastCollectedAt: now,
|
||||||
|
|
||||||
|
// fingerprint
|
||||||
|
username: fingerprint.username,
|
||||||
|
hostname: fingerprint.hostname,
|
||||||
|
platform: fingerprint.platform,
|
||||||
|
osVersion: fingerprint.osVersion,
|
||||||
|
kernelVersion: fingerprint.kernelVersion,
|
||||||
|
arch: fingerprint.arch,
|
||||||
|
deviceModel: fingerprint.deviceModel,
|
||||||
|
serialNumber: fingerprint.serialNumber,
|
||||||
|
platformFingerprint: fingerprint.platformFingerprint,
|
||||||
|
|
||||||
|
biometricsEnabled: postures.biometricsEnabled,
|
||||||
|
diskEncrypted: postures.diskEncrypted,
|
||||||
|
firewallEnabled: postures.firewallEnabled,
|
||||||
|
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||||
|
tpmAvailable: postures.tpmAvailable,
|
||||||
|
|
||||||
|
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||||
|
|
||||||
|
macosSipEnabled: postures.macosSipEnabled,
|
||||||
|
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||||
|
macosFirewallStealthMode: postures.macosFirewallStealthMode,
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
|
||||||
|
linuxSELinuxEnabled: postures.linuxSELinuxEnabled
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
await db.insert(fingerprintSnapshots).values({
|
||||||
|
fingerprintId: inserted.fingerprintId,
|
||||||
|
|
||||||
|
username: fingerprint.username,
|
||||||
|
hostname: fingerprint.hostname,
|
||||||
|
platform: fingerprint.platform,
|
||||||
|
osVersion: fingerprint.osVersion,
|
||||||
|
kernelVersion: fingerprint.kernelVersion,
|
||||||
|
arch: fingerprint.arch,
|
||||||
|
deviceModel: fingerprint.deviceModel,
|
||||||
|
serialNumber: fingerprint.serialNumber,
|
||||||
|
platformFingerprint: fingerprint.platformFingerprint,
|
||||||
|
|
||||||
|
biometricsEnabled: postures.biometricsEnabled,
|
||||||
|
diskEncrypted: postures.diskEncrypted,
|
||||||
|
firewallEnabled: postures.firewallEnabled,
|
||||||
|
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||||
|
tpmAvailable: postures.tpmAvailable,
|
||||||
|
|
||||||
|
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||||
|
|
||||||
|
macosSipEnabled: postures.macosSipEnabled,
|
||||||
|
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||||
|
macosFirewallStealthMode: postures.macosFirewallStealthMode,
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
|
||||||
|
linuxSELinuxEnabled: postures.linuxSELinuxEnabled,
|
||||||
|
|
||||||
|
hash,
|
||||||
|
collectedAt: now
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [latestSnapshot] = await db
|
||||||
|
.select({ hash: fingerprintSnapshots.hash })
|
||||||
|
.from(fingerprintSnapshots)
|
||||||
|
.where(eq(fingerprintSnapshots.fingerprintId, current.fingerprintId))
|
||||||
|
.orderBy(desc(fingerprintSnapshots.collectedAt))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const changed = !latestSnapshot || latestSnapshot.hash !== hash;
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
await db.insert(fingerprintSnapshots).values({
|
||||||
|
fingerprintId: current.fingerprintId,
|
||||||
|
|
||||||
|
username: fingerprint.username,
|
||||||
|
hostname: fingerprint.hostname,
|
||||||
|
platform: fingerprint.platform,
|
||||||
|
osVersion: fingerprint.osVersion,
|
||||||
|
kernelVersion: fingerprint.kernelVersion,
|
||||||
|
arch: fingerprint.arch,
|
||||||
|
deviceModel: fingerprint.deviceModel,
|
||||||
|
serialNumber: fingerprint.serialNumber,
|
||||||
|
platformFingerprint: fingerprint.platformFingerprint,
|
||||||
|
|
||||||
|
biometricsEnabled: postures.biometricsEnabled,
|
||||||
|
diskEncrypted: postures.diskEncrypted,
|
||||||
|
firewallEnabled: postures.firewallEnabled,
|
||||||
|
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||||
|
tpmAvailable: postures.tpmAvailable,
|
||||||
|
|
||||||
|
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||||
|
|
||||||
|
macosSipEnabled: postures.macosSipEnabled,
|
||||||
|
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||||
|
macosFirewallStealthMode: postures.macosFirewallStealthMode,
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
|
||||||
|
linuxSELinuxEnabled: postures.linuxSELinuxEnabled,
|
||||||
|
|
||||||
|
hash,
|
||||||
|
collectedAt: now
|
||||||
|
});
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(currentFingerprint)
|
||||||
|
.set({
|
||||||
|
lastSeen: now,
|
||||||
|
lastCollectedAt: now,
|
||||||
|
|
||||||
|
username: fingerprint.username,
|
||||||
|
hostname: fingerprint.hostname,
|
||||||
|
platform: fingerprint.platform,
|
||||||
|
osVersion: fingerprint.osVersion,
|
||||||
|
kernelVersion: fingerprint.kernelVersion,
|
||||||
|
arch: fingerprint.arch,
|
||||||
|
deviceModel: fingerprint.deviceModel,
|
||||||
|
serialNumber: fingerprint.serialNumber,
|
||||||
|
platformFingerprint: fingerprint.platformFingerprint,
|
||||||
|
|
||||||
|
biometricsEnabled: postures.biometricsEnabled,
|
||||||
|
diskEncrypted: postures.diskEncrypted,
|
||||||
|
firewallEnabled: postures.firewallEnabled,
|
||||||
|
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||||
|
tpmAvailable: postures.tpmAvailable,
|
||||||
|
|
||||||
|
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||||
|
|
||||||
|
macosSipEnabled: postures.macosSipEnabled,
|
||||||
|
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||||
|
macosFirewallStealthMode: postures.macosFirewallStealthMode,
|
||||||
|
|
||||||
|
linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
|
||||||
|
linuxSELinuxEnabled: postures.linuxSELinuxEnabled
|
||||||
|
})
|
||||||
|
.where(eq(currentFingerprint.fingerprintId, current.fingerprintId));
|
||||||
|
} else {
|
||||||
|
await db
|
||||||
|
.update(currentFingerprint)
|
||||||
|
.set({ lastSeen: now })
|
||||||
|
.where(eq(currentFingerprint.fingerprintId, current.fingerprintId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanUpOldFingerprintSnapshots(retentionDays: number) {
|
||||||
|
const cutoff = calculateCutoffTimestamp(retentionDays);
|
||||||
|
|
||||||
|
await db
|
||||||
|
.delete(fingerprintSnapshots)
|
||||||
|
.where(lt(fingerprintSnapshots.collectedAt, cutoff));
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { olms, clients, fingerprints } from "@server/db";
|
import { olms, clients, currentFingerprint } from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -8,8 +8,8 @@ import response from "@server/lib/response";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
|
||||||
import { getUserDeviceName } from "@server/db/names";
|
import { getUserDeviceName } from "@server/db/names";
|
||||||
|
// import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -66,16 +66,14 @@ export async function getUserOlm(
|
|||||||
.select()
|
.select()
|
||||||
.from(olms)
|
.from(olms)
|
||||||
.where(and(eq(olms.userId, userId), eq(olms.olmId, olmId)))
|
.where(and(eq(olms.userId, userId), eq(olms.olmId, olmId)))
|
||||||
.leftJoin(fingerprints, eq(olms.olmId, fingerprints.olmId))
|
.leftJoin(
|
||||||
|
currentFingerprint,
|
||||||
|
eq(olms.olmId, currentFingerprint.olmId)
|
||||||
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!result || !result.olms) {
|
if (!result || !result.olms) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.NOT_FOUND, "Olm not found"));
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
"Olm not found"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const olm = result.olms;
|
const olm = result.olms;
|
||||||
@@ -98,12 +96,13 @@ export async function getUserOlm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace name with device name
|
// Replace name with device name
|
||||||
const model = result.fingerprints?.deviceModel || null;
|
const model = result.currentFingerprint?.deviceModel || null;
|
||||||
const newName = getUserDeviceName(model, olm.name);
|
const newName = getUserDeviceName(model, olm.name);
|
||||||
|
|
||||||
const responseData = blocked !== undefined
|
const responseData =
|
||||||
? { ...olm, name: newName, blocked }
|
blocked !== undefined
|
||||||
: { ...olm, name: newName };
|
? { ...olm, name: newName, blocked }
|
||||||
|
: { ...olm, name: newName };
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: responseData,
|
data: responseData,
|
||||||
|
|||||||
34
server/routers/olm/handleOlmDisconnectingMessage.ts
Normal file
34
server/routers/olm/handleOlmDisconnectingMessage.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { MessageHandler } from "@server/routers/ws";
|
||||||
|
import { clients, db, Olm } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles disconnecting messages from clients to show disconnected in the ui
|
||||||
|
*/
|
||||||
|
export const handleOlmDisconnecingMessage: MessageHandler = async (context) => {
|
||||||
|
const { message, client: c, sendToClient } = context;
|
||||||
|
const olm = c as Olm;
|
||||||
|
|
||||||
|
if (!olm) {
|
||||||
|
logger.warn("Olm not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!olm.clientId) {
|
||||||
|
logger.warn("Olm has no client ID!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update the client's last ping timestamp
|
||||||
|
await db
|
||||||
|
.update(clients)
|
||||||
|
.set({
|
||||||
|
online: false
|
||||||
|
})
|
||||||
|
.where(eq(clients.clientId, olm.clientId));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error handling disconnecting message", { error });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { disconnectClient, getClientConfigVersion } from "#dynamic/routers/ws";
|
import { disconnectClient, getClientConfigVersion } from "#dynamic/routers/ws";
|
||||||
import { clientPostureSnapshots, db, fingerprints } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
import { MessageHandler } from "@server/routers/ws";
|
||||||
import { clients, olms, Olm } from "@server/db";
|
import { clients, olms, Olm } from "@server/db";
|
||||||
import { eq, lt, isNull, and, or } from "drizzle-orm";
|
import { eq, lt, isNull, and, or } from "drizzle-orm";
|
||||||
@@ -10,6 +10,8 @@ import { sendTerminateClient } from "../client/terminate";
|
|||||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||||
import { sha256 } from "@oslojs/crypto/sha2";
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
import { sendOlmSyncMessage } from "./sync";
|
import { sendOlmSyncMessage } from "./sync";
|
||||||
|
import { OlmErrorCodes } from "./error";
|
||||||
|
import { handleFingerprintInsertion } from "./fingerprintingUtils";
|
||||||
|
|
||||||
// Track if the offline checker interval is running
|
// Track if the offline checker interval is running
|
||||||
let offlineCheckerInterval: NodeJS.Timeout | null = null;
|
let offlineCheckerInterval: NodeJS.Timeout | null = null;
|
||||||
@@ -64,6 +66,7 @@ export const startOlmOfflineChecker = (): void => {
|
|||||||
try {
|
try {
|
||||||
await sendTerminateClient(
|
await sendTerminateClient(
|
||||||
offlineClient.clientId,
|
offlineClient.clientId,
|
||||||
|
OlmErrorCodes.TERMINATED_INACTIVITY,
|
||||||
offlineClient.olmId
|
offlineClient.olmId
|
||||||
); // terminate first
|
); // terminate first
|
||||||
// wait a moment to ensure the message is sent
|
// wait a moment to ensure the message is sent
|
||||||
@@ -171,15 +174,25 @@ export const handleOlmPingMessage: MessageHandler = async (context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the version
|
// get the version
|
||||||
logger.debug(`handleOlmPingMessage: About to get config version for olmId: ${olm.olmId}`);
|
logger.debug(
|
||||||
|
`handleOlmPingMessage: About to get config version for olmId: ${olm.olmId}`
|
||||||
|
);
|
||||||
const configVersion = await getClientConfigVersion(olm.olmId);
|
const configVersion = await getClientConfigVersion(olm.olmId);
|
||||||
logger.debug(`handleOlmPingMessage: Got config version: ${configVersion} (type: ${typeof configVersion})`);
|
logger.debug(
|
||||||
|
`handleOlmPingMessage: Got config version: ${configVersion} (type: ${typeof configVersion})`
|
||||||
|
);
|
||||||
|
|
||||||
if (configVersion == null || configVersion === undefined) {
|
if (configVersion == null || configVersion === undefined) {
|
||||||
logger.debug(`handleOlmPingMessage: could not get config version from server for olmId: ${olm.olmId}`)
|
logger.debug(
|
||||||
|
`handleOlmPingMessage: could not get config version from server for olmId: ${olm.olmId}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.configVersion != null && configVersion != null && configVersion != message.configVersion) {
|
if (
|
||||||
|
message.configVersion != null &&
|
||||||
|
configVersion != null &&
|
||||||
|
configVersion != message.configVersion
|
||||||
|
) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`handleOlmPingMessage: Olm ping with outdated config version: ${message.configVersion} (current: ${configVersion})`
|
`handleOlmPingMessage: Olm ping with outdated config version: ${message.configVersion} (current: ${configVersion})`
|
||||||
);
|
);
|
||||||
@@ -206,73 +219,7 @@ export const handleOlmPingMessage: MessageHandler = async (context) => {
|
|||||||
logger.error("Error handling ping message", { error });
|
logger.error("Error handling ping message", { error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
await handleFingerprintInsertion(olm, fingerprint, postures);
|
||||||
|
|
||||||
if (fingerprint && olm.olmId) {
|
|
||||||
const [existingFingerprint] = await db
|
|
||||||
.select()
|
|
||||||
.from(fingerprints)
|
|
||||||
.where(eq(fingerprints.olmId, olm.olmId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!existingFingerprint) {
|
|
||||||
await db.insert(fingerprints).values({
|
|
||||||
olmId: olm.olmId,
|
|
||||||
firstSeen: now,
|
|
||||||
lastSeen: now,
|
|
||||||
|
|
||||||
username: fingerprint.username,
|
|
||||||
hostname: fingerprint.hostname,
|
|
||||||
platform: fingerprint.platform,
|
|
||||||
osVersion: fingerprint.osVersion,
|
|
||||||
kernelVersion: fingerprint.kernelVersion,
|
|
||||||
arch: fingerprint.arch,
|
|
||||||
deviceModel: fingerprint.deviceModel,
|
|
||||||
serialNumber: fingerprint.serialNumber,
|
|
||||||
platformFingerprint: fingerprint.platformFingerprint
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await db
|
|
||||||
.update(fingerprints)
|
|
||||||
.set({
|
|
||||||
lastSeen: now,
|
|
||||||
|
|
||||||
username: fingerprint.username,
|
|
||||||
hostname: fingerprint.hostname,
|
|
||||||
platform: fingerprint.platform,
|
|
||||||
osVersion: fingerprint.osVersion,
|
|
||||||
kernelVersion: fingerprint.kernelVersion,
|
|
||||||
arch: fingerprint.arch,
|
|
||||||
deviceModel: fingerprint.deviceModel,
|
|
||||||
serialNumber: fingerprint.serialNumber,
|
|
||||||
platformFingerprint: fingerprint.platformFingerprint
|
|
||||||
})
|
|
||||||
.where(eq(fingerprints.olmId, olm.olmId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postures && olm.clientId) {
|
|
||||||
await db.insert(clientPostureSnapshots).values({
|
|
||||||
clientId: olm.clientId,
|
|
||||||
|
|
||||||
biometricsEnabled: postures?.biometricsEnabled,
|
|
||||||
diskEncrypted: postures?.diskEncrypted,
|
|
||||||
firewallEnabled: postures?.firewallEnabled,
|
|
||||||
autoUpdatesEnabled: postures?.autoUpdatesEnabled,
|
|
||||||
tpmAvailable: postures?.tpmAvailable,
|
|
||||||
|
|
||||||
windowsDefenderEnabled: postures?.windowsDefenderEnabled,
|
|
||||||
|
|
||||||
macosSipEnabled: postures?.macosSipEnabled,
|
|
||||||
macosGatekeeperEnabled: postures?.macosGatekeeperEnabled,
|
|
||||||
macosFirewallStealthMode: postures?.macosFirewallStealthMode,
|
|
||||||
|
|
||||||
linuxAppArmorEnabled: postures?.linuxAppArmorEnabled,
|
|
||||||
linuxSELinuxEnabled: postures?.linuxSELinuxEnabled,
|
|
||||||
|
|
||||||
collectedAt: now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: {
|
message: {
|
||||||
|
|||||||
@@ -1,32 +1,21 @@
|
|||||||
import {
|
import { db, orgs } from "@server/db";
|
||||||
Client,
|
|
||||||
clientPostureSnapshots,
|
|
||||||
clientSiteResourcesAssociationsCache,
|
|
||||||
db,
|
|
||||||
fingerprints,
|
|
||||||
orgs,
|
|
||||||
siteResources
|
|
||||||
} from "@server/db";
|
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
import { MessageHandler } from "@server/routers/ws";
|
||||||
import {
|
import {
|
||||||
clients,
|
clients,
|
||||||
clientSitesAssociationsCache,
|
clientSitesAssociationsCache,
|
||||||
exitNodes,
|
|
||||||
Olm,
|
Olm,
|
||||||
olms,
|
olms,
|
||||||
sites
|
sites
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { and, count, eq, inArray, isNull } from "drizzle-orm";
|
import { count, eq } from "drizzle-orm";
|
||||||
import { addPeer, deletePeer } from "../newt/peers";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { generateAliasConfig } from "@server/lib/ip";
|
|
||||||
import { generateRemoteSubnets } from "@server/lib/ip";
|
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { validateSessionToken } from "@server/auth/sessions/app";
|
import { validateSessionToken } from "@server/auth/sessions/app";
|
||||||
import config from "@server/lib/config";
|
|
||||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||||
import { sha256 } from "@oslojs/crypto/sha2";
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
import { buildSiteConfigurationForOlmClient } from "./buildConfiguration";
|
import { buildSiteConfigurationForOlmClient } from "./buildConfiguration";
|
||||||
|
import { OlmErrorCodes, sendOlmError } from "./error";
|
||||||
|
import { handleFingerprintInsertion } from "./fingerprintingUtils";
|
||||||
|
|
||||||
export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
||||||
logger.info("Handling register olm message!");
|
logger.info("Handling register olm message!");
|
||||||
@@ -53,79 +42,11 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
|
|
||||||
if (!olm.clientId) {
|
if (!olm.clientId) {
|
||||||
logger.warn("Olm client ID not found");
|
logger.warn("Olm client ID not found");
|
||||||
|
sendOlmError(OlmErrorCodes.CLIENT_ID_NOT_FOUND, olm.olmId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [client] = await db
|
await handleFingerprintInsertion(olm, fingerprint, postures);
|
||||||
.select()
|
|
||||||
.from(clients)
|
|
||||||
.where(eq(clients.clientId, olm.clientId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
logger.warn("Client ID not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.blocked) {
|
|
||||||
logger.debug(`Client ${client.clientId} is blocked. Ignoring register.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [org] = await db
|
|
||||||
.select()
|
|
||||||
.from(orgs)
|
|
||||||
.where(eq(orgs.orgId, client.orgId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!org) {
|
|
||||||
logger.warn("Org not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orgId) {
|
|
||||||
if (!olm.userId) {
|
|
||||||
logger.warn("Olm has no user ID");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { session: userSession, user } =
|
|
||||||
await validateSessionToken(userToken);
|
|
||||||
if (!userSession || !user) {
|
|
||||||
logger.warn("Invalid user session for olm register");
|
|
||||||
return; // by returning here we just ignore the ping and the setInterval will force it to disconnect
|
|
||||||
}
|
|
||||||
if (user.userId !== olm.userId) {
|
|
||||||
logger.warn("User ID mismatch for olm register");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionId = encodeHexLowerCase(
|
|
||||||
sha256(new TextEncoder().encode(userToken))
|
|
||||||
);
|
|
||||||
|
|
||||||
const policyCheck = await checkOrgAccessPolicy({
|
|
||||||
orgId: orgId,
|
|
||||||
userId: olm.userId,
|
|
||||||
sessionId // this is the user token passed in the message
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!policyCheck.allowed) {
|
|
||||||
logger.warn(
|
|
||||||
`Olm user ${olm.userId} does not pass access policies for org ${orgId}: ${policyCheck.error}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Olm client ID: ${client.clientId}, Public Key: ${publicKey}, Relay: ${relay}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!publicKey) {
|
|
||||||
logger.warn("Public key not provided");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(olmVersion && olm.version !== olmVersion) ||
|
(olmVersion && olm.version !== olmVersion) ||
|
||||||
@@ -142,6 +63,133 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
.where(eq(olms.olmId, olm.olmId));
|
.where(eq(olms.olmId, olm.olmId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [client] = await db
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(eq(clients.clientId, olm.clientId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
logger.warn("Client ID not found");
|
||||||
|
sendOlmError(OlmErrorCodes.CLIENT_NOT_FOUND, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.blocked) {
|
||||||
|
logger.debug(
|
||||||
|
`Client ${client.clientId} is blocked. Ignoring register.`
|
||||||
|
);
|
||||||
|
sendOlmError(OlmErrorCodes.CLIENT_BLOCKED, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.approvalState == "pending") {
|
||||||
|
logger.debug(
|
||||||
|
`Client ${client.clientId} approval is pending. Ignoring register.`
|
||||||
|
);
|
||||||
|
sendOlmError(OlmErrorCodes.CLIENT_PENDING, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [org] = await db
|
||||||
|
.select()
|
||||||
|
.from(orgs)
|
||||||
|
.where(eq(orgs.orgId, client.orgId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
logger.warn("Org not found");
|
||||||
|
sendOlmError(OlmErrorCodes.ORG_NOT_FOUND, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orgId) {
|
||||||
|
if (!olm.userId) {
|
||||||
|
logger.warn("Olm has no user ID");
|
||||||
|
sendOlmError(OlmErrorCodes.USER_ID_NOT_FOUND, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { session: userSession, user } =
|
||||||
|
await validateSessionToken(userToken);
|
||||||
|
if (!userSession || !user) {
|
||||||
|
logger.warn("Invalid user session for olm register");
|
||||||
|
sendOlmError(OlmErrorCodes.INVALID_USER_SESSION, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.userId !== olm.userId) {
|
||||||
|
logger.warn("User ID mismatch for olm register");
|
||||||
|
sendOlmError(OlmErrorCodes.USER_ID_MISMATCH, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = encodeHexLowerCase(
|
||||||
|
sha256(new TextEncoder().encode(userToken))
|
||||||
|
);
|
||||||
|
|
||||||
|
const policyCheck = await checkOrgAccessPolicy({
|
||||||
|
orgId: orgId,
|
||||||
|
userId: olm.userId,
|
||||||
|
sessionId // this is the user token passed in the message
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("Policy check result:", policyCheck);
|
||||||
|
|
||||||
|
if (policyCheck?.error) {
|
||||||
|
logger.error(
|
||||||
|
`Error checking access policies for olm user ${olm.userId} in org ${orgId}: ${policyCheck?.error}`
|
||||||
|
);
|
||||||
|
sendOlmError(OlmErrorCodes.ORG_ACCESS_POLICY_DENIED, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!policyCheck.policies?.passwordAge?.compliant === false) {
|
||||||
|
logger.warn(
|
||||||
|
`Olm user ${olm.userId} has non-compliant password age for org ${orgId}`
|
||||||
|
);
|
||||||
|
sendOlmError(
|
||||||
|
OlmErrorCodes.ORG_ACCESS_POLICY_PASSWORD_EXPIRED,
|
||||||
|
olm.olmId
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else if (
|
||||||
|
!policyCheck.policies?.maxSessionLength?.compliant === false
|
||||||
|
) {
|
||||||
|
logger.warn(
|
||||||
|
`Olm user ${olm.userId} has non-compliant session length for org ${orgId}`
|
||||||
|
);
|
||||||
|
sendOlmError(
|
||||||
|
OlmErrorCodes.ORG_ACCESS_POLICY_SESSION_EXPIRED,
|
||||||
|
olm.olmId
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else if (policyCheck.policies?.requiredTwoFactor === false) {
|
||||||
|
logger.warn(
|
||||||
|
`Olm user ${olm.userId} does not have 2FA enabled for org ${orgId}`
|
||||||
|
);
|
||||||
|
sendOlmError(
|
||||||
|
OlmErrorCodes.ORG_ACCESS_POLICY_2FA_REQUIRED,
|
||||||
|
olm.olmId
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else if (!policyCheck.allowed) {
|
||||||
|
logger.warn(
|
||||||
|
`Olm user ${olm.userId} does not pass access policies for org ${orgId}: ${policyCheck.error}`
|
||||||
|
);
|
||||||
|
sendOlmError(OlmErrorCodes.ORG_ACCESS_POLICY_DENIED, olm.olmId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Olm client ID: ${client.clientId}, Public Key: ${publicKey}, Relay: ${relay}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!publicKey) {
|
||||||
|
logger.warn("Public key not provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (client.pubKey !== publicKey || client.archived) {
|
if (client.pubKey !== publicKey || client.archived) {
|
||||||
logger.info(
|
logger.info(
|
||||||
"Public key mismatch. Updating public key and clearing session info..."
|
"Public key mismatch. Updating public key and clearing session info..."
|
||||||
@@ -151,7 +199,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
.update(clients)
|
.update(clients)
|
||||||
.set({
|
.set({
|
||||||
pubKey: publicKey,
|
pubKey: publicKey,
|
||||||
archived: false,
|
archived: false
|
||||||
})
|
})
|
||||||
.where(eq(clients.clientId, client.clientId));
|
.where(eq(clients.clientId, client.clientId));
|
||||||
|
|
||||||
@@ -198,72 +246,6 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
relay
|
relay
|
||||||
);
|
);
|
||||||
|
|
||||||
if (fingerprint) {
|
|
||||||
const [existingFingerprint] = await db
|
|
||||||
.select()
|
|
||||||
.from(fingerprints)
|
|
||||||
.where(eq(fingerprints.olmId, olm.olmId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!existingFingerprint) {
|
|
||||||
await db.insert(fingerprints).values({
|
|
||||||
olmId: olm.olmId,
|
|
||||||
firstSeen: now,
|
|
||||||
lastSeen: now,
|
|
||||||
|
|
||||||
username: fingerprint.username,
|
|
||||||
hostname: fingerprint.hostname,
|
|
||||||
platform: fingerprint.platform,
|
|
||||||
osVersion: fingerprint.osVersion,
|
|
||||||
kernelVersion: fingerprint.kernelVersion,
|
|
||||||
arch: fingerprint.arch,
|
|
||||||
deviceModel: fingerprint.deviceModel,
|
|
||||||
serialNumber: fingerprint.serialNumber,
|
|
||||||
platformFingerprint: fingerprint.platformFingerprint
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await db
|
|
||||||
.update(fingerprints)
|
|
||||||
.set({
|
|
||||||
lastSeen: now,
|
|
||||||
|
|
||||||
username: fingerprint.username,
|
|
||||||
hostname: fingerprint.hostname,
|
|
||||||
platform: fingerprint.platform,
|
|
||||||
osVersion: fingerprint.osVersion,
|
|
||||||
kernelVersion: fingerprint.kernelVersion,
|
|
||||||
arch: fingerprint.arch,
|
|
||||||
deviceModel: fingerprint.deviceModel,
|
|
||||||
serialNumber: fingerprint.serialNumber,
|
|
||||||
platformFingerprint: fingerprint.platformFingerprint
|
|
||||||
})
|
|
||||||
.where(eq(fingerprints.olmId, olm.olmId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postures && olm.clientId) {
|
|
||||||
await db.insert(clientPostureSnapshots).values({
|
|
||||||
clientId: olm.clientId,
|
|
||||||
|
|
||||||
biometricsEnabled: postures?.biometricsEnabled,
|
|
||||||
diskEncrypted: postures?.diskEncrypted,
|
|
||||||
firewallEnabled: postures?.firewallEnabled,
|
|
||||||
autoUpdatesEnabled: postures?.autoUpdatesEnabled,
|
|
||||||
tpmAvailable: postures?.tpmAvailable,
|
|
||||||
|
|
||||||
windowsDefenderEnabled: postures?.windowsDefenderEnabled,
|
|
||||||
|
|
||||||
macosSipEnabled: postures?.macosSipEnabled,
|
|
||||||
macosGatekeeperEnabled: postures?.macosGatekeeperEnabled,
|
|
||||||
macosFirewallStealthMode: postures?.macosFirewallStealthMode,
|
|
||||||
|
|
||||||
linuxAppArmorEnabled: postures?.linuxAppArmorEnabled,
|
|
||||||
linuxSELinuxEnabled: postures?.linuxSELinuxEnabled,
|
|
||||||
|
|
||||||
collectedAt: now
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// REMOVED THIS SO IT CREATES THE INTERFACE AND JUST WAITS FOR THE SITES
|
// REMOVED THIS SO IT CREATES THE INTERFACE AND JUST WAITS FOR THE SITES
|
||||||
// if (siteConfigurations.length === 0) {
|
// if (siteConfigurations.length === 0) {
|
||||||
// logger.warn("No valid site configurations found");
|
// logger.warn("No valid site configurations found");
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export * from "./getUserOlm";
|
|||||||
export * from "./handleOlmServerPeerAddMessage";
|
export * from "./handleOlmServerPeerAddMessage";
|
||||||
export * from "./handleOlmUnRelayMessage";
|
export * from "./handleOlmUnRelayMessage";
|
||||||
export * from "./recoverOlmWithFingerprint";
|
export * from "./recoverOlmWithFingerprint";
|
||||||
|
export * from "./handleOlmDisconnectingMessage";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { db, fingerprints } from "@server/db";
|
import { db, currentFingerprint } from "@server/db";
|
||||||
import { olms } from "@server/db";
|
import { olms } from "@server/db";
|
||||||
import { eq, count, desc } from "drizzle-orm";
|
import { eq, count, desc } from "drizzle-orm";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -104,13 +104,16 @@ export async function listUserOlms(
|
|||||||
.select()
|
.select()
|
||||||
.from(olms)
|
.from(olms)
|
||||||
.where(eq(olms.userId, userId))
|
.where(eq(olms.userId, userId))
|
||||||
.leftJoin(fingerprints, eq(olms.olmId, fingerprints.olmId))
|
.leftJoin(
|
||||||
|
currentFingerprint,
|
||||||
|
eq(olms.olmId, currentFingerprint.olmId)
|
||||||
|
)
|
||||||
.orderBy(desc(olms.dateCreated))
|
.orderBy(desc(olms.dateCreated))
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
|
||||||
const userOlms = list.map((item) => {
|
const userOlms = list.map((item) => {
|
||||||
const model = item.fingerprints?.deviceModel || null;
|
const model = item.currentFingerprint?.deviceModel || null;
|
||||||
const newName = getUserDeviceName(model, item.olms.name);
|
const newName = getUserDeviceName(model, item.olms.name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { db, fingerprints, olms } from "@server/db";
|
import { db, currentFingerprint, olms } from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
@@ -55,18 +55,23 @@ export async function recoverOlmWithFingerprint(
|
|||||||
const result = await db
|
const result = await db
|
||||||
.select({
|
.select({
|
||||||
olm: olms,
|
olm: olms,
|
||||||
fingerprint: fingerprints
|
fingerprint: currentFingerprint
|
||||||
})
|
})
|
||||||
.from(olms)
|
.from(olms)
|
||||||
.innerJoin(fingerprints, eq(fingerprints.olmId, olms.olmId))
|
.innerJoin(
|
||||||
|
currentFingerprint,
|
||||||
|
eq(currentFingerprint.olmId, olms.olmId)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(olms.userId, userId),
|
eq(olms.userId, userId),
|
||||||
eq(olms.archived, false),
|
eq(
|
||||||
eq(fingerprints.platformFingerprint, platformFingerprint)
|
currentFingerprint.platformFingerprint,
|
||||||
|
platformFingerprint
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.orderBy(fingerprints.lastSeen);
|
.orderBy(currentFingerprint.lastSeen);
|
||||||
|
|
||||||
if (!result || result.length == 0) {
|
if (!result || result.length == 0) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export async function sendOlmSyncMessage(olm: Olm, client: Client) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("sendOlmSyncMessage: sending sync message")
|
logger.debug("sendOlmSyncMessage: sending sync message");
|
||||||
|
|
||||||
await sendToClient(olm.olmId, {
|
await sendToClient(olm.olmId, {
|
||||||
type: "olm/sync",
|
type: "olm/sync",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import { deletePeer } from "../gerbil/peers";
|
import { deletePeer } from "../gerbil/peers";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { OlmErrorCodes } from "../olm/error";
|
||||||
|
import { sendTerminateClient } from "../client/terminate";
|
||||||
|
|
||||||
const deleteOrgSchema = z.strictObject({
|
const deleteOrgSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -206,10 +208,11 @@ export async function deleteOrg(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const olmId of olmsToTerminate) {
|
for (const olmId of olmsToTerminate) {
|
||||||
sendToClient(olmId, {
|
sendTerminateClient(
|
||||||
type: "olm/terminate",
|
0, // clientId not needed since we're passing olmId
|
||||||
data: {}
|
OlmErrorCodes.TERMINATED_REKEYED,
|
||||||
}).catch((error) => {
|
olmId
|
||||||
|
).catch((error) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to send termination message to olm:",
|
"Failed to send termination message to olm:",
|
||||||
error
|
error
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, orgs, type Role } from "@server/db";
|
import { db, type Role } from "@server/db";
|
||||||
import { roles } from "@server/db";
|
import { roles } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
@@ -10,9 +10,9 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
const updateRoleParamsSchema = z.strictObject({
|
const updateRoleParamsSchema = z.strictObject({
|
||||||
orgId: z.string(),
|
|
||||||
roleId: z.string().transform(Number).pipe(z.int().positive())
|
roleId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,6 +30,24 @@ export type UpdateRoleBody = z.infer<typeof updateRoleBodySchema>;
|
|||||||
|
|
||||||
export type UpdateRoleResponse = Role;
|
export type UpdateRoleResponse = Role;
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "post",
|
||||||
|
path: "/role/{roleId}",
|
||||||
|
description: "Update a role.",
|
||||||
|
tags: [OpenAPITags.Role],
|
||||||
|
request: {
|
||||||
|
params: updateRoleParamsSchema,
|
||||||
|
body: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: updateRoleBodySchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
export async function updateRole(
|
export async function updateRole(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
@@ -56,14 +74,13 @@ export async function updateRole(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { roleId, orgId } = parsedParams.data;
|
const { roleId } = parsedParams.data;
|
||||||
const updateData = parsedBody.data;
|
const updateData = parsedBody.data;
|
||||||
|
|
||||||
const role = await db
|
const role = await db
|
||||||
.select()
|
.select()
|
||||||
.from(roles)
|
.from(roles)
|
||||||
.where(eq(roles.roleId, roleId))
|
.where(eq(roles.roleId, roleId))
|
||||||
.innerJoin(orgs, eq(roles.orgId, orgs.orgId))
|
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (role.length === 0) {
|
if (role.length === 0) {
|
||||||
@@ -75,7 +92,7 @@ export async function updateRole(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role[0].roles.isAdmin) {
|
if (role[0].isAdmin) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
@@ -84,6 +101,16 @@ export async function updateRole(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const orgId = role[0].orgId;
|
||||||
|
if (!orgId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Role does not have an organization ID"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
if (build === "oss" || !isLicensed) {
|
if (build === "oss" || !isLicensed) {
|
||||||
updateData.requireDeviceApproval = undefined;
|
updateData.requireDeviceApproval = undefined;
|
||||||
|
|||||||
60
server/routers/serverInfo/getServerInfo.ts
Normal file
60
server/routers/serverInfo/getServerInfo.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { response as sendResponse } from "@server/lib/response";
|
||||||
|
import config from "@server/lib/config";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import { APP_VERSION } from "@server/lib/consts";
|
||||||
|
import license from "#dynamic/license/license";
|
||||||
|
|
||||||
|
export type GetServerInfoResponse = {
|
||||||
|
version: string;
|
||||||
|
supporterStatusValid: boolean;
|
||||||
|
build: "oss" | "enterprise" | "saas";
|
||||||
|
enterpriseLicenseValid: boolean;
|
||||||
|
enterpriseLicenseType: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getServerInfo(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const supporterData = config.getSupporterData();
|
||||||
|
const supporterStatusValid = supporterData?.valid || false;
|
||||||
|
|
||||||
|
let enterpriseLicenseValid = false;
|
||||||
|
let enterpriseLicenseType: string | null = null;
|
||||||
|
|
||||||
|
if (build === "enterprise") {
|
||||||
|
try {
|
||||||
|
const licenseStatus = await license.check();
|
||||||
|
enterpriseLicenseValid = licenseStatus.isLicenseValid;
|
||||||
|
enterpriseLicenseType = licenseStatus.tier || null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn("Failed to check enterprise license status:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendResponse<GetServerInfoResponse>(res, {
|
||||||
|
data: {
|
||||||
|
version: APP_VERSION,
|
||||||
|
supporterStatusValid,
|
||||||
|
build,
|
||||||
|
enterpriseLicenseValid,
|
||||||
|
enterpriseLicenseType
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Server info retrieved",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
server/routers/serverInfo/index.ts
Normal file
1
server/routers/serverInfo/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./getServerInfo";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db, siteResources } from "@server/db";
|
||||||
import { newts, newtSessions, sites } from "@server/db";
|
import { newts, newtSessions, sites } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
@@ -11,6 +11,7 @@ import { deletePeer } from "../gerbil/peers";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const deleteSiteSchema = z.strictObject({
|
const deleteSiteSchema = z.strictObject({
|
||||||
siteId: z.string().transform(Number).pipe(z.int().positive())
|
siteId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -63,23 +64,37 @@ export async function deleteSite(
|
|||||||
let deletedNewtId: string | null = null;
|
let deletedNewtId: string | null = null;
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
if (site.pubKey) {
|
if (site.type == "wireguard") {
|
||||||
if (site.type == "wireguard") {
|
if (site.pubKey) {
|
||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
} else if (site.type == "newt") {
|
}
|
||||||
// get the newt on the site by querying the newt table for siteId
|
} else if (site.type == "newt") {
|
||||||
const [deletedNewt] = await trx
|
// delete all of the site resources on this site
|
||||||
.delete(newts)
|
const siteResourcesOnSite = trx
|
||||||
.where(eq(newts.siteId, siteId))
|
.delete(siteResources)
|
||||||
.returning();
|
.where(eq(siteResources.siteId, siteId))
|
||||||
if (deletedNewt) {
|
.returning();
|
||||||
deletedNewtId = deletedNewt.newtId;
|
|
||||||
|
|
||||||
// delete all of the sessions for the newt
|
// loop through them
|
||||||
await trx
|
for (const removedSiteResource of await siteResourcesOnSite) {
|
||||||
.delete(newtSessions)
|
await rebuildClientAssociationsFromSiteResource(
|
||||||
.where(eq(newtSessions.newtId, deletedNewt.newtId));
|
removedSiteResource,
|
||||||
}
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the newt on the site by querying the newt table for siteId
|
||||||
|
const [deletedNewt] = await trx
|
||||||
|
.delete(newts)
|
||||||
|
.where(eq(newts.siteId, siteId))
|
||||||
|
.returning();
|
||||||
|
if (deletedNewt) {
|
||||||
|
deletedNewtId = deletedNewt.newtId;
|
||||||
|
|
||||||
|
// delete all of the sessions for the newt
|
||||||
|
await trx
|
||||||
|
.delete(newtSessions)
|
||||||
|
.where(eq(newtSessions.newtId, deletedNewt.newtId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export async function listAllSiteResourcesByOrg(
|
|||||||
destination: siteResources.destination,
|
destination: siteResources.destination,
|
||||||
enabled: siteResources.enabled,
|
enabled: siteResources.enabled,
|
||||||
alias: siteResources.alias,
|
alias: siteResources.alias,
|
||||||
|
aliasAddress: siteResources.aliasAddress,
|
||||||
tcpPortRangeString: siteResources.tcpPortRangeString,
|
tcpPortRangeString: siteResources.tcpPortRangeString,
|
||||||
udpPortRangeString: siteResources.udpPortRangeString,
|
udpPortRangeString: siteResources.udpPortRangeString,
|
||||||
disableIcmp: siteResources.disableIcmp,
|
disableIcmp: siteResources.disableIcmp,
|
||||||
|
|||||||
@@ -23,15 +23,10 @@ const paramsSchema = z.strictObject({
|
|||||||
|
|
||||||
const bodySchema = z.strictObject({
|
const bodySchema = z.strictObject({
|
||||||
email: z
|
email: z
|
||||||
|
.string()
|
||||||
.email()
|
.email()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.optional()
|
.optional(),
|
||||||
.refine((data) => {
|
|
||||||
if (data) {
|
|
||||||
return z.email().safeParse(data).success;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
username: z.string().nonempty().toLowerCase(),
|
username: z.string().nonempty().toLowerCase(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
type: z.enum(["internal", "oidc"]).optional(),
|
type: z.enum(["internal", "oidc"]).optional(),
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
handleOlmPingMessage,
|
handleOlmPingMessage,
|
||||||
startOlmOfflineChecker,
|
startOlmOfflineChecker,
|
||||||
handleOlmServerPeerAddMessage,
|
handleOlmServerPeerAddMessage,
|
||||||
handleOlmUnRelayMessage
|
handleOlmUnRelayMessage,
|
||||||
|
handleOlmDisconnecingMessage
|
||||||
} from "../olm";
|
} from "../olm";
|
||||||
import { handleHealthcheckStatusMessage } from "../target";
|
import { handleHealthcheckStatusMessage } from "../target";
|
||||||
import { MessageHandler } from "./types";
|
import { MessageHandler } from "./types";
|
||||||
@@ -25,6 +26,7 @@ export const messageHandlers: Record<string, MessageHandler> = {
|
|||||||
"olm/wg/relay": handleOlmRelayMessage,
|
"olm/wg/relay": handleOlmRelayMessage,
|
||||||
"olm/wg/unrelay": handleOlmUnRelayMessage,
|
"olm/wg/unrelay": handleOlmUnRelayMessage,
|
||||||
"olm/ping": handleOlmPingMessage,
|
"olm/ping": handleOlmPingMessage,
|
||||||
|
"olm/disconnecting": handleOlmDisconnecingMessage,
|
||||||
"newt/ping": handleNewtPingMessage,
|
"newt/ping": handleNewtPingMessage,
|
||||||
"newt/wg/register": handleNewtRegisterMessage,
|
"newt/wg/register": handleNewtRegisterMessage,
|
||||||
"newt/wg/get-config": handleGetConfigMessage,
|
"newt/wg/get-config": handleGetConfigMessage,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import m8 from "./scriptsPg/1.11.1";
|
|||||||
import m9 from "./scriptsPg/1.12.0";
|
import m9 from "./scriptsPg/1.12.0";
|
||||||
import m10 from "./scriptsPg/1.13.0";
|
import m10 from "./scriptsPg/1.13.0";
|
||||||
import m11 from "./scriptsPg/1.14.0";
|
import m11 from "./scriptsPg/1.14.0";
|
||||||
|
import m12 from "./scriptsPg/1.15.0";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
@@ -32,7 +33,8 @@ const migrations = [
|
|||||||
{ version: "1.11.1", run: m8 },
|
{ version: "1.11.1", run: m8 },
|
||||||
{ version: "1.12.0", run: m9 },
|
{ version: "1.12.0", run: m9 },
|
||||||
{ version: "1.13.0", run: m10 },
|
{ version: "1.13.0", run: m10 },
|
||||||
{ version: "1.14.0", run: m11 }
|
{ version: "1.14.0", run: m11 },
|
||||||
|
{ version: "1.15.0", run: m12 }
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as {
|
] as {
|
||||||
version: string;
|
version: string;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import m29 from "./scriptsSqlite/1.11.1";
|
|||||||
import m30 from "./scriptsSqlite/1.12.0";
|
import m30 from "./scriptsSqlite/1.12.0";
|
||||||
import m31 from "./scriptsSqlite/1.13.0";
|
import m31 from "./scriptsSqlite/1.13.0";
|
||||||
import m32 from "./scriptsSqlite/1.14.0";
|
import m32 from "./scriptsSqlite/1.14.0";
|
||||||
|
import m33 from "./scriptsSqlite/1.15.0";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
@@ -66,7 +67,8 @@ const migrations = [
|
|||||||
{ version: "1.11.1", run: m29 },
|
{ version: "1.11.1", run: m29 },
|
||||||
{ version: "1.12.0", run: m30 },
|
{ version: "1.12.0", run: m30 },
|
||||||
{ version: "1.13.0", run: m31 },
|
{ version: "1.13.0", run: m31 },
|
||||||
{ version: "1.14.0", run: m32 }
|
{ version: "1.14.0", run: m32 },
|
||||||
|
{ version: "1.15.0", run: m33 }
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|||||||
136
server/setup/scriptsPg/1.15.0.ts
Normal file
136
server/setup/scriptsPg/1.15.0.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { db } from "@server/db/pg/driver";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { __DIRNAME } from "@server/lib/consts";
|
||||||
|
|
||||||
|
const version = "1.15.0";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.execute(sql`BEGIN`);
|
||||||
|
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TABLE "approvals" (
|
||||||
|
"approvalId" serial PRIMARY KEY NOT NULL,
|
||||||
|
"timestamp" integer NOT NULL,
|
||||||
|
"orgId" varchar NOT NULL,
|
||||||
|
"clientId" integer,
|
||||||
|
"userId" varchar NOT NULL,
|
||||||
|
"decision" varchar DEFAULT 'pending' NOT NULL,
|
||||||
|
"type" varchar NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TABLE "clientPostureSnapshots" (
|
||||||
|
"snapshotId" serial PRIMARY KEY NOT NULL,
|
||||||
|
"clientId" integer,
|
||||||
|
"collectedAt" integer NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TABLE "currentFingerprint" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"olmId" text NOT NULL,
|
||||||
|
"firstSeen" integer NOT NULL,
|
||||||
|
"lastSeen" integer NOT NULL,
|
||||||
|
"lastCollectedAt" integer NOT NULL,
|
||||||
|
"username" text,
|
||||||
|
"hostname" text,
|
||||||
|
"platform" text,
|
||||||
|
"osVersion" text,
|
||||||
|
"kernelVersion" text,
|
||||||
|
"arch" text,
|
||||||
|
"deviceModel" text,
|
||||||
|
"serialNumber" text,
|
||||||
|
"platformFingerprint" varchar,
|
||||||
|
"biometricsEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"diskEncrypted" boolean DEFAULT false NOT NULL,
|
||||||
|
"firewallEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"autoUpdatesEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"tpmAvailable" boolean DEFAULT false NOT NULL,
|
||||||
|
"windowsDefenderEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"macosSipEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"macosGatekeeperEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"macosFirewallStealthMode" boolean DEFAULT false NOT NULL,
|
||||||
|
"linuxAppArmorEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"linuxSELinuxEnabled" boolean DEFAULT false NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TABLE "fingerprintSnapshots" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"fingerprintId" integer,
|
||||||
|
"username" text,
|
||||||
|
"hostname" text,
|
||||||
|
"platform" text,
|
||||||
|
"osVersion" text,
|
||||||
|
"kernelVersion" text,
|
||||||
|
"arch" text,
|
||||||
|
"deviceModel" text,
|
||||||
|
"serialNumber" text,
|
||||||
|
"platformFingerprint" varchar,
|
||||||
|
"biometricsEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"diskEncrypted" boolean DEFAULT false NOT NULL,
|
||||||
|
"firewallEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"autoUpdatesEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"tpmAvailable" boolean DEFAULT false NOT NULL,
|
||||||
|
"windowsDefenderEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"macosSipEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"macosGatekeeperEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"macosFirewallStealthMode" boolean DEFAULT false NOT NULL,
|
||||||
|
"linuxAppArmorEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"linuxSELinuxEnabled" boolean DEFAULT false NOT NULL,
|
||||||
|
"hash" text NOT NULL,
|
||||||
|
"collectedAt" integer NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "loginPageBranding" ALTER COLUMN "logoUrl" DROP NOT NULL;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "clients" ADD COLUMN "archived" boolean DEFAULT false NOT NULL;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "clients" ADD COLUMN "blocked" boolean DEFAULT false NOT NULL;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "clients" ADD COLUMN "approvalState" varchar;`
|
||||||
|
);
|
||||||
|
await db.execute(sql`ALTER TABLE "idp" ADD COLUMN "tags" text;`);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "olms" ADD COLUMN "archived" boolean DEFAULT false NOT NULL;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "roles" ADD COLUMN "requireDeviceApproval" boolean DEFAULT false;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "approvals" ADD CONSTRAINT "approvals_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "approvals" ADD CONSTRAINT "approvals_clientId_clients_clientId_fk" FOREIGN KEY ("clientId") REFERENCES "public"."clients"("clientId") ON DELETE cascade ON UPDATE no action;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "approvals" ADD CONSTRAINT "approvals_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "clientPostureSnapshots" ADD CONSTRAINT "clientPostureSnapshots_clientId_clients_clientId_fk" FOREIGN KEY ("clientId") REFERENCES "public"."clients"("clientId") ON DELETE cascade ON UPDATE no action;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "currentFingerprint" ADD CONSTRAINT "currentFingerprint_olmId_olms_id_fk" FOREIGN KEY ("olmId") REFERENCES "public"."olms"("id") ON DELETE cascade ON UPDATE no action;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "fingerprintSnapshots" ADD CONSTRAINT "fingerprintSnapshots_fingerprintId_currentFingerprint_id_fk" FOREIGN KEY ("fingerprintId") REFERENCES "public"."currentFingerprint"("id") ON DELETE set null ON UPDATE no action;`
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.execute(sql`COMMIT`);
|
||||||
|
console.log("Migrated database");
|
||||||
|
} catch (e) {
|
||||||
|
await db.execute(sql`ROLLBACK`);
|
||||||
|
console.log("Unable to migrate database");
|
||||||
|
console.log(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${version} migration complete`);
|
||||||
|
}
|
||||||
@@ -305,7 +305,7 @@ export default async function migration() {
|
|||||||
const subnets = site.remoteSubnets.split(",");
|
const subnets = site.remoteSubnets.split(",");
|
||||||
for (const subnet of subnets) {
|
for (const subnet of subnets) {
|
||||||
// Generate a unique niceId for each new site resource
|
// Generate a unique niceId for each new site resource
|
||||||
let niceId = generateName();
|
const niceId = generateName();
|
||||||
insertCidrResource.run(
|
insertCidrResource.run(
|
||||||
site.siteId,
|
site.siteId,
|
||||||
subnet.trim(),
|
subnet.trim(),
|
||||||
|
|||||||
155
server/setup/scriptsSqlite/1.15.0.ts
Normal file
155
server/setup/scriptsSqlite/1.15.0.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import { __DIRNAME, APP_PATH } from "@server/lib/consts";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const version = "1.15.0";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
|
const db = new Database(location);
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.pragma("foreign_keys = OFF");
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
CREATE TABLE 'approvals' (
|
||||||
|
'approvalId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
'timestamp' integer NOT NULL,
|
||||||
|
'orgId' text NOT NULL,
|
||||||
|
'clientId' integer,
|
||||||
|
'userId' text,
|
||||||
|
'decision' text DEFAULT 'pending' NOT NULL,
|
||||||
|
'type' text NOT NULL,
|
||||||
|
FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY ('clientId') REFERENCES 'clients'('clientId') ON UPDATE no action ON DELETE cascade,
|
||||||
|
FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
CREATE TABLE 'currentFingerprint' (
|
||||||
|
'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
'olmId' text NOT NULL,
|
||||||
|
'firstSeen' integer NOT NULL,
|
||||||
|
'lastSeen' integer NOT NULL,
|
||||||
|
'lastCollectedAt' integer NOT NULL,
|
||||||
|
'username' text,
|
||||||
|
'hostname' text,
|
||||||
|
'platform' text,
|
||||||
|
'osVersion' text,
|
||||||
|
'kernelVersion' text,
|
||||||
|
'arch' text,
|
||||||
|
'deviceModel' text,
|
||||||
|
'serialNumber' text,
|
||||||
|
'platformFingerprint' text,
|
||||||
|
'biometricsEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'diskEncrypted' integer DEFAULT false NOT NULL,
|
||||||
|
'firewallEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'autoUpdatesEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'tpmAvailable' integer DEFAULT false NOT NULL,
|
||||||
|
'windowsDefenderEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'macosSipEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'macosGatekeeperEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'macosFirewallStealthMode' integer DEFAULT false NOT NULL,
|
||||||
|
'linuxAppArmorEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'linuxSELinuxEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
FOREIGN KEY ('olmId') REFERENCES 'olms'('id') ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
CREATE TABLE 'fingerprintSnapshots' (
|
||||||
|
'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
'fingerprintId' integer,
|
||||||
|
'username' text,
|
||||||
|
'hostname' text,
|
||||||
|
'platform' text,
|
||||||
|
'osVersion' text,
|
||||||
|
'kernelVersion' text,
|
||||||
|
'arch' text,
|
||||||
|
'deviceModel' text,
|
||||||
|
'serialNumber' text,
|
||||||
|
'platformFingerprint' text,
|
||||||
|
'biometricsEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'diskEncrypted' integer DEFAULT false NOT NULL,
|
||||||
|
'firewallEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'autoUpdatesEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'tpmAvailable' integer DEFAULT false NOT NULL,
|
||||||
|
'windowsDefenderEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'macosSipEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'macosGatekeeperEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'macosFirewallStealthMode' integer DEFAULT false NOT NULL,
|
||||||
|
'linuxAppArmorEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'linuxSELinuxEnabled' integer DEFAULT false NOT NULL,
|
||||||
|
'hash' text NOT NULL,
|
||||||
|
'collectedAt' integer NOT NULL,
|
||||||
|
FOREIGN KEY ('fingerprintId') REFERENCES 'currentFingerprint'('id') ON UPDATE no action ON DELETE set null
|
||||||
|
);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
CREATE TABLE '__new_loginPageBranding' (
|
||||||
|
'loginPageBrandingId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
'logoUrl' text,
|
||||||
|
'logoWidth' integer NOT NULL,
|
||||||
|
'logoHeight' integer NOT NULL,
|
||||||
|
'primaryColor' text,
|
||||||
|
'resourceTitle' text NOT NULL,
|
||||||
|
'resourceSubtitle' text,
|
||||||
|
'orgTitle' text,
|
||||||
|
'orgSubtitle' text
|
||||||
|
);
|
||||||
|
`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`INSERT INTO '__new_loginPageBranding'("loginPageBrandingId", "logoUrl", "logoWidth", "logoHeight", "primaryColor", "resourceTitle", "resourceSubtitle", "orgTitle", "orgSubtitle") SELECT "loginPageBrandingId", "logoUrl", "logoWidth", "logoHeight", "primaryColor", "resourceTitle", "resourceSubtitle", "orgTitle", "orgSubtitle" FROM 'loginPageBranding';`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(`DROP TABLE 'loginPageBranding';`).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`ALTER TABLE '__new_loginPageBranding' RENAME TO 'loginPageBranding';`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`ALTER TABLE 'clients' ADD 'archived' integer DEFAULT false NOT NULL;`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`ALTER TABLE 'clients' ADD 'blocked' integer DEFAULT false NOT NULL;`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(`ALTER TABLE 'clients' ADD 'approvalState' text;`).run();
|
||||||
|
|
||||||
|
db.prepare(`ALTER TABLE 'idp' ADD 'tags' text;`).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`ALTER TABLE 'olms' ADD 'archived' integer DEFAULT false NOT NULL;`
|
||||||
|
).run();
|
||||||
|
|
||||||
|
db.prepare(
|
||||||
|
`ALTER TABLE 'roles' ADD 'requireDeviceApproval' integer DEFAULT false;`
|
||||||
|
).run();
|
||||||
|
})();
|
||||||
|
|
||||||
|
db.pragma("foreign_keys = ON");
|
||||||
|
|
||||||
|
console.log(`Migrated database`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to migrate db:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${version} migration complete`);
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import { ApprovalFeed } from "@app/components/ApprovalFeed";
|
import { ApprovalFeed } from "@app/components/ApprovalFeed";
|
||||||
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
|
import ApprovalsBanner from "@app/components/ApprovalsBanner";
|
||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
||||||
import type { ApprovalItem } from "@app/lib/queries";
|
import type { ApprovalItem } from "@app/lib/queries";
|
||||||
import OrgProvider from "@app/providers/OrgProvider";
|
import OrgProvider from "@app/providers/OrgProvider";
|
||||||
import type { GetOrgResponse } from "@server/routers/org";
|
import type { GetOrgResponse } from "@server/routers/org";
|
||||||
|
import type { ListRolesResponse } from "@server/routers/role";
|
||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
@@ -34,6 +37,21 @@ export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) {
|
|||||||
org = orgRes.data.data;
|
org = orgRes.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch roles to check if approvals are enabled
|
||||||
|
let hasApprovalsEnabled = false;
|
||||||
|
const rolesRes = await internal
|
||||||
|
.get<AxiosResponse<ListRolesResponse>>(
|
||||||
|
`/org/${params.orgId}/roles`,
|
||||||
|
await authCookieHeader()
|
||||||
|
)
|
||||||
|
.catch((e) => {});
|
||||||
|
|
||||||
|
if (rolesRes && rolesRes.status === 200) {
|
||||||
|
hasApprovalsEnabled = rolesRes.data.data.roles.some(
|
||||||
|
(role) => role.requireDeviceApproval === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations();
|
const t = await getTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -42,9 +60,17 @@ export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) {
|
|||||||
title={t("accessApprovalsManage")}
|
title={t("accessApprovalsManage")}
|
||||||
description={t("accessApprovalsDescription")}
|
description={t("accessApprovalsDescription")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ApprovalsBanner />
|
||||||
|
|
||||||
|
<PaidFeaturesAlert />
|
||||||
|
|
||||||
<OrgProvider org={org}>
|
<OrgProvider org={org}>
|
||||||
<div className="container mx-auto max-w-12xl">
|
<div className="container mx-auto max-w-12xl">
|
||||||
<ApprovalFeed orgId={params.orgId} />
|
<ApprovalFeed
|
||||||
|
orgId={params.orgId}
|
||||||
|
hasApprovalsEnabled={hasApprovalsEnabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</OrgProvider>
|
</OrgProvider>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ export default function Page() {
|
|||||||
const res = await api
|
const res = await api
|
||||||
.put(`/org/${orgId}/user`, {
|
.put(`/org/${orgId}/user`, {
|
||||||
username: values.email, // Use email as username for Google/Azure
|
username: values.email, // Use email as username for Google/Azure
|
||||||
email: values.email,
|
email: values.email || undefined,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
type: "oidc",
|
type: "oidc",
|
||||||
idpId: selectedUserOption.idpId,
|
idpId: selectedUserOption.idpId,
|
||||||
@@ -403,7 +403,7 @@ export default function Page() {
|
|||||||
const res = await api
|
const res = await api
|
||||||
.put(`/org/${orgId}/user`, {
|
.put(`/org/${orgId}/user`, {
|
||||||
username: values.username,
|
username: values.username,
|
||||||
email: values.email,
|
email: values.email || undefined,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
type: "oidc",
|
type: "oidc",
|
||||||
idpId: selectedUserOption.idpId,
|
idpId: selectedUserOption.idpId,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
|
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||||
import { ListUsersResponse } from "@server/routers/user";
|
import { ListUsersResponse } from "@server/routers/user";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import UsersTable, { UserRow } from "../../../../../components/UsersTable";
|
import UsersTable, { UserRow } from "../../../../../components/UsersTable";
|
||||||
@@ -73,7 +74,11 @@ export default async function UsersPage(props: UsersPageProps) {
|
|||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
displayUsername: user.email || user.name || user.username,
|
displayUsername: getUserDisplayName({
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
username: user.username
|
||||||
|
}),
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
type: user.type,
|
type: user.type,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
|
|||||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
||||||
import { InfoIcon } from "lucide-react";
|
import { InfoIcon } from "lucide-react";
|
||||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
|
import { OlmInstallCommands } from "@app/components/olm-install-commands";
|
||||||
|
|
||||||
export default function CredentialsPage() {
|
export default function CredentialsPage() {
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
@@ -204,6 +205,12 @@ export default function CredentialsPage() {
|
|||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
)}
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
|
<OlmInstallCommands
|
||||||
|
id={displayOlmId ?? "********"}
|
||||||
|
endpoint={env.app.dashboardUrl}
|
||||||
|
secret={displaySecret ?? "********"}
|
||||||
|
/>
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|
||||||
<ConfirmDeleteDialog
|
<ConfirmDeleteDialog
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import { ListSitesResponse } from "@server/routers/site";
|
|||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useTransition } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import ActionBanner from "@app/components/ActionBanner";
|
||||||
|
import { Shield, ShieldOff } from "lucide-react";
|
||||||
|
|
||||||
const GeneralFormSchema = z.object({
|
const GeneralFormSchema = z.object({
|
||||||
name: z.string().nonempty("Name is required"),
|
name: z.string().nonempty("Name is required"),
|
||||||
@@ -45,7 +47,9 @@ export default function GeneralPage() {
|
|||||||
const { client, updateClient } = useClientContext();
|
const { client, updateClient } = useClientContext();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [, startTransition] = useTransition();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
@@ -109,8 +113,54 @@ export default function GeneralPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleUnblock = async () => {
|
||||||
|
if (!client?.clientId) return;
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
await api.post(`/client/${client.clientId}/unblock`);
|
||||||
|
// Optimistically update the client context
|
||||||
|
updateClient({ blocked: false, approvalState: null });
|
||||||
|
toast({
|
||||||
|
title: t("unblockClient"),
|
||||||
|
description: t("unblockClientDescription")
|
||||||
|
});
|
||||||
|
startTransition(() => {
|
||||||
|
router.refresh();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("error"),
|
||||||
|
description: formatAxiosError(e, t("error"))
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
|
{/* Blocked Device Banner */}
|
||||||
|
{client?.blocked && (
|
||||||
|
<ActionBanner
|
||||||
|
variant="destructive"
|
||||||
|
title={t("blocked")}
|
||||||
|
titleIcon={<Shield className="w-5 h-5" />}
|
||||||
|
description={t("deviceBlockedDescription")}
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
onClick={handleUnblock}
|
||||||
|
disabled={isRefreshing}
|
||||||
|
loading={isRefreshing}
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<ShieldOff className="size-4" />
|
||||||
|
{t("unblock")}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||||
|
import {
|
||||||
|
InfoSection,
|
||||||
|
InfoSectionContent,
|
||||||
|
InfoSections,
|
||||||
|
InfoSectionTitle
|
||||||
|
} from "@app/components/InfoSection";
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
SettingsContainer,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
SettingsSectionForm,
|
|
||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle
|
SettingsSectionTitle
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import { StrategySelect } from "@app/components/StrategySelect";
|
import HeaderTitle from "@app/components/SettingsSectionTitle";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -19,44 +26,24 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
import HeaderTitle from "@app/components/SettingsSectionTitle";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { createElement, useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Input } from "@app/components/ui/input";
|
import { Input } from "@app/components/ui/input";
|
||||||
import { ChevronDown, ChevronUp, InfoIcon, Terminal } from "lucide-react";
|
|
||||||
import { Button } from "@app/components/ui/button";
|
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
|
||||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
|
||||||
import {
|
|
||||||
InfoSection,
|
|
||||||
InfoSectionContent,
|
|
||||||
InfoSections,
|
|
||||||
InfoSectionTitle
|
|
||||||
} from "@app/components/InfoSection";
|
|
||||||
import {
|
|
||||||
FaApple,
|
|
||||||
FaCubes,
|
|
||||||
FaDocker,
|
|
||||||
FaFreebsd,
|
|
||||||
FaWindows
|
|
||||||
} from "react-icons/fa";
|
|
||||||
import { SiNixos, SiKubernetes } from "react-icons/si";
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
|
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
import {
|
||||||
CreateClientBody,
|
CreateClientBody,
|
||||||
CreateClientResponse,
|
CreateClientResponse,
|
||||||
PickClientDefaultsResponse
|
PickClientDefaultsResponse
|
||||||
} from "@server/routers/client";
|
} from "@server/routers/client";
|
||||||
import { ListSitesResponse } from "@server/routers/site";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { Tag, TagInput } from "@app/components/tags/tag-input";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { OlmInstallCommands } from "@app/components/olm-install-commands";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
type ClientType = "olm";
|
type ClientType = "olm";
|
||||||
@@ -68,17 +55,6 @@ interface TunnelTypeOption {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandItem = string | { title: string; command: string };
|
|
||||||
|
|
||||||
type Commands = {
|
|
||||||
unix: Record<string, CommandItem[]>;
|
|
||||||
windows: Record<string, CommandItem[]>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const platforms = ["unix", "windows"] as const;
|
|
||||||
|
|
||||||
type Platform = (typeof platforms)[number];
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
const api = createApiClient({ env });
|
const api = createApiClient({ env });
|
||||||
@@ -112,13 +88,9 @@ export default function Page() {
|
|||||||
|
|
||||||
const [loadingPage, setLoadingPage] = useState(true);
|
const [loadingPage, setLoadingPage] = useState(true);
|
||||||
|
|
||||||
const [platform, setPlatform] = useState<Platform>("unix");
|
|
||||||
const [architecture, setArchitecture] = useState("All");
|
|
||||||
const [commands, setCommands] = useState<Commands | null>(null);
|
|
||||||
|
|
||||||
const [olmId, setOlmId] = useState("");
|
const [olmId, setOlmId] = useState("");
|
||||||
const [olmSecret, setOlmSecret] = useState("");
|
const [olmSecret, setOlmSecret] = useState("");
|
||||||
const [olmCommand, setOlmCommand] = useState("");
|
const [olmVersion, setOlmVersion] = useState("latest");
|
||||||
|
|
||||||
const [createLoading, setCreateLoading] = useState(false);
|
const [createLoading, setCreateLoading] = useState(false);
|
||||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||||
@@ -126,113 +98,6 @@ export default function Page() {
|
|||||||
const [clientDefaults, setClientDefaults] =
|
const [clientDefaults, setClientDefaults] =
|
||||||
useState<PickClientDefaultsResponse | null>(null);
|
useState<PickClientDefaultsResponse | null>(null);
|
||||||
|
|
||||||
const hydrateCommands = (
|
|
||||||
id: string,
|
|
||||||
secret: string,
|
|
||||||
endpoint: string,
|
|
||||||
version: string
|
|
||||||
) => {
|
|
||||||
const commands = {
|
|
||||||
unix: {
|
|
||||||
All: [
|
|
||||||
{
|
|
||||||
title: t("install"),
|
|
||||||
command: `curl -fsSL https://static.pangolin.net/get-olm.sh | bash`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("run"),
|
|
||||||
command: `sudo olm --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
windows: {
|
|
||||||
x64: [
|
|
||||||
{
|
|
||||||
title: t("install"),
|
|
||||||
command: `curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("run"),
|
|
||||||
command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setCommands(commands);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getArchitectures = () => {
|
|
||||||
switch (platform) {
|
|
||||||
case "unix":
|
|
||||||
return ["All"];
|
|
||||||
case "windows":
|
|
||||||
return ["x64"];
|
|
||||||
default:
|
|
||||||
return ["x64"];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPlatformName = (platformName: string) => {
|
|
||||||
switch (platformName) {
|
|
||||||
case "windows":
|
|
||||||
return "Windows";
|
|
||||||
case "unix":
|
|
||||||
return "Unix & macOS";
|
|
||||||
case "docker":
|
|
||||||
return "Docker";
|
|
||||||
default:
|
|
||||||
return "Unix & macOS";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCommand = (): CommandItem[] => {
|
|
||||||
const placeholder: CommandItem[] = [t("unknownCommand")];
|
|
||||||
if (!commands) {
|
|
||||||
return placeholder;
|
|
||||||
}
|
|
||||||
let platformCommands = commands[platform as keyof Commands];
|
|
||||||
|
|
||||||
if (!platformCommands) {
|
|
||||||
// get first key
|
|
||||||
const firstPlatform = Object.keys(commands)[0] as Platform;
|
|
||||||
platformCommands = commands[firstPlatform as keyof Commands];
|
|
||||||
|
|
||||||
setPlatform(firstPlatform);
|
|
||||||
}
|
|
||||||
|
|
||||||
let architectureCommands = platformCommands[architecture];
|
|
||||||
if (!architectureCommands) {
|
|
||||||
// get first key
|
|
||||||
const firstArchitecture = Object.keys(platformCommands)[0];
|
|
||||||
architectureCommands = platformCommands[firstArchitecture];
|
|
||||||
|
|
||||||
setArchitecture(firstArchitecture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return architectureCommands || placeholder;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPlatformIcon = (platformName: string) => {
|
|
||||||
switch (platformName) {
|
|
||||||
case "windows":
|
|
||||||
return <FaWindows className="h-4 w-4 mr-2" />;
|
|
||||||
case "unix":
|
|
||||||
return <Terminal className="h-4 w-4 mr-2" />;
|
|
||||||
case "docker":
|
|
||||||
return <FaDocker className="h-4 w-4 mr-2" />;
|
|
||||||
case "kubernetes":
|
|
||||||
return <SiKubernetes className="h-4 w-4 mr-2" />;
|
|
||||||
case "podman":
|
|
||||||
return <FaCubes className="h-4 w-4 mr-2" />;
|
|
||||||
case "freebsd":
|
|
||||||
return <FaFreebsd className="h-4 w-4 mr-2" />;
|
|
||||||
case "nixos":
|
|
||||||
return <SiNixos className="h-4 w-4 mr-2" />;
|
|
||||||
default:
|
|
||||||
return <Terminal className="h-4 w-4 mr-2" />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const form = useForm<CreateClientFormValues>({
|
const form = useForm<CreateClientFormValues>({
|
||||||
resolver: zodResolver(createClientFormSchema),
|
resolver: zodResolver(createClientFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -287,23 +152,6 @@ export default function Page() {
|
|||||||
const load = async () => {
|
const load = async () => {
|
||||||
setLoadingPage(true);
|
setLoadingPage(true);
|
||||||
|
|
||||||
// Fetch available sites
|
|
||||||
|
|
||||||
// const res = await api.get<AxiosResponse<ListSitesResponse>>(
|
|
||||||
// `/org/${orgId}/sites/`
|
|
||||||
// );
|
|
||||||
// const sites = res.data.data.sites.filter(
|
|
||||||
// (s) => s.type === "newt" && s.subnet
|
|
||||||
// );
|
|
||||||
// setSites(
|
|
||||||
// sites.map((site) => ({
|
|
||||||
// id: site.siteId.toString(),
|
|
||||||
// text: site.name
|
|
||||||
// }))
|
|
||||||
// );
|
|
||||||
|
|
||||||
let olmVersion = "latest";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
||||||
@@ -324,7 +172,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const latestVersion = data.tag_name;
|
const latestVersion = data.tag_name;
|
||||||
olmVersion = latestVersion;
|
setOlmVersion(latestVersion);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.name === "AbortError") {
|
if (error instanceof Error && error.name === "AbortError") {
|
||||||
console.error(t("olmErrorFetchTimeout"));
|
console.error(t("olmErrorFetchTimeout"));
|
||||||
@@ -353,18 +201,9 @@ export default function Page() {
|
|||||||
|
|
||||||
const olmId = data.olmId;
|
const olmId = data.olmId;
|
||||||
const olmSecret = data.olmSecret;
|
const olmSecret = data.olmSecret;
|
||||||
const olmCommand = `olm --id ${olmId} --secret ${olmSecret} --endpoint ${env.app.dashboardUrl}`;
|
|
||||||
|
|
||||||
setOlmId(olmId);
|
setOlmId(olmId);
|
||||||
setOlmSecret(olmSecret);
|
setOlmSecret(olmSecret);
|
||||||
setOlmCommand(olmCommand);
|
|
||||||
|
|
||||||
hydrateCommands(
|
|
||||||
olmId,
|
|
||||||
olmSecret,
|
|
||||||
env.app.dashboardUrl,
|
|
||||||
olmVersion
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data.subnet) {
|
if (data.subnet) {
|
||||||
form.setValue("subnet", data.subnet);
|
form.setValue("subnet", data.subnet);
|
||||||
@@ -547,118 +386,12 @@ export default function Page() {
|
|||||||
</InfoSections>
|
</InfoSections>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
<SettingsSection>
|
<OlmInstallCommands
|
||||||
<SettingsSectionHeader>
|
id={olmId}
|
||||||
<SettingsSectionTitle>
|
endpoint={env.app.dashboardUrl}
|
||||||
{t("clientInstallOlm")}
|
secret={olmSecret}
|
||||||
</SettingsSectionTitle>
|
version={olmVersion}
|
||||||
<SettingsSectionDescription>
|
/>
|
||||||
{t("clientInstallOlmDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<div>
|
|
||||||
<p className="font-bold mb-3">
|
|
||||||
{t("operatingSystem")}
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
|
||||||
{platforms.map((os) => (
|
|
||||||
<Button
|
|
||||||
key={os}
|
|
||||||
variant={
|
|
||||||
platform === os
|
|
||||||
? "squareOutlinePrimary"
|
|
||||||
: "squareOutline"
|
|
||||||
}
|
|
||||||
className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""} shadow-none`}
|
|
||||||
onClick={() => {
|
|
||||||
setPlatform(os);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getPlatformIcon(os)}
|
|
||||||
{getPlatformName(os)}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="font-bold mb-3">
|
|
||||||
{["docker", "podman"].includes(
|
|
||||||
platform
|
|
||||||
)
|
|
||||||
? t("method")
|
|
||||||
: t("architecture")}
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
|
||||||
{getArchitectures().map(
|
|
||||||
(arch) => (
|
|
||||||
<Button
|
|
||||||
key={arch}
|
|
||||||
variant={
|
|
||||||
architecture ===
|
|
||||||
arch
|
|
||||||
? "squareOutlinePrimary"
|
|
||||||
: "squareOutline"
|
|
||||||
}
|
|
||||||
className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
|
|
||||||
onClick={() =>
|
|
||||||
setArchitecture(
|
|
||||||
arch
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{arch}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="pt-4">
|
|
||||||
<p className="font-bold mb-3">
|
|
||||||
{t("commands")}
|
|
||||||
</p>
|
|
||||||
<div className="mt-2 space-y-3">
|
|
||||||
{getCommand().map(
|
|
||||||
(item, index) => {
|
|
||||||
const commandText =
|
|
||||||
typeof item ===
|
|
||||||
"string"
|
|
||||||
? item
|
|
||||||
: item.command;
|
|
||||||
const title =
|
|
||||||
typeof item ===
|
|
||||||
"string"
|
|
||||||
? undefined
|
|
||||||
: item.title;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
{title && (
|
|
||||||
<p className="text-sm font-medium mb-1.5">
|
|
||||||
{
|
|
||||||
title
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<CopyTextBox
|
|
||||||
text={
|
|
||||||
commandText
|
|
||||||
}
|
|
||||||
outline={
|
|
||||||
true
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|||||||
775
src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx
Normal file
775
src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx
Normal file
@@ -0,0 +1,775 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SettingsContainer,
|
||||||
|
SettingsSection,
|
||||||
|
SettingsSectionBody,
|
||||||
|
SettingsSectionDescription,
|
||||||
|
SettingsSectionFooter,
|
||||||
|
SettingsSectionHeader,
|
||||||
|
SettingsSectionTitle
|
||||||
|
} from "@app/components/Settings";
|
||||||
|
import { useClientContext } from "@app/hooks/useClientContext";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import {
|
||||||
|
InfoSection,
|
||||||
|
InfoSectionContent,
|
||||||
|
InfoSections,
|
||||||
|
InfoSectionTitle
|
||||||
|
} from "@app/components/InfoSection";
|
||||||
|
import { Badge } from "@app/components/ui/badge";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import ActionBanner from "@app/components/ActionBanner";
|
||||||
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState, useEffect, useTransition } from "react";
|
||||||
|
import { Check, Ban, Shield, ShieldOff, Clock, CheckCircle2, XCircle } from "lucide-react";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
|
||||||
|
import { SiAndroid } from "react-icons/si";
|
||||||
|
|
||||||
|
function formatTimestamp(timestamp: number | null | undefined): string {
|
||||||
|
if (!timestamp) return "-";
|
||||||
|
return new Date(timestamp * 1000).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPlatform(platform: string | null | undefined): string {
|
||||||
|
if (!platform) return "-";
|
||||||
|
const platformMap: Record<string, string> = {
|
||||||
|
macos: "macOS",
|
||||||
|
windows: "Windows",
|
||||||
|
linux: "Linux",
|
||||||
|
ios: "iOS",
|
||||||
|
android: "Android",
|
||||||
|
unknown: "Unknown"
|
||||||
|
};
|
||||||
|
return platformMap[platform.toLowerCase()] || platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlatformIcon(platform: string | null | undefined) {
|
||||||
|
if (!platform) return null;
|
||||||
|
const normalizedPlatform = platform.toLowerCase();
|
||||||
|
switch (normalizedPlatform) {
|
||||||
|
case "macos":
|
||||||
|
case "ios":
|
||||||
|
return <FaApple className="h-4 w-4" />;
|
||||||
|
case "windows":
|
||||||
|
return <FaWindows className="h-4 w-4" />;
|
||||||
|
case "linux":
|
||||||
|
return <FaLinux className="h-4 w-4" />;
|
||||||
|
case "android":
|
||||||
|
return <SiAndroid className="h-4 w-4" />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldConfig = {
|
||||||
|
show: boolean;
|
||||||
|
labelKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPlatformFieldConfig(
|
||||||
|
platform: string | null | undefined
|
||||||
|
): Record<string, FieldConfig> {
|
||||||
|
const normalizedPlatform = platform?.toLowerCase() || "unknown";
|
||||||
|
|
||||||
|
const configs: Record<string, Record<string, FieldConfig>> = {
|
||||||
|
macos: {
|
||||||
|
osVersion: { show: true, labelKey: "macosVersion" },
|
||||||
|
kernelVersion: { show: false, labelKey: "kernelVersion" },
|
||||||
|
arch: { show: true, labelKey: "architecture" },
|
||||||
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
|
serialNumber: { show: true, labelKey: "serialNumber" },
|
||||||
|
username: { show: true, labelKey: "username" },
|
||||||
|
hostname: { show: true, labelKey: "hostname" }
|
||||||
|
},
|
||||||
|
windows: {
|
||||||
|
osVersion: { show: true, labelKey: "windowsVersion" },
|
||||||
|
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
||||||
|
arch: { show: true, labelKey: "architecture" },
|
||||||
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
|
serialNumber: { show: true, labelKey: "serialNumber" },
|
||||||
|
username: { show: true, labelKey: "username" },
|
||||||
|
hostname: { show: true, labelKey: "hostname" }
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
osVersion: { show: true, labelKey: "osVersion" },
|
||||||
|
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
||||||
|
arch: { show: true, labelKey: "architecture" },
|
||||||
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
|
serialNumber: { show: true, labelKey: "serialNumber" },
|
||||||
|
username: { show: true, labelKey: "username" },
|
||||||
|
hostname: { show: true, labelKey: "hostname" }
|
||||||
|
},
|
||||||
|
ios: {
|
||||||
|
osVersion: { show: true, labelKey: "iosVersion" },
|
||||||
|
kernelVersion: { show: false, labelKey: "kernelVersion" },
|
||||||
|
arch: { show: true, labelKey: "architecture" },
|
||||||
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
osVersion: { show: true, labelKey: "androidVersion" },
|
||||||
|
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
||||||
|
arch: { show: true, labelKey: "architecture" },
|
||||||
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
osVersion: { show: true, labelKey: "osVersion" },
|
||||||
|
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
||||||
|
arch: { show: true, labelKey: "architecture" },
|
||||||
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
|
serialNumber: { show: true, labelKey: "serialNumber" },
|
||||||
|
username: { show: true, labelKey: "username" },
|
||||||
|
hostname: { show: true, labelKey: "hostname" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return configs[normalizedPlatform] || configs.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function GeneralPage() {
|
||||||
|
const { client, updateClient } = useClientContext();
|
||||||
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
const t = useTranslations();
|
||||||
|
const api = createApiClient(useEnvContext());
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const orgId = params.orgId as string;
|
||||||
|
const [approvalId, setApprovalId] = useState<number | null>(null);
|
||||||
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
const [, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const showApprovalFeatures = build !== "oss" && isPaidUser;
|
||||||
|
|
||||||
|
const formatPostureValue = (value: boolean | null | undefined) => {
|
||||||
|
if (value === null || value === undefined) return "-";
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{value ? (
|
||||||
|
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<XCircle className="h-4 w-4 text-red-600" />
|
||||||
|
)}
|
||||||
|
<span>{value ? t("enabled") : t("disabled")}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch approval ID for this client if pending
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
showApprovalFeatures &&
|
||||||
|
client.approvalState === "pending" &&
|
||||||
|
client.clientId
|
||||||
|
) {
|
||||||
|
api.get(`/org/${orgId}/approvals?approvalState=pending`)
|
||||||
|
.then((res) => {
|
||||||
|
const approval = res.data.data.approvals.find(
|
||||||
|
(a: any) => a.clientId === client.clientId
|
||||||
|
);
|
||||||
|
if (approval) {
|
||||||
|
setApprovalId(approval.approvalId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Silently fail - approval might not exist
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
showApprovalFeatures,
|
||||||
|
client.approvalState,
|
||||||
|
client.clientId,
|
||||||
|
orgId,
|
||||||
|
api
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleApprove = async () => {
|
||||||
|
if (!approvalId) return;
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
await api.put(`/org/${orgId}/approvals/${approvalId}`, {
|
||||||
|
decision: "approved"
|
||||||
|
});
|
||||||
|
// Optimistically update the client context
|
||||||
|
updateClient({ approvalState: "approved" });
|
||||||
|
toast({
|
||||||
|
title: t("accessApprovalUpdated"),
|
||||||
|
description: t("accessApprovalApprovedDescription")
|
||||||
|
});
|
||||||
|
startTransition(() => {
|
||||||
|
router.refresh();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("accessApprovalErrorUpdate"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
t("accessApprovalErrorUpdateDescription")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeny = async () => {
|
||||||
|
if (!approvalId) return;
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
await api.put(`/org/${orgId}/approvals/${approvalId}`, {
|
||||||
|
decision: "denied"
|
||||||
|
});
|
||||||
|
// Optimistically update the client context
|
||||||
|
updateClient({ approvalState: "denied", blocked: true });
|
||||||
|
toast({
|
||||||
|
title: t("accessApprovalUpdated"),
|
||||||
|
description: t("accessApprovalDeniedDescription")
|
||||||
|
});
|
||||||
|
startTransition(() => {
|
||||||
|
router.refresh();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("accessApprovalErrorUpdate"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
t("accessApprovalErrorUpdateDescription")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlock = async () => {
|
||||||
|
if (!client.clientId) return;
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
await api.post(`/client/${client.clientId}/block`);
|
||||||
|
// Optimistically update the client context
|
||||||
|
updateClient({ blocked: true, approvalState: "denied" });
|
||||||
|
toast({
|
||||||
|
title: t("blockClient"),
|
||||||
|
description: t("blockClientMessage")
|
||||||
|
});
|
||||||
|
startTransition(() => {
|
||||||
|
router.refresh();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("error"),
|
||||||
|
description: formatAxiosError(e, t("error"))
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnblock = async () => {
|
||||||
|
if (!client.clientId) return;
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
await api.post(`/client/${client.clientId}/unblock`);
|
||||||
|
// Optimistically update the client context
|
||||||
|
updateClient({ blocked: false, approvalState: null });
|
||||||
|
toast({
|
||||||
|
title: t("unblockClient"),
|
||||||
|
description: t("unblockClientDescription")
|
||||||
|
});
|
||||||
|
startTransition(() => {
|
||||||
|
router.refresh();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("error"),
|
||||||
|
description: formatAxiosError(e, t("error"))
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContainer>
|
||||||
|
{/* Pending Approval Banner */}
|
||||||
|
{showApprovalFeatures && client.approvalState === "pending" && (
|
||||||
|
<ActionBanner
|
||||||
|
variant="warning"
|
||||||
|
title={t("pendingApproval")}
|
||||||
|
titleIcon={<Clock className="w-5 h-5" />}
|
||||||
|
description={t("devicePendingApprovalBannerDescription")}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={handleApprove}
|
||||||
|
disabled={isRefreshing || !approvalId}
|
||||||
|
loading={isRefreshing}
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Check className="size-4" />
|
||||||
|
{t("approve")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeny}
|
||||||
|
disabled={isRefreshing || !approvalId}
|
||||||
|
loading={isRefreshing}
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Ban className="size-4" />
|
||||||
|
{t("deny")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Blocked Device Banner */}
|
||||||
|
{client.blocked && client.approvalState !== "pending" && (
|
||||||
|
<ActionBanner
|
||||||
|
variant="destructive"
|
||||||
|
title={t("blocked")}
|
||||||
|
titleIcon={<Shield className="w-5 h-5" />}
|
||||||
|
description={t("deviceBlockedDescription")}
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
onClick={handleUnblock}
|
||||||
|
disabled={isRefreshing}
|
||||||
|
loading={isRefreshing}
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<ShieldOff className="size-4" />
|
||||||
|
{t("unblock")}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Device Information Section */}
|
||||||
|
{(client.fingerprint || (client.agent && client.olmVersion)) && (
|
||||||
|
<SettingsSection>
|
||||||
|
<SettingsSectionHeader>
|
||||||
|
<SettingsSectionTitle>
|
||||||
|
{t("deviceInformation")}
|
||||||
|
</SettingsSectionTitle>
|
||||||
|
<SettingsSectionDescription>
|
||||||
|
{t("deviceInformationDescription")}
|
||||||
|
</SettingsSectionDescription>
|
||||||
|
</SettingsSectionHeader>
|
||||||
|
|
||||||
|
<SettingsSectionBody>
|
||||||
|
{client.agent && client.olmVersion && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("agent")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
<Badge variant="secondary">
|
||||||
|
{client.agent +
|
||||||
|
" v" +
|
||||||
|
client.olmVersion}
|
||||||
|
</Badge>
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint &&
|
||||||
|
(() => {
|
||||||
|
const platform = client.fingerprint.platform;
|
||||||
|
const fieldConfig =
|
||||||
|
getPlatformFieldConfig(platform);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InfoSections cols={3}>
|
||||||
|
{platform && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("platform")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{getPlatformIcon(
|
||||||
|
platform
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{formatPlatform(
|
||||||
|
platform
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.osVersion &&
|
||||||
|
fieldConfig.osVersion?.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t(
|
||||||
|
fieldConfig
|
||||||
|
.osVersion
|
||||||
|
?.labelKey || "osVersion"
|
||||||
|
)}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.osVersion
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.kernelVersion &&
|
||||||
|
fieldConfig.kernelVersion?.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("kernelVersion")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.kernelVersion
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.arch &&
|
||||||
|
fieldConfig.arch.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("architecture")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.arch
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.deviceModel &&
|
||||||
|
fieldConfig.deviceModel?.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("deviceModel")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.deviceModel
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.serialNumber &&
|
||||||
|
fieldConfig.serialNumber.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("serialNumber")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.serialNumber
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.username &&
|
||||||
|
fieldConfig.username?.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("username")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.username
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.hostname &&
|
||||||
|
fieldConfig.hostname?.show && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("hostname")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{
|
||||||
|
client.fingerprint
|
||||||
|
.hostname
|
||||||
|
}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.firstSeen && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("firstSeen")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{formatTimestamp(
|
||||||
|
client.fingerprint
|
||||||
|
.firstSeen
|
||||||
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.fingerprint.lastSeen && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("lastSeen")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{formatTimestamp(
|
||||||
|
client.fingerprint
|
||||||
|
.lastSeen
|
||||||
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
</InfoSections>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</SettingsSectionBody>
|
||||||
|
</SettingsSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Device Security Section */}
|
||||||
|
{build !== "oss" && (
|
||||||
|
<SettingsSection>
|
||||||
|
<SettingsSectionHeader>
|
||||||
|
<SettingsSectionTitle>
|
||||||
|
{t("deviceSecurity")}
|
||||||
|
</SettingsSectionTitle>
|
||||||
|
<SettingsSectionDescription>
|
||||||
|
{t("deviceSecurityDescription")}
|
||||||
|
</SettingsSectionDescription>
|
||||||
|
</SettingsSectionHeader>
|
||||||
|
|
||||||
|
<SettingsSectionBody>
|
||||||
|
{client.posture && Object.keys(client.posture).length > 0 ? (
|
||||||
|
<>
|
||||||
|
{!isPaidUser && <PaidFeaturesAlert />}
|
||||||
|
<InfoSections cols={3}>
|
||||||
|
{client.posture.biometricsEnabled !== null &&
|
||||||
|
client.posture.biometricsEnabled !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("biometricsEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture.biometricsEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.diskEncrypted !== null &&
|
||||||
|
client.posture.diskEncrypted !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("diskEncrypted")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture.diskEncrypted
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.firewallEnabled !== null &&
|
||||||
|
client.posture.firewallEnabled !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("firewallEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture.firewallEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.autoUpdatesEnabled !== null &&
|
||||||
|
client.posture.autoUpdatesEnabled !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("autoUpdatesEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture.autoUpdatesEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.tpmAvailable !== null &&
|
||||||
|
client.posture.tpmAvailable !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("tpmAvailable")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture.tpmAvailable
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.windowsDefenderEnabled !== null &&
|
||||||
|
client.posture.windowsDefenderEnabled !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("windowsDefenderEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture
|
||||||
|
.windowsDefenderEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.macosSipEnabled !== null &&
|
||||||
|
client.posture.macosSipEnabled !== undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("macosSipEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture.macosSipEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.macosGatekeeperEnabled !== null &&
|
||||||
|
client.posture.macosGatekeeperEnabled !==
|
||||||
|
undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("macosGatekeeperEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture
|
||||||
|
.macosGatekeeperEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.macosFirewallStealthMode !== null &&
|
||||||
|
client.posture.macosFirewallStealthMode !==
|
||||||
|
undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("macosFirewallStealthMode")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture
|
||||||
|
.macosFirewallStealthMode
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.linuxAppArmorEnabled !== null &&
|
||||||
|
client.posture.linuxAppArmorEnabled !==
|
||||||
|
undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("linuxAppArmorEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture
|
||||||
|
.linuxAppArmorEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{client.posture.linuxSELinuxEnabled !== null &&
|
||||||
|
client.posture.linuxSELinuxEnabled !==
|
||||||
|
undefined && (
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>
|
||||||
|
{t("linuxSELinuxEnabled")}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{isPaidUser
|
||||||
|
? formatPostureValue(
|
||||||
|
client.posture
|
||||||
|
.linuxSELinuxEnabled
|
||||||
|
)
|
||||||
|
: "-"}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
)}
|
||||||
|
</InfoSections>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
{t("noData")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SettingsSectionBody>
|
||||||
|
</SettingsSection>
|
||||||
|
)}
|
||||||
|
</SettingsContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
src/app/[orgId]/settings/clients/user/[niceId]/layout.tsx
Normal file
57
src/app/[orgId]/settings/clients/user/[niceId]/layout.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import ClientInfoCard from "@app/components/ClientInfoCard";
|
||||||
|
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||||
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
|
import { internal } from "@app/lib/api";
|
||||||
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
|
import ClientProvider from "@app/providers/ClientProvider";
|
||||||
|
import { GetClientResponse } from "@server/routers/client";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
type SettingsLayoutProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{ niceId: number | string; orgId: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
|
const params = await props.params;
|
||||||
|
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
let client = null;
|
||||||
|
try {
|
||||||
|
const res = await internal.get<AxiosResponse<GetClientResponse>>(
|
||||||
|
`/org/${params.orgId}/client/${params.niceId}`,
|
||||||
|
await authCookieHeader()
|
||||||
|
);
|
||||||
|
client = res.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
redirect(`/${params.orgId}/settings/clients/user`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = await getTranslations();
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{
|
||||||
|
title: t("general"),
|
||||||
|
href: `/${params.orgId}/settings/clients/user/${params.niceId}/general`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsSectionTitle
|
||||||
|
title={`${client?.name} Settings`}
|
||||||
|
description={t("deviceSettingsDescription")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ClientProvider client={client}>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<ClientInfoCard />
|
||||||
|
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
|
||||||
|
</div>
|
||||||
|
</ClientProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
src/app/[orgId]/settings/clients/user/[niceId]/page.tsx
Normal file
10
src/app/[orgId]/settings/clients/user/[niceId]/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default async function ClientPage(props: {
|
||||||
|
params: Promise<{ orgId: string; niceId: number | string }>;
|
||||||
|
}) {
|
||||||
|
const params = await props.params;
|
||||||
|
redirect(
|
||||||
|
`/${params.orgId}/settings/clients/user/${params.niceId}/general`
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -41,6 +41,32 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
const mapClientToRow = (
|
const mapClientToRow = (
|
||||||
client: ListClientsResponse["clients"][0]
|
client: ListClientsResponse["clients"][0]
|
||||||
): ClientRow => {
|
): ClientRow => {
|
||||||
|
// Build fingerprint object if any fingerprint data exists
|
||||||
|
const hasFingerprintData =
|
||||||
|
(client as any).fingerprintPlatform ||
|
||||||
|
(client as any).fingerprintOsVersion ||
|
||||||
|
(client as any).fingerprintKernelVersion ||
|
||||||
|
(client as any).fingerprintArch ||
|
||||||
|
(client as any).fingerprintSerialNumber ||
|
||||||
|
(client as any).fingerprintUsername ||
|
||||||
|
(client as any).fingerprintHostname ||
|
||||||
|
(client as any).deviceModel;
|
||||||
|
|
||||||
|
const fingerprint = hasFingerprintData
|
||||||
|
? {
|
||||||
|
platform: (client as any).fingerprintPlatform || null,
|
||||||
|
osVersion: (client as any).fingerprintOsVersion || null,
|
||||||
|
kernelVersion:
|
||||||
|
(client as any).fingerprintKernelVersion || null,
|
||||||
|
arch: (client as any).fingerprintArch || null,
|
||||||
|
deviceModel: (client as any).deviceModel || null,
|
||||||
|
serialNumber:
|
||||||
|
(client as any).fingerprintSerialNumber || null,
|
||||||
|
username: (client as any).fingerprintUsername || null,
|
||||||
|
hostname: (client as any).fingerprintHostname || null
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: client.name,
|
name: client.name,
|
||||||
id: client.clientId,
|
id: client.clientId,
|
||||||
@@ -58,7 +84,8 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
agent: client.agent,
|
agent: client.agent,
|
||||||
archived: client.archived || false,
|
archived: client.archived || false,
|
||||||
blocked: client.blocked || false,
|
blocked: client.blocked || false,
|
||||||
approvalState: client.approvalState
|
approvalState: client.approvalState,
|
||||||
|
fingerprint
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
GetLoginPageResponse
|
GetLoginPageResponse
|
||||||
} from "@server/routers/loginPage/types";
|
} from "@server/routers/loginPage/types";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export interface AuthPageProps {
|
export interface AuthPageProps {
|
||||||
params: Promise<{ orgId: string }>;
|
params: Promise<{ orgId: string }>;
|
||||||
@@ -18,6 +19,12 @@ export interface AuthPageProps {
|
|||||||
|
|
||||||
export default async function AuthPage(props: AuthPageProps) {
|
export default async function AuthPage(props: AuthPageProps) {
|
||||||
const orgId = (await props.params).orgId;
|
const orgId = (await props.params).orgId;
|
||||||
|
|
||||||
|
// custom auth branding is only available in enterprise and saas
|
||||||
|
if (build === "oss") {
|
||||||
|
redirect(`/${orgId}/settings/general/`);
|
||||||
|
}
|
||||||
|
|
||||||
let subscriptionStatus: GetOrgTierResponse | null = null;
|
let subscriptionStatus: GetOrgTierResponse | null = null;
|
||||||
try {
|
try {
|
||||||
const subRes = await getCachedSubscription(orgId);
|
const subRes = await getCachedSubscription(orgId);
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ export default async function GeneralSettingsPage({
|
|||||||
title: t("general"),
|
title: t("general"),
|
||||||
href: `/{orgId}/settings/general`,
|
href: `/{orgId}/settings/general`,
|
||||||
exact: true
|
exact: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("security"),
|
||||||
|
href: `/{orgId}/settings/general/security`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
if (build !== "oss") {
|
if (build !== "oss") {
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||||
import AuthPageSettings, {
|
|
||||||
AuthPageSettingsRef
|
|
||||||
} from "@app/components/private/AuthPageSettings";
|
|
||||||
|
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
import { userOrgUserContext } from "@app/hooks/useOrgUserContext";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import {
|
import {
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
|
||||||
useTransition,
|
useTransition,
|
||||||
useActionState,
|
useActionState
|
||||||
type ComponentRef
|
|
||||||
} from "react";
|
} from "react";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -25,13 +18,6 @@ import {
|
|||||||
FormMessage
|
FormMessage
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -55,79 +41,19 @@ import {
|
|||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { SwitchInput } from "@app/components/SwitchInput";
|
|
||||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
|
||||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
|
||||||
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
|
||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
|
||||||
import type { t } from "@faker-js/faker/dist/airline-DF6RqYmq";
|
|
||||||
import type { OrgContextType } from "@app/contexts/orgContext";
|
import type { OrgContextType } from "@app/contexts/orgContext";
|
||||||
|
|
||||||
// Session length options in hours
|
|
||||||
const SESSION_LENGTH_OPTIONS = [
|
|
||||||
{ value: null, labelKey: "unenforced" },
|
|
||||||
{ value: 1, labelKey: "1Hour" },
|
|
||||||
{ value: 3, labelKey: "3Hours" },
|
|
||||||
{ value: 6, labelKey: "6Hours" },
|
|
||||||
{ value: 12, labelKey: "12Hours" },
|
|
||||||
{ value: 24, labelKey: "1DaySession" },
|
|
||||||
{ value: 72, labelKey: "3Days" },
|
|
||||||
{ value: 168, labelKey: "7Days" },
|
|
||||||
{ value: 336, labelKey: "14Days" },
|
|
||||||
{ value: 720, labelKey: "30DaysSession" },
|
|
||||||
{ value: 2160, labelKey: "90DaysSession" },
|
|
||||||
{ value: 4320, labelKey: "180DaysSession" }
|
|
||||||
];
|
|
||||||
|
|
||||||
// Password expiry options in days - will be translated in component
|
|
||||||
const PASSWORD_EXPIRY_OPTIONS = [
|
|
||||||
{ value: null, labelKey: "neverExpire" },
|
|
||||||
{ value: 1, labelKey: "1Day" },
|
|
||||||
{ value: 30, labelKey: "30Days" },
|
|
||||||
{ value: 60, labelKey: "60Days" },
|
|
||||||
{ value: 90, labelKey: "90Days" },
|
|
||||||
{ value: 180, labelKey: "180Days" },
|
|
||||||
{ value: 365, labelKey: "1Year" }
|
|
||||||
];
|
|
||||||
|
|
||||||
// Schema for general organization settings
|
// Schema for general organization settings
|
||||||
const GeneralFormSchema = z.object({
|
const GeneralFormSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
subnet: z.string().optional(),
|
subnet: z.string().optional()
|
||||||
requireTwoFactor: z.boolean().optional(),
|
|
||||||
maxSessionLengthHours: z.number().nullable().optional(),
|
|
||||||
passwordExpiryDays: z.number().nullable().optional(),
|
|
||||||
settingsLogRetentionDaysRequest: z.number(),
|
|
||||||
settingsLogRetentionDaysAccess: z.number(),
|
|
||||||
settingsLogRetentionDaysAction: z.number()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
|
||||||
|
|
||||||
const LOG_RETENTION_OPTIONS = [
|
|
||||||
{ label: "logRetentionDisabled", value: 0 },
|
|
||||||
{ label: "logRetention3Days", value: 3 },
|
|
||||||
{ label: "logRetention7Days", value: 7 },
|
|
||||||
{ label: "logRetention14Days", value: 14 },
|
|
||||||
{ label: "logRetention30Days", value: 30 },
|
|
||||||
{ label: "logRetention90Days", value: 90 },
|
|
||||||
...(build != "saas"
|
|
||||||
? [
|
|
||||||
{ label: "logRetentionForever", value: -1 },
|
|
||||||
{ label: "logRetentionEndOfFollowingYear", value: 9001 }
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function GeneralPage() {
|
export default function GeneralPage() {
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
return (
|
return (
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<GeneralSectionForm org={org.org} />
|
<GeneralSectionForm org={org.org} />
|
||||||
|
|
||||||
<LogRetentionSectionForm org={org.org} />
|
|
||||||
|
|
||||||
{build !== "oss" && <SecuritySettingsSectionForm org={org.org} />}
|
|
||||||
{build !== "saas" && <DeleteForm org={org.org} />}
|
{build !== "saas" && <DeleteForm org={org.org} />}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
@@ -339,638 +265,3 @@ function GeneralSectionForm({ org }: SectionFormProps) {
|
|||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|
||||||
const form = useForm({
|
|
||||||
resolver: zodResolver(
|
|
||||||
GeneralFormSchema.pick({
|
|
||||||
settingsLogRetentionDaysRequest: true,
|
|
||||||
settingsLogRetentionDaysAccess: true,
|
|
||||||
settingsLogRetentionDaysAction: true
|
|
||||||
})
|
|
||||||
),
|
|
||||||
defaultValues: {
|
|
||||||
settingsLogRetentionDaysRequest:
|
|
||||||
org.settingsLogRetentionDaysRequest ?? 15,
|
|
||||||
settingsLogRetentionDaysAccess:
|
|
||||||
org.settingsLogRetentionDaysAccess ?? 15,
|
|
||||||
settingsLogRetentionDaysAction:
|
|
||||||
org.settingsLogRetentionDaysAction ?? 15
|
|
||||||
},
|
|
||||||
mode: "onChange"
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const t = useTranslations();
|
|
||||||
const { isPaidUser, hasSaasSubscription } = usePaidStatus();
|
|
||||||
|
|
||||||
const [, formAction, loadingSave] = useActionState(performSave, null);
|
|
||||||
const api = createApiClient(useEnvContext());
|
|
||||||
|
|
||||||
async function performSave() {
|
|
||||||
const isValid = await form.trigger();
|
|
||||||
if (!isValid) return;
|
|
||||||
|
|
||||||
const data = form.getValues();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const reqData = {
|
|
||||||
settingsLogRetentionDaysRequest:
|
|
||||||
data.settingsLogRetentionDaysRequest,
|
|
||||||
settingsLogRetentionDaysAccess:
|
|
||||||
data.settingsLogRetentionDaysAccess,
|
|
||||||
settingsLogRetentionDaysAction:
|
|
||||||
data.settingsLogRetentionDaysAction
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
// Update organization
|
|
||||||
await api.post(`/org/${org.orgId}`, reqData);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: t("orgUpdated"),
|
|
||||||
description: t("orgUpdatedDescription")
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
} catch (e) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("orgErrorUpdate"),
|
|
||||||
description: formatAxiosError(e, t("orgErrorUpdateMessage"))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>{t("logRetention")}</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t("logRetentionDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<SettingsSectionForm>
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
action={formAction}
|
|
||||||
className="grid gap-4"
|
|
||||||
id="org-log-retention-settings-form"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="settingsLogRetentionDaysRequest"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t("logRetentionRequestLabel")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={field.value.toString()}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
field.onChange(
|
|
||||||
parseInt(value, 10)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectLogRetention"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{LOG_RETENTION_OPTIONS.filter(
|
|
||||||
(option) => {
|
|
||||||
if (
|
|
||||||
hasSaasSubscription &&
|
|
||||||
option.value >
|
|
||||||
30
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
).map((option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={option.value}
|
|
||||||
value={option.value.toString()}
|
|
||||||
>
|
|
||||||
{t(option.label)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{build !== "oss" && (
|
|
||||||
<>
|
|
||||||
<PaidFeaturesAlert />
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="settingsLogRetentionDaysAccess"
|
|
||||||
render={({ field }) => {
|
|
||||||
const isDisabled = !isPaidUser;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t(
|
|
||||||
"logRetentionAccessLabel"
|
|
||||||
)}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={field.value.toString()}
|
|
||||||
onValueChange={(
|
|
||||||
value
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
!isDisabled
|
|
||||||
) {
|
|
||||||
field.onChange(
|
|
||||||
parseInt(
|
|
||||||
value,
|
|
||||||
10
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={
|
|
||||||
isDisabled
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectLogRetention"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{LOG_RETENTION_OPTIONS.map(
|
|
||||||
(
|
|
||||||
option
|
|
||||||
) => (
|
|
||||||
<SelectItem
|
|
||||||
key={
|
|
||||||
option.value
|
|
||||||
}
|
|
||||||
value={option.value.toString()}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
option.label
|
|
||||||
)}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="settingsLogRetentionDaysAction"
|
|
||||||
render={({ field }) => {
|
|
||||||
const isDisabled = !isPaidUser;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t(
|
|
||||||
"logRetentionActionLabel"
|
|
||||||
)}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={field.value.toString()}
|
|
||||||
onValueChange={(
|
|
||||||
value
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
!isDisabled
|
|
||||||
) {
|
|
||||||
field.onChange(
|
|
||||||
parseInt(
|
|
||||||
value,
|
|
||||||
10
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={
|
|
||||||
isDisabled
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectLogRetention"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{LOG_RETENTION_OPTIONS.map(
|
|
||||||
(
|
|
||||||
option
|
|
||||||
) => (
|
|
||||||
<SelectItem
|
|
||||||
key={
|
|
||||||
option.value
|
|
||||||
}
|
|
||||||
value={option.value.toString()}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
option.label
|
|
||||||
)}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</SettingsSectionForm>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 mt-4">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
form="org-log-retention-settings-form"
|
|
||||||
loading={loadingSave}
|
|
||||||
disabled={loadingSave}
|
|
||||||
>
|
|
||||||
{t("saveSettings")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SettingsSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SecuritySettingsSectionForm({ org }: SectionFormProps) {
|
|
||||||
const router = useRouter();
|
|
||||||
const form = useForm({
|
|
||||||
resolver: zodResolver(
|
|
||||||
GeneralFormSchema.pick({
|
|
||||||
requireTwoFactor: true,
|
|
||||||
maxSessionLengthHours: true,
|
|
||||||
passwordExpiryDays: true
|
|
||||||
})
|
|
||||||
),
|
|
||||||
defaultValues: {
|
|
||||||
requireTwoFactor: org.requireTwoFactor || false,
|
|
||||||
maxSessionLengthHours: org.maxSessionLengthHours || null,
|
|
||||||
passwordExpiryDays: org.passwordExpiryDays || null
|
|
||||||
},
|
|
||||||
mode: "onChange"
|
|
||||||
});
|
|
||||||
const t = useTranslations();
|
|
||||||
const { isPaidUser } = usePaidStatus();
|
|
||||||
|
|
||||||
// Track initial security policy values
|
|
||||||
const initialSecurityValues = {
|
|
||||||
requireTwoFactor: org.requireTwoFactor || false,
|
|
||||||
maxSessionLengthHours: org.maxSessionLengthHours || null,
|
|
||||||
passwordExpiryDays: org.passwordExpiryDays || null
|
|
||||||
};
|
|
||||||
|
|
||||||
const [isSecurityPolicyConfirmOpen, setIsSecurityPolicyConfirmOpen] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
// Check if security policies have changed
|
|
||||||
const hasSecurityPolicyChanged = () => {
|
|
||||||
const currentValues = form.getValues();
|
|
||||||
return (
|
|
||||||
currentValues.requireTwoFactor !==
|
|
||||||
initialSecurityValues.requireTwoFactor ||
|
|
||||||
currentValues.maxSessionLengthHours !==
|
|
||||||
initialSecurityValues.maxSessionLengthHours ||
|
|
||||||
currentValues.passwordExpiryDays !==
|
|
||||||
initialSecurityValues.passwordExpiryDays
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [, formAction, loadingSave] = useActionState(onSubmit, null);
|
|
||||||
const api = createApiClient(useEnvContext());
|
|
||||||
|
|
||||||
const formRef = useRef<ComponentRef<"form">>(null);
|
|
||||||
|
|
||||||
async function onSubmit() {
|
|
||||||
// Check if security policies have changed
|
|
||||||
if (hasSecurityPolicyChanged()) {
|
|
||||||
setIsSecurityPolicyConfirmOpen(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await performSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function performSave() {
|
|
||||||
const isValid = await form.trigger();
|
|
||||||
if (!isValid) return;
|
|
||||||
|
|
||||||
const data = form.getValues();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const reqData = {
|
|
||||||
requireTwoFactor: data.requireTwoFactor || false,
|
|
||||||
maxSessionLengthHours: data.maxSessionLengthHours,
|
|
||||||
passwordExpiryDays: data.passwordExpiryDays
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
// Update organization
|
|
||||||
await api.post(`/org/${org.orgId}`, reqData);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: t("orgUpdated"),
|
|
||||||
description: t("orgUpdatedDescription")
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
} catch (e) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: t("orgErrorUpdate"),
|
|
||||||
description: formatAxiosError(e, t("orgErrorUpdateMessage"))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ConfirmDeleteDialog
|
|
||||||
open={isSecurityPolicyConfirmOpen}
|
|
||||||
setOpen={setIsSecurityPolicyConfirmOpen}
|
|
||||||
dialog={
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p>{t("securityPolicyChangeDescription")}</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
buttonText={t("saveSettings")}
|
|
||||||
onConfirm={performSave}
|
|
||||||
string={t("securityPolicyChangeConfirmMessage")}
|
|
||||||
title={t("securityPolicyChangeWarning")}
|
|
||||||
warningText={t("securityPolicyChangeWarningText")}
|
|
||||||
/>
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
{t("securitySettings")}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t("securitySettingsDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<SettingsSectionForm>
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
action={formAction}
|
|
||||||
ref={formRef}
|
|
||||||
id="security-settings-section-form"
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
<PaidFeaturesAlert />
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="requireTwoFactor"
|
|
||||||
render={({ field }) => {
|
|
||||||
const isDisabled = !isPaidUser;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem className="col-span-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<FormControl>
|
|
||||||
<SwitchInput
|
|
||||||
id="require-two-factor"
|
|
||||||
defaultChecked={
|
|
||||||
field.value ||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
label={t(
|
|
||||||
"requireTwoFactorForAllUsers"
|
|
||||||
)}
|
|
||||||
disabled={
|
|
||||||
isDisabled
|
|
||||||
}
|
|
||||||
onCheckedChange={(
|
|
||||||
val
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
!isDisabled
|
|
||||||
) {
|
|
||||||
form.setValue(
|
|
||||||
"requireTwoFactor",
|
|
||||||
val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
<FormMessage />
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"requireTwoFactorDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="maxSessionLengthHours"
|
|
||||||
render={({ field }) => {
|
|
||||||
const isDisabled = !isPaidUser;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem className="col-span-2">
|
|
||||||
<FormLabel>
|
|
||||||
{t("maxSessionLength")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={
|
|
||||||
field.value?.toString() ||
|
|
||||||
"null"
|
|
||||||
}
|
|
||||||
onValueChange={(
|
|
||||||
value
|
|
||||||
) => {
|
|
||||||
if (!isDisabled) {
|
|
||||||
const numValue =
|
|
||||||
value ===
|
|
||||||
"null"
|
|
||||||
? null
|
|
||||||
: parseInt(
|
|
||||||
value,
|
|
||||||
10
|
|
||||||
);
|
|
||||||
form.setValue(
|
|
||||||
"maxSessionLengthHours",
|
|
||||||
numValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isDisabled}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectSessionLength"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{SESSION_LENGTH_OPTIONS.map(
|
|
||||||
(option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={
|
|
||||||
option.value ===
|
|
||||||
null
|
|
||||||
? "null"
|
|
||||||
: option.value.toString()
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
option.value ===
|
|
||||||
null
|
|
||||||
? "null"
|
|
||||||
: option.value.toString()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
option.labelKey
|
|
||||||
)}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"maxSessionLengthDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="passwordExpiryDays"
|
|
||||||
render={({ field }) => {
|
|
||||||
const isDisabled = !isPaidUser;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItem className="col-span-2">
|
|
||||||
<FormLabel>
|
|
||||||
{t("passwordExpiryDays")}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
value={
|
|
||||||
field.value?.toString() ||
|
|
||||||
"null"
|
|
||||||
}
|
|
||||||
onValueChange={(
|
|
||||||
value
|
|
||||||
) => {
|
|
||||||
if (!isDisabled) {
|
|
||||||
const numValue =
|
|
||||||
value ===
|
|
||||||
"null"
|
|
||||||
? null
|
|
||||||
: parseInt(
|
|
||||||
value,
|
|
||||||
10
|
|
||||||
);
|
|
||||||
form.setValue(
|
|
||||||
"passwordExpiryDays",
|
|
||||||
numValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isDisabled}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"selectPasswordExpiry"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{PASSWORD_EXPIRY_OPTIONS.map(
|
|
||||||
(option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={
|
|
||||||
option.value ===
|
|
||||||
null
|
|
||||||
? "null"
|
|
||||||
: option.value.toString()
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
option.value ===
|
|
||||||
null
|
|
||||||
? "null"
|
|
||||||
: option.value.toString()
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
option.labelKey
|
|
||||||
)}
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
{t(
|
|
||||||
"editPasswordExpiryDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</SettingsSectionForm>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 mt-4">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
form="security-settings-section-form"
|
|
||||||
loading={loadingSave}
|
|
||||||
disabled={loadingSave}
|
|
||||||
>
|
|
||||||
{t("saveSettings")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SettingsSection>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
751
src/app/[orgId]/settings/general/security/page.tsx
Normal file
751
src/app/[orgId]/settings/general/security/page.tsx
Normal file
@@ -0,0 +1,751 @@
|
|||||||
|
"use client";
|
||||||
|
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import {
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
useActionState,
|
||||||
|
type ComponentRef
|
||||||
|
} from "react";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { createApiClient } from "@app/lib/api";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import {
|
||||||
|
SettingsContainer,
|
||||||
|
SettingsSection,
|
||||||
|
SettingsSectionHeader,
|
||||||
|
SettingsSectionTitle,
|
||||||
|
SettingsSectionDescription,
|
||||||
|
SettingsSectionBody,
|
||||||
|
SettingsSectionForm
|
||||||
|
} from "@app/components/Settings";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import { SwitchInput } from "@app/components/SwitchInput";
|
||||||
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
import type { OrgContextType } from "@app/contexts/orgContext";
|
||||||
|
|
||||||
|
// Session length options in hours
|
||||||
|
const SESSION_LENGTH_OPTIONS = [
|
||||||
|
{ value: null, labelKey: "unenforced" },
|
||||||
|
{ value: 1, labelKey: "1Hour" },
|
||||||
|
{ value: 3, labelKey: "3Hours" },
|
||||||
|
{ value: 6, labelKey: "6Hours" },
|
||||||
|
{ value: 12, labelKey: "12Hours" },
|
||||||
|
{ value: 24, labelKey: "1DaySession" },
|
||||||
|
{ value: 72, labelKey: "3Days" },
|
||||||
|
{ value: 168, labelKey: "7Days" },
|
||||||
|
{ value: 336, labelKey: "14Days" },
|
||||||
|
{ value: 720, labelKey: "30DaysSession" },
|
||||||
|
{ value: 2160, labelKey: "90DaysSession" },
|
||||||
|
{ value: 4320, labelKey: "180DaysSession" }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Password expiry options in days - will be translated in component
|
||||||
|
const PASSWORD_EXPIRY_OPTIONS = [
|
||||||
|
{ value: null, labelKey: "neverExpire" },
|
||||||
|
{ value: 1, labelKey: "1Day" },
|
||||||
|
{ value: 30, labelKey: "30Days" },
|
||||||
|
{ value: 60, labelKey: "60Days" },
|
||||||
|
{ value: 90, labelKey: "90Days" },
|
||||||
|
{ value: 180, labelKey: "180Days" },
|
||||||
|
{ value: 365, labelKey: "1Year" }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Schema for security organization settings
|
||||||
|
const SecurityFormSchema = z.object({
|
||||||
|
requireTwoFactor: z.boolean().optional(),
|
||||||
|
maxSessionLengthHours: z.number().nullable().optional(),
|
||||||
|
passwordExpiryDays: z.number().nullable().optional(),
|
||||||
|
settingsLogRetentionDaysRequest: z.number(),
|
||||||
|
settingsLogRetentionDaysAccess: z.number(),
|
||||||
|
settingsLogRetentionDaysAction: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
const LOG_RETENTION_OPTIONS = [
|
||||||
|
{ label: "logRetentionDisabled", value: 0 },
|
||||||
|
{ label: "logRetention3Days", value: 3 },
|
||||||
|
{ label: "logRetention7Days", value: 7 },
|
||||||
|
{ label: "logRetention14Days", value: 14 },
|
||||||
|
{ label: "logRetention30Days", value: 30 },
|
||||||
|
{ label: "logRetention90Days", value: 90 },
|
||||||
|
...(build != "saas"
|
||||||
|
? [
|
||||||
|
{ label: "logRetentionForever", value: -1 },
|
||||||
|
{ label: "logRetentionEndOfFollowingYear", value: 9001 }
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
];
|
||||||
|
|
||||||
|
type SectionFormProps = {
|
||||||
|
org: OrgContextType["org"]["org"];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SecurityPage() {
|
||||||
|
const { org } = useOrgContext();
|
||||||
|
return (
|
||||||
|
<SettingsContainer>
|
||||||
|
<LogRetentionSectionForm org={org.org} />
|
||||||
|
{build !== "oss" && <SecuritySettingsSectionForm org={org.org} />}
|
||||||
|
</SettingsContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(
|
||||||
|
SecurityFormSchema.pick({
|
||||||
|
settingsLogRetentionDaysRequest: true,
|
||||||
|
settingsLogRetentionDaysAccess: true,
|
||||||
|
settingsLogRetentionDaysAction: true
|
||||||
|
})
|
||||||
|
),
|
||||||
|
defaultValues: {
|
||||||
|
settingsLogRetentionDaysRequest:
|
||||||
|
org.settingsLogRetentionDaysRequest ?? 15,
|
||||||
|
settingsLogRetentionDaysAccess:
|
||||||
|
org.settingsLogRetentionDaysAccess ?? 15,
|
||||||
|
settingsLogRetentionDaysAction:
|
||||||
|
org.settingsLogRetentionDaysAction ?? 15
|
||||||
|
},
|
||||||
|
mode: "onChange"
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const t = useTranslations();
|
||||||
|
const { isPaidUser, hasSaasSubscription } = usePaidStatus();
|
||||||
|
|
||||||
|
const [, formAction, loadingSave] = useActionState(performSave, null);
|
||||||
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
|
async function performSave() {
|
||||||
|
const isValid = await form.trigger();
|
||||||
|
if (!isValid) return;
|
||||||
|
|
||||||
|
const data = form.getValues();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const reqData = {
|
||||||
|
settingsLogRetentionDaysRequest:
|
||||||
|
data.settingsLogRetentionDaysRequest,
|
||||||
|
settingsLogRetentionDaysAccess:
|
||||||
|
data.settingsLogRetentionDaysAccess,
|
||||||
|
settingsLogRetentionDaysAction:
|
||||||
|
data.settingsLogRetentionDaysAction
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Update organization
|
||||||
|
await api.post(`/org/${org.orgId}`, reqData);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: t("orgUpdated"),
|
||||||
|
description: t("orgUpdatedDescription")
|
||||||
|
});
|
||||||
|
router.refresh();
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("orgErrorUpdate"),
|
||||||
|
description: formatAxiosError(e, t("orgErrorUpdateMessage"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsSection>
|
||||||
|
<SettingsSectionHeader>
|
||||||
|
<SettingsSectionTitle>{t("logRetention")}</SettingsSectionTitle>
|
||||||
|
<SettingsSectionDescription>
|
||||||
|
{t("logRetentionDescription")}
|
||||||
|
</SettingsSectionDescription>
|
||||||
|
</SettingsSectionHeader>
|
||||||
|
<SettingsSectionBody>
|
||||||
|
<SettingsSectionForm>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
action={formAction}
|
||||||
|
className="grid gap-4"
|
||||||
|
id="org-log-retention-settings-form"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="settingsLogRetentionDaysRequest"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("logRetentionRequestLabel")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
field.onChange(
|
||||||
|
parseInt(value, 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"selectLogRetention"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{LOG_RETENTION_OPTIONS.filter(
|
||||||
|
(option) => {
|
||||||
|
if (
|
||||||
|
hasSaasSubscription &&
|
||||||
|
option.value >
|
||||||
|
30
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
).map((option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value.toString()}
|
||||||
|
>
|
||||||
|
{t(option.label)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{build !== "oss" && (
|
||||||
|
<>
|
||||||
|
<PaidFeaturesAlert />
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="settingsLogRetentionDaysAccess"
|
||||||
|
render={({ field }) => {
|
||||||
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"logRetentionAccessLabel"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(
|
||||||
|
value
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!isDisabled
|
||||||
|
) {
|
||||||
|
field.onChange(
|
||||||
|
parseInt(
|
||||||
|
value,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
isDisabled
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"selectLogRetention"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{LOG_RETENTION_OPTIONS.map(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.value
|
||||||
|
}
|
||||||
|
value={option.value.toString()}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
option.label
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="settingsLogRetentionDaysAction"
|
||||||
|
render={({ field }) => {
|
||||||
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"logRetentionActionLabel"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value.toString()}
|
||||||
|
onValueChange={(
|
||||||
|
value
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!isDisabled
|
||||||
|
) {
|
||||||
|
field.onChange(
|
||||||
|
parseInt(
|
||||||
|
value,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
isDisabled
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"selectLogRetention"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{LOG_RETENTION_OPTIONS.map(
|
||||||
|
(
|
||||||
|
option
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.value
|
||||||
|
}
|
||||||
|
value={option.value.toString()}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
option.label
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</SettingsSectionForm>
|
||||||
|
</SettingsSectionBody>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 mt-4">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="org-log-retention-settings-form"
|
||||||
|
loading={loadingSave}
|
||||||
|
disabled={loadingSave}
|
||||||
|
>
|
||||||
|
{t("saveSettings")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SettingsSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SecuritySettingsSectionForm({ org }: SectionFormProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(
|
||||||
|
SecurityFormSchema.pick({
|
||||||
|
requireTwoFactor: true,
|
||||||
|
maxSessionLengthHours: true,
|
||||||
|
passwordExpiryDays: true
|
||||||
|
})
|
||||||
|
),
|
||||||
|
defaultValues: {
|
||||||
|
requireTwoFactor: org.requireTwoFactor || false,
|
||||||
|
maxSessionLengthHours: org.maxSessionLengthHours || null,
|
||||||
|
passwordExpiryDays: org.passwordExpiryDays || null
|
||||||
|
},
|
||||||
|
mode: "onChange"
|
||||||
|
});
|
||||||
|
const t = useTranslations();
|
||||||
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
|
||||||
|
// Track initial security policy values
|
||||||
|
const initialSecurityValues = {
|
||||||
|
requireTwoFactor: org.requireTwoFactor || false,
|
||||||
|
maxSessionLengthHours: org.maxSessionLengthHours || null,
|
||||||
|
passwordExpiryDays: org.passwordExpiryDays || null
|
||||||
|
};
|
||||||
|
|
||||||
|
const [isSecurityPolicyConfirmOpen, setIsSecurityPolicyConfirmOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
// Check if security policies have changed
|
||||||
|
const hasSecurityPolicyChanged = () => {
|
||||||
|
const currentValues = form.getValues();
|
||||||
|
return (
|
||||||
|
currentValues.requireTwoFactor !==
|
||||||
|
initialSecurityValues.requireTwoFactor ||
|
||||||
|
currentValues.maxSessionLengthHours !==
|
||||||
|
initialSecurityValues.maxSessionLengthHours ||
|
||||||
|
currentValues.passwordExpiryDays !==
|
||||||
|
initialSecurityValues.passwordExpiryDays
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [, formAction, loadingSave] = useActionState(onSubmit, null);
|
||||||
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
|
const formRef = useRef<ComponentRef<"form">>(null);
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
// Check if security policies have changed
|
||||||
|
if (hasSecurityPolicyChanged()) {
|
||||||
|
setIsSecurityPolicyConfirmOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await performSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function performSave() {
|
||||||
|
const isValid = await form.trigger();
|
||||||
|
if (!isValid) return;
|
||||||
|
|
||||||
|
const data = form.getValues();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const reqData = {
|
||||||
|
requireTwoFactor: data.requireTwoFactor || false,
|
||||||
|
maxSessionLengthHours: data.maxSessionLengthHours,
|
||||||
|
passwordExpiryDays: data.passwordExpiryDays
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Update organization
|
||||||
|
await api.post(`/org/${org.orgId}`, reqData);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: t("orgUpdated"),
|
||||||
|
description: t("orgUpdatedDescription")
|
||||||
|
});
|
||||||
|
router.refresh();
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("orgErrorUpdate"),
|
||||||
|
description: formatAxiosError(e, t("orgErrorUpdateMessage"))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
open={isSecurityPolicyConfirmOpen}
|
||||||
|
setOpen={setIsSecurityPolicyConfirmOpen}
|
||||||
|
dialog={
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p>{t("securityPolicyChangeDescription")}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
buttonText={t("saveSettings")}
|
||||||
|
onConfirm={performSave}
|
||||||
|
string={t("securityPolicyChangeConfirmMessage")}
|
||||||
|
title={t("securityPolicyChangeWarning")}
|
||||||
|
warningText={t("securityPolicyChangeWarningText")}
|
||||||
|
/>
|
||||||
|
<SettingsSection>
|
||||||
|
<SettingsSectionHeader>
|
||||||
|
<SettingsSectionTitle>
|
||||||
|
{t("securitySettings")}
|
||||||
|
</SettingsSectionTitle>
|
||||||
|
<SettingsSectionDescription>
|
||||||
|
{t("securitySettingsDescription")}
|
||||||
|
</SettingsSectionDescription>
|
||||||
|
</SettingsSectionHeader>
|
||||||
|
<SettingsSectionBody>
|
||||||
|
<SettingsSectionForm>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
action={formAction}
|
||||||
|
ref={formRef}
|
||||||
|
id="security-settings-section-form"
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<PaidFeaturesAlert />
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="requireTwoFactor"
|
||||||
|
render={({ field }) => {
|
||||||
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormControl>
|
||||||
|
<SwitchInput
|
||||||
|
id="require-two-factor"
|
||||||
|
defaultChecked={
|
||||||
|
field.value ||
|
||||||
|
false
|
||||||
|
}
|
||||||
|
label={t(
|
||||||
|
"requireTwoFactorForAllUsers"
|
||||||
|
)}
|
||||||
|
disabled={
|
||||||
|
isDisabled
|
||||||
|
}
|
||||||
|
onCheckedChange={(
|
||||||
|
val
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!isDisabled
|
||||||
|
) {
|
||||||
|
form.setValue(
|
||||||
|
"requireTwoFactor",
|
||||||
|
val
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
<FormDescription>
|
||||||
|
{t(
|
||||||
|
"requireTwoFactorDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="maxSessionLengthHours"
|
||||||
|
render={({ field }) => {
|
||||||
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<FormLabel>
|
||||||
|
{t("maxSessionLength")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
field.value?.toString() ||
|
||||||
|
"null"
|
||||||
|
}
|
||||||
|
onValueChange={(
|
||||||
|
value
|
||||||
|
) => {
|
||||||
|
if (!isDisabled) {
|
||||||
|
const numValue =
|
||||||
|
value ===
|
||||||
|
"null"
|
||||||
|
? null
|
||||||
|
: parseInt(
|
||||||
|
value,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
form.setValue(
|
||||||
|
"maxSessionLengthHours",
|
||||||
|
numValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"selectSessionLength"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{SESSION_LENGTH_OPTIONS.map(
|
||||||
|
(option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.value ===
|
||||||
|
null
|
||||||
|
? "null"
|
||||||
|
: option.value.toString()
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
option.value ===
|
||||||
|
null
|
||||||
|
? "null"
|
||||||
|
: option.value.toString()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
option.labelKey
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
<FormDescription>
|
||||||
|
{t(
|
||||||
|
"maxSessionLengthDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="passwordExpiryDays"
|
||||||
|
render={({ field }) => {
|
||||||
|
const isDisabled = !isPaidUser;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<FormLabel>
|
||||||
|
{t("passwordExpiryDays")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
field.value?.toString() ||
|
||||||
|
"null"
|
||||||
|
}
|
||||||
|
onValueChange={(
|
||||||
|
value
|
||||||
|
) => {
|
||||||
|
if (!isDisabled) {
|
||||||
|
const numValue =
|
||||||
|
value ===
|
||||||
|
"null"
|
||||||
|
? null
|
||||||
|
: parseInt(
|
||||||
|
value,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
form.setValue(
|
||||||
|
"passwordExpiryDays",
|
||||||
|
numValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"selectPasswordExpiry"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{PASSWORD_EXPIRY_OPTIONS.map(
|
||||||
|
(option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
option.value ===
|
||||||
|
null
|
||||||
|
? "null"
|
||||||
|
: option.value.toString()
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
option.value ===
|
||||||
|
null
|
||||||
|
? "null"
|
||||||
|
: option.value.toString()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
option.labelKey
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
{t(
|
||||||
|
"editPasswordExpiryDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</SettingsSectionForm>
|
||||||
|
</SettingsSectionBody>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 mt-4">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="security-settings-section-form"
|
||||||
|
loading={loadingSave}
|
||||||
|
disabled={loadingSave}
|
||||||
|
>
|
||||||
|
{t("saveSettings")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SettingsSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -67,6 +67,7 @@ export default async function ClientResourcesPage(
|
|||||||
destination: siteResource.destination,
|
destination: siteResource.destination,
|
||||||
// destinationPort: siteResource.destinationPort,
|
// destinationPort: siteResource.destinationPort,
|
||||||
alias: siteResource.alias || null,
|
alias: siteResource.alias || null,
|
||||||
|
aliasAddress: siteResource.aliasAddress || null,
|
||||||
siteNiceId: siteResource.siteNiceId,
|
siteNiceId: siteResource.siteNiceId,
|
||||||
niceId: siteResource.niceId,
|
niceId: siteResource.niceId,
|
||||||
tcpPortRangeString: siteResource.tcpPortRangeString || null,
|
tcpPortRangeString: siteResource.tcpPortRangeString || null,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
|||||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||||
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
@@ -154,7 +155,10 @@ export default function ResourceAuthenticationPage() {
|
|||||||
const allUsers = useMemo(() => {
|
const allUsers = useMemo(() => {
|
||||||
return orgUsers.map((user) => ({
|
return orgUsers.map((user) => ({
|
||||||
id: user.id.toString(),
|
id: user.id.toString(),
|
||||||
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
text: `${getUserDisplayName({
|
||||||
|
email: user.email,
|
||||||
|
username: user.username
|
||||||
|
})}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
||||||
}));
|
}));
|
||||||
}, [orgUsers]);
|
}, [orgUsers]);
|
||||||
|
|
||||||
@@ -229,7 +233,10 @@ export default function ResourceAuthenticationPage() {
|
|||||||
"users",
|
"users",
|
||||||
resourceUsers.map((i) => ({
|
resourceUsers.map((i) => ({
|
||||||
id: i.userId.toString(),
|
id: i.userId.toString(),
|
||||||
text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
|
text: `${getUserDisplayName({
|
||||||
|
email: i.email,
|
||||||
|
username: i.username
|
||||||
|
})}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
} from "@app/lib/wireguard";
|
} from "@app/lib/wireguard";
|
||||||
import { QRCodeCanvas } from "qrcode.react";
|
import { QRCodeCanvas } from "qrcode.react";
|
||||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
|
import { NewtSiteInstallCommands } from "@app/components/newt-install-commands";
|
||||||
|
|
||||||
export default function CredentialsPage() {
|
export default function CredentialsPage() {
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
@@ -186,107 +187,119 @@ export default function CredentialsPage() {
|
|||||||
return site?.name || site?.niceId || "My site";
|
return site?.name || site?.niceId || "My site";
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayNewtId = currentNewtId || siteDefaults?.newtId || null;
|
const displayNewtId = currentNewtId ?? siteDefaults?.newtId ?? null;
|
||||||
const displaySecret = regeneratedSecret || null;
|
const displaySecret = regeneratedSecret ?? null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
{site?.type === "newt" && (
|
{site?.type === "newt" && (
|
||||||
<SettingsSection>
|
<>
|
||||||
<SettingsSectionHeader>
|
<SettingsSection>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionHeader>
|
||||||
{t("siteNewtCredentials")}
|
<SettingsSectionTitle>
|
||||||
</SettingsSectionTitle>
|
{t("siteNewtCredentials")}
|
||||||
<SettingsSectionDescription>
|
</SettingsSectionTitle>
|
||||||
{t("siteNewtCredentialsDescription")}
|
<SettingsSectionDescription>
|
||||||
</SettingsSectionDescription>
|
{t("siteNewtCredentialsDescription")}
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionDescription>
|
||||||
|
</SettingsSectionHeader>
|
||||||
|
|
||||||
<PaidFeaturesAlert />
|
<PaidFeaturesAlert />
|
||||||
|
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<InfoSections cols={3}>
|
<InfoSections cols={3}>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("newtEndpoint")}
|
{t("newtEndpoint")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
<CopyToClipboard
|
|
||||||
text={env.app.dashboardUrl}
|
|
||||||
/>
|
|
||||||
</InfoSectionContent>
|
|
||||||
</InfoSection>
|
|
||||||
<InfoSection>
|
|
||||||
<InfoSectionTitle>
|
|
||||||
{t("newtId")}
|
|
||||||
</InfoSectionTitle>
|
|
||||||
<InfoSectionContent>
|
|
||||||
{displayNewtId ? (
|
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
text={displayNewtId}
|
text={env.app.dashboardUrl}
|
||||||
/>
|
/>
|
||||||
) : (
|
</InfoSectionContent>
|
||||||
<span>{"••••••••••••••••"}</span>
|
</InfoSection>
|
||||||
)}
|
<InfoSection>
|
||||||
</InfoSectionContent>
|
<InfoSectionTitle>
|
||||||
</InfoSection>
|
{t("newtId")}
|
||||||
<InfoSection>
|
</InfoSectionTitle>
|
||||||
<InfoSectionTitle>
|
<InfoSectionContent>
|
||||||
{t("newtSecretKey")}
|
{displayNewtId ? (
|
||||||
</InfoSectionTitle>
|
<CopyToClipboard
|
||||||
<InfoSectionContent>
|
text={displayNewtId}
|
||||||
{displaySecret ? (
|
/>
|
||||||
<CopyToClipboard
|
) : (
|
||||||
text={displaySecret}
|
<span>
|
||||||
/>
|
{"••••••••••••••••"}
|
||||||
) : (
|
</span>
|
||||||
<span>
|
)}
|
||||||
{
|
</InfoSectionContent>
|
||||||
"••••••••••••••••••••••••••••••••"
|
</InfoSection>
|
||||||
}
|
<InfoSection>
|
||||||
</span>
|
<InfoSectionTitle>
|
||||||
)}
|
{t("newtSecretKey")}
|
||||||
</InfoSectionContent>
|
</InfoSectionTitle>
|
||||||
</InfoSection>
|
<InfoSectionContent>
|
||||||
</InfoSections>
|
{displaySecret ? (
|
||||||
|
<CopyToClipboard
|
||||||
|
text={displaySecret}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
"••••••••••••••••••••••••••••••••"
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
</InfoSections>
|
||||||
|
|
||||||
{showCredentialsAlert && displaySecret && (
|
{showCredentialsAlert && displaySecret && (
|
||||||
<Alert variant="neutral" className="mt-4">
|
<Alert variant="neutral" className="mt-4">
|
||||||
<InfoIcon className="h-4 w-4" />
|
<InfoIcon className="h-4 w-4" />
|
||||||
<AlertTitle className="font-semibold">
|
<AlertTitle className="font-semibold">
|
||||||
{t("siteCredentialsSave")}
|
{t("siteCredentialsSave")}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
{t("siteCredentialsSaveDescription")}
|
{t(
|
||||||
</AlertDescription>
|
"siteCredentialsSaveDescription"
|
||||||
</Alert>
|
)}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</SettingsSectionBody>
|
||||||
|
{build !== "oss" && (
|
||||||
|
<SettingsSectionFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setShouldDisconnect(false);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
disabled={isSecurityFeatureDisabled()}
|
||||||
|
>
|
||||||
|
{t("regenerateCredentialsButton")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setShouldDisconnect(true);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
disabled={isSecurityFeatureDisabled()}
|
||||||
|
>
|
||||||
|
{t("siteRegenerateAndDisconnect")}
|
||||||
|
</Button>
|
||||||
|
</SettingsSectionFooter>
|
||||||
)}
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSection>
|
||||||
{build !== "oss" && (
|
|
||||||
<SettingsSectionFooter>
|
<NewtSiteInstallCommands
|
||||||
<Button
|
id={displayNewtId ?? "**********"}
|
||||||
variant="outline"
|
secret={displaySecret ?? "**************"}
|
||||||
onClick={() => {
|
endpoint={env.app.dashboardUrl}
|
||||||
setShouldDisconnect(false);
|
/>
|
||||||
setModalOpen(true);
|
</>
|
||||||
}}
|
|
||||||
disabled={isSecurityFeatureDisabled()}
|
|
||||||
>
|
|
||||||
{t("regenerateCredentialsButton")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setShouldDisconnect(true);
|
|
||||||
setModalOpen(true);
|
|
||||||
}}
|
|
||||||
disabled={isSecurityFeatureDisabled()}
|
|
||||||
>
|
|
||||||
{t("siteRegenerateAndDisconnect")}
|
|
||||||
</Button>
|
|
||||||
</SettingsSectionFooter>
|
|
||||||
)}
|
|
||||||
</SettingsSection>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{site?.type === "wireguard" && (
|
{site?.type === "wireguard" && (
|
||||||
@@ -306,7 +319,7 @@ export default function CredentialsPage() {
|
|||||||
{!loadingDefaults && (
|
{!loadingDefaults && (
|
||||||
<>
|
<>
|
||||||
{wgConfig ? (
|
{wgConfig ? (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-col sm:flex-row items-center gap-4">
|
||||||
<CopyTextBox
|
<CopyTextBox
|
||||||
text={wgConfig}
|
text={wgConfig}
|
||||||
outline={true}
|
outline={true}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user