diff --git a/dbrepo-ui/api/container.service.js b/dbrepo-ui/api/container.service.js index 77f56972b5e85df9c3a5a844551c349645f7c1e6..c24ffbd9d57aebc5d31f872d982e81b60e169727 100644 --- a/dbrepo-ui/api/container.service.js +++ b/dbrepo-ui/api/container.service.js @@ -19,6 +19,23 @@ class ContainerService { }) } + findAllImages () { + return new Promise((resolve, reject) => { + api.get('/api/image', { headers: { Accept: 'application/json' } }) + .then((response) => { + const images = response.data + console.debug('response images', images) + resolve(images) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to load images', error) + Vue.$toast.error(`[${code}] Failed to load images: ${message}`) + reject(error) + }) + }) + } + findOne (id) { return new Promise((resolve, reject) => { api.get(`/api/container/${id}`, { headers: { Accept: 'application/json' } }) diff --git a/dbrepo-ui/api/database.service.js b/dbrepo-ui/api/database.service.js index 17cbf65479badd1aeb6c21d61e9828d22115ab8f..85756eaca0db53e7b963558abcb68af4a8a51de4 100644 --- a/dbrepo-ui/api/database.service.js +++ b/dbrepo-ui/api/database.service.js @@ -176,6 +176,23 @@ class DatabaseService { }) } + findView (id, databaseId, viewId) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/view/${viewId}`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const view = response.data + console.debug('response view', view) + resolve(view) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to find view', error) + Vue.$toast.error(`[${code}] Failed to find view: ${message}`) + reject(error) + }) + }) + } + createView (id, databaseId, data) { return new Promise((resolve, reject) => { api.post(`/api/container/${id}/database/${databaseId}/view`, data, { headers: { Accept: 'application/json' } }) diff --git a/dbrepo-ui/api/identifier.service.js b/dbrepo-ui/api/identifier.service.js index 51181e9c590de56c1d69856e6d61585848579281..9410ccf9043299e6bd98bc2398782c593e247456 100644 --- a/dbrepo-ui/api/identifier.service.js +++ b/dbrepo-ui/api/identifier.service.js @@ -20,9 +20,13 @@ class IdentifierService { }) } - findOne (id) { + find (id) { + return this.findAccept(id, 'application/json') + } + + findAccept (id, accept) { return new Promise((resolve, reject) => { - api.get(`/api/pid/${id}`, { headers: { Accept: 'application/json' } }) + api.get(`/api/pid/${id}`, { headers: { Accept: accept } }) .then((response) => { const identifier = response.data console.debug('response identifier', identifier) diff --git a/dbrepo-ui/api/query.service.js b/dbrepo-ui/api/query.service.js index 09db2312e76e49ebf8cdf11f40a2fa2664c4fdb4..a7900546e0cd3cf7a7c99db12c7a677296673bc3 100644 --- a/dbrepo-ui/api/query.service.js +++ b/dbrepo-ui/api/query.service.js @@ -119,7 +119,9 @@ class QueryService { return new Promise((resolve, reject) => { api.get(`/api/container/${id}/database/${databaseId}/query/${queryId}/export`, { headers: { Accept: 'text/csv' } }) .then((response) => { - resolve(response.data) + const subset = response.data + console.debug('response subset', subset) + resolve(subset) }) .catch((error) => { const { code, message } = error @@ -134,12 +136,99 @@ class QueryService { return new Promise((resolve, reject) => { api.get(`/api/pid/${id}`, { headers: { Accept: mime } }) .then((response) => { - resolve(response.data) + const metadata = response.data + console.debug('response metadata', metadata) + resolve(metadata) }) .catch((error) => { const { code, message } = error - console.error('Failed to export query', error) - Vue.$toast.error(`[${code}] Failed to export query: ${message}`) + console.error('Failed to export metadata', error) + Vue.$toast.error(`[${code}] Failed to export metadata: ${message}`) + reject(error) + }) + }) + } + + execute (id, databaseId, data, page, size) { + return new Promise((resolve, reject) => { + api.post(`/api/container/${id}/database/${databaseId}?page=${page}&size=${size}`, data, { headers: { Accept: 'application/json' } }) + .then((response) => { + const result = response.data + console.debug('response result', result) + resolve(result) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to execute statement', error) + Vue.$toast.error(`[${code}] Failed to execute statement: ${message}`) + reject(error) + }) + }) + } + + reExecuteQuery (id, databaseId, queryId, page, size) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/query/${queryId}/data?page=${page}&size=${size}`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const result = response.data + console.debug('response result', result) + resolve(result) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to re-execute query', error) + Vue.$toast.error(`[${code}] Failed to re-execute query: ${message}`) + reject(error) + }) + }) + } + + reExecuteQueryCount (id, databaseId, queryId) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/query/${queryId}/data/count`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const count = response.data + console.debug('response count', count) + resolve(count) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to re-execute query count', error) + Vue.$toast.error(`[${code}] Failed to re-execute query count: ${message}`) + reject(error) + }) + }) + } + + reExecuteView (id, databaseId, viewId, page, size) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/view/${viewId}/data?page=${page}&size=${size}`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const result = response.data + console.debug('response result', result) + resolve(result) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to re-execute view', error) + Vue.$toast.error(`[${code}] Failed to re-execute view: ${message}`) + reject(error) + }) + }) + } + + reExecuteViewCount (id, databaseId, viewId) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/view/${viewId}/data/count`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const count = response.data + console.debug('response count', count) + resolve(count) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to re-execute view count', error) + Vue.$toast.error(`[${code}] Failed to re-execute view count: ${message}`) reject(error) }) }) diff --git a/dbrepo-ui/api/search.service.js b/dbrepo-ui/api/search.service.js new file mode 100644 index 0000000000000000000000000000000000000000..4cdd577bb8094a210d3171e842c190281f7102b8 --- /dev/null +++ b/dbrepo-ui/api/search.service.js @@ -0,0 +1,24 @@ +import Vue from 'vue' +import axios from 'axios' +import { elasticPassword } from '../config' + +class SearchService { + search (query) { + return new Promise((resolve, reject) => { + axios.get(`/retrieve/_all/_search?q=${query}*&terminate_after=50`, { headers: { Accept: 'application/json' }, auth: { username: 'elastic', password: elasticPassword } }) + .then((response) => { + const hits = response.data.hits.hits + console.debug('response hits', hits) + resolve(hits) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to load search results', error) + Vue.$toast.error(`[${code}] Failed to load search results: ${message}`) + reject(error) + }) + }) + } +} + +export default new SearchService() diff --git a/dbrepo-ui/api/table.service.js b/dbrepo-ui/api/table.service.js index d5fc87b38bdb0927fb435e5306871a23df7e64ad..930aae5a8ee79ef8f008860fbae3cd1f7de94cc0 100644 --- a/dbrepo-ui/api/table.service.js +++ b/dbrepo-ui/api/table.service.js @@ -41,6 +41,95 @@ class TableService { }) } + updateColumn (id, databaseId, tableId, columnId, data) { + return new Promise((resolve, reject) => { + api.put(`/api/container/${id}/database/${databaseId}/table/${tableId}/column/${columnId}`, data, { headers: { Accept: 'application/json' } }) + .then((response) => { + const column = response.data + console.debug('response column', column) + resolve(column) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to update column', error) + Vue.$toast.error(`[${code}] Failed to update column: ${message}`) + reject(error) + }) + }) + } + + data (id, databaseId, tableId, page, size, timestamp) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/data?page=${page}&size=${size}×tamp=${timestamp}`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const data = response.data + console.debug('response data', data) + resolve(data) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to load table data', error) + Vue.$toast.error(`[${code}] Failed to load table data: ${message}`) + reject(error) + }) + }) + } + + dataCount (id, databaseId, tableId, timestamp) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/data/count?timestamp=${timestamp}`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const count = response.data + console.debug('response count', count) + resolve(count) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to load table count', error) + Vue.$toast.error(`[${code}] Failed to load table count: ${message}`) + reject(error) + }) + }) + } + + findHistory (id, databaseId, tableId) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/history`, { headers: { Accept: 'application/json' } }) + .then((response) => { + const history = response.data + console.debug('response history', history) + resolve(history) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to load table history', error) + Vue.$toast.error(`[${code}] Failed to load table history: ${message}`) + reject(error) + }) + }) + } + + exportData (id, databaseId, tableId) { + return this.exportDataTimestamp(id, databaseId, tableId, null) + } + + exportDataTimestamp (id, databaseId, tableId, timestamp) { + return new Promise((resolve, reject) => { + api.get(`/api/container/${id}/database/${databaseId}/table/${tableId}/export?timestamp=${timestamp}`, { responseType: 'text' }) + .then((response) => { + const data = response.data + console.debug('response data', data) + resolve(data) + }) + .catch((error) => { + const { code, message } = error + console.error('Failed to export table data', error) + Vue.$toast.error(`[${code}] Failed to export table data: ${message}`) + reject(error) + }) + }) + } + create (id, databaseId, data) { return new Promise((resolve, reject) => { api.post(`/api/container/${id}/database/${databaseId}/table`, data, { headers: { Accept: 'application/json' } }) diff --git a/dbrepo-ui/components/TableList.vue b/dbrepo-ui/components/TableList.vue index 86aed98663b9b03ce9f2f121342b87fa302a2f8c..f627f9387e4e329e73c7ff922e55fe2387c26c0a 100644 --- a/dbrepo-ui/components/TableList.vue +++ b/dbrepo-ui/components/TableList.vue @@ -1,6 +1,6 @@ <template> <div> - <v-progress-linear v-if="loading" :color="loadingColor" indeterminate /> + <v-progress-linear v-if="loading" indeterminate /> <v-card v-if="!loading && tables && tables.length === 0" flat> <v-card-text> (no tables) @@ -24,7 +24,6 @@ <script> import { formatTimestampUTCLabel } from '@/utils' -import TableService from '@/api/table.service' export default { data () { @@ -79,17 +78,6 @@ export default { token () { return this.$store.state.token }, - loadingColor () { - return this.error ? 'red lighten-2' : 'primary' - }, - config () { - if (this.token === null) { - return {} - } - return { - headers: { Authorization: `Bearer ${this.token}` } - } - }, user () { return this.$store.state.user }, @@ -144,53 +132,12 @@ export default { } return column.column_type }, - details (table) { - /* use cache */ - this.tableDetails = table - /* load remaining info */ - if (this.canRead) { - this.loadingDetails = true - TableService.findOne(this.$route.params.container_id, this.$route.params.database_id, table.id) - .then((table) => { - this.tableDetails = table - if (table.id) { - this.openPanelByTableId(table.id) - } - }) - .finally(() => { - this.loadingDetails = false - }) - } - }, - is_owner (table) { - if (!this.user) { - return false - } - return table.creator.username === this.user.username - }, closed (data) { console.debug('closed dialog', data) this.dialogSemantic = false }, created (created) { return formatTimestampUTCLabel(created) - }, - async deleteTable () { - try { - this.loading = true - await this.$axios.delete(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.deleteTableId}`, this.config) - this.loading = false - this.refresh() - } catch (err) { - this.$toast.error('Could not delete table') - } - this.dialogDelete = false - }, - /** - * open up the accordion with the table that has been updated (by the ColumnUnit dialog) - */ - openPanelByTableId (id) { - this.panel = this.tables.findIndex(t => t.id === id) } } } diff --git a/dbrepo-ui/components/dialogs/CreateDB.vue b/dbrepo-ui/components/dialogs/CreateDB.vue index e73cd6f336e9b332c0c2c127d5c3394bfb6cdd19..0259ba127e6470ca28562c0c97536d3ba7cfc263 100644 --- a/dbrepo-ui/components/dialogs/CreateDB.vue +++ b/dbrepo-ui/components/dialogs/CreateDB.vue @@ -65,7 +65,6 @@ export default { return { valid: false, loading: false, - error: false, engine: { repository: null, tag: null @@ -90,23 +89,9 @@ export default { } }, computed: { - loadingColor () { - return this.error ? 'red lighten-2' : 'primary' - }, token () { return this.$store.state.token }, - config () { - if (this.token === null) { - return { - headers: {} - } - } - return { - headers: { Authorization: `Bearer ${this.token}` }, - progress: false - } - }, user () { return this.$store.state.user } @@ -121,20 +106,18 @@ export default { cancel () { this.$emit('close', { success: false }) }, - async getImages () { - try { - this.loading = true - const res = await this.$axios.get('/api/image') - this.engines = res.data - console.debug('engines', this.engines) - if (this.engines.length > 0) { - this.engine = this.engines[0] - } - } catch (err) { - this.error = true - this.$toast.error('Failed to fetch supported engines. Try reload the page') - } - this.loading = false + getImages () { + this.loading = true + ContainerService.findAllImages() + .then((images) => { + this.engines = images + if (this.engines.length > 0) { + this.engine = this.engines[0] + } + }) + .finally(() => { + this.loading = false + }) }, async create () { await this.createContainer() diff --git a/dbrepo-ui/components/dialogs/EditRoles.vue b/dbrepo-ui/components/dialogs/EditRoles.vue deleted file mode 100644 index 81860aa010978cfab86fdb329c6cb85e8089657f..0000000000000000000000000000000000000000 --- a/dbrepo-ui/components/dialogs/EditRoles.vue +++ /dev/null @@ -1,174 +0,0 @@ -<template> - <div> - <v-form ref="form" v-model="valid" autocomplete="off" @submit.prevent="submit"> - <v-card> - <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" /> - <v-card-title> - User Role - </v-card-title> - <v-card-subtitle> - Modify user role - </v-card-subtitle> - <v-card-text> - <v-alert - v-if="becomeDeveloper" - border="left" - color="warning"> - <strong>Dangerous operation:</strong> you are giving this user developer access. This cannot be undone. - </v-alert> - <v-row> - <v-col> - <v-autocomplete - v-model="selectedUser" - :items="users" - :loading="loadingUsers" - :rules="[v => !!v || $t('Required')]" - required - hide-no-data - hide-selected - hide-details - return-object - item-text="username" - item-value="username" - single-line - label="Username" /> - </v-col> - </v-row> - <v-row> - <v-col> - <v-select - v-model="modify.roles" - :items="roles" - multiple - item-value="code" - item-text="text" - :rules="[v => !!v || $t('Required')]" - required - label="Role type" /> - </v-col> - </v-row> - </v-card-text> - <v-card-actions> - <v-spacer /> - <v-btn - class="mb-2" - @click="cancel"> - Cancel - </v-btn> - <v-btn - id="database" - class="mb-2 ml-3 mr-2" - :disabled="!valid || loading" - color="primary" - type="submit" - :loading="loading" - @click="updateRoles"> - Save - </v-btn> - </v-card-actions> - </v-card> - </v-form> - </div> -</template> - -<script> -export default { - props: { - user: { - type: Object, - default () { - return {} - } - } - }, - data () { - return { - valid: false, - loading: false, - loadingUsers: false, - selectedUser: null, - users: [], - error: false, - roles: [ - { text: 'Researcher', value: 'researcher', code: 'ROLE_RESEARCHER' }, - { text: 'Data Steward', value: 'data_steward', code: 'ROLE_DATA_STEWARD' }, - { text: 'Developer', value: 'developer', code: 'ROLE_DEVELOPER' } - ], - modify: { - roles: [] - } - } - }, - computed: { - loadingColor () { - return this.error ? 'red lighten-2' : 'primary' - }, - token () { - return this.$store.state.token - }, - config () { - if (this.token === null) { - return {} - } - return { - headers: { Authorization: `Bearer ${this.token}` } - } - }, - becomeDeveloper () { - return this.modify.roles.filter(r => r === 'ROLE_DEVELOPER').length > 0 - } - }, - watch: { - user (newVal, oldVal) { - this.modify.roles = newVal.roles - this.selectedUser = newVal - } - }, - mounted () { - this.loadUsers() - this.modify.roles = this.user.roles - this.selectedUser = this.user - }, - methods: { - submit () { - this.$refs.form.validate() - }, - cancel () { - this.$emit('close-dialog', { success: false }) - }, - async updateRoles () { - this.loading = true - const roles = { - roles: this.modify.roles.map(role => this.roles.filter(r => r.code === role)[0].value) - } - try { - const res = await this.$axios.put(`/api/user/${this.selectedUser.id}/roles`, roles, this.config) - console.debug('roles', res.data) - this.$toast.success('Updated user roles') - this.$emit('close-dialog', { success: true }) - } catch (error) { - const { message } = error.response - this.$toast.error('Failed to update user roles: ' + message) - console.error('Failed to update user roles', error) - } - this.loading = false - }, - async loadUsers () { - this.loading = true - try { - const res = await this.$axios.get('/api/user', this.config) - this.users = res.data.map((user) => { - user.roles_pretty = user.roles.join(',') - return user - }) - console.debug('users', this.users) - } catch (error) { - const { message } = error.response - this.$toast.error('Failed to load users: ' + message) - console.error('Failed to load users', error) - } - this.loading = false - } - } -} -</script> diff --git a/dbrepo-ui/components/dialogs/Persist.vue b/dbrepo-ui/components/dialogs/Persist.vue index 1f87e7ef2d30383c52d8cdf75b6eed62af5728bb..d44f784ca5eb2e2ff982b29befca12fe13ccebe9 100644 --- a/dbrepo-ui/components/dialogs/Persist.vue +++ b/dbrepo-ui/components/dialogs/Persist.vue @@ -1,7 +1,6 @@ <template> <div> <v-card> - <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" /> <v-card-title v-text="`Persist ${title}`" /> <v-card-text> <v-alert @@ -169,6 +168,7 @@ </v-btn> <v-btn class="mb-2" + :loading="loading" :disabled="!formValid || loading" color="primary" @click="persist"> diff --git a/dbrepo-ui/components/dialogs/TimeTravel.vue b/dbrepo-ui/components/dialogs/TimeTravel.vue index 4866c7974cf588dab26c27af9b8611cb37888fd5..1007db8b50784920d627063bc05db023c86b9d4f 100644 --- a/dbrepo-ui/components/dialogs/TimeTravel.vue +++ b/dbrepo-ui/components/dialogs/TimeTravel.vue @@ -1,7 +1,7 @@ <template> <div> <v-card> - <v-progress-linear v-if="loading" :color="loadingColor" :indeterminate="!error" /> + <v-progress-linear v-if="loading" color="primary" /> <v-card-title> Versioning </v-card-title> @@ -55,6 +55,7 @@ </template> <script> +import TableService from '@/api/table.service' import { Bar } from 'vue-chartjs/legacy' import { Chart as ChartJS, Title, Tooltip, BarElement, CategoryScale, LinearScale, LogarithmicScale } from 'chart.js' import { formatTimestampUTC, formatTimestampUTCLabel } from '@/utils' @@ -69,7 +70,6 @@ export default { return { formValid: false, loading: false, - error: false, datetime: null, chartData: { labels: [], @@ -89,22 +89,6 @@ export default { totalChanges: 0 } }, - computed: { - loadingColor () { - return this.error ? 'error' : 'primary' - }, - token () { - return this.$store.state.token - }, - config () { - if (this.token === null) { - return {} - } - return { - headers: { Authorization: `Bearer ${this.token}` } - } - } - }, mounted () { this.loadHistory() }, @@ -129,29 +113,25 @@ export default { time: this.datetime }) }, - async loadHistory () { - try { - this.loading = true - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/history`, this.config) - this.error = false - this.chartData.labels = res.data.map(function (d, idx) { - if (idx === 0) { - return 'Origin' - } - return formatTimestampUTCLabel(d.timestamp) + loadHistory () { + this.loading = true + TableService.findHistory(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id) + .then((history) => { + this.chartData.labels = history.map(function (d, idx) { + if (idx === 0) { + return 'Origin' + } + return formatTimestampUTCLabel(d.timestamp) + }) + this.chartData.dates = history.map(d => formatTimestampUTC(d.timestamp)) + this.chartData.datasets = [{ + backgroundColor: this.$vuetify.theme.themes.light.primary, + data: history.map(d => d.total) + }] + }) + .finally(() => { + this.loading = false }) - this.chartData.dates = res.data.map(d => formatTimestampUTC(d.timestamp)) - this.chartData.datasets = [{ - backgroundColor: this.$vuetify.theme.themes.light.primary, - data: res.data.map(d => d.total) - }] - // this.totalChanges = this.res.data.length - console.debug('history', this.chartData) - } catch (err) { - this.error = true - console.error('failed to load table history', err) - } - this.loading = false } } } diff --git a/dbrepo-ui/components/identifier/Banner.vue b/dbrepo-ui/components/identifier/Banner.vue index ece371663f1d888d8c4ee90c316cc45a163c1eb0..990bf61f5c6dda27ee8998dddc5aee261be368a6 100644 --- a/dbrepo-ui/components/identifier/Banner.vue +++ b/dbrepo-ui/components/identifier/Banner.vue @@ -15,7 +15,7 @@ export default { }, computed: { baseUrl () { - return `${location.protocol}//${location.host}` + return `${this.$config.baseUrl}` }, baseDoi () { return this.$config.doiUrl diff --git a/dbrepo-ui/components/identifier/Citation.vue b/dbrepo-ui/components/identifier/Citation.vue index 9d3037905c1e43eb593f003c21fab7ec4f9bc88f..479243cc458e930387e90cbacb37287607136248 100644 --- a/dbrepo-ui/components/identifier/Citation.vue +++ b/dbrepo-ui/components/identifier/Citation.vue @@ -58,17 +58,9 @@ export default { citation: null } }, - computed: { - config () { - return { - headers: { Accept: 'text/bibliography;style=apa' }, - progress: false - } - } - }, watch: { - style (newVal, _) { - this.loadCitation(newVal) + style () { + this.loadCitation(this.style) }, pid () { this.loadCitation(this.style) @@ -79,25 +71,18 @@ export default { this.loadCitation(null) }, methods: { - async loadCitation (accept) { + loadCitation (accept) { if (!this.pid) { return } this.loading = true - try { - const config = this.config - if (accept != null) { - config.headers.Accept = accept - } - const res = await this.$axios.get(`/api/pid/${this.pid}`, config) - this.citation = res.data - console.debug('citation', this.citation) - } catch (err) { - console.error('Could not cite identifier', err) - this.$toast.error('Could not cite identifier') - this.error = true - } - this.loading = false + IdentifierService.findAccept(this.pid, accept) + .then((citation) => { + this.citation = citation + }) + .finally(() => { + this.loading = false + }) } } } diff --git a/dbrepo-ui/components/query/Results.vue b/dbrepo-ui/components/query/Results.vue index d04b59378614effed3b3a6595bbb912f13c164bc..1f30ee22566bf88f9184576f86ffac03c42c8967 100644 --- a/dbrepo-ui/components/query/Results.vue +++ b/dbrepo-ui/components/query/Results.vue @@ -9,6 +9,7 @@ </template> <script> +import QueryService from '@/api/query.service' export default { props: { type: { @@ -32,24 +33,6 @@ export default { total: -1 } }, - computed: { - token () { - return this.$store.state.token - }, - config () { - if (this.token === null) { - return {} - } - return { - headers: { Authorization: `Bearer ${this.token}` } - } - }, - executeUrl () { - const page = 0 - const urlParams = `page=${page}&size=${this.options.itemsPerPage}` - return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/query?${urlParams}` - } - }, watch: { options: { /* keep */ handler () { @@ -59,28 +42,20 @@ export default { } }, methods: { - async executeFirstTime (parent, sql, timestamp) { + executeFirstTime (parent, sql, timestamp) { this.loading++ - try { - const res = await this.$axios.post(this.executeUrl, { statement: sql, timestamp }, this.config) - console.debug('query result', res.data) - this.$toast.success('Successfully executed query') - this.mapResults(res.data) - parent.resultId = res.data.id - } catch (error) { - console.error('Failed to execute query', error) - const { status, data } = error.response - const { message, code } = data - if (status === 504) { - console.error('Failed to execute query: container not online', code) - this.$toast.error('Failed to execute query: container not online') - } else { - console.error('Failed to execute query', code) - this.$toast.error('Failed to execute query: ' + message) - } - this.error = true + const payload = { + statement: sql, + timestamp } - this.loading-- + QueryService.execute(this.$route.params.container_id, this.$route.params.database_id, payload, 0, this.options.itemsPerPage) + .then((result) => { + this.mapResults(result) + parent.resultId = result.id + }) + .finally(() => { + this.loading-- + }) }, buildHeaders (firstLine) { return Object.keys(firstLine).map(k => ({ @@ -89,42 +64,53 @@ export default { sortable: false })) }, - reExecuteUrl (resultId) { - const page = this.options.page - 1 - const urlParams = `page=${page}&size=${this.options.itemsPerPage}` - return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/${this.type}/${resultId}/data?${urlParams}` - }, - reExecuteCountUrl (resultId) { - return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/${this.type}/${resultId}/data/count` - }, - async reExecute (id) { + reExecute (id) { if (id === null) { return } this.loading++ - try { - const res = await this.$axios.get(this.reExecuteUrl(id), this.config) - this.mapResults(res.data) - this.id = id - } catch (error) { - console.error('failed to execute query', error) - this.error = true + if (this.type === 'query') { + QueryService.reExecuteQuery(this.$route.params.container_id, this.$route.params.database_id, this.resultId, 0, this.options.itemsPerPage) + .then((result) => { + this.mapResults(result) + this.id = id + }) + .finally(() => { + this.loading-- + }) + } else { + QueryService.reExecuteView(this.$route.params.container_id, this.$route.params.database_id, this.resultId, 0, this.options.itemsPerPage) + .then((result) => { + this.mapResults(result) + this.id = id + }) + .finally(() => { + this.loading-- + }) } - this.loading-- }, - async reExecuteCount (id) { + reExecuteCount (id) { if (id === null) { return } this.loading++ - try { - const res = await this.$axios.get(this.reExecuteCountUrl(id), this.config) - this.total = res.data - } catch (error) { - console.error('failed to execute query count', error) - this.error = true + if (this.type === 'query') { + QueryService.reExecuteQueryCount(this.$route.params.container_id, this.$route.params.database_id, this.resultId) + .then((count) => { + this.total = count + }) + .finally(() => { + this.loading-- + }) + } else { + QueryService.reExecuteViewCount(this.$route.params.container_id, this.$route.params.database_id, this.resultId) + .then((count) => { + this.total = count + }) + .finally(() => { + this.loading-- + }) } - this.loading-- }, mapResults (data) { if (data.result.length) { diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue index 6c32c271c6cb43916edf25eb1338d0321d547d30..82ed12293a3f0a75108dff7f901ac7747ce69e65 100644 --- a/dbrepo-ui/layouts/default.vue +++ b/dbrepo-ui/layouts/default.vue @@ -301,7 +301,7 @@ export default { return } this.loading = true - IdentifierService.findOne(this.database.identifier.id) + IdentifierService.find(this.database.identifier.id) .then((identifier) => { this.database.identifier = identifier this.$store.commit('SET_DATABASE', this.database) diff --git a/dbrepo-ui/nuxt.config.js b/dbrepo-ui/nuxt.config.js index 5cf3275c2760b0410c480a6c20105749327b25a5..9c06aff0c1b6dadcfebcf8354041a1a89ccb1562 100644 --- a/dbrepo-ui/nuxt.config.js +++ b/dbrepo-ui/nuxt.config.js @@ -1,6 +1,6 @@ import path from 'path' import colors from 'vuetify/es5/util/colors' -import { api, icon, search, clientSecret, title, sandbox, logo, version, defaultPublisher, doiUrl } from './config' +import { api, icon, search, clientSecret, title, sandbox, logo, version, defaultPublisher, doiUrl, baseUrl } from './config' const proxy = {} @@ -93,7 +93,8 @@ export default { logo, clientSecret, defaultPublisher, - doiUrl + doiUrl, + baseUrl }, serverMiddleware: [ @@ -109,7 +110,7 @@ export default { primary: colors.blue.darken2, accent: colors.amber.darken3, secondary: colors.blueGrey.base, - info: colors.amber.lighten4, + info: colors.amber.lighten1, code: colors.grey.lighten4, warning: colors.orange.lighten2, error: colors.red.base /* is used by forms */, diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue index 59e8aa9acf679c91e21f5f02161e790a4ace0cb5..dfd27fc763f40c273408f55263ea0ecd76647700 100644 --- a/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue +++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue @@ -41,6 +41,7 @@ <script> import TimeTravel from '@/components/dialogs/TimeTravel' import TableToolbar from '@/components/TableToolbar' +import TableService from '@/api/table.service' import { formatTimestampUTC, formatDateUTC, formatTimestamp } from '@/utils' export default { @@ -98,17 +99,6 @@ export default { table () { return this.$store.state.table }, - config () { - if (this.token === null) { - return { - headers: {}, - progress: false - } - } - return { - headers: { Authorization: `Bearer ${this.token}` } - } - }, user () { return this.$store.state.user }, @@ -118,17 +108,6 @@ export default { access () { return this.$store.state.access }, - downloadConfig () { - if (this.token === null) { - return { - responseType: 'text' - } - } - return { - headers: { Authorization: `Bearer ${this.token}` }, - responseType: 'text' - } - }, versionColor () { if (this.version === null) { return 'secondary white--text' @@ -193,28 +172,35 @@ export default { this.loadProperties() }, methods: { - async download () { + download () { this.downloadLoading = true - try { - let exportUrl = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/export` - if (this.version) { - exportUrl += `?timestamp=${this.versionISO}` - } - const res = await this.$axios.get(exportUrl, this.downloadConfig) - console.debug('export table', res) - const url = window.URL.createObjectURL(new Blob([res.data])) - const link = document.createElement('a') - link.href = url - link.setAttribute('download', 'table.csv') - document.body.appendChild(link) - link.click() - } catch (error) { - console.error('Failed to export table', error) - const { message } = error.response - this.$toast.error('Failed to export table: ' + message) - this.error = true + if (!this.version) { + TableService.exportData(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id) + .then((data) => { + const url = window.URL.createObjectURL(new Blob([data])) + const link = document.createElement('a') + link.href = url + link.setAttribute('download', 'table.csv') + document.body.appendChild(link) + link.click() + }) + .finally(() => { + this.downloadLoading = false + }) + } else { + TableService.exportData(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id, this.versionISO) + .then((data) => { + const url = window.URL.createObjectURL(new Blob([data])) + const link = document.createElement('a') + link.href = url + link.setAttribute('download', `table_${this.versionISO}.csv`) + document.body.appendChild(link) + link.click() + }) + .finally(() => { + this.downloadLoading = false + }) } - this.downloadLoading = false }, pick () { if (this.$refs.timeTravel !== undefined) { @@ -273,71 +259,37 @@ export default { this.loadData() this.loadCount() }, - async loadData () { - try { - this.loadingData++ - const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data?page=${this.options.page - 1}&size=${this.options.itemsPerPage}×tamp=${this.versionISO || this.lastReload.toISOString()}` - if (this.version !== null) { - console.info('versioning active', this.version) - } - const res = await this.$axios.get(url, this.config) - this.rows = res.data.result.map((row) => { - for (const col in row) { - const columnDefinition = this.dateColumns.filter(c => c.internal_name === col) - if (columnDefinition.length > 0) { - if (columnDefinition[0].column_type === 'date') { - row[col] = formatDateUTC(row[col]) - } else if (columnDefinition[0].column_type === 'timestamp') { - row[col] = formatTimestampUTC(row[col]) + loadData () { + this.loadingData++ + TableService.data(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id, (this.options.page - 1), this.options.itemsPerPage, (this.versionISO || this.lastReload.toISOString())) + .then((data) => { + this.rows = data.result.map((row) => { + for (const col in row) { + const columnDefinition = this.dateColumns.filter(c => c.internal_name === col) + if (columnDefinition.length > 0) { + if (columnDefinition[0].column_type === 'date') { + row[col] = formatDateUTC(row[col]) + } else if (columnDefinition[0].column_type === 'timestamp') { + row[col] = formatTimestampUTC(row[col]) + } } } - } - return row + return row + }) + }) + .finally(() => { + this.loadingData-- }) - console.debug('rows', this.rows) - } catch (error) { - console.error('Failed to load data', error) - this.error = true - this.loadProgress = 100 - const { status, data } = error.response - const { message, code } = data - if (status === 423) { - console.error('Database is offline', code) - this.$toast.error('Database is offline: ' + message) - } else { - console.error('Failed to load data', code) - this.$toast.error('Failed to load data: ' + message) - } - } finally { - this.loadingData-- - } }, - async loadCount () { - try { - this.loadingData++ - const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data/count?timestamp=${this.versionISO || this.lastReload.toISOString()}` - if (this.version !== null) { - console.info('versioning active', this.version) - } - const res = await this.$axios.get(url, this.config) - this.total = res.data - console.info('total', this.total) - } catch (error) { - console.error('Failed to load count', error) - this.error = true - this.loadProgress = 100 - const { status, data } = error.response - const { message, code } = data - if (status === 423) { - console.error('Database is offline', code) - this.$toast.error('Database is offline: ' + message) - } else { - console.error('Failed to load data', code) - this.$toast.error('Failed to load data: ' + message) - } - } finally { - this.loadingData-- - } + loadCount () { + this.loadingData++ + TableService.dataCount(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.table_id, (this.versionISO || this.lastReload.toISOString())) + .then((count) => { + this.total = count + }) + .finally(() => { + this.loadingData-- + }) }, simulateProgress () { if (this.loadProgress !== 0) { diff --git a/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue b/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue index 55930ae93e6856d48598213f02e4ded25c69ba24..9d95d70e3df1c8fdf6bbdd1d11e97379f4cd8ae8 100644 --- a/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue +++ b/dbrepo-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue @@ -103,7 +103,9 @@ </div> </template> <script> -import { formatTimestampUTCLabel, formatUser } from '@/utils' +import { formatTimestampUTCLabel } from '@/utils' +import DatabaseService from '@/api/database.service' +import UserMapper from '@/api/user.mapper' export default { data () { @@ -163,7 +165,7 @@ export default { if (!this.view) { return null } - return formatUser(this.view.creator) + return UserMapper.userToFullName(this.view.creator) } }, mounted () { @@ -171,20 +173,15 @@ export default { this.loadResult(this.$route.params.view_id) }, methods: { - async loadView () { + loadView () { this.loadingView = true - try { - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/view/${this.$route.params.view_id}`, this.config) - console.debug('view', res.data) - this.view = res.data - } catch (err) { - if (err.response.status !== 401 && err.response.status !== 405) { - console.error('Could not load view', err) - this.$toast.error('Could not load view') - } - this.error = true - } - this.loadingView = false + DatabaseService.findView(this.$route.params.container_id, this.$route.params.database_id, this.$route.params.view_id) + .then((view) => { + this.view = view + }) + .then(() => { + this.loadingView = false + }) }, loadResult (viewId) { if (!viewId) { diff --git a/dbrepo-ui/pages/login.vue b/dbrepo-ui/pages/login.vue index d7d26e64a919a0c98d63db1a0973502f903eca92..23944a38039347ecd81d7cb6834750c4785c6a7c 100644 --- a/dbrepo-ui/pages/login.vue +++ b/dbrepo-ui/pages/login.vue @@ -1,12 +1,12 @@ <template> <div> - <v-toolbar flat> + <v-toolbar v-if="!token" flat> <v-toolbar-title> Login </v-toolbar-title> </v-toolbar> - <v-form ref="form" v-model="valid" @submit.prevent="submit"> - <v-card v-if="!token" flat tile> + <v-form v-if="!token" ref="form" v-model="valid" @submit.prevent="submit"> + <v-card flat tile> <v-card-text> <v-alert border="left" @@ -53,7 +53,6 @@ </v-card-actions> </v-card> </v-form> - <p v-if="token">Already logged-in</p> </div> </template> @@ -72,9 +71,6 @@ export default { } }, computed: { - loadingColor () { - return this.error ? 'red lighten-2' : 'primary' - }, token () { return this.$store.state.token }, @@ -83,17 +79,11 @@ export default { }, user () { return this.$store.state.user - }, - clientSecret () { - return this.$config.clientSecret - }, - config () { - if (this.token === null) { - return {} - } - return { - headers: { Authorization: `Bearer ${this.token}` } - } + } + }, + mounted () { + if (this.token) { + this.$router.push('/container') } }, methods: { @@ -106,10 +96,10 @@ export default { .then(() => { const userId = UserMapper.tokenToUserId(this.token) UserService.findOne(userId) - .then((user) => { + .then(async (user) => { this.$store.commit('SET_USER', user) this.$vuetify.theme.dark = user.attributes.theme_dark - this.$router.push('/container') + await this.$router.push('/container') }) }) .catch(() => { @@ -118,9 +108,6 @@ export default { }, signup () { this.$router.push('/signup') - }, - forgot () { - this.$router.push('/forgot') } } } diff --git a/dbrepo-ui/pages/search/index.vue b/dbrepo-ui/pages/search/index.vue index d13f084ad5eaee140ee3a3503b7eb9c66aa46757..b812900945a5d81897cddc251086bebdc311db9c 100644 --- a/dbrepo-ui/pages/search/index.vue +++ b/dbrepo-ui/pages/search/index.vue @@ -33,6 +33,7 @@ </template> <script> +import SearchService from '@/api/search.service' export default { data () { return { @@ -58,14 +59,6 @@ export default { return `${this.results.length} results` } return `${this.results.length} result` - }, - elasticConfig () { - return { - auth: { - username: 'elastic', - password: this.$config.elasticPassword - } - } } }, watch: { @@ -90,20 +83,18 @@ export default { } }, methods: { - async retrieve () { + retrieve () { if (this.loading) { return } this.loading = true - try { - const res = await this.$axios.get(`/retrieve/_all/_search?q=${this.query}*&terminate_after=50`, this.elasticConfig) - console.info('search results', res.data.hits.total.value) - console.debug('search results for', this.$route.query.q, 'are', res.data.hits.hits) - this.results = res.data.hits.hits.map(h => h._source) - } catch (err) { - console.error('Failed to load search results', err) - } - this.loading = false + SearchService.search(this.query) + .then((hits) => { + this.results = hits.map(h => h._source) + }) + .finally(() => { + this.loading = false + }) }, isDatabase (item) { if (!item) {