api key page layout

This commit is contained in:
MiniDigger 2021-03-14 18:16:16 +01:00
parent ecae457660
commit 9576882ac8
5 changed files with 216 additions and 86 deletions

View File

@ -0,0 +1,82 @@
<template>
<div>
<v-row>
<v-col cols="1">
<UserAvatar :username="user.name" :avatar-url="$util.avatarUrl(user.name)" :clazz="avatarClazz"></UserAvatar>
</v-col>
<v-col cols="auto">
<h1 class="d-inline">{{ user.name }}</h1>
<v-list dense flat class="d-inline-flex">
<v-list-item v-for="btn in buttons" :key="btn.name">
<v-list-item-content>
<v-btn icon :to="btn.url">
<v-icon>{{ btn.icon }}</v-icon>
</v-btn>
</v-list-item-content>
</v-list-item>
</v-list>
<div>
<v-subheader>
<template v-if="user.tagline">{{ user.tagline }}</template>
<!-- TODO tagline edit -->
<!--<template v-else-if="u.isCurrent() || canEditOrgSettings(u, o, so)">{{ $t('author.addTagline') }}</template>-->
<v-btn icon>
<v-icon>mdi-pencil</v-icon>
</v-btn>
</v-subheader>
</div>
</v-col>
<v-spacer />
<v-col cols="2">
<v-subheader>{{ $t('author.numProjects', [user.projectCount]) }}</v-subheader>
<v-subheader>{{ $t('author.memberSince', [$util.prettyDate(user.joinDate)]) }}</v-subheader>
<a :href="$util.forumUrl(user.name)">{{ $t('author.viewForums') }}<v-icon>mdi-open-in-new</v-icon></a>
</v-col>
</v-row>
<v-divider />
<v-row>
<slot></slot>
</v-row>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator';
import { Prop } from 'vue-property-decorator';
import { HangarUser } from 'hangar-internal';
import UserAvatar from '~/components/UserAvatar.vue';
interface Button {
icon: String;
action?: Function;
url: String;
name: String;
}
@Component({
components: { UserAvatar },
})
export default class UserProfile extends Vue {
@Prop()
user!: HangarUser;
get avatarClazz(): String {
return 'user-avatar-md';
// todo check org an add 'organization-avatar'
}
get buttons(): Button[] {
const buttons = [] as Button[];
// TODO user admin
buttons.push({ icon: 'mdi-cog', url: '', name: 'Settings' });
buttons.push({ icon: 'mdi-lock-open-outline', url: '', name: 'Lock Account' });
buttons.push({ icon: 'mdi-lock-outline', url: '', name: 'Unlock Account' });
buttons.push({ icon: 'mdi-key', url: '/' + this.user.name + '/settings/api-keys', name: 'API Keys' });
buttons.push({ icon: 'mdi-calendar', url: '', name: 'Activity' });
buttons.push({ icon: 'mdi-wrench', url: '', name: 'User Admin' });
return buttons;
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -471,6 +471,18 @@ const msgs: LocaleMessageObject = {
lastUpdate: 'Last Update: {0}',
},
},
apiKeys: {
createNew: 'Create new key',
existing: 'Existing keys',
name: 'Name',
key: 'Key',
keyIdentifier: 'Key Identifier',
permissions: 'Permissions',
delete: 'Delete',
deleteKey: 'Delete Key',
createKey: 'Create key',
noKeys: 'There are no api keys yet. You can create one on the right side',
},
message: 'Good morning!',
};

View File

@ -1,59 +1,23 @@
<template>
<div>
<v-row>
<v-col cols="1">
<UserAvatar :username="user.name" :avatar-url="$util.avatarUrl(user.name)" :clazz="avatarClazz"></UserAvatar>
</v-col>
<v-col cols="auto">
<h1 class="d-inline">{{ user.name }}</h1>
<v-list dense flat class="d-inline-flex">
<v-list-item v-for="btn in buttons" :key="btn.name">
<v-list-item-content>
<v-btn icon
><v-icon>{{ btn.icon }}</v-icon></v-btn
>
</v-list-item-content>
</v-list-item>
</v-list>
<div>
<v-subheader>
<template v-if="user.tagline">{{ user.tagline }}</template>
<!-- TODO tagline edit -->
<!--<template v-else-if="u.isCurrent() || canEditOrgSettings(u, o, so)">{{ $t('author.addTagline') }}</template>-->
<v-btn icon>
<v-icon>mdi-pencil</v-icon>
</v-btn>
</v-subheader>
</div>
</v-col>
<v-spacer />
<v-col cols="2">
<v-subheader>{{ $t('author.numProjects', [user.projectCount]) }}</v-subheader>
<v-subheader>{{ $t('author.memberSince', [$util.prettyDate(user.joinDate)]) }}</v-subheader>
<a :href="$util.forumUrl(user.name)">{{ $t('author.viewForums') }}<v-icon>mdi-open-in-new</v-icon></a>
</v-col>
</v-row>
<v-divider />
<v-row>
<v-col cols="12" md="8">
<ProjectList :projects="projects" />
</v-col>
<v-col cols="12" md="4">
<v-list dense>
<v-subheader>{{ $t('author.orgs') }}</v-subheader>
<!-- todo display orgs -->
</v-list>
<v-list dense>
<v-subheader>{{ $t('author.stars') }}</v-subheader>
<!-- todo display stars -->
</v-list>
<v-list dense>
<v-subheader>{{ $t('author.watching') }}</v-subheader>
<!-- todo watching -->
</v-list>
</v-col>
</v-row>
</div>
<UserProfile :user="user">
<v-col cols="12" md="8">
<ProjectList :projects="projects" />
</v-col>
<v-col cols="12" md="4">
<v-list dense>
<v-subheader>{{ $t('author.orgs') }}</v-subheader>
<!-- todo display orgs -->
</v-list>
<v-list dense>
<v-subheader>{{ $t('author.stars') }}</v-subheader>
<!-- todo display stars -->
</v-list>
<v-list dense>
<v-subheader>{{ $t('author.watching') }}</v-subheader>
<!-- todo watching -->
</v-list>
</v-col>
</UserProfile>
</template>
<script lang="ts">
@ -63,16 +27,11 @@ import { Context } from '@nuxt/types';
import { HangarUser } from 'hangar-internal';
import UserAvatar from '~/components/UserAvatar.vue';
import ProjectList from '~/components/projects/ProjectList.vue';
interface Button {
icon: String;
action?: Function;
url: String;
name: String;
}
import UserProfile from '~/components/layouts/UserProfile.vue';
@Component({
components: {
UserProfile,
UserAvatar,
ProjectList,
},
@ -88,23 +47,6 @@ export default class AuthorPage extends Vue {
};
}
get avatarClazz(): String {
return 'user-avatar-md';
// todo check org an add 'organization-avatar'
}
get buttons(): Button[] {
const buttons = [] as Button[];
// TODO user admin
buttons.push({ icon: 'mdi-cog', url: '', name: 'Settings' });
buttons.push({ icon: 'mdi-lock-open-outline', url: '', name: 'Lock Account' });
buttons.push({ icon: 'mdi-lock-outline', url: '', name: 'Unlock Account' });
buttons.push({ icon: 'mdi-key', url: '', name: 'API Keys' });
buttons.push({ icon: 'mdi-calendar', url: '', name: 'Activity' });
buttons.push({ icon: 'mdi-wrench', url: '', name: 'User Admin' });
return buttons;
}
async asyncData({ $api, route, $util }: Context) {
const user = await $api.requestInternal<HangarUser>(`users/${route.params.author}`, false).catch($util.handlePageRequestError);
const projects = await $api

View File

@ -1,13 +1,101 @@
<template>
<div>{{ $nuxt.$route.name }}</div>
<UserProfile :user="user">
<v-col cols="12" md="6">
<h2>{{ $t('apiKeys.createNew') }}</h2>
<v-row>
<v-col v-for="perm in perms" :key="perm.frontendName" cols="6">
<v-checkbox v-model="selectedPerms" :label="perm.frontendName" :value="perm.value"> </v-checkbox>
</v-col>
</v-row>
<v-text-field v-model="name" :label="$t('apiKeys.name')" type="text">
<template #append-outer>
<v-btn @click="create">{{ $t('apiKeys.createKey') }}</v-btn>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="6">
<h2>{{ $t('apiKeys.existing') }}</h2>
<v-simple-table>
<template #default>
<thead>
<tr>
<th class="text-left">{{ $t('apiKeys.name') }}</th>
<th class="text-left">{{ $t('apiKeys.key') }}</th>
<th class="text-left">{{ $t('apiKeys.keyIdentifier') }}</th>
<th class="text-left">{{ $t('apiKeys.permissions') }}</th>
<th class="text-left">{{ $t('apiKeys.delete') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="key in apiKeys" :key="key.name">
<td>{{ key.name }}</td>
<td>{{ key.key }}</td>
<td>{{ key.identifier }}</td>
<td>{{ key.permissions }}</td>
<td>
<v-btn color="red" @click="deleteKey(key.key)">{{ $t('apiKeys.deleteKey') }}</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
<v-alert v-if="apiKeys.length === 0" type="info">{{ $t('apiKeys.noKeys') }}</v-alert>
</v-col>
</UserProfile>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator';
import { HangarUser } from 'hangar-internal';
import { Context } from '@nuxt/types';
import { ApiKey, IPermission } from 'hangar-api';
import UserProfile from '~/components/layouts/UserProfile.vue';
import { RootState } from '~/store';
import { NamedPermission } from '~/types/enums';
// TODO implement AuthorSettingsApiKeysPage (I think this should just be a modal in the user page - Jake)
@Component
export default class AuthorSettingsApiKeysPage extends Vue {}
@Component({
components: { UserProfile },
})
export default class AuthorSettingsApiKeysPage extends Vue {
user!: HangarUser;
selectedPerms: NamedPermission[] = [];
name: string = '';
// TODO load from server
apiKeys: ApiKey[] = [];
get perms(): IPermission[] {
return Array.from((this.$store.state as RootState).permissions.values());
}
async asyncData({ $api, route, $util }: Context) {
const user = await $api.requestInternal<HangarUser>(`users/${route.params.author}`, false).catch($util.handlePageRequestError);
return { user };
}
create() {
// TODO send to server
this.apiKeys.push({ key: 'blah', identifier: 'blub', permissions: this.selectedPerms, name: this.name, createdAt: new Date().toString() });
this.name = '';
this.selectedPerms = [];
}
deleteKey(key: string) {
// todo send to server
this.apiKeys = this.apiKeys.filter((e) => e.key !== key);
}
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.v-input--checkbox {
margin-top: 0;
}
.v-messages {
min-height: 0;
}
.col {
padding-top: 0;
padding-bottom: 0;
}
</style>

View File

@ -1,6 +1,6 @@
declare module 'hangar-api' {
import { Color, Model, Named } from 'hangar-api';
import { RoleCategory } from '~/types/enums';
import { NamedPermission, RoleCategory } from '~/types/enums';
interface Role {
value: string;
@ -17,4 +17,10 @@ declare module 'hangar-api' {
roles: Role[];
projectCount: number;
}
interface ApiKey extends Model, Named {
key: string;
identifier: string;
permissions: NamedPermission[];
}
}