mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
chore(integration): revamp Zapier and MakeCom plugin integration (#9635)
* fix(zapier): enhance authorization flow for between Gauzy and Zapier platform * chore: upgraded zapier app version, refactor dependecies and app oauth * fix: enhance security by storing sensive credentials on server-side and improve UI * fix: update zapier model interfaces * fix: update zapier translation and model interface * fix: refactor make settings by avoiding exposing sensitive credentials * chore: add Make API resources types * feat: resolve post-install redirect url and add methods to interact with Make settings * chore: update texte translations for new added texte in all language translations * feat: add and refactor UI components for the Make settings and interactions * fix: fix typos spelling * fix: add review suggestions * fix: add review suggestions * fix: fix review suggestions from AI bots * fix: add review suggestions from AI bots * fix: add review comment suggestions from bots * fix: fix Deepscan error * fix: add review suggestions * feat: add settings for zaps and zap-template with texte translations * fix: review suggestions * fix: extend OAuth scopes
This commit is contained in:
@@ -329,6 +329,9 @@
|
||||
"iquery",
|
||||
"iqueue",
|
||||
"iremote",
|
||||
"isinvalid",
|
||||
"islinked",
|
||||
"islocked",
|
||||
"ISMTP",
|
||||
"isready",
|
||||
"isready",
|
||||
@@ -700,6 +703,7 @@
|
||||
"sslmode",
|
||||
"SSSSZ",
|
||||
"stackoverflow",
|
||||
"suberrors",
|
||||
"subquery",
|
||||
"stagecivo",
|
||||
"stagecw",
|
||||
|
||||
@@ -44,12 +44,102 @@ export interface IMakeComAuthConfig {
|
||||
postInstallUrl: string;
|
||||
}
|
||||
|
||||
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {}
|
||||
|
||||
export interface IMakeComOAuthCodeExchange {
|
||||
code: string;
|
||||
state: string; // CSRF protection
|
||||
}
|
||||
|
||||
// Make.com API Resource Types
|
||||
|
||||
export type MakeComZone = 'eu1' | 'eu2' | 'us1' | 'us2';
|
||||
|
||||
export const MAKE_COM_ZONES: MakeComZone[] = ['eu1', 'eu2', 'us1', 'us2'];
|
||||
|
||||
export interface IMakeComOrganization {
|
||||
id: number;
|
||||
name: string;
|
||||
countryId?: number;
|
||||
timezoneId?: number;
|
||||
license?: string;
|
||||
zone?: MakeComZone;
|
||||
}
|
||||
|
||||
export interface IMakeComTeam {
|
||||
id: number;
|
||||
name: string;
|
||||
organizationId: number;
|
||||
}
|
||||
|
||||
export interface IMakeComConnection {
|
||||
id: number;
|
||||
name: string;
|
||||
accountName?: string;
|
||||
accountLabel?: string;
|
||||
accountType?: string;
|
||||
packageName?: string;
|
||||
expire?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
teamId?: number;
|
||||
upgradeable?: boolean;
|
||||
scoped?: boolean;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
export interface IMakeComScenario {
|
||||
id: number;
|
||||
name: string;
|
||||
teamId: number;
|
||||
hookId?: number;
|
||||
description?: string;
|
||||
folderId?: number;
|
||||
isEnabled?: boolean;
|
||||
isPaused?: boolean;
|
||||
usedPackages?: string[];
|
||||
lastEdit?: string;
|
||||
scheduling?: IMakeComScenarioScheduling;
|
||||
islinked?: boolean;
|
||||
isinvalid?: boolean;
|
||||
islocked?: boolean;
|
||||
createdByUser?: { id: number; name: string; email: string };
|
||||
updatedByUser?: { id: number; name: string; email: string };
|
||||
}
|
||||
|
||||
export interface IMakeComScenarioScheduling {
|
||||
type: string;
|
||||
interval?: number;
|
||||
}
|
||||
|
||||
export interface IMakeComHook {
|
||||
id: number;
|
||||
name: string;
|
||||
teamId: number;
|
||||
url?: string;
|
||||
type?: string;
|
||||
typeName?: string;
|
||||
packageName?: string;
|
||||
theme?: string;
|
||||
enabled?: boolean;
|
||||
data?: Record<string, unknown>;
|
||||
queueCount?: number;
|
||||
queueLimit?: number;
|
||||
}
|
||||
|
||||
export interface IMakeComTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
usedPackages?: string[];
|
||||
public?: boolean;
|
||||
publishedDate?: string;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
export interface IMakeComSetupStatus {
|
||||
hasAccessToken: boolean;
|
||||
zone: MakeComZone | null;
|
||||
makeOrganizationId: number | null;
|
||||
makeTeamId: number | null;
|
||||
isComplete: boolean;
|
||||
}
|
||||
|
||||
@@ -62,9 +62,72 @@ export interface IZapierEndpoint {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single step in a Zapier Zap (trigger or action).
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
|
||||
*/
|
||||
export interface IZapierZapStep {
|
||||
/** Fully-qualified action identifier, e.g. "example_core:Vn7xbE60" */
|
||||
action: string;
|
||||
/** Authentication ID to use for this step, or null */
|
||||
authentication?: string | null;
|
||||
/** Input field values for this step */
|
||||
inputs?: Record<string, any> | null;
|
||||
/** Optional user-facing title for this step */
|
||||
title?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links returned alongside a Zap, such as a direct link to the Zap editor.
|
||||
*/
|
||||
export interface IZapierZapLinks {
|
||||
html_editor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Zapier Zap.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/get-zaps-[v2]
|
||||
* and https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
|
||||
*/
|
||||
export interface IZapierZap {
|
||||
type?: 'zap';
|
||||
id: string;
|
||||
title: string;
|
||||
is_enabled?: boolean;
|
||||
last_successful_run_date?: string | null;
|
||||
updated_at?: string;
|
||||
links?: IZapierZapLinks;
|
||||
steps?: IZapierZapStep[];
|
||||
|
||||
// Fields returned by the list endpoint (GET /v2/zaps)
|
||||
state?: string;
|
||||
url?: string;
|
||||
create_time?: string;
|
||||
modified_time?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for creating a new Zap via the Zapier Partner API.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
|
||||
*/
|
||||
export interface IZapierCreateZapInput {
|
||||
title: string;
|
||||
steps: IZapierZapStep[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Zapier Zap template.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zap-templates/get-zap-templates
|
||||
*/
|
||||
export interface IZapierZapTemplate {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
create_url?: string;
|
||||
}
|
||||
|
||||
export interface ICreateZapierIntegrationInput extends IBasePerTenantAndOrganizationEntityModel {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
code?: string;
|
||||
redirect_uri?: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export * from './lib/components/make-com-authorize/make-com-authorize.component';
|
||||
export * from './lib/components/make-com-callback/make-com-callback.component';
|
||||
export * from './lib/components/make-com-settings/make-com-settings.component';
|
||||
export * from './lib/components/make-com-scenarios/make-com-scenarios.component';
|
||||
export * from './lib/components/make-com-hooks/make-com-hooks.component';
|
||||
export * from './lib/components/make-com-connections/make-com-connections.component';
|
||||
export * from './lib/components/make-com-templates/make-com-templates.component';
|
||||
export * from './lib/components/make/make.component';
|
||||
export * from './lib/integration-make-com-ui.module';
|
||||
export * from './lib/integration-make-com.layout.component';
|
||||
|
||||
+21
-49
@@ -4,52 +4,24 @@
|
||||
<h5>{{ 'MENU.MAKE_COM' | translate }}</h5>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<form class="col-xl-6 col-12" [formGroup]="form" (ngSubmit)="startAuthorization()">
|
||||
<div class="form-group">
|
||||
<label for="clientId" class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CLIENT_ID' | translate }}</label>
|
||||
<input
|
||||
fullWidth
|
||||
id="clientId"
|
||||
formControlName="clientId"
|
||||
type="text"
|
||||
nbInput
|
||||
[placeholder]="'FORM.PLACEHOLDERS.MAKE_COM_CLIENT_ID' | translate"
|
||||
/>
|
||||
@if (form.get('clientId')?.touched && form.get('clientId')?.errors?.required) {
|
||||
<div
|
||||
class="text-danger"
|
||||
>
|
||||
{{ 'VALIDATION.FIELD_REQUIRED' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="clientSecret" class="label">{{
|
||||
'INTEGRATIONS.MAKE_COM_PAGE.CLIENT_SECRET' | translate
|
||||
}}</label>
|
||||
<input
|
||||
fullWidth
|
||||
id="clientSecret"
|
||||
formControlName="clientSecret"
|
||||
type="password"
|
||||
nbInput
|
||||
[placeholder]="'FORM.PLACEHOLDERS.MAKE_COM_CLIENT_SECRET' | translate"
|
||||
/>
|
||||
@if (form.get('clientSecret')?.touched && form.get('clientSecret')?.errors?.required) {
|
||||
<div
|
||||
class="text-danger"
|
||||
>
|
||||
{{ 'VALIDATION.FIELD_REQUIRED' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="hint">
|
||||
<nb-icon icon="info-outline"></nb-icon>
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.NEXT_STEP_INFO' | translate }}
|
||||
</div>
|
||||
<button nbButton status="primary" [disabled]="form.invalid" size="small" outline type="submit">
|
||||
{{ 'BUTTONS.NEXT' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<div class="col-xl-6 col-12">
|
||||
<div class="hint mb-4">
|
||||
<nb-icon icon="info-outline"></nb-icon>
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECT_INFO' | translate }}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
status="primary"
|
||||
size="small"
|
||||
[disabled]="loading"
|
||||
(click)="connectToMakeCom()"
|
||||
>
|
||||
@if (loading) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
}
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECT_TO_MAKE_COM' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
+10
-41
@@ -1,12 +1,11 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap, catchError, filter } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { MakeComService, ToastrService, Store } from '@gauzy/ui-core/core';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IMakeComCreateIntegration, IOrganization, IIntegrationTenant, IntegrationEnum } from '@gauzy/contracts';
|
||||
import { IOrganization, IIntegrationTenant, IntegrationEnum } from '@gauzy/contracts';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { IntegrationsService } from '@gauzy/ui-core/core';
|
||||
|
||||
@@ -18,15 +17,12 @@ import { IntegrationsService } from '@gauzy/ui-core/core';
|
||||
standalone: false
|
||||
})
|
||||
export class AuthorizationComponent extends TranslationBaseComponent implements OnInit {
|
||||
public form: FormGroup;
|
||||
public loading = false;
|
||||
public oauthConfig: { clientId: string; redirectUri: string } = null;
|
||||
public organization: IOrganization;
|
||||
public rememberState: boolean;
|
||||
private static readonly MAKE_DASHBOARD_ROUTE = '/pages/integrations/makecom';
|
||||
|
||||
constructor(
|
||||
private readonly _fb: FormBuilder,
|
||||
private readonly _makeComService: MakeComService,
|
||||
private readonly _toastrService: ToastrService,
|
||||
public readonly translateService: TranslateService,
|
||||
@@ -39,16 +35,7 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._initializeForm();
|
||||
this._loadOrganization();
|
||||
this._loadOAuthConfig();
|
||||
}
|
||||
|
||||
private _initializeForm() {
|
||||
this.form = this._fb.group({
|
||||
clientId: ['', [Validators.required]],
|
||||
clientSecret: ['', [Validators.required]]
|
||||
});
|
||||
}
|
||||
|
||||
private _loadOrganization() {
|
||||
@@ -62,25 +49,6 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _loadOAuthConfig() {
|
||||
this.loading = true;
|
||||
this._makeComService
|
||||
.getOAuthConfig()
|
||||
.pipe(
|
||||
tap((config) => {
|
||||
this.oauthConfig = config;
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Error loading OAuth config:', error);
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's an existing Make.com integration
|
||||
*/
|
||||
@@ -119,22 +87,22 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Start OAuth authorization flow
|
||||
* Start OAuth authorization flow.
|
||||
* No credentials needed — server uses its own env-configured client_id/secret.
|
||||
*/
|
||||
startAuthorization() {
|
||||
if (this.form.invalid || !this.organization) {
|
||||
connectToMakeCom() {
|
||||
if (!this.organization) {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_PAGE.ERRORS.INVALID_FORM'),
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.NO_ORGANIZATION'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
const credentials: IMakeComCreateIntegration = this.form.value;
|
||||
|
||||
this._makeComService
|
||||
.addOAuthSettings(credentials)
|
||||
.initializeIntegration({ organizationId: this.organization.id })
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
// Redirect to Make.com authorization page
|
||||
@@ -146,13 +114,14 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
console.error('Error starting authorization:', error);
|
||||
this.loading = false;
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<div class="connections-container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTIONS' | translate }}</h6>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="basic"
|
||||
(click)="refresh()"
|
||||
[disabled]="loading()"
|
||||
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.REFRESH_CONNECTIONS' | translate"
|
||||
>
|
||||
@if (loading()) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
} @else {
|
||||
<nb-icon icon="refresh-outline"></nb-icon>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loading()) {
|
||||
<div class="d-flex justify-content-center py-5">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Setup incomplete -->
|
||||
@if (!loading() && setupStatus() && !setupStatus()!.isComplete) {
|
||||
<nb-card accent="warning">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
|
||||
@if (!setupStatus()!.hasAccessToken) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE' | translate }}</p>
|
||||
} @else if (!setupStatus()!.zone) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE' | translate }}</p>
|
||||
} @else if (!setupStatus()!.makeOrganizationId) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION' | translate }}</p>
|
||||
} @else if (!setupStatus()!.makeTeamId) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM' | translate }}</p>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
@if (!loading() && setupStatus()?.isComplete && connections().length === 0) {
|
||||
<nb-card accent="info">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="swap-outline" class="empty-icon mb-2"></nb-icon>
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_CONNECTIONS' | translate }}</p>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
@if (!loading() && setupStatus()?.isComplete && connections().length > 0) {
|
||||
<div class="connection-list">
|
||||
@for (conn of connections(); track conn.id) {
|
||||
<nb-card class="connection-card mb-2">
|
||||
<nb-card-body class="d-flex align-items-center justify-content-between">
|
||||
<div class="connection-info">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<nb-icon icon="swap-outline" status="primary"></nb-icon>
|
||||
<span class="connection-name">{{ conn.name }}</span>
|
||||
</div>
|
||||
@if (conn.accountName) {
|
||||
<small class="text-hint d-block mt-1">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.ACCOUNT' | translate: { value: conn.accountName } }}</small>
|
||||
}
|
||||
@if (conn.accountType) {
|
||||
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.TYPE' | translate: { value: conn.accountType } }}</small>
|
||||
}
|
||||
@if (conn.packageName) {
|
||||
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.PACKAGE' | translate: { value: conn.packageName } }}</small>
|
||||
}
|
||||
@if (conn.expire) {
|
||||
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.EXPIRES' | translate: { value: (conn.expire | date: 'medium') } }}</small>
|
||||
}
|
||||
</div>
|
||||
<div class="connection-actions">
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="info"
|
||||
[nbTooltip]="'INTEGRATIONS.MAKE_COM_PAGE.TEST_CONNECTION' | translate"
|
||||
(click)="testConnection(conn)"
|
||||
[disabled]="actionLoading()[conn.id]"
|
||||
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.TEST_CONNECTION' | translate"
|
||||
>
|
||||
@if (actionLoading()[conn.id]) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
} @else {
|
||||
<nb-icon icon="checkmark-circle-outline"></nb-icon>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
.connections-container {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.connection-card {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
nb-card-body {
|
||||
padding: 1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.connection-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--gauzy-text-color-2);
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, inject, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap, catchError, finalize } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NbButtonModule, NbCardModule, NbIconModule, NbSpinnerModule, NbTooltipModule } from '@nebular/theme';
|
||||
import { IMakeComConnection, IMakeComSetupStatus } from '@gauzy/contracts';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-make-com-connections',
|
||||
templateUrl: './make-com-connections.component.html',
|
||||
styleUrls: ['./make-com-connections.component.scss'],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
NbButtonModule,
|
||||
NbCardModule,
|
||||
NbIconModule,
|
||||
NbSpinnerModule,
|
||||
NbTooltipModule
|
||||
]
|
||||
})
|
||||
export class MakeComConnectionsComponent extends TranslationBaseComponent implements OnInit {
|
||||
private readonly _makeComStoreService = inject(MakeComStoreService);
|
||||
private readonly _toastrService = inject(ToastrService);
|
||||
|
||||
public connections = signal<IMakeComConnection[]>([]);
|
||||
public loading = signal(false);
|
||||
public actionLoading = signal<Record<number, boolean>>({});
|
||||
public setupStatus = signal<IMakeComSetupStatus | null>(null);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
|
||||
private _checkSetupAndLoad() {
|
||||
this.loading.set(true);
|
||||
this._makeComStoreService
|
||||
.loadSetupStatus()
|
||||
.pipe(
|
||||
tap((status) => {
|
||||
this.setupStatus.set(status);
|
||||
if (status.isComplete) {
|
||||
this._loadConnections();
|
||||
} else {
|
||||
this.connections.set([]);
|
||||
this.loading.set(false);
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.setupStatus.set(null);
|
||||
this.connections.set([]);
|
||||
this.loading.set(false);
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _loadConnections() {
|
||||
this.loading.set(true);
|
||||
this._makeComStoreService
|
||||
.loadConnections()
|
||||
.pipe(
|
||||
tap((connections) => this.connections.set(connections)),
|
||||
catchError((error) => {
|
||||
this.connections.set([]);
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_CONNECTIONS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => this.loading.set(false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
testConnection(connection: IMakeComConnection) {
|
||||
this.actionLoading.update((prev) => ({ ...prev, [connection.id]: true }));
|
||||
this._makeComStoreService
|
||||
.testConnection(connection.id)
|
||||
.pipe(
|
||||
tap(() => {
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.CONNECTION_VALID', { name: connection.name }),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.TEST_CONNECTION'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => this.actionLoading.update((prev) => ({ ...prev, [connection.id]: false }))),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
<div class="hooks-container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOKS' | translate }}</h6>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="basic"
|
||||
(click)="refresh()"
|
||||
[disabled]="loading"
|
||||
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOKS_REFRESH' | translate"
|
||||
>
|
||||
<nb-icon icon="refresh-outline"></nb-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loading) {
|
||||
<div class="d-flex justify-content-center py-5">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Setup incomplete -->
|
||||
@if (!loading && setupStatus && !setupStatus.isComplete) {
|
||||
<nb-card accent="warning">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
|
||||
@if (!setupStatus.hasAccessToken) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE' | translate }}</p>
|
||||
} @else if (!setupStatus.zone) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE' | translate }}</p>
|
||||
} @else if (!setupStatus.makeOrganizationId) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION' | translate }}</p>
|
||||
} @else if (!setupStatus.makeTeamId) {
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM' | translate }}</p>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
@if (!loading && setupStatus?.isComplete && hooks.length === 0) {
|
||||
<nb-card accent="info">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="link-2-outline" class="empty-icon mb-2"></nb-icon>
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_HOOKS' | translate }}</p>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
@if (!loading && setupStatus?.isComplete && hooks.length > 0) {
|
||||
<div class="hook-list">
|
||||
@for (hook of hooks; track hook.id) {
|
||||
<nb-card class="hook-card mb-2">
|
||||
<nb-card-body class="d-flex align-items-center justify-content-between">
|
||||
<div class="hook-info">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<nb-icon icon="link-2-outline" [status]="hook.enabled ? 'success' : 'basic'"></nb-icon>
|
||||
<span class="hook-name">{{ hook.name }}</span>
|
||||
@if (hook.enabled) {
|
||||
<span class="badge badge-success">{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_ENABLED' | translate }}</span>
|
||||
} @else {
|
||||
<span class="badge badge-basic">{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_DISABLED' | translate }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (hook.typeName) {
|
||||
<small class="text-hint d-block mt-1">{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_TYPE' | translate }}: {{ hook.typeName }}</small>
|
||||
}
|
||||
@if (hook.url) {
|
||||
<small class="text-hint d-block hook-url">{{ hook.url }}</small>
|
||||
}
|
||||
@if (hook.queueCount != null) {
|
||||
<small class="text-hint d-block">
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_QUEUE' | translate }}: {{ hook.queueCount }}@if (hook.queueLimit != null) { / {{ hook.queueLimit }}}
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
<div class="hook-actions d-flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
[status]="hook.enabled ? 'warning' : 'success'"
|
||||
[nbTooltip]="hook.enabled ? ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_DISABLE' | translate) : ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_ENABLE' | translate)"
|
||||
(click)="toggleHook(hook)"
|
||||
[disabled]="toggleLoading[hook.id] || pingLoading[hook.id]"
|
||||
[attr.aria-label]="hook.enabled
|
||||
? ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_DISABLE' | translate)
|
||||
: ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_ENABLE' | translate)"
|
||||
>
|
||||
@if (toggleLoading[hook.id]) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
} @else {
|
||||
<nb-icon [icon]="hook.enabled ? 'toggle-right-outline' : 'toggle-left-outline'"></nb-icon>
|
||||
}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="info"
|
||||
[nbTooltip]="'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_PING' | translate"
|
||||
(click)="pingHook(hook)"
|
||||
[disabled]="toggleLoading[hook.id] || pingLoading[hook.id]"
|
||||
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_PING' | translate"
|
||||
>
|
||||
@if (pingLoading[hook.id]) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
} @else {
|
||||
<nb-icon icon="radio-outline"></nb-icon>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
.hooks-container {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.hook-card {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
nb-card-body {
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hook-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.hook-url {
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
word-break: break-all;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
|
||||
&-success {
|
||||
background-color: var(--color-success-transparent-default);
|
||||
color: var(--color-success-default);
|
||||
}
|
||||
|
||||
&-basic {
|
||||
background-color: var(--color-basic-transparent-default);
|
||||
color: var(--color-basic-600);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--color-basic-600);
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap, catchError, finalize, switchMap, timeout } from 'rxjs/operators';
|
||||
import { EMPTY, TimeoutError } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IMakeComHook, IMakeComSetupStatus } from '@gauzy/contracts';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-make-com-hooks',
|
||||
templateUrl: './make-com-hooks.component.html',
|
||||
styleUrls: ['./make-com-hooks.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class MakeComHooksComponent extends TranslationBaseComponent implements OnInit {
|
||||
public hooks: IMakeComHook[] = [];
|
||||
public loading = false;
|
||||
public toggleLoading: Record<number, boolean> = {};
|
||||
public pingLoading: Record<number, boolean> = {};
|
||||
public setupStatus: IMakeComSetupStatus | null = null;
|
||||
|
||||
private readonly _makeComStoreService = inject(MakeComStoreService);
|
||||
private readonly _toastrService = inject(ToastrService);
|
||||
|
||||
constructor(public readonly translateService: TranslateService) {
|
||||
super(translateService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
|
||||
private _checkSetupAndLoad() {
|
||||
this.loading = true;
|
||||
this.hooks = [];
|
||||
this._makeComStoreService
|
||||
.loadSetupStatus()
|
||||
.pipe(
|
||||
timeout(10_000),
|
||||
tap((status) => {
|
||||
this.setupStatus = status;
|
||||
if (status.isComplete) {
|
||||
this._loadHooks();
|
||||
} else {
|
||||
this.hooks = [];
|
||||
this.loading = false;
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.setupStatus = null;
|
||||
this.loading = false;
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _loadHooks() {
|
||||
this.loading = true;
|
||||
this._makeComStoreService
|
||||
.loadHooks()
|
||||
.pipe(
|
||||
timeout(15_000),
|
||||
tap((hooks) => (this.hooks = hooks)),
|
||||
catchError((error) => {
|
||||
this.hooks = [];
|
||||
this._toastrService.error(
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_WEBHOOKS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.loading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
toggleHook(hook: IMakeComHook) {
|
||||
this.toggleLoading[hook.id] = true;
|
||||
const action$ = hook.enabled
|
||||
? this._makeComStoreService.disableHook(hook.id)
|
||||
: this._makeComStoreService.enableHook(hook.id);
|
||||
|
||||
action$
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.TOGGLE_WEBHOOK'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
switchMap(() => this._makeComStoreService.loadHooks()),
|
||||
catchError((error) => {
|
||||
// Toggle succeeded but refresh failed — update the local hook state
|
||||
const target = this.hooks.find((h) => h.id === hook.id);
|
||||
if (target) {
|
||||
target.enabled = !hook.enabled;
|
||||
}
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_WEBHOOKS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
tap((hooks) => {
|
||||
this.hooks = hooks;
|
||||
this._toastrService.success(
|
||||
hook.enabled
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.WEBHOOK_TOGGLED_OFF')
|
||||
: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.WEBHOOK_TOGGLED_ON'),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
finalize(() => (this.toggleLoading[hook.id] = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
pingHook(hook: IMakeComHook) {
|
||||
this.pingLoading[hook.id] = true;
|
||||
this._makeComStoreService
|
||||
.pingHook(hook.id)
|
||||
.pipe(
|
||||
tap(() => {
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.WEBHOOK_PINGED', { name: hook.name }),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.PING_WEBHOOK'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.pingLoading[hook.id] = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<div class="scenarios-container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SCENARIOS' | translate }}</h6>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="basic"
|
||||
(click)="refresh()"
|
||||
[disabled]="loading()"
|
||||
[attr.aria-label]="'BUTTONS.REFRESH' | translate"
|
||||
>
|
||||
<nb-icon icon="refresh-outline"></nb-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loading()) {
|
||||
<div class="d-flex justify-content-center py-5">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Setup incomplete -->
|
||||
@if (!loading() && setupStatus() && !setupStatus()!.isComplete) {
|
||||
<nb-card accent="warning">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
|
||||
@if (setupMessageKey()) {
|
||||
<p>{{ setupMessageKey() | translate }}</p>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
<!-- Empty state -->
|
||||
@if (!loading() && setupStatus()?.isComplete && scenarios().length === 0) {
|
||||
<nb-card accent="info">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="flash-off-outline" class="empty-icon mb-2"></nb-icon>
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_SCENARIOS' | translate }}</p>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
<!-- Scenario list -->
|
||||
@if (!loading() && setupStatus()?.isComplete && scenarios().length > 0) {
|
||||
<div class="scenario-list">
|
||||
@for (scenario of scenarios(); track scenario.id) {
|
||||
<nb-card class="scenario-card mb-2">
|
||||
<nb-card-body class="d-flex align-items-center justify-content-between">
|
||||
<div class="scenario-info">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<nb-icon icon="flash-outline" [status]="scenario.isEnabled ? 'success' : 'basic'"></nb-icon>
|
||||
<span class="scenario-name">{{ scenario.name }}</span>
|
||||
@if (scenario.isEnabled) {
|
||||
<span class="badge badge-success">
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_ACTIVE' | translate }}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="badge badge-basic">
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_INACTIVE' | translate }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@if (scenario.description) {
|
||||
<small class="text-hint d-block mt-1">{{ scenario.description }}</small>
|
||||
}
|
||||
@if (scenario.lastEdit) {
|
||||
<small class="text-hint d-block">
|
||||
{{
|
||||
'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_LAST_EDITED'
|
||||
| translate: { value: (scenario.lastEdit | date: 'medium') }
|
||||
}}
|
||||
</small>
|
||||
}
|
||||
@if (scenario.usedPackages?.length) {
|
||||
<div class="packages mt-1">
|
||||
@for (pkg of scenario.usedPackages; track pkg) {
|
||||
<span class="package-tag">{{ pkg }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="scenario-actions d-flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
[status]="scenario.isEnabled ? 'warning' : 'success'"
|
||||
[nbTooltip]="scenario.isEnabled ? ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_DISABLE' | translate) : ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_ENABLE' | translate)"
|
||||
(click)="toggleScenario(scenario)"
|
||||
[disabled]="actionLoading()[scenario.id]"
|
||||
[attr.aria-label]="scenario.isEnabled
|
||||
? ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_DISABLE' | translate)
|
||||
: ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_ENABLE' | translate)"
|
||||
>
|
||||
@if (actionLoading()[scenario.id]) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
} @else {
|
||||
<nb-icon [icon]="scenario.isEnabled ? 'pause-circle-outline' : 'play-circle-outline'"></nb-icon>
|
||||
}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="info"
|
||||
[nbTooltip]="'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_RUN_ONCE' | translate"
|
||||
(click)="runScenario(scenario)"
|
||||
[disabled]="actionLoading()[scenario.id]"
|
||||
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_RUN_ONCE' | translate"
|
||||
>
|
||||
<nb-icon icon="arrow-right-outline"></nb-icon>
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
.scenarios-container {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.scenario-card {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
nb-card-body {
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.scenario-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
|
||||
&-success {
|
||||
background-color: var(--color-success-transparent-default);
|
||||
color: var(--color-success-default);
|
||||
}
|
||||
|
||||
&-basic {
|
||||
background-color: var(--color-basic-transparent-default);
|
||||
color: var(--color-basic-600);
|
||||
}
|
||||
}
|
||||
|
||||
.package-tag {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
margin-right: 0.25rem;
|
||||
border-radius: 3px;
|
||||
background-color: var(--color-basic-transparent-default);
|
||||
color: var(--color-basic-600);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--color-basic-600);
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, computed, inject, signal } from '@angular/core';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap, catchError, finalize, switchMap } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { IMakeComScenario, IMakeComSetupStatus } from '@gauzy/contracts';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-make-com-scenarios',
|
||||
templateUrl: './make-com-scenarios.component.html',
|
||||
styleUrls: ['./make-com-scenarios.component.scss'],
|
||||
standalone: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MakeComScenariosComponent extends TranslationBaseComponent implements OnInit {
|
||||
private readonly _makeComStoreService = inject(MakeComStoreService);
|
||||
private readonly _toastrService = inject(ToastrService);
|
||||
|
||||
public scenarios = signal<IMakeComScenario[]>([]);
|
||||
public loading = signal(false);
|
||||
public actionLoading = signal<Record<number, boolean>>({});
|
||||
public setupStatus = signal<IMakeComSetupStatus | null>(null);
|
||||
|
||||
public setupMessageKey = computed<string | null>(() => {
|
||||
const status = this.setupStatus();
|
||||
if (!status || status.isComplete) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!status.hasAccessToken) {
|
||||
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE';
|
||||
}
|
||||
|
||||
if (!status.zone) {
|
||||
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE';
|
||||
}
|
||||
|
||||
if (!status.makeOrganizationId) {
|
||||
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION';
|
||||
}
|
||||
|
||||
if (!status.makeTeamId) {
|
||||
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM';
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
|
||||
private _checkSetupAndLoad() {
|
||||
this.loading.set(true);
|
||||
this.scenarios.set([]);
|
||||
this._makeComStoreService
|
||||
.loadSetupStatus()
|
||||
.pipe(
|
||||
tap((status) => {
|
||||
this.setupStatus.set(status);
|
||||
if (status.isComplete) {
|
||||
this._loadScenarios();
|
||||
} else {
|
||||
this.scenarios.set([]);
|
||||
this.loading.set(false);
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.setupStatus.set(null);
|
||||
this.scenarios.set([]);
|
||||
this.loading.set(false);
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _loadScenarios() {
|
||||
this.loading.set(true);
|
||||
this._makeComStoreService
|
||||
.loadScenarios()
|
||||
.pipe(
|
||||
tap((scenarios) => this.scenarios.set(scenarios)),
|
||||
catchError((error) => {
|
||||
this.scenarios.set([]);
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SCENARIOS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => this.loading.set(false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
toggleScenario(scenario: IMakeComScenario) {
|
||||
this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: true }));
|
||||
const action$ = scenario.isEnabled
|
||||
? this._makeComStoreService.stopScenario(scenario.id)
|
||||
: this._makeComStoreService.startScenario(scenario.id);
|
||||
|
||||
action$
|
||||
.pipe(
|
||||
switchMap(() => this._makeComStoreService.loadScenarios()),
|
||||
tap((scenarios) => {
|
||||
this.scenarios.set(scenarios);
|
||||
this._toastrService.success(
|
||||
scenario.isEnabled
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.SCENARIO_DEACTIVATED')
|
||||
: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.SCENARIO_ACTIVATED'),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.TOGGLE_SCENARIO'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: false }))),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
runScenario(scenario: IMakeComScenario) {
|
||||
this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: true }));
|
||||
this._makeComStoreService
|
||||
.runScenario(scenario.id)
|
||||
.pipe(
|
||||
tap(() => {
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.SCENARIO_EXECUTED'),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.RUN_SCENARIO'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: false }))),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
}
|
||||
+154
-30
@@ -3,41 +3,164 @@
|
||||
<h5>{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.TITLE' | translate }}</h5>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
@if (loading) {
|
||||
<div class="d-flex justify-content-center">
|
||||
@if (loading && !settings) {
|
||||
<div class="d-flex justify-content-center py-4">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
}
|
||||
@if (!loading) {
|
||||
<form class="col-xl-6 col-12" [formGroup]="form" (ngSubmit)="saveSettings()">
|
||||
} @else {
|
||||
<!-- Zone & Context Setup -->
|
||||
<div class="setup-section mb-4">
|
||||
<h6 class="section-title">
|
||||
<nb-icon icon="globe-outline" class="mr-1"></nb-icon>
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONFIGURATION_TITLE' | translate }}
|
||||
</h6>
|
||||
|
||||
<!-- Zone Selector -->
|
||||
<div class="form-group">
|
||||
<nb-checkbox formControlName="isEnabled">
|
||||
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.ENABLE_INTEGRATION' | translate }}
|
||||
</nb-checkbox>
|
||||
<label id="zone-picker-label" class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.ZONE_LABEL' | translate }}</label>
|
||||
<div class="d-flex align-items-center gap-2" role="radiogroup" aria-labelledby="zone-picker-label">
|
||||
@for (zone of zones; track zone) {
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
[status]="selectedZone === zone ? 'primary' : 'basic'"
|
||||
size="small"
|
||||
(click)="onZoneChange(zone)"
|
||||
[disabled]="zoneLoading"
|
||||
role="radio"
|
||||
[attr.aria-checked]="selectedZone === zone"
|
||||
>
|
||||
{{ zone | uppercase }}
|
||||
</button>
|
||||
}
|
||||
@if (zoneLoading) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
}
|
||||
</div>
|
||||
<small class="text-hint mt-1 d-block">
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.ZONE_HINT' | translate }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webhookUrl" class="label">
|
||||
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
type="url"
|
||||
id="webhookUrl"
|
||||
formControlName="webhookUrl"
|
||||
[placeholder]="'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_PLACEHOLDER' | translate"
|
||||
|
||||
<!-- Organization Selector -->
|
||||
@if (selectedZone) {
|
||||
<div class="form-group">
|
||||
<label class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.ORGANIZATION_LABEL' | translate }}</label>
|
||||
@if (orgsLoading) {
|
||||
<div class="d-flex align-items-center gap-2 py-1">
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
<small class="text-hint">{{ 'INTEGRATIONS.MAKE_COM_PAGE.LOADING_ORGANIZATIONS' | translate }}</small>
|
||||
</div>
|
||||
} @else if (makeOrganizations.length === 0) {
|
||||
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_ORGANIZATIONS' | translate }}</small>
|
||||
} @else {
|
||||
<div class="selector-list" role="listbox" [attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.ORGANIZATION_LABEL' | translate">
|
||||
@for (org of makeOrganizations; track org.id) {
|
||||
<div
|
||||
role="option"
|
||||
class="selector-item"
|
||||
[class.selected]="selectedMakeOrg?.id === org.id"
|
||||
[attr.aria-selected]="selectedMakeOrg?.id === org.id"
|
||||
tabindex="0"
|
||||
(click)="onMakeOrgSelect(org)"
|
||||
(keydown.enter)="onMakeOrgSelect(org)"
|
||||
(keydown.space)="$event.preventDefault(); onMakeOrgSelect(org)"
|
||||
>
|
||||
<nb-icon
|
||||
[icon]="selectedMakeOrg?.id === org.id ? 'checkmark-circle-2-outline' : 'radio-button-off-outline'"
|
||||
[status]="selectedMakeOrg?.id === org.id ? 'success' : 'basic'"
|
||||
></nb-icon>
|
||||
<span>{{ org.name }}</span>
|
||||
@if (org.zone) {
|
||||
<small class="text-hint ml-auto">{{ org.zone }}</small>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Team Selector -->
|
||||
@if (selectedMakeOrg) {
|
||||
<div class="form-group">
|
||||
<label class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEAM_LABEL' | translate }}</label>
|
||||
@if (teamsLoading) {
|
||||
<div class="d-flex align-items-center gap-2 py-1">
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
<small class="text-hint">{{ 'INTEGRATIONS.MAKE_COM_PAGE.LOADING_TEAMS' | translate }}</small>
|
||||
</div>
|
||||
} @else if (makeTeams.length === 0) {
|
||||
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_TEAMS' | translate }}</small>
|
||||
} @else {
|
||||
<div class="selector-list" role="listbox" [attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.TEAM_LABEL' | translate">
|
||||
@for (team of makeTeams; track team.id) {
|
||||
<div
|
||||
role="option"
|
||||
class="selector-item"
|
||||
[class.selected]="selectedMakeTeam?.id === team.id"
|
||||
[attr.aria-selected]="selectedMakeTeam?.id === team.id"
|
||||
tabindex="0"
|
||||
(click)="onMakeTeamSelect(team)"
|
||||
(keydown.enter)="onMakeTeamSelect(team)"
|
||||
(keydown.space)="$event.preventDefault(); onMakeTeamSelect(team)"
|
||||
>
|
||||
<nb-icon
|
||||
[icon]="selectedMakeTeam?.id === team.id ? 'checkmark-circle-2-outline' : 'radio-button-off-outline'"
|
||||
[status]="selectedMakeTeam?.id === team.id ? 'success' : 'basic'"
|
||||
></nb-icon>
|
||||
<span>{{ team.name }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (isSetupComplete) {
|
||||
<nb-card accent="success" class="setup-complete-card mt-2">
|
||||
<nb-card-body class="py-2 d-flex align-items-center gap-2">
|
||||
<nb-icon icon="checkmark-circle-2-outline" status="success"></nb-icon>
|
||||
<span class="fw-bold">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTED_TO' | translate: { org: selectedMakeOrg.name, team: selectedMakeTeam.name, zone: (selectedZone | uppercase) } }}</span>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
</div>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<!-- Webhook Settings -->
|
||||
<div class="webhook-section">
|
||||
<h6 class="section-title">
|
||||
<nb-icon icon="link-2-outline" class="mr-1"></nb-icon>
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_SETTINGS_TITLE' | translate }}
|
||||
</h6>
|
||||
|
||||
<form class="col-xl-6 col-12 px-0" [formGroup]="form" (ngSubmit)="saveSettings()">
|
||||
<div class="form-group">
|
||||
<nb-checkbox formControlName="isEnabled">
|
||||
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.ENABLE_INTEGRATION' | translate }}
|
||||
</nb-checkbox>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webhookUrl" class="label">
|
||||
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
type="url"
|
||||
id="webhookUrl"
|
||||
formControlName="webhookUrl"
|
||||
[placeholder]="'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_PLACEHOLDER' | translate"
|
||||
/>
|
||||
@if (form.get('webhookUrl').invalid && form.get('webhookUrl').touched) {
|
||||
<div class="text-danger">
|
||||
<div class="text-danger mt-1">
|
||||
@if (form.get('webhookUrl').errors?.required) {
|
||||
<span>
|
||||
{{ 'VALIDATION.FIELD_REQUIRED' | translate }}
|
||||
</span>
|
||||
<small>{{ 'VALIDATION.FIELD_REQUIRED' | translate }}</small>
|
||||
}
|
||||
@if (form.get('webhookUrl').errors?.pattern) {
|
||||
<span>
|
||||
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_INVALID' | translate }}
|
||||
</span>
|
||||
<small>{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_INVALID' | translate }}</small>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -45,12 +168,13 @@
|
||||
<div class="actions">
|
||||
<button nbButton status="primary" type="submit" [disabled]="form.invalid || loading">
|
||||
@if (loading) {
|
||||
<nb-icon icon="loader-outline" pack="eva"></nb-icon>
|
||||
<nb-icon icon="loader-outline" pack="eva" class="spin"></nb-icon>
|
||||
}
|
||||
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.SAVE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
</div>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
+54
-6
@@ -6,12 +6,6 @@
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--danger-color);
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -23,3 +17,57 @@
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.selector-list {
|
||||
border: 1px solid var(--gauzy-card-border);
|
||||
border-radius: calc(var(--border-radius) * 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selector-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
border-bottom: 1px solid var(--gauzy-card-border);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gauzy-card-hover);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--gauzy-sidebar-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
.setup-complete-card {
|
||||
nb-card-body {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
+230
-19
@@ -1,25 +1,47 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap, catchError } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { MakeComStoreService } from '@gauzy/ui-core/core';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { tap, catchError, finalize, timeout } from 'rxjs/operators';
|
||||
import { EMPTY, TimeoutError } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IMakeComIntegrationSettings } from '@gauzy/contracts';
|
||||
import { ToastrService } from '@gauzy/ui-core/core';
|
||||
import {
|
||||
IMakeComIntegrationSettings,
|
||||
IMakeComOrganization,
|
||||
IMakeComTeam,
|
||||
MakeComZone,
|
||||
MAKE_COM_ZONES
|
||||
} from '@gauzy/contracts';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-make-com-settings',
|
||||
templateUrl: './make-com-settings.component.html',
|
||||
styleUrls: ['./make-com-settings.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class MakeComSettingsComponent extends TranslationBaseComponent implements OnInit {
|
||||
// Webhook settings form
|
||||
public form: FormGroup;
|
||||
public loading = false;
|
||||
public settings: IMakeComIntegrationSettings = null;
|
||||
|
||||
// Zone & context
|
||||
public zones = MAKE_COM_ZONES;
|
||||
public selectedZone: MakeComZone | null = null;
|
||||
public zoneLoading = false;
|
||||
|
||||
// Make.com organizations
|
||||
public makeOrganizations: IMakeComOrganization[] = [];
|
||||
public selectedMakeOrg: IMakeComOrganization | null = null;
|
||||
public orgsLoading = false;
|
||||
|
||||
// Make.com teams
|
||||
public makeTeams: IMakeComTeam[] = [];
|
||||
public selectedMakeTeam: IMakeComTeam | null = null;
|
||||
public teamsLoading = false;
|
||||
|
||||
constructor(
|
||||
private readonly _fb: FormBuilder,
|
||||
private readonly _makeComStoreService: MakeComStoreService,
|
||||
@@ -32,8 +54,11 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
|
||||
ngOnInit() {
|
||||
this._initializeForm();
|
||||
this._loadSettings();
|
||||
this._loadSetupStatus();
|
||||
}
|
||||
|
||||
// ─── Webhook Settings ───────────────────────────────────────────────────
|
||||
|
||||
private _initializeForm() {
|
||||
this.form = this._fb.group({
|
||||
isEnabled: [false],
|
||||
@@ -46,6 +71,7 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
|
||||
this._makeComStoreService
|
||||
.loadIntegrationSettings()
|
||||
.pipe(
|
||||
timeout(10_000),
|
||||
tap((settings: IMakeComIntegrationSettings) => {
|
||||
this.settings = settings;
|
||||
this.form.patchValue({
|
||||
@@ -54,23 +80,20 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
|
||||
});
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETTINGS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETTINGS');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
console.error('Error loading Make.com settings:', error);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.loading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Make.com integration settings
|
||||
*/
|
||||
saveSettings() {
|
||||
if (this.form.invalid) {
|
||||
this._toastrService.error(
|
||||
@@ -99,10 +122,198 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
|
||||
console.error('Error saving Make.com settings:', error);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.loading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
// ─── Setup Status & Zone Configuration ──────────────────────────────────
|
||||
|
||||
/**
|
||||
* Load setup status to restore previously saved zone, org, and team.
|
||||
*/
|
||||
private _loadSetupStatus() {
|
||||
this.zoneLoading = true;
|
||||
this._makeComStoreService
|
||||
.loadSetupStatus()
|
||||
.pipe(
|
||||
timeout(10_000),
|
||||
tap((status) => {
|
||||
this.selectedZone = status.zone as MakeComZone;
|
||||
|
||||
if (status.zone) {
|
||||
this._loadMakeOrganizations(status.makeOrganizationId, status.makeTeamId);
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
console.error('Error loading Make.com setup status:', error);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.zoneLoading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
onZoneChange(zone: MakeComZone) {
|
||||
this.zoneLoading = true;
|
||||
this._makeComStoreService
|
||||
.setZone(zone)
|
||||
.pipe(
|
||||
timeout(10_000),
|
||||
tap(() => {
|
||||
this.selectedZone = zone;
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.ZONE_SET', { zone: zone.toUpperCase() }),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
this.selectedMakeOrg = null;
|
||||
this.selectedMakeTeam = null;
|
||||
this.makeOrganizations = [];
|
||||
this.makeTeams = [];
|
||||
this._loadMakeOrganizations();
|
||||
}),
|
||||
catchError((error) => {
|
||||
const message =
|
||||
error instanceof TimeoutError ? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT') : error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.SET_ZONE');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.zoneLoading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
// ─── Make.com Organizations ─────────────────────────────────────────────
|
||||
|
||||
private _loadMakeOrganizations(savedOrgId?: number, savedTeamId?: number) {
|
||||
this.orgsLoading = true;
|
||||
this._makeComStoreService
|
||||
.loadMakeOrganizations()
|
||||
.pipe(
|
||||
timeout(15_000),
|
||||
tap((orgs) => {
|
||||
this.makeOrganizations = orgs;
|
||||
if (savedOrgId) {
|
||||
const savedOrg = orgs.find((o) => o.id === savedOrgId);
|
||||
if (savedOrg) {
|
||||
this.selectedMakeOrg = savedOrg;
|
||||
this._loadMakeTeams(savedOrgId, savedTeamId);
|
||||
}
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_ORGANIZATIONS');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.orgsLoading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
onMakeOrgSelect(org: IMakeComOrganization) {
|
||||
this.orgsLoading = true;
|
||||
this._makeComStoreService
|
||||
.selectMakeOrganization(org)
|
||||
.pipe(
|
||||
timeout(10_000),
|
||||
tap(() => {
|
||||
this.selectedMakeOrg = org;
|
||||
this.selectedMakeTeam = null;
|
||||
this.makeTeams = [];
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.ORGANIZATION_SELECTED', { name: org.name }),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
this._loadMakeTeams(org.id);
|
||||
}),
|
||||
catchError((error) => {
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.SELECT_ORGANIZATION');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.orgsLoading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
// ─── Make.com Teams ─────────────────────────────────────────────────────
|
||||
|
||||
private _loadMakeTeams(makeOrgId: number, savedTeamId?: number) {
|
||||
this.teamsLoading = true;
|
||||
this._makeComStoreService
|
||||
.loadMakeTeams(makeOrgId)
|
||||
.pipe(
|
||||
timeout(15_000),
|
||||
tap((teams) => {
|
||||
this.makeTeams = teams;
|
||||
if (savedTeamId) {
|
||||
const savedTeam = teams.find((t) => t.id === savedTeamId);
|
||||
if (savedTeam) {
|
||||
this.selectedMakeTeam = savedTeam;
|
||||
}
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_TEAMS');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.teamsLoading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
onMakeTeamSelect(team: IMakeComTeam) {
|
||||
this.teamsLoading = true;
|
||||
this._makeComStoreService
|
||||
.selectMakeTeam(team)
|
||||
.pipe(
|
||||
timeout(10_000),
|
||||
tap(() => {
|
||||
this.selectedMakeTeam = team;
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.TEAM_SELECTED', { name: team.name }),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
const message =
|
||||
error instanceof TimeoutError
|
||||
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
|
||||
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.SELECT_TEAM');
|
||||
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.teamsLoading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
// ─── Computed Flags ─────────────────────────────────────────────────────
|
||||
|
||||
get isSetupComplete(): boolean {
|
||||
return !!this.selectedZone && !!this.selectedMakeOrg && !!this.selectedMakeTeam;
|
||||
}
|
||||
}
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<div class="templates-container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEMPLATES' | translate }}</h6>
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
size="small"
|
||||
status="basic"
|
||||
(click)="refresh()"
|
||||
[disabled]="loading"
|
||||
[attr.aria-label]="'BUTTONS.REFRESH' | translate"
|
||||
>
|
||||
<nb-icon icon="refresh-outline"></nb-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loading) {
|
||||
<div class="d-flex justify-content-center py-5">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
} @else if (setupMessageKey) {
|
||||
<nb-card accent="warning">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
|
||||
<p>{{ setupMessageKey | translate }}</p>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
@if (!loading && setupStatus?.isComplete && templates.length === 0) {
|
||||
<nb-card accent="info">
|
||||
<nb-card-body class="text-center py-4">
|
||||
<nb-icon icon="file-text-outline" class="empty-icon mb-2"></nb-icon>
|
||||
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_TEMPLATES' | translate }}</p>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
|
||||
@if (!loading && setupStatus?.isComplete && templates.length > 0) {
|
||||
<div class="template-list">
|
||||
@for (template of templates; track template.id) {
|
||||
<nb-card class="template-card mb-2">
|
||||
<nb-card-body>
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<nb-icon icon="file-text-outline" status="primary" class="mt-1"></nb-icon>
|
||||
<div class="template-info">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="template-name">{{ template.name }}</span>
|
||||
@if (template.public) {
|
||||
<span class="badge badge-info">{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEMPLATE_PUBLIC' | translate }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (template.description) {
|
||||
<small class="text-hint d-block mt-1">{{ template.description }}</small>
|
||||
}
|
||||
@if (template.publishedDate) {
|
||||
<small class="text-hint d-block">
|
||||
{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEMPLATE_PUBLISHED' | translate }}: {{ template.publishedDate | date: 'mediumDate' }}
|
||||
</small>
|
||||
}
|
||||
@if (template.usedPackages?.length) {
|
||||
<div class="packages mt-1">
|
||||
@for (pkg of template.usedPackages; track pkg) {
|
||||
<span class="package-tag">{{ pkg }}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
.templates-container {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
nb-card-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.template-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: calc(var(--border-radius) * 0.5);
|
||||
font-weight: 500;
|
||||
|
||||
&-info {
|
||||
background-color: var(--gauzy-card-2);
|
||||
color: var(--gauzy-text-color-1);
|
||||
}
|
||||
}
|
||||
|
||||
.package-tag {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin-right: 0.25rem;
|
||||
border-radius: calc(var(--border-radius) * 0.375);
|
||||
background-color: var(--gauzy-card-2);
|
||||
color: var(--gauzy-text-color-2);
|
||||
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--gauzy-text-color-2);
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap, catchError, finalize } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IMakeComTemplate, IMakeComSetupStatus } from '@gauzy/contracts';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-make-com-templates',
|
||||
templateUrl: './make-com-templates.component.html',
|
||||
styleUrls: ['./make-com-templates.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class MakeComTemplatesComponent extends TranslationBaseComponent implements OnInit {
|
||||
public templates: IMakeComTemplate[] = [];
|
||||
public loading = false;
|
||||
public setupStatus: IMakeComSetupStatus | null = null;
|
||||
|
||||
public get setupMessageKey(): string | null {
|
||||
if (this.loading) return null;
|
||||
const s = this.setupStatus;
|
||||
if (!s || s.isComplete) return null;
|
||||
if (!s.hasAccessToken) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE';
|
||||
if (!s.zone) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE';
|
||||
if (!s.makeOrganizationId) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION';
|
||||
if (!s.makeTeamId) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM';
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _makeComStoreService: MakeComStoreService,
|
||||
private readonly _toastrService: ToastrService,
|
||||
public readonly translateService: TranslateService
|
||||
) {
|
||||
super(translateService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
|
||||
private _checkSetupAndLoad() {
|
||||
this.loading = true;
|
||||
this.templates = [];
|
||||
this._makeComStoreService
|
||||
.loadSetupStatus()
|
||||
.pipe(
|
||||
tap((status) => {
|
||||
this.setupStatus = status;
|
||||
if (status.isComplete) {
|
||||
this._loadTemplates();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.setupStatus = null;
|
||||
this.loading = false;
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _loadTemplates() {
|
||||
this.loading = true;
|
||||
this._makeComStoreService
|
||||
.loadTemplates()
|
||||
.pipe(
|
||||
tap((templates) => (this.templates = templates)),
|
||||
catchError((error) => {
|
||||
this._toastrService.error(
|
||||
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_TEMPLATES'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.loading = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this._checkSetupAndLoad();
|
||||
}
|
||||
}
|
||||
+12
-24
@@ -56,16 +56,10 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route for tab navigation
|
||||
*/
|
||||
getRoute(tabName: string) {
|
||||
return `./${tabName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load tabs for Make.com integration
|
||||
*/
|
||||
private _loadTabs() {
|
||||
this.tabs = [
|
||||
{
|
||||
@@ -75,16 +69,22 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
|
||||
route: this.getRoute('scenarios')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.EXECUTIONS'),
|
||||
icon: 'activity-outline',
|
||||
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOKS'),
|
||||
icon: 'link-2-outline',
|
||||
responsive: true,
|
||||
route: this.getRoute('executions')
|
||||
route: this.getRoute('hooks')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.HISTORY'),
|
||||
icon: 'clock-outline',
|
||||
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.CONNECTIONS'),
|
||||
icon: 'swap-outline',
|
||||
responsive: true,
|
||||
route: this.getRoute('history')
|
||||
route: this.getRoute('connections')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.TEMPLATES'),
|
||||
icon: 'file-text-outline',
|
||||
responsive: true,
|
||||
route: this.getRoute('templates')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SETTINGS'),
|
||||
@@ -95,9 +95,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load menu items for Make.com integration
|
||||
*/
|
||||
private _loadMenus() {
|
||||
this.menus = [
|
||||
{
|
||||
@@ -113,9 +110,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load integration settings
|
||||
*/
|
||||
private _loadIntegrationSettings() {
|
||||
if (!this.organization) return;
|
||||
|
||||
@@ -144,9 +138,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translations when language changes
|
||||
*/
|
||||
private _applyTranslationOnTabsActions() {
|
||||
this.translateService.onLangChange
|
||||
.pipe(
|
||||
@@ -159,9 +150,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the "Integrations" page
|
||||
*/
|
||||
navigateToIntegrations(): void {
|
||||
this._router.navigate(['/pages/integrations']);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
NbActionsModule,
|
||||
NbAlertModule,
|
||||
NbBadgeModule,
|
||||
NbButtonModule,
|
||||
NbCalendarKitModule,
|
||||
NbCardModule,
|
||||
@@ -24,9 +26,15 @@ import { AuthorizationComponent } from './components/make-com-authorize/make-com
|
||||
import { MakeComponent } from './components/make/make.component';
|
||||
import { MakeComCallbackComponent } from './components/make-com-callback/make-com-callback.component';
|
||||
import { MakeComSettingsComponent } from './components/make-com-settings/make-com-settings.component';
|
||||
import { MakeComScenariosComponent } from './components/make-com-scenarios/make-com-scenarios.component';
|
||||
import { MakeComHooksComponent } from './components/make-com-hooks/make-com-hooks.component';
|
||||
import { MakeComTemplatesComponent } from './components/make-com-templates/make-com-templates.component';
|
||||
import { MakeComConnectionsComponent } from './components/make-com-connections/make-com-connections.component';
|
||||
|
||||
const NB_MODULES = [
|
||||
NbActionsModule,
|
||||
NbAlertModule,
|
||||
NbBadgeModule,
|
||||
NbButtonModule,
|
||||
NbSpinnerModule,
|
||||
NbCalendarKitModule,
|
||||
@@ -44,6 +52,7 @@ const NB_MODULES = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
...NB_MODULES,
|
||||
TranslateModule.forChild(),
|
||||
IntegrationMakeComRoutes,
|
||||
@@ -51,14 +60,17 @@ const NB_MODULES = [
|
||||
SelectorsModule,
|
||||
SharedModule,
|
||||
TableComponentsModule,
|
||||
CommonModule
|
||||
MakeComConnectionsComponent
|
||||
],
|
||||
declarations: [
|
||||
IntegrationMakeComLayoutComponent,
|
||||
MakeComponent,
|
||||
AuthorizationComponent,
|
||||
MakeComponent,
|
||||
MakeComCallbackComponent,
|
||||
MakeComSettingsComponent
|
||||
MakeComSettingsComponent,
|
||||
MakeComScenariosComponent,
|
||||
MakeComHooksComponent,
|
||||
MakeComTemplatesComponent
|
||||
]
|
||||
})
|
||||
export class IntegrationMakeComUiModule {}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { IntegrationMakeComLayoutComponent } from './integration-make-com.layout
|
||||
import { MakeComponent } from './components/make/make.component';
|
||||
import { MakeComCallbackComponent } from './components/make-com-callback/make-com-callback.component';
|
||||
import { MakeComSettingsComponent } from './components/make-com-settings/make-com-settings.component';
|
||||
import { MakeComScenariosComponent } from './components/make-com-scenarios/make-com-scenarios.component';
|
||||
import { MakeComHooksComponent } from './components/make-com-hooks/make-com-hooks.component';
|
||||
import { MakeComConnectionsComponent } from './components/make-com-connections/make-com-connections.component';
|
||||
import { MakeComTemplatesComponent } from './components/make-com-templates/make-com-templates.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -38,15 +42,29 @@ import { MakeComSettingsComponent } from './components/make-com-settings/make-co
|
||||
},
|
||||
{
|
||||
path: 'scenarios',
|
||||
loadChildren: () => import('@gauzy/ui-core/shared').then((m) => m.WorkInProgressModule)
|
||||
component: MakeComScenariosComponent
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
loadChildren: () => import('@gauzy/ui-core/shared').then((m) => m.WorkInProgressModule)
|
||||
redirectTo: 'hooks',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'history',
|
||||
loadChildren: () => import('@gauzy/ui-core/shared').then((m) => m.WorkInProgressModule)
|
||||
redirectTo: 'connections',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'hooks',
|
||||
component: MakeComHooksComponent
|
||||
},
|
||||
{
|
||||
path: 'connections',
|
||||
component: MakeComConnectionsComponent
|
||||
},
|
||||
{
|
||||
path: 'templates',
|
||||
component: MakeComTemplatesComponent
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Make.com API response and request interfaces.
|
||||
*
|
||||
* These model the Make.com REST API v2 resources that Gauzy interacts with.
|
||||
* @see https://developers.make.com/api-documentation/api-reference
|
||||
*/
|
||||
|
||||
// ─── Pagination ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComPaginationParams {
|
||||
/** Number of records to skip */
|
||||
'pg[offset]'?: number;
|
||||
/** Max results per page */
|
||||
'pg[limit]'?: number;
|
||||
/** Field to sort on */
|
||||
'pg[sortBy]'?: string;
|
||||
/** Sort direction */
|
||||
'pg[sortDir]'?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface IMakeComPaginatedResponse<T> {
|
||||
[key: string]: T[] | IMakeComPagination;
|
||||
pg: IMakeComPagination;
|
||||
}
|
||||
|
||||
export interface IMakeComPagination {
|
||||
offset: number;
|
||||
limit: number;
|
||||
sortBy: string;
|
||||
sortDir: string;
|
||||
}
|
||||
|
||||
// ─── Organization ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComOrganization {
|
||||
id: number;
|
||||
name: string;
|
||||
countryId?: number;
|
||||
timezoneId?: number;
|
||||
license?: string;
|
||||
zone?: string;
|
||||
}
|
||||
|
||||
// ─── Team ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComTeam {
|
||||
id: number;
|
||||
name: string;
|
||||
organizationId: number;
|
||||
}
|
||||
|
||||
// ─── Connection ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComConnection {
|
||||
id: number;
|
||||
name: string;
|
||||
accountName?: string;
|
||||
accountLabel?: string;
|
||||
accountType?: string;
|
||||
packageName?: string;
|
||||
expire?: string;
|
||||
metadata?: Record<string, any>;
|
||||
teamId?: number;
|
||||
upgradeable?: boolean;
|
||||
scoped?: boolean;
|
||||
accountTheme?: string;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
// ─── Scenario ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComScenario {
|
||||
id: number;
|
||||
name: string;
|
||||
teamId: number;
|
||||
hookId?: number;
|
||||
deviceId?: number;
|
||||
deviceScope?: string;
|
||||
description?: string;
|
||||
folderId?: number;
|
||||
isEnabled?: boolean;
|
||||
isPaused?: boolean;
|
||||
usedPackages?: string[];
|
||||
lastEdit?: string;
|
||||
scheduling?: IMakeComScenarioScheduling;
|
||||
islinked?: boolean;
|
||||
isinvalid?: boolean;
|
||||
islocked?: boolean;
|
||||
createdByUser?: { id: number; name: string; email: string };
|
||||
updatedByUser?: { id: number; name: string; email: string };
|
||||
}
|
||||
|
||||
export interface IMakeComScenarioScheduling {
|
||||
type: string;
|
||||
interval?: number;
|
||||
}
|
||||
|
||||
export interface IMakeComCreateScenarioParams {
|
||||
teamId: number;
|
||||
name: string;
|
||||
blueprint: string; // JSON string
|
||||
scheduling: IMakeComScenarioScheduling;
|
||||
folderId?: number;
|
||||
}
|
||||
|
||||
export interface IMakeComUpdateScenarioParams {
|
||||
name?: string;
|
||||
blueprint?: string;
|
||||
scheduling?: IMakeComScenarioScheduling;
|
||||
}
|
||||
|
||||
// ─── Hook (Webhook) ─────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComHook {
|
||||
id: number;
|
||||
name: string;
|
||||
teamId: number;
|
||||
url?: string;
|
||||
type?: string;
|
||||
typeName?: string;
|
||||
packageName?: string;
|
||||
theme?: string;
|
||||
enabled?: boolean;
|
||||
data?: Record<string, any>;
|
||||
queueCount?: number;
|
||||
queueLimit?: number;
|
||||
}
|
||||
|
||||
export interface IMakeComCreateHookParams {
|
||||
teamId: number;
|
||||
name: string;
|
||||
typeName: string;
|
||||
/** Connection-related parameters */
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// ─── Template ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
usedPackages?: string[];
|
||||
public?: boolean;
|
||||
publishedDate?: string;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
// ─── User ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComUser {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
language?: string;
|
||||
locale?: string;
|
||||
timezoneId?: number;
|
||||
countryId?: number;
|
||||
}
|
||||
|
||||
// ─── API Error ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IMakeComApiError {
|
||||
message: string;
|
||||
code?: string;
|
||||
suberrors?: Array<{ message: string; path: string }>;
|
||||
}
|
||||
|
||||
// ─── Zone Config ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Available Make.com zones.
|
||||
* The zone determines the API base URL (e.g., us2.make.com/api/v2).
|
||||
*/
|
||||
export type MakeComZone = 'eu1' | 'eu2' | 'us1' | 'us2';
|
||||
|
||||
export const MAKE_COM_ZONES: MakeComZone[] = ['eu1', 'eu2', 'us1', 'us2'];
|
||||
|
||||
/**
|
||||
* Builds the Make.com API base URL for a given zone.
|
||||
*/
|
||||
export function getMakeApiBaseUrl(zone: MakeComZone): string {
|
||||
return `https://${zone}.make.com/api/v2`;
|
||||
}
|
||||
@@ -14,6 +14,12 @@ export enum MakeSettingName {
|
||||
CLIENT_ID = 'client_id',
|
||||
CLIENT_SECRET = 'client_secret',
|
||||
AUTH_CODE = 'auth_code',
|
||||
/** The Make.com zone for API calls, e.g. "us2", "eu1" */
|
||||
ZONE = 'make_zone',
|
||||
/** The Make.com organization ID (their side, not Gauzy) */
|
||||
MAKE_ORGANIZATION_ID = 'make_organization_id',
|
||||
/** The Make.com team ID (their side, not Gauzy) */
|
||||
MAKE_TEAM_ID = 'make_team_id',
|
||||
}
|
||||
|
||||
// Define the return type for the settings
|
||||
@@ -51,10 +57,7 @@ export interface IMakeComAuthConfig {
|
||||
postInstallUrl: string;
|
||||
}
|
||||
|
||||
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
}
|
||||
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {}
|
||||
|
||||
export interface IMakeComOAuthCodeExchange {
|
||||
code: string;
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
ParseIntPipe,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
BadRequestException
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger';
|
||||
import { TenantPermissionGuard, PermissionGuard, Permissions } from '@gauzy/core';
|
||||
import { PermissionsEnum } from '@gauzy/contracts';
|
||||
import { MakeComApiService } from './make-com-api.service';
|
||||
import { IMakeComPaginationParams, IMakeComScenarioScheduling, MAKE_COM_ZONES, MakeComZone } from './interfaces/make-com-api.model';
|
||||
|
||||
@ApiTags('Make.com API')
|
||||
@UseGuards(TenantPermissionGuard, PermissionGuard)
|
||||
@Permissions(PermissionsEnum.INTEGRATION_ADD, PermissionsEnum.INTEGRATION_EDIT)
|
||||
@Controller('/integration/make-com/api')
|
||||
export class MakeComApiController {
|
||||
constructor(private readonly makeComApiService: MakeComApiService) {}
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
private extractPagination(query: Record<string, any>): IMakeComPaginationParams {
|
||||
const pagination: IMakeComPaginationParams = {};
|
||||
if (query.offset != null) {
|
||||
const offset = Number.parseInt(query.offset, 10);
|
||||
if (!Number.isNaN(offset)) pagination['pg[offset]'] = offset;
|
||||
}
|
||||
if (query.limit != null) {
|
||||
const limit = Number.parseInt(query.limit, 10);
|
||||
if (!Number.isNaN(limit)) pagination['pg[limit]'] = limit;
|
||||
}
|
||||
if (query.sortBy) pagination['pg[sortBy]'] = query.sortBy;
|
||||
if (query.sortDir) pagination['pg[sortDir]'] = query.sortDir;
|
||||
return pagination;
|
||||
}
|
||||
|
||||
// ─── Setup Status ───────────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'Get Make.com setup status (zone, org, team, token)' })
|
||||
@Get('/setup-status')
|
||||
async getSetupStatus(@Query('organizationId') organizationId?: string) {
|
||||
return this.makeComApiService.getSetupStatus(organizationId);
|
||||
}
|
||||
|
||||
// ─── Zone Configuration ─────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'Get available Make.com zones' })
|
||||
@Get('/zones')
|
||||
getAvailableZones() {
|
||||
return { zones: MAKE_COM_ZONES };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get the configured zone for this tenant' })
|
||||
@Get('/zone')
|
||||
async getZone(@Query('organizationId') organizationId?: string) {
|
||||
const zone = await this.makeComApiService.getZone(organizationId);
|
||||
return { zone };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Set the Make.com zone for this tenant' })
|
||||
@Post('/zone')
|
||||
async setZone(@Body() body: { zone: MakeComZone; organizationId?: string }) {
|
||||
await this.makeComApiService.setZone(body.zone, body.organizationId);
|
||||
return { success: true, zone: body.zone };
|
||||
}
|
||||
|
||||
// ─── Context (Make.com org/team selection) ──────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'Set the Make.com organization ID for this tenant' })
|
||||
@Post('/context/organization')
|
||||
async setMakeOrganization(@Body() body: { makeOrganizationId: number; organizationId?: string }) {
|
||||
if (!body.makeOrganizationId || Number.isNaN(Number(body.makeOrganizationId))) {
|
||||
throw new BadRequestException('A valid makeOrganizationId is required');
|
||||
}
|
||||
await this.makeComApiService.setMakeOrganizationId(body.makeOrganizationId, body.organizationId);
|
||||
return { success: true, makeOrganizationId: body.makeOrganizationId };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Set the Make.com team ID for this tenant' })
|
||||
@Post('/context/team')
|
||||
async setMakeTeam(@Body() body: { makeTeamId: number; organizationId?: string }) {
|
||||
if (!body.makeTeamId || Number.isNaN(Number(body.makeTeamId))) {
|
||||
throw new BadRequestException('A valid makeTeamId is required');
|
||||
}
|
||||
await this.makeComApiService.setMakeTeamId(body.makeTeamId, body.organizationId);
|
||||
return { success: true, makeTeamId: body.makeTeamId };
|
||||
}
|
||||
|
||||
// ─── Organizations ──────────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'List Make.com organizations for the authenticated user' })
|
||||
@Get('/organizations')
|
||||
async listOrganizations(@Query('organizationId') organizationId?: string) {
|
||||
const organizations = await this.makeComApiService.listOrganizations(organizationId);
|
||||
return { organizations };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get a Make.com organization by ID' })
|
||||
@Get('/organizations/:id')
|
||||
async getOrganization(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const organization = await this.makeComApiService.getOrganization(id, organizationId);
|
||||
return { organization };
|
||||
}
|
||||
|
||||
// ─── Teams ──────────────────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'List Make.com teams' })
|
||||
@ApiQuery({ name: 'makeOrgId', required: false, type: Number })
|
||||
@Get('/teams')
|
||||
async listTeams(
|
||||
@Query('makeOrgId') makeOrgId?: string,
|
||||
@Query('organizationId') organizationId?: string,
|
||||
@Query() query?: Record<string, any>
|
||||
) {
|
||||
const parsedOrgId = makeOrgId ? Number.parseInt(makeOrgId, 10) : undefined;
|
||||
const validOrgId = parsedOrgId !== undefined && !Number.isNaN(parsedOrgId) ? parsedOrgId : undefined;
|
||||
const teams = await this.makeComApiService.listTeams(
|
||||
validOrgId,
|
||||
organizationId,
|
||||
this.extractPagination(query)
|
||||
);
|
||||
return { teams };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get a Make.com team by ID' })
|
||||
@Get('/teams/:id')
|
||||
async getTeam(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const team = await this.makeComApiService.getTeam(id, organizationId);
|
||||
return { team };
|
||||
}
|
||||
|
||||
// ─── Connections ────────────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'List Make.com connections' })
|
||||
@ApiQuery({ name: 'teamId', required: false, type: Number })
|
||||
@Get('/connections')
|
||||
async listConnections(
|
||||
@Query('teamId') teamId?: string,
|
||||
@Query('organizationId') organizationId?: string,
|
||||
@Query() query?: Record<string, any>
|
||||
) {
|
||||
const connections = await this.makeComApiService.listConnections(
|
||||
teamId ? Number.parseInt(teamId, 10) : undefined,
|
||||
organizationId,
|
||||
this.extractPagination(query)
|
||||
);
|
||||
return { connections };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get a Make.com connection by ID' })
|
||||
@Get('/connections/:id')
|
||||
async getConnection(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const connection = await this.makeComApiService.getConnection(id, organizationId);
|
||||
return { connection };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Delete a Make.com connection' })
|
||||
@Delete('/connections/:id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async deleteConnection(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
await this.makeComApiService.deleteConnection(id, organizationId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Test a Make.com connection' })
|
||||
@Post('/connections/:id/test')
|
||||
async testConnection(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
return this.makeComApiService.testConnection(id, organizationId);
|
||||
}
|
||||
|
||||
// ─── Scenarios ──────────────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'List Make.com scenarios' })
|
||||
@ApiQuery({ name: 'teamId', required: false, type: Number })
|
||||
@Get('/scenarios')
|
||||
async listScenarios(
|
||||
@Query('teamId') teamId?: string,
|
||||
@Query('organizationId') organizationId?: string,
|
||||
@Query() query?: Record<string, any>
|
||||
) {
|
||||
const scenarios = await this.makeComApiService.listScenarios(
|
||||
teamId ? Number.parseInt(teamId, 10) : undefined,
|
||||
organizationId,
|
||||
this.extractPagination(query)
|
||||
);
|
||||
return { scenarios };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get a Make.com scenario by ID' })
|
||||
@Get('/scenarios/:id')
|
||||
async getScenario(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const scenario = await this.makeComApiService.getScenario(id, organizationId);
|
||||
return { scenario };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Create a Make.com scenario' })
|
||||
@Post('/scenarios')
|
||||
async createScenario(
|
||||
@Body() body: { teamId: number; name: string; blueprint: string; scheduling: IMakeComScenarioScheduling; folderId?: number },
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const scenario = await this.makeComApiService.createScenario(body, organizationId);
|
||||
return { scenario };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Update a Make.com scenario' })
|
||||
@Patch('/scenarios/:id')
|
||||
async updateScenario(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() body: { name?: string; blueprint?: string; scheduling?: IMakeComScenarioScheduling },
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const scenario = await this.makeComApiService.updateScenario(id, body, organizationId);
|
||||
return { scenario };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Delete a Make.com scenario' })
|
||||
@Delete('/scenarios/:id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async deleteScenario(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
await this.makeComApiService.deleteScenario(id, organizationId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Activate a Make.com scenario' })
|
||||
@Post('/scenarios/:id/start')
|
||||
async startScenario(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const scenario = await this.makeComApiService.startScenario(id, organizationId);
|
||||
return { scenario };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Deactivate a Make.com scenario' })
|
||||
@Post('/scenarios/:id/stop')
|
||||
async stopScenario(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const scenario = await this.makeComApiService.stopScenario(id, organizationId);
|
||||
return { scenario };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Run a Make.com scenario on demand' })
|
||||
@Post('/scenarios/:id/run')
|
||||
async runScenario(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() body?: { responsive?: boolean; data?: any },
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
return this.makeComApiService.runScenario(id, organizationId, body);
|
||||
}
|
||||
|
||||
// ─── Hooks (Webhooks) ───────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'List Make.com hooks (webhooks)' })
|
||||
@ApiQuery({ name: 'teamId', required: false, type: Number })
|
||||
@Get('/hooks')
|
||||
async listHooks(
|
||||
@Query('teamId') teamId?: string,
|
||||
@Query('organizationId') organizationId?: string,
|
||||
@Query() query?: Record<string, any>
|
||||
) {
|
||||
const hooks = await this.makeComApiService.listHooks(
|
||||
teamId ? Number.parseInt(teamId, 10) : undefined,
|
||||
organizationId,
|
||||
this.extractPagination(query)
|
||||
);
|
||||
return { hooks };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get a Make.com hook by ID' })
|
||||
@Get('/hooks/:id')
|
||||
async getHook(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const hook = await this.makeComApiService.getHook(id, organizationId);
|
||||
return { hook };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Create a Make.com hook (webhook)' })
|
||||
@Post('/hooks')
|
||||
async createHook(
|
||||
@Body() body: { teamId: number; name: string; typeName: string; [key: string]: unknown },
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const hook = await this.makeComApiService.createHook(body, organizationId);
|
||||
return { hook };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Update a Make.com hook name' })
|
||||
@Patch('/hooks/:id')
|
||||
async updateHook(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Body() body: { name: string },
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const hook = await this.makeComApiService.updateHook(id, body.name, organizationId);
|
||||
return { hook };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Delete a Make.com hook' })
|
||||
@Delete('/hooks/:id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async deleteHook(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
await this.makeComApiService.deleteHook(id, organizationId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Ping a Make.com hook' })
|
||||
@Get('/hooks/:id/ping')
|
||||
async pingHook(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
return this.makeComApiService.pingHook(id, organizationId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Enable a Make.com hook' })
|
||||
@Post('/hooks/:id/enable')
|
||||
async enableHook(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
await this.makeComApiService.enableHook(id, organizationId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Disable a Make.com hook' })
|
||||
@Post('/hooks/:id/disable')
|
||||
async disableHook(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
await this.makeComApiService.disableHook(id, organizationId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// ─── Templates ──────────────────────────────────────────────────────────
|
||||
|
||||
@ApiOperation({ summary: 'List Make.com templates' })
|
||||
@ApiQuery({ name: 'teamId', required: false, type: Number })
|
||||
@Get('/templates')
|
||||
async listTemplates(
|
||||
@Query('teamId') teamId?: string,
|
||||
@Query('organizationId') organizationId?: string,
|
||||
@Query() query?: Record<string, any>
|
||||
) {
|
||||
const templates = await this.makeComApiService.listTemplates(
|
||||
teamId ? Number.parseInt(teamId, 10) : undefined,
|
||||
organizationId,
|
||||
this.extractPagination(query)
|
||||
);
|
||||
return { templates };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get a Make.com template by ID' })
|
||||
@Get('/templates/:id')
|
||||
async getTemplate(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
const template = await this.makeComApiService.getTemplate(id, organizationId);
|
||||
return { template };
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: 'Get the blueprint of a Make.com template' })
|
||||
@Get('/templates/:id/blueprint')
|
||||
async getTemplateBlueprint(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Query('organizationId') organizationId?: string
|
||||
) {
|
||||
return this.makeComApiService.getTemplateBlueprint(id, organizationId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
import { Injectable, Logger, BadRequestException, NotFoundException, HttpException, HttpStatus, UnauthorizedException } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import { firstValueFrom, catchError } from 'rxjs';
|
||||
import { IntegrationEnum } from '@gauzy/contracts';
|
||||
import { IntegrationSettingService, IntegrationTenantService, RequestContext, IntegrationTenant } from '@gauzy/core';
|
||||
import { MakeSettingName } from './interfaces/make-com.model';
|
||||
import {
|
||||
getMakeApiBaseUrl,
|
||||
IMakeComOrganization,
|
||||
IMakeComTeam,
|
||||
IMakeComConnection,
|
||||
IMakeComScenario,
|
||||
IMakeComHook,
|
||||
IMakeComTemplate,
|
||||
IMakeComCreateScenarioParams,
|
||||
IMakeComUpdateScenarioParams,
|
||||
IMakeComCreateHookParams,
|
||||
IMakeComPaginationParams,
|
||||
MakeComZone
|
||||
} from './interfaces/make-com-api.model';
|
||||
import { MakeComOAuthService } from './make-com-oauth.service';
|
||||
|
||||
/**
|
||||
* Service for interacting with the Make.com REST API v2.
|
||||
*
|
||||
* Handles zone-based URL resolution, token authentication (with auto-refresh),
|
||||
* and provides methods for each Make.com resource.
|
||||
*
|
||||
* @see https://developers.make.com/api-documentation/api-reference
|
||||
*/
|
||||
@Injectable()
|
||||
export class MakeComApiService {
|
||||
private readonly logger = new Logger(MakeComApiService.name);
|
||||
private readonly refreshPromises = new Map<string, Promise<string>>();
|
||||
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly integrationTenantService: IntegrationTenantService,
|
||||
private readonly integrationSettingService: IntegrationSettingService,
|
||||
private readonly makeComOAuthService: MakeComOAuthService
|
||||
) {}
|
||||
|
||||
// ─── Internal Helpers ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Resolve the integration tenant for the current request context.
|
||||
*/
|
||||
private async getIntegrationTenant(organizationId?: string) {
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new NotFoundException('Tenant ID not found in request context');
|
||||
}
|
||||
|
||||
const where: any = { name: IntegrationEnum.MakeCom, tenantId };
|
||||
if (organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
}
|
||||
|
||||
const tenant = await this.integrationTenantService.findOneByOptions({
|
||||
where,
|
||||
relations: ['settings']
|
||||
});
|
||||
|
||||
if (!tenant) {
|
||||
throw new NotFoundException('Make.com integration not found for this tenant');
|
||||
}
|
||||
return tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single setting value from the integration tenant.
|
||||
*/
|
||||
private getSettingValue(integrationTenant: IntegrationTenant, name: string): string | null {
|
||||
const setting = integrationTenant.settings?.find((s) => s.settingsName === name);
|
||||
return setting?.settingsValue ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access token, refreshing if expired.
|
||||
*/
|
||||
private async getValidAccessToken(integrationTenant: IntegrationTenant): Promise<string> {
|
||||
const accessToken = this.getSettingValue(integrationTenant, MakeSettingName.ACCESS_TOKEN);
|
||||
const expiresAt = this.getSettingValue(integrationTenant, MakeSettingName.EXPIRES_AT);
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BadRequestException('Make.com access token not found. Please authorize first.');
|
||||
}
|
||||
|
||||
// Check if the token is expired or about to expire (30s buffer)
|
||||
if (expiresAt && new Date(expiresAt).getTime() - 30_000 < Date.now()) {
|
||||
this.logger.log('Access token expired or near expiry, refreshing...');
|
||||
|
||||
// Serialize refresh per tenant to avoid race conditions with rotating tokens
|
||||
const tenantKey = integrationTenant.id as string;
|
||||
const existing = this.refreshPromises.get(tenantKey);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const refreshPromise = (async () => {
|
||||
await this.makeComOAuthService.refreshToken(integrationTenant.id);
|
||||
const updated = await this.integrationTenantService.findOneByOptions({
|
||||
where: { id: integrationTenant.id },
|
||||
relations: ['settings']
|
||||
});
|
||||
const newToken = this.getSettingValue(updated, MakeSettingName.ACCESS_TOKEN);
|
||||
if (!newToken) {
|
||||
throw new BadRequestException('Failed to refresh Make.com access token');
|
||||
}
|
||||
return newToken;
|
||||
})();
|
||||
|
||||
this.refreshPromises.set(tenantKey, refreshPromise);
|
||||
try {
|
||||
return await refreshPromise;
|
||||
} finally {
|
||||
this.refreshPromises.delete(tenantKey);
|
||||
}
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the zone API base URL.
|
||||
*/
|
||||
private getZoneBaseUrl(integrationTenant: IntegrationTenant): string {
|
||||
const zone = this.getSettingValue(integrationTenant, MakeSettingName.ZONE) as MakeComZone;
|
||||
if (!zone) {
|
||||
throw new BadRequestException(
|
||||
'Make.com zone is not configured. Please set your zone (e.g., "us2", "eu1") first.'
|
||||
);
|
||||
}
|
||||
return getMakeApiBaseUrl(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an authenticated request to the Make.com API.
|
||||
*/
|
||||
private async request<T>(
|
||||
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT',
|
||||
path: string,
|
||||
integrationTenant: IntegrationTenant,
|
||||
options?: { data?: any; params?: Record<string, any>; timeout?: number }
|
||||
): Promise<T> {
|
||||
const baseUrl = this.getZoneBaseUrl(integrationTenant);
|
||||
const accessToken = await this.getValidAccessToken(integrationTenant);
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
method,
|
||||
url: `${baseUrl}${path}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
params: options?.params,
|
||||
data: options?.data,
|
||||
timeout: options?.timeout ?? 15_000
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.request<T>(config).pipe(
|
||||
catchError((error: AxiosError) => {
|
||||
const status = error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
const data = error.response?.data as any;
|
||||
const msg = data?.message || error.message;
|
||||
this.logger.error(`Make.com API ${method} ${path} failed [${status}]: ${msg}`);
|
||||
if (status === 401) {
|
||||
throw new UnauthorizedException(`Make.com authentication failed: ${msg}`);
|
||||
}
|
||||
if (status === 404) {
|
||||
throw new NotFoundException(`Make.com resource not found: ${msg}`);
|
||||
}
|
||||
throw new HttpException(`Make.com API error: ${msg}`, status);
|
||||
})
|
||||
)
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
this.logger.error(`Unexpected error calling Make.com API: ${error.message}`);
|
||||
throw new HttpException(`Make.com API error: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pagination query params.
|
||||
*/
|
||||
private buildPaginationParams(pagination?: IMakeComPaginationParams): Record<string, any> {
|
||||
if (!pagination) return {};
|
||||
const params: Record<string, any> = {};
|
||||
if (pagination['pg[offset]'] != null) params['pg[offset]'] = pagination['pg[offset]'];
|
||||
if (pagination['pg[limit]'] != null) params['pg[limit]'] = pagination['pg[limit]'];
|
||||
if (pagination['pg[sortBy]']) params['pg[sortBy]'] = pagination['pg[sortBy]'];
|
||||
if (pagination['pg[sortDir]']) params['pg[sortDir]'] = pagination['pg[sortDir]'];
|
||||
return params;
|
||||
}
|
||||
|
||||
// ─── Zone & Context Management ──────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Set the Make.com zone for the current tenant.
|
||||
*/
|
||||
async setZone(zone: MakeComZone, organizationId?: string): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const existing = tenant.settings?.find((s) => s.settingsName === MakeSettingName.ZONE);
|
||||
|
||||
if (existing) {
|
||||
existing.settingsValue = zone;
|
||||
await this.integrationSettingService.save(existing);
|
||||
} else {
|
||||
await this.integrationSettingService.save({
|
||||
settingsName: MakeSettingName.ZONE,
|
||||
settingsValue: zone,
|
||||
integration: tenant
|
||||
} as any);
|
||||
}
|
||||
// Clear zone-scoped org/team selections only when zone actually changes
|
||||
if (existing?.settingsValue !== zone) {
|
||||
const existingOrg = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_ORGANIZATION_ID);
|
||||
if (existingOrg?.id) {
|
||||
await this.integrationSettingService.delete(existingOrg.id);
|
||||
}
|
||||
const existingTeam = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_TEAM_ID);
|
||||
if (existingTeam?.id) {
|
||||
await this.integrationSettingService.delete(existingTeam.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`Make.com zone set to "${zone}" for tenant ${tenant.tenantId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current zone for the tenant.
|
||||
*/
|
||||
async getZone(organizationId?: string): Promise<string | null> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
return this.getSettingValue(tenant, MakeSettingName.ZONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the Make.com organization ID for this tenant.
|
||||
*/
|
||||
async setMakeOrganizationId(makeOrgId: number, organizationId?: string): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const existing = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_ORGANIZATION_ID);
|
||||
|
||||
if (existing) {
|
||||
existing.settingsValue = makeOrgId.toString();
|
||||
await this.integrationSettingService.save(existing);
|
||||
} else {
|
||||
await this.integrationSettingService.save({
|
||||
settingsName: MakeSettingName.MAKE_ORGANIZATION_ID,
|
||||
settingsValue: makeOrgId.toString(),
|
||||
integration: tenant
|
||||
} as any);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the Make.com team ID for this tenant.
|
||||
*/
|
||||
async setMakeTeamId(makeTeamId: number, organizationId?: string): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const existing = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_TEAM_ID);
|
||||
|
||||
if (existing) {
|
||||
existing.settingsValue = makeTeamId.toString();
|
||||
await this.integrationSettingService.save(existing);
|
||||
} else {
|
||||
await this.integrationSettingService.save({
|
||||
settingsName: MakeSettingName.MAKE_TEAM_ID,
|
||||
settingsValue: makeTeamId.toString(),
|
||||
integration: tenant
|
||||
} as any);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current setup status: zone, orgId, teamId, and whether an access token exists.
|
||||
*/
|
||||
async getSetupStatus(organizationId?: string): Promise<{
|
||||
hasAccessToken: boolean;
|
||||
zone: MakeComZone | null;
|
||||
makeOrganizationId: number | null;
|
||||
makeTeamId: number | null;
|
||||
isComplete: boolean;
|
||||
}> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const accessToken = this.getSettingValue(tenant, MakeSettingName.ACCESS_TOKEN);
|
||||
const zone = this.getSettingValue(tenant, MakeSettingName.ZONE);
|
||||
const makeOrgId = this.getSettingValue(tenant, MakeSettingName.MAKE_ORGANIZATION_ID);
|
||||
const makeTeamId = this.getSettingValue(tenant, MakeSettingName.MAKE_TEAM_ID);
|
||||
|
||||
const parsedOrgId = makeOrgId ? Number.parseInt(makeOrgId, 10) : null;
|
||||
const parsedTeamId = makeTeamId ? Number.parseInt(makeTeamId, 10) : null;
|
||||
|
||||
return {
|
||||
hasAccessToken: !!accessToken,
|
||||
zone: zone as MakeComZone | null,
|
||||
makeOrganizationId: parsedOrgId !== null && !Number.isNaN(parsedOrgId) ? parsedOrgId : null,
|
||||
makeTeamId: parsedTeamId !== null && !Number.isNaN(parsedTeamId) ? parsedTeamId : null,
|
||||
isComplete: !!accessToken && !!zone && !!makeOrgId && !!makeTeamId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the stored Make.com organization ID.
|
||||
*/
|
||||
private async getMakeOrganizationId(integrationTenant: IntegrationTenant): Promise<number> {
|
||||
const value = this.getSettingValue(integrationTenant, MakeSettingName.MAKE_ORGANIZATION_ID);
|
||||
if (!value) {
|
||||
throw new BadRequestException('Make.com organization ID is not configured. Please select an organization first.');
|
||||
}
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (Number.isNaN(parsed)) {
|
||||
throw new BadRequestException('Invalid Make.com organization ID stored in settings.');
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the stored Make.com team ID.
|
||||
*/
|
||||
private async getMakeTeamId(integrationTenant: IntegrationTenant): Promise<number> {
|
||||
const value = this.getSettingValue(integrationTenant, MakeSettingName.MAKE_TEAM_ID);
|
||||
if (!value) {
|
||||
throw new BadRequestException('Make.com team ID is not configured. Please select a team first.');
|
||||
}
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
if (Number.isNaN(parsed)) {
|
||||
throw new BadRequestException('Invalid Make.com team ID stored in settings.');
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// ─── Organizations ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List organizations the authenticated user has access to.
|
||||
*/
|
||||
async listOrganizations(organizationId?: string): Promise<IMakeComOrganization[]> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ organizations: IMakeComOrganization[] }>(
|
||||
'GET', '/organizations', tenant
|
||||
);
|
||||
return result.organizations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single organization by its Make.com ID.
|
||||
*/
|
||||
async getOrganization(makeOrgId: number, organizationId?: string): Promise<IMakeComOrganization> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ organization: IMakeComOrganization }>(
|
||||
'GET', `/organizations/${makeOrgId}`, tenant
|
||||
);
|
||||
return result.organization;
|
||||
}
|
||||
|
||||
// ─── Teams ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List teams within a Make.com organization.
|
||||
*/
|
||||
async listTeams(
|
||||
makeOrgId?: number,
|
||||
organizationId?: string,
|
||||
pagination?: IMakeComPaginationParams
|
||||
): Promise<IMakeComTeam[]> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const orgId = makeOrgId ?? await this.getMakeOrganizationId(tenant);
|
||||
const params = { organizationId: orgId, ...this.buildPaginationParams(pagination) };
|
||||
const result = await this.request<{ teams: IMakeComTeam[] }>(
|
||||
'GET', '/teams', tenant, { params }
|
||||
);
|
||||
return result.teams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single team by ID.
|
||||
*/
|
||||
async getTeam(teamId: number, organizationId?: string): Promise<IMakeComTeam> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ team: IMakeComTeam }>(
|
||||
'GET', `/teams/${teamId}`, tenant
|
||||
);
|
||||
return result.team;
|
||||
}
|
||||
|
||||
// ─── Connections ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List connections for a team.
|
||||
*/
|
||||
async listConnections(
|
||||
teamId?: number,
|
||||
organizationId?: string,
|
||||
pagination?: IMakeComPaginationParams
|
||||
): Promise<IMakeComConnection[]> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const resolvedTeamId = teamId ?? await this.getMakeTeamId(tenant);
|
||||
const params = { teamId: resolvedTeamId, ...this.buildPaginationParams(pagination) };
|
||||
const result = await this.request<{ connections: IMakeComConnection[] }>(
|
||||
'GET', '/connections', tenant, { params }
|
||||
);
|
||||
return result.connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single connection by ID.
|
||||
*/
|
||||
async getConnection(connectionId: number, organizationId?: string): Promise<IMakeComConnection> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ connection: IMakeComConnection }>(
|
||||
'GET', `/connections/${connectionId}`, tenant
|
||||
);
|
||||
return result.connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a connection.
|
||||
*/
|
||||
async deleteConnection(connectionId: number, organizationId?: string): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
await this.request<void>('DELETE', `/connections/${connectionId}`, tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test (verify) a connection.
|
||||
*/
|
||||
async testConnection(connectionId: number, organizationId?: string): Promise<any> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
return this.request<any>('POST', `/connections/${connectionId}/test`, tenant);
|
||||
}
|
||||
|
||||
// ─── Scenarios ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List scenarios for a team.
|
||||
*/
|
||||
async listScenarios(
|
||||
teamId?: number,
|
||||
organizationId?: string,
|
||||
pagination?: IMakeComPaginationParams
|
||||
): Promise<IMakeComScenario[]> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const resolvedTeamId = teamId ?? await this.getMakeTeamId(tenant);
|
||||
const params = { teamId: resolvedTeamId, ...this.buildPaginationParams(pagination) };
|
||||
const result = await this.request<{ scenarios: IMakeComScenario[] }>(
|
||||
'GET', '/scenarios', tenant, { params }
|
||||
);
|
||||
return result.scenarios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single scenario by ID.
|
||||
*/
|
||||
async getScenario(scenarioId: number, organizationId?: string): Promise<IMakeComScenario> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ scenario: IMakeComScenario }>(
|
||||
'GET', `/scenarios/${scenarioId}`, tenant
|
||||
);
|
||||
return result.scenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new scenario.
|
||||
*/
|
||||
async createScenario(params: IMakeComCreateScenarioParams, organizationId?: string): Promise<IMakeComScenario> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ scenario: IMakeComScenario }>(
|
||||
'POST', '/scenarios', tenant, { data: params }
|
||||
);
|
||||
return result.scenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a scenario.
|
||||
*/
|
||||
async updateScenario(
|
||||
scenarioId: number,
|
||||
params: IMakeComUpdateScenarioParams,
|
||||
organizationId?: string
|
||||
): Promise<IMakeComScenario> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ scenario: IMakeComScenario }>(
|
||||
'PATCH', `/scenarios/${scenarioId}`, tenant, { data: params }
|
||||
);
|
||||
return result.scenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a scenario.
|
||||
*/
|
||||
async deleteScenario(scenarioId: number, organizationId?: string, confirmed = true): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
await this.request<void>('DELETE', `/scenarios/${scenarioId}`, tenant, {
|
||||
params: { confirmed }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate (start) a scenario.
|
||||
*/
|
||||
async startScenario(scenarioId: number, organizationId?: string): Promise<IMakeComScenario> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ scenario: IMakeComScenario }>(
|
||||
'POST', `/scenarios/${scenarioId}/start`, tenant
|
||||
);
|
||||
return result.scenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate (stop) a scenario.
|
||||
*/
|
||||
async stopScenario(scenarioId: number, organizationId?: string): Promise<IMakeComScenario> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ scenario: IMakeComScenario }>(
|
||||
'POST', `/scenarios/${scenarioId}/stop`, tenant
|
||||
);
|
||||
return result.scenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a scenario on demand.
|
||||
*/
|
||||
async runScenario(
|
||||
scenarioId: number,
|
||||
organizationId?: string,
|
||||
options?: { responsive?: boolean; data?: any }
|
||||
): Promise<any> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
return this.request<any>('POST', `/scenarios/${scenarioId}/run`, tenant, {
|
||||
data: options?.data,
|
||||
params: options?.responsive ? { responsive: true } : undefined,
|
||||
timeout: options?.responsive ? 60_000 : undefined
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Hooks (Webhooks) ───────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List hooks for a team.
|
||||
*/
|
||||
async listHooks(
|
||||
teamId?: number,
|
||||
organizationId?: string,
|
||||
pagination?: IMakeComPaginationParams
|
||||
): Promise<IMakeComHook[]> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const resolvedTeamId = teamId ?? await this.getMakeTeamId(tenant);
|
||||
const params = { teamId: resolvedTeamId, ...this.buildPaginationParams(pagination) };
|
||||
const result = await this.request<{ hooks: IMakeComHook[] }>(
|
||||
'GET', '/hooks', tenant, { params }
|
||||
);
|
||||
return result.hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single hook by ID.
|
||||
*/
|
||||
async getHook(hookId: number, organizationId?: string): Promise<IMakeComHook> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ hook: IMakeComHook }>(
|
||||
'GET', `/hooks/${hookId}`, tenant
|
||||
);
|
||||
return result.hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a hook (webhook).
|
||||
*/
|
||||
async createHook(params: IMakeComCreateHookParams, organizationId?: string): Promise<IMakeComHook> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ hook: IMakeComHook }>(
|
||||
'POST', '/hooks', tenant, { data: params }
|
||||
);
|
||||
return result.hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a hook.
|
||||
*/
|
||||
async deleteHook(hookId: number, organizationId?: string, confirmed = true): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
await this.request<void>('DELETE', `/hooks/${hookId}`, tenant, {
|
||||
params: { confirmed }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a hook name.
|
||||
*/
|
||||
async updateHook(hookId: number, name: string, organizationId?: string): Promise<IMakeComHook> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ hook: IMakeComHook }>(
|
||||
'PATCH', `/hooks/${hookId}`, tenant, { data: { name } }
|
||||
);
|
||||
return result.hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping a hook (test).
|
||||
*/
|
||||
async pingHook(hookId: number, organizationId?: string): Promise<any> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
return this.request<any>('GET', `/hooks/${hookId}/ping`, tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a hook.
|
||||
*/
|
||||
async enableHook(hookId: number, organizationId?: string): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
await this.request<void>('POST', `/hooks/${hookId}/enable`, tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a hook.
|
||||
*/
|
||||
async disableHook(hookId: number, organizationId?: string): Promise<void> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
await this.request<void>('POST', `/hooks/${hookId}/disable`, tenant);
|
||||
}
|
||||
|
||||
// ─── Templates ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List templates.
|
||||
*/
|
||||
async listTemplates(
|
||||
teamId?: number,
|
||||
organizationId?: string,
|
||||
pagination?: IMakeComPaginationParams
|
||||
): Promise<IMakeComTemplate[]> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const params: Record<string, any> = { ...this.buildPaginationParams(pagination) };
|
||||
if (teamId) params.teamId = teamId;
|
||||
const result = await this.request<{ templates: IMakeComTemplate[] }>(
|
||||
'GET', '/templates', tenant, { params }
|
||||
);
|
||||
return result.templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single template by ID.
|
||||
*/
|
||||
async getTemplate(templateId: number, organizationId?: string): Promise<IMakeComTemplate> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
const result = await this.request<{ template: IMakeComTemplate }>(
|
||||
'GET', `/templates/${templateId}`, tenant
|
||||
);
|
||||
return result.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blueprint of a template.
|
||||
*/
|
||||
async getTemplateBlueprint(templateId: number, organizationId?: string): Promise<any> {
|
||||
const tenant = await this.getIntegrationTenant(organizationId);
|
||||
return this.request<any>('GET', `/templates/${templateId}/blueprint`, tenant);
|
||||
}
|
||||
}
|
||||
+56
-22
@@ -1,15 +1,18 @@
|
||||
import { Controller, Get, Query, Res, HttpException, HttpStatus, BadRequestException } from '@nestjs/common';
|
||||
import { Controller, Get, Query, Res, BadRequestException, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { Public } from '@gauzy/common';
|
||||
import { IntegrationEnum } from '@gauzy/contracts';
|
||||
import { MakeComOAuthService } from './make-com-oauth.service';
|
||||
import { MAKE_POST_INSTALL_URL } from './make-com.config';
|
||||
|
||||
@ApiTags('Make.com OAuth')
|
||||
@Public()
|
||||
@Controller('/integration/make-com/oauth')
|
||||
export class MakeComAuthorizationController {
|
||||
private readonly logger = new Logger(MakeComAuthorizationController.name);
|
||||
|
||||
constructor(private readonly config: ConfigService, private readonly makeComOAuthService: MakeComOAuthService) {}
|
||||
|
||||
/**
|
||||
@@ -26,11 +29,47 @@ export class MakeComAuthorizationController {
|
||||
})
|
||||
@Get('/authorize')
|
||||
async authorize(@Query() { state }: { state?: string }) {
|
||||
const authorizationUrl = await this.makeComOAuthService.getAuthorizationUrl({ state });
|
||||
return {
|
||||
authorizationUrl: this.makeComOAuthService.getAuthorizationUrl({ state })
|
||||
authorizationUrl
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the post-install redirect URL from config, with fallback to
|
||||
* the statically resolved constant (which doesn't rely on dotenv-expand).
|
||||
*/
|
||||
private getPostInstallUrl(): string {
|
||||
const fromConfig = this.config.get('makeCom')?.postInstallUrl;
|
||||
|
||||
// Detect unresolved env variable interpolation (e.g. "${CLIENT_BASE_URL}/...")
|
||||
if (fromConfig && !fromConfig.includes('${')) {
|
||||
return fromConfig;
|
||||
}
|
||||
|
||||
return MAKE_POST_INSTALL_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a redirect URL by appending query params.
|
||||
* Handles hash-based Angular routing (e.g. http://host/#/path) by placing
|
||||
* query params within the hash fragment, after the route path.
|
||||
*/
|
||||
private buildRedirectUrl(baseUrl: string, params: Record<string, string>): string {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
const hashIndex = baseUrl.indexOf('#');
|
||||
if (hashIndex !== -1) {
|
||||
// Hash-based Angular routing: place query params after the hash route
|
||||
// e.g. http://host/#/path?query instead of http://host?query#/path
|
||||
const beforeHash = baseUrl.substring(0, hashIndex);
|
||||
const hashPart = baseUrl.substring(hashIndex);
|
||||
const separator = hashPart.includes('?') ? '&' : '?';
|
||||
return `${beforeHash}${hashPart}${separator}${queryString}`;
|
||||
}
|
||||
const separator = baseUrl.includes('?') ? '&' : '?';
|
||||
return `${baseUrl}${separator}${queryString}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the callback from Make.com after user authorization.
|
||||
* Exchanges the authorization code for access and refresh tokens.
|
||||
@@ -54,11 +93,8 @@ export class MakeComAuthorizationController {
|
||||
}: { code?: string; state?: string; error?: string; error_description?: string },
|
||||
@Res() response: Response
|
||||
) {
|
||||
// Get the post-installation redirect URL from config
|
||||
const postInstallUrl = this.config.get('makeCom')?.postInstallUrl;
|
||||
if (!postInstallUrl) {
|
||||
throw new HttpException('postInstallUrl not found in config', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
const postInstallUrl = this.getPostInstallUrl();
|
||||
this.logger.log(`OAuth callback received. postInstallUrl: ${postInstallUrl}`);
|
||||
|
||||
try {
|
||||
// Handle error from Make.com
|
||||
@@ -72,30 +108,28 @@ export class MakeComAuthorizationController {
|
||||
throw new BadRequestException('Missing required parameters: code and state');
|
||||
}
|
||||
|
||||
// Process the OAuth callback - store the authorization code and enable the integration
|
||||
// The actual token exchange is handled by Make.com when they call our token endpoint
|
||||
// Process the OAuth callback - verify state and exchange code for tokens
|
||||
await this.makeComOAuthService.handleAuthorizationCallback(code, state);
|
||||
|
||||
// Build successful URL with access token
|
||||
const urlObj = new URL(postInstallUrl);
|
||||
urlObj.searchParams.set('success', 'true');
|
||||
urlObj.searchParams.set('integration', IntegrationEnum.MakeCom);
|
||||
urlObj.searchParams.set('message', 'Integration connected successfully');
|
||||
// Redirect to the application with success params
|
||||
const url = this.buildRedirectUrl(postInstallUrl, {
|
||||
success: 'true',
|
||||
integration: IntegrationEnum.MakeCom,
|
||||
message: 'Integration connected successfully'
|
||||
});
|
||||
|
||||
// Redirect to the application
|
||||
return response.redirect(urlObj.toString());
|
||||
this.logger.log(`OAuth callback successful, redirecting to: ${url}`);
|
||||
return response.redirect(url);
|
||||
} catch (error) {
|
||||
// postInstallUrl is guaranteed to exist here due to earlier check
|
||||
const errorMessage = error.response?.message || error.message || 'Failed to complete OAuth flow';
|
||||
const queryParamsString = new URLSearchParams({
|
||||
this.logger.error(`OAuth callback failed: ${errorMessage}`);
|
||||
|
||||
const url = this.buildRedirectUrl(postInstallUrl, {
|
||||
success: 'false',
|
||||
integration: IntegrationEnum.MakeCom,
|
||||
message: errorMessage
|
||||
}).toString();
|
||||
});
|
||||
|
||||
const urlObj = new URL(postInstallUrl);
|
||||
urlObj.search = queryParamsString;
|
||||
const url = urlObj.toString();
|
||||
return response.redirect(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Injectable, BadRequestException, Logger, HttpException, HttpStatus, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, BadRequestException, Logger, HttpException, HttpStatus, NotFoundException, Inject } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { AxiosError } from 'axios';
|
||||
@@ -26,8 +28,8 @@ import { MakeComService } from './make-com.service';
|
||||
@Injectable()
|
||||
export class MakeComOAuthService {
|
||||
private readonly logger = new Logger(MakeComOAuthService.name);
|
||||
private pendingStates = new Map<string, { timestamp: number; codeVerifier?: string }>();
|
||||
private static readonly STATE_TTL_MS = 10 * 60 * 1000; // 10 min
|
||||
private static readonly STATE_CACHE_KEY_PREFIX = 'make_com_oauth_state:';
|
||||
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
@@ -36,30 +38,36 @@ export class MakeComOAuthService {
|
||||
private readonly integrationSettingService: IntegrationSettingService,
|
||||
private readonly integrationTenantService: IntegrationTenantService,
|
||||
private readonly integrationService: IntegrationService,
|
||||
private readonly commandBus: CommandBus
|
||||
private readonly commandBus: CommandBus,
|
||||
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure random code verifier for PKCE
|
||||
* Generate a cryptographically secure random code verifier for PKCE.
|
||||
*/
|
||||
private generateCodeVerifier(): string {
|
||||
return randomBytes(32).toString('base64url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate code challenge from code verifier using SHA256
|
||||
* Generate code challenge from code verifier using SHA256.
|
||||
*/
|
||||
private generateCodeChallenge(codeVerifier: string): string {
|
||||
return createHash('sha256').update(codeVerifier).digest('base64url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the authorization URL for the Make.com OAuth flow with PKCE support.
|
||||
* Generate the authorization URL for the Make.com OAuth v2 flow with PKCE.
|
||||
* Make.com requires code_challenge even for confidential clients.
|
||||
*
|
||||
* @see https://developers.make.com/api-documentation/authentication/oauth-flow/authorization-code-flow-with-refresh-token-confidential-clients
|
||||
*/
|
||||
getAuthorizationUrl(options?: { state?: string; clientId?: string; organizationId?: string }): string {
|
||||
async getAuthorizationUrl(options?: { state?: string; clientId?: string; organizationId?: string }): Promise<string> {
|
||||
try {
|
||||
const redirectUri = this.config.get('makeCom').redirectUri;
|
||||
if (!redirectUri) {
|
||||
throw new Error('Make.com redirect URI is not configured');
|
||||
}
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
const organizationId = options?.organizationId;
|
||||
|
||||
@@ -72,16 +80,17 @@ export class MakeComOAuthService {
|
||||
// Always generate state internally to ensure consistent format
|
||||
const state = Buffer.from(JSON.stringify({ tenantId, organizationId })).toString('base64url');
|
||||
|
||||
// Generate PKCE parameters
|
||||
// Generate PKCE parameters (required by Make.com OAuth v2)
|
||||
const codeVerifier = this.generateCodeVerifier();
|
||||
const codeChallenge = this.generateCodeChallenge(codeVerifier);
|
||||
|
||||
this.storeStateForVerification(state, codeVerifier);
|
||||
// Store state in Redis for later verification (async to ensure persistence)
|
||||
await this.storeStateForVerification(state, codeVerifier);
|
||||
|
||||
// Prepare scopes according to Make.com documentation
|
||||
const scopes = MAKE_DEFAULT_SCOPES.join(' ');
|
||||
|
||||
// Build authorization URL according to Make.com documentation with PKCE
|
||||
// Build authorization URL per Make.com OAuth v2 documentation
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
@@ -92,64 +101,55 @@ export class MakeComOAuthService {
|
||||
code_challenge_method: 'S256'
|
||||
});
|
||||
|
||||
return `${MAKE_BASE_URL}/oauth/authorize?${params.toString()}`;
|
||||
return `${MAKE_BASE_URL}/oauth/v2/authorize?${params.toString()}`;
|
||||
} catch (error) {
|
||||
this.logger.error('Error generating Make.com authorization URL:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async exchangeCodeForToken(code: string, state: string): Promise<IMakeComOAuthTokens> {
|
||||
async exchangeCodeForToken(code: string, state: string, codeVerifier?: string): Promise<IMakeComOAuthTokens> {
|
||||
try {
|
||||
const stateVerification = this.verifyState(state);
|
||||
if (!stateVerification.isValid) {
|
||||
throw new BadRequestException('Invalid state parameter');
|
||||
}
|
||||
|
||||
// Decode the state parameter
|
||||
const decodedState = JSON.parse(Buffer.from(state, 'base64url').toString());
|
||||
const { tenantId, organizationId } = decodedState;
|
||||
|
||||
// Set the tenant context for subsequent operations
|
||||
const currentTenantId = RequestContext.currentTenantId();
|
||||
if (currentTenantId !== tenantId) {
|
||||
throw new BadRequestException('Tenant ID mismatch');
|
||||
}
|
||||
|
||||
// Get client credentials from database
|
||||
const integrationTenant = await this.getIntegrationTenant(tenantId, organizationId);
|
||||
const clientId = await this.getSettingValue(integrationTenant, MakeSettingName.CLIENT_ID);
|
||||
const clientSecret = await this.getSettingValue(integrationTenant, MakeSettingName.CLIENT_SECRET);
|
||||
// Read client credentials from server-side config (env vars) — never from tenant data
|
||||
const makeComConfig = this.config.get('makeCom');
|
||||
const clientId = makeComConfig?.clientId;
|
||||
const clientSecret = makeComConfig?.clientSecret;
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
throw new BadRequestException('Make.com OAuth credentials are not fully configured');
|
||||
throw new BadRequestException('Make.com OAuth credentials are not configured on the server');
|
||||
}
|
||||
|
||||
const redirectUri = this.config.get('makeCom').redirectUri;
|
||||
if (!codeVerifier) {
|
||||
throw new BadRequestException('Missing PKCE code verifier for token exchange');
|
||||
}
|
||||
|
||||
// Prepare the request body for Make.com token endpoint with PKCE
|
||||
const redirectUri = makeComConfig?.redirectUri;
|
||||
if (!redirectUri) {
|
||||
throw new BadRequestException('Make.com redirect URI is not configured on the server');
|
||||
}
|
||||
|
||||
const tokenRequestParams = new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
redirect_uri: redirectUri,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret
|
||||
client_secret: clientSecret,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: redirectUri
|
||||
});
|
||||
|
||||
// Add code verifier for PKCE if available
|
||||
if (!stateVerification.codeVerifier) {
|
||||
throw new BadRequestException('Missing PKCE code verifier');
|
||||
}
|
||||
tokenRequestParams.append('code_verifier', stateVerification.codeVerifier);
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
|
||||
// Make the token request to Make.com
|
||||
// Make the token request to Make.com OAuth v2 token endpoint
|
||||
const tokenResponse = await firstValueFrom(
|
||||
this.httpService.post(`${MAKE_BASE_URL}/oauth/token`, tokenRequestParams, { headers, timeout: 10000 }).pipe(
|
||||
this.httpService.post(`${MAKE_BASE_URL}/oauth/v2/token`, tokenRequestParams, { headers, timeout: 10000 }).pipe(
|
||||
catchError((error: AxiosError) => {
|
||||
this.logger.error('Error while exchanging code for token:', error.response?.data);
|
||||
throw new HttpException(
|
||||
@@ -298,121 +298,69 @@ export class MakeComOAuthService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the state parameter to prevent CSRF attacks and return code verifier for PKCE.
|
||||
* Verify the state parameter to prevent CSRF attacks and return the PKCE code verifier.
|
||||
* Uses Redis-backed cache for multi-replica support and process restart durability.
|
||||
*/
|
||||
verifyState(state: string): { isValid: boolean; codeVerifier?: string } {
|
||||
const pendingState = this.pendingStates.get(state);
|
||||
async verifyState(state: string): Promise<{ isValid: boolean; codeVerifier?: string }> {
|
||||
const cacheKey = MakeComOAuthService.STATE_CACHE_KEY_PREFIX + state;
|
||||
const pendingState = await this.cacheManager.get<{ timestamp: number; codeVerifier: string }>(cacheKey);
|
||||
|
||||
if (!pendingState) {
|
||||
this.logger.warn(`State ${state} not found in pending states`);
|
||||
return { isValid: false };
|
||||
}
|
||||
|
||||
// State found - delete it immediately to ensure single-use (atomic consumption)
|
||||
await this.cacheManager.del(cacheKey);
|
||||
|
||||
// Check expiration by reading the stored timestamp
|
||||
const now = Date.now();
|
||||
const expirationTime = MakeComOAuthService.STATE_TTL_MS;
|
||||
if (now - pendingState.timestamp > expirationTime) {
|
||||
this.logger.warn(`State ${state} has expired`);
|
||||
this.pendingStates.delete(state);
|
||||
return { isValid: false };
|
||||
}
|
||||
// Valid and used - remove it to free memory and return the code verifier
|
||||
this.pendingStates.delete(state);
|
||||
|
||||
// Valid and not expired - return codeVerifier for token exchange
|
||||
return { isValid: true, codeVerifier: pendingState.codeVerifier };
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the state parameter and code verifier for later verification.
|
||||
* Store the state parameter and PKCE code verifier for later verification.
|
||||
* Uses Redis-backed cache with TTL for multi-replica support and process restart durability.
|
||||
*
|
||||
* @param {string} state - The state parameter to store.
|
||||
* @param {string} codeVerifier - The PKCE code verifier to store.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO: Implement a more robust storage mechanism for state parameters by Q4 2025.
|
||||
* This is a simple in-memory storage for small scale applications.
|
||||
* For scalability, we'll consider using cache, Redis for our case as it already exists in the project.
|
||||
*/
|
||||
private storeStateForVerification(state: string, codeVerifier?: string): void {
|
||||
this.pendingStates.set(state, { timestamp: Date.now(), codeVerifier });
|
||||
this.cleanupExpiredStates();
|
||||
private async storeStateForVerification(state: string, codeVerifier: string): Promise<void> {
|
||||
const cacheKey = MakeComOAuthService.STATE_CACHE_KEY_PREFIX + state;
|
||||
await this.cacheManager.set(cacheKey, { timestamp: Date.now(), codeVerifier }, MakeComOAuthService.STATE_TTL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up expired state parameters to prevent memory leaks.
|
||||
* Clean up expired state parameters.
|
||||
* Note: With Redis-backed cache and TTL, this is no longer needed as Redis handles expiration automatically.
|
||||
* Kept for backward compatibility but does nothing.
|
||||
*/
|
||||
private cleanupExpiredStates(): void {
|
||||
const now = Date.now();
|
||||
const expirationTime = MakeComOAuthService.STATE_TTL_MS;
|
||||
|
||||
for (const [state, { timestamp }] of this.pendingStates.entries()) {
|
||||
if (now - timestamp > expirationTime) {
|
||||
this.pendingStates.delete(state);
|
||||
}
|
||||
}
|
||||
// Redis TTL handles expiration automatically - no manual cleanup needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the callback from Make.com OAuth flow
|
||||
* This method stores information for future use
|
||||
* Handles the callback from Make.com OAuth flow.
|
||||
* Verifies the state parameter and exchanges the authorization code for tokens using PKCE.
|
||||
*/
|
||||
async handleAuthorizationCallback(code: string, state: string): Promise<void> {
|
||||
try {
|
||||
// Decode the state to get context
|
||||
const decodedState = JSON.parse(Buffer.from(state, 'base64').toString());
|
||||
const { organizationId, tenantId } = decodedState;
|
||||
|
||||
const integrationTenant = await this.integrationTenantService.findOneByOptions({
|
||||
where: {
|
||||
name: IntegrationEnum.MakeCom,
|
||||
tenantId,
|
||||
...(organizationId && { organizationId })
|
||||
},
|
||||
relations: ['settings']
|
||||
});
|
||||
|
||||
if (!integrationTenant) {
|
||||
this.logger.warn(`Integration not found for tenant ${tenantId}`);
|
||||
throw new NotFoundException('Integration not found');
|
||||
// Verify state to prevent CSRF and retrieve the PKCE code verifier
|
||||
const stateVerification = await this.verifyState(state);
|
||||
if (!stateVerification.isValid) {
|
||||
throw new BadRequestException('Invalid or expired state parameter');
|
||||
}
|
||||
|
||||
// Store the authorization code
|
||||
// but since Make.com handles the OAuth flow, we just store the fact
|
||||
// that the integration was authorized
|
||||
let authCodeSetting = integrationTenant.settings.find(
|
||||
(setting) => setting.settingsName === MakeSettingName.AUTH_CODE
|
||||
);
|
||||
|
||||
if (authCodeSetting) {
|
||||
authCodeSetting.settingsValue = code;
|
||||
} else {
|
||||
authCodeSetting = await this.integrationSettingService.create({
|
||||
settingsName: MakeSettingName.AUTH_CODE,
|
||||
settingsValue: code,
|
||||
integrationId: integrationTenant.id,
|
||||
tenantId: integrationTenant.tenantId,
|
||||
organizationId: integrationTenant.organizationId
|
||||
});
|
||||
}
|
||||
|
||||
await this.integrationSettingService.save(authCodeSetting);
|
||||
|
||||
// Set integration as enabled
|
||||
let enabledSetting = integrationTenant.settings.find(
|
||||
(setting) => setting.settingsName === MakeSettingName.IS_ENABLED
|
||||
);
|
||||
|
||||
if (enabledSetting) {
|
||||
enabledSetting.settingsValue = 'true';
|
||||
} else {
|
||||
enabledSetting = await this.integrationSettingService.create({
|
||||
settingsName: MakeSettingName.IS_ENABLED,
|
||||
settingsValue: 'true',
|
||||
integrationId: integrationTenant.id,
|
||||
tenantId: integrationTenant.tenantId,
|
||||
organizationId: integrationTenant.organizationId
|
||||
});
|
||||
}
|
||||
await this.integrationSettingService.save(enabledSetting);
|
||||
this.logger.log(`Successfully handled OAuth callback for tenant ${tenantId}`);
|
||||
// Exchange the authorization code for tokens with the PKCE code verifier
|
||||
await this.exchangeCodeForToken(code, state, stateVerification.codeVerifier);
|
||||
this.logger.log('Successfully handled OAuth callback and exchanged code for tokens');
|
||||
} catch (error) {
|
||||
this.logger.error('Error handling Make.com authorization callback:', error);
|
||||
throw error;
|
||||
@@ -439,11 +387,17 @@ export class MakeComOAuthService {
|
||||
throw new NotFoundException('Refresh token not found for this integration');
|
||||
}
|
||||
|
||||
// Get OAuth credentials from the database
|
||||
const { clientId, clientSecret } = await this.makeComService.getOAuthCredentials(integrationId);
|
||||
// Read OAuth credentials from server-side config (env vars) — never from tenant data
|
||||
const makeComConfig = this.config.get('makeCom');
|
||||
const clientId = makeComConfig?.clientId;
|
||||
const clientSecret = makeComConfig?.clientSecret;
|
||||
|
||||
// Get the Make.com token endpoint URL
|
||||
const tokenUrl = `${MAKE_BASE_URL}/oauth/token`;
|
||||
if (!clientId || !clientSecret) {
|
||||
throw new BadRequestException('Make.com OAuth credentials are not configured on the server');
|
||||
}
|
||||
|
||||
// Get the Make.com OAuth v2 token endpoint URL
|
||||
const tokenUrl = `${MAKE_BASE_URL}/oauth/v2/token`;
|
||||
|
||||
// Create the form data for the token request
|
||||
const formData = new URLSearchParams();
|
||||
|
||||
@@ -9,19 +9,29 @@ const { CLIENT_BASE_URL, API_BASE_URL } = process.env;
|
||||
/** Make authentication server URL */
|
||||
export const MAKE_BASE_URL = process.env.GAUZY_MAKE_BASE_URL ?? 'https://www.make.com';
|
||||
|
||||
/**
|
||||
* Resolve an env variable, falling back to a constructed or hardcoded default.
|
||||
* Detects unresolved dotenv interpolation (e.g. "${VAR}") and skips it.
|
||||
*/
|
||||
function resolveEnvUrl(envValue: string | undefined, constructed: string | undefined, fallback: string): string {
|
||||
if (envValue && !envValue.includes('${')) return envValue;
|
||||
if (constructed && !constructed.includes('${')) return constructed;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/** Make post-install URL */
|
||||
export const MAKE_POST_INSTALL_URL = process.env.GAUZY_MAKE_POST_INSTALL_URL ??
|
||||
(CLIENT_BASE_URL ?
|
||||
`${CLIENT_BASE_URL}/#/pages/integrations/make` :
|
||||
'http://localhost:4200/#/pages/integrations/make'
|
||||
);
|
||||
export const MAKE_POST_INSTALL_URL = resolveEnvUrl(
|
||||
process.env.GAUZY_MAKE_POST_INSTALL_URL,
|
||||
CLIENT_BASE_URL ? `${CLIENT_BASE_URL}/#/pages/integrations/make` : undefined,
|
||||
'http://localhost:4200/#/pages/integrations/make'
|
||||
);
|
||||
|
||||
/** Make OAuth redirect URL */
|
||||
export const MAKE_REDIRECT_URL = process.env.GAUZY_MAKE_REDIRECT_URL ??
|
||||
(API_BASE_URL ?
|
||||
`${API_BASE_URL}/api/integration/make-com/oauth/callback` :
|
||||
'http://localhost:3000/api/integration/make-com/oauth/callback'
|
||||
);
|
||||
export const MAKE_REDIRECT_URL = resolveEnvUrl(
|
||||
process.env.GAUZY_MAKE_REDIRECT_URL,
|
||||
API_BASE_URL ? `${API_BASE_URL}/api/integration/make-com/oauth/callback` : undefined,
|
||||
'http://localhost:3000/api/integration/make-com/oauth/callback'
|
||||
);
|
||||
|
||||
/** Default scopes for Make.com OAuth - Updated to match Make.com documentation */
|
||||
export const MAKE_DEFAULT_SCOPES =
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Controller, Get, Post, Body, UseGuards, BadRequestException, NotFoundException, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
import { Controller, Get, Post, Body, UseGuards, BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { TenantPermissionGuard, Permissions, PermissionGuard, RequestContext } from '@gauzy/core';
|
||||
import { PermissionsEnum } from '@gauzy/contracts';
|
||||
import { MakeComService } from './make-com.service';
|
||||
import { IMakeComCreateIntegration, IMakeComIntegrationSettings } from './interfaces/make-com.model';
|
||||
import { IMakeComIntegrationSettings } from './interfaces/make-com.model';
|
||||
import { UpdateMakeComSettingsDTO } from './dto';
|
||||
import { MakeComOAuthService } from './make-com-oauth.service';
|
||||
|
||||
@@ -15,8 +14,7 @@ import { MakeComOAuthService } from './make-com-oauth.service';
|
||||
export class MakeComController {
|
||||
constructor(
|
||||
private readonly makeComService: MakeComService,
|
||||
private readonly makeComOAuthService: MakeComOAuthService,
|
||||
private readonly config: ConfigService
|
||||
private readonly makeComOAuthService: MakeComOAuthService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -68,26 +66,25 @@ export class MakeComController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Make.com OAuth settings for the current tenant.
|
||||
*
|
||||
* @param {UpdateMakeComSettingsDTO} input - The DTO containing the updated Make.com OAuth settings.
|
||||
* @returns {Promise<IMakeComIntegrationSettings>} A promise that resolves to the updated integration settings.
|
||||
* Initialize Make.com OAuth integration for the current tenant.
|
||||
* Client credentials are read from server-side environment variables
|
||||
* and are never exposed to tenants.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Update Make.com OAuth settings for tenant' })
|
||||
@ApiOperation({ summary: 'Initialize Make.com OAuth integration' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Make.com OAuth settings updated successfully'
|
||||
description: 'Make.com OAuth authorization URL generated successfully'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Both clientId and clientSecret must be provided together'
|
||||
description: 'Server-side OAuth credentials not configured'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'Tenant ID not found in request context'
|
||||
})
|
||||
@Post('/oauth-settings')
|
||||
async addOAuthSettings(@Body() input: IMakeComCreateIntegration): Promise<{
|
||||
async initializeIntegration(@Body() body: { organizationId?: string }): Promise<{
|
||||
authorizationUrl: string;
|
||||
integrationId: string;
|
||||
}> {
|
||||
@@ -96,21 +93,14 @@ export class MakeComController {
|
||||
throw new NotFoundException('Tenant ID not found in request context');
|
||||
}
|
||||
|
||||
// Validate input
|
||||
if (!input.client_id || !input.client_secret) {
|
||||
throw new BadRequestException('Both client_id and client_secret are required');
|
||||
}
|
||||
|
||||
// Save the credentials and create/update integration with encrypted secret
|
||||
const integration = await this.makeComService.addIntegrationSettings({
|
||||
...input,
|
||||
client_secret: input.client_secret
|
||||
// Generate authorization URL first — if this fails (e.g. missing config),
|
||||
// we avoid leaving a partial integration record behind.
|
||||
const authorizationUrl = await this.makeComOAuthService.getAuthorizationUrl({
|
||||
organizationId: body?.organizationId
|
||||
});
|
||||
|
||||
// Generate authorization URL
|
||||
const authorizationUrl = this.makeComOAuthService.getAuthorizationUrl({
|
||||
clientId: input.client_id
|
||||
});
|
||||
// Save the integration with server-side credentials
|
||||
const integration = await this.makeComService.addIntegrationSettings(body?.organizationId);
|
||||
|
||||
return {
|
||||
authorizationUrl,
|
||||
@@ -118,31 +108,6 @@ export class MakeComController {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Make.com OAuth configuration for the frontend.
|
||||
* Note: Client secret is intentionally excluded for security reasons.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Get Make.com OAuth configuration' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Retrieved Make.com OAuth configuration'
|
||||
})
|
||||
@Get('/oauth-config')
|
||||
async getOAuthConfig(): Promise<{ clientId: string; redirectUri: string }> {
|
||||
// Get client ID from the database
|
||||
const clientId = await this.makeComService.getOAuthClientId();
|
||||
|
||||
// Get redirect URI from config (this is typically an environment variable)
|
||||
const makeComConfig = this.config.get('makeCom');
|
||||
const redirectUri = makeComConfig?.redirectUri;
|
||||
|
||||
if (!clientId || !redirectUri) {
|
||||
throw new BadRequestException('OAuth configuration is missing required values.');
|
||||
}
|
||||
|
||||
return { clientId, redirectUri };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Token requests from Make.com custom apps.
|
||||
* This endpoint is called by your Make.com custom app during the OAuth flow.
|
||||
@@ -185,8 +150,18 @@ export class MakeComController {
|
||||
throw new BadRequestException('Missing required parameters');
|
||||
}
|
||||
|
||||
// Exchange code for tokens
|
||||
const tokenResponse = await this.makeComOAuthService.exchangeCodeForToken(body.code, body.state);
|
||||
// Verify state and retrieve the PKCE code verifier
|
||||
const stateVerification = await this.makeComOAuthService.verifyState(body.state);
|
||||
if (!stateVerification.isValid) {
|
||||
throw new BadRequestException('Invalid or expired state parameter');
|
||||
}
|
||||
|
||||
// Exchange code for tokens with the PKCE code verifier
|
||||
const tokenResponse = await this.makeComOAuthService.exchangeCodeForToken(
|
||||
body.code,
|
||||
body.state,
|
||||
stateVerification.codeVerifier
|
||||
);
|
||||
|
||||
// Return the token response in the format expected by Make.com
|
||||
return {
|
||||
|
||||
@@ -11,11 +11,13 @@ import {
|
||||
} from '@gauzy/core';
|
||||
import { MakeComController } from './make-com.controller';
|
||||
import { MakeComAuthorizationController } from './make-com-authorization.controller';
|
||||
import { MakeComApiController } from './make-com-api.controller';
|
||||
import { MakeComMiddleware } from './make-com.middleware';
|
||||
import { MakeComService } from './make-com.service';
|
||||
import { WebhookService } from './webhook.service';
|
||||
import { EventHandlers } from './handlers';
|
||||
import { MakeComOAuthService } from './make-com-oauth.service';
|
||||
import { MakeComApiService } from './make-com-api.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -28,9 +30,9 @@ import { MakeComOAuthService } from './make-com-oauth.service';
|
||||
IntegrationTenantModule,
|
||||
UserModule
|
||||
],
|
||||
controllers: [MakeComController, MakeComAuthorizationController],
|
||||
providers: [WebhookService, MakeComService, MakeComOAuthService, ...EventHandlers],
|
||||
exports: [WebhookService, MakeComService, MakeComOAuthService]
|
||||
controllers: [MakeComController, MakeComAuthorizationController, MakeComApiController],
|
||||
providers: [WebhookService, MakeComService, MakeComOAuthService, MakeComApiService, ...EventHandlers],
|
||||
exports: [WebhookService, MakeComService, MakeComOAuthService, MakeComApiService]
|
||||
})
|
||||
export class MakeComModule implements NestModule {
|
||||
/**
|
||||
@@ -55,6 +57,11 @@ export class MakeComModule implements NestModule {
|
||||
{
|
||||
path: '/integration/make-com',
|
||||
method: RequestMethod.POST
|
||||
},
|
||||
// Make.com API proxy routes
|
||||
{
|
||||
path: '/integration/make-com/api/*',
|
||||
method: RequestMethod.ALL
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable, Logger, NotFoundException, InternalServerErrorException } from '@nestjs/common';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
import {
|
||||
IntegrationSettingService,
|
||||
IntegrationService,
|
||||
@@ -8,12 +9,10 @@ import {
|
||||
PROJECT_TIED_ENTITIES,
|
||||
IntegrationTenantUpdateOrCreateCommand
|
||||
} from '@gauzy/core';
|
||||
import { In } from 'typeorm';
|
||||
import { IIntegrationEntitySetting, IIntegrationTenant, IntegrationEntity, IntegrationEnum } from '@gauzy/contracts';
|
||||
import {
|
||||
IMakeComIntegrationSettings,
|
||||
MakeSettingName,
|
||||
IMakeComCreateIntegration,
|
||||
IIntegrationFilter
|
||||
} from './interfaces/make-com.model';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
@@ -24,6 +23,7 @@ export class MakeComService {
|
||||
|
||||
constructor(
|
||||
private readonly _commandBus: CommandBus,
|
||||
private readonly config: ConfigService,
|
||||
private readonly integrationSettingService: IntegrationSettingService,
|
||||
private readonly integrationTenantService: IntegrationTenantService,
|
||||
private readonly integrationService: IntegrationService
|
||||
@@ -182,25 +182,26 @@ export class MakeComService {
|
||||
|
||||
/**
|
||||
* Add Make.com integration settings for the current tenant and organization.
|
||||
* This method is used to add settings like client ID and client secret.
|
||||
* It is called when the integration is first set up.
|
||||
* The integration is automatically enabled (isEnabled = true) when created.
|
||||
* @param {Object} settings - The settings to add.
|
||||
* Client credentials (client_id, client_secret) are read from server-side
|
||||
* environment variables and are never exposed to tenants.
|
||||
*
|
||||
* @param {string} [organizationId] - Optional organization ID for organization-level integration
|
||||
* @returns The created settings.
|
||||
* @returns The created integration tenant.
|
||||
*/
|
||||
async addIntegrationSettings(
|
||||
settings: IMakeComCreateIntegration,
|
||||
organizationId?: string
|
||||
): Promise<IIntegrationTenant> {
|
||||
async addIntegrationSettings(organizationId?: string): Promise<IIntegrationTenant> {
|
||||
try {
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
const { client_id, client_secret } = settings;
|
||||
|
||||
if (!tenantId) {
|
||||
throw new NotFoundException('Tenant ID not found in request context');
|
||||
}
|
||||
|
||||
// Validate that server-side OAuth credentials are configured (do NOT persist them to tenant)
|
||||
const makeComConfig = this.config?.get('makeCom');
|
||||
if (!makeComConfig?.clientId || !makeComConfig?.clientSecret) {
|
||||
throw new InternalServerErrorException('Make.com OAuth credentials are not configured on the server. Please set GAUZY_MAKE_CLIENT_ID and GAUZY_MAKE_CLIENT_SECRET environment variables.');
|
||||
}
|
||||
|
||||
// Find or create the base integration
|
||||
let integration = await this.integrationService.findOneByOptions({
|
||||
where: { provider: IntegrationEnum.MakeCom }
|
||||
@@ -248,14 +249,6 @@ export class MakeComService {
|
||||
tenantId,
|
||||
entitySettings: entitySettings,
|
||||
settings: [
|
||||
{
|
||||
settingsName: MakeSettingName.CLIENT_ID,
|
||||
settingsValue: client_id
|
||||
},
|
||||
{
|
||||
settingsName: MakeSettingName.CLIENT_SECRET,
|
||||
settingsValue: client_secret
|
||||
},
|
||||
{
|
||||
// Automatically enable the integration when it's created
|
||||
settingsName: MakeSettingName.IS_ENABLED,
|
||||
@@ -275,145 +268,12 @@ export class MakeComService {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Saves OAuth credentials for the Make.com integration.
|
||||
* This method stores the credentials in the database for persistence across server restarts.
|
||||
* Retrieves the OAuth client ID for the Make.com integration from server-side config.
|
||||
*
|
||||
* @param {Object} credentials - The OAuth credentials to save.
|
||||
* @param {string} credentials.clientId - The OAuth client ID.
|
||||
* @param {string} credentials.clientSecret - The OAuth client secret.
|
||||
* @returns {Promise<void>} A promise that resolves when the credentials have been saved.
|
||||
* @returns {Promise<string | null>} A promise that resolves to the OAuth client ID or null if not configured.
|
||||
*/
|
||||
async saveOAuthCredentials(credentials: { clientId: string; clientSecret: string }): Promise<void> {
|
||||
try {
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new NotFoundException('Tenant ID not found in request context');
|
||||
}
|
||||
|
||||
// Find the integration for the current tenant
|
||||
const integrationTenant = await this.integrationTenantService.findOneByWhereOptions({
|
||||
name: IntegrationEnum.MakeCom,
|
||||
tenantId
|
||||
});
|
||||
|
||||
if (!integrationTenant) {
|
||||
throw new NotFoundException(`${IntegrationEnum.MakeCom} integration not found for this tenant`);
|
||||
}
|
||||
|
||||
// Define the settings to be saved or updated
|
||||
const settingsToSave = [
|
||||
{
|
||||
settingsName: MakeSettingName.CLIENT_ID,
|
||||
settingsValue: credentials.clientId,
|
||||
integration: integrationTenant
|
||||
},
|
||||
{
|
||||
settingsName: MakeSettingName.CLIENT_SECRET,
|
||||
settingsValue: credentials.clientSecret,
|
||||
integration: integrationTenant
|
||||
}
|
||||
];
|
||||
|
||||
// Use the bulkUpdateOrCreate method to save the settings
|
||||
await this.integrationSettingService.bulkUpdateOrCreate(integrationTenant.id, settingsToSave);
|
||||
|
||||
this.logger.log(`OAuth credentials updated for tenant: ${tenantId}`);
|
||||
} catch (error) {
|
||||
this.logger.error('Error saving Make.com OAuth credentials:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the OAuth credentials for the Make.com integration.
|
||||
*
|
||||
* @param {string} integrationId - The ID of the integration to get credentials for.
|
||||
* @returns {Promise<{clientId: string, clientSecret: string}>} A promise that resolves to the OAuth credentials.
|
||||
*/
|
||||
async getOAuthCredentials(
|
||||
integrationId: string,
|
||||
organizationId?: string
|
||||
): Promise<{ clientId: string; clientSecret: string }> {
|
||||
try {
|
||||
// Find the integration settings
|
||||
const where: any = {
|
||||
integration: { id: integrationId },
|
||||
settingsName: In([MakeSettingName.CLIENT_ID, MakeSettingName.CLIENT_SECRET])
|
||||
};
|
||||
if (organizationId) {
|
||||
where.organizationId = organizationId;
|
||||
}
|
||||
const settings = await this.integrationSettingService.find({ where });
|
||||
|
||||
if (!settings || settings.length < 2) {
|
||||
throw new NotFoundException(
|
||||
'OAuth credentials not found for this integration missing client_id or client_secret'
|
||||
);
|
||||
}
|
||||
|
||||
// Extract client ID and client secret
|
||||
const clientIdSetting = settings.find((setting) => setting.settingsName === MakeSettingName.CLIENT_ID);
|
||||
const clientSecretSetting = settings.find(
|
||||
(setting) => setting.settingsName === MakeSettingName.CLIENT_SECRET
|
||||
);
|
||||
|
||||
if (!clientIdSetting || !clientSecretSetting) {
|
||||
throw new NotFoundException('OAuth credentials are incomplete for this integration');
|
||||
}
|
||||
|
||||
return {
|
||||
clientId: clientIdSetting.settingsValue,
|
||||
clientSecret: clientSecretSetting.settingsValue
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Error retrieving Make.com OAuth credentials:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the OAuth client ID for the Make.com integration.
|
||||
*
|
||||
* @param {string} [organizationId] - Optional organization ID to filter by organization level
|
||||
* @returns {Promise<string>} A promise that resolves to the OAuth client ID or null if not found.
|
||||
*/
|
||||
async getOAuthClientId(organizationId?: string): Promise<string | null> {
|
||||
try {
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new NotFoundException('Tenant ID not found in request context');
|
||||
}
|
||||
|
||||
// Build the where clause with tenant and optional organization filter
|
||||
const whereClause: any = {
|
||||
name: IntegrationEnum.MakeCom,
|
||||
tenantId
|
||||
};
|
||||
|
||||
// If organizationId is provided, filter by organization level
|
||||
if (organizationId) {
|
||||
whereClause.organizationId = organizationId;
|
||||
}
|
||||
|
||||
// Find the integration for the current tenant and organization
|
||||
const integrationTenant = await this.integrationTenantService.findOneByOptions({
|
||||
where: whereClause,
|
||||
relations: ['settings']
|
||||
});
|
||||
|
||||
if (!integrationTenant) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the client ID setting
|
||||
const clientIdSetting = integrationTenant.settings.find(
|
||||
(setting) => setting.settingsName === MakeSettingName.CLIENT_ID
|
||||
);
|
||||
|
||||
return clientIdSetting ? clientIdSetting.settingsValue : null;
|
||||
} catch (error) {
|
||||
this.logger.error('Error retrieving Make.com OAuth client ID:', error);
|
||||
throw error;
|
||||
}
|
||||
async getOAuthClientId(): Promise<string | null> {
|
||||
const makeComConfig = this.config?.get('makeCom');
|
||||
return makeComConfig?.clientId ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-49
@@ -4,52 +4,23 @@
|
||||
<h5>{{ 'MENU.ZAPIER' | translate }}</h5>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<form class="col-xl-6 col-12" [formGroup]="form" (ngSubmit)="startAuthorization()">
|
||||
<div class="form-group">
|
||||
<label for="client_id" class="label">{{ 'INTEGRATIONS.ZAPIER_PAGE.CLIENT_ID' | translate }}</label>
|
||||
<input
|
||||
fullWidth
|
||||
id="client_id"
|
||||
formControlName="client_id"
|
||||
type="text"
|
||||
nbInput
|
||||
[placeholder]="'FORM.PLACEHOLDERS.ZAPIER_CLIENT_ID' | translate"
|
||||
/>
|
||||
@if (form.controls.client_id.touched && form.controls.client_id.errors?.required) {
|
||||
<div
|
||||
class="error-message"
|
||||
>
|
||||
{{ 'FORM.VALIDATION.REQUIRED' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_secret" class="label">{{
|
||||
'INTEGRATIONS.ZAPIER_PAGE.CLIENT_SECRET' | translate
|
||||
}}</label>
|
||||
<input
|
||||
fullWidth
|
||||
id="client_secret"
|
||||
formControlName="client_secret"
|
||||
type="password"
|
||||
nbInput
|
||||
[placeholder]="'FORM.PLACEHOLDERS.ZAPIER_CLIENT_SECRET' | translate"
|
||||
/>
|
||||
@if (form.controls.client_secret.touched && form.controls.client_secret.errors?.required) {
|
||||
<div
|
||||
class="error-message"
|
||||
>
|
||||
{{ 'FORM.VALIDATION.REQUIRED' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="hint">
|
||||
<nb-icon icon="info-outline"></nb-icon>
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.NEXT_STEP_INFO' | translate }}
|
||||
</div>
|
||||
<button nbButton status="primary" [disabled]="form.invalid" size="small" outline type="submit">
|
||||
{{ 'BUTTONS.NEXT' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
<div class="col-xl-6 col-12">
|
||||
<div class="hint mb-4">
|
||||
<nb-icon icon="info-outline"></nb-icon>
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.CONNECT_INFO' | translate }}
|
||||
</div>
|
||||
<button
|
||||
nbButton
|
||||
status="primary"
|
||||
size="small"
|
||||
[disabled]="loading"
|
||||
(click)="connectToZapier()"
|
||||
>
|
||||
@if (loading) {
|
||||
<nb-icon icon="loader-outline" class="spin"></nb-icon>
|
||||
}
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.CONNECT_TO_ZAPIER' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
+9
-43
@@ -1,12 +1,11 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { tap, catchError, finalize, switchMap } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { ZapierService, ToastrService, IntegrationsService } from '@gauzy/ui-core/core';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ICreateZapierIntegrationInput, IntegrationEnum, IOrganization } from '@gauzy/contracts';
|
||||
import { IntegrationEnum, IOrganization } from '@gauzy/contracts';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { Store } from '@gauzy/ui-core/core';
|
||||
|
||||
@@ -18,13 +17,10 @@ import { Store } from '@gauzy/ui-core/core';
|
||||
standalone: false
|
||||
})
|
||||
export class ZapierAuthorizeComponent extends TranslationBaseComponent implements OnInit {
|
||||
public form: FormGroup;
|
||||
public loading = false;
|
||||
public oauthConfig: { clientId: string; redirectUri: string } = null;
|
||||
public organization: IOrganization;
|
||||
|
||||
constructor(
|
||||
private readonly _fb: FormBuilder,
|
||||
private readonly _zapierService: ZapierService,
|
||||
private readonly _toastrService: ToastrService,
|
||||
private readonly _router: Router,
|
||||
@@ -36,21 +32,12 @@ export class ZapierAuthorizeComponent extends TranslationBaseComponent implement
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._initializeForm();
|
||||
this._loadOrganization()
|
||||
.pipe(
|
||||
switchMap(() => this._checkExistingIntegration()),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
this._loadOAuthConfig();
|
||||
}
|
||||
|
||||
private _initializeForm() {
|
||||
this.form = this._fb.group({
|
||||
client_id: ['', [Validators.required]],
|
||||
client_secret: ['', [Validators.required]]
|
||||
});
|
||||
}
|
||||
|
||||
private _loadOrganization() {
|
||||
@@ -61,21 +48,6 @@ export class ZapierAuthorizeComponent extends TranslationBaseComponent implement
|
||||
);
|
||||
}
|
||||
|
||||
private _loadOAuthConfig() {
|
||||
this._zapierService
|
||||
.getOAuthConfig()
|
||||
.pipe(
|
||||
tap((config) => {
|
||||
this.oauthConfig = config;
|
||||
}),
|
||||
catchError((error) => {
|
||||
return EMPTY;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _checkExistingIntegration() {
|
||||
if (!this.organization) return EMPTY;
|
||||
|
||||
@@ -104,31 +76,25 @@ export class ZapierAuthorizeComponent extends TranslationBaseComponent implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Start OAuth authorization flow
|
||||
* Start OAuth authorization flow.
|
||||
* No credentials needed — server uses its own env-configured client_id/secret.
|
||||
*/
|
||||
startAuthorization() {
|
||||
if (this.form.invalid) {
|
||||
connectToZapier() {
|
||||
if (!this.organization) {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_FORM'),
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.NO_ORGANIZATION'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.organization) {
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const credentials: ICreateZapierIntegrationInput = {
|
||||
...this.form.value,
|
||||
organizationId: this.organization.id
|
||||
};
|
||||
this.loading = true;
|
||||
|
||||
this._zapierService
|
||||
.initializeIntegration(credentials)
|
||||
.initializeIntegration({ organizationId: this.organization.id })
|
||||
.pipe(
|
||||
tap((response: { authorizationUrl: string }) => {
|
||||
// Redirect to Zapier's OAuth consent page
|
||||
window.location.href = response.authorizationUrl;
|
||||
}),
|
||||
catchError((error) => {
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<nb-card>
|
||||
<nb-card-header>
|
||||
<h6>{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAP_TEMPLATES.TITLE' | translate }}</h6>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
@if (loading) {
|
||||
<div class="d-flex justify-content-center">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
}
|
||||
@if (!loading) {
|
||||
<div class="endpoints-list">
|
||||
@for (template of templates; track template.id) {
|
||||
<div class="endpoint-item">
|
||||
<div class="endpoint-info">
|
||||
<h6>{{ template.title }}</h6>
|
||||
<p>{{ template.description }}</p>
|
||||
</div>
|
||||
<div class="endpoint-actions">
|
||||
@if (template.create_url || template.url) {
|
||||
<a
|
||||
nbButton
|
||||
status="primary"
|
||||
size="small"
|
||||
outline
|
||||
[href]="template.create_url || template.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAP_TEMPLATES.USE' | translate }}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (templates.length === 0) {
|
||||
<div class="no-data">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAP_TEMPLATES.NO_TEMPLATES' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { tap, catchError, finalize } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { ZapierService, ToastrService } from '@gauzy/ui-core/core';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IZapierZapTemplate } from '@gauzy/contracts';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-zapier-zap-templates',
|
||||
templateUrl: './zapier-zap-templates.component.html',
|
||||
styleUrls: ['./zapier-zap-templates.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ZapierZapTemplatesComponent extends TranslationBaseComponent implements OnInit {
|
||||
public loading = false;
|
||||
|
||||
/** List of Zap templates available from Zapier */
|
||||
public templates: IZapierZapTemplate[] = [];
|
||||
|
||||
constructor(
|
||||
public readonly translateService: TranslateService,
|
||||
private readonly _zapierService: ZapierService,
|
||||
private readonly _toastrService: ToastrService
|
||||
) {
|
||||
super(translateService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._loadTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads publicly available Zap templates. No OAuth access token is
|
||||
* required — the server attaches the configured Zapier client_id.
|
||||
*/
|
||||
private _loadTemplates(): void {
|
||||
this.loading = true;
|
||||
|
||||
this._zapierService
|
||||
.getZapTemplates()
|
||||
.pipe(
|
||||
tap((templates: IZapierZapTemplate[]) => {
|
||||
this.templates = templates;
|
||||
}),
|
||||
catchError(() => {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.LOAD_ZAP_TEMPLATES'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
<nb-card>
|
||||
<nb-card-header class="d-flex justify-content-between align-items-center">
|
||||
<h6 class="m-0">{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.TITLE' | translate }}</h6>
|
||||
<button nbButton status="primary" size="small" (click)="toggleCreateForm()">
|
||||
<nb-icon [icon]="showCreateForm ? 'close-outline' : 'plus-outline'"></nb-icon>
|
||||
{{
|
||||
(showCreateForm ? 'BUTTONS.CANCEL' : 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.CREATE') | translate
|
||||
}}
|
||||
</button>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
@if (showCreateForm) {
|
||||
<form [formGroup]="createZapForm" (ngSubmit)="submitCreateZap()" class="create-zap-form mb-3">
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.TITLE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
fieldSize="small"
|
||||
type="text"
|
||||
formControlName="title"
|
||||
[placeholder]="'INTEGRATIONS.ZAPIER_PAGE.ZAPS.TITLE_PLACEHOLDER' | translate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="steps-section mt-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="label m-0">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.STEPS' | translate }}
|
||||
</label>
|
||||
<button type="button" nbButton status="basic" size="tiny" (click)="addStep()">
|
||||
<nb-icon icon="plus-outline"></nb-icon>
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.ADD_STEP' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div formArrayName="steps">
|
||||
@for (stepCtrl of stepsFormArray.controls; track $index; let i = $index) {
|
||||
<div [formGroupName]="i" class="step-item p-2 mb-2">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<strong>
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.STEP' | translate }} {{ i + 1 }}
|
||||
</strong>
|
||||
@if (stepsFormArray.length > 1) {
|
||||
<button
|
||||
type="button"
|
||||
nbButton
|
||||
status="danger"
|
||||
size="tiny"
|
||||
ghost
|
||||
(click)="removeStep(i)"
|
||||
>
|
||||
<nb-icon icon="trash-2-outline"></nb-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.ACTION' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
fieldSize="small"
|
||||
type="text"
|
||||
formControlName="action"
|
||||
placeholder="example_core:Vn7xbE60"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.AUTHENTICATION' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
fieldSize="small"
|
||||
type="text"
|
||||
formControlName="authentication"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.STEP_TITLE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
fullWidth
|
||||
fieldSize="small"
|
||||
type="text"
|
||||
formControlName="title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.INPUTS' | translate }}
|
||||
</label>
|
||||
<textarea
|
||||
nbInput
|
||||
fullWidth
|
||||
fieldSize="small"
|
||||
rows="3"
|
||||
formControlName="inputs"
|
||||
placeholder='{"key": "value"}'
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-3">
|
||||
<button
|
||||
type="submit"
|
||||
nbButton
|
||||
status="primary"
|
||||
size="small"
|
||||
[disabled]="creating || createZapForm.invalid"
|
||||
>
|
||||
@if (creating) {
|
||||
<nb-icon icon="loader-outline"></nb-icon>
|
||||
}
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.SUBMIT' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@if (loading) {
|
||||
<div class="d-flex justify-content-center">
|
||||
<nb-spinner></nb-spinner>
|
||||
</div>
|
||||
}
|
||||
@if (!loading) {
|
||||
<div class="endpoints-list">
|
||||
@for (zap of zaps; track zap.id) {
|
||||
<div class="endpoint-item">
|
||||
<div class="endpoint-info">
|
||||
<h6>{{ zap.title }}</h6>
|
||||
<p>
|
||||
@if (zap.is_enabled !== undefined) {
|
||||
<span>
|
||||
{{
|
||||
(zap.is_enabled
|
||||
? 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.ENABLED'
|
||||
: 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.DISABLED'
|
||||
) | translate
|
||||
}}
|
||||
</span>
|
||||
} @else if (zap.state) {
|
||||
<span>{{ zap.state }}</span>
|
||||
}
|
||||
@if (zap.updated_at || zap.modified_time) {
|
||||
<span> · {{ zap.updated_at || zap.modified_time }}</span>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div class="endpoint-actions">
|
||||
@if (zap.links?.html_editor || zap.url) {
|
||||
<a
|
||||
nbButton
|
||||
status="primary"
|
||||
size="small"
|
||||
outline
|
||||
[href]="zap.links?.html_editor || zap.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.OPEN_EDITOR' | translate }}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (zaps.length === 0) {
|
||||
<div class="no-data">
|
||||
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.NO_ZAPS' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { tap, catchError, finalize, switchMap, map, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { ZapierService, ToastrService } from '@gauzy/ui-core/core';
|
||||
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { IZapierZap, IZapierZapStep, IZapierCreateZapInput, ID } from '@gauzy/contracts';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
|
||||
@UntilDestroy({ checkProperties: true })
|
||||
@Component({
|
||||
selector: 'ngx-zapier-zaps',
|
||||
templateUrl: './zapier-zaps.component.html',
|
||||
styleUrls: ['./zapier-zaps.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ZapierZapsComponent extends TranslationBaseComponent implements OnInit {
|
||||
public loading = false;
|
||||
public creating = false;
|
||||
public showCreateForm = false;
|
||||
|
||||
/** List of Zaps from the authenticated Zapier account */
|
||||
public zaps: IZapierZap[] = [];
|
||||
|
||||
/** Zapier integration ID from route parameters */
|
||||
public integrationId: ID | null = null;
|
||||
|
||||
/** Cached OAuth access token used for both read and create calls */
|
||||
private _accessToken: string | null = null;
|
||||
|
||||
/** Reactive form used to create a new Zap */
|
||||
public createZapForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _fb: FormBuilder,
|
||||
public readonly translateService: TranslateService,
|
||||
private readonly _zapierService: ZapierService,
|
||||
private readonly _toastrService: ToastrService
|
||||
) {
|
||||
super(translateService);
|
||||
this.createZapForm = this._buildCreateZapForm();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const parentRoute = this._activatedRoute.parent;
|
||||
if (!parentRoute) {
|
||||
this._showNoIntegrationError();
|
||||
return;
|
||||
}
|
||||
|
||||
parentRoute.params
|
||||
.pipe(
|
||||
map((p: Params) => p['id']),
|
||||
distinctUntilChanged(),
|
||||
tap((id: ID) => {
|
||||
this.integrationId = id;
|
||||
this._loadZaps();
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for the `steps` FormArray in the create-zap form.
|
||||
*/
|
||||
get stepsFormArray(): FormArray<FormGroup> {
|
||||
return this.createZapForm.get('steps') as FormArray<FormGroup>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the reactive form with a single starter step.
|
||||
*/
|
||||
private _buildCreateZapForm(): FormGroup {
|
||||
return this._fb.group({
|
||||
title: ['', [Validators.required, Validators.maxLength(255)]],
|
||||
steps: this._fb.array([this._createStepGroup()], Validators.required)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a single step FormGroup with the fields supported by the
|
||||
* Zapier create-a-zap API payload.
|
||||
*/
|
||||
private _createStepGroup(): FormGroup {
|
||||
return this._fb.group({
|
||||
action: ['', [Validators.required]],
|
||||
authentication: [''],
|
||||
title: [''],
|
||||
// `inputs` is a JSON object in the API; users enter it as JSON text.
|
||||
inputs: ['']
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the create-zap form visibility. Resets the form when opening.
|
||||
*/
|
||||
toggleCreateForm(): void {
|
||||
this.showCreateForm = !this.showCreateForm;
|
||||
if (this.showCreateForm) {
|
||||
this.createZapForm = this._buildCreateZapForm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a new empty step to the create-zap form.
|
||||
*/
|
||||
addStep(): void {
|
||||
this.stepsFormArray.push(this._createStepGroup());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a step from the create-zap form. A Zap must have at least one step.
|
||||
*/
|
||||
removeStep(index: number): void {
|
||||
if (this.stepsFormArray.length > 1) {
|
||||
this.stepsFormArray.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the create-zap form and POST the payload via the Gauzy backend.
|
||||
*/
|
||||
submitCreateZap(): void {
|
||||
if (this.createZapForm.invalid) {
|
||||
this.createZapForm.markAllAsTouched();
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_FORM'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._accessToken) {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.NO_ACCESS_TOKEN'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = this._toCreateZapPayload();
|
||||
if (!payload) {
|
||||
// Validation error toast already shown by the converter.
|
||||
return;
|
||||
}
|
||||
|
||||
this.creating = true;
|
||||
this._zapierService
|
||||
.createZap(payload, this._accessToken)
|
||||
.pipe(
|
||||
tap((zap: IZapierZap) => {
|
||||
this.zaps = [zap, ...this.zaps];
|
||||
this.showCreateForm = false;
|
||||
this.createZapForm = this._buildCreateZapForm();
|
||||
this._toastrService.success(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.SUCCESS.CREATE_ZAP'),
|
||||
this.getTranslation('TOASTR.TITLE.SUCCESS')
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._handleError(error, 'INTEGRATIONS.ZAPIER_PAGE.ERRORS.CREATE_ZAP');
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => (this.creating = false)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the reactive form value into the API payload shape.
|
||||
* Returns `null` if a step's `inputs` JSON cannot be parsed — in which
|
||||
* case an error toast is shown.
|
||||
*/
|
||||
private _toCreateZapPayload(): IZapierCreateZapInput | null {
|
||||
const raw = this.createZapForm.getRawValue() as {
|
||||
title: string;
|
||||
steps: Array<{ action: string; authentication: string; title: string; inputs: string }>;
|
||||
};
|
||||
|
||||
const steps: IZapierZapStep[] = [];
|
||||
for (const [index, step] of raw.steps.entries()) {
|
||||
let inputs: Record<string, any> | null = null;
|
||||
const inputsText = (step.inputs ?? '').trim();
|
||||
if (inputsText.length > 0) {
|
||||
try {
|
||||
const parsed = JSON.parse(inputsText);
|
||||
if (parsed !== null && typeof parsed !== 'object') {
|
||||
throw new Error('inputs must be a JSON object');
|
||||
}
|
||||
inputs = parsed as Record<string, any>;
|
||||
} catch {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_STEP_INPUTS', {
|
||||
step: index + 1
|
||||
}),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
steps.push({
|
||||
action: step.action.trim(),
|
||||
authentication: step.authentication?.trim() ? step.authentication.trim() : null,
|
||||
title: step.title?.trim() ? step.title.trim() : null,
|
||||
inputs
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
title: raw.title.trim(),
|
||||
steps
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user's Zaps using the integration ID from route parameters.
|
||||
*/
|
||||
private _loadZaps(): void {
|
||||
if (!this.integrationId) {
|
||||
this._showNoIntegrationError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear cached access token before loading a new integration to prevent
|
||||
// using a stale token from a previous failed token fetch
|
||||
this._accessToken = null;
|
||||
this.loading = true;
|
||||
|
||||
this._zapierService
|
||||
.getAccessToken(this.integrationId)
|
||||
.pipe(
|
||||
tap((accessToken: string) => (this._accessToken = accessToken)),
|
||||
switchMap((accessToken: string) => this._zapierService.getZaps(accessToken)),
|
||||
tap((zaps: IZapierZap[]) => {
|
||||
this.zaps = zaps;
|
||||
}),
|
||||
catchError((error) => {
|
||||
this._handleError(error, 'INTEGRATIONS.ZAPIER_PAGE.ERRORS.LOAD_ZAPS');
|
||||
return EMPTY;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
}),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private _handleError(error: any, fallbackKey: string): void {
|
||||
if (error?.status === 404 || error?.message?.includes('not found')) {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.TOKEN_NOT_FOUND'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
} else if (error?.status === 401 || error?.message?.includes('access token')) {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_TOKEN'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
} else {
|
||||
this._toastrService.error(
|
||||
this.getTranslation(fallbackKey),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _showNoIntegrationError(): void {
|
||||
this._toastrService.error(
|
||||
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.NO_INTEGRATION_ID'),
|
||||
this.getTranslation('TOASTR.TITLE.ERROR')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,18 @@ export class ZapierComponent extends TranslationBaseComponent implements OnInit,
|
||||
responsive: true,
|
||||
route: this.getRoute('webhooks')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.TAB.ZAPS'),
|
||||
icon: 'grid-outline',
|
||||
responsive: true,
|
||||
route: this.getRoute('zaps')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.TAB.ZAP_TEMPLATES'),
|
||||
icon: 'layout-outline',
|
||||
responsive: true,
|
||||
route: this.getRoute('zap-templates')
|
||||
},
|
||||
{
|
||||
title: this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.TAB.SETTINGS'),
|
||||
icon: 'settings-2-outline',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import {
|
||||
NbActionsModule,
|
||||
NbButtonModule,
|
||||
@@ -27,6 +28,8 @@ import { ZapierSettingsComponent } from './components/zapier-settings/zapier-set
|
||||
import { ZapierTriggersComponent } from './components/zapier-triggers/zapier-triggers.component';
|
||||
import { ZapierActionsComponent } from './components/zapier-actions/zapier-actions.component';
|
||||
import { ZapierWebhooksComponent } from './components/zapier-webhooks/zapier-webhooks.component';
|
||||
import { ZapierZapsComponent } from './components/zapier-zaps/zapier-zaps.component';
|
||||
import { ZapierZapTemplatesComponent } from './components/zapier-zap-templates/zapier-zap-templates.component';
|
||||
|
||||
const NB_MODULES = [
|
||||
NbActionsModule,
|
||||
@@ -54,7 +57,8 @@ const NB_MODULES = [
|
||||
SelectorsModule,
|
||||
SharedModule,
|
||||
TableComponentsModule,
|
||||
CommonModule
|
||||
CommonModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
declarations: [
|
||||
IntegrationZapierLayoutComponent,
|
||||
@@ -64,7 +68,9 @@ const NB_MODULES = [
|
||||
ZapierSettingsComponent,
|
||||
ZapierTriggersComponent,
|
||||
ZapierActionsComponent,
|
||||
ZapierWebhooksComponent
|
||||
ZapierWebhooksComponent,
|
||||
ZapierZapsComponent,
|
||||
ZapierZapTemplatesComponent
|
||||
]
|
||||
})
|
||||
export class IntegrationZapierUiModule {}
|
||||
|
||||
@@ -8,6 +8,8 @@ import { ZapierSettingsComponent } from './components/zapier-settings/zapier-set
|
||||
import { ZapierTriggersComponent } from './components/zapier-triggers/zapier-triggers.component';
|
||||
import { ZapierActionsComponent } from './components/zapier-actions/zapier-actions.component';
|
||||
import { ZapierWebhooksComponent } from './components/zapier-webhooks/zapier-webhooks.component';
|
||||
import { ZapierZapsComponent } from './components/zapier-zaps/zapier-zaps.component';
|
||||
import { ZapierZapTemplatesComponent } from './components/zapier-zap-templates/zapier-zap-templates.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -51,6 +53,14 @@ import { ZapierWebhooksComponent } from './components/zapier-webhooks/zapier-web
|
||||
path: 'webhooks',
|
||||
component: ZapierWebhooksComponent
|
||||
},
|
||||
{
|
||||
path: 'zaps',
|
||||
component: ZapierZapsComponent
|
||||
},
|
||||
{
|
||||
path: 'zap-templates',
|
||||
component: ZapierZapTemplatesComponent
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: ZapierSettingsComponent
|
||||
|
||||
@@ -49,13 +49,13 @@
|
||||
"rxjs": "^7.8.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typeorm": "^0.3.28",
|
||||
"zapier-platform-core": "18.2.0"
|
||||
"zapier-platform-core": "^18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/node": "^24.10.0",
|
||||
"typescript": "^5.9.3",
|
||||
"zapier-platform-cli": "18.2.0"
|
||||
"zapier-platform-cli": "^18.3.0"
|
||||
},
|
||||
"keywords": [
|
||||
"zapier",
|
||||
|
||||
@@ -1,31 +1,10 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsIn } from 'class-validator';
|
||||
import { IsString, IsNotEmpty } from 'class-validator';
|
||||
import { TenantOrganizationBaseDTO } from '@gauzy/core';
|
||||
import { ICreateZapierIntegrationInput, ZapierGrantType } from '../zapier.types';
|
||||
|
||||
export class CreateZapierIntegrationDto extends TenantOrganizationBaseDTO implements ICreateZapierIntegrationInput {
|
||||
export class CreateZapierIntegrationDto extends TenantOrganizationBaseDTO {
|
||||
@ApiProperty({ type: () => String })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
client_id!: string;
|
||||
|
||||
@ApiProperty({ type: () => String })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
code!: string;
|
||||
|
||||
@ApiProperty({ type: () => String })
|
||||
@IsString()
|
||||
@IsIn(['authorization_code', 'refresh_token'])
|
||||
grant_type!: ZapierGrantType;
|
||||
|
||||
@ApiProperty({ type: () => String })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
redirect_uri!: string;
|
||||
|
||||
@ApiProperty({ type: () => String })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
client_secret!: string;
|
||||
override organizationId!: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
import { BadRequestException, Controller, Get, HttpException, HttpStatus, Logger, Query, Res } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Headers,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
Post,
|
||||
Query,
|
||||
Res,
|
||||
UnauthorizedException
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
@@ -67,14 +81,14 @@ export class ZapierAuthorizationController {
|
||||
throw new BadRequestException('Zapier post-install URL is not configured');
|
||||
}
|
||||
|
||||
// Validate state parameter
|
||||
const isValidState = await this.zapierService.parseAuthState(query.state);
|
||||
if (!isValidState) {
|
||||
// Validate state parameter and extract tenantId
|
||||
const parsedState = await this.zapierService.parseAuthState(query.state);
|
||||
if (!parsedState) {
|
||||
throw new BadRequestException('Invalid or expired state parameter');
|
||||
}
|
||||
|
||||
// Complete the OAuth flow by exchanging the code for tokens
|
||||
await this.zapierService.completeOAuthFlow(query.code, query.state);
|
||||
// Complete the OAuth flow — pass tenantId from state since this is a @Public() endpoint
|
||||
await this.zapierService.completeOAuthFlow(query.code, query.state, parsedState.tenantId);
|
||||
|
||||
// Build query parameters for redirect
|
||||
const queryParamsString = buildQueryString({
|
||||
@@ -98,4 +112,179 @@ export class ZapierAuthorizationController {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth token exchange endpoint — @Public() so Zapier platform can call it.
|
||||
* Exchanges authorization code for access and refresh tokens.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Exchange authorization code for tokens' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully exchanged code for tokens'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad request - Invalid parameters'
|
||||
})
|
||||
@Public()
|
||||
@Post('/oauth/token')
|
||||
async exchangeCodeForToken(
|
||||
@Body()
|
||||
body: {
|
||||
code: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
redirect_uri: string;
|
||||
grant_type: string;
|
||||
}
|
||||
) {
|
||||
try {
|
||||
// Validate required parameters
|
||||
if (!body.code || !body.client_id || !body.client_secret || !body.redirect_uri) {
|
||||
throw new BadRequestException('Missing required parameters');
|
||||
}
|
||||
|
||||
if (body.grant_type !== 'authorization_code') {
|
||||
throw new BadRequestException('Invalid grant_type. Must be "authorization_code"');
|
||||
}
|
||||
|
||||
// Validate client credentials against server-side config (shared across all tenants)
|
||||
this.zapierService.validateServerClientCredentials(body.client_id, body.client_secret);
|
||||
|
||||
// Resolve the correct tenant integration by the Gauzy-issued auth code
|
||||
const integration = await this.zapierService.findIntegrationByAuthCode(body.code);
|
||||
|
||||
// Validate and consume the authorization code (single-use)
|
||||
await this.zapierService.validateAndConsumeAuthCode(integration.id!, body.code, body.redirect_uri);
|
||||
|
||||
// Generate new tokens
|
||||
const tokens = await this.zapierService.generateAndStoreNewTokens(integration.id!);
|
||||
|
||||
return tokens;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to exchange code for token', error);
|
||||
if (error instanceof BadRequestException || error instanceof NotFoundException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException('Failed to exchange code for token', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth token refresh endpoint — @Public() so Zapier platform can call it.
|
||||
* Refreshes an expired access token using a refresh token.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Refresh access token' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully refreshed token'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad request - Invalid parameters'
|
||||
})
|
||||
@Public()
|
||||
@Post('/oauth/refresh-token')
|
||||
async refreshAccessToken(
|
||||
@Body() body: { refresh_token: string; client_id: string; client_secret: string; grant_type: string }
|
||||
) {
|
||||
try {
|
||||
// Validate required parameters
|
||||
if (!body.refresh_token || !body.client_id || !body.client_secret) {
|
||||
throw new BadRequestException('Missing required parameters');
|
||||
}
|
||||
|
||||
if (body.grant_type !== 'refresh_token') {
|
||||
throw new BadRequestException('Invalid grant_type. Must be "refresh_token"');
|
||||
}
|
||||
|
||||
// Validate client credentials against server-side config (shared across all tenants)
|
||||
this.zapierService.validateServerClientCredentials(body.client_id, body.client_secret);
|
||||
|
||||
// Resolve the correct tenant integration by the Gauzy-issued refresh token
|
||||
const integration = await this.zapierService.findIntegrationByGauzyRefreshToken(body.refresh_token);
|
||||
if (!integration.id) {
|
||||
throw new BadRequestException('Invalid refresh token');
|
||||
}
|
||||
|
||||
const refreshResult = await this.zapierService.refreshTokenByRefreshToken(
|
||||
integration.id,
|
||||
body.refresh_token
|
||||
);
|
||||
|
||||
return refreshResult;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to refresh token', error);
|
||||
if (error instanceof BadRequestException || error instanceof NotFoundException) {
|
||||
throw error;
|
||||
}
|
||||
throw new HttpException('Failed to refresh token', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth test endpoint — validates a Zapier access token.
|
||||
* Called by the Zapier CLI app to verify the connection is working.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Test Zapier authentication' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Authentication is valid'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 401,
|
||||
description: 'Invalid or missing access token'
|
||||
})
|
||||
@Public()
|
||||
@Get('/auth/test')
|
||||
async testAuth(@Headers('authorization') authHeader: string) {
|
||||
try {
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Missing or invalid Authorization header');
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7); // Strip "Bearer "
|
||||
const integration = await this.zapierService.findIntegrationByToken(token);
|
||||
|
||||
return {
|
||||
authenticated: true,
|
||||
integrationId: integration.id,
|
||||
name: IntegrationEnum.ZAPIER
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Zapier auth test failed', error);
|
||||
throw new UnauthorizedException('Invalid access token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection label endpoint — returns integration info for Zapier's UI.
|
||||
* Called by the Zapier CLI app to display a label for the connected account.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Get Zapier connection info' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Returns connection info for the Zapier account label'
|
||||
})
|
||||
@Public()
|
||||
@Get('/auth/me')
|
||||
async getConnectionInfo(@Headers('authorization') authHeader: string) {
|
||||
try {
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('Missing or invalid Authorization header');
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
const integration = await this.zapierService.findIntegrationByToken(token);
|
||||
|
||||
return {
|
||||
id: integration.id,
|
||||
name: IntegrationEnum.ZAPIER,
|
||||
email: `zapier-integration@${integration.tenantId || 'gauzy'}`
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Zapier connection info failed', error);
|
||||
throw new UnauthorizedException('Invalid access token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,18 @@ export const ZAPIER_API_URL = 'https://api.zapier.com';
|
||||
* Based on: https://docs.zapier.com/powered-by-zapier/api-reference/authentication
|
||||
*
|
||||
* Scopes included:
|
||||
* - 'zap': Read access to Zaps (view existing Zaps)
|
||||
* - 'zap': Read access to the authenticated user's own Zaps
|
||||
* - 'zap:all': Read access to all Zaps the user can see (including shared)
|
||||
* - 'zap:account:all': Read access to all Zaps across the user's account
|
||||
* - 'zap:write': Write access to Zaps (create, update, delete Zaps)
|
||||
* - 'authentication': Access to authentication information (manage user auth)
|
||||
*
|
||||
* These scopes provide full access to the Zapier Workflow API for managing
|
||||
* Zaps and user authentication on behalf of the authenticated user.
|
||||
* These scopes cover the Zapier Workflow API endpoints used by this plugin:
|
||||
* GET /v2/zaps, POST /v2/zaps, GET /v2/triggers, GET /v2/actions, and the
|
||||
* user authentication endpoints. The zap templates endpoint (/v1/zap-templates)
|
||||
* is public and does not consume any of these scopes.
|
||||
*/
|
||||
export const ZAPIER_OAUTH_SCOPES = 'zap zap:write authentication';
|
||||
export const ZAPIER_OAUTH_SCOPES = 'zap zap:all zap:account:all zap:write authentication';
|
||||
|
||||
/**
|
||||
* Zapier token expiration time (in seconds)
|
||||
@@ -36,7 +40,7 @@ export const ZAPIER_AUTHORIZATION_URL = `${process.env['API_BASE_URL']}/api/inte
|
||||
export const ZAPIER_CALLBACK_URL = `${process.env['API_BASE_URL']}/api/integration/zapier/oauth/callback`;
|
||||
|
||||
/** URL for obtaining OAuth access tokens from Gauzy */
|
||||
export const ZAPIER_TOKEN_URL = `${process.env['API_BASE_URL']}/api/integration/zapier/token`;
|
||||
export const ZAPIER_TOKEN_URL = `${process.env['API_BASE_URL']}/api/integration/zapier/oauth/token`;
|
||||
|
||||
/** URL for redirecting users after OAuth authorization */
|
||||
export const ZAPIER_REDIRECT_URI = 'https://zapier.com/dashboard/auth/oauth/return/App221848CLIAPI/';
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
Param
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { PermissionsEnum } from '@gauzy/contracts';
|
||||
import { IZapierCreateZapInput, IZapierZap, IZapierZapTemplate, PermissionsEnum } from '@gauzy/contracts';
|
||||
import { PermissionGuard, Permissions, RequestContext, TenantPermissionGuard } from '@gauzy/core';
|
||||
import { ZapierService } from './zapier.service';
|
||||
import { ICreateZapierIntegrationInput, IZapierEndpoint, IZapierIntegrationSettings } from './zapier.types';
|
||||
import { IZapierEndpoint, IZapierIntegrationSettings } from './zapier.types';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
@ApiTags('Zapier Integrations')
|
||||
@@ -35,9 +35,9 @@ export class ZapierController {
|
||||
constructor(private readonly zapierService: ZapierService) {}
|
||||
|
||||
/**
|
||||
* Creates a new Zapier integration.
|
||||
* This method handles the initialization of a new Zapier integration by generating an authorization URL
|
||||
* for the user to authorize the integration.
|
||||
* Initialize a new Zapier integration.
|
||||
* Stores client credentials and returns the authorization URL to redirect
|
||||
* the admin to Zapier's OAuth consent page.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Initialize a new Zapier integration' })
|
||||
@ApiResponse({
|
||||
@@ -49,30 +49,27 @@ export class ZapierController {
|
||||
description: 'Bad Request - Missing required fields'
|
||||
})
|
||||
@Post('/settings')
|
||||
async initializeIntegration(@Body() body: ICreateZapierIntegrationInput) {
|
||||
async initializeIntegration(@Body() body: { organizationId: string }) {
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new BadRequestException('Tenant ID is required');
|
||||
}
|
||||
// validate body inputs
|
||||
if (!body.client_id || !body.client_secret) {
|
||||
throw new BadRequestException('Missing required fields');
|
||||
|
||||
if (!body?.organizationId) {
|
||||
throw new BadRequestException('Organization ID is required');
|
||||
}
|
||||
|
||||
// Generate state parameter for CSRF protection
|
||||
const state = Buffer.from(randomBytes(32)).toString('base64url');
|
||||
|
||||
// Store the client credentials and state for later use in the callback
|
||||
// Store the integration with server-side credentials and state
|
||||
const integration = await this.zapierService.storeIntegrationCredentials({
|
||||
...body,
|
||||
organizationId: body.organizationId,
|
||||
state
|
||||
});
|
||||
|
||||
// Generate authorization URL for user to complete OAuth flow
|
||||
const authorizationUrl = this.zapierService.getAuthorizationUrl({
|
||||
clientId: body.client_id,
|
||||
state
|
||||
});
|
||||
// Generate authorization URL using server-side client ID
|
||||
const authorizationUrl = this.zapierService.getAuthorizationUrl({ state });
|
||||
|
||||
return {
|
||||
authorizationUrl,
|
||||
@@ -122,6 +119,80 @@ export class ZapierController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zaps for the authenticated Zapier account.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Get Zapier zaps' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully retrieved Zapier zaps'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 401,
|
||||
description: 'Unauthorized - Invalid or missing authorization token'
|
||||
})
|
||||
@Get('/zaps')
|
||||
async getZaps(@Query('token') token: string): Promise<IZapierZap[]> {
|
||||
try {
|
||||
this.validateToken(token, true);
|
||||
return await this.zapierService.fetchZaps(token);
|
||||
} catch (error) {
|
||||
this.handleZapierError(error, 'zaps');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Zap on the authenticated Zapier account.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Create a new Zapier zap' })
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'Successfully created Zapier zap'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad Request - Missing required fields'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 401,
|
||||
description: 'Unauthorized - Invalid or missing authorization token'
|
||||
})
|
||||
@Post('/zaps')
|
||||
async createZap(
|
||||
@Query('token') token: string,
|
||||
@Body() body: IZapierCreateZapInput
|
||||
): Promise<IZapierZap> {
|
||||
try {
|
||||
this.validateToken(token, true);
|
||||
if (!body?.title || !Array.isArray(body?.steps) || body.steps.length === 0) {
|
||||
throw new BadRequestException('Zap title and at least one step are required');
|
||||
}
|
||||
return await this.zapierService.createZap(body, token);
|
||||
} catch (error) {
|
||||
this.handleZapierError(error, 'zaps');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get publicly available Zap templates from Zapier.
|
||||
* This Zapier endpoint does not require an OAuth access token — it only
|
||||
* needs the server-configured `client_id`, which is attached in the service.
|
||||
*/
|
||||
@ApiOperation({ summary: 'Get Zapier zap templates (public)' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully retrieved Zapier zap templates'
|
||||
})
|
||||
@Get('/zap-templates')
|
||||
async getZapTemplates(@Query('limit') limit?: string): Promise<IZapierZapTemplate[]> {
|
||||
try {
|
||||
const parsedLimit = limit ? Number(limit) : undefined;
|
||||
return await this.zapierService.fetchZapTemplates(parsedLimit);
|
||||
} catch (error) {
|
||||
this.handleZapierError(error, 'zap-templates');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to validate Zapier token
|
||||
*/
|
||||
@@ -157,116 +228,6 @@ export class ZapierController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth token exchange endpoint
|
||||
* Exchanges authorization code for access and refresh tokens
|
||||
*/
|
||||
@ApiOperation({ summary: 'Exchange authorization code for tokens' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully exchanged code for tokens'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad request - Invalid parameters'
|
||||
})
|
||||
@Post('/token')
|
||||
async exchangeCodeForToken(
|
||||
@Body()
|
||||
body: {
|
||||
code: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
redirect_uri: string;
|
||||
grant_type: string;
|
||||
state: string;
|
||||
}
|
||||
) {
|
||||
try {
|
||||
// Add tenant validation to token exchange endpoint
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new BadRequestException('Tenant ID is required');
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (!body.code || !body.client_id || !body.client_secret || !body.redirect_uri || !body.state) {
|
||||
throw new BadRequestException('Missing required parameters');
|
||||
}
|
||||
|
||||
if (body.grant_type !== 'authorization_code') {
|
||||
throw new BadRequestException('Invalid grant_type. Must be "authorization_code"');
|
||||
}
|
||||
|
||||
// Validate and delete the state parameter
|
||||
const isValidState = await this.zapierService.validateAndDeleteState(body.state, tenantId);
|
||||
if (!isValidState) {
|
||||
throw new BadRequestException('Invalid or expired state parameter');
|
||||
}
|
||||
|
||||
// Find the integration record using the client_id from the request
|
||||
const integration = await this.zapierService.findIntegrationByClientId(body.client_id);
|
||||
if (!integration.id) {
|
||||
throw new BadRequestException('Invalid integration ID');
|
||||
}
|
||||
|
||||
// Generate new tokens
|
||||
const tokens = await this.zapierService.generateAndStoreNewTokens(integration.id);
|
||||
|
||||
return tokens;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to exchange code for token', error);
|
||||
if (error instanceof BadRequestException || error instanceof NotFoundException) {
|
||||
throw error;
|
||||
}
|
||||
throw new InternalServerErrorException('Failed to exchange code for token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth token refresh endpoint
|
||||
* Refreshes an expired access token using a refresh token
|
||||
*/
|
||||
@ApiOperation({ summary: 'Refresh access token' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Successfully refreshed token'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad request - Invalid parameters'
|
||||
})
|
||||
@Post('/refresh-token')
|
||||
async refreshAccessToken(
|
||||
@Body() body: { refresh_token: string; client_id: string; client_secret: string; grant_type: string }
|
||||
) {
|
||||
try {
|
||||
// Validate required parameters
|
||||
if (!body.refresh_token || !body.client_id || !body.client_secret) {
|
||||
throw new BadRequestException('Missing required parameters');
|
||||
}
|
||||
|
||||
if (body.grant_type !== 'refresh_token') {
|
||||
throw new BadRequestException('Invalid grant_type. Must be "refresh_token"');
|
||||
}
|
||||
|
||||
// Find integration by client_id and verify refresh token
|
||||
const integration = await this.zapierService.findIntegrationByClientId(body.client_id);
|
||||
const refreshResult = await this.zapierService.refreshTokenByRefreshToken(
|
||||
integration.id ?? '',
|
||||
body.refresh_token
|
||||
);
|
||||
|
||||
return refreshResult;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to refresh token', error);
|
||||
if (error instanceof BadRequestException || error instanceof NotFoundException) {
|
||||
throw error;
|
||||
}
|
||||
throw new InternalServerErrorException('Failed to refresh token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zapier access token for a given integration
|
||||
*/
|
||||
|
||||
@@ -24,12 +24,12 @@ import {
|
||||
} from '@gauzy/core';
|
||||
import { ZAPIER_API_URL, ZAPIER_BASE_URL, ZAPIER_TOKEN_EXPIRATION_TIME, ZAPIER_OAUTH_SCOPES } from './zapier.config';
|
||||
import {
|
||||
ICreateZapierIntegrationInput,
|
||||
IIntegrationFilter,
|
||||
IZapierAccessTokens,
|
||||
IZapierEndpoint,
|
||||
IZapierIntegrationSettings
|
||||
} from './zapier.types';
|
||||
import { IZapierCreateZapInput, IZapierZap, IZapierZapTemplate } from '@gauzy/contracts';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
@Injectable()
|
||||
@@ -75,8 +75,7 @@ export class ZapierService {
|
||||
* @param {ID} integrationId - The ID of the integration.
|
||||
* @returns {Promise<any>} - The new tokens.
|
||||
* @throws {NotFoundException} - When no settings are found for the given integration ID
|
||||
* @throws {BadRequestException} - When required settings (client_id, client_secret, refresh_token) are missing
|
||||
|
||||
* @throws {BadRequestException} - When the refresh token is missing
|
||||
*/
|
||||
async refreshToken(integrationId: ID): Promise<IZapierAccessTokens> {
|
||||
try {
|
||||
@@ -92,21 +91,18 @@ export class ZapierService {
|
||||
throw new NotFoundException(`No settings found for integration ID ${integrationId}`);
|
||||
}
|
||||
|
||||
// Extract required settings
|
||||
const client_id = settings.find((s) => s.settingsName === 'client_id')?.settingsValue;
|
||||
const client_secret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
|
||||
const refresh_token = settings.find((s) => s.settingsName === 'refresh_token')?.settingsValue;
|
||||
// Extract the Gauzy-issued OAuth refresh token
|
||||
const refresh_token = settings.find((s) => s.settingsName === 'oauth_refresh_token')?.settingsValue;
|
||||
|
||||
if (!client_id || !client_secret || !refresh_token) {
|
||||
this.logger.warn(`Missing required settings for integration ID ${integrationId}`);
|
||||
throw new BadRequestException('Missing required Zapier integration settings');
|
||||
if (!refresh_token) {
|
||||
this.logger.warn(`Missing refresh token for integration ID ${integrationId}`);
|
||||
throw new BadRequestException('Missing refresh token for integration');
|
||||
}
|
||||
|
||||
const result = await this.generateAndStoreNewTokens(integrationId);
|
||||
|
||||
this.logger.log(`Successfully refreshed tokens for integration ID ${integrationId}`, {
|
||||
integrationId,
|
||||
client_id,
|
||||
tenantId: settings[0]?.tenantId,
|
||||
organizationId: settings[0]?.organizationId
|
||||
});
|
||||
@@ -135,7 +131,7 @@ export class ZapierService {
|
||||
return await this._integrationSettingService.findOneByWhereOptions({
|
||||
integration: { id: integrationId },
|
||||
integrationId,
|
||||
settingsName: 'access_token'
|
||||
settingsName: 'zapier_access_token'
|
||||
});
|
||||
} catch (error) {
|
||||
throw new NotFoundException(`Access token for integration ID ${integrationId} not found`);
|
||||
@@ -181,7 +177,7 @@ export class ZapierService {
|
||||
scope: ZAPIER_OAUTH_SCOPES
|
||||
});
|
||||
|
||||
return `${ZAPIER_API_URL}/v2/authorize?${params.toString()}`;
|
||||
return `${ZAPIER_BASE_URL}/oauth/authorize/?${params.toString()}`;
|
||||
} catch (error) {
|
||||
this.logger.error('Error generating Zapier authorization URL:', error);
|
||||
throw new BadRequestException('Failed to generate authorization URL');
|
||||
@@ -330,55 +326,49 @@ export class ZapierService {
|
||||
|
||||
/**
|
||||
* Validates the state parameter without deleting it.
|
||||
* This is used for initial validation before the OAuth flow.
|
||||
* Searches across all tenants by state value so it works from @Public() endpoints.
|
||||
*
|
||||
* @param state The state parameter to validate
|
||||
* @returns The parsed state data if valid
|
||||
* @returns The parsed state data if valid (includes tenantId and integrationId)
|
||||
* @throws {BadRequestException} If state is invalid or expired
|
||||
*/
|
||||
async parseAuthState(state: string): Promise<{ state: string; expiresAt?: string }> {
|
||||
async parseAuthState(
|
||||
state: string
|
||||
): Promise<{ state: string; tenantId?: string; integrationId?: string; expiresAt?: string }> {
|
||||
try {
|
||||
// Basic validation - state should be a non-empty string
|
||||
if (!state || typeof state !== 'string' || state.trim().length === 0) {
|
||||
throw new BadRequestException('Invalid state parameter');
|
||||
}
|
||||
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new BadRequestException('Tenant ID is required');
|
||||
}
|
||||
// Escape LIKE metacharacters (including backslash) to prevent unintended pattern matching
|
||||
const escapedState = state.replace(/\\/g, '\\\\').replace(/[%_]/g, '\\$&');
|
||||
|
||||
// Find the integration by current tenant and Zapier provider
|
||||
const integration = await this._integrationService.findOneByOptions({
|
||||
// Search for the state across all tenants by value
|
||||
const stateSettings = await this._integrationSettingService.find({
|
||||
where: {
|
||||
tenantId,
|
||||
name: IntegrationEnum.ZAPIER
|
||||
} as IIntegrationFilter,
|
||||
relations: ['settings']
|
||||
});
|
||||
|
||||
if (!integration) {
|
||||
throw new NotFoundException('Zapier integration not found for current tenant');
|
||||
}
|
||||
|
||||
// Get settings to retrieve state
|
||||
const settings = await this._integrationSettingService.find({
|
||||
where: {
|
||||
integration: { id: integration.id }
|
||||
settingsName: 'state',
|
||||
settingsValue: Like(`%"state":"${escapedState}"%`)
|
||||
}
|
||||
});
|
||||
|
||||
const storedState = settings.find((s) => s.settingsName === 'state')?.settingsValue;
|
||||
// Find the matching state setting
|
||||
const stateSetting = stateSettings.find((s) => {
|
||||
try {
|
||||
const parsed = JSON.parse(s.settingsValue ?? '{}');
|
||||
return parsed.state === state;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!storedState) {
|
||||
if (!stateSetting) {
|
||||
throw new BadRequestException('State not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedState = JSON.parse(storedState);
|
||||
if (parsedState.state !== state) {
|
||||
throw new BadRequestException('Invalid state parameter');
|
||||
}
|
||||
const parsedState = JSON.parse(stateSetting.settingsValue);
|
||||
|
||||
// Check expiration
|
||||
if (parsedState.expiresAt && new Date(parsedState.expiresAt) < new Date()) {
|
||||
throw new BadRequestException('State has expired');
|
||||
@@ -386,6 +376,9 @@ export class ZapierService {
|
||||
|
||||
return parsedState;
|
||||
} catch (error) {
|
||||
if (error instanceof BadRequestException) {
|
||||
throw error;
|
||||
}
|
||||
throw new BadRequestException('Invalid state format');
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -410,7 +403,7 @@ export class ZapierService {
|
||||
integrationId: string,
|
||||
organizationId?: string
|
||||
): Promise<void> {
|
||||
const expirationTime = new Date();
|
||||
let expirationTime = new Date();
|
||||
expirationTime.setMinutes(expirationTime.getMinutes() + 10); // State expires in 10 minutes
|
||||
|
||||
await this._integrationSettingService.create({
|
||||
@@ -420,25 +413,35 @@ export class ZapierService {
|
||||
integrationId,
|
||||
settingsValue: JSON.stringify({
|
||||
state,
|
||||
tenantId,
|
||||
integrationId,
|
||||
expiresAt: expirationTime.toISOString()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store client credentials for later use in OAuth flow
|
||||
* Store integration credentials using server-side config.
|
||||
* Client credentials (client_id, client_secret) are read from environment variables
|
||||
* and are never exposed to or stored in tenant records.
|
||||
*/
|
||||
async storeIntegrationCredentials(input: ICreateZapierIntegrationInput): Promise<IIntegrationTenant> {
|
||||
async storeIntegrationCredentials(input: { organizationId: string; state: string }): Promise<IIntegrationTenant> {
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
throw new BadRequestException('Tenant ID is required');
|
||||
}
|
||||
const { client_id, client_secret, state, organizationId } = input;
|
||||
const { state, organizationId } = input;
|
||||
|
||||
if (!state) {
|
||||
throw new BadRequestException('State parameter is required');
|
||||
}
|
||||
|
||||
// Validate that server-side OAuth credentials are configured (do NOT persist them to tenant)
|
||||
const zapierConfig = this.config.get('zapier');
|
||||
if (!zapierConfig?.clientId || !zapierConfig?.clientSecret) {
|
||||
throw new BadRequestException('Zapier OAuth credentials are not configured on the server. Please set GAUZY_ZAPIER_CLIENT_ID and GAUZY_ZAPIER_CLIENT_SECRET environment variables.');
|
||||
}
|
||||
|
||||
// Find or create the base integration
|
||||
const baseIntegration =
|
||||
(await this._integrationService.findOneByOptions({
|
||||
@@ -466,7 +469,7 @@ export class ZapierService {
|
||||
};
|
||||
}) as IIntegrationEntitySetting[];
|
||||
|
||||
// Create the integration first
|
||||
// Create the integration (without storing global OAuth credentials in tenant records)
|
||||
const integration = await this._commandBus.execute(
|
||||
new IntegrationTenantUpdateOrCreateCommand(
|
||||
{
|
||||
@@ -487,18 +490,6 @@ export class ZapierService {
|
||||
settingsValue: 'true',
|
||||
tenantId,
|
||||
organizationId
|
||||
},
|
||||
{
|
||||
settingsName: 'client_id',
|
||||
settingsValue: client_id,
|
||||
tenantId,
|
||||
organizationId
|
||||
},
|
||||
{
|
||||
settingsName: 'client_secret',
|
||||
settingsValue: client_secret,
|
||||
tenantId,
|
||||
organizationId
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -512,23 +503,28 @@ export class ZapierService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the OAuth flow by exchanging the authorization code for tokens
|
||||
* Complete the OAuth flow by exchanging the authorization code for tokens.
|
||||
* Accepts tenantId as a parameter so it works from @Public() callback endpoints.
|
||||
*
|
||||
* @param code The authorization code from Zapier
|
||||
* @param state The state parameter for CSRF validation
|
||||
* @param tenantId The tenant ID (from parsed state or RequestContext)
|
||||
*/
|
||||
async completeOAuthFlow(code: string, state: string): Promise<IIntegrationTenant> {
|
||||
async completeOAuthFlow(code: string, state: string, tenantId?: string): Promise<IIntegrationTenant> {
|
||||
try {
|
||||
// Get current tenant ID from request context
|
||||
const tenantId = RequestContext.currentTenantId();
|
||||
if (!tenantId) {
|
||||
// Use provided tenantId or fall back to request context
|
||||
const resolvedTenantId = tenantId || RequestContext.currentTenantId();
|
||||
if (!resolvedTenantId) {
|
||||
throw new BadRequestException('Tenant ID is required');
|
||||
}
|
||||
|
||||
// Validate and atomically delete the state
|
||||
await this.validateAndDeleteState(state, tenantId);
|
||||
await this.validateAndDeleteState(state, resolvedTenantId);
|
||||
|
||||
// Find the integration by current tenant and Zapier provider
|
||||
// Find the integration by tenant and Zapier provider
|
||||
const integration = await this._integrationService.findOneByOptions({
|
||||
where: {
|
||||
tenantId,
|
||||
tenantId: resolvedTenantId,
|
||||
name: IntegrationEnum.ZAPIER
|
||||
} as IIntegrationFilter,
|
||||
relations: ['settings']
|
||||
@@ -543,43 +539,44 @@ export class ZapierService {
|
||||
name: IntegrationEnum.ZAPIER
|
||||
};
|
||||
|
||||
// Get settings to retrieve client_id and client_secret
|
||||
// Read client credentials from server-side config (env vars) — never from tenant data
|
||||
const zapierConfig = this.config.get('zapier');
|
||||
const client_id = zapierConfig?.clientId;
|
||||
const client_secret = zapierConfig?.clientSecret;
|
||||
|
||||
if (!client_id || !client_secret) {
|
||||
throw new BadRequestException('Zapier OAuth credentials are not configured on the server');
|
||||
}
|
||||
|
||||
// Fetch settings for other purposes (not client credentials)
|
||||
const settings = await this._integrationSettingService.find({
|
||||
where: {
|
||||
integration: { id: integrationTenant.id }
|
||||
}
|
||||
});
|
||||
|
||||
const client_id = settings.find((s) => s.settingsName === 'client_id')?.settingsValue;
|
||||
const client_secret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
|
||||
|
||||
if (!client_id || !client_secret) {
|
||||
throw new BadRequestException('Missing required Zapier integration settings');
|
||||
}
|
||||
|
||||
const redirect_uri = this.config.get('zapier')?.redirectUri;
|
||||
if (!redirect_uri) {
|
||||
throw new Error('Zapier redirect URI is not configured');
|
||||
}
|
||||
|
||||
// Exchange code for tokens
|
||||
// Exchange code for tokens using application/x-www-form-urlencoded (OAuth2 standard)
|
||||
const tokenParams = new URLSearchParams({
|
||||
client_id,
|
||||
client_secret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri
|
||||
});
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this._httpService
|
||||
.post(
|
||||
`${ZAPIER_BASE_URL}/oauth/token/`,
|
||||
{
|
||||
client_id,
|
||||
client_secret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
.post(`${ZAPIER_BASE_URL}/oauth/token/`, tokenParams.toString(), {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json'
|
||||
}
|
||||
)
|
||||
})
|
||||
.pipe(
|
||||
catchError((error: AxiosError) => {
|
||||
this.logger.error(
|
||||
@@ -592,28 +589,41 @@ export class ZapierService {
|
||||
)
|
||||
);
|
||||
|
||||
// Store the tokens using the existing settings' tenant and organization info
|
||||
// Store the tokens — delete ALL old token settings (Zapier + Gauzy OAuth) first, then save new ones
|
||||
const existingSetting = settings[0];
|
||||
const allTokenSettingNames = [
|
||||
'zapier_access_token',
|
||||
'zapier_refresh_token',
|
||||
'oauth_access_token',
|
||||
'oauth_refresh_token'
|
||||
];
|
||||
const staleTokens = settings.filter((s) => allTokenSettingNames.includes(s.settingsName));
|
||||
await Promise.all(
|
||||
staleTokens
|
||||
.map((s) => s.id)
|
||||
.filter((id): id is string => id != null)
|
||||
.map((id) => this._integrationSettingService.delete(id))
|
||||
);
|
||||
|
||||
const filteredSettings = settings.filter((s) => !allTokenSettingNames.includes(s.settingsName));
|
||||
|
||||
const updatedSettings = [
|
||||
...settings,
|
||||
...filteredSettings,
|
||||
{
|
||||
settingsName: 'access_token',
|
||||
settingsName: 'zapier_access_token',
|
||||
settingsValue: response.access_token,
|
||||
tenantId: existingSetting?.tenantId || tenantId,
|
||||
tenantId: existingSetting?.tenantId || resolvedTenantId,
|
||||
organizationId: existingSetting?.organizationId,
|
||||
integrationId: integrationTenant.id
|
||||
},
|
||||
{
|
||||
settingsName: 'refresh_token',
|
||||
settingsName: 'zapier_refresh_token',
|
||||
settingsValue: response.refresh_token,
|
||||
tenantId: existingSetting?.tenantId || tenantId,
|
||||
tenantId: existingSetting?.tenantId || resolvedTenantId,
|
||||
organizationId: existingSetting?.organizationId,
|
||||
integrationId: integrationTenant.id
|
||||
}
|
||||
].filter((setting, index, self) => {
|
||||
// Keep only unique settings by name (avoiding duplicates)
|
||||
return index === self.findIndex((s) => s.settingsName === setting.settingsName);
|
||||
}) as DeepPartial<IIntegrationEntitySetting>;
|
||||
] as any;
|
||||
|
||||
await this._integrationSettingService.save(updatedSettings);
|
||||
|
||||
@@ -636,10 +646,13 @@ export class ZapierService {
|
||||
*/
|
||||
async fetchTriggers(token: string): Promise<IZapierEndpoint[]> {
|
||||
try {
|
||||
const response = await this.fetchIntegration<{ triggers: IZapierEndpoint[] }>('triggers', token);
|
||||
const response = await this.fetchIntegration<{ triggers: IZapierEndpoint[] }>(
|
||||
`${ZAPIER_API_URL}/v2/triggers`,
|
||||
token
|
||||
);
|
||||
return response.triggers;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch Zapier triggers:', error);
|
||||
this.logger.error('Failed to fetch Zapier triggers:', error);
|
||||
throw new Error('Unable to fetch triggers from Zapier');
|
||||
}
|
||||
}
|
||||
@@ -653,14 +666,133 @@ export class ZapierService {
|
||||
*/
|
||||
async fetchActions(token: string): Promise<IZapierEndpoint[]> {
|
||||
try {
|
||||
const response = await this.fetchIntegration<{ actions: IZapierEndpoint[] }>('actions', token);
|
||||
const response = await this.fetchIntegration<{ actions: IZapierEndpoint[] }>(
|
||||
`${ZAPIER_API_URL}/v2/actions`,
|
||||
token
|
||||
);
|
||||
return response.actions;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch Zapier actions:', error);
|
||||
this.logger.error('Failed to fetch Zapier actions:', error);
|
||||
throw new Error('Unable to fetch actions from Zapier');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of Zaps belonging to the authenticated user.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/get-zaps-[v2]
|
||||
*
|
||||
* @param {string} token - The access token for authentication with the Zapier API.
|
||||
* @returns {Promise<IZapierZap[]>} - A promise that resolves to an array of Zaps.
|
||||
*/
|
||||
async fetchZaps(token: string): Promise<IZapierZap[]> {
|
||||
try {
|
||||
const response = await this.fetchIntegration<{ zaps: IZapierZap[] }>(
|
||||
`${ZAPIER_API_URL}/v2/zaps`,
|
||||
token
|
||||
);
|
||||
return response.zaps ?? [];
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to fetch Zapier zaps:', error);
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error('Unable to fetch zaps from Zapier');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Zap on behalf of the authenticated user.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
|
||||
*
|
||||
* @param {IZapierCreateZapInput} input - The Zap creation payload (title and steps).
|
||||
* @param {string} token - The access token for authentication with the Zapier API.
|
||||
* @returns {Promise<IZapierZap>} - A promise that resolves to the created Zap.
|
||||
*/
|
||||
async createZap(input: IZapierCreateZapInput, token: string): Promise<IZapierZap> {
|
||||
try {
|
||||
const headers = { Authorization: `Bearer ${token}` };
|
||||
return await firstValueFrom(
|
||||
this._httpService.post<IZapierZap>(`${ZAPIER_API_URL}/v2/zaps`, input, { headers }).pipe(
|
||||
catchError((error: AxiosError<any>) => {
|
||||
if (!error.response) {
|
||||
throw new HttpException(
|
||||
{ message: error.message, error },
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
const response: AxiosResponse<any> = error.response;
|
||||
throw new HttpException({ message: error.message, error }, response.status);
|
||||
}),
|
||||
map((response: AxiosResponse<IZapierZap>) => response.data)
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create Zapier zap:', error);
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error('Unable to create zap on Zapier');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches publicly available Zap templates from Zapier.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zap-templates/get-zap-templates
|
||||
*
|
||||
* Zap templates are a public endpoint and do not require an OAuth access token.
|
||||
* Per the Zapier docs, the endpoint lives at /v1/zap-templates and requires the
|
||||
* server-configured `client_id` to be passed as a query parameter.
|
||||
* See https://docs.zapier.com/powered-by-zapier/api-reference/zap-templates/get-zap-templates
|
||||
*
|
||||
* @param {number} [limit] - Optional page size to forward to Zapier.
|
||||
* @returns {Promise<IZapierZapTemplate[]>} - A promise that resolves to an array of Zap templates.
|
||||
*/
|
||||
async fetchZapTemplates(limit?: number): Promise<IZapierZapTemplate[]> {
|
||||
const clientId = this.config.get('zapier')?.clientId;
|
||||
if (!clientId) {
|
||||
throw new BadRequestException(
|
||||
'Zapier client_id is not configured on the server. Please set GAUZY_ZAPIER_CLIENT_ID.'
|
||||
);
|
||||
}
|
||||
|
||||
const params: Record<string, string> = { client_id: clientId };
|
||||
if (limit !== undefined && Number.isFinite(limit)) {
|
||||
params['limit'] = String(limit);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this._httpService
|
||||
.get<{ zap_templates: IZapierZapTemplate[] }>(`${ZAPIER_API_URL}/v1/zap-templates`, {
|
||||
params
|
||||
})
|
||||
.pipe(
|
||||
catchError((error: AxiosError<any>) => {
|
||||
if (!error.response) {
|
||||
throw new HttpException(
|
||||
{ message: error.message, error },
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
const response: AxiosResponse<any> = error.response;
|
||||
throw new HttpException({ message: error.message, error }, response.status);
|
||||
}),
|
||||
map(
|
||||
(response: AxiosResponse<{ zap_templates: IZapierZapTemplate[] }>) =>
|
||||
response.data
|
||||
)
|
||||
)
|
||||
);
|
||||
return response.zap_templates ?? [];
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to fetch Zapier zap templates:', error);
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error('Unable to fetch zap templates from Zapier');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Zapier integration tenant associated with the given access token.
|
||||
* @param token - The OAuth access token to verify.
|
||||
@@ -668,9 +800,9 @@ export class ZapierService {
|
||||
* @throws NotFoundException if the token is invalid or no Zapier integration is found.
|
||||
*/
|
||||
async findIntegrationByToken(token: string): Promise<IIntegrationTenant> {
|
||||
// 1) Lookup the access_token setting
|
||||
// 1) Lookup the Gauzy-issued OAuth access token (separate from Zapier API tokens)
|
||||
const setting = await this._integrationSettingService.findOneByWhereOptions({
|
||||
settingsName: 'access_token',
|
||||
settingsName: 'oauth_access_token',
|
||||
settingsValue: token
|
||||
});
|
||||
|
||||
@@ -698,30 +830,52 @@ export class ZapierService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find integration by client_id
|
||||
* @param clientId - The OAuth client ID
|
||||
* Resolve the tenant integration by searching for a Gauzy-issued authorization code.
|
||||
* Auth codes are stored per-integration with tenantId, so this reliably maps
|
||||
* the code to the correct tenant regardless of shared global client_id.
|
||||
*
|
||||
* @param code - The Gauzy-issued authorization code
|
||||
* @returns The matching IIntegrationTenant
|
||||
* @throws NotFoundException if no integration is found
|
||||
* @throws NotFoundException if the code is invalid or expired
|
||||
*/
|
||||
async findIntegrationByClientId(clientId: string): Promise<IIntegrationTenant> {
|
||||
// Find the client_id setting
|
||||
const setting = await this._integrationSettingService.findOneByWhereOptions({
|
||||
settingsName: 'client_id',
|
||||
settingsValue: clientId
|
||||
async findIntegrationByAuthCode(code: string): Promise<IIntegrationTenant> {
|
||||
// Search for auth_code settings by value across all tenants
|
||||
const codeSettings = await this._integrationSettingService.find({
|
||||
where: {
|
||||
settingsName: 'auth_code'
|
||||
}
|
||||
});
|
||||
|
||||
if (!setting?.integrationId) {
|
||||
throw new NotFoundException('Invalid client ID');
|
||||
const codeSetting = codeSettings.find((s) => {
|
||||
try {
|
||||
const parsed = JSON.parse(s.settingsValue ?? '{}');
|
||||
return parsed.code === code;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!codeSetting?.integrationId) {
|
||||
throw new NotFoundException('Invalid authorization code');
|
||||
}
|
||||
|
||||
// Load the integration tenant
|
||||
const integrationTenant = await this._integrationService.findOneByIdString(setting.integrationId, {
|
||||
// Verify the code hasn't expired
|
||||
try {
|
||||
const parsed = JSON.parse(codeSetting.settingsValue ?? '{}');
|
||||
if (parsed.expiresAt && new Date(parsed.expiresAt) < new Date()) {
|
||||
throw new BadRequestException('Authorization code has expired');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof BadRequestException) throw e;
|
||||
}
|
||||
|
||||
const integrationTenant = await this._integrationService.findOneByIdString(codeSetting.integrationId, {
|
||||
where: { name: IntegrationEnum.ZAPIER },
|
||||
relations: ['settings']
|
||||
});
|
||||
|
||||
if (!integrationTenant) {
|
||||
throw new NotFoundException('Zapier integration not found for the provided client ID');
|
||||
throw new NotFoundException('Zapier integration not found for the provided authorization code');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -731,21 +885,60 @@ export class ZapierService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate client secret for OAuth flow
|
||||
* @param integrationId - The integration ID
|
||||
* @param clientSecret - The client secret to validate
|
||||
* @throws BadRequestException if client secret is invalid
|
||||
* Resolve the tenant integration by searching for a Gauzy-issued refresh token.
|
||||
* Refresh tokens are stored per-integration with tenantId, so this reliably maps
|
||||
* the token to the correct tenant regardless of shared global client_id.
|
||||
*
|
||||
* @param refreshToken - The Gauzy-issued refresh token
|
||||
* @returns The matching IIntegrationTenant
|
||||
* @throws NotFoundException if the token is invalid
|
||||
*/
|
||||
async validateClientSecret(integrationId: ID, clientSecret: string): Promise<void> {
|
||||
const settings = await this._integrationSettingService.find({
|
||||
async findIntegrationByGauzyRefreshToken(refreshToken: string): Promise<IIntegrationTenant> {
|
||||
// Search for oauth_refresh_token settings by value across all tenants
|
||||
const tokenSettings = await this._integrationSettingService.find({
|
||||
where: {
|
||||
integration: { id: integrationId }
|
||||
settingsName: 'oauth_refresh_token'
|
||||
}
|
||||
});
|
||||
|
||||
const storedClientSecret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
|
||||
const tokenSetting = tokenSettings.find((s) => s.settingsValue === refreshToken);
|
||||
|
||||
if (!storedClientSecret || storedClientSecret !== clientSecret) {
|
||||
if (!tokenSetting?.integrationId) {
|
||||
throw new NotFoundException('Invalid refresh token');
|
||||
}
|
||||
|
||||
const integrationTenant = await this._integrationService.findOneByIdString(tokenSetting.integrationId, {
|
||||
where: { name: IntegrationEnum.ZAPIER },
|
||||
relations: ['settings']
|
||||
});
|
||||
|
||||
if (!integrationTenant) {
|
||||
throw new NotFoundException('Zapier integration not found for the provided refresh token');
|
||||
}
|
||||
|
||||
return {
|
||||
...integrationTenant,
|
||||
name: IntegrationEnum.ZAPIER
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the provided client_id and client_secret match the server-side
|
||||
* Zapier OAuth configuration. This replaces the old approach of looking up
|
||||
* credentials in tenant integration records.
|
||||
*
|
||||
* @param clientId - The client_id from the request
|
||||
* @param clientSecret - The client_secret from the request
|
||||
* @throws BadRequestException if credentials don't match server config
|
||||
*/
|
||||
validateServerClientCredentials(clientId: string, clientSecret: string): void {
|
||||
const zapierConfig = this.config.get('zapier');
|
||||
if (
|
||||
!zapierConfig?.clientId ||
|
||||
!zapierConfig?.clientSecret ||
|
||||
zapierConfig.clientId !== clientId ||
|
||||
zapierConfig.clientSecret !== clientSecret
|
||||
) {
|
||||
throw new BadRequestException('Invalid client credentials');
|
||||
}
|
||||
}
|
||||
@@ -768,17 +961,17 @@ export class ZapierService {
|
||||
const tenantId = existingSettings[0]?.tenantId;
|
||||
const organizationId = existingSettings[0]?.organizationId;
|
||||
|
||||
// Prepare token settings
|
||||
// Prepare Gauzy-issued OAuth token settings (distinct from Zapier API tokens)
|
||||
const tokenSettings = [
|
||||
{
|
||||
settingsName: 'access_token',
|
||||
settingsName: 'oauth_access_token',
|
||||
settingsValue: accessToken,
|
||||
tenantId,
|
||||
organizationId,
|
||||
integrationId
|
||||
},
|
||||
{
|
||||
settingsName: 'refresh_token',
|
||||
settingsName: 'oauth_refresh_token',
|
||||
settingsValue: refreshToken,
|
||||
tenantId,
|
||||
organizationId,
|
||||
@@ -786,9 +979,21 @@ export class ZapierService {
|
||||
}
|
||||
];
|
||||
|
||||
// Remove existing token settings and add new ones
|
||||
// Delete only Gauzy OAuth token settings before saving new ones (preserve Zapier API tokens)
|
||||
const oauthTokenSettingNames = [
|
||||
'oauth_access_token',
|
||||
'oauth_refresh_token'
|
||||
];
|
||||
const staleTokens = existingSettings.filter((setting) => oauthTokenSettingNames.includes(setting.settingsName));
|
||||
await Promise.all(
|
||||
staleTokens
|
||||
.map((s) => s.id)
|
||||
.filter((id): id is string => id != null)
|
||||
.map((id) => this._integrationSettingService.delete(id))
|
||||
);
|
||||
|
||||
const filteredExistingSettings = existingSettings.filter(
|
||||
(setting) => !['access_token', 'refresh_token'].includes(setting.settingsName)
|
||||
(setting) => !oauthTokenSettingNames.includes(setting.settingsName)
|
||||
);
|
||||
|
||||
const allSettings = [...filteredExistingSettings, ...tokenSettings] as DeepPartial<IIntegrationEntitySetting>;
|
||||
@@ -835,20 +1040,12 @@ export class ZapierService {
|
||||
throw new NotFoundException(`No settings found for integration ID ${integrationId}`);
|
||||
}
|
||||
|
||||
// Verify the refresh token matches
|
||||
const storedRefreshToken = settings.find((s) => s.settingsName === 'refresh_token')?.settingsValue;
|
||||
// Verify the Gauzy-issued OAuth refresh token matches
|
||||
const storedRefreshToken = settings.find((s) => s.settingsName === 'oauth_refresh_token')?.settingsValue;
|
||||
if (!storedRefreshToken || storedRefreshToken !== refreshToken) {
|
||||
throw new BadRequestException('Invalid refresh token');
|
||||
}
|
||||
|
||||
// Extract required settings
|
||||
const client_id = settings.find((s) => s.settingsName === 'client_id')?.settingsValue;
|
||||
const client_secret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
|
||||
|
||||
if (!client_id || !client_secret) {
|
||||
throw new BadRequestException('Missing required Zapier integration settings');
|
||||
}
|
||||
|
||||
return this.generateAndStoreNewTokens(integrationId);
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Failed to refresh token for integration ID ${integrationId}`, {
|
||||
@@ -862,6 +1059,125 @@ export class ZapierService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a short-lived authorization code for the OAuth flow.
|
||||
* The code is single-use and expires in 10 minutes.
|
||||
*
|
||||
* @param integrationId The integration ID
|
||||
* @param code The authorization code
|
||||
* @param redirectUri The redirect URI used in the authorization request
|
||||
*/
|
||||
async storeAuthCode(integrationId: ID, code: string, redirectUri: string): Promise<void> {
|
||||
const existingSettings = await this._integrationSettingService.find({
|
||||
where: { integration: { id: integrationId } }
|
||||
});
|
||||
|
||||
const tenantId = existingSettings[0]?.tenantId;
|
||||
const organizationId = existingSettings[0]?.organizationId;
|
||||
|
||||
// Invalidate any existing auth_code records for this integration
|
||||
const existingAuthCodes = await this._integrationSettingService.find({
|
||||
where: {
|
||||
integration: { id: integrationId },
|
||||
settingsName: 'auth_code'
|
||||
}
|
||||
});
|
||||
await Promise.all(
|
||||
existingAuthCodes
|
||||
.map((s) => s.id)
|
||||
.filter((id): id is string => id != null)
|
||||
.map((id) => this._integrationSettingService.delete(id))
|
||||
);
|
||||
|
||||
let expirationTime = new Date();
|
||||
expirationTime.setMinutes(expirationTime.getMinutes() + 10);
|
||||
|
||||
await this._integrationSettingService.create({
|
||||
settingsName: 'auth_code',
|
||||
tenantId,
|
||||
organizationId,
|
||||
integrationId,
|
||||
settingsValue: JSON.stringify({
|
||||
code,
|
||||
redirectUri,
|
||||
expiresAt: expirationTime.toISOString()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and consume an authorization code (single-use, atomic delete).
|
||||
* Uses a conditional delete (id + original settingsValue) to prevent
|
||||
* concurrent requests from both validating and minting tokens.
|
||||
*
|
||||
* @param integrationId The integration ID
|
||||
* @param code The authorization code to validate
|
||||
* @param redirectUri The redirect URI to verify against the stored one
|
||||
* @throws BadRequestException if code is invalid, expired, already consumed, or redirect_uri mismatch
|
||||
*/
|
||||
async validateAndConsumeAuthCode(integrationId: ID, code: string, redirectUri: string): Promise<void> {
|
||||
const codeSettings = await this._integrationSettingService.find({
|
||||
where: {
|
||||
integration: { id: integrationId },
|
||||
settingsName: 'auth_code'
|
||||
}
|
||||
});
|
||||
|
||||
// Find matching auth code
|
||||
const codeSetting = codeSettings.find((s) => {
|
||||
try {
|
||||
const parsed = JSON.parse(s.settingsValue ?? '{}');
|
||||
return parsed.code === code;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!codeSetting || !codeSetting.id) {
|
||||
throw new BadRequestException('Invalid authorization code');
|
||||
}
|
||||
|
||||
let parsedCode: any;
|
||||
try {
|
||||
parsedCode = JSON.parse(codeSetting.settingsValue ?? '{}');
|
||||
} catch {
|
||||
// Corrupted data — cleanup by ID only and fail
|
||||
await this._integrationSettingService.typeOrmIntegrationSettingRepository.delete(codeSetting.id);
|
||||
throw new BadRequestException('Corrupted authorization code data');
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if (parsedCode.expiresAt && new Date(parsedCode.expiresAt) < new Date()) {
|
||||
// Use repository.delete() with FindOptionsWhere for atomic single-use guarantee
|
||||
const result = await this._integrationSettingService.typeOrmIntegrationSettingRepository.delete({
|
||||
id: codeSetting.id,
|
||||
settingsValue: codeSetting.settingsValue
|
||||
});
|
||||
if (result.affected !== 1) {
|
||||
throw new BadRequestException('Invalid authorization code');
|
||||
}
|
||||
throw new BadRequestException('Authorization code has expired');
|
||||
}
|
||||
|
||||
// Atomic single-use delete: consume the code before any further validation to prevent probing.
|
||||
// We match on both id AND the exact settingsValue to detect concurrent consumption.
|
||||
// Use repository.delete() with FindOptionsWhere to ensure both predicates are applied.
|
||||
const result = await this._integrationSettingService.typeOrmIntegrationSettingRepository.delete({
|
||||
id: codeSetting.id,
|
||||
settingsValue: codeSetting.settingsValue
|
||||
});
|
||||
|
||||
if (result.affected !== 1) {
|
||||
// Another concurrent request already consumed this code.
|
||||
throw new BadRequestException('Invalid authorization code');
|
||||
}
|
||||
|
||||
// Verify redirect_uri matches (code already consumed to prevent probing)
|
||||
if (parsedCode.redirectUri && parsedCode.redirectUri !== redirectUri) {
|
||||
throw new BadRequestException('Redirect URI mismatch');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Zapier integration settings for the current tenant.
|
||||
*
|
||||
@@ -905,23 +1221,21 @@ export class ZapierService {
|
||||
const enabledSetting = integrationTenant.settings?.find(
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'is_enabled'
|
||||
);
|
||||
const clientIdSetting = integrationTenant.settings?.find(
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'client_id'
|
||||
);
|
||||
const clientSecretSetting = integrationTenant.settings?.find(
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'client_secret'
|
||||
);
|
||||
const accessTokenSetting = integrationTenant.settings?.find(
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'access_token'
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'zapier_access_token'
|
||||
);
|
||||
const refreshTokenSetting = integrationTenant.settings?.find(
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'refresh_token'
|
||||
(setting: IIntegrationSetting) => setting.settingsName === 'zapier_refresh_token'
|
||||
);
|
||||
|
||||
// Client credentials are server-side config, not tenant-specific
|
||||
const zapierConfig = this.config.get('zapier');
|
||||
const hasClientCredentials = !!(zapierConfig?.clientId && zapierConfig?.clientSecret);
|
||||
|
||||
// Map to sanitized DTO
|
||||
return {
|
||||
isEnabled: enabledSetting ? enabledSetting.settingsValue === 'true' : false,
|
||||
hasClientCredentials: !!(clientIdSetting?.settingsValue && clientSecretSetting?.settingsValue),
|
||||
hasClientCredentials,
|
||||
hasAccessToken: !!accessTokenSetting?.settingsValue,
|
||||
hasRefreshToken: !!refreshTokenSetting?.settingsValue
|
||||
};
|
||||
|
||||
@@ -11,8 +11,6 @@ export interface IZapierAccessTokens {
|
||||
}
|
||||
|
||||
export interface ICreateZapierIntegrationInput extends IBasePerTenantAndOrganizationEntityModel {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
code?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
|
||||
@@ -44,6 +44,7 @@ typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
.cache/
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
build
|
||||
.cache
|
||||
.env
|
||||
src
|
||||
tsconfig.json
|
||||
tsconfig.tsbuildinfo
|
||||
*.ts
|
||||
!*.d.ts
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gauzy-zapier",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "Zapier integration for Gauzy",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -13,13 +13,13 @@
|
||||
"clean": "rimraf lib dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"zapier-platform-core": "16.5.0"
|
||||
"zapier-platform-core": "18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.9.3",
|
||||
"zapier-platform-cli": "16.5.0"
|
||||
"zapier-platform-cli": "18.3.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ const appName = process.env.APP_NAME || 'Gauzy';
|
||||
export const authentication = {
|
||||
/** Specifies OAuth2 as the authentication type */
|
||||
type: 'oauth2',
|
||||
/** Configuration for testing the authentication */
|
||||
/** Configuration for testing the authentication — uses Zapier-specific endpoint */
|
||||
test: {
|
||||
url: `${process.env.API_BASE_URL}/api/auth/authenticated`,
|
||||
url: `${process.env.API_BASE_URL}/api/integration/zapier/auth/test`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer {{bundle.authData.access_token}}'
|
||||
@@ -20,18 +20,17 @@ export const authentication = {
|
||||
connectionLabel: async (z: ZObject, bundle: Bundle) => {
|
||||
try {
|
||||
const response = await z.request({
|
||||
url: `${process.env.API_BASE_URL}/api/user/me`,
|
||||
url: `${process.env.API_BASE_URL}/api/integration/zapier/auth/me`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${bundle.authData.access_token}`
|
||||
}
|
||||
});
|
||||
// Format the connection label with user information
|
||||
const userData = response.data;
|
||||
if (userData?.name) {
|
||||
return `${userData.name} - ${appName}`;
|
||||
const data = response.data;
|
||||
if (data?.name) {
|
||||
return `${data.name} - ${appName}`;
|
||||
}
|
||||
if (userData?.email) {
|
||||
return `${userData.email} - ${appName}`;
|
||||
if (data?.email) {
|
||||
return `${data.email} - ${appName}`;
|
||||
}
|
||||
|
||||
return `${appName} Connection`;
|
||||
@@ -56,7 +55,7 @@ export const authentication = {
|
||||
/** Configuration for obtaining access token */
|
||||
getAccessToken: {
|
||||
method: 'POST',
|
||||
url: `${process.env.API_BASE_URL}/api/integration/zapier/token`,
|
||||
url: `${process.env.API_BASE_URL}/api/integration/zapier/oauth/token`,
|
||||
body: {
|
||||
code: '{{bundle.inputData.code}}',
|
||||
client_id: '{{process.env.CLIENT_ID}}',
|
||||
@@ -72,7 +71,7 @@ export const authentication = {
|
||||
/** Configuration for refreshing access token */
|
||||
refreshAccessToken: {
|
||||
method: 'POST',
|
||||
url: `${process.env.API_BASE_URL}/api/integration/zapier/refresh-token`,
|
||||
url: `${process.env.API_BASE_URL}/api/integration/zapier/oauth/refresh-token`,
|
||||
body: {
|
||||
refresh_token: '{{bundle.authData.refresh_token}}',
|
||||
client_id: '{{process.env.CLIENT_ID}}',
|
||||
|
||||
@@ -1,30 +1,81 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||
import { tap, catchError } from 'rxjs/operators';
|
||||
import { IMakeComIntegrationSettings } from '@gauzy/contracts';
|
||||
import {
|
||||
IMakeComIntegrationSettings,
|
||||
IMakeComOrganization,
|
||||
IMakeComTeam,
|
||||
IMakeComScenario,
|
||||
IMakeComHook,
|
||||
IMakeComConnection,
|
||||
IMakeComTemplate,
|
||||
IMakeComSetupStatus,
|
||||
MakeComZone
|
||||
} from '@gauzy/contracts';
|
||||
import { MakeComService } from './make-com.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MakeComStoreService {
|
||||
private _settings$: BehaviorSubject<IMakeComIntegrationSettings | null> =
|
||||
new BehaviorSubject<IMakeComIntegrationSettings | null>(null);
|
||||
public settings$: Observable<IMakeComIntegrationSettings | null> = this._settings$.asObservable();
|
||||
// ─── Setup status state ──────────────────────────────────────────────────
|
||||
private readonly _setupStatus$ = new BehaviorSubject<IMakeComSetupStatus | null>(null);
|
||||
public setupStatus$ = this._setupStatus$.asObservable();
|
||||
|
||||
private _oauthConfig$: BehaviorSubject<{ clientId: string; redirectUri: string } | null> = new BehaviorSubject<{
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
} | null>(null);
|
||||
public oauthConfig$: Observable<{ clientId: string; redirectUri: string } | null> =
|
||||
this._oauthConfig$.asObservable();
|
||||
// ─── Settings state ─────────────────────────────────────────────────────
|
||||
private readonly _settings$ = new BehaviorSubject<IMakeComIntegrationSettings | null>(null);
|
||||
public settings$ = this._settings$.asObservable();
|
||||
|
||||
// ─── Zone state ─────────────────────────────────────────────────────────
|
||||
private readonly _zone$ = new BehaviorSubject<MakeComZone | null>(null);
|
||||
public zone$ = this._zone$.asObservable();
|
||||
|
||||
// ─── Make.com context state ─────────────────────────────────────────────
|
||||
private readonly _makeOrganizations$ = new BehaviorSubject<IMakeComOrganization[]>([]);
|
||||
public makeOrganizations$ = this._makeOrganizations$.asObservable();
|
||||
|
||||
private readonly _selectedMakeOrganization$ = new BehaviorSubject<IMakeComOrganization | null>(null);
|
||||
public selectedMakeOrganization$ = this._selectedMakeOrganization$.asObservable();
|
||||
|
||||
private readonly _makeTeams$ = new BehaviorSubject<IMakeComTeam[]>([]);
|
||||
public makeTeams$ = this._makeTeams$.asObservable();
|
||||
|
||||
private readonly _selectedMakeTeam$ = new BehaviorSubject<IMakeComTeam | null>(null);
|
||||
public selectedMakeTeam$ = this._selectedMakeTeam$.asObservable();
|
||||
|
||||
// ─── Resource state ─────────────────────────────────────────────────────
|
||||
private readonly _scenarios$ = new BehaviorSubject<IMakeComScenario[]>([]);
|
||||
public scenarios$ = this._scenarios$.asObservable();
|
||||
|
||||
private readonly _hooks$ = new BehaviorSubject<IMakeComHook[]>([]);
|
||||
public hooks$ = this._hooks$.asObservable();
|
||||
|
||||
private readonly _connections$ = new BehaviorSubject<IMakeComConnection[]>([]);
|
||||
public connections$ = this._connections$.asObservable();
|
||||
|
||||
private readonly _templates$ = new BehaviorSubject<IMakeComTemplate[]>([]);
|
||||
public templates$ = this._templates$.asObservable();
|
||||
|
||||
constructor(private readonly _makeComService: MakeComService) {}
|
||||
|
||||
/**
|
||||
* Loads the current Make.com integration settings
|
||||
* @returns An observable of the settings
|
||||
*/
|
||||
// ─── Setup Status ───────────────────────────────────────────────────────
|
||||
|
||||
loadSetupStatus(organizationId?: string): Observable<IMakeComSetupStatus> {
|
||||
return this._makeComService.getSetupStatus(organizationId).pipe(
|
||||
tap((status) => this._setupStatus$.next(status)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com setup status:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getSetupStatus(): IMakeComSetupStatus | null {
|
||||
return this._setupStatus$.getValue();
|
||||
}
|
||||
|
||||
// ─── Settings ───────────────────────────────────────────────────────────
|
||||
|
||||
loadIntegrationSettings(): Observable<IMakeComIntegrationSettings> {
|
||||
return this._makeComService.getIntegrationSettings().pipe(
|
||||
tap((settings) => this._settings$.next(settings)),
|
||||
@@ -35,11 +86,6 @@ export class MakeComStoreService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Make.com integration settings
|
||||
* @param settings The updated settings
|
||||
* @returns An observable of the updated settings
|
||||
*/
|
||||
updateIntegrationSettings(settings: {
|
||||
isEnabled: boolean;
|
||||
webhookUrl: string;
|
||||
@@ -53,58 +99,218 @@ export class MakeComStoreService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates Make.com OAuth settings
|
||||
* @param credentials The OAuth credentials
|
||||
* @returns An observable of the updated settings
|
||||
*/
|
||||
addOAuthSettings(credentials: { clientId: string; clientSecret: string }): Observable<{
|
||||
initializeIntegration(body: { organizationId: string }): Observable<{
|
||||
authorizationUrl: string;
|
||||
integrationId: string;
|
||||
}> {
|
||||
return this._makeComService.addOAuthSettings(credentials).pipe(
|
||||
return this._makeComService.initializeIntegration(body).pipe(
|
||||
catchError((error) => {
|
||||
console.error('Error adding Make.com OAuth settings:', error);
|
||||
console.error('Error initializing Make.com integration:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the OAuth configuration
|
||||
* @returns An observable of the OAuth config
|
||||
*/
|
||||
loadOAuthConfig(): Observable<{ clientId: string; redirectUri: string }> {
|
||||
return this._makeComService.getOAuthConfig().pipe(
|
||||
tap((config) => this._oauthConfig$.next(config)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com OAuth config:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current integration settings without making an API call
|
||||
* @returns The current settings or null if not loaded
|
||||
*/
|
||||
getCurrentSettings(): IMakeComIntegrationSettings | null {
|
||||
return this._settings$.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current OAuth configuration without making an API call
|
||||
* @returns The current OAuth config or null if not loaded
|
||||
*/
|
||||
getCurrentOAuthConfig(): { clientId: string; redirectUri: string } | null {
|
||||
return this._oauthConfig$.getValue();
|
||||
// ─── Zone ───────────────────────────────────────────────────────────────
|
||||
|
||||
loadZone(organizationId?: string): Observable<MakeComZone | null> {
|
||||
return this._makeComService.getZone(organizationId).pipe(
|
||||
tap((zone) => this._zone$.next(zone)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com zone:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all stored data
|
||||
*/
|
||||
setZone(zone: MakeComZone, organizationId?: string): Observable<{ success: boolean; zone: MakeComZone }> {
|
||||
return this._makeComService.setZone(zone, organizationId).pipe(
|
||||
tap(() => {
|
||||
this._zone$.next(zone);
|
||||
this._setupStatus$.next(null);
|
||||
this._selectedMakeOrganization$.next(null);
|
||||
this._makeOrganizations$.next([]);
|
||||
this._selectedMakeTeam$.next(null);
|
||||
this._makeTeams$.next([]);
|
||||
this._scenarios$.next([]);
|
||||
this._hooks$.next([]);
|
||||
this._connections$.next([]);
|
||||
this._templates$.next([]);
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Error setting Make.com zone:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getZone(): MakeComZone | null {
|
||||
return this._zone$.getValue();
|
||||
}
|
||||
|
||||
// ─── Organizations ──────────────────────────────────────────────────────
|
||||
|
||||
loadMakeOrganizations(organizationId?: string): Observable<IMakeComOrganization[]> {
|
||||
return this._makeComService.listOrganizations(organizationId).pipe(
|
||||
tap((orgs) => this._makeOrganizations$.next(orgs)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com organizations:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
selectMakeOrganization(org: IMakeComOrganization, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.setMakeOrganization(org.id, organizationId).pipe(
|
||||
tap(() => {
|
||||
this._selectedMakeOrganization$.next(org);
|
||||
this._selectedMakeTeam$.next(null);
|
||||
this._makeTeams$.next([]);
|
||||
this._scenarios$.next([]);
|
||||
this._hooks$.next([]);
|
||||
this._connections$.next([]);
|
||||
this._templates$.next([]);
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Error selecting Make.com organization:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Teams ──────────────────────────────────────────────────────────────
|
||||
|
||||
loadMakeTeams(makeOrgId?: number, organizationId?: string): Observable<IMakeComTeam[]> {
|
||||
return this._makeComService.listTeams(makeOrgId, organizationId).pipe(
|
||||
tap((teams) => this._makeTeams$.next(teams)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com teams:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
selectMakeTeam(team: IMakeComTeam, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.setMakeTeam(team.id, organizationId).pipe(
|
||||
tap(() => {
|
||||
this._selectedMakeTeam$.next(team);
|
||||
this._scenarios$.next([]);
|
||||
this._hooks$.next([]);
|
||||
this._connections$.next([]);
|
||||
this._templates$.next([]);
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Error selecting Make.com team:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Scenarios ──────────────────────────────────────────────────────────
|
||||
|
||||
loadScenarios(teamId?: number, organizationId?: string): Observable<IMakeComScenario[]> {
|
||||
return this._makeComService.listScenarios(teamId, organizationId).pipe(
|
||||
tap((scenarios) => this._scenarios$.next(scenarios)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com scenarios:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
startScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
|
||||
return this._makeComService.startScenario(id, organizationId);
|
||||
}
|
||||
|
||||
stopScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
|
||||
return this._makeComService.stopScenario(id, organizationId);
|
||||
}
|
||||
|
||||
runScenario(id: number, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.runScenario(id, organizationId);
|
||||
}
|
||||
|
||||
deleteScenario(id: number, organizationId?: string): Observable<void> {
|
||||
return this._makeComService.deleteScenario(id, organizationId);
|
||||
}
|
||||
|
||||
// ─── Hooks ──────────────────────────────────────────────────────────────
|
||||
|
||||
loadHooks(teamId?: number, organizationId?: string): Observable<IMakeComHook[]> {
|
||||
return this._makeComService.listHooks(teamId, organizationId).pipe(
|
||||
tap((hooks) => this._hooks$.next(hooks)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com hooks:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
enableHook(id: number, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.enableHook(id, organizationId);
|
||||
}
|
||||
|
||||
disableHook(id: number, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.disableHook(id, organizationId);
|
||||
}
|
||||
|
||||
pingHook(id: number, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.pingHook(id, organizationId);
|
||||
}
|
||||
|
||||
deleteHook(id: number, organizationId?: string): Observable<void> {
|
||||
return this._makeComService.deleteHook(id, organizationId);
|
||||
}
|
||||
|
||||
// ─── Connections ────────────────────────────────────────────────────────
|
||||
|
||||
loadConnections(teamId?: number, organizationId?: string): Observable<IMakeComConnection[]> {
|
||||
return this._makeComService.listConnections(teamId, organizationId).pipe(
|
||||
tap((connections) => this._connections$.next(connections)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com connections:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
testConnection(id: number, organizationId?: string): Observable<any> {
|
||||
return this._makeComService.testConnection(id, organizationId);
|
||||
}
|
||||
|
||||
deleteConnection(id: number, organizationId?: string): Observable<void> {
|
||||
return this._makeComService.deleteConnection(id, organizationId);
|
||||
}
|
||||
|
||||
// ─── Templates ──────────────────────────────────────────────────────────
|
||||
|
||||
loadTemplates(teamId?: number, organizationId?: string): Observable<IMakeComTemplate[]> {
|
||||
return this._makeComService.listTemplates(teamId, organizationId).pipe(
|
||||
tap((templates) => this._templates$.next(templates)),
|
||||
catchError((error) => {
|
||||
console.error('Error loading Make.com templates:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Store Management ───────────────────────────────────────────────────
|
||||
|
||||
clearStore(): void {
|
||||
this._setupStatus$.next(null);
|
||||
this._settings$.next(null);
|
||||
this._oauthConfig$.next(null);
|
||||
this._zone$.next(null);
|
||||
this._makeOrganizations$.next([]);
|
||||
this._selectedMakeOrganization$.next(null);
|
||||
this._makeTeams$.next([]);
|
||||
this._selectedMakeTeam$.next(null);
|
||||
this._scenarios$.next([]);
|
||||
this._hooks$.next([]);
|
||||
this._connections$.next([]);
|
||||
this._templates$.next([]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,59 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { API_PREFIX } from '@gauzy/ui-core/common';
|
||||
import { IMakeComIntegrationSettings, IMakeComCreateIntegration, IMakeComOAuthTokenDTO } from '@gauzy/contracts';
|
||||
import {
|
||||
IMakeComIntegrationSettings,
|
||||
IMakeComOAuthTokenDTO,
|
||||
IMakeComOrganization,
|
||||
IMakeComTeam,
|
||||
IMakeComConnection,
|
||||
IMakeComScenario,
|
||||
IMakeComHook,
|
||||
IMakeComTemplate,
|
||||
IMakeComSetupStatus,
|
||||
MakeComZone
|
||||
} from '@gauzy/contracts';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MakeComService {
|
||||
private readonly API_URL = `${API_PREFIX}/integration/make-com`;
|
||||
private readonly API_BASE = `${this.API_URL}/api`;
|
||||
|
||||
constructor(private readonly http: HttpClient) {}
|
||||
|
||||
/**
|
||||
* Get Make.com integration settings for the current tenant
|
||||
*/
|
||||
// ─── Settings & Auth ────────────────────────────────────────────────────
|
||||
|
||||
getIntegrationSettings(): Observable<IMakeComIntegrationSettings> {
|
||||
return this.http.get<IMakeComIntegrationSettings>(`${API_PREFIX}/integration/make-com`);
|
||||
return this.http.get<IMakeComIntegrationSettings>(this.API_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Make.com integration settings
|
||||
*/
|
||||
updateIntegrationSettings(settings: {
|
||||
isEnabled: boolean;
|
||||
webhookUrl: string;
|
||||
}): Observable<IMakeComIntegrationSettings> {
|
||||
return this.http.post<IMakeComIntegrationSettings>(`${API_PREFIX}/integration/make-com`, settings);
|
||||
return this.http.post<IMakeComIntegrationSettings>(this.API_URL, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update Make.com OAuth settings
|
||||
*/
|
||||
addOAuthSettings(credentials: IMakeComCreateIntegration): Observable<{
|
||||
initializeIntegration(body: { organizationId: string }): Observable<{
|
||||
authorizationUrl: string;
|
||||
integrationId: string;
|
||||
}> {
|
||||
return this.http.post<{
|
||||
authorizationUrl: string;
|
||||
integrationId: string;
|
||||
}>(`${API_PREFIX}/integration/make-com/oauth-settings`, {
|
||||
client_id: credentials.clientId,
|
||||
client_secret: credentials.clientSecret
|
||||
});
|
||||
}>(`${this.API_URL}/oauth-settings`, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Make.com OAuth configuration
|
||||
*/
|
||||
getOAuthConfig(): Observable<{ clientId: string; redirectUri: string }> {
|
||||
return this.http.get<{ clientId: string; redirectUri: string }>(
|
||||
`${API_PREFIX}/integration/make-com/oauth-config`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAuth callback from Make.com
|
||||
*/
|
||||
handleOAuthCallback(code: string, state: string): Observable<IMakeComOAuthTokenDTO> {
|
||||
return this.http.get<IMakeComOAuthTokenDTO>(`${API_PREFIX}/integration/make-com/oauth/callback`, {
|
||||
return this.http.get<IMakeComOAuthTokenDTO>(`${this.API_URL}/oauth/callback`, {
|
||||
params: { code, state }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle token requests from Make.com custom apps
|
||||
*/
|
||||
handleTokenRequest(body: {
|
||||
grant_type: string;
|
||||
code: string;
|
||||
@@ -72,6 +62,190 @@ export class MakeComService {
|
||||
client_secret: string;
|
||||
redirect_uri: string;
|
||||
}): Observable<IMakeComOAuthTokenDTO> {
|
||||
return this.http.post<IMakeComOAuthTokenDTO>(`${API_PREFIX}/integration/make-com/token`, body);
|
||||
return this.http.post<IMakeComOAuthTokenDTO>(`${this.API_URL}/token`, body);
|
||||
}
|
||||
|
||||
// ─── Setup Status ───────────────────────────────────────────────────────
|
||||
|
||||
getSetupStatus(organizationId?: string): Observable<IMakeComSetupStatus> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.get<IMakeComSetupStatus>(`${this.API_BASE}/setup-status`, { params });
|
||||
}
|
||||
|
||||
// ─── Zone Configuration ─────────────────────────────────────────────────
|
||||
|
||||
getZone(organizationId?: string): Observable<MakeComZone | null> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.get<{ zone: MakeComZone | null }>(`${this.API_BASE}/zone`, { params }).pipe(
|
||||
map((res) => res.zone)
|
||||
);
|
||||
}
|
||||
|
||||
setZone(zone: MakeComZone, organizationId?: string): Observable<{ success: boolean; zone: MakeComZone }> {
|
||||
return this.http.post<{ success: boolean; zone: MakeComZone }>(`${this.API_BASE}/zone`, {
|
||||
zone,
|
||||
organizationId
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Context (Make.com org/team selection) ──────────────────────────────
|
||||
|
||||
setMakeOrganization(
|
||||
makeOrganizationId: number,
|
||||
organizationId?: string
|
||||
): Observable<{ success: boolean; makeOrganizationId: number }> {
|
||||
return this.http.post<{ success: boolean; makeOrganizationId: number }>(
|
||||
`${this.API_BASE}/context/organization`,
|
||||
{ makeOrganizationId, organizationId }
|
||||
);
|
||||
}
|
||||
|
||||
setMakeTeam(
|
||||
makeTeamId: number,
|
||||
organizationId?: string
|
||||
): Observable<{ success: boolean; makeTeamId: number }> {
|
||||
return this.http.post<{ success: boolean; makeTeamId: number }>(`${this.API_BASE}/context/team`, {
|
||||
makeTeamId,
|
||||
organizationId
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Organizations ──────────────────────────────────────────────────────
|
||||
|
||||
listOrganizations(organizationId?: string): Observable<IMakeComOrganization[]> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.get<{ organizations: IMakeComOrganization[] }>(`${this.API_BASE}/organizations`, { params })
|
||||
.pipe(map((res) => res.organizations));
|
||||
}
|
||||
|
||||
// ─── Teams ──────────────────────────────────────────────────────────────
|
||||
|
||||
listTeams(makeOrgId?: number, organizationId?: string): Observable<IMakeComTeam[]> {
|
||||
const params: any = {};
|
||||
if (makeOrgId) params.makeOrgId = makeOrgId;
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.get<{ teams: IMakeComTeam[] }>(`${this.API_BASE}/teams`, { params }).pipe(
|
||||
map((res) => res.teams)
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Connections ────────────────────────────────────────────────────────
|
||||
|
||||
listConnections(teamId?: number, organizationId?: string): Observable<IMakeComConnection[]> {
|
||||
const params: any = {};
|
||||
if (teamId) params.teamId = teamId;
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.get<{ connections: IMakeComConnection[] }>(`${this.API_BASE}/connections`, { params })
|
||||
.pipe(map((res) => res.connections));
|
||||
}
|
||||
|
||||
deleteConnection(id: number, organizationId?: string): Observable<void> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.delete<void>(`${this.API_BASE}/connections/${id}`, { params });
|
||||
}
|
||||
|
||||
testConnection(id: number, organizationId?: string): Observable<any> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.post<any>(`${this.API_BASE}/connections/${id}/test`, null, { params });
|
||||
}
|
||||
|
||||
// ─── Scenarios ──────────────────────────────────────────────────────────
|
||||
|
||||
listScenarios(teamId?: number, organizationId?: string): Observable<IMakeComScenario[]> {
|
||||
const params: any = {};
|
||||
if (teamId) params.teamId = teamId;
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.get<{ scenarios: IMakeComScenario[] }>(`${this.API_BASE}/scenarios`, { params })
|
||||
.pipe(map((res) => res.scenarios));
|
||||
}
|
||||
|
||||
getScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.get<{ scenario: IMakeComScenario }>(`${this.API_BASE}/scenarios/${id}`, { params })
|
||||
.pipe(map((res) => res.scenario));
|
||||
}
|
||||
|
||||
startScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.post<{ scenario: IMakeComScenario }>(`${this.API_BASE}/scenarios/${id}/start`, null, { params })
|
||||
.pipe(map((res) => res.scenario));
|
||||
}
|
||||
|
||||
stopScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.post<{ scenario: IMakeComScenario }>(`${this.API_BASE}/scenarios/${id}/stop`, null, { params })
|
||||
.pipe(map((res) => res.scenario));
|
||||
}
|
||||
|
||||
runScenario(id: number, organizationId?: string): Observable<any> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.post<any>(`${this.API_BASE}/scenarios/${id}/run`, null, { params });
|
||||
}
|
||||
|
||||
deleteScenario(id: number, organizationId?: string): Observable<void> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.delete<void>(`${this.API_BASE}/scenarios/${id}`, { params });
|
||||
}
|
||||
|
||||
// ─── Hooks (Webhooks) ───────────────────────────────────────────────────
|
||||
|
||||
listHooks(teamId?: number, organizationId?: string): Observable<IMakeComHook[]> {
|
||||
const params: any = {};
|
||||
if (teamId) params.teamId = teamId;
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.get<{ hooks: IMakeComHook[] }>(`${this.API_BASE}/hooks`, { params }).pipe(
|
||||
map((res) => res.hooks)
|
||||
);
|
||||
}
|
||||
|
||||
enableHook(id: number, organizationId?: string): Observable<any> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.post<any>(`${this.API_BASE}/hooks/${id}/enable`, null, { params });
|
||||
}
|
||||
|
||||
disableHook(id: number, organizationId?: string): Observable<any> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.post<any>(`${this.API_BASE}/hooks/${id}/disable`, null, { params });
|
||||
}
|
||||
|
||||
pingHook(id: number, organizationId?: string): Observable<any> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.get<any>(`${this.API_BASE}/hooks/${id}/ping`, { params });
|
||||
}
|
||||
|
||||
deleteHook(id: number, organizationId?: string): Observable<void> {
|
||||
const params: any = {};
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http.delete<void>(`${this.API_BASE}/hooks/${id}`, { params });
|
||||
}
|
||||
|
||||
// ─── Templates ──────────────────────────────────────────────────────────
|
||||
|
||||
listTemplates(teamId?: number, organizationId?: string): Observable<IMakeComTemplate[]> {
|
||||
const params: any = {};
|
||||
if (teamId) params.teamId = teamId;
|
||||
if (organizationId) params.organizationId = organizationId;
|
||||
return this.http
|
||||
.get<{ templates: IMakeComTemplate[] }>(`${this.API_BASE}/templates`, { params })
|
||||
.pipe(map((res) => res.templates));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import { API_PREFIX } from '@gauzy/ui-core/common';
|
||||
import {
|
||||
IZapierEndpoint,
|
||||
IZapierOAuthTokenDTO,
|
||||
ICreateZapierIntegrationInput,
|
||||
IZapierWebhook,
|
||||
IZapierCreateWebhookInput,
|
||||
IZapierAuthConfig,
|
||||
IZapierIntegrationSettings,
|
||||
IIntegrationSetting,
|
||||
IZapierZap,
|
||||
IZapierCreateZapInput,
|
||||
IZapierZapTemplate,
|
||||
ID
|
||||
} from '@gauzy/contracts';
|
||||
|
||||
@@ -80,9 +82,10 @@ export class ZapierService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new Zapier integration
|
||||
* Initialize a new Zapier integration.
|
||||
* No client credentials needed — server uses its own env-configured credentials.
|
||||
*/
|
||||
initializeIntegration(body: ICreateZapierIntegrationInput): Observable<{
|
||||
initializeIntegration(body: { organizationId: string }): Observable<{
|
||||
authorizationUrl: string;
|
||||
integrationId: string;
|
||||
}> {
|
||||
@@ -97,7 +100,9 @@ export class ZapierService {
|
||||
*/
|
||||
getTriggers(token: string): Observable<IZapierEndpoint[]> {
|
||||
return this.http.get<IZapierEndpoint[]>(`${API_PREFIX}/integration/zapier/triggers`, {
|
||||
params: { token }
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,7 +111,46 @@ export class ZapierService {
|
||||
*/
|
||||
getActions(token: string): Observable<IZapierEndpoint[]> {
|
||||
return this.http.get<IZapierEndpoint[]>(`${API_PREFIX}/integration/zapier/actions`, {
|
||||
params: { token }
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zaps for the authenticated Zapier account
|
||||
*/
|
||||
getZaps(token: string): Observable<IZapierZap[]> {
|
||||
return this.http.get<IZapierZap[]>(`${API_PREFIX}/integration/zapier/zaps`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Zap on the authenticated Zapier account
|
||||
*/
|
||||
createZap(body: IZapierCreateZapInput, token: string): Observable<IZapierZap> {
|
||||
return this.http.post<IZapierZap>(`${API_PREFIX}/integration/zapier/zaps`, body, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get publicly available Zap templates.
|
||||
* Zap templates do not require an OAuth access token — the server attaches
|
||||
* the configured `client_id` on the way out to Zapier.
|
||||
*/
|
||||
getZapTemplates(limit?: number): Observable<IZapierZapTemplate[]> {
|
||||
const params: Record<string, string> = {};
|
||||
if (limit !== undefined) {
|
||||
params['limit'] = String(limit);
|
||||
}
|
||||
return this.http.get<IZapierZapTemplate[]>(`${API_PREFIX}/integration/zapier/zap-templates`, {
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,7 +164,7 @@ export class ZapierService {
|
||||
redirect_uri: string;
|
||||
grant_type: string;
|
||||
}): Observable<IZapierOAuthTokenDTO> {
|
||||
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/token`, body);
|
||||
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/oauth/token`, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +176,7 @@ export class ZapierService {
|
||||
client_secret: string;
|
||||
grant_type: string;
|
||||
}): Observable<IZapierOAuthTokenDTO> {
|
||||
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/refresh-token`, body);
|
||||
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/oauth/refresh-token`, body);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1189,16 +1189,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Scenarios",
|
||||
"EXECUTIONS": "Executions",
|
||||
"HISTORY": "History",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Refresh webhooks",
|
||||
"WEBHOOK_ENABLE": "Enable webhook",
|
||||
"WEBHOOK_DISABLE": "Disable webhook",
|
||||
"WEBHOOK_PING": "Ping webhook",
|
||||
"SCENARIO_ACTIVE": "Active",
|
||||
"SCENARIO_INACTIVE": "Inactive",
|
||||
"SCENARIO_LAST_EDITED": "Last edited: {{value}}",
|
||||
"SCENARIO_DISABLE": "Disable scenario",
|
||||
"SCENARIO_ENABLE": "Enable scenario",
|
||||
"SCENARIO_RUN_ONCE": "Run scenario once",
|
||||
"WEBHOOK_ENABLED": "Enabled",
|
||||
"WEBHOOK_DISABLED": "Disabled",
|
||||
"WEBHOOK_TYPE": "Type",
|
||||
"WEBHOOK_QUEUE": "Queue",
|
||||
"TEMPLATE_PUBLIC": "Public",
|
||||
"TEMPLATE_PUBLISHED": "Published",
|
||||
"REFRESH_CONNECTIONS": "Refresh connections",
|
||||
"CONFIGURATION_TITLE": "Make.com Configuration",
|
||||
"ZONE_LABEL": "Zone",
|
||||
"ZONE_HINT": "Select the zone that matches your Make.com account (e.g., us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Make.com Organization",
|
||||
"LOADING_ORGANIZATIONS": "Loading organizations...",
|
||||
"NO_ORGANIZATIONS": "No organizations found",
|
||||
"TEAM_LABEL": "Make.com Team",
|
||||
"LOADING_TEAMS": "Loading teams...",
|
||||
"NO_TEAMS": "No teams found",
|
||||
"CONNECTED_TO": "Connected to {{org}} / {{team}} on {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Webhook Settings",
|
||||
"CONNECTIONS": "Connections",
|
||||
"TEMPLATES": "Templates",
|
||||
"SETTINGS": "Settings",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Tim ber iyie pot buk ma mito ni ber",
|
||||
"START_AUTHORIZATION": "Please start the authorization process"
|
||||
"CONNECT_TO_MAKE_COM": "Connect to Make.com",
|
||||
"CONNECT_INFO": "Click the button below to connect your Make.com account. You will be redirected to Make.com to authorize the integration.",
|
||||
"NO_SCENARIOS": "No scenarios found. Create scenarios in your Make.com dashboard.",
|
||||
"NO_HOOKS": "No webhooks found. Create webhooks in your Make.com dashboard.",
|
||||
"NO_CONNECTIONS": "No connections found. Create connections in your Make.com dashboard.",
|
||||
"NO_TEMPLATES": "No templates found.",
|
||||
"TEST_CONNECTION": "Test connection",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Account: {{value}}",
|
||||
"TYPE": "Type: {{value}}",
|
||||
"PACKAGE": "Package: {{value}}",
|
||||
"EXPIRES": "Expires: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "Client Id",
|
||||
"CLIENT_SECRET": "Client Secret",
|
||||
"NEXT_STEP_INFO": "I cokke ki authorization, ibi nyuto i Make.com me tiyo ki integration"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Please fill in all required fields",
|
||||
"START_AUTHORIZATION": "Failed to start the authorization process",
|
||||
"NO_ORGANIZATION": "Please select an organization first",
|
||||
"LOAD_SETTINGS": "Failed to load Make.com settings",
|
||||
"SAVE_SETTINGS": "Failed to save Make.com settings",
|
||||
"LOAD_SETUP_STATUS": "Failed to load setup status",
|
||||
"LOAD_SCENARIOS": "Failed to load scenarios",
|
||||
"TOGGLE_SCENARIO": "Failed to toggle scenario",
|
||||
"RUN_SCENARIO": "Failed to run scenario",
|
||||
"LOAD_CONNECTIONS": "Failed to load connections",
|
||||
"TEST_CONNECTION": "Failed to test connection",
|
||||
"LOAD_WEBHOOKS": "Failed to load webhooks",
|
||||
"TOGGLE_WEBHOOK": "Failed to toggle webhook",
|
||||
"PING_WEBHOOK": "Failed to ping webhook",
|
||||
"SET_ZONE": "Failed to set zone",
|
||||
"LOAD_ORGANIZATIONS": "Failed to load organizations",
|
||||
"SELECT_ORGANIZATION": "Failed to select organization",
|
||||
"LOAD_TEAMS": "Failed to load teams",
|
||||
"SELECT_TEAM": "Failed to select team",
|
||||
"LOAD_TEMPLATES": "Failed to load templates",
|
||||
"REQUEST_TIMEOUT": "Request timed out. Please try again."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Make.com settings saved successfully",
|
||||
"SCENARIO_ACTIVATED": "Scenario activated",
|
||||
"SCENARIO_DEACTIVATED": "Scenario deactivated",
|
||||
"SCENARIO_EXECUTED": "Scenario executed",
|
||||
"CONNECTION_VALID": "Connection \"{{name}}\" is valid",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook enabled",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook disabled",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" pinged successfully",
|
||||
"ZONE_SET": "Zone set to {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organization \"{{name}}\" selected",
|
||||
"TEAM_SELECTED": "Team \"{{name}}\" selected"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Please authorize with Make.com first. Go to the Settings tab to connect your account.",
|
||||
"ZONE": "Please select your Make.com zone in the Settings tab.",
|
||||
"ORGANIZATION": "Please select your Make.com organization in the Settings tab.",
|
||||
"TEAM": "Please select your Make.com team in the Settings tab."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1211,19 +1288,24 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "Ket Zapier Client ID ni",
|
||||
"CLIENT_SECRET": "Ket Zapier Client Secret ni",
|
||||
"NEXT_STEP_INFO": "I cokke ki authorization, ibi nyuto i Zapier me tiyo ki integration",
|
||||
"CONNECT_TO_ZAPIER": "Kub ki Zapier",
|
||||
"CONNECT_INFO": "Dii gin ame piny me kube akaunt mamegi me Zapier. Ibi nyuto i Zapier me ye kwede integration.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Peko oo pe gonyo ter pa Zapier",
|
||||
"SAVE_SETTINGS": "Peko oo pe gam ter pa Zapier",
|
||||
"INVALID_FORM": "Tim ber iyie pot buk ma mito ni ber",
|
||||
"MISSING_CREDENTIALS": "Peko pe tye lok ma loyo client",
|
||||
"NO_ORGANIZATION": "Tim ber i yer lawoti mukwongo",
|
||||
"NO_ACCESS_TOKEN": "Peke pe tye token ma loyo",
|
||||
"LOAD_WEBHOOKS": "Peko oo pe gonyo webhooks",
|
||||
"DELETE_WEBHOOK": "Peko oo pe junk webhook",
|
||||
"LOAD_ACTIONS": "Peko oo pe gonyo tic",
|
||||
"LOAD_TRIGGERS": "Peko oo pe gonyo lok ma gonyo",
|
||||
"LOAD_ZAPS": "Peko oo pe gonyo zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Peko oo pe gonyo template pa zap",
|
||||
"CREATE_ZAP": "Peko oo pe create zap",
|
||||
"ZAP_TITLE_REQUIRED": "Tim ber i miyo title me zap manyen",
|
||||
"NO_INTEGRATION_ID": "Pe ki miyo ID me integration pa Zapier",
|
||||
"START_AUTHORIZATION": "Ket cwoki pa authorization ni",
|
||||
"INVALID_CALLBACK": "Peko tye callback ma tye maber",
|
||||
"AUTHORIZATION": "Peko tye authorization ma tye maber",
|
||||
@@ -1233,6 +1315,7 @@
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Ter pa Zapier ogam ki ber",
|
||||
"DELETE_WEBHOOK": "Webhook ojunk ki ber",
|
||||
"CREATE_ZAP": "Ocreate zap ki ber",
|
||||
"AUTHORIZATION": "Authorization puched ki ber"
|
||||
},
|
||||
"TAB": {
|
||||
@@ -1275,6 +1358,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "Pe tye webhook ma kigoyo"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "Pe tye zaps",
|
||||
"CREATE": "Lok Zap",
|
||||
"SUBMIT": "Lok",
|
||||
"ADD_STEP": "Med step",
|
||||
"OPEN_EDITOR": "Yab i Zapier",
|
||||
"ENABLED": "Cako",
|
||||
"DISABLED": "Juko",
|
||||
"TITLE_PLACEHOLDER": "Ket title me Zap manyen",
|
||||
"FIELDS": {
|
||||
"TITLE": "Title",
|
||||
"STEPS": "Steps",
|
||||
"STEP": "Step",
|
||||
"STEP_TITLE": "Step title",
|
||||
"ACTION": "Tic",
|
||||
"AUTHENTICATION": "Authentication",
|
||||
"INPUTS": "Inputs (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ACTIVEPIECES_PAGE": {
|
||||
@@ -1503,7 +1606,17 @@
|
||||
"FILTER_INTEGRATIONS": "crwdns8270:0crwdne8270:0",
|
||||
"SEARCH_INTEGRATIONS": "crwdns8272:0crwdne8272:0",
|
||||
"PAID": "crwdns11205:0crwdne11205:0",
|
||||
"INTEGRATION": "crwdns11207:0crwdne11207:0"
|
||||
"INTEGRATION": "crwdns11207:0crwdne11207:0",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Processing Make.com authorization...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Make.com authorization failed",
|
||||
"INVALID_CALLBACK": "Invalid callback parameters"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Make.com authorization completed successfully"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "crwdns3190:0crwdne3190:0",
|
||||
|
||||
@@ -1293,16 +1293,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "سيناريوهات",
|
||||
"EXECUTIONS": "تنفيذات",
|
||||
"HISTORY": "السجل",
|
||||
"SETTINGS": "الإعدادات",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "يرجى إدخال جميع الحقول المطلوبة",
|
||||
"START_AUTHORIZATION": "يرجى بدء عملية التفويض"
|
||||
"WEBHOOKS": "الويب هوكس",
|
||||
"WEBHOOKS_REFRESH": "تحديث الويب هوكس",
|
||||
"WEBHOOK_ENABLE": "تفعيل الويب هوك",
|
||||
"WEBHOOK_DISABLE": "تعطيل الويب هوك",
|
||||
"WEBHOOK_PING": "اختبار الويب هوك",
|
||||
"SCENARIO_ACTIVE": "نشط",
|
||||
"SCENARIO_INACTIVE": "غير نشط",
|
||||
"SCENARIO_LAST_EDITED": "آخر تعديل: {{value}}",
|
||||
"SCENARIO_DISABLE": "تعطيل السيناريو",
|
||||
"SCENARIO_ENABLE": "تفعيل السيناريو",
|
||||
"SCENARIO_RUN_ONCE": "تشغيل السيناريو مرة واحدة",
|
||||
"WEBHOOK_ENABLED": "مفعّل",
|
||||
"WEBHOOK_DISABLED": "معطّل",
|
||||
"WEBHOOK_TYPE": "النوع",
|
||||
"WEBHOOK_QUEUE": "قائمة الانتظار",
|
||||
"TEMPLATE_PUBLIC": "عام",
|
||||
"TEMPLATE_PUBLISHED": "منشور",
|
||||
"REFRESH_CONNECTIONS": "تحديث الاتصالات",
|
||||
"CONFIGURATION_TITLE": "إعدادات Make.com",
|
||||
"ZONE_LABEL": "المنطقة",
|
||||
"ZONE_HINT": "اختر المنطقة التي تطابق حساب Make.com الخاص بك (مثال: us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "منظمة Make.com",
|
||||
"LOADING_ORGANIZATIONS": "جاري تحميل المنظمات...",
|
||||
"NO_ORGANIZATIONS": "لم يتم العثور على منظمات",
|
||||
"TEAM_LABEL": "فريق Make.com",
|
||||
"LOADING_TEAMS": "جاري تحميل الفرق...",
|
||||
"NO_TEAMS": "لم يتم العثور على فرق",
|
||||
"CONNECTED_TO": "متصل بـ {{org}} / {{team}} على {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "إعدادات الويب هوك",
|
||||
"CONNECTIONS": "اتصالات",
|
||||
"TEMPLATES": "قوالب",
|
||||
"SETTINGS": "إعدادات",
|
||||
"CONNECT_TO_MAKE_COM": "الاتصال بـ Make.com",
|
||||
"CONNECT_INFO": "انقر على الزر أدناه لربط حساب Make.com الخاص بك. سيتم إعادة توجيهك إلى Make.com لتفويض التكامل.",
|
||||
"NO_SCENARIOS": "لم يتم العثور على سيناريوهات. أنشئ سيناريوهات في لوحة تحكم Make.com.",
|
||||
"NO_HOOKS": "لم يتم العثور على webhooks. أنشئ webhooks في لوحة تحكم Make.com.",
|
||||
"NO_CONNECTIONS": "لم يتم العثور على اتصالات. أنشئ اتصالات في لوحة تحكم Make.com.",
|
||||
"NO_TEMPLATES": "لم يتم العثور على قوالب.",
|
||||
"TEST_CONNECTION": "اختبار الاتصال",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "الحساب: {{value}}",
|
||||
"TYPE": "النوع: {{value}}",
|
||||
"PACKAGE": "الحزمة: {{value}}",
|
||||
"EXPIRES": "ينتهي: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "معرف العميل",
|
||||
"CLIENT_SECRET": "الرمز السري للعميل",
|
||||
"NEXT_STEP_INFO": "بعد التفويض، سيتم توجيهك إلى Make.com لإكمال إعداد التكامل"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "يرجى ملء جميع الحقول المطلوبة",
|
||||
"START_AUTHORIZATION": "فشل في بدء عملية التفويض",
|
||||
"NO_ORGANIZATION": "يرجى اختيار منظمة أولاً",
|
||||
"LOAD_SETTINGS": "فشل في تحميل إعدادات Make.com",
|
||||
"SAVE_SETTINGS": "فشل في حفظ إعدادات Make.com",
|
||||
"LOAD_SETUP_STATUS": "فشل في تحميل حالة الإعداد",
|
||||
"LOAD_SCENARIOS": "فشل في تحميل السيناريوهات",
|
||||
"TOGGLE_SCENARIO": "فشل في تبديل السيناريو",
|
||||
"RUN_SCENARIO": "فشل في تشغيل السيناريو",
|
||||
"LOAD_CONNECTIONS": "فشل في تحميل الاتصالات",
|
||||
"TEST_CONNECTION": "فشل في اختبار الاتصال",
|
||||
"LOAD_WEBHOOKS": "فشل في تحميل الويب هوكس",
|
||||
"TOGGLE_WEBHOOK": "فشل في تبديل الويب هوك",
|
||||
"PING_WEBHOOK": "فشل في اختبار الويب هوك",
|
||||
"SET_ZONE": "فشل في تعيين المنطقة",
|
||||
"LOAD_ORGANIZATIONS": "فشل في تحميل المنظمات",
|
||||
"SELECT_ORGANIZATION": "فشل في اختيار المنظمة",
|
||||
"LOAD_TEAMS": "فشل في تحميل الفرق",
|
||||
"SELECT_TEAM": "فشل في اختيار الفريق",
|
||||
"LOAD_TEMPLATES": "فشل في تحميل القوالب",
|
||||
"REQUEST_TIMEOUT": "انتهت مهلة الطلب. يرجى المحاولة مرة أخرى."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "تم حفظ إعدادات Make.com بنجاح",
|
||||
"SCENARIO_ACTIVATED": "تم تفعيل السيناريو",
|
||||
"SCENARIO_DEACTIVATED": "تم تعطيل السيناريو",
|
||||
"SCENARIO_EXECUTED": "تم تنفيذ السيناريو",
|
||||
"CONNECTION_VALID": "الاتصال \"{{name}}\" صالح",
|
||||
"WEBHOOK_TOGGLED_ON": "تم تمكين الويب هوك",
|
||||
"WEBHOOK_TOGGLED_OFF": "تم تعطيل الويب هوك",
|
||||
"WEBHOOK_PINGED": "تم اختبار الويب هوك \"{{name}}\" بنجاح",
|
||||
"ZONE_SET": "تم تعيين المنطقة إلى {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "تم اختيار المنظمة \"{{name}}\"",
|
||||
"TEAM_SELECTED": "تم اختيار الفريق \"{{name}}\""
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "يرجى تفويض Make.com أولاً. انتقل إلى علامة التبويب الإعدادات لربط حسابك.",
|
||||
"ZONE": "يرجى اختيار منطقة Make.com في علامة التبويب الإعدادات.",
|
||||
"ORGANIZATION": "يرجى اختيار منظمة Make.com في علامة التبويب الإعدادات.",
|
||||
"TEAM": "يرجى اختيار فريق Make.com في علامة التبويب الإعدادات."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1315,26 +1392,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "معرف العميل",
|
||||
"CLIENT_SECRET": "الرمز السري للعميل",
|
||||
"NEXT_STEP_INFO": "بعد التفويض، سيتم توجيهك إلى Zapier لإكمال إعداد التكامل",
|
||||
"CONNECT_TO_ZAPIER": "الاتصال بـ Zapier",
|
||||
"CONNECT_INFO": "انقر على الزر أدناه لربط حسابك في Zapier. سيتم توجيهك إلى Zapier للتفويض بالتكامل.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "فشل تحميل إعدادات Zapier",
|
||||
"SAVE_SETTINGS": "فشل حفظ إعدادات Zapier",
|
||||
"INVALID_FORM": "يرجى إدخال جميع الحقول المطلوبة",
|
||||
"MISSING_CREDENTIALS": "بيانات الاعتماد للعميل مفقودة",
|
||||
"NO_ORGANIZATION": "يرجى اختيار منظمة أولاً",
|
||||
"NO_ACCESS_TOKEN": "لا يوجد رمز وصول متاح",
|
||||
"LOAD_WEBHOOKS": "فشل تحميل ويب هوكس",
|
||||
"DELETE_WEBHOOK": "فشل حذف ويب هوك",
|
||||
"LOAD_ACTIONS": "فشل تحميل الإجراءات",
|
||||
"LOAD_TRIGGERS": "فشل تحميل المحفزات",
|
||||
"LOAD_ZAPS": "فشل تحميل zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "فشل تحميل قوالب zap",
|
||||
"CREATE_ZAP": "فشل إنشاء zap",
|
||||
"ZAP_TITLE_REQUIRED": "يرجى تقديم عنوان لـ zap الجديد",
|
||||
"NO_INTEGRATION_ID": "لم يتم تقديم معرف تكامل Zapier",
|
||||
"START_AUTHORIZATION": "يرجى بدء عملية التفويض",
|
||||
"INVALID_TOKEN": "تم توفير رمز غير صالح",
|
||||
"TOKEN_NOT_FOUND": "لم يتم العثور على الرمز"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "تم حفظ إعدادات Zapier بنجاح",
|
||||
"DELETE_WEBHOOK": "تم حذف ويب هوك بنجاح"
|
||||
"DELETE_WEBHOOK": "تم حذف ويب هوك بنجاح",
|
||||
"CREATE_ZAP": "تم إنشاء Zap بنجاح"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "المحفزات",
|
||||
@@ -1376,6 +1459,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "ويب هوكس",
|
||||
"NO_WEBHOOKS": "لا يوجد ويب هوكس مضبوط"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "زابز",
|
||||
"NO_ZAPS": "لا توجد زابز متاحة",
|
||||
"CREATE": "إنشاء زاب",
|
||||
"SUBMIT": "إنشاء",
|
||||
"ADD_STEP": "إضافة خطوة",
|
||||
"OPEN_EDITOR": "فتح في زابير",
|
||||
"ENABLED": "مفعّل",
|
||||
"DISABLED": "معطّل",
|
||||
"TITLE_PLACEHOLDER": "أدخل عنواناً للزاب الجديد",
|
||||
"FIELDS": {
|
||||
"TITLE": "العنوان",
|
||||
"STEPS": "الخطوات",
|
||||
"STEP": "خطوة",
|
||||
"STEP_TITLE": "عنوان الخطوة",
|
||||
"ACTION": "الإجراء",
|
||||
"AUTHENTICATION": "المصادقة",
|
||||
"INPUTS": "المدخلات (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1608,7 +1711,17 @@
|
||||
"FILTER_INTEGRATIONS": "ترشيح التكاملات",
|
||||
"SEARCH_INTEGRATIONS": "البحث في التكاملات",
|
||||
"PAID": "مدفوع",
|
||||
"INTEGRATION": "التكامل"
|
||||
"INTEGRATION": "التكامل",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "جاري معالجة تفويض Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "فشل تفويض Make.com",
|
||||
"INVALID_CALLBACK": "معلمات رد الاتصال غير صالحة"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "تم إكمال تفويض Make.com بنجاح"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "المحاسبة",
|
||||
|
||||
@@ -1326,16 +1326,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Сценарии",
|
||||
"EXECUTIONS": "Изпълнения",
|
||||
"HISTORY": "История",
|
||||
"WEBHOOKS": "Уебхукове",
|
||||
"WEBHOOKS_REFRESH": "Опресняване на уебхукове",
|
||||
"WEBHOOK_ENABLE": "Активиране на уебхук",
|
||||
"WEBHOOK_DISABLE": "Деактивиране на уебхук",
|
||||
"WEBHOOK_PING": "Тест на уебхук",
|
||||
"SCENARIO_ACTIVE": "Активен",
|
||||
"SCENARIO_INACTIVE": "Неактивен",
|
||||
"SCENARIO_LAST_EDITED": "Последна редакция: {{value}}",
|
||||
"SCENARIO_DISABLE": "Деактивиране на сценарий",
|
||||
"SCENARIO_ENABLE": "Активиране на сценарий",
|
||||
"SCENARIO_RUN_ONCE": "Еднократно изпълнение на сценарий",
|
||||
"WEBHOOK_ENABLED": "Активиран",
|
||||
"WEBHOOK_DISABLED": "Деактивиран",
|
||||
"WEBHOOK_TYPE": "Тип",
|
||||
"WEBHOOK_QUEUE": "Опашка",
|
||||
"TEMPLATE_PUBLIC": "Публичен",
|
||||
"TEMPLATE_PUBLISHED": "Публикуван",
|
||||
"REFRESH_CONNECTIONS": "Опресняване на връзките",
|
||||
"CONFIGURATION_TITLE": "Make.com Конфигурация",
|
||||
"ZONE_LABEL": "Зона",
|
||||
"ZONE_HINT": "Изберете зоната, която съвпада с вашия Make.com акаунт (напр. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Make.com Организация",
|
||||
"LOADING_ORGANIZATIONS": "Зареждане на организации...",
|
||||
"NO_ORGANIZATIONS": "Няма намерени организации",
|
||||
"TEAM_LABEL": "Make.com Екип",
|
||||
"LOADING_TEAMS": "Зареждане на екипи...",
|
||||
"NO_TEAMS": "Няма намерени екипи",
|
||||
"CONNECTED_TO": "Свързан с {{org}} / {{team}} в {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Настройки на уебхук",
|
||||
"CONNECTIONS": "Връзки",
|
||||
"TEMPLATES": "Шаблони",
|
||||
"SETTINGS": "Настройки",
|
||||
"CONNECT_TO_MAKE_COM": "Свързване с Make.com",
|
||||
"CONNECT_INFO": "Кликнете бутона по-долу, за да свържете акаунта си в Make.com. Ще бъдете пренасочени към Make.com за оторизация на интеграцията.",
|
||||
"NO_SCENARIOS": "Няма намерени сценарии. Създайте сценарии в таблото на Make.com.",
|
||||
"NO_HOOKS": "Няма намерени уебхукове. Създайте уебхукове в таблото на Make.com.",
|
||||
"NO_CONNECTIONS": "Няма намерени връзки. Създайте връзки в таблото на Make.com.",
|
||||
"NO_TEMPLATES": "Няма намерени шаблони.",
|
||||
"TEST_CONNECTION": "Тест на връзката",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Акаунт: {{value}}",
|
||||
"TYPE": "Тип: {{value}}",
|
||||
"PACKAGE": "Пакет: {{value}}",
|
||||
"EXPIRES": "Изтича: {{value}}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Моля, попълнете всички задължителни полета",
|
||||
"START_AUTHORIZATION": "Моля, започнете процеса на оторизация"
|
||||
"START_AUTHORIZATION": "Неуспешно стартиране на процеса на оторизация",
|
||||
"NO_ORGANIZATION": "Моля, първо изберете организация",
|
||||
"LOAD_SETTINGS": "Неуспешно зареждане на настройките на Make.com",
|
||||
"SAVE_SETTINGS": "Неуспешно запазване на настройките на Make.com",
|
||||
"LOAD_SETUP_STATUS": "Неуспешно зареждане на състоянието на настройката",
|
||||
"LOAD_SCENARIOS": "Неуспешно зареждане на сценарии",
|
||||
"TOGGLE_SCENARIO": "Неуспешно превключване на сценарий",
|
||||
"RUN_SCENARIO": "Неуспешно изпълнение на сценарий",
|
||||
"LOAD_CONNECTIONS": "Неуспешно зареждане на връзки",
|
||||
"TEST_CONNECTION": "Неуспешно тестване на връзка",
|
||||
"LOAD_WEBHOOKS": "Неуспешно зареждане на уебхукове",
|
||||
"TOGGLE_WEBHOOK": "Неуспешно превключване на уебхук",
|
||||
"PING_WEBHOOK": "Неуспешно тестване на уебхук",
|
||||
"SET_ZONE": "Неуспешно задаване на зона",
|
||||
"LOAD_ORGANIZATIONS": "Неуспешно зареждане на организации",
|
||||
"SELECT_ORGANIZATION": "Неуспешно избиране на организация",
|
||||
"LOAD_TEAMS": "Неуспешно зареждане на екипи",
|
||||
"SELECT_TEAM": "Неуспешно избиране на екип",
|
||||
"LOAD_TEMPLATES": "Неуспешно зареждане на шаблони",
|
||||
"REQUEST_TIMEOUT": "Времето за заявка изтече. Моля, опитайте отново."
|
||||
},
|
||||
"CLIENT_ID": "ID на клиент",
|
||||
"CLIENT_SECRET": "Тайна на клиент",
|
||||
"NEXT_STEP_INFO": "След оторизацията ще бъдете пренасочени към Make.com, за да завършите настройката на интеграцията"
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Настройките на Make.com са запазени успешно",
|
||||
"SCENARIO_ACTIVATED": "Сценарият е активиран",
|
||||
"SCENARIO_DEACTIVATED": "Сценарият е деактивиран",
|
||||
"SCENARIO_EXECUTED": "Сценарият е изпълнен",
|
||||
"CONNECTION_VALID": "Връзката \"{{name}}\" е валидна",
|
||||
"WEBHOOK_TOGGLED_ON": "Уебхукът е активиран",
|
||||
"WEBHOOK_TOGGLED_OFF": "Уебхукът е деактивиран",
|
||||
"WEBHOOK_PINGED": "Уебхук \"{{name}}\" е тестван успешно",
|
||||
"ZONE_SET": "Зоната е зададена на {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Организация \"{{name}}\" е избрана",
|
||||
"TEAM_SELECTED": "Екип \"{{name}}\" е избран"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Моля, първо оторизирайте Make.com. Отидете в раздел Настройки, за да свържете акаунта си.",
|
||||
"ZONE": "Моля, изберете вашата зона Make.com в раздел Настройки.",
|
||||
"ORGANIZATION": "Моля, изберете вашата организация Make.com в раздел Настройки.",
|
||||
"TEAM": "Моля, изберете вашия екип Make.com в раздел Настройки."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1348,26 +1425,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "ID на клиент",
|
||||
"CLIENT_SECRET": "Тайна на клиент",
|
||||
"NEXT_STEP_INFO": "След оторизацията ще бъдете пренасочени към Zapier, за да завършите настройката на интеграцията",
|
||||
"CONNECT_TO_ZAPIER": "Свързване със Zapier",
|
||||
"CONNECT_INFO": "Натиснете бутона по-долу, за да свържете вашия акаунт в Zapier. Ще бъдете пренасочени към Zapier за оторизация на интеграцията.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Неуспешно зареждане на настройките на Zapier",
|
||||
"SAVE_SETTINGS": "Неуспешно запазване на настройките на Zapier",
|
||||
"INVALID_FORM": "Моля, попълнете всички задължителни полета",
|
||||
"MISSING_CREDENTIALS": "Липсват идентификационни данни на клиента",
|
||||
"NO_ORGANIZATION": "Моля, първо изберете организация",
|
||||
"NO_ACCESS_TOKEN": "Няма достъпен токен за достъп",
|
||||
"LOAD_WEBHOOKS": "Неуспешно зареждане на уебхукове",
|
||||
"DELETE_WEBHOOK": "Неуспешно изтриване на уебхук",
|
||||
"LOAD_ACTIONS": "Неуспешно зареждане на действия",
|
||||
"LOAD_TRIGGERS": "Неуспешно зареждане на тригери",
|
||||
"LOAD_ZAPS": "Неуспешно зареждане на zap-ове",
|
||||
"LOAD_ZAP_TEMPLATES": "Неуспешно зареждане на шаблони за zap",
|
||||
"CREATE_ZAP": "Неуспешно създаване на zap",
|
||||
"ZAP_TITLE_REQUIRED": "Моля, предоставете заглавие за новия zap",
|
||||
"NO_INTEGRATION_ID": "Не е предоставен идентификатор за интеграция на Zapier",
|
||||
"START_AUTHORIZATION": "Моля, започнете процеса на оторизация",
|
||||
"INVALID_TOKEN": "Предоставен е невалиден токен",
|
||||
"TOKEN_NOT_FOUND": "Токенът не е намерен"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Настройките на Zapier са запазени успешно",
|
||||
"DELETE_WEBHOOK": "Уебхукът е изтрит успешно"
|
||||
"DELETE_WEBHOOK": "Уебхукът е изтрит успешно",
|
||||
"CREATE_ZAP": "Zap-ът е създаден успешно"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Тригери",
|
||||
@@ -1409,6 +1492,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Уебхукове",
|
||||
"NO_WEBHOOKS": "Няма конфигурирани уебхукове"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Запс",
|
||||
"NO_ZAPS": "Няма налични запс",
|
||||
"CREATE": "Създай Запс",
|
||||
"SUBMIT": "Създай",
|
||||
"ADD_STEP": "Добави стъпка",
|
||||
"OPEN_EDITOR": "Отвори в Zapier",
|
||||
"ENABLED": "Активирано",
|
||||
"DISABLED": "Деактивирано",
|
||||
"TITLE_PLACEHOLDER": "Въведете заглавие за новия Запс",
|
||||
"FIELDS": {
|
||||
"TITLE": "Заглавие",
|
||||
"STEPS": "Стъпки",
|
||||
"STEP": "Стъпка",
|
||||
"STEP_TITLE": "Заглавие на стъпката",
|
||||
"ACTION": "Действие",
|
||||
"AUTHENTICATION": "Удостоверяване",
|
||||
"INPUTS": "Входни данни (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1687,7 +1790,17 @@
|
||||
"INTEGRATION_ADDED": "Интеграцията с '{{provider}}' беше добавена към '{{organization}}'"
|
||||
},
|
||||
"ENABLED": "Активирано",
|
||||
"DISABLED": "Деактивирано"
|
||||
"DISABLED": "Деактивирано",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Обработка на оторизацията на Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Оторизацията на Make.com е неуспешна",
|
||||
"INVALID_CALLBACK": "Невалидни параметри за обратно извикване"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Оторизацията на Make.com е завършена успешно"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Счетоводство",
|
||||
|
||||
@@ -1294,16 +1294,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Szenarien",
|
||||
"EXECUTIONS": "Ausführungen",
|
||||
"HISTORY": "Verlauf",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Webhooks aktualisieren",
|
||||
"WEBHOOK_ENABLE": "Webhook aktivieren",
|
||||
"WEBHOOK_DISABLE": "Webhook deaktivieren",
|
||||
"WEBHOOK_PING": "Webhook testen",
|
||||
"SCENARIO_ACTIVE": "Aktiv",
|
||||
"SCENARIO_INACTIVE": "Inaktiv",
|
||||
"SCENARIO_LAST_EDITED": "Zuletzt bearbeitet: {{value}}",
|
||||
"SCENARIO_DISABLE": "Szenario deaktivieren",
|
||||
"SCENARIO_ENABLE": "Szenario aktivieren",
|
||||
"SCENARIO_RUN_ONCE": "Szenario einmal ausführen",
|
||||
"WEBHOOK_ENABLED": "Aktiviert",
|
||||
"WEBHOOK_DISABLED": "Deaktiviert",
|
||||
"WEBHOOK_TYPE": "Typ",
|
||||
"WEBHOOK_QUEUE": "Warteschlange",
|
||||
"TEMPLATE_PUBLIC": "Öffentlich",
|
||||
"TEMPLATE_PUBLISHED": "Veröffentlicht",
|
||||
"REFRESH_CONNECTIONS": "Verbindungen aktualisieren",
|
||||
"CONFIGURATION_TITLE": "Make.com-Konfiguration",
|
||||
"ZONE_LABEL": "Zone",
|
||||
"ZONE_HINT": "Wählen Sie die Zone, die mit Ihrem Make.com-Konto übereinstimmt (z.B. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Make.com-Organisation",
|
||||
"LOADING_ORGANIZATIONS": "Organisationen werden geladen...",
|
||||
"NO_ORGANIZATIONS": "Keine Organisationen gefunden",
|
||||
"TEAM_LABEL": "Make.com-Team",
|
||||
"LOADING_TEAMS": "Teams werden geladen...",
|
||||
"NO_TEAMS": "Keine Teams gefunden",
|
||||
"CONNECTED_TO": "Verbunden mit {{org}} / {{team}} auf {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Webhook-Einstellungen",
|
||||
"CONNECTIONS": "Verbindungen",
|
||||
"TEMPLATES": "Vorlagen",
|
||||
"SETTINGS": "Einstellungen",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Bitte füllen Sie alle erforderlichen Felder aus",
|
||||
"START_AUTHORIZATION": "Bitte starten Sie den Autorisierungsprozess"
|
||||
"CONNECT_TO_MAKE_COM": "Mit Make.com verbinden",
|
||||
"CONNECT_INFO": "Klicken Sie auf die Schaltfläche unten, um Ihr Make.com-Konto zu verbinden. Sie werden zu Make.com weitergeleitet, um die Integration zu autorisieren.",
|
||||
"NO_SCENARIOS": "Keine Szenarien gefunden. Erstellen Sie Szenarien in Ihrem Make.com-Dashboard.",
|
||||
"NO_HOOKS": "Keine Webhooks gefunden. Erstellen Sie Webhooks in Ihrem Make.com-Dashboard.",
|
||||
"NO_CONNECTIONS": "Keine Verbindungen gefunden. Erstellen Sie Verbindungen in Ihrem Make.com-Dashboard.",
|
||||
"NO_TEMPLATES": "Keine Vorlagen gefunden.",
|
||||
"TEST_CONNECTION": "Verbindung testen",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Konto: {{value}}",
|
||||
"TYPE": "Typ: {{value}}",
|
||||
"PACKAGE": "Paket: {{value}}",
|
||||
"EXPIRES": "Läuft ab: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "Client-ID",
|
||||
"CLIENT_SECRET": "Client-Geheimnis",
|
||||
"NEXT_STEP_INFO": "Nach der Autorisierung werden Sie zu Make.com weitergeleitet, um die Integration abzuschließen"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Bitte füllen Sie alle Pflichtfelder aus",
|
||||
"START_AUTHORIZATION": "Fehler beim Starten des Autorisierungsprozesses",
|
||||
"NO_ORGANIZATION": "Bitte wählen Sie zuerst eine Organisation aus",
|
||||
"LOAD_SETTINGS": "Fehler beim Laden der Make.com-Einstellungen",
|
||||
"SAVE_SETTINGS": "Fehler beim Speichern der Make.com-Einstellungen",
|
||||
"LOAD_SETUP_STATUS": "Fehler beim Laden des Einrichtungsstatus",
|
||||
"LOAD_SCENARIOS": "Fehler beim Laden der Szenarien",
|
||||
"TOGGLE_SCENARIO": "Fehler beim Umschalten des Szenarios",
|
||||
"RUN_SCENARIO": "Fehler beim Ausführen des Szenarios",
|
||||
"LOAD_CONNECTIONS": "Fehler beim Laden der Verbindungen",
|
||||
"TEST_CONNECTION": "Fehler beim Testen der Verbindung",
|
||||
"LOAD_WEBHOOKS": "Fehler beim Laden der Webhooks",
|
||||
"TOGGLE_WEBHOOK": "Fehler beim Umschalten des Webhooks",
|
||||
"PING_WEBHOOK": "Fehler beim Testen des Webhooks",
|
||||
"SET_ZONE": "Fehler beim Festlegen der Zone",
|
||||
"LOAD_ORGANIZATIONS": "Fehler beim Laden der Organisationen",
|
||||
"SELECT_ORGANIZATION": "Fehler beim Auswählen der Organisation",
|
||||
"LOAD_TEAMS": "Fehler beim Laden der Teams",
|
||||
"SELECT_TEAM": "Fehler beim Auswählen des Teams",
|
||||
"LOAD_TEMPLATES": "Fehler beim Laden der Vorlagen",
|
||||
"REQUEST_TIMEOUT": "Zeitüberschreitung der Anfrage. Bitte versuchen Sie es erneut."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Make.com-Einstellungen erfolgreich gespeichert",
|
||||
"SCENARIO_ACTIVATED": "Szenario aktiviert",
|
||||
"SCENARIO_DEACTIVATED": "Szenario deaktiviert",
|
||||
"SCENARIO_EXECUTED": "Szenario ausgeführt",
|
||||
"CONNECTION_VALID": "Verbindung \"{{name}}\" ist gültig",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook aktiviert",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook deaktiviert",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" erfolgreich getestet",
|
||||
"ZONE_SET": "Zone auf {{zone}} gesetzt",
|
||||
"ORGANIZATION_SELECTED": "Organisation \"{{name}}\" ausgewählt",
|
||||
"TEAM_SELECTED": "Team \"{{name}}\" ausgewählt"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Bitte autorisieren Sie zuerst Make.com. Gehen Sie zur Registerkarte Einstellungen, um Ihr Konto zu verbinden.",
|
||||
"ZONE": "Bitte wählen Sie Ihre Make.com-Zone in der Registerkarte Einstellungen.",
|
||||
"ORGANIZATION": "Bitte wählen Sie Ihre Make.com-Organisation in der Registerkarte Einstellungen.",
|
||||
"TEAM": "Bitte wählen Sie Ihr Make.com-Team in der Registerkarte Einstellungen."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1316,26 +1393,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "Client ID",
|
||||
"CLIENT_SECRET": "Client Secret",
|
||||
"NEXT_STEP_INFO": "Nach der Autorisierung werden Sie zu Zapier weitergeleitet, um die Integration abzuschließen",
|
||||
"CONNECT_TO_ZAPIER": "Mit Zapier verbinden",
|
||||
"CONNECT_INFO": "Klicken Sie auf die Schaltfläche unten, um Ihr Zapier-Konto zu verbinden. Sie werden zu Zapier weitergeleitet, um die Integration zu autorisieren.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Fehler beim Laden der Zapier-Einstellungen",
|
||||
"SAVE_SETTINGS": "Fehler beim Speichern der Zapier-Einstellungen",
|
||||
"INVALID_FORM": "Bitte füllen Sie alle erforderlichen Felder aus",
|
||||
"MISSING_CREDENTIALS": "Fehlende Client-Anmeldedaten",
|
||||
"NO_ORGANIZATION": "Bitte wählen Sie zuerst eine Organisation aus",
|
||||
"NO_ACCESS_TOKEN": "Kein Zugriffstoken verfügbar",
|
||||
"LOAD_WEBHOOKS": "Fehler beim Laden der Webhooks",
|
||||
"DELETE_WEBHOOK": "Fehler beim Löschen des Webhooks",
|
||||
"LOAD_ACTIONS": "Fehler beim Laden der Aktionen",
|
||||
"LOAD_TRIGGERS": "Fehler beim Laden der Trigger",
|
||||
"LOAD_ZAPS": "Fehler beim Laden der Zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Fehler beim Laden der Zap-Vorlagen",
|
||||
"CREATE_ZAP": "Fehler beim Erstellen des Zaps",
|
||||
"ZAP_TITLE_REQUIRED": "Bitte geben Sie einen Titel für den neuen Zap an",
|
||||
"NO_INTEGRATION_ID": "Es wurde keine Zapier-Integrations-ID angegeben",
|
||||
"START_AUTHORIZATION": "Bitte starten Sie den Autorisierungsprozess",
|
||||
"INVALID_TOKEN": "Ungültiges Token bereitgestellt",
|
||||
"TOKEN_NOT_FOUND": "Token nicht gefunden"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Zapier-Einstellungen erfolgreich gespeichert",
|
||||
"DELETE_WEBHOOK": "Webhook erfolgreich gelöscht"
|
||||
"DELETE_WEBHOOK": "Webhook erfolgreich gelöscht",
|
||||
"CREATE_ZAP": "Zap erfolgreich erstellt"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Trigger",
|
||||
@@ -1377,6 +1460,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "Keine Webhooks konfiguriert"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "Keine Zaps verfügbar",
|
||||
"CREATE": "Zap erstellen",
|
||||
"SUBMIT": "Erstellen",
|
||||
"ADD_STEP": "Schritt hinzufügen",
|
||||
"OPEN_EDITOR": "In Zapier öffnen",
|
||||
"ENABLED": "Aktiviert",
|
||||
"DISABLED": "Deaktiviert",
|
||||
"TITLE_PLACEHOLDER": "Geben Sie einen Titel für den neuen Zap ein",
|
||||
"FIELDS": {
|
||||
"TITLE": "Titel",
|
||||
"STEPS": "Schritte",
|
||||
"STEP": "Schritt",
|
||||
"STEP_TITLE": "Schritttitel",
|
||||
"ACTION": "Aktion",
|
||||
"AUTHENTICATION": "Authentifizierung",
|
||||
"INPUTS": "Eingaben (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1609,7 +1712,17 @@
|
||||
"FILTER_INTEGRATIONS": "Filterintegrationen",
|
||||
"SEARCH_INTEGRATIONS": "Suchen Sie nach Integrationen.",
|
||||
"PAID": "bezahlt",
|
||||
"INTEGRATION": "Integration"
|
||||
"INTEGRATION": "Integration",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Make.com-Autorisierung wird verarbeitet...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Make.com-Autorisierung fehlgeschlagen",
|
||||
"INVALID_CALLBACK": "Ungültige Callback-Parameter"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Make.com-Autorisierung erfolgreich abgeschlossen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Buchhaltung",
|
||||
|
||||
@@ -1343,16 +1343,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Scenarios",
|
||||
"EXECUTIONS": "Executions",
|
||||
"HISTORY": "History",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Refresh webhooks",
|
||||
"WEBHOOK_ENABLE": "Enable webhook",
|
||||
"WEBHOOK_DISABLE": "Disable webhook",
|
||||
"WEBHOOK_PING": "Ping webhook",
|
||||
"SCENARIO_ACTIVE": "Active",
|
||||
"SCENARIO_INACTIVE": "Inactive",
|
||||
"SCENARIO_LAST_EDITED": "Last edited: {{value}}",
|
||||
"SCENARIO_DISABLE": "Disable scenario",
|
||||
"SCENARIO_ENABLE": "Enable scenario",
|
||||
"SCENARIO_RUN_ONCE": "Run scenario once",
|
||||
"WEBHOOK_ENABLED": "Enabled",
|
||||
"WEBHOOK_DISABLED": "Disabled",
|
||||
"WEBHOOK_TYPE": "Type",
|
||||
"WEBHOOK_QUEUE": "Queue",
|
||||
"TEMPLATE_PUBLIC": "Public",
|
||||
"TEMPLATE_PUBLISHED": "Published",
|
||||
"REFRESH_CONNECTIONS": "Refresh connections",
|
||||
"CONFIGURATION_TITLE": "Make.com Configuration",
|
||||
"ZONE_LABEL": "Zone",
|
||||
"ZONE_HINT": "Select the zone that matches your Make.com account (e.g., us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Make.com Organization",
|
||||
"LOADING_ORGANIZATIONS": "Loading organizations...",
|
||||
"NO_ORGANIZATIONS": "No organizations found",
|
||||
"TEAM_LABEL": "Make.com Team",
|
||||
"LOADING_TEAMS": "Loading teams...",
|
||||
"NO_TEAMS": "No teams found",
|
||||
"CONNECTED_TO": "Connected to {{org}} / {{team}} on {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Webhook Settings",
|
||||
"CONNECTIONS": "Connections",
|
||||
"TEMPLATES": "Templates",
|
||||
"SETTINGS": "Settings",
|
||||
"CONNECT_TO_MAKE_COM": "Connect to Make.com",
|
||||
"CONNECT_INFO": "Click the button below to connect your Make.com account. You will be redirected to Make.com to authorize the integration.",
|
||||
"NO_SCENARIOS": "No scenarios found. Create scenarios in your Make.com dashboard.",
|
||||
"NO_HOOKS": "No webhooks found. Create webhooks in your Make.com dashboard.",
|
||||
"NO_CONNECTIONS": "No connections found. Create connections in your Make.com dashboard.",
|
||||
"NO_TEMPLATES": "No templates found.",
|
||||
"TEST_CONNECTION": "Test connection",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Account: {{value}}",
|
||||
"TYPE": "Type: {{value}}",
|
||||
"PACKAGE": "Package: {{value}}",
|
||||
"EXPIRES": "Expires: {{value}}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Please fill in all required fields",
|
||||
"START_AUTHORIZATION": "Please start the authorization process"
|
||||
"START_AUTHORIZATION": "Failed to start the authorization process",
|
||||
"NO_ORGANIZATION": "Please select an organization first",
|
||||
"LOAD_SETTINGS": "Failed to load Make.com settings",
|
||||
"SAVE_SETTINGS": "Failed to save Make.com settings",
|
||||
"LOAD_SETUP_STATUS": "Failed to load setup status",
|
||||
"LOAD_SCENARIOS": "Failed to load scenarios",
|
||||
"TOGGLE_SCENARIO": "Failed to toggle scenario",
|
||||
"RUN_SCENARIO": "Failed to run scenario",
|
||||
"LOAD_CONNECTIONS": "Failed to load connections",
|
||||
"TEST_CONNECTION": "Failed to test connection",
|
||||
"LOAD_WEBHOOKS": "Failed to load webhooks",
|
||||
"TOGGLE_WEBHOOK": "Failed to toggle webhook",
|
||||
"PING_WEBHOOK": "Failed to ping webhook",
|
||||
"SET_ZONE": "Failed to set zone",
|
||||
"LOAD_ORGANIZATIONS": "Failed to load organizations",
|
||||
"SELECT_ORGANIZATION": "Failed to select organization",
|
||||
"LOAD_TEAMS": "Failed to load teams",
|
||||
"SELECT_TEAM": "Failed to select team",
|
||||
"LOAD_TEMPLATES": "Failed to load templates",
|
||||
"REQUEST_TIMEOUT": "Request timed out. Please try again."
|
||||
},
|
||||
"CLIENT_ID": "Client ID",
|
||||
"CLIENT_SECRET": "Client Secret",
|
||||
"NEXT_STEP_INFO": "After authorization, you will be redirected to Make.com to complete the integration setup"
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Make.com settings saved successfully",
|
||||
"SCENARIO_ACTIVATED": "Scenario activated",
|
||||
"SCENARIO_DEACTIVATED": "Scenario deactivated",
|
||||
"SCENARIO_EXECUTED": "Scenario executed",
|
||||
"CONNECTION_VALID": "Connection \"{{name}}\" is valid",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook enabled",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook disabled",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" pinged successfully",
|
||||
"ZONE_SET": "Zone set to {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organization \"{{name}}\" selected",
|
||||
"TEAM_SELECTED": "Team \"{{name}}\" selected"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Please authorize with Make.com first. Go to the Settings tab to connect your account.",
|
||||
"ZONE": "Please select your Make.com zone in the Settings tab.",
|
||||
"ORGANIZATION": "Please select your Make.com organization in the Settings tab.",
|
||||
"TEAM": "Please select your Make.com team in the Settings tab."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1365,31 +1442,40 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "Client ID",
|
||||
"CLIENT_SECRET": "Client Secret",
|
||||
"NEXT_STEP_INFO": "After authorization, you will be redirected to Zapier to complete the integration setup",
|
||||
"CONNECT_TO_ZAPIER": "Connect to Zapier",
|
||||
"CONNECT_INFO": "Click the button below to connect your Zapier account. You will be redirected to Zapier to authorize the integration.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Failed to load Zapier settings",
|
||||
"SAVE_SETTINGS": "Failed to save Zapier settings",
|
||||
"INVALID_FORM": "Please fill in all required fields",
|
||||
"MISSING_CREDENTIALS": "Missing client credentials",
|
||||
"NO_ORGANIZATION": "Please select an organization first",
|
||||
"NO_ACCESS_TOKEN": "No access token available",
|
||||
"LOAD_WEBHOOKS": "Failed to load webhooks",
|
||||
"DELETE_WEBHOOK": "Failed to delete webhook",
|
||||
"LOAD_ACTIONS": "Failed to load actions",
|
||||
"LOAD_TRIGGERS": "Failed to load triggers",
|
||||
"LOAD_ZAPS": "Failed to load zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Failed to load zap templates",
|
||||
"CREATE_ZAP": "Failed to create zap",
|
||||
"ZAP_TITLE_REQUIRED": "Please provide a title for the new zap",
|
||||
"INVALID_STEP_INPUTS": "Step {{step}} has invalid JSON in the inputs field",
|
||||
"NO_INTEGRATION_ID": "No Zapier integration ID was provided",
|
||||
"START_AUTHORIZATION": "Please start the authorization process",
|
||||
"INVALID_TOKEN": "Invalid token provided",
|
||||
"TOKEN_NOT_FOUND": "Token not found"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Zapier settings saved successfully",
|
||||
"DELETE_WEBHOOK": "Webhook deleted successfully"
|
||||
"DELETE_WEBHOOK": "Webhook deleted successfully",
|
||||
"CREATE_ZAP": "Zap created successfully"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Triggers",
|
||||
"ACTIONS": "Actions",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"ZAPS": "Zaps",
|
||||
"ZAP_TEMPLATES": "Zap Templates",
|
||||
"SETTINGS": "Settings"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
@@ -1426,6 +1512,31 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "No webhooks configured"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "No zaps available",
|
||||
"CREATE": "Create Zap",
|
||||
"SUBMIT": "Create",
|
||||
"ADD_STEP": "Add step",
|
||||
"OPEN_EDITOR": "Open in Zapier",
|
||||
"ENABLED": "Enabled",
|
||||
"DISABLED": "Disabled",
|
||||
"TITLE_PLACEHOLDER": "Enter a title for the new Zap",
|
||||
"FIELDS": {
|
||||
"TITLE": "Title",
|
||||
"STEPS": "Steps",
|
||||
"STEP": "Step",
|
||||
"STEP_TITLE": "Step title",
|
||||
"ACTION": "Action",
|
||||
"AUTHENTICATION": "Authentication",
|
||||
"INPUTS": "Inputs (JSON)"
|
||||
}
|
||||
},
|
||||
"ZAP_TEMPLATES": {
|
||||
"TITLE": "Zap Templates",
|
||||
"NO_TEMPLATES": "No zap templates available",
|
||||
"USE": "Use Template"
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1709,7 +1820,17 @@
|
||||
"INTEGRATION_ADDED": "Integration '{{provider}}' has been added to '{{organization}}'"
|
||||
},
|
||||
"ENABLED": "Enabled",
|
||||
"DISABLED": "Disabled"
|
||||
"DISABLED": "Disabled",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Processing Make.com authorization...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Make.com authorization failed",
|
||||
"INVALID_CALLBACK": "Invalid callback parameters"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Make.com authorization completed successfully"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Accounting",
|
||||
|
||||
@@ -1296,16 +1296,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Escenarios",
|
||||
"EXECUTIONS": "Ejecuciones",
|
||||
"HISTORY": "Historial",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Actualizar webhooks",
|
||||
"WEBHOOK_ENABLE": "Activar webhook",
|
||||
"WEBHOOK_DISABLE": "Desactivar webhook",
|
||||
"WEBHOOK_PING": "Probar webhook",
|
||||
"SCENARIO_ACTIVE": "Activo",
|
||||
"SCENARIO_INACTIVE": "Inactivo",
|
||||
"SCENARIO_LAST_EDITED": "Última edición: {{value}}",
|
||||
"SCENARIO_DISABLE": "Desactivar escenario",
|
||||
"SCENARIO_ENABLE": "Activar escenario",
|
||||
"SCENARIO_RUN_ONCE": "Ejecutar escenario una vez",
|
||||
"WEBHOOK_ENABLED": "Activado",
|
||||
"WEBHOOK_DISABLED": "Desactivado",
|
||||
"WEBHOOK_TYPE": "Tipo",
|
||||
"WEBHOOK_QUEUE": "Cola",
|
||||
"TEMPLATE_PUBLIC": "Público",
|
||||
"TEMPLATE_PUBLISHED": "Publicado",
|
||||
"REFRESH_CONNECTIONS": "Actualizar conexiones",
|
||||
"CONFIGURATION_TITLE": "Configuración de Make.com",
|
||||
"ZONE_LABEL": "Zona",
|
||||
"ZONE_HINT": "Seleccione la zona que coincide con su cuenta de Make.com (ej. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Organización Make.com",
|
||||
"LOADING_ORGANIZATIONS": "Cargando organizaciones...",
|
||||
"NO_ORGANIZATIONS": "No se encontraron organizaciones",
|
||||
"TEAM_LABEL": "Equipo Make.com",
|
||||
"LOADING_TEAMS": "Cargando equipos...",
|
||||
"NO_TEAMS": "No se encontraron equipos",
|
||||
"CONNECTED_TO": "Conectado a {{org}} / {{team}} en {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Configuración de Webhook",
|
||||
"CONNECTIONS": "Conexiones",
|
||||
"TEMPLATES": "Plantillas",
|
||||
"SETTINGS": "Configuración",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Por favor, complete todos los campos requeridos",
|
||||
"START_AUTHORIZATION": "Por favor, inicie el proceso de autorización"
|
||||
"CONNECT_TO_MAKE_COM": "Conectar a Make.com",
|
||||
"CONNECT_INFO": "Haga clic en el botón de abajo para conectar su cuenta de Make.com. Será redirigido a Make.com para autorizar la integración.",
|
||||
"NO_SCENARIOS": "No se encontraron escenarios. Cree escenarios en su panel de Make.com.",
|
||||
"NO_HOOKS": "No se encontraron webhooks. Cree webhooks en su panel de Make.com.",
|
||||
"NO_CONNECTIONS": "No se encontraron conexiones. Cree conexiones en su panel de Make.com.",
|
||||
"NO_TEMPLATES": "No se encontraron plantillas.",
|
||||
"TEST_CONNECTION": "Probar conexión",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Cuenta: {{value}}",
|
||||
"TYPE": "Tipo: {{value}}",
|
||||
"PACKAGE": "Paquete: {{value}}",
|
||||
"EXPIRES": "Caduca: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "ID de Cliente",
|
||||
"CLIENT_SECRET": "Clave Secreta del Cliente",
|
||||
"NEXT_STEP_INFO": "Después de la autorización, será redirigido a Make.com para completar la configuración de la integración"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Por favor complete todos los campos obligatorios",
|
||||
"START_AUTHORIZATION": "Error al iniciar el proceso de autorización",
|
||||
"NO_ORGANIZATION": "Por favor seleccione una organización primero",
|
||||
"LOAD_SETTINGS": "Error al cargar la configuración de Make.com",
|
||||
"SAVE_SETTINGS": "Error al guardar la configuración de Make.com",
|
||||
"LOAD_SETUP_STATUS": "Error al cargar el estado de configuración",
|
||||
"LOAD_SCENARIOS": "Error al cargar escenarios",
|
||||
"TOGGLE_SCENARIO": "Error al alternar escenario",
|
||||
"RUN_SCENARIO": "Error al ejecutar escenario",
|
||||
"LOAD_CONNECTIONS": "Error al cargar conexiones",
|
||||
"TEST_CONNECTION": "Error al probar la conexión",
|
||||
"LOAD_WEBHOOKS": "Error al cargar webhooks",
|
||||
"TOGGLE_WEBHOOK": "Error al alternar webhook",
|
||||
"PING_WEBHOOK": "Error al probar webhook",
|
||||
"SET_ZONE": "Error al establecer la zona",
|
||||
"LOAD_ORGANIZATIONS": "Error al cargar organizaciones",
|
||||
"SELECT_ORGANIZATION": "Error al seleccionar organización",
|
||||
"LOAD_TEAMS": "Error al cargar equipos",
|
||||
"SELECT_TEAM": "Error al seleccionar equipo",
|
||||
"LOAD_TEMPLATES": "Error al cargar plantillas",
|
||||
"REQUEST_TIMEOUT": "La solicitud ha expirado. Por favor, inténtelo de nuevo."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Configuración de Make.com guardada con éxito",
|
||||
"SCENARIO_ACTIVATED": "Escenario activado",
|
||||
"SCENARIO_DEACTIVATED": "Escenario desactivado",
|
||||
"SCENARIO_EXECUTED": "Escenario ejecutado",
|
||||
"CONNECTION_VALID": "La conexión \"{{name}}\" es válida",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook activado",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook desactivado",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" probado con éxito",
|
||||
"ZONE_SET": "Zona establecida en {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organización \"{{name}}\" seleccionada",
|
||||
"TEAM_SELECTED": "Equipo \"{{name}}\" seleccionado"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Por favor autorice Make.com primero. Vaya a la pestaña Configuración para conectar su cuenta.",
|
||||
"ZONE": "Por favor seleccione su zona de Make.com en la pestaña Configuración.",
|
||||
"ORGANIZATION": "Por favor seleccione su organización de Make.com en la pestaña Configuración.",
|
||||
"TEAM": "Por favor seleccione su equipo de Make.com en la pestaña Configuración."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1318,26 +1395,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "ID de Cliente",
|
||||
"CLIENT_SECRET": "Clave Secreta del Cliente",
|
||||
"NEXT_STEP_INFO": "Después de la autorización, será redirigido a Zapier para completar la configuración de la integración",
|
||||
"CONNECT_TO_ZAPIER": "Conectar con Zapier",
|
||||
"CONNECT_INFO": "Haga clic en el botón de abajo para conectar su cuenta de Zapier. Será redirigido a Zapier para autorizar la integración.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Error al cargar la configuración de Zapier",
|
||||
"SAVE_SETTINGS": "Error al guardar la configuración de Zapier",
|
||||
"INVALID_FORM": "Por favor, complete todos los campos requeridos",
|
||||
"MISSING_CREDENTIALS": "Faltan credenciales del cliente",
|
||||
"NO_ORGANIZATION": "Por favor, seleccione una organización primero",
|
||||
"NO_ACCESS_TOKEN": "No hay token de acceso disponible",
|
||||
"LOAD_WEBHOOKS": "Error al cargar webhooks",
|
||||
"DELETE_WEBHOOK": "Error al eliminar webhook",
|
||||
"LOAD_ACTIONS": "Error al cargar acciones",
|
||||
"LOAD_TRIGGERS": "Error al cargar disparadores",
|
||||
"LOAD_ZAPS": "Error al cargar zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Error al cargar plantillas de zap",
|
||||
"CREATE_ZAP": "Error al crear zap",
|
||||
"ZAP_TITLE_REQUIRED": "Por favor, proporcione un título para el nuevo zap",
|
||||
"NO_INTEGRATION_ID": "No se proporcionó ningún ID de integración de Zapier",
|
||||
"START_AUTHORIZATION": "Por favor, inicie el proceso de autorización",
|
||||
"INVALID_TOKEN": "Token inválido proporcionado",
|
||||
"TOKEN_NOT_FOUND": "Token no encontrado"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Configuración de Zapier guardada correctamente",
|
||||
"DELETE_WEBHOOK": "Webhook eliminado correctamente"
|
||||
"DELETE_WEBHOOK": "Webhook eliminado correctamente",
|
||||
"CREATE_ZAP": "Zap creado exitosamente"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Disparadores",
|
||||
@@ -1379,6 +1462,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "No hay webhooks configurados"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "No hay zaps disponibles",
|
||||
"CREATE": "Crear Zap",
|
||||
"SUBMIT": "Crear",
|
||||
"ADD_STEP": "Agregar paso",
|
||||
"OPEN_EDITOR": "Abrir en Zapier",
|
||||
"ENABLED": "Habilitado",
|
||||
"DISABLED": "Deshabilitado",
|
||||
"TITLE_PLACEHOLDER": "Ingrese un título para el nuevo Zap",
|
||||
"FIELDS": {
|
||||
"TITLE": "Título",
|
||||
"STEPS": "Pasos",
|
||||
"STEP": "Paso",
|
||||
"STEP_TITLE": "Título del paso",
|
||||
"ACTION": "Acción",
|
||||
"AUTHENTICATION": "Autenticación",
|
||||
"INPUTS": "Entradas (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1611,7 +1714,17 @@
|
||||
"FILTER_INTEGRATIONS": "Integraciones de filtro",
|
||||
"SEARCH_INTEGRATIONS": "Búsquedas de integraciones",
|
||||
"PAID": "Pagado",
|
||||
"INTEGRATION": "Integración"
|
||||
"INTEGRATION": "Integración",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Procesando autorización de Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "La autorización de Make.com falló",
|
||||
"INVALID_CALLBACK": "Parámetros de devolución de llamada no válidos"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Autorización de Make.com completada con éxito"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Contabilidad",
|
||||
|
||||
@@ -1295,16 +1295,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Scénarios",
|
||||
"EXECUTIONS": "Exécutions",
|
||||
"HISTORY": "Historique",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Actualiser les webhooks",
|
||||
"WEBHOOK_ENABLE": "Activer le webhook",
|
||||
"WEBHOOK_DISABLE": "Désactiver le webhook",
|
||||
"WEBHOOK_PING": "Tester le webhook",
|
||||
"SCENARIO_ACTIVE": "Actif",
|
||||
"SCENARIO_INACTIVE": "Inactif",
|
||||
"SCENARIO_LAST_EDITED": "Dernière modification : {{value}}",
|
||||
"SCENARIO_DISABLE": "Désactiver le scénario",
|
||||
"SCENARIO_ENABLE": "Activer le scénario",
|
||||
"SCENARIO_RUN_ONCE": "Exécuter le scénario une fois",
|
||||
"WEBHOOK_ENABLED": "Activé",
|
||||
"WEBHOOK_DISABLED": "Désactivé",
|
||||
"WEBHOOK_TYPE": "Type",
|
||||
"WEBHOOK_QUEUE": "File d'attente",
|
||||
"TEMPLATE_PUBLIC": "Public",
|
||||
"TEMPLATE_PUBLISHED": "Publié",
|
||||
"REFRESH_CONNECTIONS": "Actualiser les connexions",
|
||||
"CONFIGURATION_TITLE": "Configuration Make.com",
|
||||
"ZONE_LABEL": "Zone",
|
||||
"ZONE_HINT": "Sélectionnez la zone qui correspond à votre compte Make.com (ex. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Organisation Make.com",
|
||||
"LOADING_ORGANIZATIONS": "Chargement des organisations...",
|
||||
"NO_ORGANIZATIONS": "Aucune organisation trouvée",
|
||||
"TEAM_LABEL": "Équipe Make.com",
|
||||
"LOADING_TEAMS": "Chargement des équipes...",
|
||||
"NO_TEAMS": "Aucune équipe trouvée",
|
||||
"CONNECTED_TO": "Connecté à {{org}} / {{team}} sur {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Paramètres Webhook",
|
||||
"CONNECTIONS": "Connexions",
|
||||
"TEMPLATES": "Modèles",
|
||||
"SETTINGS": "Paramètres",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Veuillez remplir tous les champs requis",
|
||||
"START_AUTHORIZATION": "Veuillez démarrer le processus d'autorisation"
|
||||
"CONNECT_TO_MAKE_COM": "Se connecter à Make.com",
|
||||
"CONNECT_INFO": "Cliquez sur le bouton ci-dessous pour connecter votre compte Make.com. Vous serez redirigé vers Make.com pour autoriser l'intégration.",
|
||||
"NO_SCENARIOS": "Aucun scénario trouvé. Créez des scénarios dans votre tableau de bord Make.com.",
|
||||
"NO_HOOKS": "Aucun webhook trouvé. Créez des webhooks dans votre tableau de bord Make.com.",
|
||||
"NO_CONNECTIONS": "Aucune connexion trouvée. Créez des connexions dans votre tableau de bord Make.com.",
|
||||
"NO_TEMPLATES": "Aucun modèle trouvé.",
|
||||
"TEST_CONNECTION": "Tester la connexion",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Compte : {{value}}",
|
||||
"TYPE": "Type : {{value}}",
|
||||
"PACKAGE": "Package : {{value}}",
|
||||
"EXPIRES": "Expire : {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "Identifiant Client",
|
||||
"CLIENT_SECRET": "Clé Secrète Client",
|
||||
"NEXT_STEP_INFO": "Après l'autorisation, vous serez redirigé vers Make.com pour finaliser la configuration de l'intégration"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Veuillez remplir tous les champs obligatoires",
|
||||
"START_AUTHORIZATION": "Échec du démarrage du processus d'autorisation",
|
||||
"NO_ORGANIZATION": "Veuillez d'abord sélectionner une organisation",
|
||||
"LOAD_SETTINGS": "Échec du chargement des paramètres Make.com",
|
||||
"SAVE_SETTINGS": "Échec de la sauvegarde des paramètres Make.com",
|
||||
"LOAD_SETUP_STATUS": "Échec du chargement du statut de configuration",
|
||||
"LOAD_SCENARIOS": "Échec du chargement des scénarios",
|
||||
"TOGGLE_SCENARIO": "Échec de l'activation/désactivation du scénario",
|
||||
"RUN_SCENARIO": "Échec de l'exécution du scénario",
|
||||
"LOAD_CONNECTIONS": "Échec du chargement des connexions",
|
||||
"TEST_CONNECTION": "Échec du test de connexion",
|
||||
"LOAD_WEBHOOKS": "Échec du chargement des webhooks",
|
||||
"TOGGLE_WEBHOOK": "Échec de l'activation/désactivation du webhook",
|
||||
"PING_WEBHOOK": "Échec du test du webhook",
|
||||
"SET_ZONE": "Échec de la définition de la zone",
|
||||
"LOAD_ORGANIZATIONS": "Échec du chargement des organisations",
|
||||
"SELECT_ORGANIZATION": "Échec de la sélection de l'organisation",
|
||||
"LOAD_TEAMS": "Échec du chargement des équipes",
|
||||
"SELECT_TEAM": "Échec de la sélection de l'équipe",
|
||||
"LOAD_TEMPLATES": "Échec du chargement des modèles",
|
||||
"REQUEST_TIMEOUT": "La demande a expiré. Veuillez réessayer."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Paramètres Make.com enregistrés avec succès",
|
||||
"SCENARIO_ACTIVATED": "Scénario activé",
|
||||
"SCENARIO_DEACTIVATED": "Scénario désactivé",
|
||||
"SCENARIO_EXECUTED": "Scénario exécuté",
|
||||
"CONNECTION_VALID": "La connexion \"{{name}}\" est valide",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook activé",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook désactivé",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" testé avec succès",
|
||||
"ZONE_SET": "Zone définie sur {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organisation \"{{name}}\" sélectionnée",
|
||||
"TEAM_SELECTED": "Équipe \"{{name}}\" sélectionnée"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Veuillez d'abord autoriser Make.com. Allez dans l'onglet Paramètres pour connecter votre compte.",
|
||||
"ZONE": "Veuillez sélectionner votre zone Make.com dans l'onglet Paramètres.",
|
||||
"ORGANIZATION": "Veuillez sélectionner votre organisation Make.com dans l'onglet Paramètres.",
|
||||
"TEAM": "Veuillez sélectionner votre équipe Make.com dans l'onglet Paramètres."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1317,26 +1394,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "Identifiant Client",
|
||||
"CLIENT_SECRET": "Clé Secrète Client",
|
||||
"NEXT_STEP_INFO": "Après l'autorisation, vous serez redirigé vers Zapier pour finaliser la configuration de l'intégration",
|
||||
"CONNECT_TO_ZAPIER": "Se connecter à Zapier",
|
||||
"CONNECT_INFO": "Cliquez sur le bouton ci-dessous pour connecter votre compte Zapier. Vous serez redirigé vers Zapier pour autoriser l'intégration.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Échec du chargement des paramètres Zapier",
|
||||
"SAVE_SETTINGS": "Échec de l'enregistrement des paramètres Zapier",
|
||||
"INVALID_FORM": "Veuillez remplir tous les champs requis",
|
||||
"MISSING_CREDENTIALS": "Identifiants client manquants",
|
||||
"MISSING_CREDENTIALS": "L'intégration Zapier n'est pas configurée sur le serveur",
|
||||
"NO_ORGANIZATION": "Veuillez d'abord sélectionner une organisation",
|
||||
"NO_ACCESS_TOKEN": "Aucun jeton d'accès disponible",
|
||||
"LOAD_WEBHOOKS": "Échec du chargement des webhooks",
|
||||
"DELETE_WEBHOOK": "Échec de la suppression du webhook",
|
||||
"LOAD_ACTIONS": "Échec du chargement des actions",
|
||||
"LOAD_TRIGGERS": "Échec du chargement des déclencheurs",
|
||||
"LOAD_ZAPS": "Échec du chargement des zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Échec du chargement des modèles de zap",
|
||||
"CREATE_ZAP": "Échec de la création du zap",
|
||||
"ZAP_TITLE_REQUIRED": "Veuillez fournir un titre pour le nouveau zap",
|
||||
"NO_INTEGRATION_ID": "Aucun ID d'intégration Zapier n'a été fourni",
|
||||
"START_AUTHORIZATION": "Veuillez démarrer le processus d'autorisation",
|
||||
"INVALID_TOKEN": "Jeton invalide fourni",
|
||||
"TOKEN_NOT_FOUND": "Jeton non trouvé"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Paramètres Zapier enregistrés avec succès",
|
||||
"DELETE_WEBHOOK": "Webhook supprimé avec succès"
|
||||
"DELETE_WEBHOOK": "Webhook supprimé avec succès",
|
||||
"CREATE_ZAP": "Zap créé avec succès"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Déclencheurs",
|
||||
@@ -1378,6 +1461,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "Aucun webhook configuré"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "Aucun zap disponible",
|
||||
"CREATE": "Créer un Zap",
|
||||
"SUBMIT": "Créer",
|
||||
"ADD_STEP": "Ajouter une étape",
|
||||
"OPEN_EDITOR": "Ouvrir dans Zapier",
|
||||
"ENABLED": "Activé",
|
||||
"DISABLED": "Désactivé",
|
||||
"TITLE_PLACEHOLDER": "Entrez un titre pour le nouveau Zap",
|
||||
"FIELDS": {
|
||||
"TITLE": "Titre",
|
||||
"STEPS": "Étapes",
|
||||
"STEP": "Étape",
|
||||
"STEP_TITLE": "Titre de l'étape",
|
||||
"ACTION": "Action",
|
||||
"AUTHENTICATION": "Authentification",
|
||||
"INPUTS": "Entrées (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1610,7 +1713,17 @@
|
||||
"FILTER_INTEGRATIONS": "Intégrations de filtres",
|
||||
"SEARCH_INTEGRATIONS": "Intégrations de recherche",
|
||||
"PAID": "Payé",
|
||||
"INTEGRATION": "Intégration"
|
||||
"INTEGRATION": "Intégration",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Traitement de l'autorisation Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "L'autorisation Make.com a échoué",
|
||||
"INVALID_CALLBACK": "Paramètres de rappel invalides"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Autorisation Make.com terminée avec succès"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Comptabilité",
|
||||
|
||||
@@ -1297,17 +1297,94 @@
|
||||
"HOURLY": "Hourly"
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "סצנות",
|
||||
"EXECUTIONS": "מימושים",
|
||||
"HISTORY": "היסטוריה",
|
||||
"SCENARIOS": "תרחישים",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "רענן webhooks",
|
||||
"WEBHOOK_ENABLE": "הפעל webhook",
|
||||
"WEBHOOK_DISABLE": "השבת webhook",
|
||||
"WEBHOOK_PING": "בדוק webhook",
|
||||
"SCENARIO_ACTIVE": "פעיל",
|
||||
"SCENARIO_INACTIVE": "לא פעיל",
|
||||
"SCENARIO_LAST_EDITED": "עריכה אחרונה: {{value}}",
|
||||
"SCENARIO_DISABLE": "השבת תרחיש",
|
||||
"SCENARIO_ENABLE": "הפעל תרחיש",
|
||||
"SCENARIO_RUN_ONCE": "הפעל תרחיש פעם אחת",
|
||||
"WEBHOOK_ENABLED": "מופעל",
|
||||
"WEBHOOK_DISABLED": "מושבת",
|
||||
"WEBHOOK_TYPE": "סוג",
|
||||
"WEBHOOK_QUEUE": "תור",
|
||||
"TEMPLATE_PUBLIC": "ציבורי",
|
||||
"TEMPLATE_PUBLISHED": "פורסם",
|
||||
"REFRESH_CONNECTIONS": "רענן חיבורים",
|
||||
"CONFIGURATION_TITLE": "תצורת Make.com",
|
||||
"ZONE_LABEL": "אזור",
|
||||
"ZONE_HINT": "בחר את האזור התואם לחשבון Make.com שלך (למשל us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "ארגון Make.com",
|
||||
"LOADING_ORGANIZATIONS": "טוען ארגונים...",
|
||||
"NO_ORGANIZATIONS": "לא נמצאו ארגונים",
|
||||
"TEAM_LABEL": "צוות Make.com",
|
||||
"LOADING_TEAMS": "טוען צוותים...",
|
||||
"NO_TEAMS": "לא נמצאו צוותים",
|
||||
"CONNECTED_TO": "מחובר ל-{{org}} / {{team}} ב-{{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "הגדרות Webhook",
|
||||
"CONNECTIONS": "חיבורים",
|
||||
"TEMPLATES": "תבניות",
|
||||
"SETTINGS": "הגדרות",
|
||||
"CONNECT_TO_MAKE_COM": "התחבר ל-Make.com",
|
||||
"CONNECT_INFO": "לחץ על הכפתור למטה כדי לחבר את חשבון ה-Make.com שלך. תועבר ל-Make.com כדי לאשר את האינטגרציה.",
|
||||
"NO_SCENARIOS": "לא נמצאו תרחישים. צור תרחישים בלוח הבקרה של Make.com.",
|
||||
"NO_HOOKS": "לא נמצאו webhooks. צור webhooks בלוח הבקרה של Make.com.",
|
||||
"NO_CONNECTIONS": "לא נמצאו חיבורים. צור חיבורים בלוח הבקרה של Make.com.",
|
||||
"NO_TEMPLATES": "לא נמצאו תבניות.",
|
||||
"TEST_CONNECTION": "בדיקת חיבור",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "חשבון: {{value}}",
|
||||
"TYPE": "סוג: {{value}}",
|
||||
"PACKAGE": "חבילה: {{value}}",
|
||||
"EXPIRES": "פג תוקף: {{value}}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "אנא מלא את כל השדות הנדרשים",
|
||||
"START_AUTHORIZATION": "אנא התחיל את תהליך האישור"
|
||||
"START_AUTHORIZATION": "כשל בהתחלת תהליך ההרשאה",
|
||||
"NO_ORGANIZATION": "אנא בחר ארגון תחילה",
|
||||
"LOAD_SETTINGS": "כשל בטעינת הגדרות Make.com",
|
||||
"SAVE_SETTINGS": "כשל בשמירת הגדרות Make.com",
|
||||
"LOAD_SETUP_STATUS": "כשל בטעינת סטטוס הגדרה",
|
||||
"LOAD_SCENARIOS": "כשל בטעינת תרחישים",
|
||||
"TOGGLE_SCENARIO": "כשל בהחלפת מצב תרחיש",
|
||||
"RUN_SCENARIO": "כשל בהפעלת תרחיש",
|
||||
"LOAD_CONNECTIONS": "כשל בטעינת חיבורים",
|
||||
"TEST_CONNECTION": "כשל בבדיקת חיבור",
|
||||
"LOAD_WEBHOOKS": "כשל בטעינת webhooks",
|
||||
"TOGGLE_WEBHOOK": "כשל בהחלפת מצב webhook",
|
||||
"PING_WEBHOOK": "כשל בבדיקת webhook",
|
||||
"SET_ZONE": "כשל בהגדרת אזור",
|
||||
"LOAD_ORGANIZATIONS": "כשל בטעינת ארגונים",
|
||||
"SELECT_ORGANIZATION": "כשל בבחירת ארגון",
|
||||
"LOAD_TEAMS": "כשל בטעינת צוותים",
|
||||
"SELECT_TEAM": "כשל בבחירת צוות",
|
||||
"LOAD_TEMPLATES": "כשל בטעינת תבניות",
|
||||
"REQUEST_TIMEOUT": "פסק זמן לבקשה. אנא נסה שוב."
|
||||
},
|
||||
"CLIENT_ID": "מזהה לקוח",
|
||||
"CLIENT_SECRET": "סיסמת לקוח",
|
||||
"NEXT_STEP_INFO": "לאחר האישור, תועבר ל-Make.com להשלמת הגדרות האינטגרציה"
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "הגדרות Make.com נשמרו בהצלחה",
|
||||
"SCENARIO_ACTIVATED": "תרחיש הופעל",
|
||||
"SCENARIO_DEACTIVATED": "תרחיש הושבת",
|
||||
"SCENARIO_EXECUTED": "תרחיש בוצע",
|
||||
"CONNECTION_VALID": "החיבור \"{{name}}\" תקין",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook הופעל",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook הושבת",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" נבדק בהצלחה",
|
||||
"ZONE_SET": "האזור הוגדר ל-{{zone}}",
|
||||
"ORGANIZATION_SELECTED": "ארגון \"{{name}}\" נבחר",
|
||||
"TEAM_SELECTED": "צוות \"{{name}}\" נבחר"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "אנא אשר תחילה את Make.com. עבור ללשונית הגדרות כדי לחבר את החשבון שלך.",
|
||||
"ZONE": "אנא בחר את אזור ה-Make.com שלך בלשונית הגדרות.",
|
||||
"ORGANIZATION": "אנא בחר את ארגון ה-Make.com שלך בלשונית הגדרות.",
|
||||
"TEAM": "אנא בחר את צוות ה-Make.com שלך בלשונית הגדרות."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1320,26 +1397,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "מזהה לקוח",
|
||||
"CLIENT_SECRET": "סיסמת לקוח",
|
||||
"NEXT_STEP_INFO": "לאחר האישור, תועבר ל-Zapier להשלמת הגדרות האינטגרציה",
|
||||
"CONNECT_TO_ZAPIER": "התחבר ל-Zapier",
|
||||
"CONNECT_INFO": "לחץ על הכפתור למטה כדי לחבר את חשבון ה-Zapier שלך. תועבר ל-Zapier כדי לאשר את האינטגרציה.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "נכשל בטעינת הגדרות Zapier",
|
||||
"SAVE_SETTINGS": "נכשל בשמירת הגדרות Zapier",
|
||||
"INVALID_FORM": "אנא מלא את כל השדות הנדרשים",
|
||||
"MISSING_CREDENTIALS": "חסרים פרטי גישה של הלקוח",
|
||||
"MISSING_CREDENTIALS": "שילוב Zapier לא מוגדר בשרת",
|
||||
"NO_ORGANIZATION": "אנא בחר ארגון תחילה",
|
||||
"NO_ACCESS_TOKEN": "אין אסימון גישה זמין",
|
||||
"LOAD_WEBHOOKS": "נכשל בטעינת וובחוקים",
|
||||
"DELETE_WEBHOOK": "נכשל במחיקת וובחוק",
|
||||
"LOAD_ACTIONS": "נכשל בטעינת פעולות",
|
||||
"LOAD_TRIGGERS": "נכשל בטעינת טריגרים",
|
||||
"LOAD_ZAPS": "כשל בטעינת zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "כשל בטעינת תבניות zap",
|
||||
"CREATE_ZAP": "כשל ביצירת zap",
|
||||
"ZAP_TITLE_REQUIRED": "אנא ספק כותרת ל-zap החדש",
|
||||
"NO_INTEGRATION_ID": "לא סופק מזהה אינטגרציה של Zapier",
|
||||
"START_AUTHORIZATION": "אנא התחיל את תהליך האישור",
|
||||
"INVALID_TOKEN": "אסימון לא תקין סופק",
|
||||
"TOKEN_NOT_FOUND": "אסימון לא נמצא"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "הגדרות Zapier נשמרו בהצלחה",
|
||||
"DELETE_WEBHOOK": "וובחוק נמחק בהצלחה"
|
||||
"DELETE_WEBHOOK": "וובחוק נמחק בהצלחה",
|
||||
"CREATE_ZAP": "Zap נוצר בהצלחה"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "טריגרים",
|
||||
@@ -1381,6 +1464,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "וובחוקים",
|
||||
"NO_WEBHOOKS": "אין וובחוקים מוגדרים"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "זאפים",
|
||||
"NO_ZAPS": "אין זאפים זמינים",
|
||||
"CREATE": "צור זאפ",
|
||||
"SUBMIT": "צור",
|
||||
"ADD_STEP": "הוסף שלב",
|
||||
"OPEN_EDITOR": "פתח ב-Zapier",
|
||||
"ENABLED": "מופעל",
|
||||
"DISABLED": "מושבת",
|
||||
"TITLE_PLACEHOLDER": "הזן כותרת לזאפ החדש",
|
||||
"FIELDS": {
|
||||
"TITLE": "כותרת",
|
||||
"STEPS": "שלבים",
|
||||
"STEP": "שלב",
|
||||
"STEP_TITLE": "כותרת שלב",
|
||||
"ACTION": "פעולה",
|
||||
"AUTHENTICATION": "אימות",
|
||||
"INPUTS": "קלטים (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1652,8 +1755,18 @@
|
||||
"INTEGRATION_DISABLED": "'{{provider}}' integration successfully disabled",
|
||||
"INTEGRATION_ADDED": "Integration '{{provider}}' has been added to '{{organization}}'"
|
||||
},
|
||||
"ENABLED": "Enabled",
|
||||
"DISABLED": "Disabled"
|
||||
"ENABLED": "מופעל",
|
||||
"DISABLED": "מושבת",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "מעבד הרשאת Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "הרשאת Make.com נכשלה",
|
||||
"INVALID_CALLBACK": "פרמטרי callback לא חוקיים"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "הרשאת Make.com הושלמה בהצלחה"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Accounting",
|
||||
@@ -1798,8 +1911,8 @@
|
||||
"END_WORK": "סיום עבודה",
|
||||
"WORK_ENDED": "העבודה הסתיימה",
|
||||
"DELETED": "Deleted",
|
||||
"ENABLED": "Enabled",
|
||||
"DISABLED": "Disabled",
|
||||
"ENABLED": "מופעל",
|
||||
"DISABLED": "מושבת",
|
||||
"RECURRING_EXPENSE": "Employee Recurring Expense",
|
||||
"RECURRING_EXPENSE_EDIT": "Edit the current & all future expenses. There will be no change to any past expense.",
|
||||
"RECURRING_EXPENSE_ADD": "This will add an expense recurring every month.",
|
||||
|
||||
@@ -1294,16 +1294,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Scenari",
|
||||
"EXECUTIONS": "Esecuzioni",
|
||||
"HISTORY": "Cronologia",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Aggiorna webhook",
|
||||
"WEBHOOK_ENABLE": "Abilita webhook",
|
||||
"WEBHOOK_DISABLE": "Disabilita webhook",
|
||||
"WEBHOOK_PING": "Testa webhook",
|
||||
"SCENARIO_ACTIVE": "Attivo",
|
||||
"SCENARIO_INACTIVE": "Inattivo",
|
||||
"SCENARIO_LAST_EDITED": "Ultima modifica: {{value}}",
|
||||
"SCENARIO_DISABLE": "Disabilita scenario",
|
||||
"SCENARIO_ENABLE": "Abilita scenario",
|
||||
"SCENARIO_RUN_ONCE": "Esegui scenario una volta",
|
||||
"WEBHOOK_ENABLED": "Abilitato",
|
||||
"WEBHOOK_DISABLED": "Disabilitato",
|
||||
"WEBHOOK_TYPE": "Tipo",
|
||||
"WEBHOOK_QUEUE": "Coda",
|
||||
"TEMPLATE_PUBLIC": "Pubblico",
|
||||
"TEMPLATE_PUBLISHED": "Pubblicato",
|
||||
"REFRESH_CONNECTIONS": "Aggiorna connessioni",
|
||||
"CONFIGURATION_TITLE": "Configurazione Make.com",
|
||||
"ZONE_LABEL": "Zona",
|
||||
"ZONE_HINT": "Seleziona la zona che corrisponde al tuo account Make.com (es. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Organizzazione Make.com",
|
||||
"LOADING_ORGANIZATIONS": "Caricamento organizzazioni...",
|
||||
"NO_ORGANIZATIONS": "Nessuna organizzazione trovata",
|
||||
"TEAM_LABEL": "Team Make.com",
|
||||
"LOADING_TEAMS": "Caricamento team...",
|
||||
"NO_TEAMS": "Nessun team trovato",
|
||||
"CONNECTED_TO": "Connesso a {{org}} / {{team}} su {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Impostazioni Webhook",
|
||||
"CONNECTIONS": "Connessioni",
|
||||
"TEMPLATES": "Modelli",
|
||||
"SETTINGS": "Impostazioni",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Per favore, compila tutti i campi obbligatori",
|
||||
"START_AUTHORIZATION": "Per favore, inizia il processo di autorizzazione"
|
||||
"CONNECT_TO_MAKE_COM": "Connetti a Make.com",
|
||||
"CONNECT_INFO": "Clicca il pulsante qui sotto per collegare il tuo account Make.com. Verrai reindirizzato a Make.com per autorizzare l'integrazione.",
|
||||
"NO_SCENARIOS": "Nessuno scenario trovato. Crea scenari nella tua dashboard Make.com.",
|
||||
"NO_HOOKS": "Nessun webhook trovato. Crea webhook nella tua dashboard Make.com.",
|
||||
"NO_CONNECTIONS": "Nessuna connessione trovata. Crea connessioni nella tua dashboard Make.com.",
|
||||
"NO_TEMPLATES": "Nessun modello trovato.",
|
||||
"TEST_CONNECTION": "Testa connessione",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Account: {{value}}",
|
||||
"TYPE": "Tipo: {{value}}",
|
||||
"PACKAGE": "Pacchetto: {{value}}",
|
||||
"EXPIRES": "Scadenza: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "ID Cliente",
|
||||
"CLIENT_SECRET": "Segreto Cliente",
|
||||
"NEXT_STEP_INFO": "Dopo l'autorizzazione, sarai reindirizzato a Make.com per completare la configurazione dell'integrazione"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Per favore compila tutti i campi obbligatori",
|
||||
"START_AUTHORIZATION": "Impossibile avviare il processo di autorizzazione",
|
||||
"NO_ORGANIZATION": "Per favore seleziona prima un'organizzazione",
|
||||
"LOAD_SETTINGS": "Impossibile caricare le impostazioni di Make.com",
|
||||
"SAVE_SETTINGS": "Impossibile salvare le impostazioni di Make.com",
|
||||
"LOAD_SETUP_STATUS": "Impossibile caricare lo stato di configurazione",
|
||||
"LOAD_SCENARIOS": "Impossibile caricare gli scenari",
|
||||
"TOGGLE_SCENARIO": "Impossibile modificare lo stato dello scenario",
|
||||
"RUN_SCENARIO": "Impossibile eseguire lo scenario",
|
||||
"LOAD_CONNECTIONS": "Impossibile caricare le connessioni",
|
||||
"TEST_CONNECTION": "Impossibile testare la connessione",
|
||||
"LOAD_WEBHOOKS": "Impossibile caricare i webhook",
|
||||
"TOGGLE_WEBHOOK": "Impossibile modificare lo stato del webhook",
|
||||
"PING_WEBHOOK": "Impossibile testare il webhook",
|
||||
"SET_ZONE": "Impossibile impostare la zona",
|
||||
"LOAD_ORGANIZATIONS": "Impossibile caricare le organizzazioni",
|
||||
"SELECT_ORGANIZATION": "Impossibile selezionare l'organizzazione",
|
||||
"LOAD_TEAMS": "Impossibile caricare i team",
|
||||
"SELECT_TEAM": "Impossibile selezionare il team",
|
||||
"LOAD_TEMPLATES": "Impossibile caricare i modelli",
|
||||
"REQUEST_TIMEOUT": "Richiesta scaduta. Per favore riprova."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Impostazioni di Make.com salvate con successo",
|
||||
"SCENARIO_ACTIVATED": "Scenario attivato",
|
||||
"SCENARIO_DEACTIVATED": "Scenario disattivato",
|
||||
"SCENARIO_EXECUTED": "Scenario eseguito",
|
||||
"CONNECTION_VALID": "La connessione \"{{name}}\" è valida",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook abilitato",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook disabilitato",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" testato con successo",
|
||||
"ZONE_SET": "Zona impostata su {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organizzazione \"{{name}}\" selezionata",
|
||||
"TEAM_SELECTED": "Team \"{{name}}\" selezionato"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Per favore autorizza prima Make.com. Vai alla scheda Impostazioni per collegare il tuo account.",
|
||||
"ZONE": "Per favore seleziona la tua zona Make.com nella scheda Impostazioni.",
|
||||
"ORGANIZATION": "Per favore seleziona la tua organizzazione Make.com nella scheda Impostazioni.",
|
||||
"TEAM": "Per favore seleziona il tuo team Make.com nella scheda Impostazioni."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1316,26 +1393,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "ID Cliente",
|
||||
"CLIENT_SECRET": "Segreto Cliente",
|
||||
"NEXT_STEP_INFO": "Dopo l'autorizzazione, sarai reindirizzato a Zapier per completare la configurazione dell'integrazione",
|
||||
"CONNECT_TO_ZAPIER": "Connetti a Zapier",
|
||||
"CONNECT_INFO": "Clicca il pulsante qui sotto per collegare il tuo account Zapier. Sarai reindirizzato a Zapier per autorizzare l'integrazione.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Impossibile caricare le impostazioni di Zapier",
|
||||
"SAVE_SETTINGS": "Impossibile salvare le impostazioni di Zapier",
|
||||
"INVALID_FORM": "Per favore, compila tutti i campi obbligatori",
|
||||
"MISSING_CREDENTIALS": "Credenziali client mancanti",
|
||||
"NO_ORGANIZATION": "Per favore, seleziona prima un'organizzazione",
|
||||
"NO_ACCESS_TOKEN": "Nessun token di accesso disponibile",
|
||||
"LOAD_WEBHOOKS": "Impossibile caricare i webhook",
|
||||
"DELETE_WEBHOOK": "Impossibile eliminare il webhook",
|
||||
"LOAD_ACTIONS": "Impossibile caricare le azioni",
|
||||
"LOAD_TRIGGERS": "Impossibile caricare i trigger",
|
||||
"LOAD_ZAPS": "Impossibile caricare gli zap",
|
||||
"LOAD_ZAP_TEMPLATES": "Impossibile caricare i modelli di zap",
|
||||
"CREATE_ZAP": "Impossibile creare lo zap",
|
||||
"ZAP_TITLE_REQUIRED": "Si prega di fornire un titolo per il nuovo zap",
|
||||
"NO_INTEGRATION_ID": "Non è stato fornito alcun ID di integrazione Zapier",
|
||||
"START_AUTHORIZATION": "Per favore, inizia il processo di autorizzazione",
|
||||
"INVALID_TOKEN": "Token non valido fornito",
|
||||
"TOKEN_NOT_FOUND": "Token non trovato"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Impostazioni di Zapier salvate con successo",
|
||||
"DELETE_WEBHOOK": "Webhook eliminato con successo"
|
||||
"DELETE_WEBHOOK": "Webhook eliminato con successo",
|
||||
"CREATE_ZAP": "Zap creato con successo"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Trigger",
|
||||
@@ -1377,6 +1460,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhook",
|
||||
"NO_WEBHOOKS": "Nessun webhook configurato"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zap",
|
||||
"NO_ZAPS": "Nessun zap disponibile",
|
||||
"CREATE": "Crea Zap",
|
||||
"SUBMIT": "Crea",
|
||||
"ADD_STEP": "Aggiungi passaggio",
|
||||
"OPEN_EDITOR": "Apri in Zapier",
|
||||
"ENABLED": "Abilitato",
|
||||
"DISABLED": "Disabilitato",
|
||||
"TITLE_PLACEHOLDER": "Inserisci un titolo per il nuovo Zap",
|
||||
"FIELDS": {
|
||||
"TITLE": "Titolo",
|
||||
"STEPS": "Passaggi",
|
||||
"STEP": "Passaggio",
|
||||
"STEP_TITLE": "Titolo passaggio",
|
||||
"ACTION": "Azione",
|
||||
"AUTHENTICATION": "Autenticazione",
|
||||
"INPUTS": "Input (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1609,7 +1712,17 @@
|
||||
"FILTER_INTEGRATIONS": "Filtra le integrazioni",
|
||||
"SEARCH_INTEGRATIONS": "Integrazioni di ricerca",
|
||||
"PAID": "Pagato",
|
||||
"INTEGRATION": "Integrazione"
|
||||
"INTEGRATION": "Integrazione",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Elaborazione autorizzazione Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Autorizzazione Make.com fallita",
|
||||
"INVALID_CALLBACK": "Parametri di callback non validi"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Autorizzazione Make.com completata con successo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Contabilità",
|
||||
|
||||
@@ -1294,16 +1294,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Scenario's",
|
||||
"EXECUTIONS": "Uitvoeringen",
|
||||
"HISTORY": "Geschiedenis",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Webhooks vernieuwen",
|
||||
"WEBHOOK_ENABLE": "Webhook inschakelen",
|
||||
"WEBHOOK_DISABLE": "Webhook uitschakelen",
|
||||
"WEBHOOK_PING": "Webhook testen",
|
||||
"SCENARIO_ACTIVE": "Actief",
|
||||
"SCENARIO_INACTIVE": "Inactief",
|
||||
"SCENARIO_LAST_EDITED": "Laatst bewerkt: {{value}}",
|
||||
"SCENARIO_DISABLE": "Scenario uitschakelen",
|
||||
"SCENARIO_ENABLE": "Scenario inschakelen",
|
||||
"SCENARIO_RUN_ONCE": "Voer scenario eenmaal uit",
|
||||
"WEBHOOK_ENABLED": "Ingeschakeld",
|
||||
"WEBHOOK_DISABLED": "Uitgeschakeld",
|
||||
"WEBHOOK_TYPE": "Type",
|
||||
"WEBHOOK_QUEUE": "Wachtrij",
|
||||
"TEMPLATE_PUBLIC": "Openbaar",
|
||||
"TEMPLATE_PUBLISHED": "Gepubliceerd",
|
||||
"REFRESH_CONNECTIONS": "Verbindingen vernieuwen",
|
||||
"CONFIGURATION_TITLE": "Make.com-configuratie",
|
||||
"ZONE_LABEL": "Zone",
|
||||
"ZONE_HINT": "Selecteer de zone die overeenkomt met uw Make.com-account (bijv. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Make.com-organisatie",
|
||||
"LOADING_ORGANIZATIONS": "Organisaties laden...",
|
||||
"NO_ORGANIZATIONS": "Geen organisaties gevonden",
|
||||
"TEAM_LABEL": "Make.com-team",
|
||||
"LOADING_TEAMS": "Teams laden...",
|
||||
"NO_TEAMS": "Geen teams gevonden",
|
||||
"CONNECTED_TO": "Verbonden met {{org}} / {{team}} op {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Webhook-instellingen",
|
||||
"CONNECTIONS": "Verbindingen",
|
||||
"TEMPLATES": "Sjablonen",
|
||||
"SETTINGS": "Instellingen",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Vul alstublieft alle verplichte velden in",
|
||||
"START_AUTHORIZATION": "Start alstublieft de autorisatieproces"
|
||||
"CONNECT_TO_MAKE_COM": "Verbinden met Make.com",
|
||||
"CONNECT_INFO": "Klik op de knop hieronder om uw Make.com-account te verbinden. U wordt doorgestuurd naar Make.com om de integratie te autoriseren.",
|
||||
"NO_SCENARIOS": "Geen scenario's gevonden. Maak scenario's aan in uw Make.com-dashboard.",
|
||||
"NO_HOOKS": "Geen webhooks gevonden. Maak webhooks aan in uw Make.com-dashboard.",
|
||||
"NO_CONNECTIONS": "Geen verbindingen gevonden. Maak verbindingen aan in uw Make.com-dashboard.",
|
||||
"NO_TEMPLATES": "Geen sjablonen gevonden.",
|
||||
"TEST_CONNECTION": "Verbinding testen",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Account: {{value}}",
|
||||
"TYPE": "Type: {{value}}",
|
||||
"PACKAGE": "Pakket: {{value}}",
|
||||
"EXPIRES": "Verloopt: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "Client ID",
|
||||
"CLIENT_SECRET": "Client Geheim",
|
||||
"NEXT_STEP_INFO": "Na autorisatie word je doorgestuurd naar Make.com om de integratie-instellingen te voltooien"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Vul alle verplichte velden in",
|
||||
"START_AUTHORIZATION": "Kan het autorisatieproces niet starten",
|
||||
"NO_ORGANIZATION": "Selecteer eerst een organisatie",
|
||||
"LOAD_SETTINGS": "Kan Make.com-instellingen niet laden",
|
||||
"SAVE_SETTINGS": "Kan Make.com-instellingen niet opslaan",
|
||||
"LOAD_SETUP_STATUS": "Kan installatiestatus niet laden",
|
||||
"LOAD_SCENARIOS": "Kan scenario's niet laden",
|
||||
"TOGGLE_SCENARIO": "Kan scenario niet schakelen",
|
||||
"RUN_SCENARIO": "Kan scenario niet uitvoeren",
|
||||
"LOAD_CONNECTIONS": "Kan verbindingen niet laden",
|
||||
"TEST_CONNECTION": "Kan verbinding niet testen",
|
||||
"LOAD_WEBHOOKS": "Kan webhooks niet laden",
|
||||
"TOGGLE_WEBHOOK": "Kan webhook niet schakelen",
|
||||
"PING_WEBHOOK": "Kan webhook niet testen",
|
||||
"SET_ZONE": "Kan zone niet instellen",
|
||||
"LOAD_ORGANIZATIONS": "Kan organisaties niet laden",
|
||||
"SELECT_ORGANIZATION": "Kan organisatie niet selecteren",
|
||||
"LOAD_TEAMS": "Kan teams niet laden",
|
||||
"SELECT_TEAM": "Kan team niet selecteren",
|
||||
"LOAD_TEMPLATES": "Kan sjablonen niet laden",
|
||||
"REQUEST_TIMEOUT": "Aanvraagtijd verstreken. Probeer het opnieuw."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Make.com-instellingen succesvol opgeslagen",
|
||||
"SCENARIO_ACTIVATED": "Scenario geactiveerd",
|
||||
"SCENARIO_DEACTIVATED": "Scenario gedeactiveerd",
|
||||
"SCENARIO_EXECUTED": "Scenario uitgevoerd",
|
||||
"CONNECTION_VALID": "Verbinding \"{{name}}\" is geldig",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook ingeschakeld",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook uitgeschakeld",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" succesvol getest",
|
||||
"ZONE_SET": "Zone ingesteld op {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organisatie \"{{name}}\" geselecteerd",
|
||||
"TEAM_SELECTED": "Team \"{{name}}\" geselecteerd"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Autoriseer eerst Make.com. Ga naar het tabblad Instellingen om uw account te verbinden.",
|
||||
"ZONE": "Selecteer uw Make.com-zone in het tabblad Instellingen.",
|
||||
"ORGANIZATION": "Selecteer uw Make.com-organisatie in het tabblad Instellingen.",
|
||||
"TEAM": "Selecteer uw Make.com-team in het tabblad Instellingen."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1316,26 +1393,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "Client ID",
|
||||
"CLIENT_SECRET": "Client Geheim",
|
||||
"NEXT_STEP_INFO": "Na autorisatie word je doorgestuurd naar Zapier om de integratie-instellingen te voltooien",
|
||||
"CONNECT_TO_ZAPIER": "Verbinden met Zapier",
|
||||
"CONNECT_INFO": "Klik op de knop hieronder om je Zapier-account te verbinden. Je wordt doorgestuurd naar Zapier om de integratie te autoriseren.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Fout bij het laden van Zapier-instellingen",
|
||||
"SAVE_SETTINGS": "Fout bij het opslaan van Zapier-instellingen",
|
||||
"INVALID_FORM": "Vul alstublieft alle verplichte velden in",
|
||||
"MISSING_CREDENTIALS": "Ontbrekende clientreferenties",
|
||||
"NO_ORGANIZATION": "Selecteer alstublieft eerst een organisatie",
|
||||
"NO_ACCESS_TOKEN": "Geen toegangstoken beschikbaar",
|
||||
"LOAD_WEBHOOKS": "Fout bij het laden van webhooks",
|
||||
"DELETE_WEBHOOK": "Fout bij het verwijderen van webhook",
|
||||
"LOAD_ACTIONS": "Fout bij het laden van acties",
|
||||
"LOAD_TRIGGERS": "Fout bij het laden van triggers",
|
||||
"LOAD_ZAPS": "Fout bij het laden van zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Fout bij het laden van zap-sjablonen",
|
||||
"CREATE_ZAP": "Fout bij het maken van zap",
|
||||
"ZAP_TITLE_REQUIRED": "Geef alstublieft een titel voor de nieuwe zap",
|
||||
"NO_INTEGRATION_ID": "Er is geen Zapier-integratie-ID verstrekt",
|
||||
"START_AUTHORIZATION": "Start alstublieft de autorisatieproces",
|
||||
"INVALID_TOKEN": "Ongeldig token verstrekt",
|
||||
"TOKEN_NOT_FOUND": "Token niet gevonden"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Zapier-instellingen succesvol opgeslagen",
|
||||
"DELETE_WEBHOOK": "Webhook succesvol verwijderd"
|
||||
"DELETE_WEBHOOK": "Webhook succesvol verwijderd",
|
||||
"CREATE_ZAP": "Zap succesvol aangemaakt"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Triggers",
|
||||
@@ -1377,6 +1460,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "Geen webhooks geconfigureerd"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "Geen zaps beschikbaar",
|
||||
"CREATE": "Zap maken",
|
||||
"SUBMIT": "Maken",
|
||||
"ADD_STEP": "Stap toevoegen",
|
||||
"OPEN_EDITOR": "Openen in Zapier",
|
||||
"ENABLED": "Ingeschakeld",
|
||||
"DISABLED": "Uitgeschakeld",
|
||||
"TITLE_PLACEHOLDER": "Voer een titel in voor de nieuwe Zap",
|
||||
"FIELDS": {
|
||||
"TITLE": "Titel",
|
||||
"STEPS": "Stappen",
|
||||
"STEP": "Stap",
|
||||
"STEP_TITLE": "Stap titel",
|
||||
"ACTION": "Actie",
|
||||
"AUTHENTICATION": "Authenticatie",
|
||||
"INPUTS": "Invoer (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1609,7 +1712,17 @@
|
||||
"FILTER_INTEGRATIONS": "Filter integraties",
|
||||
"SEARCH_INTEGRATIONS": "Zoekintegraties",
|
||||
"PAID": "Betaald",
|
||||
"INTEGRATION": "Integratie"
|
||||
"INTEGRATION": "Integratie",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Make.com-autorisatie wordt verwerkt...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Make.com-autorisatie mislukt",
|
||||
"INVALID_CALLBACK": "Ongeldige callback-parameters"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Make.com-autorisatie succesvol voltooid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Boekhouding",
|
||||
|
||||
@@ -1293,16 +1293,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Scenariusze",
|
||||
"EXECUTIONS": "Wykonania",
|
||||
"HISTORY": "Historia",
|
||||
"WEBHOOKS": "Webhooki",
|
||||
"WEBHOOKS_REFRESH": "Odśwież webhooki",
|
||||
"WEBHOOK_ENABLE": "Włącz webhook",
|
||||
"WEBHOOK_DISABLE": "Wyłącz webhook",
|
||||
"WEBHOOK_PING": "Testuj webhook",
|
||||
"SCENARIO_ACTIVE": "Aktywny",
|
||||
"SCENARIO_INACTIVE": "Nieaktywny",
|
||||
"SCENARIO_LAST_EDITED": "Ostatnia edycja: {{value}}",
|
||||
"SCENARIO_DISABLE": "Wyłącz scenariusz",
|
||||
"SCENARIO_ENABLE": "Włącz scenariusz",
|
||||
"SCENARIO_RUN_ONCE": "Uruchom scenariusz raz",
|
||||
"WEBHOOK_ENABLED": "Włączony",
|
||||
"WEBHOOK_DISABLED": "Wyłączony",
|
||||
"WEBHOOK_TYPE": "Typ",
|
||||
"WEBHOOK_QUEUE": "Kolejka",
|
||||
"TEMPLATE_PUBLIC": "Publiczny",
|
||||
"TEMPLATE_PUBLISHED": "Opublikowany",
|
||||
"REFRESH_CONNECTIONS": "Odśwież połączenia",
|
||||
"CONFIGURATION_TITLE": "Konfiguracja Make.com",
|
||||
"ZONE_LABEL": "Strefa",
|
||||
"ZONE_HINT": "Wybierz strefę odpowiadającą Twojemu kontu Make.com (np. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Organizacja Make.com",
|
||||
"LOADING_ORGANIZATIONS": "Ładowanie organizacji...",
|
||||
"NO_ORGANIZATIONS": "Nie znaleziono organizacji",
|
||||
"TEAM_LABEL": "Zespół Make.com",
|
||||
"LOADING_TEAMS": "Ładowanie zespołów...",
|
||||
"NO_TEAMS": "Nie znaleziono zespołów",
|
||||
"CONNECTED_TO": "Połączony z {{org}} / {{team}} na {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Ustawienia Webhook",
|
||||
"CONNECTIONS": "Połączenia",
|
||||
"TEMPLATES": "Szablony",
|
||||
"SETTINGS": "Ustawienia",
|
||||
"CONNECT_TO_MAKE_COM": "Połącz z Make.com",
|
||||
"CONNECT_INFO": "Kliknij poniższy przycisk, aby połączyć swoje konto Make.com. Zostaniesz przekierowany do Make.com w celu autoryzacji integracji.",
|
||||
"NO_SCENARIOS": "Nie znaleziono scenariuszy. Utwórz scenariusze w panelu Make.com.",
|
||||
"NO_HOOKS": "Nie znaleziono webhooków. Utwórz webhooki w panelu Make.com.",
|
||||
"NO_CONNECTIONS": "Nie znaleziono połączeń. Utwórz połączenia w panelu Make.com.",
|
||||
"NO_TEMPLATES": "Nie znaleziono szablonów.",
|
||||
"TEST_CONNECTION": "Testuj połączenie",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Konto: {{value}}",
|
||||
"TYPE": "Typ: {{value}}",
|
||||
"PACKAGE": "Pakiet: {{value}}",
|
||||
"EXPIRES": "Wygasa: {{value}}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Proszę wypełnić wszystkie wymagane pola",
|
||||
"START_AUTHORIZATION": "Proszę rozpocząć proces autoryzacji"
|
||||
"START_AUTHORIZATION": "Nie udało się rozpocząć procesu autoryzacji",
|
||||
"NO_ORGANIZATION": "Proszę najpierw wybrać organizację",
|
||||
"LOAD_SETTINGS": "Nie udało się załadować ustawień Make.com",
|
||||
"SAVE_SETTINGS": "Nie udało się zapisać ustawień Make.com",
|
||||
"LOAD_SETUP_STATUS": "Nie udało się załadować statusu konfiguracji",
|
||||
"LOAD_SCENARIOS": "Nie udało się załadować scenariuszy",
|
||||
"TOGGLE_SCENARIO": "Nie udało się przełączyć scenariusza",
|
||||
"RUN_SCENARIO": "Nie udało się uruchomić scenariusza",
|
||||
"LOAD_CONNECTIONS": "Nie udało się załadować połączeń",
|
||||
"TEST_CONNECTION": "Nie udało się przetestować połączenia",
|
||||
"LOAD_WEBHOOKS": "Nie udało się załadować webhooków",
|
||||
"TOGGLE_WEBHOOK": "Nie udało się przełączyć webhooka",
|
||||
"PING_WEBHOOK": "Nie udało się przetestować webhooka",
|
||||
"SET_ZONE": "Nie udało się ustawić strefy",
|
||||
"LOAD_ORGANIZATIONS": "Nie udało się załadować organizacji",
|
||||
"SELECT_ORGANIZATION": "Nie udało się wybrać organizacji",
|
||||
"LOAD_TEAMS": "Nie udało się załadować zespołów",
|
||||
"SELECT_TEAM": "Nie udało się wybrać zespołu",
|
||||
"LOAD_TEMPLATES": "Nie udało się załadować szablonów",
|
||||
"REQUEST_TIMEOUT": "Upłynął czas żądania. Spróbuj ponownie."
|
||||
},
|
||||
"CLIENT_ID": "ID Klienta",
|
||||
"CLIENT_SECRET": "Sekret Klienta",
|
||||
"NEXT_STEP_INFO": "Po autoryzacji zostaniesz przekierowany do Make.com, aby dokończyć konfigurację integracji"
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Ustawienia Make.com zostały pomyślnie zapisane",
|
||||
"SCENARIO_ACTIVATED": "Scenariusz aktywowany",
|
||||
"SCENARIO_DEACTIVATED": "Scenariusz dezaktywowany",
|
||||
"SCENARIO_EXECUTED": "Scenariusz wykonany",
|
||||
"CONNECTION_VALID": "Połączenie \"{{name}}\" jest prawidłowe",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook włączony",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook wyłączony",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" przetestowany pomyślnie",
|
||||
"ZONE_SET": "Strefa ustawiona na {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Wybrano organizację \"{{name}}\"",
|
||||
"TEAM_SELECTED": "Wybrano zespół \"{{name}}\""
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Najpierw autoryzuj Make.com. Przejdź do zakładki Ustawienia, aby połączyć konto.",
|
||||
"ZONE": "Wybierz strefę Make.com w zakładce Ustawienia.",
|
||||
"ORGANIZATION": "Wybierz organizację Make.com w zakładce Ustawienia.",
|
||||
"TEAM": "Wybierz zespół Make.com w zakładce Ustawienia."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1315,26 +1392,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "ID Klienta",
|
||||
"CLIENT_SECRET": "Sekret Klienta",
|
||||
"NEXT_STEP_INFO": "Po autoryzacji zostaniesz przekierowany do Zapier, aby dokończyć konfigurację integracji",
|
||||
"CONNECT_TO_ZAPIER": "Połącz z Zapier",
|
||||
"CONNECT_INFO": "Kliknij przycisk poniżej, aby połączyć swoje konto Zapier. Zostaniesz przekierowany do Zapier w celu autoryzacji integracji.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Nie udało się załadować ustawień Zapier",
|
||||
"SAVE_SETTINGS": "Nie udało się zapisać ustawień Zapier",
|
||||
"INVALID_FORM": "Proszę wypełnić wszystkie wymagane pola",
|
||||
"MISSING_CREDENTIALS": "Brakujące dane uwierzytelniające klienta",
|
||||
"NO_ORGANIZATION": "Proszę najpierw wybrać organizację",
|
||||
"NO_ACCESS_TOKEN": "Brak dostępnego tokenu dostępu",
|
||||
"LOAD_WEBHOOKS": "Nie udało się załadować webhooków",
|
||||
"DELETE_WEBHOOK": "Nie udało się usunąć webhooka",
|
||||
"LOAD_ACTIONS": "Nie udało się załadować akcji",
|
||||
"LOAD_TRIGGERS": "Nie udało się załadować triggerów",
|
||||
"LOAD_ZAPS": "Nie udało się załadować zapów",
|
||||
"LOAD_ZAP_TEMPLATES": "Nie udało się załadować szablonów zap",
|
||||
"CREATE_ZAP": "Nie udało się utworzyć zapa",
|
||||
"ZAP_TITLE_REQUIRED": "Proszę podać tytuł dla nowego zapa",
|
||||
"NO_INTEGRATION_ID": "Nie podano identyfikatora integracji Zapier",
|
||||
"START_AUTHORIZATION": "Proszę rozpocząć proces autoryzacji",
|
||||
"INVALID_TOKEN": "Podano nieprawidłowy token",
|
||||
"TOKEN_NOT_FOUND": "Token nie został znaleziony"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Ustawienia Zapier zostały pomyślnie zapisane",
|
||||
"DELETE_WEBHOOK": "Webhook został pomyślnie usunięty"
|
||||
"DELETE_WEBHOOK": "Webhook został pomyślnie usunięty",
|
||||
"CREATE_ZAP": "Zap został pomyślnie utworzony"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Wyzwalacze",
|
||||
@@ -1376,6 +1459,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooki",
|
||||
"NO_WEBHOOKS": "Brak skonfigurowanych webhooków"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zapy",
|
||||
"NO_ZAPS": "Brak dostępnych zapów",
|
||||
"CREATE": "Utwórz Zap",
|
||||
"SUBMIT": "Utwórz",
|
||||
"ADD_STEP": "Dodaj krok",
|
||||
"OPEN_EDITOR": "Otwórz w Zapier",
|
||||
"ENABLED": "Włączone",
|
||||
"DISABLED": "Wyłączone",
|
||||
"TITLE_PLACEHOLDER": "Wprowadź tytuł dla nowego Zapa",
|
||||
"FIELDS": {
|
||||
"TITLE": "Tytuł",
|
||||
"STEPS": "Kroki",
|
||||
"STEP": "Krok",
|
||||
"STEP_TITLE": "Tytuł kroku",
|
||||
"ACTION": "Akcja",
|
||||
"AUTHENTICATION": "Uwierzytelnianie",
|
||||
"INPUTS": "Wejścia (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1608,7 +1711,17 @@
|
||||
"FILTER_INTEGRATIONS": "Filtruj integracje",
|
||||
"SEARCH_INTEGRATIONS": "Integracje wyszukiwania",
|
||||
"PAID": "Opłacone",
|
||||
"INTEGRATION": "Integracja"
|
||||
"INTEGRATION": "Integracja",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Przetwarzanie autoryzacji Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Autoryzacja Make.com nie powiodła się",
|
||||
"INVALID_CALLBACK": "Nieprawidłowe parametry wywołania zwrotnego"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Autoryzacja Make.com zakończona pomyślnie"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Rachunkowość",
|
||||
|
||||
@@ -1294,16 +1294,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Cenários",
|
||||
"EXECUTIONS": "Execuções",
|
||||
"HISTORY": "História",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "Atualizar webhooks",
|
||||
"WEBHOOK_ENABLE": "Ativar webhook",
|
||||
"WEBHOOK_DISABLE": "Desativar webhook",
|
||||
"WEBHOOK_PING": "Testar webhook",
|
||||
"SCENARIO_ACTIVE": "Ativo",
|
||||
"SCENARIO_INACTIVE": "Inativo",
|
||||
"SCENARIO_LAST_EDITED": "Última edição: {{value}}",
|
||||
"SCENARIO_DISABLE": "Desativar cenário",
|
||||
"SCENARIO_ENABLE": "Ativar cenário",
|
||||
"SCENARIO_RUN_ONCE": "Executar cenário uma vez",
|
||||
"WEBHOOK_ENABLED": "Ativado",
|
||||
"WEBHOOK_DISABLED": "Desativado",
|
||||
"WEBHOOK_TYPE": "Tipo",
|
||||
"WEBHOOK_QUEUE": "Fila",
|
||||
"TEMPLATE_PUBLIC": "Público",
|
||||
"TEMPLATE_PUBLISHED": "Publicado",
|
||||
"REFRESH_CONNECTIONS": "Atualizar conexões",
|
||||
"CONFIGURATION_TITLE": "Configuração do Make.com",
|
||||
"ZONE_LABEL": "Zona",
|
||||
"ZONE_HINT": "Selecione a zona que corresponde à sua conta Make.com (ex. us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Organização Make.com",
|
||||
"LOADING_ORGANIZATIONS": "Carregando organizações...",
|
||||
"NO_ORGANIZATIONS": "Nenhuma organização encontrada",
|
||||
"TEAM_LABEL": "Equipe Make.com",
|
||||
"LOADING_TEAMS": "Carregando equipes...",
|
||||
"NO_TEAMS": "Nenhuma equipe encontrada",
|
||||
"CONNECTED_TO": "Conectado a {{org}} / {{team}} em {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Configurações do Webhook",
|
||||
"CONNECTIONS": "Conexões",
|
||||
"TEMPLATES": "Modelos",
|
||||
"SETTINGS": "Configurações",
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Por favor, preencha todos os campos obrigatórios",
|
||||
"START_AUTHORIZATION": "Por favor, inicie o processo de autorização"
|
||||
"CONNECT_TO_MAKE_COM": "Conectar ao Make.com",
|
||||
"CONNECT_INFO": "Clique no botão abaixo para conectar sua conta Make.com. Você será redirecionado para o Make.com para autorizar a integração.",
|
||||
"NO_SCENARIOS": "Nenhum cenário encontrado. Crie cenários no seu painel Make.com.",
|
||||
"NO_HOOKS": "Nenhum webhook encontrado. Crie webhooks no seu painel Make.com.",
|
||||
"NO_CONNECTIONS": "Nenhuma conexão encontrada. Crie conexões no seu painel Make.com.",
|
||||
"NO_TEMPLATES": "Nenhum modelo encontrado.",
|
||||
"TEST_CONNECTION": "Testar conexão",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Conta: {{value}}",
|
||||
"TYPE": "Tipo: {{value}}",
|
||||
"PACKAGE": "Pacote: {{value}}",
|
||||
"EXPIRES": "Expira: {{value}}"
|
||||
},
|
||||
"CLIENT_ID": "ID do Cliente",
|
||||
"CLIENT_SECRET": "Segredo do Cliente",
|
||||
"NEXT_STEP_INFO": "Após a autorização, você será redirecionado para o Make.com para completar a configuração da integração"
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Por favor preencha todos os campos obrigatórios",
|
||||
"START_AUTHORIZATION": "Falha ao iniciar o processo de autorização",
|
||||
"NO_ORGANIZATION": "Por favor selecione uma organização primeiro",
|
||||
"LOAD_SETTINGS": "Falha ao carregar as configurações do Make.com",
|
||||
"SAVE_SETTINGS": "Falha ao salvar as configurações do Make.com",
|
||||
"LOAD_SETUP_STATUS": "Falha ao carregar o status de configuração",
|
||||
"LOAD_SCENARIOS": "Falha ao carregar cenários",
|
||||
"TOGGLE_SCENARIO": "Falha ao alternar cenário",
|
||||
"RUN_SCENARIO": "Falha ao executar cenário",
|
||||
"LOAD_CONNECTIONS": "Falha ao carregar conexões",
|
||||
"TEST_CONNECTION": "Falha ao testar conexão",
|
||||
"LOAD_WEBHOOKS": "Falha ao carregar webhooks",
|
||||
"TOGGLE_WEBHOOK": "Falha ao alternar webhook",
|
||||
"PING_WEBHOOK": "Falha ao testar webhook",
|
||||
"SET_ZONE": "Falha ao definir zona",
|
||||
"LOAD_ORGANIZATIONS": "Falha ao carregar organizações",
|
||||
"SELECT_ORGANIZATION": "Falha ao selecionar organização",
|
||||
"LOAD_TEAMS": "Falha ao carregar equipes",
|
||||
"SELECT_TEAM": "Falha ao selecionar equipe",
|
||||
"LOAD_TEMPLATES": "Falha ao carregar modelos",
|
||||
"REQUEST_TIMEOUT": "A solicitação expirou. Por favor, tente novamente."
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Configurações do Make.com salvas com sucesso",
|
||||
"SCENARIO_ACTIVATED": "Cenário ativado",
|
||||
"SCENARIO_DEACTIVATED": "Cenário desativado",
|
||||
"SCENARIO_EXECUTED": "Cenário executado",
|
||||
"CONNECTION_VALID": "A conexão \"{{name}}\" é válida",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook ativado",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook desativado",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" testado com sucesso",
|
||||
"ZONE_SET": "Zona definida como {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Organização \"{{name}}\" selecionada",
|
||||
"TEAM_SELECTED": "Equipe \"{{name}}\" selecionada"
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Por favor autorize o Make.com primeiro. Vá para a aba Configurações para conectar sua conta.",
|
||||
"ZONE": "Por favor selecione sua zona do Make.com na aba Configurações.",
|
||||
"ORGANIZATION": "Por favor selecione sua organização do Make.com na aba Configurações.",
|
||||
"TEAM": "Por favor selecione sua equipe do Make.com na aba Configurações."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1316,26 +1393,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "ID do Cliente",
|
||||
"CLIENT_SECRET": "Segredo do Cliente",
|
||||
"NEXT_STEP_INFO": "Após a autorização, você será redirecionado para o Zapier para completar a configuração da integração",
|
||||
"CONNECT_TO_ZAPIER": "Conectar ao Zapier",
|
||||
"CONNECT_INFO": "Clique no botão abaixo para conectar sua conta Zapier. Você será redirecionado ao Zapier para autorizar a integração.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Falha ao carregar configurações do Zapier",
|
||||
"SAVE_SETTINGS": "Falha ao salvar configurações do Zapier",
|
||||
"INVALID_FORM": "Por favor, preencha todos os campos obrigatórios",
|
||||
"MISSING_CREDENTIALS": "Credenciais do cliente ausentes",
|
||||
"MISSING_CREDENTIALS": "A integração do Zapier não está configurada no servidor",
|
||||
"NO_ORGANIZATION": "Por favor, selecione uma organização primeiro",
|
||||
"NO_ACCESS_TOKEN": "Nenhum token de acesso disponível",
|
||||
"LOAD_WEBHOOKS": "Falha ao carregar webhooks",
|
||||
"DELETE_WEBHOOK": "Falha ao excluir webhook",
|
||||
"LOAD_ACTIONS": "Falha ao carregar ações",
|
||||
"LOAD_TRIGGERS": "Falha ao carregar gatilhos",
|
||||
"LOAD_ZAPS": "Falha ao carregar zaps",
|
||||
"LOAD_ZAP_TEMPLATES": "Falha ao carregar modelos de zap",
|
||||
"CREATE_ZAP": "Falha ao criar zap",
|
||||
"ZAP_TITLE_REQUIRED": "Por favor, forneça um título para o novo zap",
|
||||
"NO_INTEGRATION_ID": "Nenhum ID de integração do Zapier foi fornecido",
|
||||
"START_AUTHORIZATION": "Por favor, inicie o processo de autorização",
|
||||
"INVALID_TOKEN": "Token inválido fornecido",
|
||||
"TOKEN_NOT_FOUND": "Token não encontrado"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Configurações do Zapier salvas com sucesso",
|
||||
"DELETE_WEBHOOK": "Webhook excluído com sucesso"
|
||||
"DELETE_WEBHOOK": "Webhook excluído com sucesso",
|
||||
"CREATE_ZAP": "Zap criado com sucesso"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Gatilhos",
|
||||
@@ -1377,6 +1460,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "Nenhum webhook configurado"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "Nenhum zap disponível",
|
||||
"CREATE": "Criar Zap",
|
||||
"SUBMIT": "Criar",
|
||||
"ADD_STEP": "Adicionar etapa",
|
||||
"OPEN_EDITOR": "Abrir no Zapier",
|
||||
"ENABLED": "Habilitado",
|
||||
"DISABLED": "Desabilitado",
|
||||
"TITLE_PLACEHOLDER": "Insira um título para o novo Zap",
|
||||
"FIELDS": {
|
||||
"TITLE": "Título",
|
||||
"STEPS": "Etapas",
|
||||
"STEP": "Etapa",
|
||||
"STEP_TITLE": "Título da etapa",
|
||||
"ACTION": "Ação",
|
||||
"AUTHENTICATION": "Autenticação",
|
||||
"INPUTS": "Entradas (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1609,7 +1712,17 @@
|
||||
"FILTER_INTEGRATIONS": "Integrações de filtro",
|
||||
"SEARCH_INTEGRATIONS": "Integrações de busca",
|
||||
"PAID": "Pago",
|
||||
"INTEGRATION": "Integração"
|
||||
"INTEGRATION": "Integração",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Processando autorização do Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Autorização do Make.com falhou",
|
||||
"INVALID_CALLBACK": "Parâmetros de retorno inválidos"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Autorização do Make.com concluída com sucesso"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Contabilidade",
|
||||
|
||||
@@ -1302,16 +1302,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "Сценарии",
|
||||
"EXECUTIONS": "Выполнения",
|
||||
"HISTORY": "История",
|
||||
"WEBHOOKS": "Вебхуки",
|
||||
"WEBHOOKS_REFRESH": "Обновить вебхуки",
|
||||
"WEBHOOK_ENABLE": "Включить вебхук",
|
||||
"WEBHOOK_DISABLE": "Отключить вебхук",
|
||||
"WEBHOOK_PING": "Тестировать вебхук",
|
||||
"SCENARIO_ACTIVE": "Активный",
|
||||
"SCENARIO_INACTIVE": "Неактивный",
|
||||
"SCENARIO_LAST_EDITED": "Последнее редактирование: {{value}}",
|
||||
"SCENARIO_DISABLE": "Отключить сценарий",
|
||||
"SCENARIO_ENABLE": "Включить сценарий",
|
||||
"SCENARIO_RUN_ONCE": "Запустить сценарий один раз",
|
||||
"WEBHOOK_ENABLED": "Включен",
|
||||
"WEBHOOK_DISABLED": "Отключен",
|
||||
"WEBHOOK_TYPE": "Тип",
|
||||
"WEBHOOK_QUEUE": "Очередь",
|
||||
"TEMPLATE_PUBLIC": "Публичный",
|
||||
"TEMPLATE_PUBLISHED": "Опубликованный",
|
||||
"REFRESH_CONNECTIONS": "Обновить подключения",
|
||||
"CONFIGURATION_TITLE": "Конфигурация Make.com",
|
||||
"ZONE_LABEL": "Зона",
|
||||
"ZONE_HINT": "Выберите зону, соответствующую вашей учетной записи Make.com (например, us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Организация Make.com",
|
||||
"LOADING_ORGANIZATIONS": "Загрузка организаций...",
|
||||
"NO_ORGANIZATIONS": "Организации не найдены",
|
||||
"TEAM_LABEL": "Команда Make.com",
|
||||
"LOADING_TEAMS": "Загрузка команд...",
|
||||
"NO_TEAMS": "Команды не найдены",
|
||||
"CONNECTED_TO": "Подключено к {{org}} / {{team}} на {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Настройки вебхука",
|
||||
"CONNECTIONS": "Подключения",
|
||||
"TEMPLATES": "Шаблоны",
|
||||
"SETTINGS": "Настройки",
|
||||
"CONNECT_TO_MAKE_COM": "Подключиться к Make.com",
|
||||
"CONNECT_INFO": "Нажмите кнопку ниже, чтобы подключить вашу учётную запись Make.com. Вы будете перенаправлены на Make.com для авторизации интеграции.",
|
||||
"NO_SCENARIOS": "Сценарии не найдены. Создайте сценарии в панели управления Make.com.",
|
||||
"NO_HOOKS": "Вебхуки не найдены. Создайте вебхуки в панели управления Make.com.",
|
||||
"NO_CONNECTIONS": "Подключения не найдены. Создайте подключения в панели управления Make.com.",
|
||||
"NO_TEMPLATES": "Шаблоны не найдены.",
|
||||
"TEST_CONNECTION": "Проверить подключение",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "Аккаунт: {{value}}",
|
||||
"TYPE": "Тип: {{value}}",
|
||||
"PACKAGE": "Пакет: {{value}}",
|
||||
"EXPIRES": "Истекает: {{value}}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "Пожалуйста, заполните все обязательные поля",
|
||||
"START_AUTHORIZATION": "Пожалуйста, начните процесс авторизации"
|
||||
"START_AUTHORIZATION": "Не удалось начать процесс авторизации",
|
||||
"NO_ORGANIZATION": "Пожалуйста, сначала выберите организацию",
|
||||
"LOAD_SETTINGS": "Не удалось загрузить настройки Make.com",
|
||||
"SAVE_SETTINGS": "Не удалось сохранить настройки Make.com",
|
||||
"LOAD_SETUP_STATUS": "Не удалось загрузить статус настройки",
|
||||
"LOAD_SCENARIOS": "Не удалось загрузить сценарии",
|
||||
"TOGGLE_SCENARIO": "Не удалось переключить сценарий",
|
||||
"RUN_SCENARIO": "Не удалось запустить сценарий",
|
||||
"LOAD_CONNECTIONS": "Не удалось загрузить подключения",
|
||||
"TEST_CONNECTION": "Не удалось проверить подключение",
|
||||
"LOAD_WEBHOOKS": "Не удалось загрузить вебхуки",
|
||||
"TOGGLE_WEBHOOK": "Не удалось переключить вебхук",
|
||||
"PING_WEBHOOK": "Не удалось протестировать вебхук",
|
||||
"SET_ZONE": "Не удалось установить зону",
|
||||
"LOAD_ORGANIZATIONS": "Не удалось загрузить организации",
|
||||
"SELECT_ORGANIZATION": "Не удалось выбрать организацию",
|
||||
"LOAD_TEAMS": "Не удалось загрузить команды",
|
||||
"SELECT_TEAM": "Не удалось выбрать команду",
|
||||
"LOAD_TEMPLATES": "Не удалось загрузить шаблоны",
|
||||
"REQUEST_TIMEOUT": "Время запроса истекло. Пожалуйста, попробуйте снова."
|
||||
},
|
||||
"CLIENT_ID": "ID клиента",
|
||||
"CLIENT_SECRET": "Секрет клиента",
|
||||
"NEXT_STEP_INFO": "После авторизации вы будете перенаправлены на Make.com для завершения настройки интеграции"
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Настройки Make.com успешно сохранены",
|
||||
"SCENARIO_ACTIVATED": "Сценарий активирован",
|
||||
"SCENARIO_DEACTIVATED": "Сценарий деактивирован",
|
||||
"SCENARIO_EXECUTED": "Сценарий выполнен",
|
||||
"CONNECTION_VALID": "Подключение \"{{name}}\" действительно",
|
||||
"WEBHOOK_TOGGLED_ON": "Вебхук включен",
|
||||
"WEBHOOK_TOGGLED_OFF": "Вебхук отключен",
|
||||
"WEBHOOK_PINGED": "Вебхук \"{{name}}\" успешно протестирован",
|
||||
"ZONE_SET": "Зона установлена на {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "Выбрана организация \"{{name}}\"",
|
||||
"TEAM_SELECTED": "Выбрана команда \"{{name}}\""
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "Сначала авторизуйтесь в Make.com. Перейдите на вкладку Настройки, чтобы подключить аккаунт.",
|
||||
"ZONE": "Выберите зону Make.com на вкладке Настройки.",
|
||||
"ORGANIZATION": "Выберите организацию Make.com на вкладке Настройки.",
|
||||
"TEAM": "Выберите команду Make.com на вкладке Настройки."
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1324,26 +1401,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "ID клиента",
|
||||
"CLIENT_SECRET": "Секрет клиента",
|
||||
"NEXT_STEP_INFO": "После авторизации вы будете перенаправлены на Zapier для завершения настройки интеграции",
|
||||
"CONNECT_TO_ZAPIER": "Подключиться к Zapier",
|
||||
"CONNECT_INFO": "Нажмите кнопку ниже, чтобы подключить вашу учетную запись Zapier. Вы будете перенаправлены на Zapier для авторизации интеграции.",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "Не удалось загрузить настройки Zapier",
|
||||
"SAVE_SETTINGS": "Не удалось сохранить настройки Zapier",
|
||||
"INVALID_FORM": "Пожалуйста, заполните все обязательные поля",
|
||||
"MISSING_CREDENTIALS": "Отсутствуют учетные данные клиента",
|
||||
"NO_ORGANIZATION": "Пожалуйста, сначала выберите организацию",
|
||||
"NO_ACCESS_TOKEN": "Нет доступного токена доступа",
|
||||
"LOAD_WEBHOOKS": "Не удалось загрузить вебхуки",
|
||||
"DELETE_WEBHOOK": "Не удалось удалить вебхук",
|
||||
"LOAD_ACTIONS": "Не удалось загрузить действия",
|
||||
"LOAD_TRIGGERS": "Не удалось загрузить триггеры",
|
||||
"LOAD_ZAPS": "Не удалось загрузить запы",
|
||||
"LOAD_ZAP_TEMPLATES": "Не удалось загрузить шаблоны зап",
|
||||
"CREATE_ZAP": "Не удалось создать зап",
|
||||
"ZAP_TITLE_REQUIRED": "Пожалуйста, укажите название для нового запа",
|
||||
"NO_INTEGRATION_ID": "Не был предоставлен идентификатор интеграции Zapier",
|
||||
"START_AUTHORIZATION": "Пожалуйста, начните процесс авторизации",
|
||||
"INVALID_TOKEN": "Предоставлен недействительный токен",
|
||||
"TOKEN_NOT_FOUND": "Токен не найден"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Настройки Zapier успешно сохранены",
|
||||
"DELETE_WEBHOOK": "Вебхук успешно удален"
|
||||
"DELETE_WEBHOOK": "Вебхук успешно удален",
|
||||
"CREATE_ZAP": "Зап успешно создан"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "Триггеры",
|
||||
@@ -1385,6 +1468,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Вебхуки",
|
||||
"NO_WEBHOOKS": "Нет настроенных вебхуков"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Запы",
|
||||
"NO_ZAPS": "Нет доступных запов",
|
||||
"CREATE": "Создать Зап",
|
||||
"SUBMIT": "Создать",
|
||||
"ADD_STEP": "Добавить шаг",
|
||||
"OPEN_EDITOR": "Открыть в Zapier",
|
||||
"ENABLED": "Включено",
|
||||
"DISABLED": "Отключено",
|
||||
"TITLE_PLACEHOLDER": "Введите название для нового Запа",
|
||||
"FIELDS": {
|
||||
"TITLE": "Название",
|
||||
"STEPS": "Шаги",
|
||||
"STEP": "Шаг",
|
||||
"STEP_TITLE": "Название шага",
|
||||
"ACTION": "Действие",
|
||||
"AUTHENTICATION": "Аутентификация",
|
||||
"INPUTS": "Входные данные (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1659,8 +1762,18 @@
|
||||
"INTEGRATION_DISABLED": "Интеграция '{{provider}}' успешно отключена",
|
||||
"INTEGRATION_ADDED": "Интеграция '{{provider}}' добавлена в '{{organization}}'"
|
||||
},
|
||||
"ENABLED": "Enabled",
|
||||
"DISABLED": "Disabled"
|
||||
"ENABLED": "Включено",
|
||||
"DISABLED": "Отключено",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "Обработка авторизации Make.com...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Авторизация Make.com не удалась",
|
||||
"INVALID_CALLBACK": "Недопустимые параметры обратного вызова"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Авторизация Make.com успешно завершена"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "Бухгалтер",
|
||||
|
||||
@@ -1294,16 +1294,93 @@
|
||||
},
|
||||
"MAKE_COM_PAGE": {
|
||||
"SCENARIOS": "场景",
|
||||
"EXECUTIONS": "执行",
|
||||
"HISTORY": "历史",
|
||||
"WEBHOOKS": "Webhooks",
|
||||
"WEBHOOKS_REFRESH": "刷新 Webhooks",
|
||||
"WEBHOOK_ENABLE": "启用 Webhook",
|
||||
"WEBHOOK_DISABLE": "禁用 Webhook",
|
||||
"WEBHOOK_PING": "测试 Webhook",
|
||||
"SCENARIO_ACTIVE": "活跃",
|
||||
"SCENARIO_INACTIVE": "非活跃",
|
||||
"SCENARIO_LAST_EDITED": "最后编辑:{{value}}",
|
||||
"SCENARIO_DISABLE": "禁用场景",
|
||||
"SCENARIO_ENABLE": "启用场景",
|
||||
"SCENARIO_RUN_ONCE": "运行场景一次",
|
||||
"WEBHOOK_ENABLED": "已启用",
|
||||
"WEBHOOK_DISABLED": "已禁用",
|
||||
"WEBHOOK_TYPE": "类型",
|
||||
"WEBHOOK_QUEUE": "队列",
|
||||
"TEMPLATE_PUBLIC": "公开",
|
||||
"TEMPLATE_PUBLISHED": "已发布",
|
||||
"REFRESH_CONNECTIONS": "刷新连接",
|
||||
"CONFIGURATION_TITLE": "Make.com 配置",
|
||||
"ZONE_LABEL": "区域",
|
||||
"ZONE_HINT": "选择与您的 Make.com 帐户匹配的区域(例如 us2.make.com)",
|
||||
"ORGANIZATION_LABEL": "Make.com 组织",
|
||||
"LOADING_ORGANIZATIONS": "正在加载组织...",
|
||||
"NO_ORGANIZATIONS": "未找到组织",
|
||||
"TEAM_LABEL": "Make.com 团队",
|
||||
"LOADING_TEAMS": "正在加载团队...",
|
||||
"NO_TEAMS": "未找到团队",
|
||||
"CONNECTED_TO": "已连接到 {{org}} / {{team}} 在 {{zone}}",
|
||||
"WEBHOOK_SETTINGS_TITLE": "Webhook 设置",
|
||||
"CONNECTIONS": "连接",
|
||||
"TEMPLATES": "模板",
|
||||
"SETTINGS": "设置",
|
||||
"CONNECT_TO_MAKE_COM": "连接到 Make.com",
|
||||
"CONNECT_INFO": "点击下面的按钮连接您的 Make.com 帐户。您将被重定向到 Make.com 以授权集成。",
|
||||
"NO_SCENARIOS": "未找到场景。在您的 Make.com 仪表板中创建场景。",
|
||||
"NO_HOOKS": "未找到 Webhooks。在您的 Make.com 仪表板中创建 Webhooks。",
|
||||
"NO_CONNECTIONS": "未找到连接。在您的 Make.com 仪表板中创建连接。",
|
||||
"NO_TEMPLATES": "未找到模板。",
|
||||
"TEST_CONNECTION": "测试连接",
|
||||
"CONNECTION": {
|
||||
"ACCOUNT": "账户:{{value}}",
|
||||
"TYPE": "类型:{{value}}",
|
||||
"PACKAGE": "套餐:{{value}}",
|
||||
"EXPIRES": "过期:{{value}}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"INVALID_FORM": "请填写所有必填字段",
|
||||
"START_AUTHORIZATION": "请开始授权过程"
|
||||
"START_AUTHORIZATION": "启动授权过程失败",
|
||||
"NO_ORGANIZATION": "请先选择一个组织",
|
||||
"LOAD_SETTINGS": "加载 Make.com 设置失败",
|
||||
"SAVE_SETTINGS": "保存 Make.com 设置失败",
|
||||
"LOAD_SETUP_STATUS": "加载设置状态失败",
|
||||
"LOAD_SCENARIOS": "加载场景失败",
|
||||
"TOGGLE_SCENARIO": "切换场景失败",
|
||||
"RUN_SCENARIO": "运行场景失败",
|
||||
"LOAD_CONNECTIONS": "加载连接失败",
|
||||
"TEST_CONNECTION": "测试连接失败",
|
||||
"LOAD_WEBHOOKS": "加载 Webhooks 失败",
|
||||
"TOGGLE_WEBHOOK": "切换 Webhook 失败",
|
||||
"PING_WEBHOOK": "测试 Webhook 失败",
|
||||
"SET_ZONE": "设置区域失败",
|
||||
"LOAD_ORGANIZATIONS": "加载组织失败",
|
||||
"SELECT_ORGANIZATION": "选择组织失败",
|
||||
"LOAD_TEAMS": "加载团队失败",
|
||||
"SELECT_TEAM": "选择团队失败",
|
||||
"LOAD_TEMPLATES": "加载模板失败",
|
||||
"REQUEST_TIMEOUT": "请求超时,请重试。"
|
||||
},
|
||||
"CLIENT_ID": "客户端ID",
|
||||
"CLIENT_SECRET": "客户端密钥",
|
||||
"NEXT_STEP_INFO": "授权后,您将被重定向到Make.com以完成集成设置"
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Make.com 设置已成功保存",
|
||||
"SCENARIO_ACTIVATED": "场景已激活",
|
||||
"SCENARIO_DEACTIVATED": "场景已停用",
|
||||
"SCENARIO_EXECUTED": "场景已执行",
|
||||
"CONNECTION_VALID": "连接 \"{{name}}\" 有效",
|
||||
"WEBHOOK_TOGGLED_ON": "Webhook 已启用",
|
||||
"WEBHOOK_TOGGLED_OFF": "Webhook 已禁用",
|
||||
"WEBHOOK_PINGED": "Webhook \"{{name}}\" 测试成功",
|
||||
"ZONE_SET": "区域已设置为 {{zone}}",
|
||||
"ORGANIZATION_SELECTED": "已选择组织 \"{{name}}\"",
|
||||
"TEAM_SELECTED": "已选择团队 \"{{name}}\""
|
||||
},
|
||||
"SETUP_REQUIRED": {
|
||||
"AUTHORIZE": "请先授权 Make.com。转到设置选项卡连接您的帐户。",
|
||||
"ZONE": "请在设置选项卡中选择您的 Make.com 区域。",
|
||||
"ORGANIZATION": "请在设置选项卡中选择您的 Make.com 组织。",
|
||||
"TEAM": "请在设置选项卡中选择您的 Make.com 团队。"
|
||||
}
|
||||
},
|
||||
"MAKE_COM": {
|
||||
"SETTINGS": {
|
||||
@@ -1316,26 +1393,32 @@
|
||||
}
|
||||
},
|
||||
"ZAPIER_PAGE": {
|
||||
"CLIENT_ID": "客户端ID",
|
||||
"CLIENT_SECRET": "客户端密钥",
|
||||
"NEXT_STEP_INFO": "授权后,您将被重定向到Zapier以完成集成设置",
|
||||
"CONNECT_TO_ZAPIER": "连接到 Zapier",
|
||||
"CONNECT_INFO": "点击下方按钮连接您的 Zapier 账户。您将被重定向到 Zapier 以授权集成。",
|
||||
"ERRORS": {
|
||||
"LOAD_SETTINGS": "加载Zapier设置失败",
|
||||
"SAVE_SETTINGS": "保存Zapier设置失败",
|
||||
"INVALID_FORM": "请填写所有必填字段",
|
||||
"MISSING_CREDENTIALS": "缺少客户端凭据",
|
||||
"NO_ORGANIZATION": "请先选择一个组织",
|
||||
"NO_ACCESS_TOKEN": "没有可用的访问令牌",
|
||||
"LOAD_WEBHOOKS": "加载Webhooks失败",
|
||||
"DELETE_WEBHOOK": "删除Webhook失败",
|
||||
"LOAD_ACTIONS": "加载动作失败",
|
||||
"LOAD_TRIGGERS": "加载触发器失败",
|
||||
"LOAD_ZAPS": "加载zaps失败",
|
||||
"LOAD_ZAP_TEMPLATES": "加载zap模板失败",
|
||||
"CREATE_ZAP": "创建zap失败",
|
||||
"ZAP_TITLE_REQUIRED": "请为新zap提供标题",
|
||||
"NO_INTEGRATION_ID": "未提供Zapier集成ID",
|
||||
"START_AUTHORIZATION": "请开始授权过程",
|
||||
"INVALID_TOKEN": "提供的令牌无效",
|
||||
"TOKEN_NOT_FOUND": "未找到令牌"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"SETTINGS_SAVED": "Zapier设置保存成功",
|
||||
"DELETE_WEBHOOK": "Webhook删除成功"
|
||||
"DELETE_WEBHOOK": "Webhook删除成功",
|
||||
"CREATE_ZAP": "Zap创建成功"
|
||||
},
|
||||
"TAB": {
|
||||
"TRIGGERS": "触发器",
|
||||
@@ -1377,6 +1460,26 @@
|
||||
"WEBHOOKS": {
|
||||
"TITLE": "Webhooks",
|
||||
"NO_WEBHOOKS": "未配置Webhooks"
|
||||
},
|
||||
"ZAPS": {
|
||||
"TITLE": "Zaps",
|
||||
"NO_ZAPS": "没有可用的Zaps",
|
||||
"CREATE": "创建Zap",
|
||||
"SUBMIT": "创建",
|
||||
"ADD_STEP": "添加步骤",
|
||||
"OPEN_EDITOR": "在Zapier中打开",
|
||||
"ENABLED": "已启用",
|
||||
"DISABLED": "已禁用",
|
||||
"TITLE_PLACEHOLDER": "输入新Zap的标题",
|
||||
"FIELDS": {
|
||||
"TITLE": "标题",
|
||||
"STEPS": "步骤",
|
||||
"STEP": "步骤",
|
||||
"STEP_TITLE": "步骤标题",
|
||||
"ACTION": "操作",
|
||||
"AUTHENTICATION": "身份验证",
|
||||
"INPUTS": "输入 (JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GAUZY_AI_PAGE": {
|
||||
@@ -1609,7 +1712,17 @@
|
||||
"FILTER_INTEGRATIONS": "筛选集成",
|
||||
"SEARCH_INTEGRATIONS": "搜索集成",
|
||||
"PAID": "支付",
|
||||
"INTEGRATION": "整合"
|
||||
"INTEGRATION": "整合",
|
||||
"MAKE_PAGE": {
|
||||
"PROCESSING_AUTHORIZATION": "正在处理 Make.com 授权...",
|
||||
"ERRORS": {
|
||||
"AUTHORIZATION_FAILED": "Make.com 授权失败",
|
||||
"INVALID_CALLBACK": "无效的回调参数"
|
||||
},
|
||||
"SUCCESS": {
|
||||
"AUTHORIZATION_COMPLETE": "Make.com 授权已成功完成"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DASHBOARD_PAGE": {
|
||||
"ACCOUNTING": "会计",
|
||||
@@ -4473,8 +4586,8 @@
|
||||
"DB_CLEANUP_KEEP": "保留",
|
||||
"DB_CLEANUP_UNSYNCED_TITLE": "检测到未同步数据",
|
||||
"DB_CLEANUP_UNSYNCED_MESSAGE": "警告:可能丢失数据",
|
||||
"DB_CLEANUP_UNSYNCED_DETAIL": "您有 {{countsText}}。删除数据库将永久清除这些离线数据。如果您不确定,请点击\u201c保留\u201d。",
|
||||
"DB_CLEANUP_UNKNOWN_DETAIL": "我们无法确定您是否有未同步的离线数据。删除数据库可能会永久清除未保存的工作。如果您不确定,请点击\u201c保留\u201d。",
|
||||
"DB_CLEANUP_UNSYNCED_DETAIL": "您有 {{countsText}}。删除数据库将永久清除这些离线数据。如果您不确定,请点击“保留”。",
|
||||
"DB_CLEANUP_UNKNOWN_DETAIL": "我们无法确定您是否有未同步的离线数据。删除数据库可能会永久清除未保存的工作。如果您不确定,请点击“保留”。",
|
||||
"UNSYNCED_TIMERS_COUNT": "{{count}} unsynced timer(s)",
|
||||
"UNSYNCED_INTERVALS_COUNT": "{{count}} unsynced interval(s)",
|
||||
"AND_JOIN": "and"
|
||||
|
||||
@@ -44403,10 +44403,10 @@ yoctocolors@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/yoctocolors/-/yoctocolors-2.1.2.tgz#d795f54d173494e7d8db93150cec0ed7f678c83a"
|
||||
integrity sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==
|
||||
|
||||
zapier-platform-cli@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/zapier-platform-cli/-/zapier-platform-cli-18.2.0.tgz#e389ea17a3d8816b2645f3cce76b07b18b856e44"
|
||||
integrity sha512-HhqZ2lwVv7qxh791h5kJzadeZlbN2wTjVSf0LUvSVyE1ZbVXwP5IXchv0uatH10CyueizfUExfPlAH3ilNGUog==
|
||||
zapier-platform-cli@^18.3.0:
|
||||
version "18.3.0"
|
||||
resolved "https://registry.yarnpkg.com/zapier-platform-cli/-/zapier-platform-cli-18.3.0.tgz#a99a654ecc5e99f86c0a9466a25992be411568a6"
|
||||
integrity sha512-XKtrhOgfrVuY8MeCYA3mpqKe/tCg996r0mbgi9FV20aT/divPTMvk2IIHspUZSvSYBQJiDF5SSwD5CprBN9/5w==
|
||||
dependencies:
|
||||
"@oclif/core" "4.5.2"
|
||||
"@oclif/plugin-autocomplete" "3.2.34"
|
||||
@@ -44446,10 +44446,10 @@ zapier-platform-cli@18.2.0:
|
||||
yeoman-environment "4.4.3"
|
||||
yeoman-generator "7.5.1"
|
||||
|
||||
zapier-platform-core@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/zapier-platform-core/-/zapier-platform-core-18.2.0.tgz#4b0c5193d6a732f3157b20e51047cf840adeed59"
|
||||
integrity sha512-adC07pEj4mqxneq3eOqbT2aTSqyUVlzLYQWp5kbA3P5tSYZs/OdovRYoFzNyXCu/UplcmKgeXxjQPIiikEZXUQ==
|
||||
zapier-platform-core@^18.3.0:
|
||||
version "18.3.0"
|
||||
resolved "https://registry.yarnpkg.com/zapier-platform-core/-/zapier-platform-core-18.3.0.tgz#e13547af6ffdd0b956f0b37d22efe1736cef8e92"
|
||||
integrity sha512-qhvWPLqGd29o4SnAE4Ve9K6BLeCUjko16pdxqK9KNzjDH5e57RLOqFICuXS+0DATcKirdqUCoodxnMTHahk/XA==
|
||||
dependencies:
|
||||
"@zapier/secret-scrubber" "^1.1.2"
|
||||
content-disposition "0.5.4"
|
||||
@@ -44462,14 +44462,14 @@ zapier-platform-core@18.2.0:
|
||||
node-fetch "2.7.0"
|
||||
oauth-sign "0.9.0"
|
||||
semver "7.7.2"
|
||||
zapier-platform-schema "18.2.0"
|
||||
zapier-platform-schema "18.3.0"
|
||||
optionalDependencies:
|
||||
"@types/node" "^20.3.1"
|
||||
|
||||
zapier-platform-schema@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/zapier-platform-schema/-/zapier-platform-schema-18.2.0.tgz#832055ea338d48576c3493ecb97081d6f4fafda2"
|
||||
integrity sha512-Iga89Fy1UgJZNMsTkd6QQmb0yBv8YgSjU9QUP6tyt1E4PX1R7XMlUc/7L6AaP8Nmuw9kaDu1fURiuWqoTDfsAg==
|
||||
zapier-platform-schema@18.3.0:
|
||||
version "18.3.0"
|
||||
resolved "https://registry.yarnpkg.com/zapier-platform-schema/-/zapier-platform-schema-18.3.0.tgz#384f733535fe2d9cf4b3761452a18db2e8f3e67a"
|
||||
integrity sha512-WigaXQyHGowxerFO6lfF44RvgmdFbTIHRWus7AeVT+3bQdN+rJeadlTM9/gTrkmrgcbK3dQaoi4OuNsoASf3Cg==
|
||||
dependencies:
|
||||
jsonschema "1.5.0"
|
||||
lodash "4.17.23"
|
||||
|
||||
Reference in New Issue
Block a user