diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 67204a64caecd0976cb9274a5f7d83b25d3cc1d8..54b7b8edbbe0652379fc74c5a81a58ada8a57e3a 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -277,6 +277,7 @@ services: image: registry.datalab.tuwien.ac.at/dbrepo/ui:1.4.5 environment: NUXT_PUBLIC_API_CLIENT: "${BASE_URL:-http://localhost}" + NUXT_PUBLIC_API_SERVER: "${BASE_URL:-http://localhost}" NUXT_PUBLIC_UPLOAD_CLIENT: "${BASE_URL:-http://localhost}/api/upload/files" depends_on: dbrepo-search-service: diff --git a/.docs/api/gateway-service.md b/.docs/api/gateway-service.md index 923b95a9f30ac9af06bb029682bd67bc7f2f0961..26ad76f092c599fbb865648577eba7b1a283ba72 100644 --- a/.docs/api/gateway-service.md +++ b/.docs/api/gateway-service.md @@ -39,6 +39,12 @@ services: If your TLS private key as a password, you need to specify it in the `dbrepo.conf` file. +### Connection Timeouts + +The reverse proxy has a defined timeout of 90 seconds on all requests (these are also enforced in the +[User Interface](../ui) using the [`axios`](https://www.npmjs.com/package/axios) module). For large databases these +timeouts may need to be increased, e.g. the timeout for creating subsets is by default already increased to 600 seconds. + ### User Interface To serve the [User Interface](../ui/) under different port than `80`, change the port mapping in diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java index 8f5f4a821484d23a51dd5c60a6bba658278fdb14..a7c74946923a36f639ae692e6d153881bcc2401f 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java @@ -86,19 +86,11 @@ public class SubsetEndpoint { schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<List<QueryDto>> list(@NotNull @PathVariable("databaseId") Long databaseId, - @RequestParam(name = "persisted", required = false) Boolean filterPersisted, - Principal principal) + @RequestParam(name = "persisted", required = false) Boolean filterPersisted) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, QueryNotFoundException, NotAllowedException, MetadataServiceException { log.debug("endpoint find subsets in database, databaseId={}, filterPersisted={}", databaseId, filterPersisted); final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); - if (!database.getIsPublic()) { - if (principal == null) { - log.error("Failed to find subsets in database: no access"); - throw new NotAllowedException("Failed to find subsets in database: no access"); - } - metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); - } final List<QueryDto> queries; try { queries = subsetService.findAll(database, filterPersisted); @@ -151,8 +143,7 @@ public class SubsetEndpoint { public ResponseEntity<?> findById(@NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("subsetId") Long subsetId, @NotNull HttpServletRequest httpServletRequest, - @RequestParam(required = false) Instant timestamp, - Principal principal) + @RequestParam(required = false) Instant timestamp) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, QueryNotFoundException, FormatNotAvailableException, StorageUnavailableException, QueryMalformedException, SidecarExportException, StorageNotFoundException, NotAllowedException, UserNotFoundException, @@ -161,13 +152,6 @@ public class SubsetEndpoint { log.debug("endpoint find subset in database, databaseId={}, subsetId={}, accept={}, timestamp={}", databaseId, subsetId, accept, timestamp); final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); - if (!database.getIsPublic()) { - if (principal == null) { - log.error("Failed to find subsets in database: no access"); - throw new NotAllowedException("Failed to find subsets in database: no access"); - } - metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); - } final QueryDto query; try { query = subsetService.findById(database, subsetId); @@ -262,7 +246,7 @@ public class SubsetEndpoint { StorageNotFoundException, QueryStoreInsertException, TableMalformedException, PaginationException, QueryNotSupportedException, NotAllowedException, UserNotFoundException, MetadataServiceException { log.debug("endpoint create subset in database, databaseId={}, data.statement={}, principal.name={}, " + - "page={}, size={}, timestamp={}", databaseId, data.getStatement(), principal.getName(), page, size, + "page={}, size={}, timestamp={}", databaseId, data.getStatement(), principal.getName(), page, size, timestamp); /* check */ endpointValidator.validateDataParams(page, size); @@ -360,21 +344,21 @@ public class SubsetEndpoint { } try { final QueryDto query = subsetService.findById(database, subsetId); - final Long count = subsetService.reExecuteCount(database, query); final HttpHeaders headers = new HttpHeaders(); - headers.set("X-Count", "" + count); headers.set("Access-Control-Expose-Headers", "X-Count"); - if (request.getMethod().equals("GET")) { - final QueryResultDto result = subsetService.reExecute(database, query, page, size, null, null); - result.setId(subsetId); - log.trace("re-execute query resulted in result {}", result); + if (request.getMethod().equals("HEAD")) { + final Long count = subsetService.reExecuteCount(database, query); + headers.set("X-Count", "" + count); return ResponseEntity.ok() .headers(headers) - .body(result); + .build(); } + final QueryResultDto result = subsetService.reExecute(database, query, page, size, null, null); + result.setId(subsetId); + log.trace("re-execute query resulted in result {}", result); return ResponseEntity.ok() .headers(headers) - .build(); + .body(result); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 7bc3a2aa5058abb664bc950e12cb502870266ac3..e26a37b43aaa1b5307cf87d294e1dedc5cef87c4 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -224,8 +224,8 @@ public class TableEndpoint { final HttpHeaders headers = new HttpHeaders(); headers.set("Access-Control-Expose-Headers", "X-Count"); try { - headers.set("X-Count", "" + tableService.getCount(table, timestamp)); if (request.getMethod().equals("HEAD")) { + headers.set("X-Count", "" + tableService.getCount(table, timestamp)); return ResponseEntity.ok() .headers(headers) .build(); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java index e9dbfd5c3a7f3d59088f048398cd2c4f9d9d8f45..e4ddab603685103027dcb6dfad936b882c987294 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java @@ -92,8 +92,7 @@ public class ViewEndpoint { }) public ResponseEntity<List<ViewDto>> getSchema(@NotBlank @PathVariable("databaseId") Long databaseId) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, - ViewMalformedException, ViewNotFoundException, DatabaseMalformedException, ViewSchemaException, - MetadataServiceException { + ViewNotFoundException, DatabaseMalformedException, MetadataServiceException { log.debug("endpoint inspect view schemas, databaseId={}", databaseId); final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); try { @@ -265,20 +264,19 @@ public class ViewEndpoint { metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); } try { - final Long count = viewService.count(view, timestamp); final HttpHeaders headers = new HttpHeaders(); - headers.set("X-Count", "" + count); headers.set("Access-Control-Expose-Headers", "X-Count"); - if (request.getMethod().equals("GET")) { - final QueryResultDto result = viewService.data(view, timestamp, page, size); - log.trace("get view data resulted in result {}", result); + if (request.getMethod().equals("HEAD")) { + headers.set("X-Count", "" + viewService.count(view, timestamp)); return ResponseEntity.ok() .headers(headers) - .body(result); + .build(); } + final QueryResultDto result = viewService.data(view, timestamp, page, size); + log.trace("get view data resulted in result {}", result); return ResponseEntity.ok() .headers(headers) - .build(); + .body(result); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java index d554667467009b396caee8f2800b522f21f8ace6..6a0015b6f61e567fbf65f2e8f97f6c568fe142fa 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java @@ -29,7 +29,6 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.io.InputStream; -import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; @@ -69,7 +68,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { DatabaseNotFoundException, RemoteUnavailableException, SQLException, MetadataServiceException { /* test */ - final List<QueryDto> response = generic_findAllById(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO, null); + final List<QueryDto> response = generic_findAllById(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO); assertEquals(6, response.size()); } @@ -79,17 +78,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseNotFoundException.class, () -> { - generic_findAllById(null, null, null); - }); - } - - @Test - @WithAnonymousUser - public void findAllById_privateNoAccess_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_findAllById(DATABASE_1_ID, DATABASE_1_PRIVILEGED_DTO, null); + generic_findAllById(null, null); }); } @@ -105,7 +94,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_3_PRIVILEGED_DTO); /* test */ - generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.APPLICATION_JSON, null, null); + generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.APPLICATION_JSON, null); } @Test @@ -126,7 +115,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(mock); /* test */ - generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.parseMediaType("text/csv"), null, null); + generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.parseMediaType("text/csv"), null); } @Test @@ -147,7 +136,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(mock); /* test */ - generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.parseMediaType("text/csv"), Instant.now(), null); + generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.parseMediaType("text/csv"), Instant.now()); } @Test @@ -161,7 +150,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseNotFoundException.class, () -> { - generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.APPLICATION_JSON, null, null); + generic_findById(QUERY_5_ID, QUERY_5_DTO, MediaType.APPLICATION_JSON, null); }); } @@ -272,9 +261,6 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ final ResponseEntity<QueryResultDto> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getHeaders().get("X-Count")); - assertEquals(1, response.getHeaders().get("X-Count").size()); - assertEquals(QUERY_5_RESULT_NUMBER, Long.parseLong(response.getHeaders().get("X-Count").get(0))); assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); assertEquals(1, response.getHeaders().get("Access-Control-Expose-Headers").size()); assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); @@ -325,9 +311,6 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* test */ final ResponseEntity<QueryResultDto> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getHeaders().get("X-Count")); - assertEquals(1, response.getHeaders().get("X-Count").size()); - assertEquals(QUERY_1_RESULT_NUMBER, Long.parseLong(response.getHeaders().get("X-Count").get(0))); assertNotNull(response.getBody()); } @@ -464,7 +447,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { }); } - protected List<QueryDto> generic_findAllById(Long databaseId, PrivilegedDatabaseDto database, Principal principal) + protected List<QueryDto> generic_findAllById(Long databaseId, PrivilegedDatabaseDto database) throws DatabaseUnavailableException, NotAllowedException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, SQLException, MetadataServiceException { @@ -481,16 +464,16 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { } /* test */ - final ResponseEntity<List<QueryDto>> response = subsetEndpoint.list(databaseId, null, principal); + final ResponseEntity<List<QueryDto>> response = subsetEndpoint.list(databaseId, null); assertEquals(HttpStatus.OK, response.getStatusCode()); return response.getBody(); } - protected void generic_findById(Long subsetId, QueryDto subset, MediaType accept, Instant timestamp, - Principal principal) throws UserNotFoundException, DatabaseUnavailableException, - StorageUnavailableException, NotAllowedException, QueryMalformedException, QueryNotFoundException, - DatabaseNotFoundException, SidecarExportException, RemoteUnavailableException, FormatNotAvailableException, - StorageNotFoundException, SQLException, MetadataServiceException { + protected void generic_findById(Long subsetId, QueryDto subset, MediaType accept, Instant timestamp) + throws UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, + NotAllowedException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException, + SidecarExportException, RemoteUnavailableException, FormatNotAvailableException, StorageNotFoundException, + SQLException, MetadataServiceException { /* mock */ when(queryService.findById(DATABASE_3_PRIVILEGED_DTO, subsetId)) @@ -499,7 +482,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(accept.toString()); /* test */ - final ResponseEntity<?> response = subsetEndpoint.findById(DATABASE_3_ID, subsetId, mockHttpServletRequest, timestamp, principal); + final ResponseEntity<?> response = subsetEndpoint.findById(DATABASE_3_ID, subsetId, mockHttpServletRequest, timestamp); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java index 22da53c0a908cef6ccd9b387fdbce2214320b626..76c34ef47cd2d13f698f3d552b9aebbbb4175492 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java @@ -158,9 +158,6 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* test */ final ResponseEntity<QueryResultDto> response = tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, httpServletRequest, null); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getHeaders().get("X-Count")); - assertEquals(1, response.getHeaders().get("X-Count").size()); - assertEquals(QUERY_5_RESULT_NUMBER, Long.parseLong(response.getHeaders().get("X-Count").get(0))); assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); assertEquals(1, response.getHeaders().get("Access-Control-Expose-Headers").size()); assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java index b9b814378e9bd537d42c421c3e3fab81d381443f..543b4a5cf299d48a8a4594f95e8586752d4278cc 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java @@ -161,17 +161,12 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); when(httpServletRequest.getMethod()) .thenReturn("GET"); - when(viewService.count(eq(VIEW_1_PRIVILEGED_DTO), any(Instant.class))) - .thenReturn(VIEW_1_DATA_COUNT); when(viewService.data(eq(VIEW_1_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L))) .thenReturn(VIEW_1_DATA_DTO); /* test */ final ResponseEntity<QueryResultDto> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getHeaders().get("X-Count")); - assertEquals(1, response.getHeaders().get("X-Count").size()); - assertEquals(VIEW_1_DATA_COUNT, Long.parseLong(response.getHeaders().get("X-Count").get(0))); assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); assertEquals(1, response.getHeaders().get("Access-Control-Expose-Headers").size()); assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java index 3bf5f2a89a554206775e4138516d49616c4810b1..5898372633b0d63c5b256f6b567f8a8869583ab7 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java @@ -126,12 +126,12 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { } @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"dbrepo_subset_list", "execute-query", "persist-query"}) + @WithMockUser(username = USER_1_USERNAME, authorities = {"execute-query", "persist-query"}) public void prometheusSubsetEndpoint_succeeds() { /* mock */ try { - subsetEndpoint.list(DATABASE_1_ID, null, USER_1_PRINCIPAL); + subsetEndpoint.list(DATABASE_1_ID, null); } catch (Exception e) { /* ignore */ } @@ -151,7 +151,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* ignore */ } try { - subsetEndpoint.findById(DATABASE_1_ID, QUERY_1_ID, new MockHttpServletRequest(), null, USER_1_PRINCIPAL); + subsetEndpoint.findById(DATABASE_1_ID, QUERY_1_ID, new MockHttpServletRequest(), null); } catch (Exception e) { /* ignore */ } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java index 07394c765864671e1d286c242e3902be66a16767..b34053ec88a47a1f02ab99048fffc7e7f0cf35dd 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java @@ -21,6 +21,7 @@ import at.tuwien.api.database.table.constraints.foreign.ForeignKeyReferenceDto; import at.tuwien.api.database.table.constraints.foreign.ReferenceTypeDto; import at.tuwien.api.database.table.constraints.primary.PrimaryKeyDto; import at.tuwien.api.database.table.constraints.unique.UniqueDto; +import at.tuwien.api.user.UserDto; import at.tuwien.config.QueryConfig; import at.tuwien.exception.QueryNotFoundException; import at.tuwien.exception.TableNotFoundException; @@ -402,6 +403,9 @@ public interface DataMapper { .created(LocalDateTime.parse(data.getString(2), mariaDbFormatter) .atZone(ZoneId.of("UTC")) .toInstant()) + .creator(UserDto.builder() + .id(UUID.fromString(data.getString(3))) + .build()) .createdBy(UUID.fromString(data.getString(3))) .query(data.getString(4)) .queryHash(data.getString(5)) diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java index 265c361cd76180e36e92a292d9a36c006c9dfc97..97778e9e11679bc349ef6037fe8c7a9150cf530a 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java @@ -186,14 +186,16 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final QueryResultDto queryResult; try { /* find table data */ - final long start = System.currentTimeMillis(); + long start = System.currentTimeMillis(); final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectDatasetRawQuery( table.getDatabase().getInternalName(), table.getInternalName(), table.getColumns(), timestamp, size, page)) .executeQuery(); log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + start = System.currentTimeMillis(); connection.commit(); queryResult = dataMapper.resultListToQueryResultDto(table.getColumns(), resultSet); + log.debug("mapped result in {} ms", System.currentTimeMillis() - start); } catch (SQLException e) { connection.rollback(); log.error("Failed to find data from table {}.{}: {}", table.getDatabase().getInternalName(), table.getInternalName(), e.getMessage()); diff --git a/dbrepo-gateway-service/dbrepo.conf b/dbrepo-gateway-service/dbrepo.conf index f9c0001cebfbba5bbb842984377fb01197617d96..de8eaa417bbe422999436e83dc24f8fb66f9b1d5 100644 --- a/dbrepo-gateway-service/dbrepo.conf +++ b/dbrepo-gateway-service/dbrepo.conf @@ -134,7 +134,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://data; - proxy_read_timeout 90; + proxy_read_timeout 600; } location ~ /api/(database|concept|container|identifier|image|message|license|oai|ontology|unit|user) { diff --git a/dbrepo-ui/components/database/DatabaseCard.vue b/dbrepo-ui/components/database/DatabaseCard.vue index 9485fdee101584250d10bc1b8b959565a6699541..48aefa7493ce5beb52d8ec62cf794d64368b8489 100644 --- a/dbrepo-ui/components/database/DatabaseCard.vue +++ b/dbrepo-ui/components/database/DatabaseCard.vue @@ -4,11 +4,20 @@ :to="`/database/${database.id}/info`" variant="flat" rounded="0" - :href="`/database/${database.id}`"> + :href="`/database/${database.id}`" + @click="loading = true"> <v-divider class="mx-4" /> - <v-card-title - class="text-primary text-decoration-underline" - v-text="formatTitle(database)" /> + <v-card-title> + <span + class="text-primary text-decoration-underline" + v-text="formatTitle(database)" /> + <v-progress-circular + v-if="loading" + color="primary" + size="24" + class="ml-1" + indeterminate /> + </v-card-title> <v-card-subtitle v-text="formatCreators(database)" /> <v-card-text> @@ -68,6 +77,11 @@ import { formatLanguage } from '@/utils' export default { + data() { + return { + loading: false + } + }, props: { database: { default: () => { diff --git a/dbrepo-ui/components/database/DatabaseToolbar.vue b/dbrepo-ui/components/database/DatabaseToolbar.vue index 5a86bbaa01410fb068533930af6636fc9f20d1d7..0c87e5599c688b3c4feebfbadb09a8868071d54d 100644 --- a/dbrepo-ui/components/database/DatabaseToolbar.vue +++ b/dbrepo-ui/components/database/DatabaseToolbar.vue @@ -10,21 +10,21 @@ <span v-if="database && $vuetify.display.lgAndUp" v-text="database.name" /> - <v-tooltip - v-if="database" - bottom> - <template v-slot:activator="{ props }"> - <v-icon - class="mr-2" - size="small" - right - :color="database.is_public ? 'success' : 'chip'" - v-bind="props"> - {{ database.is_public ? 'mdi-lock-open-outline' : 'mdi-lock-outline' }} - </v-icon> - </template> - <span>{{ $t('toolbars.database.' + (database.is_public ? 'public' : 'private')) }}</span> - </v-tooltip> + <v-chip + v-if="database && database.is_public" + size="small" + class="ml-2" + color="success" + :text="$t('toolbars.database.public')" + variant="outlined" /> + <v-chip + v-if="database && !database.is_public" + size="small" + class="ml-2" + :color="colorVariant" + variant="outlined" + :text="$t('toolbars.database.private')" + flat /> </v-toolbar-title> <v-spacer /> <v-btn @@ -33,6 +33,14 @@ color="tertiary" :variant="buttonVariant" :text="$t('toolbars.database.dashboard.permanent') + ($vuetify.display.lgAndUp ? ' ' + $t('toolbars.database.dashboard.xl') : '')" /> + <v-btn + v-if="canCreateTable" + :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-table-large-plus' : null" + color="secondary" + variant="flat" + :text="($vuetify.display.lgAndUp ? $t('toolbars.database.create-table.xl') + ' ' : '') + $t('toolbars.database.create-table.permanent')" + class="mr-2" + :to="`/database/${$route.params.database_id}/table/create/dataset`" /> <v-btn v-if="canCreateSubset" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-wrench' : null" @@ -49,14 +57,6 @@ :text="($vuetify.display.lgAndUp ? $t('toolbars.database.create-view.xl') + ' ' : '') + $t('toolbars.database.create-view.permanent')" class="mr-2 white--text" :to="`/database/${$route.params.database_id}/view/create`" /> - <v-btn - v-if="canCreateTable" - :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-table-large-plus' : null" - color="secondary" - variant="flat" - :text="($vuetify.display.lgAndUp ? $t('toolbars.database.create-table.xl') + ' ' : '') + $t('toolbars.database.create-table.permanent')" - class="mr-2" - :to="`/database/${$route.params.database_id}/table/create/dataset`" /> <v-btn v-if="canCreateIdentifier" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-identifier' : null" @@ -117,6 +117,9 @@ export default { roles () { return this.userStore.getRoles }, + colorVariant () { + return this.isContrastTheme ? '' : (this.isDarkTheme ? 'tertiary' : 'secondary') + }, canCreateIdentifier () { if (!this.roles) { return false diff --git a/dbrepo-ui/components/identifier/Citation.vue b/dbrepo-ui/components/identifier/Citation.vue index 8cd96902d48009da22cd761849be1962ba07c248..6f35ac915e255610965015ccd16adcf1dad03597 100644 --- a/dbrepo-ui/components/identifier/Citation.vue +++ b/dbrepo-ui/components/identifier/Citation.vue @@ -10,8 +10,8 @@ <v-select v-model="style" :items="styles" - item-title="style" - item-value="accept" + item-title="title" + item-value="value" dense variant="outlined" single-line /> @@ -33,39 +33,38 @@ export default { return { loading: false, styles: [ - { style: 'APA', accept: 'text/bibliography;style=apa' }, - { style: 'IEEE', accept: 'text/bibliography;style=ieee' }, - { style: 'BibTeX', accept: 'text/bibliography;style=bibtex' } + { title: 'APA', value: 'text/bibliography;style=apa' }, + { title: 'IEEE', value: 'text/bibliography;style=ieee' }, + { title: 'BibTeX', value: 'text/bibliography;style=bibtex' } ], - style: null, + style: 'text/bibliography;style=apa', citation: null } }, watch: { style () { - this.loadCitation(this.style) + this.loadCitation() }, pid () { - this.loadCitation(this.style) + this.loadCitation() } }, mounted () { - this.style = this.styles[0].accept - this.loadCitation(null) + this.loadCitation() }, methods: { - loadCitation (accept) { - if (!this.identifier || !accept) { + loadCitation () { + if (!this.identifier || !this.style) { return } this.loading = true const identifierService = useIdentifierService() - identifierService.findOne(this.identifier.id, accept) + identifierService.findOne(this.identifier.id, this.style) .then((citation) => { this.citation = citation this.loading = false }) - .error(({code, message}) => { + .catch(({code, message}) => { const toast = useToastInstance() toast.error(this.$t(`${code}: ${message}`)) this.loading = false diff --git a/dbrepo-ui/components/subset/SubsetList.vue b/dbrepo-ui/components/subset/SubsetList.vue index 21d4e7263eedd942f8abfec1470dd3a13af9d53e..b977daffa5aaf71dbe2980b98761244e8a3627b0 100644 --- a/dbrepo-ui/components/subset/SubsetList.vue +++ b/dbrepo-ui/components/subset/SubsetList.vue @@ -1,7 +1,7 @@ <template> <div> <v-card - v-if="!loadingSubsets && queries.length === 0" + v-if="!loadingSubsets && subsets.length === 0" variant="flat" rounded="0" :text="$t('pages.database.subpages.subsets.empty')" /> @@ -14,7 +14,7 @@ <Loading /> </v-list-item> <div - v-for="(item, i) in queries" + v-for="(item, i) in subsets" :key="`q-${i}`"> <v-divider v-if="i !== 0" class="mx-4" /> <v-list> @@ -54,9 +54,7 @@ export default { return { loadingSubsets: false, loadingIdentifiers: false, - queries: [], - identifiers: [], - isAuthorizationError: false, + subsets: [], cacheStore: useCacheStore(), userStore: useUserStore() } @@ -77,8 +75,9 @@ export default { this.loadingSubsets = true const queryService = useQueryService() queryService.findAll(this.$route.params.database_id, true) - .then((queries) => { - this.queries = queries + .then((subsets) => { + this.loadingSubsets = false + this.subsets = subsets }) .catch(({code}) => { this.loadingSubsets = false @@ -88,9 +87,6 @@ export default { } toast.error(this.$t(code)) }) - .finally(() => { - this.loadingSubsets = false - }) }, title (query) { if (query.identifiers.length === 0) { diff --git a/dbrepo-ui/components/subset/SubsetToolbar.vue b/dbrepo-ui/components/subset/SubsetToolbar.vue index b51cc1d089085b657cb72d689fbf9ef2c6776b36..5c5081a2f8f5c3944928bbd59cd76dddc2c79d89 100644 --- a/dbrepo-ui/components/subset/SubsetToolbar.vue +++ b/dbrepo-ui/components/subset/SubsetToolbar.vue @@ -1,6 +1,7 @@ <template> <div> - <v-toolbar flat> + <v-toolbar + flat> <v-btn class="mr-2" variant="plain" @@ -27,16 +28,16 @@ color="secondary" variant="flat" class="mb-1 ml-2" - :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-content-save-outline' : null" + :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-star' : null" :text="$t('toolbars.subset.save.permanent')" @click.stop="save" /> <v-btn v-if="canForgetQuery" :loading="loadingSave" - color="error" + color="warning" variant="flat" class="mb-1 ml-2" - :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-trash-can-outline' : null" + :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-star-off' : null" :text="$t('toolbars.subset.unsave.permanent')" @click.stop="forget" /> <v-btn @@ -116,7 +117,10 @@ export default { if (!this.database) { return false } - return this.database.is_public + if (this.database.is_public) { + return true + } + return this.access }, identifier () { /* mount pid */ @@ -154,7 +158,7 @@ export default { return formatTimestampUTCLabel(this.subset.created) }, result_visibility () { - if (!this.database) { + if (!this.database || !this.subset) { return false } if (this.database.is_public) { diff --git a/dbrepo-ui/components/view/ViewToolbar.vue b/dbrepo-ui/components/view/ViewToolbar.vue index c107c3c0ef006c2d5456f6db187908e092664c5a..c43b730397a977559f85b55f92e62b486b15aa6f 100644 --- a/dbrepo-ui/components/view/ViewToolbar.vue +++ b/dbrepo-ui/components/view/ViewToolbar.vue @@ -11,16 +11,17 @@ <v-spacer /> <v-btn v-if="canDeleteView" - prepend-icon="mdi-delete" class="mr-2" variant="flat" - color="error" - :text="$vuetify.display.lgAndUp ? $t('navigation.delete') : ''" + :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-delete' : null" :loading="loadingDelete" + color="error" + :text="$t('navigation.delete')" @click="deleteView" /> <v-btn v-if="canCreatePid" - prepend-icon="mdi-content-save-outline" + class="mr-2" + :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-content-save-outline' : null" variant="flat" color="primary" :text="($vuetify.display.lgAndUp ? $t('toolbars.view.pid.xl') + ' ' : '') + $t('toolbars.view.pid.permanent')" @@ -63,6 +64,10 @@ export default { database () { return this.cacheStore.getDatabase }, + buttonVariant () { + const runtimeConfig = useRuntimeConfig() + return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.button.contrast : runtimeConfig.public.variant.button.normal + }, view () { if (!this.database) { return null diff --git a/dbrepo-ui/composables/axios-instance.ts b/dbrepo-ui/composables/axios-instance.ts index a2b6ca4f6ef5b138473b7812b460981a6f7c7f2f..d4f274b717e938c3305bc20463c93d9e9474e079 100644 --- a/dbrepo-ui/composables/axios-instance.ts +++ b/dbrepo-ui/composables/axios-instance.ts @@ -8,7 +8,7 @@ export const useAxiosInstance = () => { const userStore = useUserStore() if (!instance) { instance = axios.create({ - timeout: 10_000, + timeout: 90_000, params: {}, headers: { Accept: 'application/json', diff --git a/dbrepo-ui/composables/identifier-service.ts b/dbrepo-ui/composables/identifier-service.ts index a85f05a45c5d4211ca9374b874ccc367d5b56372..96e5610c8c11161b0a4c1687e630204f80e53554 100644 --- a/dbrepo-ui/composables/identifier-service.ts +++ b/dbrepo-ui/composables/identifier-service.ts @@ -2,7 +2,7 @@ import type {AxiosError, AxiosRequestConfig} from 'axios' import {axiosErrorToApiError} from '@/utils' export const useIdentifierService = (): any => { - async function findOne(id: number, accept: string | null): Promise<IdentifierDto> { + async function findOne(id: number, accept: string): Promise<IdentifierDto> { const axios = useAxiosInstance() console.debug('find identifier with id', id) const config: AxiosRequestConfig = { diff --git a/dbrepo-ui/composables/query-service.ts b/dbrepo-ui/composables/query-service.ts index fa18a55ec053bad22339a14bcb053b9a405f3716..abcd928b66d0350fd855ae380b959a33818b8f3d 100644 --- a/dbrepo-ui/composables/query-service.ts +++ b/dbrepo-ui/composables/query-service.ts @@ -13,6 +13,10 @@ export const useQueryService = (): any => { resolve(response.data) }) .catch((error) => { + if (error.response.status === 403) { + /* ignore */ + resolve([]) + } console.error('Failed to find queries', error) reject(axiosErrorToApiError(error)) }) @@ -77,7 +81,7 @@ export const useQueryService = (): any => { const axios = useAxiosInstance() console.debug('execute query in database with id', databaseId) return new Promise<QueryResultDto>((resolve, reject) => { - axios.post<QueryResultDto>(`/api/database/${databaseId}/subset`, data, {params: mapFilter(timestamp, page, size)}) + axios.post<QueryResultDto>(`/api/database/${databaseId}/subset`, data, {params: mapFilter(timestamp, page, size), timeout: 600_000}) .then((response) => { console.info('Executed query with id', response.data.id, ' in database with id', databaseId) resolve(response.data) @@ -93,7 +97,7 @@ export const useQueryService = (): any => { const axios = useAxiosInstance() console.debug('re-execute query in database with id', databaseId) return new Promise<QueryResultDto>((resolve, reject) => { - axios.get<QueryResultDto>(`/api/database/${databaseId}/subset/${queryId}/data`, { params: mapFilter(null, page, size), timeout: 30_000 }) + axios.get<QueryResultDto>(`/api/database/${databaseId}/subset/${queryId}/data`, { params: mapFilter(null, page, size) }) .then((response) => { console.info('Re-executed query in database with id', databaseId) resolve(response.data) @@ -109,7 +113,7 @@ export const useQueryService = (): any => { const axios = useAxiosInstance() console.debug('re-execute query in database with id', databaseId) return new Promise<number>((resolve, reject) => { - axios.head<void>(`/api/database/${databaseId}/subset/${queryId}/data`, { timeout: 30_000 }) + axios.head<void>(`/api/database/${databaseId}/subset/${queryId}/data`) .then((response) => { const count: number = Number(response.headers['x-count']) console.info('Found', count, 'tuples for query', queryId, 'in database with id', databaseId) diff --git a/dbrepo-ui/composables/table-service.ts b/dbrepo-ui/composables/table-service.ts index 16c7f06ce6137ae9a87061ebcf253bc3e582bf0d..ffd7ebcd6074cc23d9bfa9412e19e48ef87f8f37 100644 --- a/dbrepo-ui/composables/table-service.ts +++ b/dbrepo-ui/composables/table-service.ts @@ -71,9 +71,8 @@ export const useTableService = (): any => { const axios = useAxiosInstance() console.debug('get data for table with id', tableId, 'in database with id', databaseId); return new Promise<QueryResultDto>((resolve, reject) => { - axios.get<QueryResultDto>(`/api/database/${databaseId}/table/${tableId}/data`, { params: mapFilter(timestamp, page, size), timeout: 60_000 }) + axios.get<QueryResultDto>(`/api/database/${databaseId}/table/${tableId}/data`, { params: mapFilter(timestamp, page, size) }) .then((response) => { - response.data.count = Number(response.headers['x-count']) console.info('Got data for table with id', tableId, 'in database with id', databaseId) resolve(response.data) }) @@ -88,7 +87,7 @@ export const useTableService = (): any => { const axios = useAxiosInstance() console.debug('get data count for table with id', tableId, 'in database with id', databaseId); return new Promise<number>((resolve, reject) => { - axios.head<void>(`/api/database/${databaseId}/table/${tableId}/data`, { params: mapFilter(timestamp, null, null), timeout: 60_000 }) + axios.head<void>(`/api/database/${databaseId}/table/${tableId}/data`, { params: mapFilter(timestamp, null, null) }) .then((response: AxiosResponse<void>) => { const count: number = Number(response.headers['x-count']) console.info('Found' + count + 'in table with id', tableId, 'in database with id', databaseId) @@ -192,7 +191,7 @@ export const useTableService = (): any => { const axios = useAxiosInstance() console.debug('suggest semantic entities for table column with id', columnId, 'of table with id', tableId, 'of database with id', databaseId) return new Promise<TableColumnEntityDto[]>((resolve, reject) => { - axios.get<TableColumnEntityDto[]>(`/api/database/${databaseId}/table/${tableId}/column/${columnId}/suggest`, {timeout: 60_000}) + axios.get<TableColumnEntityDto[]>(`/api/database/${databaseId}/table/${tableId}/column/${columnId}/suggest`) .then((response) => { console.info('Suggested semantic entities for table column with id', columnId, 'of table with id', tableId, 'of database with id', databaseId) resolve(response.data) diff --git a/dbrepo-ui/composables/view-service.ts b/dbrepo-ui/composables/view-service.ts index adc76e32a1407f7e893cbe92692ec0b2f597591b..642a7c6e51ca7344dae72dd3ee12550c84c893a3 100644 --- a/dbrepo-ui/composables/view-service.ts +++ b/dbrepo-ui/composables/view-service.ts @@ -37,7 +37,7 @@ export const useViewService = (): any => { const axios = useAxiosInstance() console.debug('re-execute view with id', viewId, 'in database with id', databaseId) return new Promise<QueryResultDto>((resolve, reject) => { - axios.get<QueryResultDto>(`/api/database/${databaseId}/view/${viewId}/data`, { params: {page, size}, timeout: 30_000 }) + axios.get<QueryResultDto>(`/api/database/${databaseId}/view/${viewId}/data`, { params: {page, size} }) .then((response) => { console.info('Re-executed view with id', viewId, 'in database with id', databaseId) resolve(response.data) @@ -53,7 +53,7 @@ export const useViewService = (): any => { const axios = useAxiosInstance() console.debug('re-execute view with id', viewId, 'in database with id', databaseId) return new Promise<number>((resolve, reject) => { - axios.head<number>(`/api/database/${databaseId}/view/${viewId}/data`, { timeout: 30_000 }) + axios.head<number>(`/api/database/${databaseId}/view/${viewId}/data`) .then((response) => { const count: number = Number(response.headers['x-count']) console.info('Found', count, 'tuples for view with id', viewId, 'in database with id', databaseId) diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json index cf8dcfd03e8df91fd4ba737c4c3662dec41402f0..be8ddfcc2317d441d3a8b6edacd4b3f5a663c6af 100644 --- a/dbrepo-ui/locales/en-US.json +++ b/dbrepo-ui/locales/en-US.json @@ -1382,7 +1382,7 @@ }, "subset": { "save": { - "permanent": "Save" + "permanent": "Star" }, "export": { "data": { @@ -1399,7 +1399,7 @@ "permanent": "PID" }, "unsave": { - "permanent": "Unsave" + "permanent": "Unstar" } }, "view": { diff --git a/dbrepo-ui/pages/database/[database_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/settings.vue index 61e9dc62449b48cba17d77b4bb717e8164c9f257..a79ad48c4193c9b4f0fbc83f84b3a2c0db3a6193 100644 --- a/dbrepo-ui/pages/database/[database_id]/settings.vue +++ b/dbrepo-ui/pages/database/[database_id]/settings.vue @@ -422,7 +422,7 @@ export default { databaseService.updateVisibility(this.$route.params.database_id, this.modifyVisibility) .then((database) => { const toast = useToastInstance() - toast.success('success.database.visibility') + toast.success(this.$t('success.database.visibility')) this.cacheStore.setDatabase(database) }) .catch(() => { diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue index f740416faaf70387ed73935754c777435f58fa67..4902e2c54f243c7a2e4f3df90f3aa16cf7c26753 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue @@ -14,6 +14,15 @@ v-else v-text="executionUTC" /> </v-toolbar-title> + <v-spacer /> + <v-btn + :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-refresh' : null" + variant="flat" + :text="$t('toolbars.table.data.refresh')" + class="mr-2" + :disabled="loadingSubset" + :loading="loadingSubset" + @click="loadSubset" /> </v-toolbar> <v-card tile> <QueryResults diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue index d52ac91642901fe4b012345c1309235ea4f66420..1d9101fbf240d8d577026e53361f2cedb8f6486e 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue @@ -103,7 +103,12 @@ <script setup> const config = useRuntimeConfig() const { database_id, subset_id } = useRoute().params -const { data } = await useFetch(`${config.public.api.server}/api/database/${database_id}/subset/${subset_id}`) +const requestConfig = { timeout: 90_000, headers: { Accept: 'application/json', 'Content-Type': 'application/json' } } +const userStore = useUserStore() +if (userStore.getToken) { + requestConfig.headers.Authorization = `Bearer ${userStore.getToken}` +} +const { data } = await useFetch(`${config.public.api.server}/api/database/${database_id}/subset/${subset_id}`, requestConfig) if (data.value) { const identifierService = useIdentifierService() useServerHead(identifierService.subsetToServerHead(data.value)) diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue index 34527f7f195ec143519043aa3aca9a62c7fd77f8..bcab9b60be8812f270836644e5f521553a22040d 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue @@ -72,7 +72,7 @@ :headers="headers" :items="rows" :items-length="total" - :loading="loadingData" + :loading="loadingData || loadingCount" :options.sync="options" :footer-props="footerProps" @update:options="loadData"> @@ -192,9 +192,6 @@ export default { } }, computed: { - loadingColor () { - return this.error ? 'error' : 'primary' - }, roles () { return this.userStore.getRoles }, @@ -283,6 +280,7 @@ export default { }, watch: { version () { + this.loadCount() this.reload() }, table (newTable, oldTable) { @@ -293,6 +291,7 @@ export default { }, mounted () { this.loadProperties() + this.loadCount() }, methods: { addTuple () { @@ -429,14 +428,25 @@ export default { this.lastReload = new Date() this.loadData({ page: this.options.page, itemsPerPage: this.options.itemsPerPage, sortBy: null}) }, - loadData ({ page, itemsPerPage, sortBy }) { + loadCount() { + this.loadingCount = true + const tableService = useTableService() + tableService.getCount(this.$route.params.database_id, this.$route.params.table_id, (this.versionISO || this.lastReload.toISOString())) + .then((count) => { + this.total = count + this.loadingCount = false + }) + .catch((error) => { + this.loadingCount = false + }) + }, + loadData({ page, itemsPerPage, sortBy }) { this.options.page = page this.options.itemsPerPage = itemsPerPage const tableService = useTableService() this.loadingData = true tableService.getData(this.$route.params.database_id, this.$route.params.table_id, (page - 1), itemsPerPage, (this.versionISO || this.lastReload.toISOString())) .then((data) => { - this.total = data.count this.rows = data.result.map((row) => { for (const col in row) { const column = this.table.columns.filter(c => c.internal_name === col)[0] diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue index 03464f5dbba66d89312f7b1c4a55332c79af161a..838ef2f0f1adf0a90f722ffd73d43457a5b92186 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue @@ -10,7 +10,7 @@ :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-refresh' : null" variant="flat" :text="$t('toolbars.table.data.refresh')" - class="mb-1 ml-2" + class="mb-1 mr-2" :loading="loadingData" @click="reload" /> </v-toolbar> diff --git a/install.sh b/install.sh index a9fcf6d10d374f048d14289d20fc89d01ef82813..5e367f4d53fefc633fb3131dad7360569ea971ac 100644 --- a/install.sh +++ b/install.sh @@ -69,15 +69,15 @@ fi echo "[📦] Pulling images for version ${VERSION} ..." docker compose pull -echo "[✨] Starting DBRepo ..." -docker compose up -d - -if [ $? -eq 0 ]; then - echo "[🎉] Successfully started!" - echo "" - echo "You can now inspect the logs with:" - echo "" - echo " docker compose logs -f" - echo "" - echo "Read about next steps online: https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/${VERSION}/installation/#next-steps" -fi +echo "[🎉] Success!" +echo "" +echo "You can now:" +echo "" +echo " 1) Either start the deployment running on http://localhost, or" +echo " 2) Edit the BASE_URL variable in .env to set your hostname" +echo "" +echo "Then start the local deployment with:" +echo "" +echo " docker compose up -d" +echo "" +echo "Read about next steps online: https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/${VERSION}/installation/#next-steps"