diff --git a/.env b/.env
index 9b3684855..d194a083c 100644
--- a/.env
+++ b/.env
@@ -3,6 +3,9 @@ FRONTEND_URL=http://localhost:4200
COLLABORATION_SERV_URL=http://localhost:4444
SPAN_SERV_URL=http://localhost:8083
USER_SERV_URL=http://localhost:8084
+USER_SERV_API_URL=http://localhost:8080
+SHARE_SNAPSHOT_URL=http://localhost:4200/
+GITLAB_API=http://localhost:5000
CODE_SERV_URL=http://localhost:8085
METRICS_SERV_URL=http://localhost:8086
VSCODE_SERV_URL=http://localhost:3000
diff --git a/.eslintrc.js b/.eslintrc.js
index f91e51dc8..474112ae9 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -6,7 +6,12 @@ module.exports = {
sourceType: 'module',
project: './tsconfig.json',
},
- plugins: ['@typescript-eslint', 'import', 'prettier'],
+ plugins: [
+ // 'ember',
+ 'prettier',
+ '@typescript-eslint',
+ 'import',
+ ],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
@@ -24,13 +29,18 @@ module.exports = {
auth0: false,
},
rules: {
+ 'prettier/prettier': 'error',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-inferrable-types': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{ 'ts-ignore': 'allow-with-description' },
],
- '@typescript-eslint/no-explicit-any': 'off',
- '@typescript-eslint/no-inferrable-types': 'off',
- '@typescript-eslint/no-non-null-assertion': 'off',
+ '@typescript-eslint/type-annotation-spacing': ['error'],
+ 'linebreak-style': 'off',
+ 'class-methods-use-this': 'off',
+ 'import/no-unresolved': 'off',
'@typescript-eslint/no-this-alias': [
'error',
{
@@ -38,33 +48,27 @@ module.exports = {
allowedNames: ['self'],
},
],
- '@typescript-eslint/type-annotation-spacing': ['error'],
- 'class-methods-use-this': 'off',
+ 'require-yield': 'off',
+ 'no-plusplus': 'off',
+ 'import/no-cycle': 'off',
+ 'prefer-rest-params': 'off',
'ember/no-mixins': 'off',
'ember/require-computed-property-dependencies': 'off',
- 'func-names': ['error', 'always', { generators: 'never' }],
- 'import/no-cycle': 'off',
- 'import/no-unresolved': 'off',
- 'linebreak-style': 'off',
- 'no-console': ['error', { allow: ['warn', 'error'] }],
'no-param-reassign': ['error', { props: false }],
- 'no-plusplus': 'off',
- 'prefer-rest-params': 'off',
- 'prettier/prettier': 'error',
- 'require-yield': 'off',
+ 'func-names': ['error', 'always', { generators: 'never' }],
},
overrides: [
// node files
{
files: [
- 'config/**/*.js',
'ember-cli-build.js',
- 'lib/*/index.js',
'testem.js',
+ 'config/**/*.js',
+ 'lib/*/index.js',
],
parserOptions: {
- ecmaVersion: 2015,
sourceType: 'script',
+ ecmaVersion: 2015,
},
env: {
browser: false,
diff --git a/app/components/additional-snapshot-info.hbs b/app/components/additional-snapshot-info.hbs
new file mode 100644
index 000000000..f5c18070f
--- /dev/null
+++ b/app/components/additional-snapshot-info.hbs
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/app/components/additional-snapshot-info.ts b/app/components/additional-snapshot-info.ts
new file mode 100644
index 000000000..d1206bbf7
--- /dev/null
+++ b/app/components/additional-snapshot-info.ts
@@ -0,0 +1,55 @@
+import Component from '@glimmer/component';
+import ToastHandlerService from 'explorviz-frontend/services/toast-handler';
+import Auth from 'explorviz-frontend/services/auth';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { TinySnapshot } from 'explorviz-frontend/services/snapshot-token';
+
+export default class AdditionalSnapshotInfoComponent extends Component {
+ @service('auth')
+ auth!: Auth;
+
+ @service('toast-handler')
+ toastHandlerService!: ToastHandlerService;
+
+ focusedClicks = 0;
+
+ @action
+ // eslint-disable-next-line class-methods-use-this
+ onTokenIdCopied() {
+ this.toastHandlerService.showSuccessToastMessage(
+ 'Token id copied to clipboard'
+ );
+ }
+
+ @action
+ hidePopover(event: Event) {
+ if (this.isMouseOnPopover()) {
+ return;
+ }
+
+ // Clicks enable us to differentiate between opened and closed popovers
+ if (this.focusedClicks % 2 === 1) {
+ event.target?.dispatchEvent(new Event('click'));
+ }
+ this.focusedClicks = 0;
+ }
+
+ isMouseOnPopover() {
+ const hoveredElements = document.querySelectorAll(':hover');
+
+ for (const element of hoveredElements) {
+ if (element.matches('.popover')) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @action
+ onClick(event: Event) {
+ this.focusedClicks += 1;
+ // Prevent click on table row which would trigger to open the visualization
+ event.stopPropagation();
+ }
+}
diff --git a/app/components/api-token-selection.hbs b/app/components/api-token-selection.hbs
new file mode 100644
index 000000000..bf960922b
--- /dev/null
+++ b/app/components/api-token-selection.hbs
@@ -0,0 +1,133 @@
+
+
API-Tokens
+
+
+
+
+ Name |
+ API Token |
+ Created |
+ Expires |
+ |
+
+
+
+ {{#each
+ (sort-by
+ (concat this.sortProperty ':' this.sortOrder) 'createdAt' @apiTokens
+ )
+ as |apiToken|
+ }}
+
+ {{apiToken.name}} |
+ {{apiToken.token}} |
+ {{this.formatDate apiToken.createdAt true}} |
+ {{this.formatDate apiToken.expires}} |
+
+
+ |
+
+ {{else}}
+ There are no saved API-Tokens.
+ {{/each}}
+
+
+
+
+ {{svg-jar 'plus-16' class='octicon'}}
+
+
+ |
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/components/api-token-selection.ts b/app/components/api-token-selection.ts
new file mode 100644
index 000000000..799a1762e
--- /dev/null
+++ b/app/components/api-token-selection.ts
@@ -0,0 +1,141 @@
+import Component from '@glimmer/component';
+import { inject as service } from '@ember/service';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import UserApiTokenService, {
+ ApiToken,
+} from 'explorviz-frontend/services/user-api-token';
+import { format } from 'date-fns';
+import convertDate from 'explorviz-frontend/utils/helpers/time-convter';
+
+export default class ApiTokenSelectionComponent extends Component {
+ today: string = format(new Date().getTime() + 86400 * 1000, 'yyyy-MM-dd');
+
+ @service('user-api-token')
+ userApiTokenService!: UserApiTokenService;
+
+ @tracked
+ sortProperty: keyof ApiToken = 'createdAt';
+
+ @tracked
+ sortOrder: 'asc' | 'desc' = 'desc';
+
+ @tracked
+ createToken: boolean = false;
+
+ @tracked
+ name: string = '';
+
+ @tracked
+ expDate: number | null = null;
+
+ @tracked
+ token: string = '';
+
+ @tracked
+ hostUrl: string = '';
+
+ @tracked
+ saveBtnDisabled: boolean = true;
+
+ @action
+ sortBy(property: keyof ApiToken) {
+ if (property === this.sortProperty) {
+ if (this.sortOrder === 'asc') {
+ this.sortOrder = 'desc';
+ } else {
+ this.sortOrder = 'asc';
+ }
+ } else {
+ this.sortOrder = 'asc';
+ this.sortProperty = property;
+ }
+ }
+
+ @action
+ async deleteApiToken(apiToken: ApiToken) {
+ await this.userApiTokenService.deleteApiToken(apiToken.token, apiToken.uid);
+ if (localStorage.getItem('gitAPIToken') !== null) {
+ if (localStorage.getItem('gitAPIToken') === JSON.stringify(apiToken)) {
+ localStorage.removeItem('gitAPIToken');
+ localStorage.removeItem('gitProject');
+ }
+ }
+ }
+
+ @action
+ openMenu() {
+ this.createToken = true;
+ }
+
+ @action
+ closeMenu() {
+ this.reset();
+ this.createToken = false;
+ }
+
+ @action
+ async createApiToken() {
+ this.userApiTokenService.createApiToken(
+ this.name,
+ this.token,
+ this.hostUrl,
+ this.expDate
+ );
+ this.reset();
+ }
+
+ @action
+ reset() {
+ this.name = '';
+ this.expDate = null;
+ this.token = '';
+ this.createToken = false;
+ this.hostUrl = '';
+ }
+
+ @action
+ updateName(event: InputEvent) {
+ const target: HTMLInputElement = event.target as HTMLInputElement;
+ this.name = target.value;
+ this.canSaveToken();
+ }
+
+ @action
+ updateToken(event: InputEvent) {
+ const target: HTMLInputElement = event.target as HTMLInputElement;
+ this.token = target.value;
+ this.canSaveToken();
+ }
+
+ @action
+ updateHostUrl(event: InputEvent) {
+ const target: HTMLInputElement = event.target as HTMLInputElement;
+ this.hostUrl = target.value;
+ this.canSaveToken();
+ }
+
+ @action
+ updateExpDate(event: InputEvent) {
+ const target: HTMLInputElement = event.target as HTMLInputElement;
+ const date = convertDate(target.value);
+ this.expDate = date;
+ }
+
+ @action
+ canSaveToken() {
+ if (this.token !== '' && this.name !== '' && this.hostUrl !== '') {
+ this.saveBtnDisabled = false;
+ } else {
+ this.saveBtnDisabled = true;
+ }
+ }
+
+ formatDate(date: number, showMin: boolean): string {
+ if (date === 0) {
+ return '-';
+ } else if (showMin) {
+ return format(new Date(date), 'dd/MM/yyyy, HH:mm');
+ } else return format(new Date(date), 'dd/MM/yyyy');
+ }
+}
diff --git a/app/components/delete-snapshot.hbs b/app/components/delete-snapshot.hbs
new file mode 100644
index 000000000..22875ea5f
--- /dev/null
+++ b/app/components/delete-snapshot.hbs
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/app/components/delete-snapshot.ts b/app/components/delete-snapshot.ts
new file mode 100644
index 000000000..261d94e87
--- /dev/null
+++ b/app/components/delete-snapshot.ts
@@ -0,0 +1,20 @@
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { inject as service } from '@ember/service';
+import SnapshotTokenService, {
+ TinySnapshot,
+} from 'explorviz-frontend/services/snapshot-token';
+
+export default class DeleteSnapshotComponent extends Component {
+ @service('snapshot-token')
+ snapshotService!: SnapshotTokenService;
+
+ @action
+ async deleteSnapshot(
+ snapShot: TinySnapshot,
+ isShared: boolean,
+ subscribed: boolean
+ ) {
+ this.snapshotService.deleteSnapshot(snapShot, isShared, subscribed);
+ }
+}
diff --git a/app/components/page-setup/navbar.hbs b/app/components/page-setup/navbar.hbs
index a1566d0e6..394b97654 100644
--- a/app/components/page-setup/navbar.hbs
+++ b/app/components/page-setup/navbar.hbs
@@ -76,6 +76,16 @@
{{/if}}
+
+
+