Skip to content

Commit

Permalink
Merge branch 'feature/web-app-changes' into 'master'
Browse files Browse the repository at this point in the history
root is now admin web app, changes for keys

See merge request b650/Deep-Lynx!112
  • Loading branch information
DnOberon committed Nov 4, 2021
2 parents 838d07c + ffe6c50 commit ac6f60b
Show file tree
Hide file tree
Showing 18 changed files with 390 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ SUPERUSER_PASSWORD=admin
VUE_APP_BUNDLED_BUILD=true
VUE_APP_DEEP_LYNX_API_URL=http://localhost:8090
VUE_APP_DEEP_LYNX_API_AUTH_METHOD=token
VUE_APP_APP_URL=http://localhost:8090/gui/#
VUE_APP_APP_URL=http://localhost:8090/#
# if you are bundling the web app, you must set a unique ID in order for DeepLynx to recognize the internal admin web app
# when it attempts to authenticate against the program, we recommend at least 15 random alphanumeric characters, and not
# a recognizable name - at no point will a user see this information
Expand Down
13 changes: 13 additions & 0 deletions AdminWebApp/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ExportT,
ResultT,
FileT,
KeyPairT,
} from '@/api/types';
import {RetrieveJWT} from '@/auth/authentication_service';
import {UserT} from '@/auth/types';
Expand Down Expand Up @@ -247,6 +248,18 @@ export class Client {
return this.put<boolean>(`/containers/${containerID}/metatypes/${metatypeID}/keys/${keyID}`, key);
}

listKeyPairsForUser(): Promise<KeyPairT[]> {
return this.get<KeyPairT[]>('/users/keys');
}

generateKeyPairForUser(): Promise<KeyPairT> {
return this.post<KeyPairT>('/users/keys', undefined);
}

deleteKeyPairForUser(keyID: string): Promise<boolean> {
return this.delete(`/users/keys/${keyID}`);
}

listMetatypeRelationships(
containerID: string,
{
Expand Down
6 changes: 6 additions & 0 deletions AdminWebApp/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export type MetatypeRelationshipKeyT = {
modified_by: string;
};

export type KeyPairT = {
key: string;
secret_raw: string;
user_id: string;
};

export type FileT = {
id: string;
container_id: string;
Expand Down
56 changes: 56 additions & 0 deletions AdminWebApp/src/components/createApiKeyDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<v-dialog v-model="dialog" max-width="700px" @click:outside="errorMessage = ''; dialog = false; returnedKey = null">
<template v-slot:activator="{ on }">
<v-btn color="primary" dark class="mb-2" v-on="on">{{$t("createApiKey.createApiKey")}}</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{$t("createApiKey.formTitle")}}</span>
</v-card-title>

<v-card-text>
<error-banner :message="errorMessage"></error-banner>
<v-alert type="success" v-if="returnedKey" class="multi-line">
<p>{{$t('createApiKey.successfullyCreated')}}</p>
<p><strong>{{$t('createApiKey.key')}}</strong></p>
<p>{{returnedKey.key}}</p>
<p><strong>{{$t('createApiKey.secret')}}</strong></p>
<p>{{returnedKey.secret_raw}}</p>
</v-alert>
<v-container>
<v-row>
<v-col :cols="12">
<p>{{$t('createApiKey.description')}}</p>
</v-col>
</v-row>
</v-container>
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="dialog = false; returnedKey = null" >{{$t("createApiKey.cancel")}}</v-btn>
<v-btn color="blue darken-1" text @click="generateApiKey">{{$t("createApiKey.create")}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import {KeyPairT} from "@/api/types";
@Component
export default class CreateApiKeyDialog extends Vue {
errorMessage = ""
dialog = false
returnedKey: KeyPairT | null = null
generateApiKey() {
this.$client.generateKeyPairForUser()
.then(key => {
this.returnedKey = key
})
.catch((e) => this.errorMessage = e)
}
}
</script>
68 changes: 68 additions & 0 deletions AdminWebApp/src/components/deleteApiKeyDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<v-dialog v-model="dialog" @click:outside="dialog = false" max-width="60%">
<template v-slot:activator="{ on }">
<v-icon
v-if="icon"
small
class="mr-2"
v-on="on"
>mdi-delete</v-icon>
<v-btn v-if="!displayIcon" color="primary" dark class="mb-1" v-on="on">{{$t("deleteApiKey.deleteApiKey")}}</v-btn>
</template>

<v-card>
<v-card-text>
<v-container>
<error-banner :message="errorMessage"></error-banner>
<span class="headline">{{$t('deleteApiKey.deleteTitle')}}</span>
<v-row>
<v-col :cols="12">
<v-alert type="warning">
{{$t('deleteApiKey.deleteWarning')}}
</v-alert>
</v-col>
</v-row>
</v-container>
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="dialog = false">{{$t("deleteApiKey.cancel")}}</v-btn>
<v-btn color="red darken-1" text @click="deleteApiKey">
<span>{{$t("deleteApiKey.delete")}}</span>
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
import {KeyPairT} from "@/api/types";
@Component
export default class DeleteApiKeyDialog extends Vue {
@Prop({required: true})
readonly keyPair!: KeyPairT
@Prop({required: false, default: false})
readonly icon!: boolean
errorMessage = ""
dialog = false
get displayIcon() {
return this.icon
}
deleteApiKey() {
this.$client.deleteKeyPairForUser(this.keyPair.key)
.then(() => {
this.dialog = false
this.$emit('apiKeyDeleted')
})
.catch(e => this.errorMessage = e)
}
}
</script>
25 changes: 24 additions & 1 deletion AdminWebApp/src/pages/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
<div class="mx-3">
<v-divider class="my-4"></v-divider>
<h2 class="text-h5 pb-0" style="line-height: 1rem">Current Container</h2>
<span>{{container.name}}</span>
<p>{{container.name}}</p>
<p>{{$t('home.id')}}# {{container.id}}</p>
<v-divider class="my-4"></v-divider>
<span class="d-block">{{user.display_name}}</span>
<span class="d-block text-h6" style="line-height: .875rem">{{user.email}}</span>
Expand Down Expand Up @@ -161,6 +162,19 @@
</v-list-item>
</v-list-group>

<v-list-group :value="false" >
<template v-slot:activator>
<v-list-item-title >{{$t("home.accessManagement")}}</v-list-item-title>
</template>

<v-list-item two-line link @click="setActiveComponent('api-keys')" :input-value="currentMainComponent === 'ApiKeys'">
<v-list-item-content>
<v-list-item-title>{{$t("home.apiKeys")}}</v-list-item-title>
<v-list-item-subtitle >{{$t("home.apiKeysDescription")}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list-group>

<v-list-item link @click="containerSelect">
<v-list-item-content>
<v-list-item-title>{{$t("home.changeContainer")}}</v-list-item-title>
Expand Down Expand Up @@ -224,6 +238,7 @@ import Settings from "@/views/Settings.vue"
import Users from "@/views/Users.vue"
import ContainerUsers from "@/views/ContainerUsers.vue"
import Containers from "@/views/Containers.vue"
import ApiKeys from "@/views/ApiKeys.vue";
import LanguageSelect from "@/components/languageSelect.vue";
import ContainerSelect from "@/components/containerSelect.vue"
import {TranslateResult} from "vue-i18n";
Expand All @@ -233,6 +248,7 @@ import Config from "@/config";
@Component({components: {
ContainerSelect,
ApiKeys,
LanguageSelect,
DataImports,
Metatypes,
Expand Down Expand Up @@ -391,6 +407,13 @@ export default class Home extends Vue {
break;
}
case "api-keys": {
this.currentMainComponent = "ApiKeys"
this.componentName = this.$t('home.apiKeys')
this.$router.replace(`/containers/${this.containerID}/api-keys`)
break;
}
default : {
this.currentMainComponent = "";
break;
Expand Down
3 changes: 3 additions & 0 deletions AdminWebApp/src/pages/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
export default class Login extends Vue {
errorMessage = ""
created() {
window.location.href = this.loginURL
}
get loginURL(): string {
let state = localStorage.getItem('state')
Expand Down
27 changes: 27 additions & 0 deletions AdminWebApp/src/translations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export default {
en: {
home: {
id: 'ID',
accessManagement: 'Access Management',
apiKeys: 'API Keys',
apiKeysDescription: 'Generate and Manage API Keys',
domainSelect: 'Select Domain',
selectContainer: 'Select Container',
taxonomy: 'Taxonomy',
Expand Down Expand Up @@ -45,6 +49,18 @@ export default {
create: 'Create',
changeContainer: 'Change Container',
},
apiKeys: {
title: 'Current API Keys',
key: 'Key',
actions: 'Actions',
},
deleteApiKey: {
deleteTitle: 'Permanently Delete API Key/Secret Pair',
deleteWarning:
'Deleting this key will cause any integration which uses it to no longer be able to authenticate with Deep Lynx. Only delete a key that you know is not in use or has been compromised. Deleting a key/pair cannot be undone.',
cancel: 'Cancel',
delete: 'Delete',
},
dataQuery: {
dataQuery: 'Data Query',
results: 'Results',
Expand Down Expand Up @@ -90,6 +106,17 @@ export default {
selectContainer: 'Select Container',
selectDataSource: 'Select Data Source',
},
createApiKey: {
formTitle: 'Generate New API Key/Secret',
description:
'The API Key/Secret pair you generate here will have all the same permissions as your user. It is NOT tied to this current container and users of this key/secret pair will have access to all containers and data you have permission for. Please use these keys cautiously.',
successfullyCreated: 'WRITE DOWN YOUR SECRET - this is the only time you will be able to see it.',
key: 'Key',
secret: 'Secret',
create: 'Generate',
cancel: 'Cancel',
createApiKey: 'Generate API Key',
},
importMapping: {
importMappings: 'Import Type Mappings',
title: 'Import Type Mappings From File',
Expand Down
66 changes: 66 additions & 0 deletions AdminWebApp/src/views/ApiKeys.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<div>
<error-banner :message="errorMessage"></error-banner>
<v-data-table
:headers="headers()"
:items="keyPairs"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>{{$t('apiKeys.title')}}</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<create-api-key-dialog></create-api-key-dialog>
</v-toolbar>
</template>

<template v-slot:[`item.actions`]="{ item }">
<delete-api-key-dialog :key-pair="item" :icon="true" @apiKeyDeleted="refreshKeys()"></delete-api-key-dialog>
</template>
</v-data-table>
</div>
</template>

<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator'
import {KeyPairT} from "@/api/types";
import CreateApiKeyDialog from "@/components/createApiKeyDialog.vue";
import DeleteApiKeyDialog from "@/components/deleteApiKeyDialog.vue";
@Component({components:{CreateApiKeyDialog, DeleteApiKeyDialog}})
export default class ApiKeys extends Vue {
dialog= false
select = ""
keyPairs: KeyPairT[] = []
errorMessage = ""
headers() {
return [
{ text: this.$t('apiKeys.key'), value: 'key'},
{ text: this.$t('apiKeys.actions'), value: 'actions', sortable: false }
]
}
mounted() {
this.refreshKeys()
}
refreshKeys() {
this.$client.listKeyPairsForUser()
.then(keys => {
this.keyPairs = keys
})
.catch((e) => this.errorMessage = e)
}
copyID(id: string) {
navigator.clipboard.writeText(id)
}
}
</script>
1 change: 0 additions & 1 deletion AdminWebApp/vue.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module.exports = {
publicPath: process.env.VUE_APP_BUNDLED_BUILD === 'true' ? '/gui/' : undefined,
transpileDependencies: ['vuetify'],
devServer: {
clientLogLevel: 'info',
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"start": "npm run build:dev && node -r dotenv/config ./dist/main.js",
"migrate": "tsc && node -r dotenv/config ./dist/data_access_layer/migrate.js",
"build:dev-with-web": "npm run build:admin-web-app && tsc && copyfiles -u 1 \"./src/**/*.hbs\" ./dist && copyfiles -u 1 \"src/http_server/assets/**\" ./dist",
"build:dev": "tsc && copyfiles -u 1 \"./src/**/*.hbs\" ./dist && copyfiles -u 1 \"src/http_server/assets/**\" ./dist",
"build:dev": "npm run build:dev-with-web",
"test": "cross-env TS_NODE_FILES=true npm run test:raw",
"test:raw": " cd src/tests && cross-env-shell nyc mocha --exit -r ts-node/register -r dotenv/config --recursive \"**/*.spec.ts\" \"**/**/*.spec.ts\" \"**/**/**/*.spec.ts\" --prof ",
"prepare": "husky install",
Expand Down Expand Up @@ -119,6 +119,7 @@
"express-graphql": "^0.11.0",
"express-handlebars": "^5.3.4",
"express-validator": "^6.10.0",
"express-winston": "^4.2.0",
"fast-xml-parser": "^3.19.0",
"flat": "^5.0.2",
"fp-ts": "^2.11.3",
Expand Down
Loading

0 comments on commit ac6f60b

Please sign in to comment.