diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index 416a0013b..346b08e4c 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -206,6 +206,9 @@ import { TProjectEnvironments, TProjectEnvironmentsInsert, TProjectEnvironmentsUpdate, + TProjectGateways, + TProjectGatewaysInsert, + TProjectGatewaysUpdate, TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate, @@ -937,6 +940,11 @@ declare module "knex/types/tables" { TKmipClientCertificatesUpdate >; [TableName.Gateway]: KnexOriginal.CompositeTableType; + [TableName.ProjectGateway]: KnexOriginal.CompositeTableType< + TProjectGateways, + TProjectGatewaysInsert, + TProjectGatewaysUpdate + >; [TableName.OrgGatewayConfig]: KnexOriginal.CompositeTableType< TOrgGatewayConfig, TOrgGatewayConfigInsert, diff --git a/backend/src/db/migrations/20250212191958_create-gateway.ts b/backend/src/db/migrations/20250212191958_create-gateway.ts index 3d9f37101..3aa7e11f1 100644 --- a/backend/src/db/migrations/20250212191958_create-gateway.ts +++ b/backend/src/db/migrations/20250212191958_create-gateway.ts @@ -68,13 +68,29 @@ export async function up(knex: Knex): Promise { await createOnUpdateTrigger(knex, TableName.Gateway); } + if (!(await knex.schema.hasTable(TableName.ProjectGateway))) { + await knex.schema.createTable(TableName.ProjectGateway, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + t.string("projectId").notNullable(); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + + t.uuid("gatewayId").notNullable(); + t.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("CASCADE"); + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.ProjectGateway); + } + if (await knex.schema.hasTable(TableName.DynamicSecret)) { const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId"); await knex.schema.alterTable(TableName.DynamicSecret, (t) => { // not setting a foreign constraint so that cascade effects are not triggered if (!doesGatewayColExist) { - t.uuid("gatewayId"); - t.foreign("gatewayId").references("id").inTable(TableName.Identity); + t.uuid("projectGatewayId"); + t.foreign("projectGatewayId").references("id").inTable(TableName.ProjectGateway); } }); } diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index b60683e85..913a6d475 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -27,7 +27,7 @@ export const DynamicSecretsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), encryptedInput: zodBuffer, - gatewayId: z.string().uuid().nullable().optional() + projectGatewayId: z.string().uuid().nullable().optional() }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index b4d4138f8..92fc47c23 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -67,6 +67,7 @@ export * from "./pki-collection-items"; export * from "./pki-collections"; export * from "./project-bots"; export * from "./project-environments"; +export * from "./project-gateways"; export * from "./project-keys"; export * from "./project-memberships"; export * from "./project-roles"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 7d7f2fe4e..e626443c3 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -116,6 +116,7 @@ export enum TableName { // Gateway OrgGatewayConfig = "org_gateway_config", Gateway = "gateways", + ProjectGateway = "project_gateways", // junction tables with tags SecretV2JnTag = "secret_v2_tag_junction", JnSecretTag = "secret_tag_junction", diff --git a/backend/src/db/schemas/project-gateways.ts b/backend/src/db/schemas/project-gateways.ts new file mode 100644 index 000000000..f4b572661 --- /dev/null +++ b/backend/src/db/schemas/project-gateways.ts @@ -0,0 +1,20 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const ProjectGatewaysSchema = z.object({ + id: z.string().uuid(), + projectId: z.string(), + gatewayId: z.string().uuid(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TProjectGateways = z.infer; +export type TProjectGatewaysInsert = Omit, TImmutableDBKeys>; +export type TProjectGatewaysUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/ee/routes/v1/gateway-router.ts b/backend/src/ee/routes/v1/gateway-router.ts index edb887e2d..ea3e1f6d6 100644 --- a/backend/src/ee/routes/v1/gateway-router.ts +++ b/backend/src/ee/routes/v1/gateway-router.ts @@ -17,6 +17,7 @@ const SanitizedGatewaySchema = GatewaysSchema.pick({ serialNumber: true, heartbeat: true }); + const isValidRelayAddress = (relayAddress: string) => { const [ip, port] = relayAddress.split(":"); return isValidIp(ip) && Number(port) <= 65535 && Number(port) >= 40000; @@ -120,21 +121,20 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { identity: z.object({ name: z.string(), id: z.string() - }) + }), + projects: z + .object({ + name: z.string(), + id: z.string(), + slug: z.string() + }) + .array() }).array() }) } }, onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]), handler: async (req) => { - if (req.query.projectId) { - const gateways = await server.services.gateway.getProjectGateways({ - projectId: req.query.projectId, - projectPermission: req.permission - }); - return { gateways }; - } - const gateways = await server.services.gateway.listGateways({ orgPermission: req.permission }); @@ -142,6 +142,38 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "GET", + url: "/projects/:projectId", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + projectId: z.string() + }), + response: { + 200: z.object({ + gateways: SanitizedGatewaySchema.extend({ + identity: z.object({ + name: z.string(), + id: z.string() + }), + projectGatewayId: z.string() + }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]), + handler: async (req) => { + const gateways = await server.services.gateway.getProjectGateways({ + projectId: req.params.projectId, + projectPermission: req.permission + }); + return { gateways }; + } + }); + server.route({ method: "GET", url: "/:id", @@ -184,7 +216,8 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { id: z.string() }), body: z.object({ - name: slugSchema({ field: "name" }).optional() + name: slugSchema({ field: "name" }).optional(), + projectIds: z.string().array().optional() }), response: { 200: z.object({ @@ -197,7 +230,8 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => { const gateway = await server.services.gateway.updateGatewayById({ orgPermission: req.permission, id: req.params.id, - name: req.body.name + name: req.body.name, + projectIds: req.body.projectIds }); return { gateway }; } diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index 5579bc9af..25ea21024 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -16,8 +16,7 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal"; import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue"; -import { TGatewayDALFactory } from "../gateway/gateway-dal"; -import { TOrgGatewayConfigDALFactory } from "../gateway/org-gateway-config-dal"; +import { TProjectGatewayDALFactory } from "../gateway/project-gateway-dal"; import { TDynamicSecretDALFactory } from "./dynamic-secret-dal"; import { DynamicSecretStatus, @@ -46,8 +45,7 @@ type TDynamicSecretServiceFactoryDep = { projectDAL: Pick; permissionService: Pick; kmsService: Pick; - gatewayDAL: Pick; - orgGatewayConfigDAL: Pick; + projectGatewayDAL: Pick; }; export type TDynamicSecretServiceFactory = ReturnType; @@ -62,8 +60,7 @@ export const dynamicSecretServiceFactory = ({ dynamicSecretQueueService, projectDAL, kmsService, - gatewayDAL, - orgGatewayConfigDAL + projectGatewayDAL }: TDynamicSecretServiceFactoryDep) => { const create = async ({ path, @@ -115,20 +112,15 @@ export const dynamicSecretServiceFactory = ({ const inputs = await selectedProvider.validateProviderInputs(provider.inputs); let selectedGatewayId: string | null = null; - if (inputs && typeof inputs === "object" && "gatewayId" in inputs && inputs.gatewayId) { - const gatewayId = inputs.gatewayId as string; + if (inputs && typeof inputs === "object" && "projectGatewayId" in inputs && inputs.projectGatewayId) { + const projectGatewayId = inputs.projectGatewayId as string; - const orgGateway = await orgGatewayConfigDAL.findOne({ orgId: actorOrgId }); - if (!orgGateway) + const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId }); + if (!projectGateway) throw new NotFoundError({ - message: `Gateway with ${gatewayId} not found` + message: `Project gateway with ${projectGatewayId} not found` }); - const gateway = await gatewayDAL.findOne({ id: gatewayId, orgGatewayRootCaId: orgGateway.id }); - if (!gateway) - throw new NotFoundError({ - message: `Gateway with ${gatewayId} not found` - }); - selectedGatewayId = gateway.id; + selectedGatewayId = projectGateway.id; } const isConnected = await selectedProvider.validateConnection(provider.inputs); @@ -147,7 +139,7 @@ export const dynamicSecretServiceFactory = ({ defaultTTL, folderId: folder.id, name, - gatewayId: selectedGatewayId + projectGatewayId: selectedGatewayId }); return dynamicSecretCfg; }; @@ -220,20 +212,20 @@ export const dynamicSecretServiceFactory = ({ const updatedInput = await selectedProvider.validateProviderInputs(newInput); let selectedGatewayId: string | null = null; - if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) { - const gatewayId = updatedInput.gatewayId as string; + if ( + updatedInput && + typeof updatedInput === "object" && + "projectGatewayId" in updatedInput && + updatedInput?.projectGatewayId + ) { + const projectGatewayId = updatedInput.projectGatewayId as string; - const orgGateway = await orgGatewayConfigDAL.findOne({ orgId: actorOrgId }); - if (!orgGateway) + const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId }); + if (!projectGateway) throw new NotFoundError({ - message: `Gateway with ${gatewayId} not found` + message: `Project gateway with ${projectGatewayId} not found` }); - const gateway = await gatewayDAL.findOne({ id: gatewayId, orgGatewayRootCaId: orgGateway.id }); - if (!gateway) - throw new NotFoundError({ - message: `Gateway with ${gatewayId} not found` - }); - selectedGatewayId = gateway.id; + selectedGatewayId = projectGateway.id; } const isConnected = await selectedProvider.validateConnection(newInput); @@ -246,7 +238,7 @@ export const dynamicSecretServiceFactory = ({ name: newName ?? name, status: null, statusDetails: null, - gatewayId: selectedGatewayId + projectGatewayId: selectedGatewayId }); return updatedDynamicCfg; diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index 8b742dc54..621c3c631 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -104,7 +104,7 @@ export const DynamicSecretSqlDBSchema = z.object({ revocationStatement: z.string().trim(), renewStatement: z.string().trim().optional(), ca: z.string().optional(), - gatewayId: z.string().nullable().optional() + projectGatewayId: z.string().nullable().optional() }); export const DynamicSecretCassandraSchema = z.object({ diff --git a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts index 53b16e026..7513109a8 100644 --- a/backend/src/ee/services/dynamic-secret/providers/sql-database.ts +++ b/backend/src/ee/services/dynamic-secret/providers/sql-database.ts @@ -34,7 +34,7 @@ type TSqlDatabaseProviderDTO = { export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => { const validateProviderInputs = async (inputs: unknown) => { const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs); - verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.gatewayId)); + verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.projectGatewayId)); return providerInputs; }; @@ -71,7 +71,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) providerInputs: z.infer, gatewayCallback: (port: number) => Promise ) => { - const relayDetails = await gatewayService.fnGetGatewayClientTls(providerInputs.gatewayId as string); + const relayDetails = await gatewayService.fnGetGatewayClientTls(providerInputs.projectGatewayId as string); const [relayHost, relayPort] = relayDetails.relayAddress.split(":"); await withGatewayProxy( async (port) => { @@ -105,7 +105,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); }; - if (providerInputs.gatewayId) { + if (providerInputs.projectGatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); @@ -138,7 +138,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) }); await db.destroy(); }; - if (providerInputs.gatewayId) { + if (providerInputs.projectGatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); @@ -163,7 +163,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); }; - if (providerInputs.gatewayId) { + if (providerInputs.projectGatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); @@ -198,7 +198,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO) await db.destroy(); }; - if (providerInputs.gatewayId) { + if (providerInputs.projectGatewayId) { await gatewayProxyWrapper(providerInputs, gatewayCallback); } else { await gatewayCallback(); diff --git a/backend/src/ee/services/gateway/gateway-dal.ts b/backend/src/ee/services/gateway/gateway-dal.ts index 5d0be06bf..fbf5558e4 100644 --- a/backend/src/ee/services/gateway/gateway-dal.ts +++ b/backend/src/ee/services/gateway/gateway-dal.ts @@ -1,9 +1,16 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName, TGateways } from "@app/db/schemas"; +import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex"; +import { + buildFindFilter, + ormify, + selectAllTableCols, + sqlNestRelationships, + TFindFilter, + TFindOpt +} from "@app/lib/knex"; export type TGatewayDALFactory = ReturnType; @@ -16,8 +23,15 @@ export const gatewayDALFactory = (db: TDbClient) => { // eslint-disable-next-line @typescript-eslint/no-misused-promises .where(buildFindFilter(filter)) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`) + .leftJoin(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`) + .leftJoin(TableName.Project, `${TableName.Project}.id`, `${TableName.ProjectGateway}.projectId`) .select(selectAllTableCols(TableName.Gateway)) - .select(db.ref("name").withSchema(TableName.Identity).as("identityName")); + .select( + db.ref("name").withSchema(TableName.Identity).as("identityName"), + db.ref("name").withSchema(TableName.Project).as("projectName"), + db.ref("slug").withSchema(TableName.Project).as("projectSlug"), + db.ref("id").withSchema(TableName.Project).as("projectId") + ); if (limit) void query.limit(limit); if (offset) void query.offset(offset); if (sort) { @@ -25,7 +39,25 @@ export const gatewayDALFactory = (db: TDbClient) => { } const docs = await query; - return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } })); + return sqlNestRelationships({ + data: docs, + key: "id", + parentMapper: (data) => ({ + ...GatewaysSchema.parse(data), + identity: { id: data.identityId, name: data.identityName } + }), + childrenMapper: [ + { + key: "projectId", + label: "projects" as const, + mapper: ({ projectId, projectName, projectSlug }) => ({ + id: projectId, + name: projectName, + slug: projectSlug + }) + } + ] + }); } catch (error) { throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` }); } @@ -35,14 +67,14 @@ export const gatewayDALFactory = (db: TDbClient) => { try { const query = (tx || db)(TableName.Gateway) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`) - .join( - TableName.IdentityProjectMembership, - `${TableName.Identity}.id`, - `${TableName.IdentityProjectMembership}.identityId` - ) + .join(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`) .select(selectAllTableCols(TableName.Gateway)) - .select(db.ref("name").withSchema(TableName.Identity).as("identityName")) - .where({ [`${TableName.IdentityProjectMembership}.projectId` as "projectId"]: projectId }); + .select( + db.ref("name").withSchema(TableName.Identity).as("identityName"), + db.ref("id").withSchema(TableName.ProjectGateway).as("projectGatewayId") + ) + .where({ [`${TableName.ProjectGateway}.projectId` as "projectId"]: projectId }); + const docs = await query; return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } })); } catch (error) { diff --git a/backend/src/ee/services/gateway/gateway-service.ts b/backend/src/ee/services/gateway/gateway-service.ts index d370035e4..7b800331b 100644 --- a/backend/src/ee/services/gateway/gateway-service.ts +++ b/backend/src/ee/services/gateway/gateway-service.ts @@ -1,6 +1,6 @@ import crypto from "node:crypto"; -import { ForbiddenError, subject } from "@casl/ability"; +import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; import { z } from "zod"; @@ -23,7 +23,6 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission"; import { TPermissionServiceFactory } from "../permission/permission-service"; -import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; import { TGatewayDALFactory } from "./gateway-dal"; import { TExchangeAllocatedRelayAddressDTO, @@ -34,9 +33,11 @@ import { TUpdateGatewayByIdDTO } from "./gateway-types"; import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal"; +import { TProjectGatewayDALFactory } from "./project-gateway-dal"; type TGatewayServiceFactoryDep = { gatewayDAL: TGatewayDALFactory; + projectGatewayDAL: TProjectGatewayDALFactory; orgGatewayConfigDAL: Pick; licenseService: Pick; kmsService: Pick; @@ -56,7 +57,8 @@ export const gatewayServiceFactory = ({ kmsService, permissionService, orgGatewayConfigDAL, - keyStore + keyStore, + projectGatewayDAL }: TGatewayServiceFactoryDep) => { const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => { if (!licenseService.onPremFeatures.gateway) { @@ -410,7 +412,7 @@ export const gatewayServiceFactory = ({ }).cipherTextBlob, identityId, orgGatewayRootCaId: orgGatewayConfig.id, - name: `gateway-${alphaNumericNanoId(6)}` + name: `gateway-${alphaNumericNanoId(6).toLowerCase()}` }); }); @@ -520,7 +522,7 @@ export const gatewayServiceFactory = ({ return gateway; }; - const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => { + const updateGatewayById = async ({ orgPermission, id, name, projectIds }: TUpdateGatewayByIdDTO) => { const { permission } = await permissionService.getOrgPermission( orgPermission.type, orgPermission.id, @@ -537,6 +539,16 @@ export const gatewayServiceFactory = ({ const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); + if (projectIds) { + await projectGatewayDAL.transaction(async (tx) => { + await projectGatewayDAL.delete({ gatewayId: gateway.id }, tx); + await projectGatewayDAL.insertMany( + projectIds.map((el) => ({ gatewayId: gateway.id, projectId: el })), + tx + ); + }); + } + return gateway; }; @@ -561,7 +573,7 @@ export const gatewayServiceFactory = ({ }; const getProjectGateways = async ({ projectId, projectPermission }: TGetProjectGatewayByIdDTO) => { - const { permission } = await permissionService.getProjectPermission({ + await permissionService.getProjectPermission({ projectId, actor: projectPermission.type, actorId: projectPermission.id, @@ -571,18 +583,16 @@ export const gatewayServiceFactory = ({ }); const gateways = await gatewayDAL.findByProjectId(projectId); - const allowedGateways = gateways.filter((el) => - permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Identity, { identityId: el.identityId }) - ) - ); - return allowedGateways; + return gateways; }; // this has no permission check and used for dynamic secrets directly // assumes permission check is already done - const fnGetGatewayClientTls = async (gatewayId: string) => { + const fnGetGatewayClientTls = async (projectGatewayId: string) => { + const projectGateway = await projectGatewayDAL.findById(projectGatewayId); + if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` }); + + const { gatewayId } = projectGateway; const gateway = await gatewayDAL.findById(gatewayId); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` }); diff --git a/backend/src/ee/services/gateway/gateway-types.ts b/backend/src/ee/services/gateway/gateway-types.ts index 823028154..220dc7147 100644 --- a/backend/src/ee/services/gateway/gateway-types.ts +++ b/backend/src/ee/services/gateway/gateway-types.ts @@ -20,6 +20,7 @@ export type TGetGatewayByIdDTO = { export type TUpdateGatewayByIdDTO = { id: string; name?: string; + projectIds?: string[]; orgPermission: OrgServiceActor; }; diff --git a/backend/src/ee/services/gateway/project-gateway-dal.ts b/backend/src/ee/services/gateway/project-gateway-dal.ts new file mode 100644 index 000000000..44c36f5f6 --- /dev/null +++ b/backend/src/ee/services/gateway/project-gateway-dal.ts @@ -0,0 +1,10 @@ +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { ormify } from "@app/lib/knex"; + +export type TProjectGatewayDALFactory = ReturnType; + +export const projectGatewayDALFactory = (db: TDbClient) => { + const orm = ormify(db, TableName.ProjectGateway); + return orm; +}; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 9b8d1a36a..c1ab5d149 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -30,6 +30,7 @@ import { externalKmsServiceFactory } from "@app/ee/services/external-kms/externa import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; +import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal"; import { groupDALFactory } from "@app/ee/services/group/group-dal"; import { groupServiceFactory } from "@app/ee/services/group/group-service"; import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; @@ -398,6 +399,7 @@ export const registerRoutes = async ( const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db); const gatewayDAL = gatewayDALFactory(db); + const projectGatewayDAL = projectGatewayDALFactory(db); const permissionService = permissionServiceFactory({ permissionDAL, @@ -1312,7 +1314,8 @@ export const registerRoutes = async ( kmsService, licenseService, orgGatewayConfigDAL, - keyStore + keyStore, + projectGatewayDAL }); const dynamicSecretProviders = buildDynamicSecretProviders({ @@ -1336,8 +1339,7 @@ export const registerRoutes = async ( permissionService, licenseService, kmsService, - gatewayDAL, - orgGatewayConfigDAL + projectGatewayDAL }); const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({ diff --git a/docs/documentation/platform/gateways/overview.mdx b/docs/documentation/platform/gateways/overview.mdx index 304fb87db..a64575d56 100644 --- a/docs/documentation/platform/gateways/overview.mdx +++ b/docs/documentation/platform/gateways/overview.mdx @@ -52,12 +52,32 @@ You can easily deploy a gateway using the Infisical CLI by following these steps - - 1. Check the gateway log to have **Gateway started successfully** - - 2. Navigate to **Gateways** list page in Org Access Control - ![list-gateway](../../../images/platform/gateways/gateway-list.png) - + + 1. Check gateway deployment status: + - Look for the message "**Gateway started successfully**" in the gateway logs + - This confirms your gateway is running correctly + + 2. Verify gateway registration: + - Open your Infisical dashboard + - Navigate to **Organization Access Control** + - Select the **Gateways** tab + - Your newly deployed gateway should appear in the list + ![Gateway list in Organization Access Control](../../../images/platform/gateways/gateway-list.png) + + + + 1. Access gateway settings: + - Find your gateway in the list + - Click the options menu (⋮) + - Select **Edit Details** + ![Edit gateway option](../../../images/platform/gateways/edit-gateway.png) + + 2. Configure project access: + - In the edit modal, you'll see a list of available projects + - Select the projects you want to grant gateway access to + - Click Save to apply your changes + ![Project assignment modal](../../../images/platform/gateways/assign-project.png) + ## Using Your Gateway diff --git a/docs/images/platform/gateways/assign-project.png b/docs/images/platform/gateways/assign-project.png new file mode 100644 index 000000000..a1ff61909 Binary files /dev/null and b/docs/images/platform/gateways/assign-project.png differ diff --git a/docs/images/platform/gateways/edit-gateway.png b/docs/images/platform/gateways/edit-gateway.png new file mode 100644 index 000000000..04ef2a7d2 Binary files /dev/null and b/docs/images/platform/gateways/edit-gateway.png differ diff --git a/frontend/public/lotties/gateway.json b/frontend/public/lotties/gateway.json new file mode 100644 index 000000000..d444658a4 --- /dev/null +++ b/frontend/public/lotties/gateway.json @@ -0,0 +1 @@ +{"v":"5.7.5","fr":100,"ip":0,"op":667,"w":200,"h":200,"nm":"Comp 1","ddd":0,"metadata":{},"assets":[],"layers":[{"ddd":0,"ind":12345679,"ty":4,"nm":"Group Layer 8","sr":1,"ks":{"p":{"a":0,"k":[146,185.75409836065575,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[20.491803278688526,20.491803278688526,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[220.741,37.184],[225.501,35.896],[228.749,32.36800000000001],[229.981,27.216000000000008],[228.749,22.12],[225.501,18.592],[220.741,17.304],[215.981,18.592],[212.677,22.12],[211.501,27.216000000000008],[212.677,32.36800000000001],[215.981,35.896],[220.741,37.184],[220.741,37.184],[220.741,37.184]],"i":[[0,0],[-1.380999999999972,0.8586999999999989],[-0.7839999999999918,1.493299999999991],[0,1.903999999999996],[0.8220000000000027,1.493299999999991],[1.382000000000062,0.8586999999999989],[1.79200000000003,0],[1.418999999999983,-0.8586999999999989],[0.8220000000000027,-1.493300000000005],[0,-1.904000000000011],[-0.7839999999999918,-1.5307000000000102],[-1.380999999999972,-0.8586999999999989],[-1.754000000000019,0],[0,0],[0,0]],"o":[[1.79200000000003,0],[1.382000000000062,-0.8586999999999989],[0.8220000000000027,-1.5307000000000102],[0,-1.904000000000011],[-0.7839999999999918,-1.493300000000005],[-1.380999999999972,-0.8586999999999989],[-1.754000000000019,0],[-1.380999999999972,0.8586999999999989],[-0.7839999999999918,1.493299999999991],[0,1.903999999999996],[0.8220000000000027,1.493299999999991],[1.418999999999983,0.8586999999999989],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[221.357,43.06400000000001],[214.917,41.608],[210.49300000000005,37.408],[211.221,36.232],[211.221,42.392],[205.173,42.392],[205.173,0],[211.501,0],[211.501,18.36800000000001],[210.49300000000005,16.912000000000006],[214.973,12.88],[221.357,11.424000000000007],[229.085,13.49600000000001],[234.51700000000005,19.152],[236.533,27.216000000000008],[234.51700000000005,35.28],[229.141,40.992],[221.357,43.06400000000001],[221.357,43.06400000000001],[221.357,43.06400000000001]],"i":[[0,0],[1.942000000000007,0.9706999999999937],[1.045999999999935,1.829300000000003],[-0.2426666666666506,0.3919999999999959],[0,-2.053333333333327],[2.015999999999963,0],[0,14.13066666666667],[-2.109333333333325,0],[0,-6.122666666666674],[0.3360000000000127,0.4853333333333296],[-1.865999999999985,0.9707000000000079],[-2.38900000000001,0],[-2.277000000000044,-1.3813000000000102],[-1.30600000000004,-2.389300000000006],[0,-2.986699999999999],[1.343999999999937,-2.389300000000006],[2.27800000000002,-1.4187000000000012],[2.912000000000035,0],[0,0],[0,0]],"o":[[-2.351999999999975,0],[-1.903999999999996,-0.9707000000000079],[0.2426666666666506,-0.3919999999999959],[0,2.053333333333327],[-2.015999999999963,0],[0,-14.13066666666667],[2.109333333333325,0],[0,6.122666666666667],[-0.3360000000000127,-0.4853333333333296],[1.120000000000005,-1.717300000000009],[1.867000000000075,-0.9706999999999937],[2.875,0],[2.314999999999941,1.381299999999996],[1.343999999999937,2.389300000000006],[0,2.986699999999999],[-1.30600000000004,2.389300000000006],[-2.27699999999993,1.381299999999996],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[181.87,43.06400000000001],[176.438,42],[172.854,38.976],[171.566,34.384],[172.63,29.960000000000008],[176.046,26.656000000000006],[181.814,24.752],[192.342,23.016000000000005],[192.342,28],[183.046,29.624],[179.35,31.248],[178.174,34.16],[179.462,37.016000000000005],[182.878,38.08],[187.35799999999995,36.96000000000001],[190.38199999999995,33.992],[191.446,29.792],[191.446,22.008],[189.766,18.36800000000001],[185.398,16.912000000000006],[180.974,18.256],[178.23,21.616],[172.966,18.98400000000001],[175.71,15.064000000000007],[180.134,12.376],[185.566,11.424000000000007],[191.894,12.768],[196.206,16.52],[197.774,22.008],[197.774,42.392],[191.726,42.392],[191.726,36.904],[193.014,37.072],[190.27,40.264],[186.518,42.336],[181.87,43.06400000000001],[181.87,43.06400000000001],[181.87,43.06400000000001]],"i":[[0,0],[1.567999999999984,0.7092999999999989],[0.8589999999999236,1.2693000000000012],[0,1.7547],[-0.7089999999999463,1.306699999999992],[-1.530000000000086,0.8960000000000008],[-2.313999999999965,0.3733000000000004],[-3.509333333333302,0.5786666666666633],[0,-1.661333333333332],[3.098666666666645,-0.541333333333327],[0.7839999999999918,-0.784000000000006],[0,-1.194699999999997],[-0.8579999999999472,-0.7467000000000041],[-1.381000000000085,0],[-1.268999999999892,0.7466999999999899],[-0.7089999999999463,1.2319999999999993],[0,1.530699999999996],[0,2.594666666666669],[1.120000000000005,0.9332999999999885],[1.829999999999927,0],[1.269999999999982,-0.8960000000000008],[0.5979999999999563,-1.381299999999996],[1.754666666666708,0.8773333333333255],[-1.269000000000005,1.11999999999999],[-1.680000000000064,0.6346999999999952],[-1.903999999999996,0],[-1.828999999999951,-0.8960000000000008],[-1.008000000000038,-1.6053],[0,-2.090699999999998],[0,-6.794666666666672],[2.015999999999963,0],[0,1.829333333333338],[-0.4293333333333749,-0.05599999999999739],[1.120000000000005,-0.8959999999999866],[1.418999999999983,-0.4852999999999952],[1.717999999999961,0],[0,0],[0,0]],"o":[[-2.052999999999997,0],[-1.529999999999973,-0.7467000000000041],[-0.8580000000000609,-1.306699999999992],[0,-1.642700000000005],[0.7469999999999573,-1.306700000000006],[1.530999999999949,-0.8960000000000008],[3.509333333333302,-0.5786666666666633],[0,1.661333333333332],[-3.098666666666645,0.541333333333327],[-1.680000000000064,0.2987000000000108],[-0.7839999999999918,0.7467000000000041],[0,1.157300000000006],[0.8959999999999582,0.7092999999999989],[1.717999999999961,0],[1.307000000000016,-0.7467000000000041],[0.7100000000000364,-1.2693000000000012],[0,-2.594666666666669],[0,-1.493299999999991],[-1.081999999999994,-0.9707000000000079],[-1.680000000000064,0],[-1.232000000000085,0.8586999999999989],[-1.754666666666708,-0.8773333333333255],[0.5599999999999454,-1.493300000000005],[1.269999999999982,-1.157300000000006],[1.717999999999961,-0.6347000000000094],[2.389999999999986,0],[1.866999999999962,0.8960000000000008],[1.045999999999935,1.568000000000012],[0,6.794666666666672],[-2.015999999999963,0],[0,-1.829333333333338],[0.4293333333333749,0.05599999999999739],[-0.70900000000006,1.2319999999999993],[-1.081999999999994,0.8960000000000008],[-1.380999999999972,0.4853000000000094],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[159.072,42.392],[159.072,0],[165.4,0],[165.4,42.392],[159.072,42.392],[159.072,42.392],[159.072,42.392]],"i":[[0,0],[0,14.13066666666667],[-2.109333333333325,0],[0,-14.13066666666667],[2.109333333333325,0],[0,0],[0,0]],"o":[[0,-14.13066666666667],[2.109333333333325,0],[0,14.13066666666667],[-2.109333333333325,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[138.952,43.06400000000001],[130.888,40.992],[125.456,35.28],[123.496,27.16],[125.456,19.040000000000006],[130.832,13.49600000000001],[138.448,11.424000000000007],[144.552,12.600000000000009],[149.088,15.848],[151.888,20.49600000000001],[152.896,26.096],[152.84,27.608],[152.616,29.064000000000007],[128.48,29.064000000000007],[128.48,24.024],[149.032,24.024],[146.008,26.320000000000007],[145.616,21.448000000000008],[142.816,18.032],[138.448,16.744],[133.968,18.032],[130.944,21.616],[130.104,27.216000000000008],[130.944,32.592],[134.192,36.176],[139.008,37.464],[143.65599999999995,36.232],[146.736,33.040000000000006],[151.888,35.56],[149.088,39.42400000000001],[144.60799999999995,42.11200000000001],[138.952,43.06400000000001],[138.952,43.06400000000001],[138.952,43.06400000000001]],"i":[[0,0],[2.351999999999975,1.381299999999996],[1.307000000000016,2.389300000000006],[0,2.986699999999999],[-1.307000000000016,2.35199999999999],[-2.240000000000009,1.343999999999994],[-2.836999999999989,0],[-1.79200000000003,-0.784000000000006],[-1.231999999999971,-1.381299999999996],[-0.6349999999999909,-1.754700000000014],[0,-1.978700000000003],[0.03699999999992087,-0.5227000000000004],[0.1119999999999663,-0.4480000000000075],[8.04533333333336,0],[0,1.680000000000007],[-6.850666666666712,0],[1.008000000000038,-0.7653333333333308],[0.6349999999999909,1.4187000000000012],[1.269000000000005,0.8213000000000079],[1.680000000000064,0],[1.307000000000016,-0.8586999999999989],[0.70900000000006,-1.567999999999998],[-0.1490000000000009,-2.202700000000007],[-0.7469999999999573,-1.530699999999996],[-1.380999999999972,-0.8586999999999989],[-1.79200000000003,0],[-1.268999999999892,0.8213000000000079],[-0.7469999999999573,1.306699999999992],[-1.717333333333386,-0.8400000000000034],[1.269000000000005,-1.157300000000006],[1.755000000000109,-0.6720000000000113],[2.052999999999997,0],[0,0],[0,0]],"o":[[-3.024000000000001,0],[-2.315000000000055,-1.4187000000000012],[-1.307000000000016,-2.426699999999997],[0,-3.061299999999989],[1.343999999999937,-2.352000000000004],[2.240000000000009,-1.3813000000000102],[2.277000000000044,0],[1.79200000000003,0.7839999999999918],[1.232000000000085,1.344000000000008],[0.6720000000000255,1.7547],[0,0.4852999999999952],[-0.03700000000003456,0.5227000000000004],[-8.04533333333336,0],[0,-1.680000000000007],[6.850666666666712,0],[-1.008000000000038,0.7653333333333308],[0.3729999999999336,-1.829300000000003],[-0.59699999999998,-1.456000000000003],[-1.232000000000085,-0.8586999999999989],[-1.67999999999995,0],[-1.306999999999903,0.8213000000000079],[-0.7089999999999463,1.530699999999996],[-0.1870000000000118,2.053299999999993],[0.7839999999999918,1.53070000000001],[1.418999999999983,0.8586999999999989],[1.828999999999951,0],[1.307000000000016,-0.8212999999999937],[1.717333333333386,0.8400000000000034],[-0.59699999999998,1.4187000000000012],[-1.231999999999971,1.11999999999999],[-1.716999999999985,0.6346999999999952],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[111.001,7.951999999999998],[111.001,0.6720000000000041],[117.329,0.6720000000000041],[117.329,7.951999999999998],[111.001,7.951999999999998],[111.001,7.951999999999998],[111.001,7.951999999999998]],"i":[[0,0],[0,2.426666666666662],[-2.109333333333325,0],[0,-2.426666666666662],[2.109333333333325,0],[0,0],[0,0]],"o":[[0,-2.426666666666662],[2.109333333333325,0],[0,2.426666666666662],[-2.109333333333325,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[111.001,42.392],[111.001,12.096],[117.329,12.096],[117.329,42.392],[111.001,42.392],[111.001,42.392],[111.001,42.392]],"i":[[0,0],[0,10.09866666666667],[-2.109333333333325,0],[0,-10.09866666666667],[2.109333333333325,0],[0,0],[0,0]],"o":[[0,-10.09866666666666],[2.109333333333325,0],[0,10.09866666666666],[-2.109333333333325,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[101.41,42.72800000000001],[94.01800000000003,40.040000000000006],[91.38599999999997,32.48],[91.38599999999997,17.808000000000007],[86.06600000000003,17.808000000000007],[86.06600000000003,12.096],[86.90599999999995,12.096],[90.21000000000004,10.864],[91.38599999999997,7.504000000000005],[91.38599999999997,5.152000000000001],[97.71400000000006,5.152000000000001],[97.71400000000006,12.096],[104.602,12.096],[104.602,17.808000000000007],[97.71400000000006,17.808000000000007],[97.71400000000006,32.2],[98.21799999999996,34.888000000000005],[99.84199999999998,36.568],[102.754,37.128],[103.76200000000006,37.072],[104.826,36.96000000000001],[104.826,42.392],[103.09,42.616],[101.41,42.72800000000001],[101.41,42.72800000000001],[101.41,42.72800000000001]],"i":[[0,0],[1.754999999999995,1.792000000000002],[0,3.248000000000005],[0,4.890666666666661],[1.773333333333312,0],[0,1.903999999999996],[-0.2799999999999727,0],[-0.7839999999999918,0.8212999999999937],[0,1.4187000000000012],[0,0.7839999999999989],[-2.109333333333325,0],[0,-2.314666666666668],[-2.295999999999935,0],[0,-1.903999999999996],[2.295999999999935,0],[0,-4.797333333333327],[-0.3360000000000127,-0.7467000000000041],[-0.7469999999999573,-0.4106999999999914],[-1.19500000000005,0],[-0.3730000000000473,0.03730000000000189],[-0.3360000000000127,0.03729999999998768],[0,-1.810666666666663],[0.6349999999999909,-0.07469999999999288],[0.4850000000000136,0],[0,0],[0,0]],"o":[[-3.173000000000002,0],[-1.754999999999995,-1.792000000000002],[0,-4.890666666666661],[-1.773333333333312,0],[0,-1.903999999999996],[0.2799999999999727,0],[1.419000000000096,0],[0.7839999999999918,-0.8213000000000079],[0,-0.7839999999999989],[2.109333333333325,0],[0,2.314666666666668],[2.295999999999935,0],[0,1.903999999999996],[-2.295999999999935,0],[0,4.797333333333327],[0,1.045299999999997],[0.3360000000000127,0.7092999999999989],[0.7470000000000709,0.3733000000000004],[0.2989999999999782,0],[0.3729999999999336,-0.03730000000000189],[0,1.810666666666663],[-0.5230000000000246,0.0747000000000071],[-0.6349999999999909,0.0747000000000071],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[78.71499999999997,42.72800000000001],[71.32299999999998,40.040000000000006],[68.69100000000003,32.48],[68.69100000000003,17.808000000000007],[63.37099999999998,17.808000000000007],[63.37099999999998,12.096],[64.21100000000001,12.096],[67.51499999999999,10.864],[68.69100000000003,7.504000000000005],[68.69100000000003,5.152000000000001],[75.019,5.152000000000001],[75.019,12.096],[81.90700000000004,12.096],[81.90700000000004,17.808000000000007],[75.019,17.808000000000007],[75.019,32.2],[75.52300000000002,34.888000000000005],[77.14699999999999,36.568],[80.05900000000003,37.128],[81.06700000000001,37.072],[82.13099999999997,36.96000000000001],[82.13099999999997,42.392],[80.39499999999998,42.616],[78.71499999999997,42.72800000000001],[78.71499999999997,42.72800000000001],[78.71499999999997,42.72800000000001]],"i":[[0,0],[1.754000000000019,1.792000000000002],[0,3.248000000000005],[0,4.890666666666661],[1.773333333333369,0],[0,1.903999999999996],[-0.2800000000000296,0],[-0.7839999999999918,0.8212999999999937],[0,1.4187000000000012],[0,0.7839999999999989],[-2.109333333333325,0],[0,-2.314666666666668],[-2.295999999999992,0],[0,-1.903999999999996],[2.295999999999992,0],[0,-4.797333333333327],[-0.3360000000000127,-0.7467000000000041],[-0.7470000000000141,-0.4106999999999914],[-1.19500000000005,0],[-0.3740000000000236,0.03730000000000189],[-0.3360000000000127,0.03729999999998768],[0,-1.810666666666663],[0.6340000000000146,-0.07469999999999288],[0.4850000000000136,0],[0,0],[0,0]],"o":[[-3.173999999999978,0],[-1.754999999999995,-1.792000000000002],[0,-4.890666666666661],[-1.773333333333369,0],[0,-1.903999999999996],[0.2800000000000296,0],[1.418000000000006,0],[0.7839999999999918,-0.8213000000000079],[0,-0.7839999999999989],[2.109333333333325,0],[0,2.314666666666668],[2.295999999999992,0],[0,1.903999999999996],[-2.295999999999992,0],[0,4.797333333333327],[0,1.045299999999997],[0.3359999999999559,0.7092999999999989],[0.7460000000000377,0.3733000000000004],[0.297999999999945,0],[0.3730000000000473,-0.03730000000000189],[0,1.810666666666663],[-0.5230000000000246,0.0747000000000071],[-0.6349999999999909,0.0747000000000071],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[44.18799999999999,37.184],[48.94799999999998,35.896],[52.19600000000003,32.36800000000001],[53.428,27.216000000000008],[52.19600000000003,22.12],[48.94799999999998,18.592],[44.18799999999999,17.304],[39.428,18.592],[36.12400000000002,22.12],[34.94799999999998,27.216000000000008],[36.12400000000002,32.36800000000001],[39.428,35.896],[44.18799999999999,37.184],[44.18799999999999,37.184],[44.18799999999999,37.184]],"i":[[0,0],[-1.381999999999948,0.8586999999999989],[-0.7840000000000487,1.493299999999991],[0,1.903999999999996],[0.8209999999999695,1.493299999999991],[1.381000000000029,0.8586999999999989],[1.79200000000003,0],[1.418000000000006,-0.8586999999999989],[0.8209999999999695,-1.493300000000005],[0,-1.904000000000011],[-0.7840000000000487,-1.5307000000000102],[-1.382000000000005,-0.8586999999999989],[-1.754999999999995,0],[0,0],[0,0]],"o":[[1.79200000000003,0],[1.381000000000029,-0.8586999999999989],[0.8209999999999695,-1.5307000000000102],[0,-1.904000000000011],[-0.7840000000000487,-1.493300000000005],[-1.381999999999948,-0.8586999999999989],[-1.754999999999995,0],[-1.382000000000005,0.8586999999999989],[-0.7840000000000487,1.493299999999991],[0,1.903999999999996],[0.8209999999999695,1.493299999999991],[1.418000000000006,0.8586999999999989],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[44.18799999999999,43.06400000000001],[36.18000000000001,40.992],[30.46800000000002,35.336],[28.33999999999997,27.216000000000008],[30.46800000000002,19.096],[36.18000000000001,13.49600000000001],[44.18799999999999,11.424000000000007],[52.19600000000003,13.49600000000001],[57.85199999999998,19.096],[59.98000000000002,27.216000000000008],[57.85199999999998,35.392],[52.139999999999986,41.048],[44.18799999999999,43.06400000000001],[44.18799999999999,43.06400000000001],[44.18799999999999,43.06400000000001]],"i":[[0,0],[2.425999999999988,1.381299999999996],[1.418000000000006,2.389300000000006],[0,3.024000000000001],[-1.41900000000004,2.352000000000004],[-2.389999999999986,1.343999999999994],[-2.949999999999989,0],[-2.352000000000032,-1.3813000000000102],[-1.381999999999948,-2.389300000000006],[0,-3.061300000000003],[1.418000000000006,-2.389299999999992],[2.38900000000001,-1.381299999999996],[2.912000000000035,0],[0,0],[0,0]],"o":[[-2.911999999999978,0],[-2.389999999999986,-1.381299999999996],[-1.41900000000004,-2.389299999999992],[0,-3.061300000000003],[1.418000000000006,-2.389300000000006],[2.38900000000001,-1.3813000000000102],[2.98599999999999,0],[2.388999999999953,1.343999999999994],[1.418000000000006,2.352000000000004],[0,3.061299999999989],[-1.418999999999983,2.389300000000006],[-2.389999999999986,1.343999999999994],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[0,42.392],[0,0.6720000000000041],[6.608000000000004,0.6720000000000041],[6.608000000000004,36.512],[24.639999999999986,36.512],[24.639999999999986,42.392],[0,42.392],[0,42.392],[0,42.392]],"i":[[0,0],[0,13.90666666666666],[-2.202666666666687,0],[0,-11.94666666666666],[-6.01066666666668,0],[0,-1.959999999999994],[8.21333333333331,0],[0,0],[0,0]],"o":[[0,-13.90666666666667],[2.202666666666687,0],[0,11.94666666666667],[6.01066666666668,0],[0,1.959999999999994],[-8.21333333333331,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"fl","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"r":1,"bm":0},{"ty":"tr","p":{"a":0,"k":[98.08047485351562,-21.67217254638672],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[99.99999403953552,99.99999403953552],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[246.681,42.392],[246.681,0],[253.009,0],[253.009,18.032],[252.001,17.248],[255.585,12.936000000000007],[261.297,11.424000000000007],[267.23299999999995,12.88],[271.265,16.912000000000006],[272.721,22.792],[272.721,42.392],[266.449,42.392],[266.449,24.528000000000006],[265.553,20.664],[263.201,18.2],[259.729,17.304],[256.25699999999995,18.2],[253.849,20.664],[253.009,24.528000000000006],[253.009,42.392],[246.681,42.392],[246.681,42.392],[246.681,42.392]],"i":[[0,0],[0,14.13066666666667],[-2.109333333333325,0],[0,-6.010666666666665],[0.3360000000000127,0.2613333333333259],[-1.643000000000029,0.9706999999999937],[-2.166000000000054,0],[-1.717999999999961,-0.9706999999999937],[-0.9710000000000036,-1.717300000000009],[0,-2.202699999999993],[0,-6.533333333333331],[2.090666666666721,0],[0,5.954666666666668],[0.59699999999998,1.045299999999997],[1.007999999999925,0.5600000000000023],[1.305999999999926,0],[1.045000000000073,-0.5973000000000042],[0.59699999999998,-1.082700000000003],[0,-1.493300000000005],[0,-5.954666666666668],[2.109333333333325,0],[0,0],[0,0]],"o":[[0,-14.13066666666667],[2.109333333333325,0],[0,6.010666666666665],[-0.3360000000000127,-0.2613333333333259],[0.7459999999999809,-1.903999999999996],[1.641999999999967,-1.0080000000000098],[2.240000000000009,0],[1.717000000000098,0.9707000000000079],[0.9700000000000273,1.717299999999994],[0,6.533333333333331],[-2.090666666666721,0],[0,-5.954666666666668],[0,-1.5307000000000102],[-0.5599999999999454,-1.082700000000003],[-1.008000000000038,-0.5973000000000042],[-1.270000000000095,0],[-1.007999999999953,0.5600000000000023],[-0.5600000000000023,1.082700000000003],[0,5.954666666666668],[-2.109333333333325,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-354.5325317382812,-77.50520324707031],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[237.089,42.72800000000001],[229.697,40.040000000000006],[227.065,32.48],[227.065,17.808000000000007],[221.745,17.808000000000007],[221.745,12.096],[222.585,12.096],[225.889,10.864],[227.065,7.504000000000005],[227.065,5.152000000000001],[233.393,5.152000000000001],[233.393,12.096],[240.281,12.096],[240.281,17.808000000000007],[233.393,17.808000000000007],[233.393,32.2],[233.897,34.888000000000005],[235.521,36.568],[238.433,37.128],[239.441,37.072],[240.505,36.96000000000001],[240.505,42.392],[238.769,42.616],[237.089,42.72800000000001],[237.089,42.72800000000001],[237.089,42.72800000000001]],"i":[[0,0],[1.755000000000052,1.792000000000002],[0,3.248000000000005],[0,4.890666666666661],[1.773333333333369,0],[0,1.903999999999996],[-0.2800000000000296,0],[-0.7839999999999918,0.8212999999999937],[0,1.4187000000000012],[0,0.7839999999999989],[-2.109333333333325,0],[0,-2.314666666666668],[-2.295999999999992,0],[0,-1.903999999999996],[2.295999999999992,0],[0,-4.797333333333327],[-0.3360000000000127,-0.7467000000000041],[-0.7459999999999809,-0.4106999999999914],[-1.194000000000017,0],[-0.3729999999999905,0.03730000000000189],[-0.3360000000000127,0.03729999999998768],[0,-1.810666666666663],[0.6350000000000477,-0.07469999999999288],[0.48599999999999,0],[0,0],[0,0]],"o":[[-3.173000000000002,0],[-1.753999999999962,-1.792000000000002],[0,-4.890666666666661],[-1.773333333333369,0],[0,-1.903999999999996],[0.2800000000000296,0],[1.418999999999983,0],[0.7839999999999918,-0.8213000000000079],[0,-0.7839999999999989],[2.109333333333325,0],[0,2.314666666666668],[2.295999999999992,0],[0,1.903999999999996],[-2.295999999999992,0],[0,4.797333333333327],[0,1.045299999999997],[0.3359999999999559,0.7092999999999989],[0.7470000000000141,0.3733000000000004],[0.2989999999999782,0],[0.3740000000000236,-0.03730000000000189],[0,1.810666666666663],[-0.5220000000000482,0.0747000000000071],[-0.6339999999999577,0.0747000000000071],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-354.5325317382812,-77.50520324707031],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[210.259,7.951999999999998],[210.259,0.6720000000000041],[216.587,0.6720000000000041],[216.587,7.951999999999998],[210.259,7.951999999999998],[210.259,7.951999999999998],[210.259,7.951999999999998]],"i":[[0,0],[0,2.426666666666662],[-2.109333333333325,0],[0,-2.426666666666662],[2.109333333333325,0],[0,0],[0,0]],"o":[[0,-2.426666666666662],[2.109333333333325,0],[0,2.426666666666662],[-2.109333333333325,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-354.5325317382812,-77.50520324707031],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[210.259,42.392],[210.259,12.096],[216.587,12.096],[216.587,42.392],[210.259,42.392],[210.259,42.392],[210.259,42.392]],"i":[[0,0],[0,10.09866666666667],[-2.109333333333325,0],[0,-10.09866666666667],[2.109333333333325,0],[0,0],[0,0]],"o":[[0,-10.09866666666666],[2.109333333333325,0],[0,10.09866666666666],[-2.109333333333325,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-354.5325317382812,-77.50520324707031],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[169.688,42.392],[159.272,12.096],[165.992,12.096],[173.944,36.232],[171.592,36.232],[179.712,12.096],[185.48,12.096],[193.544,36.232],[191.192,36.232],[199.2,12.096],[205.92,12.096],[195.448,42.392],[189.736,42.392],[181.56,17.696],[183.632,17.696],[175.456,42.392],[169.688,42.392],[169.688,42.392],[169.688,42.392]],"i":[[0,0],[3.47199999999998,10.09866666666667],[-2.240000000000009,0],[-2.650666666666666,-8.045333333333332],[0.7839999999999918,0],[-2.706666666666649,8.045333333333332],[-1.922666666666657,0],[-2.687999999999988,-8.045333333333332],[0.7839999999999918,0],[-2.669333333333327,8.045333333333332],[-2.240000000000009,0],[3.490666666666641,-10.09866666666667],[1.903999999999996,0],[2.725333333333367,8.232],[-0.6906666666666865,0],[2.72533333333331,-8.232],[1.922666666666657,0],[0,0],[0,0]],"o":[[-3.47199999999998,-10.09866666666666],[2.240000000000009,0],[2.650666666666666,8.045333333333332],[-0.7839999999999918,0],[2.706666666666649,-8.045333333333332],[1.922666666666657,0],[2.687999999999988,8.045333333333332],[-0.7839999999999918,0],[2.669333333333327,-8.045333333333332],[2.240000000000009,0],[-3.490666666666641,10.09866666666666],[-1.903999999999996,0],[-2.725333333333367,-8.232],[0.6906666666666865,0],[-2.72533333333331,8.232],[-1.922666666666657,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-354.5325317382812,-77.50520324707031],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"fl","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"r":1,"bm":0},{"ty":"tr","p":{"a":0,"k":[155.86146545410156,56.001014709472656],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[99.99999403953552,99.99999403953552],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[132.444,43.06400000000001],[124.38,40.992],[118.948,35.28],[116.988,27.16],[118.948,19.040000000000006],[124.324,13.49600000000001],[131.94,11.424000000000007],[138.044,12.600000000000009],[142.58,15.848],[145.38,20.49600000000001],[146.388,26.096],[146.332,27.608],[146.108,29.064000000000007],[121.972,29.064000000000007],[121.972,24.024],[142.524,24.024],[139.5,26.320000000000007],[139.108,21.448000000000008],[136.308,18.032],[131.94,16.744],[127.46,18.032],[124.436,21.616],[123.596,27.216000000000008],[124.436,32.592],[127.684,36.176],[132.5,37.464],[137.148,36.232],[140.228,33.040000000000006],[145.38,35.56],[142.58,39.42400000000001],[138.1,42.11200000000001],[132.444,43.06400000000001],[132.444,43.06400000000001],[132.444,43.06400000000001]],"i":[[0,0],[2.351999999999975,1.381299999999996],[1.305999999999983,2.389300000000006],[0,2.986699999999999],[-1.307000000000016,2.35199999999999],[-2.240000000000009,1.343999999999994],[-2.838000000000022,0],[-1.79200000000003,-0.784000000000006],[-1.232000000000028,-1.381299999999996],[-0.6350000000000477,-1.754700000000014],[0,-1.978700000000003],[0.03699999999997772,-0.5227000000000004],[0.1120000000000232,-0.4480000000000075],[8.045333333333303,0],[0,1.680000000000007],[-6.850666666666655,0],[1.007999999999981,-0.7653333333333308],[0.6340000000000146,1.4187000000000012],[1.269000000000005,0.8213000000000079],[1.67999999999995,0],[1.305999999999983,-0.8586999999999989],[0.7090000000000032,-1.567999999999998],[-0.1499999999999773,-2.202700000000007],[-0.7470000000000141,-1.530699999999996],[-1.382000000000005,-0.8586999999999989],[-1.791999999999973,0],[-1.269999999999982,0.8213000000000079],[-0.7469999999999573,1.306699999999992],[-1.717333333333329,-0.8400000000000034],[1.269000000000005,-1.157300000000006],[1.754000000000019,-0.6720000000000113],[2.052999999999997,0],[0,0],[0,0]],"o":[[-3.024000000000001,0],[-2.314999999999998,-1.4187000000000012],[-1.307000000000016,-2.426699999999997],[0,-3.061299999999989],[1.343999999999994,-2.352000000000004],[2.240000000000009,-1.3813000000000102],[2.276999999999987,0],[1.791999999999973,0.7839999999999918],[1.231999999999971,1.344000000000008],[0.6719999999999686,1.7547],[0,0.4852999999999952],[-0.03800000000001091,0.5227000000000004],[-8.045333333333303,0],[0,-1.680000000000007],[6.850666666666655,0],[-1.007999999999981,0.7653333333333308],[0.3730000000000473,-1.829300000000003],[-0.5979999999999563,-1.456000000000003],[-1.232000000000028,-0.8586999999999989],[-1.680000000000007,0],[-1.307000000000016,0.8213000000000079],[-0.7100000000000364,1.530699999999996],[-0.186999999999955,2.053299999999993],[0.7839999999999918,1.53070000000001],[1.418000000000006,0.8586999999999989],[1.829000000000008,0],[1.305999999999983,-0.8212999999999937],[1.717333333333329,0.8400000000000034],[-0.5980000000000132,1.4187000000000012],[-1.232000000000028,1.11999999999999],[-1.718000000000018,0.6346999999999952],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-211.7300415039062,-77.67320251464844],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[95.32,37.184],[100.024,35.896],[103.328,32.36800000000001],[104.56,27.216000000000008],[103.328,22.12],[100.024,18.592],[95.32,17.304],[90.56,18.592],[87.256,22.12],[86.08000000000001,27.216000000000008],[87.256,32.36800000000001],[90.50399999999999,35.896],[95.32,37.184],[95.32,37.184],[95.32,37.184]],"i":[[0,0],[-1.381,0.8586999999999989],[-0.7839999999999918,1.493299999999991],[0,1.903999999999996],[0.820999999999998,1.493299999999991],[1.419000000000011,0.8586999999999989],[1.754999999999995,0],[1.418999999999983,-0.8586999999999989],[0.7839999999999918,-1.493300000000005],[0,-1.904000000000011],[-0.7839999999999918,-1.5307000000000102],[-1.381,-0.8586999999999989],[-1.792000000000002,0],[0,0],[0,0]],"o":[[1.754999999999995,0],[1.419000000000011,-0.8586999999999989],[0.820999999999998,-1.5307000000000102],[0,-1.904000000000011],[-0.7839999999999918,-1.493300000000005],[-1.381,-0.8586999999999989],[-1.754999999999995,0],[-1.419000000000011,0.8586999999999989],[-0.7839999999999918,1.493299999999991],[0,1.903999999999996],[0.7839999999999918,1.493299999999991],[1.419000000000011,0.8586999999999989],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-211.7300415039062,-77.67320251464844],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[94.70400000000001,43.06400000000001],[86.864,40.992],[81.43199999999999,35.28],[79.47200000000001,27.216000000000008],[81.488,19.152],[86.91999999999999,13.49600000000001],[94.648,11.424000000000007],[101.088,12.88],[105.512,16.912000000000006],[104.56,18.36800000000001],[104.56,0],[110.832,0],[110.832,42.392],[104.84,42.392],[104.84,36.232],[105.568,37.408],[101.088,41.608],[94.70400000000001,43.06400000000001],[94.70400000000001,43.06400000000001],[94.70400000000001,43.06400000000001]],"i":[[0,0],[2.314999999999998,1.381299999999996],[1.344000000000023,2.389300000000006],[0,2.986699999999999],[-1.343999999999994,2.389300000000006],[-2.276999999999987,1.381299999999996],[-2.875,0],[-1.8669999999999902,-0.9706999999999937],[-1.082999999999998,-1.717300000000009],[0.3173333333333233,-0.4853333333333296],[0,6.122666666666674],[-2.090666666666664,0],[0,-14.13066666666667],[1.99733333333333,0],[0,2.053333333333327],[-0.242666666666679,-0.3919999999999959],[1.941000000000003,-0.9707000000000079],[2.314999999999998,0],[0,0],[0,0]],"o":[[-2.912000000000006,0],[-2.277000000000015,-1.4187000000000012],[-1.306999999999988,-2.389300000000006],[0,-2.986699999999999],[1.343999999999994,-2.389300000000006],[2.277000000000015,-1.3813000000000102],[2.426999999999992,0],[1.867000000000019,0.9707000000000079],[-0.3173333333333233,0.4853333333333296],[0,-6.122666666666674],[2.090666666666664,0],[0,14.13066666666667],[-1.99733333333333,0],[0,-2.053333333333327],[0.242666666666679,0.3919999999999959],[-1.045000000000016,1.829300000000003],[-1.941000000000003,0.9706999999999937],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-211.7300415039062,-77.67320251464844],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[57.40100000000001,43.06400000000001],[51.968999999999994,42],[48.38499999999999,38.976],[47.09700000000001,34.384],[48.161,29.960000000000008],[51.577,26.656000000000006],[57.345,24.752],[67.87299999999999,23.016000000000005],[67.87299999999999,28],[58.577,29.624],[54.881,31.248],[53.70500000000001,34.16],[54.992999999999995,37.016000000000005],[58.40899999999999,38.08],[62.88900000000001,36.96000000000001],[65.91300000000001,33.992],[66.977,29.792],[66.977,22.008],[65.297,18.36800000000001],[60.929,16.912000000000006],[56.505,18.256],[53.761,21.616],[48.496999999999986,18.98400000000001],[51.240999999999985,15.064000000000007],[55.66499999999999,12.376],[61.09700000000001,11.424000000000007],[67.42500000000001,12.768],[71.737,16.52],[73.305,22.008],[73.305,42.392],[67.257,42.392],[67.257,36.904],[68.54499999999999,37.072],[65.80099999999999,40.264],[62.04900000000001,42.336],[57.40100000000001,43.06400000000001],[57.40100000000001,43.06400000000001],[57.40100000000001,43.06400000000001]],"i":[[0,0],[1.568000000000012,0.7092999999999989],[0.8590000000000089,1.2693000000000012],[0,1.7547],[-0.7090000000000032,1.306699999999992],[-1.531000000000006,0.8960000000000008],[-2.314999999999998,0.3733000000000004],[-3.509333333333331,0.5786666666666633],[0,-1.661333333333332],[3.098666666666674,-0.541333333333327],[0.7839999999999918,-0.784000000000006],[0,-1.194699999999997],[-0.8590000000000089,-0.7467000000000041],[-1.381,0],[-1.269000000000005,0.7466999999999899],[-0.7090000000000032,1.2319999999999993],[0,1.530699999999996],[0,2.594666666666669],[1.120000000000005,0.9332999999999885],[1.829000000000008,0],[1.269000000000005,-0.8960000000000008],[0.5970000000000084,-1.381299999999996],[1.754666666666679,0.8773333333333255],[-1.268999999999977,1.11999999999999],[-1.680000000000007,0.6346999999999952],[-1.903999999999996,0],[-1.829000000000008,-0.8960000000000008],[-1.0080000000000098,-1.6053],[0,-2.090699999999998],[0,-6.794666666666672],[2.015999999999991,0],[0,1.829333333333338],[-0.429333333333318,-0.05599999999999739],[1.120000000000005,-0.8959999999999866],[1.418999999999983,-0.4852999999999952],[1.716999999999985,0],[0,0],[0,0]],"o":[[-2.053000000000026,0],[-1.531000000000006,-0.7467000000000041],[-0.8589999999999804,-1.306699999999992],[0,-1.642700000000005],[0.7469999999999857,-1.306700000000006],[1.531000000000006,-0.8960000000000008],[3.509333333333331,-0.5786666666666633],[0,1.661333333333332],[-3.098666666666674,0.541333333333327],[-1.680000000000007,0.2987000000000108],[-0.7839999999999918,0.7467000000000041],[0,1.157300000000006],[0.896000000000015,0.7092999999999989],[1.717000000000013,0],[1.306999999999988,-0.7467000000000041],[0.7089999999999748,-1.2693000000000012],[0,-2.594666666666669],[0,-1.493299999999991],[-1.082999999999998,-0.9707000000000079],[-1.680000000000007,0],[-1.2319999999999993,0.8586999999999989],[-1.754666666666679,-0.8773333333333255],[0.5600000000000023,-1.493300000000005],[1.269000000000005,-1.157300000000006],[1.717000000000013,-0.6347000000000094],[2.388999999999982,0],[1.86699999999999,0.8960000000000008],[1.045000000000016,1.568000000000012],[0,6.794666666666672],[-2.015999999999991,0],[0,-1.829333333333338],[0.429333333333318,0.05599999999999739],[-0.7089999999999748,1.2319999999999993],[-1.082999999999998,0.8960000000000008],[-1.381,0.4853000000000094],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-211.7300415039062,-77.67320251464844],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[0,42.392],[0,0.6720000000000041],[6.159999999999997,0.6720000000000041],[21.84,22.400000000000006],[18.75999999999999,22.400000000000006],[34.16,0.6720000000000041],[40.31999999999999,0.6720000000000041],[40.31999999999999,42.392],[33.768,42.392],[33.768,8.456000000000003],[36.232,9.128],[20.49600000000001,30.632000000000005],[19.824000000000012,30.632000000000005],[4.424000000000007,9.128],[6.608000000000004,8.456000000000003],[6.608000000000004,42.392],[0,42.392],[0,42.392],[0,42.392]],"i":[[0,0],[0,13.90666666666666],[-2.053333333333342,0],[-5.226666666666659,-7.242666666666665],[1.026666666666671,0],[-5.133333333333326,7.242666666666672],[-2.053333333333342,0],[0,-13.90666666666667],[2.183999999999997,0],[0,11.312],[-0.8213333333333424,-0.2240000000000038],[5.245333333333321,-7.168000000000006],[0.2239999999999895,0],[5.133333333333326,7.168000000000006],[-0.7280000000000086,0.2240000000000038],[0,-11.312],[2.202666666666659,0],[0,0],[0,0]],"o":[[0,-13.90666666666667],[2.053333333333342,0],[5.226666666666659,7.242666666666672],[-1.026666666666671,0],[5.133333333333326,-7.242666666666665],[2.053333333333342,0],[0,13.90666666666666],[-2.183999999999997,0],[0,-11.312],[0.8213333333333424,0.2240000000000038],[-5.245333333333321,7.168000000000006],[-0.2239999999999895,0],[-5.133333333333326,-7.168000000000006],[0.7280000000000086,-0.2240000000000038],[0,11.312],[-2.202666666666659,0],[0,0],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[-211.7300415039062,-77.67320251464844],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"fl","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"r":1,"bm":0},{"ty":"tr","p":{"a":0,"k":[2.91259765625,56.001014709472656],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[99.99999403953552,99.99999403953552],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[702.6863719370097,144],"ix":2},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":72,"ix":2}},{"ty":"fl","c":{"a":0,"k":[0,0,0],"ix":2},"o":{"a":0,"k":100,"ix":2},"r":1,"bm":0},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"tr","p":{"a":0,"k":[56.54167175292969,-0.000022762338630855083],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[99.99999403953552,99.99999403953552],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":80,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[122.0000003294881,25.00000012138912],"ix":2},"a":{"a":0,"k":[56.54167175292969,-0.00002288818359375],"ix":2},"s":{"a":0,"k":[34.403572049765366,34.403572049765366],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":1,"ty":4,"nm":"Line (Group)","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","nm":"Group 1","it":[{"ty":"gr","nm":"Path 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[3.475,0.875],[-0.5,-3.292]],"i":[[0,0],[0,0]],"o":[[0,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Path 2","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[-4.9,0.875],[-0.5,-3.292],[-0.528,9.25]],"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]]}}},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":4,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[9.5,12.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[27,24.984],"ix":2},"a":{"a":0,"k":[9.5,12.734],"ix":2},"s":{"a":1,"k":[{"t":25,"s":[0,0],"i":{"x":[0.658],"y":[1]},"o":{"x":[0.461],"y":[0]}},{"t":53,"s":[134,134],"i":{"x":[0.343],"y":[1]},"o":{"x":[0.41],"y":[0]}},{"t":75,"s":[100,100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":1,"k":[{"t":103,"s":[106.9561767578125,94.498046875],"i":{"x":[0.537],"y":[1]},"o":{"x":[0.457],"y":[0]},"ti":[0,0],"to":[-7.5,0]},{"t":223,"s":[61.9561767578125,94.498046875],"i":{"x":[0.565],"y":[0.988]},"o":{"x":[0.452],"y":[0]},"ti":[-7.5,0],"to":[0,0]},{"t":343,"s":[106.9561767578125,94.498046875],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":1,"k":[{"t":3,"s":[0,0],"i":{"x":[0.658],"y":[1]},"o":{"x":[0.461],"y":[0]}},{"t":47,"s":[231.297699213028,231.297699213028],"i":{"x":[0.598],"y":[1]},"o":{"x":[0.488],"y":[0]}},{"t":80,"s":[139.8142808675766,139.8142808675766],"i":{"x":[0.49],"y":[1]},"o":{"x":[0.392],"y":[0]}},{"t":103,"s":[172.61022329330444,172.61022329330444],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Group 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":true,"v":[[17,0],[0,17],[-17,0],[0,-17],[17,0]],"i":[[0,0],[9.389,0],[0,9.389],[-9.389,0],[0,-9.389]],"o":[[0,9.389],[-9.389,0],[0,-9.389],[9.389,0],[0,0]]}}},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":6,"ix":2},"lc":2,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[27,27],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":1,"k":[{"t":103,"s":[106.9561767578125,94.498046875],"i":{"x":[0.537],"y":[1]},"o":{"x":[0.457],"y":[0]},"ti":[0,0],"to":[-7.5,0]},{"t":223,"s":[61.9561767578125,94.498046875],"i":{"x":[0.565],"y":[0.988]},"o":{"x":[0.452],"y":[0]},"ti":[-7.5,0],"to":[0,0]},{"t":343,"s":[106.9561767578125,94.498046875],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":1,"k":[{"t":3,"s":[0,0],"i":{"x":[0.658],"y":[1]},"o":{"x":[0.461],"y":[0]}},{"t":47,"s":[231.297699213028,231.297699213028],"i":{"x":[0.598],"y":[1]},"o":{"x":[0.488],"y":[0]}},{"t":80,"s":[139.8142808675766,139.8142808675766],"i":{"x":[0.49],"y":[1]},"o":{"x":[0.392],"y":[0]}},{"t":103,"s":[172.61022329330444,172.61022329330444],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Line","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[-27.40187177921811,4.543100883332414],[-4.315255398302065,-18.988849854688404],[21.47271086195107,5.7461940883790295],[-4.3014465810274976,30.26202305721272],[-89.75731228468294,30.20506168595513]],"i":[[0,0],[-9.600580210142434,0.32450720595231525],[-0.0034522043186416516,-15.348500400680782],[18.757552165339415,-0.16397970513547847],[2.825629234808192,-0.01553491943388743]],"o":[[0,-16.734560434615407],[13.503297192366823,-0.4556909700606981],[0.0017261021593208258,9.40898287045782],[-2.3664860604288522,0.02071322591184991],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":73,"s":[36],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":100,"s":[21],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":73,"s":[36],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":100,"s":[57],"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":103,"s":[57],"i":{"x":[0.647],"y":[1]},"o":{"x":[0.336],"y":[0]}},{"t":223,"s":[95],"i":{"x":[0.653],"y":[1.011]},"o":{"x":[0.353],"y":[0]}},{"t":343,"s":[57],"i":{"x":[0.647],"y":[1]},"o":{"x":[0.336],"y":[0]}},{"t":463,"s":[95],"i":{"x":[0.67],"y":[1]},"o":{"x":[0.369],"y":[0]}},{"t":583,"s":[57],"i":{"x":[0.647],"y":[1]},"o":{"x":[0.336],"y":[0]}},{"t":703,"s":[95],"i":{"x":[0.715],"y":[0.99]},"o":{"x":[0.369],"y":[0]}},{"t":833,"s":[57],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":-57,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":8,"ix":2},"lc":2,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[169.95892333984375,104.85466003417969],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Line","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[-7.762281410465753,-27.947320061563488],[-21.576276991510323,-31.39434607372718],[-52.64611585928519,-0.0017261021593208258],[-21.576276991510323,31.39434607372718],[45.52594445208678,31.287327739849292]],"i":[[0,0],[4.962543708047374,0],[0,-17.15918156580833],[-17.15918156580833,0],[0,0]],"o":[[-4.161632306122511,-2.0695964890256704],[-17.15918156580833,0],[0,17.15918156580833],[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":103,"s":[100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":223,"s":[58],"i":{"x":[0.653],"y":[1]},"o":{"x":[0.322],"y":[0]}},{"t":343,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":3,"s":[100],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":40,"s":[0],"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]}},{"t":103,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":8,"ix":2},"lc":2,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[61.2144775390625,103.66709899902344],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Line","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[-36.88335094036741,20.71322591184991],[4.543100883332414,-20.71322591184991],[36.88335094036741,-5.1800325801217975]],"i":[[0,0],[-22.879484121797546,0],[-7.593123398852313,-9.469396446034049]],"o":[[0,-22.879484121797546],[13.082128265492537,0],[0,0]]}}},{"ty":"tm","s":{"a":0,"k":0,"ix":2},"e":{"a":1,"k":[{"t":30,"s":[0],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":63,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":8,"ix":2},"lc":2,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[78.67915344238281,50.26668167114258],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Line","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[-31.677426827855797,-3.4539304208009725],[-2.8446163585607205,-18.987123752529083],[31.677426827855797,15.534919433887433],[31.508268816242357,18.987123752529083]],"i":[[0,0],[-12.049919174218685,0],[0,-19.06652445185784],[0.11219664035585368,-1.1357752208331036]],"o":[[6.175993526049915,-9.358925907837518],[19.06652445185784,0],[0,1.1651189575415575],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":57,"s":[22],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":83,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":57,"s":[22],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0.223]}},{"t":83,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":8,"ix":2},"lc":2,"lj":1,"ml":4},{"ty":"tr","p":{"a":0,"k":[137.41671752929688,64.291259765625],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Line","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[14.544136794437279,1.0287568869552122],[-14.544136794437279,11.053958228290568]],"i":[[0,0],[8.373321574865326,-22.107916456581137]],"o":[[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":33,"s":[50],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":57,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":33,"s":[50],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":57,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":4,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[61.34219741821289,102.09980773925781],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Line","sr":1,"ks":{"p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}},"ao":0,"shapes":[{"ty":"gr","nm":"Line","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[14.544136794437279,1.0287568869552122],[-14.544136794437279,11.053958228290568]],"i":[[0,0],[8.373321574865326,-22.107916456581137]],"o":[[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":77,"s":[50],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":97,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":77,"s":[50],"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]}},{"t":97,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[1,1,1],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":4,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[109.67306518554688,72.75605773925781],"ix":2},"a":{"a":0,"k":[0,0],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}],"ip":0,"op":668,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/frontend/src/hooks/api/gateways/mutation.tsx b/frontend/src/hooks/api/gateways/mutation.tsx index 7bba847bd..e93197fdd 100644 --- a/frontend/src/hooks/api/gateways/mutation.tsx +++ b/frontend/src/hooks/api/gateways/mutation.tsx @@ -12,7 +12,7 @@ export const useDeleteGatewayById = () => { return apiRequest.delete(`/api/v1/gateways/${id}`); }, onSuccess: () => { - queryClient.invalidateQueries(gatewaysQueryKeys.list({})); + queryClient.invalidateQueries(gatewaysQueryKeys.list()); } }); }; @@ -20,11 +20,11 @@ export const useDeleteGatewayById = () => { export const useUpdateGatewayById = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ id, name }: TUpdateGatewayDTO) => { - return apiRequest.patch(`/api/v1/gateways/${id}`, { name }); + mutationFn: ({ id, name, projectIds }: TUpdateGatewayDTO) => { + return apiRequest.patch(`/api/v1/gateways/${id}`, { name, projectIds }); }, onSuccess: () => { - queryClient.invalidateQueries(gatewaysQueryKeys.list({})); + queryClient.invalidateQueries(gatewaysQueryKeys.list()); } }); }; diff --git a/frontend/src/hooks/api/gateways/queries.tsx b/frontend/src/hooks/api/gateways/queries.tsx index a38fe39bd..6ec374a6c 100644 --- a/frontend/src/hooks/api/gateways/queries.tsx +++ b/frontend/src/hooks/api/gateways/queries.tsx @@ -2,22 +2,31 @@ import { queryOptions } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; -import { TGateway, TListGatewayDTO } from "./types"; +import { TGateway, TListProjectGatewayDTO, TProjectGateway } from "./types"; export const gatewaysQueryKeys = { allKey: () => ["gateways"], - listKey: ({ projectId }: TListGatewayDTO) => [ + listKey: () => [...gatewaysQueryKeys.allKey(), "list"], + list: () => + queryOptions({ + queryKey: gatewaysQueryKeys.listKey(), + queryFn: async () => { + const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways"); + return data.gateways; + } + }), + listProjectGatewayKey: ({ projectId }: TListProjectGatewayDTO) => [ ...gatewaysQueryKeys.allKey(), "list", { projectId } ], - list: ({ projectId }: TListGatewayDTO = {}) => + listProjectGateways: ({ projectId }: TListProjectGatewayDTO) => queryOptions({ - queryKey: gatewaysQueryKeys.listKey({ projectId }), + queryKey: gatewaysQueryKeys.listProjectGatewayKey({ projectId }), queryFn: async () => { - const { data } = await apiRequest.get<{ gateways: TGateway[] }>("/api/v1/gateways", { - params: { projectId } - }); + const { data } = await apiRequest.get<{ gateways: TProjectGateway[] }>( + `/api/v1/gateways/projects/${projectId}` + ); return data.gateways; } }) diff --git a/frontend/src/hooks/api/gateways/types.ts b/frontend/src/hooks/api/gateways/types.ts index 42ce1ebc2..a522b6c48 100644 --- a/frontend/src/hooks/api/gateways/types.ts +++ b/frontend/src/hooks/api/gateways/types.ts @@ -11,17 +11,39 @@ export type TGateway = { name: string; id: string; }; + projects: { + name: string; + id: string; + slug: string; + }[]; +}; + +export type TProjectGateway = { + id: string; + identityId: string; + name: string; + createdAt: string; + updatedAt: string; + issuedAt: string; + serialNumber: string; + heartbeat: string; + projectGatewayId: string; + identity: { + name: string; + id: string; + }; }; export type TUpdateGatewayDTO = { id: string; name?: string; + projectIds?: string[]; }; export type TDeleteGatewayDTO = { id: string; }; -export type TListGatewayDTO = { - projectId?: string; +export type TListProjectGatewayDTO = { + projectId: string; }; diff --git a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx index efd2335e5..ed5a0ab10 100644 --- a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx +++ b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx @@ -103,7 +103,7 @@ export const OrganizationLayout = () => { {({ isActive }) => ( - + Gateways )} diff --git a/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx b/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx index 4a06534b6..d2bf32a55 100644 --- a/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx +++ b/frontend/src/pages/organization/Gateways/GatewayListPage/GatewayListPage.tsx @@ -32,6 +32,7 @@ import { Table, TableContainer, TableSkeleton, + Tag, TBody, Td, Th, @@ -127,6 +128,7 @@ export const GatewayListPage = withPermission( Name Cert Issued At + Projects Identity Health Check @@ -149,6 +151,13 @@ export const GatewayListPage = withPermission( {el.name} {format(new Date(el.issuedAt), "yyyy-MM-dd hh:mm:ss aaa")} + + {el.projects.map((projectDetails) => ( + + {projectDetails.name} + + ))} + {el.identity.name} {el.heartbeat diff --git a/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx b/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx index 1e07036ba..d828fef51 100644 --- a/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx +++ b/frontend/src/pages/organization/Gateways/GatewayListPage/components/EditGatewayDetailsModal.tsx @@ -3,9 +3,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; -import { Button, FormControl, Input } from "@app/components/v2"; -import { useUpdateGatewayById } from "@app/hooks/api"; +import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; +import { useGetUserWorkspaces, useUpdateGatewayById } from "@app/hooks/api"; import { TGateway } from "@app/hooks/api/gateways/types"; +import { ProjectType } from "@app/hooks/api/workspace/types"; type Props = { gatewayDetails: TGateway; @@ -13,7 +14,13 @@ type Props = { }; const schema = z.object({ - name: z.string() + name: z.string(), + projects: z + .object({ + id: z.string(), + name: z.string() + }) + .array() }); export type FormData = z.infer; @@ -31,13 +38,20 @@ export const EditGatewayDetailsModal = ({ gatewayDetails, onClose }: Props) => { }); const updateGatewayById = useUpdateGatewayById(); + // when gateway goes to other products switch to all + const { data: secretManagerWorkspaces, isLoading: isSecretManagerLoading } = useGetUserWorkspaces( + { + type: ProjectType.SecretManager + } + ); - const onFormSubmit = ({ name }: FormData) => { + const onFormSubmit = ({ name, projects }: FormData) => { if (isSubmitting) return; updateGatewayById.mutate( { id: gatewayDetails.id, - name + name, + projectIds: projects.map((el) => el.id) }, { onSuccess: () => { @@ -62,6 +76,30 @@ export const EditGatewayDetailsModal = ({ gatewayDetails, onClose }: Props) => { )} /> + ( + + option.id} + getOptionLabel={(option) => option.name} + /> + + )} + />