From 4d390cfaa095064d73077b41eda65418379751d7 Mon Sep 17 00:00:00 2001 From: MoritzWeber Date: Thu, 14 Dec 2023 18:19:47 +0100 Subject: [PATCH] feat: Support Git paths in diagram cache code snippet The commits identifies the primary Git model of a model and adds the information to the code snippet of the diagram cache. In addition, there were some minor/internal changes: - The code snippet code was moved to a new component `ModelDiagramCacheBlockComponent`. - Syntax highlighting was added in the frontend. - A duplication of the code snippet declaration was removed. - Margins and paddings were updated, responsive design was added. - The colors of the code snippet were inverted (white text on dark background) - Information about token revocation was added to the dialog. --- backend/capellacollab/core/metadata.py | 23 ++- backend/config/config_template.yaml | 2 +- frontend/.eslintrc.js | 7 +- frontend/angular.json | 3 +- frontend/package-lock.json | 144 ++---------------- frontend/package.json | 1 + frontend/src/app/app.module.ts | 6 + .../app/general/metadata/metadata.service.ts | 4 + .../model-diagram-code-block.component.html | 52 +++++++ .../model-diagram-code-block.component.ts | 110 +++++++++++++ .../model-diagram-dialog.component.css | 5 - .../model-diagram-dialog.component.html | 63 ++------ .../model-diagram-dialog.component.ts | 65 ++------ .../model-overview.component.ts | 2 +- frontend/tailwind.config.js | 1 + 15 files changed, 230 insertions(+), 258 deletions(-) create mode 100644 frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.html create mode 100644 frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.ts diff --git a/backend/capellacollab/core/metadata.py b/backend/capellacollab/core/metadata.py index 60dc808ca7..f04ed29b09 100644 --- a/backend/capellacollab/core/metadata.py +++ b/backend/capellacollab/core/metadata.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors # SPDX-License-Identifier: Apache-2.0 +import typing as t + import fastapi import pydantic @@ -18,9 +20,15 @@ class Metadata(pydantic.BaseModel): authentication_provider: str | None environment: str | None + host: str | None + port: str | None + protocol: str | None + router = fastapi.APIRouter() -cfg: dict[str, str | None] = config["general"].get("metadata", {}) + +general_cfg: dict[str, t.Any] = config["general"] +metadata_cfg: dict[str, str | None] = general_cfg.get("metadata", {}) @router.get( @@ -30,9 +38,12 @@ class Metadata(pydantic.BaseModel): def get_metadata(): return Metadata( version=capellacollab.__version__, - privacy_policy_url=cfg.get("privacyPolicyURL"), - imprint_url=cfg.get("imprintURL"), - provider=cfg.get("provider"), - authentication_provider=cfg.get("authenticationProvider"), - environment=cfg.get("environment"), + privacy_policy_url=metadata_cfg.get("privacyPolicyURL"), + imprint_url=metadata_cfg.get("imprintURL"), + provider=metadata_cfg.get("provider"), + authentication_provider=metadata_cfg.get("authenticationProvider"), + environment=metadata_cfg.get("environment"), + host=general_cfg.get("host"), + port=str(general_cfg.get("port")), + protocol=general_cfg.get("scheme"), ) diff --git a/backend/config/config_template.yaml b/backend/config/config_template.yaml index 9a7ee18da6..c6d356a45d 100644 --- a/backend/config/config_template.yaml +++ b/backend/config/config_template.yaml @@ -33,7 +33,7 @@ k8s: general: host: localhost - port: 4200 + port: 8000 scheme: http wildcardHost: False diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index f5fb2ed36f..4095a67b2a 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -70,7 +70,12 @@ module.exports = { parser: "@angular-eslint/template-parser", rules: { "tailwindcss/classnames-order": "off", - "tailwindcss/no-custom-classname": "error", + "tailwindcss/no-custom-classname": [ + "error", + { + whitelist: ["language-python"], + }, + ], "tailwindcss/enforces-negative-arbitrary-values": "error", "tailwindcss/enforces-shorthand": "error", "tailwindcss/no-contradicting-classname": "error", diff --git a/frontend/angular.json b/frontend/angular.json index ca7adad156..5bb481bd1e 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -33,7 +33,8 @@ "styles": [ "src/custom-theme.scss", "src/styles.css", - "node_modules/ngx-toastr/toastr.css" + "node_modules/ngx-toastr/toastr.css", + "node_modules/highlight.js/styles/atom-one-dark.css" ], "optimization": { "fonts": false diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8f4dd38a36..acfd0d7aee 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "@types/semver": "^7.5.6", "buffer": "^6.0.3", "file-saver": "^2.0.5", + "highlight.js": "^11.9.0", "http-status-codes": "^2.3.0", "ngx-cookie": "^6.0.1", "ngx-skeleton-loader": "^8.1.0", @@ -776,45 +777,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.3.tgz", - "integrity": "sha512-SOngD3rKnwZWhhUV68AYlH8M3LRGvF69jnDrYKwtRy1ESqSH7tt+1vexGC290gKvqH7bNMgYv8f5BS1AASRfzw==", - "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular-eslint/builder": { "version": "17.1.1", "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-17.1.1.tgz", @@ -977,48 +939,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1700.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.3.tgz", - "integrity": "sha512-HUjx7vD16paWXHKHYc2LsSn/kaYbFr2YNnlzuSr9C0kauKS1e7sRpRvtGwQzXfohzgyKi81AAU5uA2KLRGq83w==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "17.0.3", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.3.tgz", - "integrity": "sha512-SOngD3rKnwZWhhUV68AYlH8M3LRGvF69jnDrYKwtRy1ESqSH7tt+1vexGC290gKvqH7bNMgYv8f5BS1AASRfzw==", - "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular/cli/node_modules/@npmcli/git": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.3.tgz", @@ -1388,18 +1308,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@angular/cli/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular/cli/node_modules/read-package-json": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", @@ -5474,45 +5382,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.3.tgz", - "integrity": "sha512-SOngD3rKnwZWhhUV68AYlH8M3LRGvF69jnDrYKwtRy1ESqSH7tt+1vexGC290gKvqH7bNMgYv8f5BS1AASRfzw==", - "dev": true, - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "picomatch": "3.0.1", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@sigstore/bundle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.0.0.tgz", @@ -11272,6 +11141,14 @@ "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", "dev": true }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hosted-git-info": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", @@ -16117,9 +15994,6 @@ "version": "10.1.0", "inBundle": true, "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, "engines": { "node": "14 || >=16.14" } diff --git a/frontend/package.json b/frontend/package.json index 742f261484..8227c4c4bc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ "@types/semver": "^7.5.6", "buffer": "^6.0.3", "file-saver": "^2.0.5", + "highlight.js": "^11.9.0", "http-status-codes": "^2.3.0", "ngx-cookie": "^6.0.1", "ngx-skeleton-loader": "^8.1.0", diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index ecb909b4cd..b5dfd3f875 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -46,6 +46,10 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { CookieModule } from 'ngx-cookie'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { ToastrModule } from 'ngx-toastr'; +import { + HighlightPipeTransform, + ModelDiagramCodeBlockComponent, +} from 'src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component'; import { BasicAuthTokenComponent } from 'src/app/users/basic-auth-token/basic-auth-token.component'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -182,6 +186,7 @@ import { UsersProfileComponent } from './users/users-profile/users-profile.compo FormFieldSkeletonLoaderComponent, GitSettingsComponent, HeaderComponent, + HighlightPipeTransform, InitModelComponent, InputDialogComponent, JobRunOverviewComponent, @@ -198,6 +203,7 @@ import { UsersProfileComponent } from './users/users-profile/users-profile.compo ModelComplexityBadgeComponent, ModelDescriptionComponent, ModelDetailComponent, + ModelDiagramCodeBlockComponent, ModelDiagramDialogComponent, ModelDiagramPreviewDialogComponent, ModelOverviewComponent, diff --git a/frontend/src/app/general/metadata/metadata.service.ts b/frontend/src/app/general/metadata/metadata.service.ts index 6f78699a56..61d08dbc96 100644 --- a/frontend/src/app/general/metadata/metadata.service.ts +++ b/frontend/src/app/general/metadata/metadata.service.ts @@ -108,4 +108,8 @@ export interface BackendMetadata { provider: string; authentication_provider: string; environment: string; + + host: string; + port: string; + protocol: string; } diff --git a/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.html b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.html new file mode 100644 index 0000000000..0e30a8becd --- /dev/null +++ b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.html @@ -0,0 +1,52 @@ + + + + + + Learn how to use the diagram cache with capellambse! + + +
+ You can also access the diagrams via our Python library + capellambse. Follow the installation instructions on + Githubopen_in_new + + and then use the code snippet. To authenticate, you have to insert a + personal access token. The token has the same scope as your user. Be careful + with it. You can revoke the token in the settings. +
+
+
+ + +
+
diff --git a/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.ts b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.ts new file mode 100644 index 0000000000..02e865fd97 --- /dev/null +++ b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component.ts @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + AfterViewInit, + Component, + Input, + Pipe, + PipeTransform, +} from '@angular/core'; +import hljs from 'highlight.js'; +import { + BackendMetadata, + MetadataService, +} from 'src/app/general/metadata/metadata.service'; +import { + Model, + getPrimaryGitModel, +} from 'src/app/projects/models/service/model.service'; +import { Project } from 'src/app/projects/service/project.service'; +import { UserService } from 'src/app/services/user/user.service'; +import { TokenService } from 'src/app/users/basic-auth-service/basic-auth-token.service'; + +@Component({ + selector: 'app-model-diagram-code-block', + templateUrl: './model-diagram-code-block.component.html', +}) +export class ModelDiagramCodeBlockComponent implements AfterViewInit { + passwordValue?: string; + + metadata?: BackendMetadata; + + constructor( + private metadataService: MetadataService, + private userService: UserService, + private tokenService: TokenService, + ) { + this.metadataService.backendMetadata.subscribe((metadata) => { + this.metadata = metadata; + }); + } + + @Input({ required: true }) + model!: Model; + + @Input({ required: true }) + project!: Project; + + ngAfterViewInit(): void { + hljs.highlightAll(); + } + + get codeBlockContent(): string { + const basePath = `${this.metadata?.protocol}://${this.metadata?.host}:${this.metadata?.port}`; + let capellaMBSEFlags = + '"path": "",'; + + const primaryGitModel = getPrimaryGitModel(this.model); + if (primaryGitModel) { + capellaMBSEFlags = `path="git+${primaryGitModel.path}", + entrypoint="${primaryGitModel.entrypoint}", + revision="${primaryGitModel.revision}",`; + } + + if (primaryGitModel?.password) { + capellaMBSEFlags += ` + username=input("Please enter the username to access the Git repository."), + password=getpass.getpass("Please enter the password or personal access token to access the Git repository."),`; + } + + return `import capellambse +import getpass + +model = capellambse.MelodyModel( + ${capellaMBSEFlags} + diagram_cache={ + "path": "${basePath}/api/v1/projects/${this.project!.slug}/models/${ + this.model.slug + }/diagrams/%s", + "username": "${this.userService.user?.name}", + "password": "${this.passwordValue ? this.passwordValue : '**************'}", + } +)`; + } + + async insertToken() { + const expirationDate = new Date(); + expirationDate.setDate(expirationDate.getDate() + 30); + this.tokenService + .createToken( + 'Created in diagram cache dialog', + expirationDate, + 'Diagram-cache', + ) + .subscribe((token) => { + this.passwordValue = token.password; + }); + } +} + +@Pipe({ + name: 'hightlight', +}) +export class HighlightPipeTransform implements PipeTransform { + transform(value: string, language: string): string { + return hljs.highlight(language, value).value; + } +} diff --git a/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.css b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.css index e82de10f25..de79bc5b75 100644 --- a/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.css +++ b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.css @@ -92,8 +92,3 @@ button { mat-divider { margin: 10px 0; } - -pre > code > i { - white-space: pre; - font-style: normal; -} diff --git a/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.html b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.html index 407f3805a5..4c58d2cc32 100644 --- a/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.html +++ b/frontend/src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-dialog.component.html @@ -5,65 +5,20 @@

View diagrams

- Last update: +
+ Last update: {{ (diagramMetadata?.last_updated | date: "EE, dd MMM y HH:mm:ss") || "loading..." - }} + }} +
-
-
- - - - Learn how to use the diagram cache with capellambse! - - - You can also access the diagrams via our Python library - capellambse. Follow the installation instructions on - [Github] - and then use the code snippet. To authenticate, you have to insert a - personal access token. The token has the same scope as your user. Be - careful with it. -
-

-        model = capellambse.MelodyModel(
-          path="path to the aird file of the model on your machine",
-          diagram_cache={
-            "path": "{{ this.path + "/diagrams/%s"}}",
-            "username": "{{ username }}",
-            "password": "{{ passwordValue !== undefined ? passwordValue : 'You can insert your project token here using the "Insert token" button. The inserted token is valid for 30 days.' }}",
-            }
-          )
-      
-
- - -
-
-
+ +
, private dialog: MatDialog, @Inject(MAT_DIALOG_DATA) - public data: { modelSlug: string; projectSlug: string }, + public data: { model: Model; project: Project }, ) { this.modelDiagramService - .getDiagramMetadata(this.data.projectSlug, this.data.modelSlug) + .getDiagramMetadata(this.data.project.slug, this.data.model.slug) .subscribe({ next: (diagramMetadata) => { this.diagramMetadata = diagramMetadata; this.observeVisibleDiagrams(); }, - error: () => { - this.dialogRef.close(); + error: (err) => { + this.errorMessage = err.error; }, }); - - this.userService - .getCurrentUser() - .subscribe((user) => (this.username = user.name)); - this.path = this.modelService.backendURLFactory( - data.projectSlug, - data.modelSlug, - ); - } - - get codeBlockContent(): string { - return `model = capellambse.MelodyModel( - path="path to the aird file of the model on your machine", - diagram_cache={ - "path": "http://localhost:8000/api/v1/projects/coffee-machine/models/coffee-machine/diagrams/%s", - "username": "${this.username}", - "password": "${ - this.passwordValue ? this.passwordValue : 'yourPassword' - }", - } - )`; } observeVisibleDiagrams() { @@ -133,7 +102,7 @@ export class ModelDiagramDialogComponent { if (!this.diagrams[uuid]) { this.diagrams[uuid] = { loading: true, content: undefined }; this.modelDiagramService - .getDiagram(this.data.projectSlug, this.data.modelSlug, uuid) + .getDiagram(this.data.project.slug, this.data.model.slug, uuid) .subscribe({ next: (response: Blob) => { const reader = new FileReader(); @@ -180,23 +149,11 @@ export class ModelDiagramDialogComponent { downloadDiagram(uuid: string) { this.modelDiagramService - .getDiagram(this.data.projectSlug, this.data.modelSlug, uuid) + .getDiagram(this.data.project.slug, this.data.model.slug, uuid) .subscribe((response: Blob) => { saveAs(response, `${uuid}.svg`); }); } - - async insertToken() { - const expirationDate = new Date(); - expirationDate.setDate(expirationDate.getDate() + 30); - this.tokenService - .createToken( - 'Created in diagram cache dialog', - expirationDate, - 'Diagram-cache', - ) - .subscribe((token) => (this.passwordValue = token.password)); - } } interface Diagrams { diff --git a/frontend/src/app/projects/project-detail/model-overview/model-overview.component.ts b/frontend/src/app/projects/project-detail/model-overview/model-overview.component.ts index e2b46877ab..071c5220cc 100644 --- a/frontend/src/app/projects/project-detail/model-overview/model-overview.component.ts +++ b/frontend/src/app/projects/project-detail/model-overview/model-overview.component.ts @@ -85,7 +85,7 @@ export class ModelOverviewComponent implements OnInit { 'w-full', 'h-full', ], - data: { modelSlug: model.slug, projectSlug: this.project?.slug }, + data: { model: model, project: this.project }, }); } diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index ddcbbd9c49..23f8c73cca 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -15,6 +15,7 @@ module.exports = { success: "var(--success-color)", hover: "var(--hover-color)", archived: "#D1D5DB", + url: "#2563eb", }, spacing: { button: "0.5rem",