Browse Source

bult

master
Paul Scheltema 8 months ago
parent
commit
2f6b11236b
  1. 17
      README.md
  2. 17
      TODO
  3. 28
      components/app/list/item.vue
  4. 118
      components/apps/grid.vue
  5. 24
      components/apps/list.vue
  6. 13
      components/contributor/list/item.vue
  7. 76
      components/contributors/deleteButton.vue
  8. 55
      components/contributors/editForm.vue
  9. 26
      components/contributors/inviteButton.vue
  10. 143
      components/contributors/list.vue
  11. 76
      components/researchStudies/deleteButton.vue
  12. 29
      components/researchStudies/list.vue
  13. 20
      components/ui/rug/card/form.vue
  14. 14
      components/user/profile/list/item.vue
  15. 11
      config/routes.js
  16. 51
      lib/form.js
  17. 2
      lib/store.js
  18. 82
      nuxt.config.js
  19. 6
      pages/login.vue
  20. 112
      pages/studies/create.vue
  21. 4
      pages/studies/list.vue
  22. 22
      pages/studies/study/apps.vue
  23. 5
      pages/studies/study/apps/app/edit.vue
  24. 177
      pages/studies/study/apps/app/windows-vdi.vue
  25. 212
      pages/studies/study/apps/app/windows-vdi/create.vue
  26. 21
      pages/studies/study/apps/index.vue
  27. 42
      pages/studies/study/contributors.vue
  28. 83
      pages/studies/study/contributors/contributor/edit.vue
  29. 118
      pages/studies/study/contributors/contributor/invite.vue
  30. 14
      pages/studies/study/overview.vue
  31. 25
      pages/studies/study/settings.vue
  32. 5
      scripts/deploy-on-server.sh
  33. 4
      scripts/deploy.sh
  34. 10
      scripts/feature-create.sh
  35. 31
      scripts/feature-merge.sh
  36. 37
      store/apps.js
  37. 48
      store/authorisation.js
  38. 85
      store/studies.js
  39. 27
      store/virtualmachines.js

17
README.md

@ -1,5 +1,22 @@ @@ -1,5 +1,22 @@
# vre-web
## Development
```bash
# install nvm
nvm install
# use nvm
nvm use
# install node modules
yarn install
# serve with hot reload at localhost:3000
yarn dev
```
## Build Setup
```bash

17
TODO

@ -8,9 +8,6 @@ TODO: @@ -8,9 +8,6 @@ TODO:
https://lokalise.com/blog/vue-i18n/
https://alecolombo.medium.com/a-simple-multilanguage-site-with-nuxt-js-and-nuxt-i18n-43cce9f9f0fe
auth:
- auth flow with Surfnet
forms:
- use backend responses as form error's
@ -22,6 +19,17 @@ TODO: @@ -22,6 +19,17 @@ TODO:
BLOCKED:
on 3:
http://localhost:3000/en/researchStudies/6/settings
after delete go back to http://localhost:3000/en/researchStudies
as form
APPS:
Alle dialogs via forms:
alle dialogs met formErrors component
PRIO:
- testrunner => als commit dan run test...
@ -42,6 +50,9 @@ DONE: @@ -42,6 +50,9 @@ DONE:
axios:
- Accept-Language nl_NL en_US
auth:
- auth flow with Surfnet
layout:
- setup the "templates"
- create the default layout a la vuetify

28
components/app/list/item.vue

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
<template>
<v-card link :to="{ name: `researchStudies.study.apps.${app.component.slug}`, params: {studyId: studyId, appId: app.id } }">
<v-img
:src="app.avatar"
height="50"
width="50"
/>
<v-card-title>{{app.name}}</v-card-title>
<v-card-subtitle v-text="app.description"></v-card-subtitle>
</v-card>
</template>
<script>
export default {
props: {
studyId: {
type: [String, Number],
default: 0
},
app: {
type: Object,
default() {
return {}
}
}
}
}
</script>

118
components/apps/grid.vue

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
<template>
<v-card>
<v-card-title>
Apps
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
</v-card-title>
<v-data-table
multi-sort
hide-default-footer
:headers="headers"
:items="apps"
:search="search"
@click:row="navigateToRow"
>
</v-data-table>
</v-card>
</template>
provider
cpu
gpu
ram
storage
user
action
app
access: []
additional_gpu_amount: 1
additional_gpu_type: 1
additional_memory_amount: 0
additional_memory_type: null
additional_storage_amount: 0
additional_storage_type: null
base_memory_amount: 12
base_memory_type: 2
base_storage_amount: 100
base_storage_type: 2
created_at: "2021-10-05T10:47:01.186516+02:00"
id: 19
name: "VM_a"
networks: [1, 2]
operating_system: 2
operating_system_name: "Windows 10 standaard"
profile: 2
profile_name: "Premium"
researcher: 7
study: 10
total_memory: 12884901888
total_storage: 107374182400
updated_at: "2021-10-05T10:47:01.186640+02:00"
<script>
export default {
props: {
studies: {
type: Array,
default() {
return []
},
},
},
data () {
return {
search: '',
headers: [
{
text: 'provider',
value: 'provider',
},
{
text: 'CPU',
value: 'cpu'
},
{
text: 'GPU',
value: 'gpu',
},
{
text: 'RAM',
value: 'ram'
},
{
text: 'Storage',
value: 'storage'
},
{
text: 'Researcher',
value: 'user'
},
{
text: '',
sortable: false,
value: 'isShared'
}
],
}
},
methods: {
navigateToRow(row) {
this.$router.push({
name: 'researchStudies.study.overview',
params: {
studyId: row.id
}
});
}
},
}
</script>

24
components/apps/list.vue

@ -6,27 +6,7 @@ @@ -6,27 +6,7 @@
:key="app.id"
cols="6"
>
<v-card link :to="{ name: 'researchStudies.study.apps.edit', params: {studyId: studyId, appId: app.id } }">
<v-img
:src="`https://picsum.photos/seed/${app.id}/50/50`"
height="50"
width="50"
/>
<v-card-title>{{app.profile_name}}</v-card-title>
<v-card-subtitle v-text="app.operating_system_name"></v-card-subtitle>
<v-card-text>
Name: {{ app.name }}
<br/>
Researcher: {{ app.researcher }}
<br/>
<br/>
Created at: {{ $d(new Date(app.created_at), 'long') }}
<br/>
Updated at: {{ $d(new Date(app.updated_at), 'long') }}
</v-card-text>
</v-card>
<app-list-item :app="app" :study-id="studyId" />
</v-col>
</v-row>
</v-container>
@ -40,7 +20,7 @@ export default { @@ -40,7 +20,7 @@ export default {
default() {return []},
},
studyId: {
type: String,
type: [Number, String],
default: '',
}
}

13
components/contributor/list/item.vue

@ -1,15 +1,17 @@ @@ -1,15 +1,17 @@
<template>
<v-list-item class="px-2" link :to="{ name: 'researchStudies.study.contributors.edit', params: { studyId: studyId, contributorId: contributor.researcher.id } }">
<v-list-item class="px-2" link :to="{ name: 'researchStudies.study.contributors.edit', params: { studyId: studyId, contributorId: contributor.id } }">
<v-list-item-avatar>
<v-img :src="`https://randomuser.me/api/portraits/men/${contributor.researcher.id+6}.jpg`"></v-img>
<v-img :src="contributor.researcher.avatar ||`https://randomuser.me/api/portraits/men/${contributor.researcher.id+6}.jpg`"></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="text-h6">
{{contributor.researcher.first_name}} {{contributor.researcher.last_name}}
{{contributor.researcher.display_name}}
</v-list-item-title>
<v-list-item-subtitle>{{contributor.researcher.email_address}}</v-list-item-subtitle>
<v-list-item-subtitle>Factuly: {{contributor.researcher.faculty}}</v-list-item-subtitle>
<v-list-item-subtitle>Role: {{contributor.role}}</v-list-item-subtitle>
<v-list-item-subtitle>Faculty: {{contributor.researcher.faculty.name}}</v-list-item-subtitle>
<v-list-item-subtitle>
Role: {{contributor.role}}<span v-if="contributor.isOwner">, OWNER</span>
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
@ -24,6 +26,7 @@ export default { @@ -24,6 +26,7 @@ export default {
contributor: {
type: Object,
default: () => ({
id: '',
researcher: {
id: '',
first_name: '',

76
components/contributors/deleteButton.vue

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
<template>
<v-row justify="center">
<v-dialog
v-model="dialog"
persistent
max-width="290"
>
<template #activator="{ on, attrs }">
<v-icon
v-if="!contributor.isOwner"
small
v-bind="attrs"
v-on="on"
>
mdi-delete
</v-icon>
</template>
<v-card>
<v-card-title>Remove contributor</v-card-title>
<v-card-text>Are you sure you want to remove this contributor?</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
text
@click="dialog = false"
>
No
</v-btn>
<v-btn
text
@click="clickDelete(contributor)"
>
Yes
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
<script>
import { mapActions } from 'vuex';
export default {
props: {
studyId: {
type: [Number, String],
default: 0,
},
contributor: {
type: Object,
default() { return {} }
}
},
data () {
return {
dialog: false,
}
},
methods: {
...mapActions(['studies/deleteContributorFromStudy']),
clickDelete(contributor) {
this['studies/deleteContributorFromStudy']({
studyId: this.studyId,
contributorId: contributor.researcher.id
}).then(() => {
this.$nuxt.refresh();
this.dialog = false;
})
}
}
}
</script>

55
components/contributors/editForm.vue

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
<template>
<v-card>
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
<span class="text-h5">Edit contributor</span>
</v-card-title>
<contributor-list-item :contributor="contributor" />
<v-card-text>
<v-container>
<v-row>
@ -13,7 +15,7 @@ @@ -13,7 +15,7 @@
md="4"
>
<v-select
v-model="editContributor.roles"
v-model="formData.roles"
:items="roles"
item-text="name"
item-value="id"
@ -47,51 +49,22 @@ @@ -47,51 +49,22 @@
</template>
<script>
import Form from '@/lib/form';
export default {
props: {
defaultContributor: {
type: Object,
default: () => { return {}; },
},
item: {
type: Object,
default: () => { return {}; },
},
isNew: {
type: Boolean,
default: true,
},
roles: {
type: Array,
default: () => [],
},
apps: {
type: Array,
default: () => [],
},
onClose: {
type: Function,
default: () => {},
},
},
data() {
return {
editContributor: this.item,
formData: {},
contributor: {},
roles: [],
form: Form({
})
}
},
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
methods: {
close() {
this.onClose();
},
save() {
},
}
}
</script>

26
components/contributors/inviteButton.vue

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<template>
<v-btn @click="goToInviteContributor">
Invite contributor
</v-btn>
</template>
<script>
export default {
props: {
studyId: {
type: [Number, String],
default: 0,
}
},
methods: {
goToInviteContributor() {
this.$router.push({
name: 'researchStudies.study.contributors.invite',
params: {
studyId: this.studyId,
}
})
}
}
}
</script>

143
components/contributors/list.vue

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
<template>
<v-card>
<v-card-title>
Contributors
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
</v-card-title>
<v-data-table
multi-sort
hide-default-footer
:headers="headers"
:items="contributors"
:search="search"
:custom-filter="customFilter"
@click:row="navigateToRow"
>
<template #item.image="{ item }">
<v-avatar>
<v-img
:src="`https://randomuser.me/api/portraits/men/${item.researcher.id+6}.jpg`"
:alt="`${item.researcher.first_name} ${item.researcher.last_name}`"
/>
</v-avatar>
</template>
<template #item.name="{item}">
{{item.researcher.first_name}} {{item.researcher.last_name}}
</template>
<template #item.email="{item}">
{{item.researcher.email_address}}
</template>
<template #item.faculty="{item}">
{{item.researcher.faculty.name}}
</template>
<template #item.university="{item}">
{{item.researcher.faculty.university.name}}
</template>
<template #item.isOwner="{item}">
{{owner.id === item.researcher.id}}
</template>
<template #item.actions="{item}">
<contributors-delete-button :contributor="item" :study-id="studyId" />
</template>
</v-data-table>
</v-card>
</template>
<script>
export default {
props: {
contributors: {
type: Array,
default() {
return []
},
},
owner: {
type: Object,
default() {
return {}
}
},
studyId: {
type: [String, Number],
default: 0,
},
},
data () {
return {
search: '',
headers: [
{
text: '',
align: 'start',
value: 'image',
},
{
text: 'Name',
value: 'name',
},
{
text: 'Email',
value: 'email',
},
{
text: 'Faculty',
value: 'faculty',
},
{
text: 'University',
value: 'university',
},
{
text: 'Role',
value: 'role'
},
{
text: 'Owner',
value: 'isOwner'
},
{
text: '',
value: 'actions'
}
],
}
},
methods: {
navigateToRow(row) {
this.$router.push({
name: 'researchStudies.study.contributors.edit',
params: {
studyId: this.studyId,
contributorId: row.id
}
});
},
customFilter(_, search, item) {
search = search.toString().toLowerCase();
return (
item.role.toLowerCase().includes(search)
|| item.researcher.email_address.toLowerCase().includes(search)
|| String(item.researcher.faculty).toLowerCase().includes(search)
|| item.researcher.first_name.toLowerCase().includes(search)
|| item.researcher.last_name.toLowerCase().includes(search)
|| item.researcher.faculty.name.toLowerCase().includes(search)
|| item.researcher.faculty.university.name.toLowerCase().includes(search)
)
}
},
}
</script>

76
components/researchStudies/deleteButton.vue

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
<template>
<v-row justify="center">
<v-dialog
v-model="dialog"
persistent
max-width="290"
>
<template #activator="{ on, attrs }">
<v-btn
small
v-bind="attrs"
v-on="on"
>
delete study
</v-btn>
</template>
<ui-rug-card-form
:form="form"
>
<v-card-title>Remove research study</v-card-title>
<v-card-text>Are you sure you want to remove this research study?</v-card-text>
<span slot="actions">
<v-btn
text
@click="dialog = false"
>
No
</v-btn>
<v-btn
text
type="submit"
>
Yes
</v-btn>
</span>
</ui-rug-card-form>
</v-dialog>
</v-row>
</template>
<script>
import { mapActions } from 'vuex';
import Form from '@/lib/form';
export default {
props: {
studyId: {
type: [Number, String],
default: 0,
},
},
data () {
return {
dialog: false,
formData: {
studyId: this.studyId,
},
form: new Form({
vm: this,
action: this['studies/deleteStudy'],
onResponse() {
this.dialog = false;
this.$router.push({ name: 'researchStudies' });
}
}),
}
},
methods: {
...mapActions(['studies/deleteStudy']),
}
}
</script>

29
components/researchStudies/list.vue

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
:headers="headers"
:items="studies"
:search="search"
:custom-filter="customFilter"
@click:row="navigateToRow"
>
<template #item.image="{ item }">
@ -27,6 +28,16 @@ @@ -27,6 +28,16 @@
/>
</v-avatar>
</template>
<template #item.field="{ item }">
{{item.field.name}}
</template>
<template #item.faculty="{ item }">
{{item.field.faculty.name}}
</template>
<template #item.university="{ item }">
{{item.field.faculty.university.name}}
</template>
<template #item.startDate="{ item }">
{{$d(item.startDate, 'short')}}
</template>
@ -64,6 +75,14 @@ @@ -64,6 +75,14 @@
text: 'Field',
value: 'field',
},
{
text: 'Faculty',
value: 'faculty',
},
{
text: 'University',
value: 'university',
},
{
text: 'Start Date',
value: 'startDate'
@ -84,6 +103,16 @@ @@ -84,6 +103,16 @@
studyId: row.id
}
});
},
customFilter(_, search, item) {
search = search.toString().toLowerCase();
return (
item.startDate.toString().toLowerCase().includes(search)
|| item.name.toLowerCase().includes(search)
|| item.field.name.toLowerCase().includes(search)
|| item.field.faculty.name.toLowerCase().includes(search)
|| item.field.faculty.university.name.toLowerCase().includes(search)
)
}
},
}

20
components/ui/rug/card/form.vue

@ -3,16 +3,18 @@ @@ -3,16 +3,18 @@
<v-card :loading="form.isLoading">
<slot />
<ui-rug-form-errors v-if="showFormErrors && form.error">
{{ form.error }}
<ui-rug-form-errors v-if="showFormErrors && form.getError('formData.__form__')">
{{ form.getError('formData.__form__') }}
</ui-rug-form-errors>
<v-divider v-if="useActions"></v-divider>
<v-card-actions v-if="useActions">
<v-spacer />
<ui-rug-form-action-cancel />
<ui-rug-form-action-save />
<v-spacer v-if="useSpacer" />
<slot name="actions">
<ui-rug-form-action-cancel />
<ui-rug-form-action-save />
</slot>
</v-card-actions>
</v-card>
@ -29,11 +31,15 @@ export default { @@ -29,11 +31,15 @@ export default {
},
showFormErrors: {
type: Boolean,
default: true
default: true,
},
useActions: {
type: Boolean,
default: true
default: true,
},
useSpacer: {
type: Boolean,
default: true,
}
}
};

14
components/user/profile/list/item.vue

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
<template>
<v-list-item class="px-2" link :to="{ name: 'profile.overview' }">
<v-list-item class="px-2">
<v-list-item-avatar>
<v-img :src="`https://randomuser.me/api/portraits/men/${user.id+6}.jpg`"></v-img>
<v-img :src="user.avatar || `https://randomuser.me/api/portraits/men/${user.id}.jpg`"></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="text-h6">
{{user.name}}
{{user.display_name}}
</v-list-item-title>
<v-list-item-subtitle>{{user.email}}</v-list-item-subtitle>
<v-list-item-subtitle>{{user.email_address}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
@ -18,9 +18,9 @@ export default { @@ -18,9 +18,9 @@ export default {
user: {
type: Object,
default: () => ({
id: '',
name: '',
email: ''
avatar: '',
display_name: '',
email_address: ''
})
}
},

11
config/routes.js

@ -64,9 +64,14 @@ export default { @@ -64,9 +64,14 @@ export default {
component: 'pages/studies/study/apps',
},
{
name: 'researchStudies.study.apps.edit',
path: '/en/researchStudies/:studyId/apps/:appId/edit',
component: 'pages/studies/study/apps/app/edit',
name: 'researchStudies.study.apps.windows-vdi',
path: '/en/researchStudies/:studyId/apps/windows-vdi/',
component: 'pages/studies/study/apps/app/windows-vdi',
},
{
name: 'researchStudies.study.apps.windows-vdi.create',
path: '/en/researchStudies/:studyId/apps/windows-vdi/create/',
component: 'pages/studies/study/apps/app/windows-vdi/create',
},
{
name: 'researchStudies.study.settings',

51
lib/form.js

@ -254,6 +254,7 @@ export default class Form { @@ -254,6 +254,7 @@ export default class Form {
* @property {Object} schema the validation schema
* @property {Object} validatorOpts the options for Ajv
* @property {Object} messages the custom validation messages
* @property {Object} action a function that returns a promise, used as alternative for $axios[${method}](${url})
* @property {function} flattenErrors this is the method used to flatten all nested errors into their root
* @property {function} createRequest optional method to create the request, should return a promise
* @property {function} getData optional method to get the form data to be used in the request
@ -276,8 +277,9 @@ export default class Form { @@ -276,8 +277,9 @@ export default class Form {
this.url = opts.url;
this.method = opts.method || 'post';
this.watchers = [];
this.schema = opts.schema || {};
this.schema = opts.schema || {properties: {}};
this.messages = opts.messages;
this.action = opts.action;
// create the "form"
this[this.formName] = {};
@ -307,7 +309,7 @@ export default class Form { @@ -307,7 +309,7 @@ export default class Form {
const defaultOpts = { allErrors: true, useDefaults: true, coerceTypes: false };
const validatorOpts = Object.assign(defaultOpts, opts.validatorOpts || {});
const ajv = new Ajv(validatorOpts);
this.validator = ajv.compile(opts.schema);
this.validator = ajv.compile(this.schema);
// start initial validation
this.validate();
@ -569,14 +571,16 @@ export default class Form { @@ -569,14 +571,16 @@ export default class Form {
/**
*
* @returns {*}
*
*/
runRequest() {
if (!this.isLoading) {
this.isLoading = true;
this.isSuccess = false;
return this.createRequest();
const request = this.action ? this.createAction() : this.createRequest();
return request
.then(this.onResponse)
.catch(this.onErrorResponse);
}
}
@ -612,14 +616,18 @@ export default class Form { @@ -612,14 +616,18 @@ export default class Form {
/**
* Creates the request and delegates to corresponding handlers
* sets loading state
*
* Creates the request
*/
createRequest() {
return this.vm.$axios[this.getMethod()](this.getUrl(), this.getData())
.then(this.onResponse)
.catch(this.onErrorResponse);
}
/**
* Creates the action
*/
createAction() {
return this.action(this.getData())
}
@ -646,11 +654,11 @@ export default class Form { @@ -646,11 +654,11 @@ export default class Form {
/**
* Handles the form response
*/
onResponse(res) {
onResponse(response) {
this.isLoading = false;
this.isSubmitted = true;
this.isSuccess = true;
setTimeout(() => this.onAfterResponse(res), 0);
setTimeout(() => this.onAfterResponse(response), 0);
}
@ -665,22 +673,25 @@ export default class Form { @@ -665,22 +673,25 @@ export default class Form {
/**
* Handles the form error response
*
* @param response
* @param error
*/
onErrorResponse(response) {
onErrorResponse(error) {
this.isLoading = false;
this.isSuccess = false;
this.isSubmitted = true;
// set all the errors
if (response && response.data) {
if (response.data.errors)
this.setErrors(response.data.errors, { touched: true, pristine: false });
else if (response.data.message)
this.setFormErrors(response.data.message);
if (error.response && error.response.data) {
if (error.response.status === 500) {
this.setErrors([{dataPath: '__form__', message: 'A server error occured, please try again later.'}], { touched: true, pristine: false });
}
if (error.response.status === 404) {
this.setErrors([{dataPath: '__form__', message: 'Resource not found'}], { touched: true, pristine: false });
} else
this.setErrors(error.response.data, { touched: true, pristine: false });
}
this.onAfterErrorResponse(response);
this.onAfterErrorResponse(error);
}

2
lib/store.js

@ -41,7 +41,7 @@ export const actionCreator = function({ @@ -41,7 +41,7 @@ export const actionCreator = function({
return result;
} catch (error) {
fail && commit(`${name}Fail`, { param: finalParam, error });
return error;
throw error;
}
};
};

82
nuxt.config.js

@ -106,10 +106,50 @@ export default { @@ -106,10 +106,50 @@ export default {
},
},
/*
oidc: {
scheme: 'openIDConnect',
clientId: 'web-client',
endpoints: {
configuration: 'https://##endpoint##/.well-known/openid-configuration',
},
redirectUri: "/signin-oidc",
logoutRedirectUri: "/signout-callback-oidc",
idToken: {
property: 'id_token',
maxAge: 60 * 60 * 24 * 30,
prefix: '_id_token.',
expirationPrefix: '_id_token_expiration.'
},
responseType: 'code',
grantType: 'authorization_code',
scope: ['openid', 'profile', 'offline_access'],
codeChallengeMethod: 'S256',
}
}
*/
auth: {
cookie: false,
localStorage: false,
strategies: {
// openIDConnect: {
// scheme: 'openIDConnect',
// endpoints: {
// configuration: 'https://connect.test.surfconext.nl/.well-known/openid-configuration',
// },
// clientId: 'vre.web.rug.nl',
// redirectUri: '/redirect',
// idToken: {
// property: 'id_token',
// maxAge: 60 * 60 * 24 * 30,
// prefix: '_id_token.',
// expirationPrefix: '_id_token_expiration.'
// },
// responseType: 'code',
// grantType: 'authorization_code',
// scope: ['openid', 'profile', 'offline_access'],
// codeChallengeMethod: 'S256',
// },
refresh: {
token: {
property: 'access',
@ -131,7 +171,7 @@ export default { @@ -131,7 +171,7 @@ export default {
endpoints: {
login: { url: '/api/auth/jwt/create/', method: 'post' },
// logout: { url: '/api/auth/logout', method: 'post' },
user: { url: '/api/auth/users/me/', method: 'get' },
user: { url: '/api/v1/researchers/me/', method: 'get' },
refresh: { url: '/api/auth/jwt/refresh', method: 'post' },
},
redirect: {
@ -145,18 +185,15 @@ export default { @@ -145,18 +185,15 @@ export default {
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
// baseURL: 'https://oops.com',
// baseURL: 'https://api-vre.web.rug.nl/',
// https: true,
proxy: true,
// proxy: true,
// baseURL: process.env.API_URL,
// https: process.env.API_HTTPS
},
proxy: [
'https://api-vre.web.rug.nl/api',
// 'api/': {
// target: 'https://api-vre.web.rug.nl/api',
// }
],
// Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
@ -189,22 +226,23 @@ export default { @@ -189,22 +226,23 @@ export default {
}
}
},
sentry: {
dsn: "https://c60ba2eccd2e4facbcb88c599b6f5149@o1022157.ingest.sentry.io/5988257", // Enter your project's DSN here
// Additional Module Options go here
// https://sentry.nuxtjs.org/sentry/options
config: {
// Add native Sentry config here
// https://docs.sentry.io/platforms/javascript/guides/vue/configuration/options/
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
logErrors: true,
},
},
// Paul: https://c60ba2eccd2e4facbcb88c599b6f5149@o1022157.ingest.sentry.io/5988257
// Elwin: https://65fdb3af442f4217bed58536be25febe@o1025289.ingest.sentry.io/5991484
// sentry: {
// dsn: "https://65fdb3af442f4217bed58536be25febe@o1025289.ingest.sentry.io/5991484", // Enter your project's DSN here
// // Additional Module Options go here
// // https://sentry.nuxtjs.org/sentry/options
// config: {
// // Add native Sentry config here
// // https://docs.sentry.io/platforms/javascript/guides/vue/configuration/options/
// // Set tracesSampleRate to 1.0 to capture 100%
// // of transactions for performance monitoring.
// // We recommend adjusting this value in production
// tracesSampleRate: 1.0,
// logErrors: true,
// },
// },
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {

6
pages/login.vue

@ -14,11 +14,7 @@ export default { @@ -14,11 +14,7 @@ export default {
})
.then(() => {
this.$auth.fetchUser().then((response) => {
this.$auth.setUser({
name: response.data?.username?.split('@')[0],
id: response.data?.id,
email: response.data?.email
});
this.$auth.setUser(response.data);
});
});
},

112
pages/studies/create.vue

@ -58,63 +58,65 @@ @@ -58,63 +58,65 @@
</template>
<script>
import Form from '@/lib/form';
import { mapActions } from 'vuex';
import Form from '@/lib/form';
export default {
async asyncData({store}) {
await store.dispatch('studies/getStudyFields');
return {
studyFields: store.getters['studies/getStudyFields'],
};
},
data () {
return {
formData: {
name: '',
description: '',
code: '',
human_subject: false,
field: null,
},
form: new Form({
form: 'formData',
vm: this,
method: 'post',
url: '/api/v1/studies/',
schema: {
type: 'object',
properties: {
name: {
type: 'string',
maxLength: 200,
},
description: {
type: 'string',
maxLength: 2048,
},
code: {
type: 'string',
maxLength: 50,
},
human_subject: {
type: 'boolean',
},
field: {
type: 'integer'
}
export default {
async asyncData({store}) {
await store.dispatch('studies/getStudyFields');
return {
studyFields: store.getters['studies/getStudyFields'],
};
},
data () {
return {
formData: {
name: '',
description: '',
code: '',
human_subject: false,
field: null,
},
form: new Form({
vm: this,
action: this['studies/createStudy'],
schema: {
type: 'object',
properties: {
name: {
type: 'string',
maxLength: 200,
},
description: {
type: 'string',
maxLength: 2048,
},
code: {
type: 'string',
maxLength: 50,
},
required: ['name', 'code', 'field', ],
human_subject: {
type: 'boolean',
},
field: {
type: 'integer'
}
},
onResponse(response) {
this.vm.$router.push({
name: 'researchStudies.study.overview',
params: {
studyId: response.data.id,
}
})
}
}),
}
},
required: ['name', 'code', 'field', ],
},
onResponse(response) {
this.vm.$router.push({
name: 'researchStudies.study.overview',
params: {
studyId: response.data.id,
}
})
}
}),
}
},
methods: {
...mapActions(['studies/createStudy']),
}
}
</script>

4
pages/studies/list.vue

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<template>
<div>
<ResearchStudiesList :studies="studies" />
<ResearchStudiesCreateButton :study-fields="studyFields" />
<research-studies-list :studies="studies" />
<research-studies-create-button :study-fields="studyFields" />
</div>
</template>

22
pages/studies/study/apps.vue

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
<template>
<div>
<apps-list :apps="apps" :studyId="studyId" />
</div>
</template>
<script>
export default {
async asyncData({ store, route }) {
const studyId = route.params.studyId;
await store.dispatch('apps/getApps');
return {
studyId,
apps: store.getters['apps/getApps'],
}
// return {
// // apps: store.getters['apps/getAppsForStudy'](studyId),
// studyId,
// };
},
}
</script>

5
pages/studies/study/apps/app/edit.vue

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
<template>
<div>
app detail edit page
</div>
</template>

177
pages/studies/study/apps/app/windows-vdi.vue

@ -0,0 +1,177 @@ @@ -0,0 +1,177 @@
<template>
<v-card>
<v-card-title>
Virtual machines
<v-spacer></v-spacer>
<!-- <v-data-table
multi-sort
hide-default-footer
:headers="headers"
:items="profiles"
:search="search"
:custom-filter="customFilter"
@click:row="navigateToRow"
/> -->
<!-- <windows-vdi-edit-button :studyId="studyId" :virtualMachineId="item.id" />
<windows-vdi-delete-button :virtualMachineId="item.id" /> -->
data-table: current vm's (name, os, ram, storage, researcher)
edit button
add vm page in edit modus
delete button
delete action => re-render
button: add vm
page:
pick-profile-input:
data-table: vm profiles choice
returns profile-id
dropdown: researcher
button: create workspace