diff --git a/Makefile b/Makefile index 3816fc6b3..24c56ac1c 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,6 @@ rollout: backend frontend undeploy: helm uninstall --kube-context k3d-$(CLUSTER_NAME) --namespace $(NAMESPACE) $(RELEASE) kubectl --context k3d-$(CLUSTER_NAME) delete --all deployments -n $(SESSION_NAMESPACE) - rm -f .provision-guacamole create-cluster: type k3d || { echo "K3D is not installed, install k3d and run 'make create-cluster' again"; exit 1; } @@ -132,7 +131,6 @@ create-cluster: delete-cluster: k3d cluster list $(CLUSTER_NAME) 2>&- && k3d cluster delete $(CLUSTER_NAME) - rm -f .provision-guacamole wait: @echo "-----------------------------------------------------------" @@ -146,8 +144,7 @@ wait: kubectl wait --for=condition=Ready pods --timeout=5m --context k3d-$(CLUSTER_NAME) -n $(NAMESPACE) -l id=$(RELEASE)-deployment-guacamole-guacamole kubectl exec --context k3d-$(CLUSTER_NAME) --namespace $(NAMESPACE) $$(kubectl get pod --namespace $(NAMESPACE) -l id=$(RELEASE)-deployment-guacamole-guacamole --no-headers | cut -f1 -d' ') -- /opt/guacamole/bin/initdb.sh --postgres | \ kubectl exec -ti --context k3d-$(CLUSTER_NAME) --namespace $(NAMESPACE) $$(kubectl get pod --namespace $(NAMESPACE) -l id=$(RELEASE)-deployment-guacamole-postgres --no-headers | cut -f1 -d' ') -- psql -U guacamole guacamole && \ - echo "Guacamole database initialized sucessfully."; \ - touch .provision-guacamole + echo "Guacamole database initialized sucessfully."; # Execute with `make -j3 dev` dev: dev-oauth-mock dev-frontend dev-backend diff --git a/backend/t4cclient/extensions/modelsources/git/core.py b/backend/t4cclient/extensions/modelsources/git/core.py new file mode 100644 index 000000000..ead872a46 --- /dev/null +++ b/backend/t4cclient/extensions/modelsources/git/core.py @@ -0,0 +1,37 @@ +# Copyright DB Netz AG and the capella-collab-manager contributors +# SPDX-License-Identifier: Apache-2.0 + +import collections.abc as cabc +import logging +import subprocess + +from fastapi import HTTPException + +log = logging.getLogger(__name__) + + +def ls_remote(url: str, env: cabc.Mapping[str, str]) -> list[str]: + try: + proc = subprocess.run( + ["git", "ls-remote", url], capture_output=True, check=True, env=env + ) + except subprocess.CalledProcessError as e: + log.debug( + { + "msg": "Exit code 128 during cloning of the repository " + url, + "stdout": e.stdout, + "stderr": e.stderr, + "exitcode": e.returncode, + } + ) + if e.returncode == 128: + raise HTTPException( + status_code=500, + detail={ + "err_code": "no_git_model_credentials", + "reason": "There was an error accessing the model. Please ask your project lead for more information. In most cases, the credentials need to be updated.", + }, + ) + else: + raise e + return proc.stdout.decode().strip().splitlines() diff --git a/backend/t4cclient/extensions/modelsources/git/routes.py b/backend/t4cclient/extensions/modelsources/git/routes.py index 4e074dc0e..81494d994 100644 --- a/backend/t4cclient/extensions/modelsources/git/routes.py +++ b/backend/t4cclient/extensions/modelsources/git/routes.py @@ -2,26 +2,30 @@ # SPDX-License-Identifier: Apache-2.0 import base64 +import logging +import os import typing as t -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from requests import Session +from .core import ls_remote from t4cclient.core.authentication.database import verify_repository_role from t4cclient.core.authentication.database.git_models import verify_gitmodel_permission from t4cclient.core.authentication.jwt_bearer import JWTBearer from t4cclient.core.database import get_db from t4cclient.core.oauth.responses import AUTHENTICATION_RESPONSES from t4cclient.extensions.modelsources import git +from t4cclient.extensions.modelsources.git.crud import get_primary_model_of_repository from t4cclient.extensions.modelsources.git.models import ( GetRepositoryGitModel, PatchRepositoryGitModel, PostGitModel, RepositoryGitInnerModel, - RepositoryGitModel, ) router = APIRouter() +log = logging.getLogger(__name__) @router.get( @@ -105,3 +109,44 @@ def patch_model( model=RepositoryGitInnerModel(**db_model.__dict__), ) return None + + +@router.get( + "/primary/revisions", tags=["Repositories"], responses=AUTHENTICATION_RESPONSES +) +def get_revisions( + project: str, db: Session = Depends(get_db), token=Depends(JWTBearer()) +): + remote_refs: dict[str, list[str]] = {"branches": [], "tags": []} + + git_model = get_primary_model_of_repository(db, project) + if not git_model: + raise HTTPException( + status_code=500, + detail={ + "err_code": "no_git_model", + "reason": "No git model is assigned to your project. Please ask a project lead to assign a git model.", + }, + ) + + url = git_model.path + log.debug("Fetch revisions of git-model '%s' with url '%s'", git_model.name, url) + + git_env = os.environ.copy() + git_env["GIT_USERNAME"] = git_model.username or "" + git_env["GIT_PASSWORD"] = git_model.password or "" + for ref in ls_remote(url, git_env): + (_, ref) = ref.split("\t") + if "^" in ref: + continue + if ref.startswith("refs/heads/"): + remote_refs["branches"].append(ref[len("refs/heads/") :]) + elif ref.startswith("refs/tags/"): + remote_refs["tags"].append(ref[len("refs/tags/") :]) + + remote_refs["default"] = git_model.revision + + log.debug("Determined branches: %s", remote_refs["branches"]) + log.debug("Determined tags: %s", remote_refs["tags"]) + log.debug("Determined default branch: %s", remote_refs["default"]) + return remote_refs diff --git a/backend/t4cclient/routes/repositories/__init__.py b/backend/t4cclient/routes/repositories/__init__.py index 495c58c9a..dccc15999 100644 --- a/backend/t4cclient/routes/repositories/__init__.py +++ b/backend/t4cclient/routes/repositories/__init__.py @@ -1,12 +1,13 @@ # Copyright DB Netz AG and the capella-collab-manager contributors # SPDX-License-Identifier: Apache-2.0 +import collections.abc as cabc import importlib import logging import typing as t from importlib import metadata -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from requests import Session import t4cclient.core.services.repositories as repository_service @@ -21,6 +22,7 @@ from t4cclient.core.database import get_db, repositories from t4cclient.core.database import users as database_users from t4cclient.core.oauth.responses import AUTHENTICATION_RESPONSES +from t4cclient.extensions.modelsources.git.crud import get_primary_model_of_repository from t4cclient.extensions.modelsources.t4c import connection from t4cclient.schemas.repositories import ( GetRepositoryUserResponse, diff --git a/backend/t4cclient/sessions/operators/k8s.py b/backend/t4cclient/sessions/operators/k8s.py index e6fb6ea9d..6d835e229 100644 --- a/backend/t4cclient/sessions/operators/k8s.py +++ b/backend/t4cclient/sessions/operators/k8s.py @@ -128,6 +128,7 @@ def start_readonly_session( entrypoint: str, git_username: str, git_password: str, + git_depth: int, ) -> t.Dict[str, t.Any]: id = self._generate_id() @@ -140,6 +141,7 @@ def start_readonly_session( "GIT_URL": git_url, "GIT_REVISION": git_revision, "GIT_ENTRYPOINT": entrypoint, + "GIT_DEPTH": git_depth, "RMT_PASSWORD": password, }, ) diff --git a/backend/t4cclient/sessions/routes.py b/backend/t4cclient/sessions/routes.py index b7bd9f057..4446de707 100644 --- a/backend/t4cclient/sessions/routes.py +++ b/backend/t4cclient/sessions/routes.py @@ -30,6 +30,7 @@ from t4cclient.sessions.operators import OPERATOR from t4cclient.sessions.schema import ( AdvancedSessionResponse, + DepthType, GetSessionsResponse, GetSessionUsageResponse, GuacamoleAuthentication, @@ -68,7 +69,7 @@ def get_current_sessions(db: Session = Depends(get_db), token=Depends(JWTBearer( ] ] ) - ) + ), ) @@ -78,7 +79,6 @@ def get_current_sessions(db: Session = Depends(get_db), token=Depends(JWTBearer( def request_session( body: PostSessionRequest, db: Session = Depends(get_db), token=Depends(JWTBearer()) ): - rdp_password = generate_password(length=64) owner = get_username(token) @@ -139,13 +139,28 @@ def request_session( "reason": "The Model has no connected Git Model. Please contact a project manager or admininistrator", }, ) + + revision = body.branch or git_model.revision + if body.depth == DepthType.LatestCommit: + depth = 1 + elif body.depth == DepthType.CompleteHistory: + depth = 0 + else: + raise HTTPException( + status_code=400, + detail={ + "err_code": "wrong_depth_format", + "reason": f"Depth type {depth} is not allowed.", + }, + ) session = OPERATOR.start_readonly_session( password=rdp_password, git_url=git_model.path, - git_revision=git_model.revision, + git_revision=revision, entrypoint=git_model.entrypoint, git_username=git_model.username, git_password=git_model.password, + git_depth=depth, ) guacamole_identifier = guacamole.create_connection( @@ -159,13 +174,17 @@ def request_session( guacamole_token, guacamole_username, guacamole_identifier ) + body_dict = body.dict() + del body_dict["branch"] + del body_dict["depth"] + database_model = DatabaseSession( guacamole_username=guacamole_username, guacamole_password=guacamole_password, rdp_password=rdp_password, guacamole_connection_id=guacamole_identifier, owner_name=owner, - **body.dict(), + **body_dict, **session, ) response = database.create_session(db=db, session=database_model).__dict__ diff --git a/backend/t4cclient/sessions/schema.py b/backend/t4cclient/sessions/schema.py index acf3a3b30..07d1632d4 100644 --- a/backend/t4cclient/sessions/schema.py +++ b/backend/t4cclient/sessions/schema.py @@ -23,6 +23,11 @@ class WorkspaceType(enum.Enum): READONLY = "readonly" +class DepthType(enum.Enum): + LatestCommit = "LatestCommit" + CompleteHistory = "CompleteHistory" + + class GetSessionsResponse(BaseModel): id: str type: WorkspaceType @@ -58,6 +63,8 @@ class Config: class PostSessionRequest(BaseModel): type: WorkspaceType + branch: str + depth: DepthType repository: t.Optional[str] class Config: diff --git a/capella-dockerimages b/capella-dockerimages index be0daeeb8..91a056775 160000 --- a/capella-dockerimages +++ b/capella-dockerimages @@ -1 +1 @@ -Subproject commit be0daeeb86cbfe6fc08a3f987bccd1f3492d0fb4 +Subproject commit 91a05677505a273c8d52226dcec556a10aefb914 diff --git a/docs/user/docs/sessions/request.md b/docs/user/docs/sessions/request.md index 773783a34..be329a1d2 100644 --- a/docs/user/docs/sessions/request.md +++ b/docs/user/docs/sessions/request.md @@ -1,15 +1,20 @@ Please follow these steps to request a session: -1. Navigate to the `Workspaces` tab inside the webapplication. -2. Please select your preferred worspace type. +1. Navigate to the `Workspaces` tab inside the web application. +1. Please select your preferred worspace type. !!! Question "Which type should I use?" Please have a look at [Session Types](types.md) -3. Click the `Request session` button. -4. You should see the state of your request. It can take up to 2 minutes, until we can assign the required resources. In most of the cases this takes under 30 seconds. -5. You should now see a success message. Please click on `Connect to Session`. -6. The session should appear in a new tab and Capella starts automatically. -7. Please follow the instructions (depends on the workflow of your project) +1. In case of read-only sessions, you have to select a project. Then, two more fields appear where you can choose the revision (i.e. branch or tag) and either the latest commit or complete commit history of the primary model of this project. Instead of choosing a single branch, it is possible to click the checkbox to mark that all branches should be downloaded. + + !!! note "" + Choosing all revisions and only the latest commit clones only the latest commits for all branches. + +1. Click the `Request session` button. +1. You should see the state of your request. It can take up to 2 minutes, until the required resources can be assigned. In most of the cases this takes under 30 seconds. +1. You should now see a success message. Please click on `Connect to Session`. +1. The session should appear in a new tab and Capella starts automatically. +1. Please follow the instructions (depends on the workflow of your project) 1. [Git](flows/git.md) - 2. [TeamForCapella](flows/t4c.md) + 1. [TeamForCapella](flows/t4c.md) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 28ee04373..dcd9b4a6d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,7 @@ "@angular/cli": "~14.1.1", "@angular/compiler-cli": "~14.1.1", "@types/jasmine": "~4.0.3", - "@types/node": "^18.6.3", + "@types/node": "^18.6.5", "jasmine-core": "~4.3.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.1", @@ -2805,9 +2805,10 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.6.3", - "dev": true, - "license": "MIT" + "version": "18.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz", + "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==", + "dev": true }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -12542,7 +12543,9 @@ "dev": true }, "@types/node": { - "version": "18.6.3", + "version": "18.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz", + "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index c850baad3..43418fe0f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@angular/cli": "~14.1.1", "@angular/compiler-cli": "~14.1.1", "@types/jasmine": "~4.0.3", - "@types/node": "^18.6.3", + "@types/node": "^18.6.5", "jasmine-core": "~4.3.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.1", diff --git a/frontend/src/app/auth/http-interceptor/auth.interceptor.ts b/frontend/src/app/auth/http-interceptor/auth.interceptor.ts index f2468f352..fffaa221b 100644 --- a/frontend/src/app/auth/http-interceptor/auth.interceptor.ts +++ b/frontend/src/app/auth/http-interceptor/auth.interceptor.ts @@ -1,23 +1,22 @@ // Copyright DB Netz AG and the capella-collab-manager contributors // SPDX-License-Identifier: Apache-2.0 -import { Injectable } from '@angular/core'; import { - HttpRequest, - HttpHandler, HttpEvent, + HttpHandler, HttpInterceptor, - HttpHeaders, + HttpRequest, } from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; -import { LocalStorageService } from '../local-storage/local-storage.service'; -import { catchError, first, map, tap, switchMap } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError, first, map, switchMap } from 'rxjs/operators'; import { AuthService, RefreshTokenResponse, } from 'src/app/services/auth/auth.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { LocalStorageService } from '../local-storage/local-storage.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { diff --git a/frontend/src/app/home/request-session/request-session.component.css b/frontend/src/app/home/request-session/request-session.component.css index 2e34a691b..6f31894cb 100644 --- a/frontend/src/app/home/request-session/request-session.component.css +++ b/frontend/src/app/home/request-session/request-session.component.css @@ -3,15 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -mat-spinner { +.big-spinner { margin-top: 70px; margin-left: auto; margin-right: auto; } -.center { - margin-left: auto; - margin-right: auto; +.small-spinner { + zoom: 0.3; + margin-bottom: 40px; } mat-card { @@ -51,6 +51,12 @@ mat-card { margin-bottom: 20px; } +.checkbox { + position: relative; + right: 20px; + bottom: 10px; +} + .center { text-align: center; } diff --git a/frontend/src/app/home/request-session/request-session.component.html b/frontend/src/app/home/request-session/request-session.component.html index 88eb83961..ed996e9b8 100644 --- a/frontend/src/app/home/request-session/request-session.component.html +++ b/frontend/src/app/home/request-session/request-session.component.html @@ -62,6 +62,7 @@
+
@@ -73,13 +74,63 @@ {{ repository.repository_name }} + >{{ repository.repository_name }} + - Please select a repository!Please select a repository!
+ + + +
+ + Download all branches + + +
+ + Branch, tag or revision + + + + {{ option }} + + + + + {{ option }} + + + + Please select a branch or tag!
+ + + Commits history depth + + {{ depth }} + + + Please select a history depth! + + +
+
+
-
+ +
Request Session keyboard_arrow_right @@ -97,7 +154,7 @@
- + = ['Latest commit', 'Complete history']; + isTag: boolean = false; + repositoryFormGroup = new FormGroup( { workspaceSwitch: new FormControl(true), @@ -35,10 +49,26 @@ export class RequestSessionComponent implements OnInit { this.validateForm() ); + referenceDepthFormGroup = new FormGroup( + { + reference: new FormControl(''), + historyDepth: new FormControl(this.history[0]), + }, + Validators.required + ); + get repository(): FormControl { return this.repositoryFormGroup.get('repository') as FormControl; } + get reference(): FormControl { + return this.referenceDepthFormGroup.get('reference') as FormControl; + } + + get historyDepth(): FormControl { + return this.referenceDepthFormGroup.get('historyDepth') as FormControl; + } + get workspaceSwitch(): FormControl { return this.repositoryFormGroup.get('workspaceSwitch') as FormControl; } @@ -46,6 +76,9 @@ export class RequestSessionComponent implements OnInit { @Input() repositories: Array = []; + chosenRepository: string = ''; + allBranches: boolean = false; + warnings: Array = []; permissions: any = {}; @@ -55,9 +88,15 @@ export class RequestSessionComponent implements OnInit { persistentWorkspaceHelpIsOpen = false; cleanWorkspaceHelpIsOpen = false; + tags: Array = []; + branches: Array = []; + constructor( public sessionService: SessionService, - private repoUserService: RepositoryUserService + private repoUserService: RepositoryUserService, + private repoService: RepositoryService, + private gitModelService: GitModelService, + private snackBar: MatSnackBar ) {} ngOnInit(): void {} @@ -77,7 +116,10 @@ export class RequestSessionComponent implements OnInit { } requestSession() { - if (this.repositoryFormGroup.valid) { + if ( + this.repositoryFormGroup.valid && + (!this.workspaceSwitch.value || this.referenceDepthFormGroup.valid) + ) { this.showSpinner = true; this.creationSuccessful = false; let type: 'readonly' | 'persistent' = 'readonly'; @@ -86,8 +128,20 @@ export class RequestSessionComponent implements OnInit { } else { type = 'persistent'; } + if ( + this.historyDepth.value == 'Latest commit' || + this.tags.includes(this.reference.value) + ) { + var depth = DepthType.LatestCommit; + } else { + var depth = DepthType.CompleteHistory; + } + var reference = this.reference.value; + if (this.allBranches) { + reference = ''; + } this.sessionService - .createNewSession(type, this.repository.value) + .createNewSession(type, this.repository.value, reference, depth) .subscribe( (res) => { this.session = res; @@ -102,10 +156,22 @@ export class RequestSessionComponent implements OnInit { } setPermissionsAndWarningsByName(event: MatSelectChange): void { + this.showSmallSpinner = true; + if (this.chosenRepository) this.chosenRepository = ''; + this.permissions = {}; this.warnings = []; for (let repo of this.repositories) { if (repo.repository_name == event.value) { + if (repo.warnings.includes('NO_GIT_MODEL_DEFINED')) { + this.snackBar.open( + 'This project has no assigned read-only model and therefore, a readonly-session cannot be created. Please contact your project lead.', + 'Ok!' + ); + this.showSmallSpinner = false; + } else { + this.getRevisions(repo.repository_name); + } for (let permission of repo.permissions) { this.permissions[permission] = this.repoUserService.PERMISSIONS[permission]; @@ -116,4 +182,37 @@ export class RequestSessionComponent implements OnInit { } this.permissions = {}; } + + getRevisions(repository_name: string) { + this.showSmallSpinner = true; + this.gitModelService.getRevisions(repository_name).subscribe({ + next: (revisions: Revisions) => { + this.branches = revisions.branches; + this.tags = revisions.tags; + this.referenceDepthFormGroup.controls['reference'].setValue( + revisions.default + ); + this.chosenRepository = repository_name; + }, + error: () => { + this.showSmallSpinner = false; + }, + complete: () => { + this.showSmallSpinner = false; + }, + }); + } + + changeIsTag(event: MatSelectChange) { + if (this.tags.includes(event.value)) { + this.isTag = true; + } else { + this.isTag = false; + } + } + + changeAllBranches() { + this.allBranches = !this.allBranches; + this.isTag = false; + } } diff --git a/frontend/src/app/services/modelsources/git-model/git-model.service.ts b/frontend/src/app/services/modelsources/git-model/git-model.service.ts index 447bac672..33469af77 100644 --- a/frontend/src/app/services/modelsources/git-model/git-model.service.ts +++ b/frontend/src/app/services/modelsources/git-model/git-model.service.ts @@ -73,6 +73,15 @@ export class GitModelService { { primary: true } ); } + + getRevisions(project_name: string) { + return this.http.get( + environment.backend_url + + '/projects/' + + project_name + + '/extensions/modelsources/git/primary/revisions' + ); + } } export interface GitModel extends BasicGitModel { @@ -97,6 +106,8 @@ export interface CreateGitModel extends BasicGitModel { }; } -function isAnCreateGitModel(obj: any): obj is CreateGitModel { - return 'model' in obj && 'name' in obj && 'credentials' in obj; +export interface Revisions { + branches: Array; + tags: Array; + default: string; } diff --git a/frontend/src/app/services/session/session.service.ts b/frontend/src/app/services/session/session.service.ts index 6fc4fa371..b541679e0 100644 --- a/frontend/src/app/services/session/session.service.ts +++ b/frontend/src/app/services/session/session.service.ts @@ -20,11 +20,15 @@ export class SessionService { createNewSession( type: 'readonly' | 'persistent', - repository: string | undefined + repository: string | undefined, + branch: string, + depth: DepthType ): Observable { return this.http.post(this.BACKEND_URL_PREFIX, { - type, - repository, + type: type, + repository: repository, + branch: branch, + depth: DepthType[depth], }); } @@ -146,3 +150,8 @@ export interface SessionState { text: string; css: string; } + +export enum DepthType { + LatestCommit, + CompleteHistory, +} diff --git a/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.css b/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.css index 9783116c2..ed6d8fb98 100644 --- a/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.css +++ b/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.css @@ -2,7 +2,6 @@ * Copyright DB Netz AG and the capella-collab-manager contributors * SPDX-License-Identifier: Apache-2.0 */ - :host ::ng-deep .mat-progress-spinner circle, .mat-spinner circle { stroke: black; @@ -11,9 +10,8 @@ } mat-icon { - font-size: 2.5em; - background-color: white; - margin-bottom: 5px; + margin-left: 5px; + transform: scale(1.75); } .error { @@ -27,3 +25,7 @@ mat-icon { .success { background-color: var(--success-color); } + +.item { + height: 35px; +} diff --git a/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.html b/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.html index e97bc2058..445ee4e33 100644 --- a/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.html +++ b/frontend/src/app/session-creation-progress/session-progress-icon/session-progress-icon.component.html @@ -3,15 +3,15 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -check_circlecheck_circle - -access_time -error