From b5ca0db529a8921c1f88ad6cd09e2f30fe98c553 Mon Sep 17 00:00:00 2001
From: Martin Weise <martin.weise@tuwien.ac.at>
Date: Tue, 8 Feb 2022 14:50:44 +0100
Subject: [PATCH] Last commit, deploy to the public instance

Former-commit-id: 67804dd934f65b796501e186b753a194ce075995
---
 .../tuwien/endpoints/IdentifierEndpoint.java  |  1 +
 .../tuwien/repository/jpa/UserRepository.java | 14 +++++
 .../at/tuwien/service/IdentifierService.java  |  6 ++-
 .../service/impl/IdentifierServiceImpl.java   |  2 +
 .../at/tuwien/endpoint/TableDataEndpoint.java | 25 +--------
 .../java/at/tuwien/mapper/QueryMapper.java    |  2 +-
 fda-ui/components/dialogs/PersistQuery.vue    | 28 +++++++++-
 fda-ui/components/dialogs/TimeTravel.vue      | 31 ++++-------
 fda-ui/components/query/Builder.vue           | 49 +++++++++++------
 .../_database_id/query/_query_id/index.vue    | 54 +++++++++++++------
 .../_database_id/table/_table_id/index.vue    | 44 ++++++++-------
 fda-ui/pages/container/index.vue              | 21 ++++----
 fda-ui/pages/pid/_pid_id/index.vue            | 20 ++++++-
 13 files changed, 190 insertions(+), 107 deletions(-)
 create mode 100644 fda-identifier-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java

diff --git a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
index 18f795121b..40858946ca 100644
--- a/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
+++ b/fda-identifier-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java
@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
+import java.security.Principal;
 import java.util.List;
 import java.util.stream.Collectors;
 
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java b/fda-identifier-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java
new file mode 100644
index 0000000000..08f813da41
--- /dev/null
+++ b/fda-identifier-service/services/src/main/java/at/tuwien/repository/jpa/UserRepository.java
@@ -0,0 +1,14 @@
+package at.tuwien.repository.jpa;
+
+import at.tuwien.entities.user.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface UserRepository extends JpaRepository<User, Long> {
+
+    Optional<User> findByUsername(String username);
+
+    Optional<User> findByEmail(String email);
+
+}
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java b/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java
index 7c0b73a5d7..4fd7bc0393 100644
--- a/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java
+++ b/fda-identifier-service/services/src/main/java/at/tuwien/service/IdentifierService.java
@@ -4,9 +4,11 @@ import at.tuwien.api.identifier.IdentifierDto;
 import at.tuwien.api.identifier.VisibilityTypeDto;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.exception.*;
+import org.bouncycastle.pqc.math.linearalgebra.PolynomialRingGF2;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.security.Principal;
 import java.util.List;
 
 @Service
@@ -40,7 +42,9 @@ public interface IdentifierService {
      * @return The created identifier from the metadata database if successful.
      * @throws IdentifierPublishingNotAllowedException When the visibility is not self.
      */
-    Identifier create(Long containerId, Long databaseId, IdentifierDto data) throws IdentifierPublishingNotAllowedException, QueryNotFoundException, RemoteUnavailableException, IdentifierAlreadyExistsException;
+    Identifier create(Long containerId, Long databaseId, IdentifierDto data)
+            throws IdentifierPublishingNotAllowedException, QueryNotFoundException, RemoteUnavailableException,
+            IdentifierAlreadyExistsException;
 
     /**
      * Finds an identifier by given id in the metadata database.
diff --git a/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java b/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
index 3595be08de..b16e034374 100644
--- a/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
+++ b/fda-identifier-service/services/src/main/java/at/tuwien/service/impl/IdentifierServiceImpl.java
@@ -5,6 +5,7 @@ import at.tuwien.api.identifier.IdentifierDto;
 import at.tuwien.api.identifier.VisibilityTypeDto;
 import at.tuwien.entities.identifier.Identifier;
 import at.tuwien.entities.identifier.VisibilityType;
+import at.tuwien.entities.user.User;
 import at.tuwien.exception.*;
 import at.tuwien.gateway.QueryServiceGateway;
 import at.tuwien.mapper.IdentifierMapper;
@@ -17,6 +18,7 @@ import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.security.Principal;
 import java.util.List;
 import java.util.Optional;
 
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
index 121bc46c04..d322f13acd 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java
@@ -79,7 +79,7 @@ public class TableDataEndpoint {
                 .body(queryService.insert(id, databaseId, tableId, data));
     }
 
-    @GetMapping
+    @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD})
     @Transactional(readOnly = true)
     @ApiOperation(value = "Get values", notes = "Get Data from a Table in the database.")
     @ApiResponses({
@@ -116,28 +116,5 @@ public class TableDataEndpoint {
                 .body(response);
     }
 
-    @RequestMapping(method = RequestMethod.HEAD)
-    @Transactional(readOnly = true)
-    @ApiOperation(value = "Get values", notes = "Get Data Count from a Table in the database.")
-    @ApiResponses({
-            @ApiResponse(code = 200, message = "Get data from the table."),
-            @ApiResponse(code = 401, message = "Not authorized to update tables."),
-            @ApiResponse(code = 404, message = "The table is not found in database."),
-            @ApiResponse(code = 405, message = "The connection to the database was unsuccessful."),
-    })
-    public ResponseEntity<QueryResultDto> getCount(@NotNull @PathVariable("id") Long id,
-                                                   @NotNull @PathVariable("databaseId") Long databaseId,
-                                                   @NotNull @PathVariable("tableId") Long tableId,
-                                                   @RequestParam(required = false) Instant timestamp)
-            throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException,
-            TableMalformedException, ContainerNotFoundException {
-        final BigInteger count = queryService.count(id, databaseId, tableId, timestamp);
-        final HttpHeaders headers = new HttpHeaders();
-        headers.set("FDA-COUNT", count.toString());
-        return ResponseEntity.ok()
-                .headers(headers)
-                .build();
-    }
-
 
 }
diff --git a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java
index e6b3e69579..1efc7caacd 100644
--- a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java
+++ b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java
@@ -225,7 +225,7 @@ public interface QueryMapper {
         if (data == null) {
             return null;
         }
-        log.debug("map data {} to table column {}", data, column);
+        log.trace("map data {} to table column {}", data, column);
         switch (column.getColumnType()) {
             case BLOB:
                 log.trace("mapping {} to blob", data);
diff --git a/fda-ui/components/dialogs/PersistQuery.vue b/fda-ui/components/dialogs/PersistQuery.vue
index 5bb38f294f..fb3b827801 100644
--- a/fda-ui/components/dialogs/PersistQuery.vue
+++ b/fda-ui/components/dialogs/PersistQuery.vue
@@ -93,9 +93,19 @@ export default {
   computed: {
     loadingColor () {
       return this.error ? 'red lighten-2' : 'primary'
+    },
+    token () {
+      return this.$store.state.token
+    },
+    headers () {
+      if (this.token === null) {
+        return null
+      }
+      return { Authorization: `Bearer ${this.token}` }
     }
   },
   beforeMount () {
+    this.loadUser()
   },
   methods: {
     cancel () {
@@ -107,11 +117,12 @@ export default {
       })
     },
     async persist () {
-      console.debug('identifier data', this.identifier)
       this.loading = true
       let res
       try {
-        res = await this.$axios.post(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/identifier`, this.identifier)
+        res = await this.$axios.post(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/identifier`, this.identifier, {
+          headers: this.headers
+        })
         console.debug('persist', res.data)
       } catch (err) {
         this.$toast.error('Failed to persist query')
@@ -119,6 +130,19 @@ export default {
       }
       this.$toast.success('Query persisted.')
       this.$emit('close')
+    },
+    async loadUser () {
+      this.loading = true
+      let res
+      try {
+        res = await this.$axios.put('/api/auth', null, {
+          headers: this.headers
+        })
+        console.debug('user data', res.data)
+      } catch (err) {
+        this.$toast.error('Failed load user data')
+        console.error('load user data failed', err)
+      }
     }
   }
 }
diff --git a/fda-ui/components/dialogs/TimeTravel.vue b/fda-ui/components/dialogs/TimeTravel.vue
index 45258f63f9..49993afbe0 100644
--- a/fda-ui/components/dialogs/TimeTravel.vue
+++ b/fda-ui/components/dialogs/TimeTravel.vue
@@ -10,7 +10,7 @@
       </v-card-subtitle>
       <v-card-text>
         <v-date-picker
-          v-model="picker"
+          v-model="date"
           no-title />
         <v-time-picker
           v-model="time"
@@ -33,7 +33,7 @@
         <v-btn
           id="version"
           class="mb-2"
-          :disabled="version === null || version === undefined"
+          :disabled="date === null || time === null"
           color="primary"
           @click="pick">
           Pick
@@ -50,8 +50,8 @@ export default {
       formValid: false,
       loading: false,
       error: false,
-      version: null,
-      versions: []
+      date: null,
+      time: null
     }
   },
   computed: {
@@ -59,9 +59,6 @@ export default {
       return this.error ? 'red lighten-2' : 'primary'
     }
   },
-  mounted () {
-    this.loadVersions()
-  },
   methods: {
     cancel () {
       this.$parent.$parent.$parent.$parent.pickVersionDialog = false
@@ -72,25 +69,19 @@ export default {
       })
     },
     reset () {
-      this.$parent.$parent.$parent.$parent.version = { id: null, created: null }
+      this.$parent.$parent.$parent.$parent.version = null
       this.cancel()
     },
     pick () {
-      this.$parent.$parent.$parent.$parent.version = this.versions[this.version]
+      this.$parent.$parent.$parent.$parent.version = this.formatDate()
       this.cancel()
     },
-    async loadVersions () {
-      this.loading = true
-      try {
-        const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/version`
-        const res = await this.$axios.get(url)
-        this.versions = res.data
-        console.debug('versions', this.versions)
-      } catch (err) {
-        console.error('Failed to get versions', err)
-        this.$toast.error('Failed to get versions')
+    formatDate () {
+      if (this.date === null || this.time === null) {
+        return null
       }
-      this.loading = false
+      console.debug('selected date', this.date, 'time', this.time)
+      return Date.parse(this.date + ' ' + this.time)
     }
   }
 }
diff --git a/fda-ui/components/query/Builder.vue b/fda-ui/components/query/Builder.vue
index b75952bb7c..2577ed9979 100644
--- a/fda-ui/components/query/Builder.vue
+++ b/fda-ui/components/query/Builder.vue
@@ -8,10 +8,10 @@
         <v-toolbar-title>Create Query</v-toolbar-title>
         <v-spacer />
         <v-toolbar-title>
-          <v-btn :disabled="!valid" color="blue-grey white--text" @click="save">
+          <v-btn :disabled="!valid || !token" color="blue-grey white--text" @click="save">
             Save without execution
           </v-btn>
-          <v-btn :disabled="!valid" color="primary" @click="execute">
+          <v-btn :disabled="!valid || !token" color="primary" @click="execute">
             <v-icon left>mdi-run</v-icon>
             Execute
           </v-btn>
@@ -115,6 +115,15 @@ export default {
     },
     tableId () {
       return this.table.id
+    },
+    token () {
+      return this.$store.state.token
+    },
+    headers () {
+      if (this.token === null) {
+        return null
+      }
+      return { Authorization: `Bearer ${this.token}` }
     }
   },
   watch: {
@@ -125,18 +134,21 @@ export default {
       }
     }
   },
-  async mounted () {
-    // XXX same as in TableList
-    try {
-      const res = await this.$axios.get(
-        `/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table`)
-      this.tables = res.data
-      console.debug('tables', this.tables)
-    } catch (err) {
-      this.$toast.error('Could not list table.')
-    }
+  beforeMount () {
+    this.loadTables()
   },
   methods: {
+    async loadTables () {
+      try {
+        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table`, {
+          headers: this.headers
+        })
+        this.tables = res.data
+        console.debug('tables', this.tables)
+      } catch (err) {
+        this.$toast.error('Could not list table.')
+      }
+    },
     async execute () {
       this.$refs.form.validate()
       this.loading = true
@@ -149,7 +161,9 @@ export default {
           })]
         }
         console.debug('send data', data)
-        const res = await this.$axios.put(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table/${this.tableId}/query/execute`, data)
+        const res = await this.$axios.put(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/query/execute`, data, {
+          headers: this.headers
+        })
         console.debug('query result', res)
         this.$toast.success('Successfully executed query')
         this.loading = false
@@ -169,13 +183,14 @@ export default {
       const query = this.query.sql.replaceAll('`', '')
       this.loading = true
       try {
-        const res = await this.$axios.post(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table/${this.tableId}/query/save`, {
-          statement: query
+        const res = await this.$axios.post(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/query/save`, { statement: query }, {
+          headers: this.headers
         })
         console.debug('query result', res)
         this.$toast.success('Successfully saved query')
         this.loading = false
         this.queryId = res.data.id
+        this.$router.push(`/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/query/${this.queryId}`)
       } catch (err) {
         console.error('query save', err)
         this.$toast.error('Could not save query')
@@ -204,7 +219,9 @@ export default {
     async loadColumns () {
       const tableId = this.table.id
       try {
-        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table/${tableId}`)
+        const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table/${tableId}`, {
+          headers: this.headers
+        })
         this.tableDetails = res.data
         this.buildQuery()
       } catch (err) {
diff --git a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue
index 6ce715e645..d99119cc37 100644
--- a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue
+++ b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue
@@ -4,23 +4,21 @@
       <v-toolbar-title>{{ identifier.title }}</v-toolbar-title>
       <v-spacer />
       <v-toolbar-title>
-        <v-btn color="blue-grey white--text" class="mr-2" :disabled="!query.execution || identifier.id" @click.stop="persistQueryDialog = true">
+        <v-btn color="blue-grey white--text" class="mr-2" :disabled="!query.execution || identifier.id || !token" @click.stop="persistQueryDialog = true">
           <v-icon left>mdi-fingerprint</v-icon> Persist
         </v-btn>
-        <v-btn color="primary" disabled>
+        <v-btn color="primary" :disabled="!token">
           <v-icon left>mdi-run</v-icon> Re-Execute
         </v-btn>
       </v-toolbar-title>
     </v-toolbar>
-    <v-card flat>
-      <v-card-title v-if="!loading">
-        <span v-if="query.execution != null">
-          sha256:{{ query.query_hash }}
-        </span>
+    <v-card v-if="!loading" flat>
+      <v-card-title>
+        Query Information
       </v-card-title>
-      <v-card-subtitle v-if="!loading">
-        <span v-if="query.execution != null">
-          Executed {{ formatDate(query.execution) }}
+      <v-card-subtitle>
+        <span v-if="query.created != null">
+          Created {{ formatDate(query.created) }}
         </span>
         <span v-if="query.execution == null">
           Query was never executed
@@ -31,9 +29,8 @@
           <strong>Query</strong>
         </p>
         <div>
-          <p>Persistent Identifier</p>
-          <p v-if="identifier.id">
-            <code>https://dbrepo.ossdip.at/pid/{{ identifier.id }}</code>
+          <p>
+            Persistent Identifier: <code v-if="identifier.id">https://dbrepo.ossdip.at/pid/{{ identifier.id }}</code><span v-if="!identifier.id">(empty)</span>
           </p>
           <p>Statement</p>
           <v-alert
@@ -58,13 +55,23 @@
           <strong>Result</strong>
         </p>
         <p>
-          Hash: <code>{{ query.result_hash }}</code>
+          Hash: <code v-if="query.result_hash">{{ query.result_hash }}</code><span v-if="!query.result_hash">(empty)</span>
+        </p>
+        <p>
+          Rows: <code v-if="query.result_number">{{ query.result_number }}</code><span v-if="!query.result_number">(empty)</span>
+        </p>
+        <p>
+          Executed: <code v-if="query.execution">{{ query.execution }}</code><span v-if="!query.execution">(empty)</span>
+        </p>
+        <p class="mt-2">
+          <strong>Creator</strong>
         </p>
         <p>
-          Rows: <code>{{ query.result_number }}</code>
+          Username: <code v-if="query.username">{{ query.username }}</code><span v-if="!query.username">(empty)</span>
         </p>
       </v-card-text>
     </v-card>
+    <v-breadcrumbs :items="items" class="pa-0 mt-2" />
     <v-dialog
       v-model="persistQueryDialog"
       persistent
@@ -84,6 +91,12 @@ export default {
   },
   data () {
     return {
+      items: [
+        { text: 'Databases', href: '/container' },
+        { text: `${this.$route.params.database_id}`, href: `/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}` },
+        { text: 'Queries', href: `/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/query` },
+        { text: `${this.$route.params.query_id}`, href: `/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/query/${this.$route.params.query_id}` }
+      ],
       query: {
         id: this.$route.params.query_id,
         database_id: null,
@@ -109,6 +122,17 @@ export default {
       loading: true
     }
   },
+  computed: {
+    token () {
+      return this.$store.state.token
+    },
+    headers () {
+      if (this.token === null) {
+        return null
+      }
+      return { Authorization: `Bearer ${this.token}` }
+    }
+  },
   mounted () {
     this.loadMetadata()
   },
diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue
index 25c5f8223d..dca090de21 100644
--- a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue
+++ b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/index.vue
@@ -18,7 +18,7 @@
     <v-toolbar :color="versionColor" flat>
       <v-toolbar-title>
         <strong>Versioning</strong>
-        <span v-if="version.id !== null">{{ version.created }}</span>
+        <span v-if="version !== null">{{ versionFormatted }}</span>
       </v-toolbar-title>
       <v-spacer />
       <v-toolbar-title>
@@ -58,6 +58,7 @@
 </template>
 <script>
 import TimeTravel from '@/components/dialogs/TimeTravel'
+import { format } from 'date-fns'
 
 export default {
   components: {
@@ -73,10 +74,7 @@ export default {
       dateMenu: false,
       timeMenu: false,
       pickVersionDialog: null,
-      version: {
-        id: null,
-        created: null
-      },
+      version: null,
       options: {
         page: 1,
         itemsPerPage: 10
@@ -101,10 +99,16 @@ export default {
     },
     versionColor () {
       console.debug('version', this.version)
-      if (this.version.created === null) {
+      if (this.version === null) {
         return 'grey lighten-1'
       }
       return 'primary white--text'
+    },
+    versionFormatted () {
+      if (this.version === null) {
+        return null
+      }
+      return this.formatDate(this.version)
     }
   },
   watch: {
@@ -139,9 +143,9 @@ export default {
       try {
         this.loading = true
         let 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}`
-        if (this.version.created !== null) {
+        if (this.version !== null) {
           console.info('versioning active', this.version)
-          url += `&timestamp=${this.version.created}`
+          url += `&timestamp=${new Date(this.version).toISOString()}`
         }
         const res = await this.$axios.get(url)
         console.debug('version', this.datetime, 'table data', res.data)
@@ -152,16 +156,17 @@ export default {
       }
       this.loading = false
     },
-    async loadDataCount () {
-      try {
-        this.loading = true
-        const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data`
-        const res = await this.$axios.head(url)
-        console.debug('data count', res.data)
-        this.total = res.data.count
-      } catch (err) {
-        console.error('failed to load total count', err)
-      }
+    loadDataCount () { // TODO
+      // try {
+      //   this.loading = true
+      //   const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data`
+      //   const res = await this.$axios.head(url)
+      //   console.debug('data count', res.data)
+      //   this.total = res.data.count
+      // } catch (err) {
+      //   console.error('failed to load total count', err)
+      // }
+      this.total = 1000000
       this.loading = false
     },
     columnAddition (column) {
@@ -172,6 +177,9 @@ export default {
         return '† '
       }
       return ''
+    },
+    formatDate (d) {
+      return format(new Date(d), 'dd.MM.yyyy HH:mm')
     }
   }
 }
diff --git a/fda-ui/pages/container/index.vue b/fda-ui/pages/container/index.vue
index ed25283940..ac075e9271 100644
--- a/fda-ui/pages/container/index.vue
+++ b/fda-ui/pages/container/index.vue
@@ -18,8 +18,8 @@
           <thead>
             <tr>
               <th>Name</th>
-              <th>Description</th>
               <th>Engine</th>
+              <th>Tables</th>
               <th>Created</th>
             </tr>
           </thead>
@@ -31,15 +31,12 @@
             </tr>
             <tr
               v-for="item in databases"
-              :key="item.id">
-              <td>
-                <v-btn :to="`/container/${item.container_id}/database/${item.id}/info`" icon>
-                  <v-icon>{{ iconSelect }}</v-icon>
-                </v-btn>
-                {{ item.name }}
-              </td>
-              <td>{{ item.description }}</td>
+              :key="item.id"
+              class="database"
+              @click="loadDatabase(item)">
+              <td>{{ item.name }}</td>
               <td>{{ item.engine }}</td>
+              <td></td>
               <td>{{ formatDate(item.created) }}</td>
             </tr>
           </tbody>
@@ -107,6 +104,9 @@ export default {
         this.error = true
       }
     },
+    loadDatabase (database) {
+      this.$router.push(`/container/${database.container_id}/database/${database.id}/info`)
+    },
     trim (s) {
       return s.slice(0, 12)
     },
@@ -131,6 +131,9 @@ export default {
     overflow: hidden;
     text-overflow: ellipsis;
   }
+  .database:hover {
+    cursor: pointer;
+  }
   .color-grey {
     color: #aaa;
   }
diff --git a/fda-ui/pages/pid/_pid_id/index.vue b/fda-ui/pages/pid/_pid_id/index.vue
index 20d0aa7208..24c62a04fa 100644
--- a/fda-ui/pages/pid/_pid_id/index.vue
+++ b/fda-ui/pages/pid/_pid_id/index.vue
@@ -1,5 +1,18 @@
 <template>
-  <div />
+  <div>
+    <v-card>
+      <v-card-title>PID Not Found</v-card-title>
+      <v-card-subtitle>{{ pid }}</v-card-subtitle>
+      <v-card-text>
+        <p>This PID cannot be found in the system. Possible reasons are:</p>
+        <ul>
+          <li>The PID is incorrect in your source.</li>
+          <li>The PID was copied incorrectly.</li>
+          <li>The PID has not been activated yet.</li>
+        </ul>
+      </v-card-text>
+    </v-card>
+  </div>
 </template>
 
 <script>
@@ -7,6 +20,11 @@ export default {
   mounted () {
     this.findPid()
   },
+  computed: {
+    pid () {
+      return this.$route.params.pid_id
+    }
+  },
   methods: {
     async findPid () {
       try {
-- 
GitLab