diff --git a/backend/tests/data/mbse-works-plugin.yml b/backend/tests/data/mbse-works-plugin.yml index 800f392321..f817c80fca 100644 --- a/backend/tests/data/mbse-works-plugin.yml +++ b/backend/tests/data/mbse-works-plugin.yml @@ -4,7 +4,7 @@ metadata: id: t4c-to-git description: | - Synchronize the content of from a T4C repository project to a Git repository. + Synchronize the content from a T4C repository project to a Git repository. trigger: cron: diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 1d8846eacb..46e9357255 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -131,6 +131,8 @@ import { T4CSettingsWrapperComponent } from './settings/modelsources/t4c-setting import { T4CSettingsComponent } from './settings/modelsources/t4c-settings/t4c-settings.component'; import { SettingsComponent } from './settings/settings.component'; import { CreatePipelineComponent } from './projects/models/backup-settings/create-pipeline/create-pipeline.component'; +import { PipelineGitInputComponent } from './projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component'; +import { PipelineT4CInputComponent } from './projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component'; @NgModule({ declarations: [ @@ -219,6 +221,8 @@ import { CreatePipelineComponent } from './projects/models/backup-settings/creat VersionComponent, ViewLogsDialogComponent, CreatePipelineComponent, + PipelineGitInputComponent, + PipelineT4CInputComponent, ], imports: [ AppRoutingModule, diff --git a/frontend/src/app/general/header/header.component.html b/frontend/src/app/general/header/header.component.html index 41b0d2f25d..f4967f83ab 100644 --- a/frontend/src/app/general/header/header.component.html +++ b/frontend/src/app/general/header/header.component.html @@ -35,7 +35,7 @@ </div> </div> <div> - <div class="hidden xl:flex xl:right-aligned gap-2"> + <div class="hidden xl:flex xl:right-aligned gap-button"> <div> <a mat-raised-button diff --git a/frontend/src/app/plugins/store/create-plugin/create-plugin.component.ts b/frontend/src/app/plugins/store/create-plugin/create-plugin.component.ts index 56a8866da9..d8513ee737 100644 --- a/frontend/src/app/plugins/store/create-plugin/create-plugin.component.ts +++ b/frontend/src/app/plugins/store/create-plugin/create-plugin.component.ts @@ -4,6 +4,7 @@ */ import { Component } from '@angular/core'; +import { Router } from '@angular/router'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Plugin, @@ -16,8 +17,10 @@ import { styleUrls: ['./create-plugin.component.css'], }) export class CreatePluginComponent { - constructor(public pluginStoreService: PluginStoreService) {} - + constructor( + public pluginStoreService: PluginStoreService, + private router: Router + ) {} createPluginForm = new FormGroup({ remote: new FormControl('', Validators.required), username: new FormControl(''), @@ -26,9 +29,12 @@ export class CreatePluginComponent { onSubmit() { if (this.createPluginForm.valid) { - this.pluginStoreService.registerPluginInStore( - this.createPluginForm.value as Plugin - ); + this.pluginStoreService + .registerPluginInStore(this.createPluginForm.value as Plugin) + .subscribe(() => { + this.pluginStoreService.fetchPluginsFromStore(); + this.router.navigate(['/plugins']); + }); } } diff --git a/frontend/src/app/plugins/store/plugin-store-overview/plugin-store-overview.component.html b/frontend/src/app/plugins/store/plugin-store-overview/plugin-store-overview.component.html index f26bb39014..2233aed2f7 100644 --- a/frontend/src/app/plugins/store/plugin-store-overview/plugin-store-overview.component.html +++ b/frontend/src/app/plugins/store/plugin-store-overview/plugin-store-overview.component.html @@ -3,7 +3,7 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -<div class="flexbox"> +<div class="flex flex-wrap"> <a routerLink="/plugins/create"> <mat-card matRipple class="mat-card-overview new"> <div class="content"> diff --git a/frontend/src/app/plugins/store/service/plugin-store.service.ts b/frontend/src/app/plugins/store/service/plugin-store.service.ts index c74a85e9f5..f960b6f033 100644 --- a/frontend/src/app/plugins/store/service/plugin-store.service.ts +++ b/frontend/src/app/plugins/store/service/plugin-store.service.ts @@ -5,7 +5,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Observable, tap } from 'rxjs'; import { environment } from 'src/environments/environment'; @Injectable({ @@ -29,17 +29,19 @@ export class PluginStoreService { .subscribe({ next: (plugins) => this._plugins.next(plugins) }); } - registerPluginInStore(plugin: Plugin) { + registerPluginInStore(plugin: Plugin): Observable<Plugin> { this._plugin.next(undefined); - this.httpClient + return this.httpClient .post<Plugin>(`${environment.backend_url}/plugins`, plugin) - .subscribe({ next: (plugin) => this._plugin.next(plugin) }); + .pipe(tap({ next: (plugin) => this._plugin.next(plugin) })); } } export type PluginMetadata = { id: string; description: string; + displayName: string; + documentationURL: string; }; export type CreatePlugin = { @@ -61,3 +63,19 @@ export type PluginTemplateContent = { export type PluginTemplateInput = { type: string; }; + +export type PluginTemplateGitInput = PluginTemplateInput & { + mapping: string; +}; + +export type PluginTemplateEnvironmentMapping = { + type: 'environment'; + key: string; +}; + +export type PluginTemplateGitMapping = { + url: PluginTemplateEnvironmentMapping; + username: PluginTemplateEnvironmentMapping; + password: PluginTemplateEnvironmentMapping; + revision: PluginTemplateEnvironmentMapping; +}; diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.css b/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.css index 85f1b145fc..69d577cc86 100644 --- a/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.css +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.css @@ -3,10 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -#get-started { - width: 46em; -} - .header { background-color: var(--primary-color); color: white; @@ -14,5 +10,4 @@ display: flex; justify-content: space-between; align-items: center; - padding: 10px 10px; -} +} \ No newline at end of file diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.html b/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.html index 668d1fd2e0..d25fcc7fcc 100644 --- a/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.html +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.html @@ -3,6 +3,17 @@ ~ SPDX-License-Identifier: Apache-2.0 --> +<mat-card + class="w-full text-center bg-primary text-white" + *ngIf="selectedPlugin" + >Selected plugin: <br /> + <b> + {{ + selectedPlugin.content?.metadata?.displayName || + selectedPlugin.content?.metadata?.id + }} + </b> +</mat-card> <mat-horizontal-stepper [linear]="false" #stepper> <mat-step> <h2>Welcome to the pipeline setup agent!</h2> @@ -27,52 +38,84 @@ <h2>Welcome to the pipeline setup agent!</h2> mat-raised-button color="primary" matStepperNext - id="get-started" + class="w-[46em]" > - Get started <mat-icon>arrow_right_alt</mat-icon> + <span> Get started </span> + <mat-icon>arrow_right_alt</mat-icon> </button> </div> </form> </mat-step> <mat-step> - <div class="flexbox"> + <div class="flex flex-wrap"> <app-mat-card-overview-skeleton-loader [loading]="(pluginStoreService.plugins | async) === undefined" ></app-mat-card-overview-skeleton-loader> <ng-container *ngIf="pluginStoreService.plugins | async"> <mat-card *ngFor="let plugin of (pluginStoreService.plugins | async)!" - class="w-[400px] p-0" + class="w-[400px] !p-0" > - <div class="header"> + <div class="header px-4 text-lg py-2.5"> {{ - plugin.content?.metadata?.id || - "No identifier provided in template" + plugin.content?.metadata?.displayName || + "No display name provided in template" }} </div> - <div class="flex justify-between gap-3 flex-col"> + <div class="flex justify-between gap-3 flex-col p-4"> <div> {{ plugin.content?.metadata?.description || "No description provided." }} - <hr /> - <br /> + <hr class="my-2" /> <div> - <a [href]="plugin.remote" target="_blank" - >Template source <mat-icon>open_in_new</mat-icon> + <a + [href]="plugin.remote" + target="_blank" + class="flex items-center text-blue-800" + > + <div>Template source</div> + <mat-icon class="scale-75">open_in_new</mat-icon> </a> </div> - <b>Input:</b> {{ getInputList(plugin) }} <br /> + <b>Requires:</b> + <ul class="list-disc pl-10"> + <li + *ngFor=" + let inputType of cleanInputTypes( + plugin.content?.input || [] + ) + " + > + {{ mapInputToDisplayName(inputType) }} + </li> + </ul> </div> <div class="flex gap-2 justify-between"> - <button mat-stroked-button> - Documentation<mat-icon class="ml-2">question_answer</mat-icon> - </button> - <a [routerLink]="['/plugin', plugin.id]" mat-stroked-button> + <div + matTooltip="No documentation available for this plugin." + [matTooltipDisabled]=" + plugin.content?.metadata?.documentationURL + " + > + <button + mat-stroked-button + [disabled]="!plugin.content?.metadata?.documentationURL" + class="flex items-center" + > + <span>Documentation</span + ><mat-icon class="ml-2">help_outline</mat-icon> + </button> + </div> + <button + mat-stroked-button + (click)="selectPlugin(stepper, plugin)" + class="flex items-center" + > Select <mat-icon>play_arrow</mat-icon> - </a> + </button> </div> </div> </mat-card> @@ -82,21 +125,30 @@ <h2>Welcome to the pipeline setup agent!</h2> <ng-template matStepLabel>Plugin selection</ng-template> </form> </mat-step> - <mat-step> <form> <ng-template matStepLabel>Configuration</ng-template> - <!-- Your Configuration Form Goes Here --> - <div> - <button mat-button matStepperPrevious>Back</button> - <button mat-button matStepperNext>Next</button> - </div> + <mat-horizontal-stepper [linear]="false" #configurationStepper> + <mat-step *ngFor="let input of selectedPlugin?.content?.input"> + <form> + <ng-template matStepLabel>{{ + mapInputToDisplayName(input.type) + }}</ng-template> + <app-pipeline-git-input + [input]="input" + *ngIf="input.type === 'git'" + ></app-pipeline-git-input> + <app-pipeline-t4c-input + *ngIf="input.type === 't4c'" + ></app-pipeline-t4c-input> + </form> + </mat-step> + </mat-horizontal-stepper> </form> </mat-step> <mat-step> <form> <ng-template matStepLabel>Trigger rules</ng-template> - <!-- Your Configuration Form Goes Here --> <div> <button mat-button matStepperPrevious>Back</button> <button mat-button matStepperNext>Next</button> diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.ts b/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.ts index 9ef707212d..85c1d81569 100644 --- a/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.ts +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/create-pipeline.component.ts @@ -4,9 +4,11 @@ */ import { Component } from '@angular/core'; +import { MatStepper } from '@angular/material/stepper'; import { PluginStoreService, Plugin, + PluginTemplateInput, } from 'src/app/plugins/store/service/plugin-store.service'; @Component({ @@ -17,7 +19,23 @@ import { export class CreatePipelineComponent { constructor(public pluginStoreService: PluginStoreService) {} - getInputList(plugin: Plugin) { - return plugin.content?.input?.map((input) => input.type).join(', '); + selectedPlugin?: Plugin = undefined; + + cleanInputTypes(inputs: PluginTemplateInput[]): string[] { + return [...new Set(inputs.map((input) => input.type))]; + } + + mapInputToDisplayName(input: string) { + return { + git: 'Git repository access', + t4c: 'TeamForCapella repository access', + yml: 'YAML configuration file', + environment: 'Individual configuration via environment variables', + }[input]; + } + + selectPlugin(stepper: MatStepper, plugin: Plugin) { + this.selectedPlugin = plugin; + stepper.next(); } } diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.css b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.css new file mode 100644 index 0000000000..d49deaffd7 --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.css @@ -0,0 +1,4 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.html b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.html new file mode 100644 index 0000000000..e67bd25063 --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.html @@ -0,0 +1,39 @@ +<!-- + ~ SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + ~ SPDX-License-Identifier: Apache-2.0 + --> + +Please select a Git model from the list: +<mat-selection-list [multiple]="false"> + <mat-list-option + [(ngModel)]="selectedGitModel" + *ngFor="let gitModel of gitModelService.gitModels$ | async" + [value]="gitModel.id" + > + <div mat-line>Git model {{ gitModel.id }}</div> + <div mat-line> + URL '{{ gitModel.path }}', revision '{{ gitModel.revision }}' + </div> + </mat-list-option> +</mat-selection-list> + +<div class="mt-separator border p-2"> + The pipeline will get access to the following information of the selected Git + model: + <ul class="list-disc pl-10"> + <li *ngFor="let mapping of gitInput?.mapping | keyvalue"> + {{ mapping.key }} + </li> + </ul> +</div> + +<mat-checkbox class="mt-separator" + >I confirm that I trust the pipeline authors and confirm that the pipeline + should have access to the above values. +</mat-checkbox> + +<div class="mt-separator"> + <button [disabled]="selectedGitModel === undefined" mat-stroked-button> + Submit and continue + </button> +</div> diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.spec.ts b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.spec.ts new file mode 100644 index 0000000000..d39392218b --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.spec.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PipelineGitInputComponent } from './pipeline-git-input.component'; + +describe('PipelineGitInputComponent', () => { + let component: PipelineGitInputComponent; + let fixture: ComponentFixture<PipelineGitInputComponent>; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PipelineGitInputComponent], + }); + fixture = TestBed.createComponent(PipelineGitInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.ts b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.ts new file mode 100644 index 0000000000..83e042436d --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-git-input/pipeline-git-input.component.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component, Input } from '@angular/core'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { combineLatest, filter } from 'rxjs'; +import { + PluginTemplateGitInput, + PluginTemplateInput, +} from 'src/app/plugins/store/service/plugin-store.service'; +import { ModelService } from 'src/app/projects/models/service/model.service'; +import { GitModelService } from 'src/app/projects/project-detail/model-overview/model-detail/git-model.service'; +import { ProjectService } from 'src/app/projects/service/project.service'; + +@Component({ + selector: 'app-pipeline-git-input', + templateUrl: './pipeline-git-input.component.html', + styleUrls: ['./pipeline-git-input.component.css'], +}) +@UntilDestroy() +export class PipelineGitInputComponent { + @Input() + input?: PluginTemplateInput = undefined; + + selectedGitModel = undefined; + + get gitInput() { + return this.input as PluginTemplateGitInput; + } + + constructor( + public gitModelService: GitModelService, + private projectService: ProjectService, + private modelService: ModelService + ) { + combineLatest([ + this.projectService.project$.pipe(filter(Boolean)), + this.modelService.model$.pipe(filter(Boolean)), + ]) + .pipe(untilDestroyed(this)) + .subscribe(([project, model]) => { + this.gitModelService.loadGitModels(project.slug, model.slug); + }); + } +} diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.css b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.css new file mode 100644 index 0000000000..d49deaffd7 --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.css @@ -0,0 +1,4 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.html b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.html new file mode 100644 index 0000000000..e97a3e8d6f --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.html @@ -0,0 +1,6 @@ +<!-- + ~ SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + ~ SPDX-License-Identifier: Apache-2.0 + --> + +<p>pipeline-t4c-input works!</p> diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.spec.ts b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.spec.ts new file mode 100644 index 0000000000..4840f1c6ef --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.spec.ts @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PipelineT4CInputComponent } from './pipeline-t4c-input.component'; + +describe('PipelineT4cInputComponent', () => { + let component: PipelineT4CInputComponent; + let fixture: ComponentFixture<PipelineT4CInputComponent>; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PipelineT4CInputComponent], + }); + fixture = TestBed.createComponent(PipelineT4CInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.ts b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.ts new file mode 100644 index 0000000000..d034f72eb5 --- /dev/null +++ b/frontend/src/app/projects/models/backup-settings/create-pipeline/pipeline-input/pipeline-t4c-input/pipeline-t4c-input.component.ts @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-pipeline-t4c-input', + templateUrl: './pipeline-t4c-input.component.html', + styleUrls: ['./pipeline-t4c-input.component.css'], +}) +export class PipelineT4CInputComponent {} diff --git a/frontend/src/assets/mbse-works-plugin.yml b/frontend/src/assets/mbse-works-plugin.yml new file mode 100644 index 0000000000..9e7726d5dd --- /dev/null +++ b/frontend/src/assets/mbse-works-plugin.yml @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors +# SPDX-License-Identifier: Apache-2.0 + +metadata: + id: t4c-to-git + displayName: TeamForCapella to Git synchronization + description: | + Synchronize the content from a T4C repository project to a Git repository. + +input: + - type: git + mapping: + url: + type: environment + key: GIT_REPO_URL + username: + type: environment + key: GIT_USERNAME + password: + type: environment + key: GIT_PASSWORD + revision: + type: environment + key: GIT_REPO_BRANCH + - type: t4c + mapping: + host: + type: environment + key: T4C_REPO_HOST + repositoryPort: + type: environment + key: T4C_REPO_PORT + cdoPort: + type: environment + key: T4C_CDO_PORT + repositoryName: + type: environment + key: T4C_REPO_NAME + projectName: + type: environment + key: T4C_PROJECT_NAME + repositoryUsername: + type: environment + key: T4C_USERNAME + repositoryPassword: + type: environment + key: T4C_PASSWORD + - type: yml + mount: /opt/configuration/test.yml + schema: + predefined: + include_commit_history: true + - type: environment + key: LOG_LEVEL + value: INFO + - type: environment + key: INCLUDE_COMMIT_HISTORY + values: ["true", "false"] + +job: + image: $DOCKER_REGISTRY/t4c/client/backup + cmd: + - backup diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index a86031c952..b7af77a12b 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -7,7 +7,19 @@ module.exports = { content: ["./src/**/*.{html,ts}"], theme: { - extend: {}, + extend: { + colors: { + primary: "var(--primary-color)", + error: "var(--error-color)", + warning: "var(--warning-color)", + success: "var(--success-color)", + hover: "var(--hover-color)", + }, + spacing: { + button: "0.5rem", + separator: "0.5rem", + }, + }, }, plugins: [], };