From 3823790f9182601392df13c62993ab90f99312f7 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Thu, 30 Mar 2023 10:15:14 +0200 Subject: [PATCH] More progress on the frontend --- fda-authentication-service/dbrepo-realm.json | 56 +++++++++---------- .../at/tuwien/endpoints/DatabaseEndpoint.java | 8 +-- .../api/container/ContainerBriefDto.java | 1 + .../tuwien/api/database/DatabaseBriefDto.java | 3 + .../at/tuwien/api/database/DatabaseDto.java | 7 ++- .../java/at/tuwien/api/user/UserBriefDto.java | 1 - .../main/java/at/tuwien/api/user/UserDto.java | 2 - .../tuwien/endpoints/TableColumnEndpoint.java | 2 + .../at/tuwien/endpoints/TableEndpoint.java | 2 - fda-ui/api/database/index.js | 19 +++++++ fda-ui/api/table/index.js | 17 ++++++ fda-ui/components/DBToolbar.vue | 16 +++++- fda-ui/components/DatabaseList.vue | 15 +++-- .../database/_database_id/query/create.vue | 11 ++-- .../database/_database_id/settings.vue | 31 ++++++---- .../database/_database_id/table/create.vue | 26 ++++++--- .../database/_database_id/table/import.vue | 21 ++++--- .../database/_database_id/view/create.vue | 11 ++-- fda-ui/utils/index.js | 5 +- 19 files changed, 163 insertions(+), 91 deletions(-) create mode 100644 fda-ui/api/table/index.js diff --git a/fda-authentication-service/dbrepo-realm.json b/fda-authentication-service/dbrepo-realm.json index 3d79aeed6d..0e26cf80c9 100644 --- a/fda-authentication-service/dbrepo-realm.json +++ b/fda-authentication-service/dbrepo-realm.json @@ -5,7 +5,7 @@ "defaultSignatureAlgorithm" : "RS256", "revokeRefreshToken" : false, "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, + "accessTokenLifespan" : 720, "accessTokenLifespanForImplicitFlow" : 900, "ssoSessionIdleTimeout" : 1800, "ssoSessionMaxLifespan" : 36000, @@ -21,8 +21,8 @@ "accessCodeLifespan" : 60, "accessCodeLifespanUserAction" : 300, "accessCodeLifespanLogin" : 1800, - "actionTokenGeneratedByAdminLifespan" : 259200, - "actionTokenGeneratedByUserLifespan" : 86400, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 1800, "oauth2DeviceCodeLifespan" : 600, "oauth2DevicePollingInterval" : 5, "enabled" : true, @@ -1761,7 +1761,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ] } }, { "id" : "1849e52a-b8c9-44a8-af3d-ee19376a1ed1", @@ -1787,11 +1787,11 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper" ] + "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper" ] } } ], "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "9ed551eb-c1e6-4af1-aaea-7aca5c7e6a97", + "id" : "2abcde8e-479c-4b3f-85ca-08687ff4b0cf", "providerId" : "declarative-user-profile", "subComponents" : { }, "config" : { } @@ -1845,7 +1845,7 @@ "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "813567bd-6600-4b6e-b286-b5dee1f5d064", + "id" : "5eb1d49c-38cd-41a8-9557-13bcffb1a642", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -1867,7 +1867,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "9826cfed-0fa3-4147-89dc-b2682c24d1ae", + "id" : "29e68803-3d8d-4fe5-af03-0814e35a6ed3", "alias" : "Authentication Options", "description" : "Authentication options.", "providerId" : "basic-flow", @@ -1896,7 +1896,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "c784fdf2-5c81-4c49-bfcd-4b4c1df23709", + "id" : "aeadaa3b-f3d2-47ef-9e0a-ea4b737fe684", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1918,7 +1918,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "14ec8779-1edf-41cc-80b4-472bf39ea78b", + "id" : "4c958b93-579e-45da-8471-a6aebc439641", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1940,7 +1940,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "d1d343ce-85ea-4dd8-ace0-e2a89b1c8aa7", + "id" : "6878c43f-0aa7-40c6-bf28-f984029e893b", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1962,7 +1962,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "9cdaab34-3b7b-49f3-b563-fb5e5e5234ad", + "id" : "aae75803-fb3c-4c74-9ee7-73fc897aad05", "alias" : "Handle Existing Account", "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId" : "basic-flow", @@ -1984,7 +1984,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "e52f4a8e-3e68-48b7-a332-e5d4cdea71f7", + "id" : "0987a7fe-5e31-40b1-be1f-6cd4a5a76e43", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -2006,7 +2006,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "26fe8be3-b879-4e42-ab17-984a779e4e3b", + "id" : "68078236-033e-4f17-b02f-fb5f401be3fe", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -2029,7 +2029,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "88be070e-9dca-477a-a309-ee6a128b3cdb", + "id" : "28c746e7-fc22-4c61-872a-26670fffb8ed", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -2051,7 +2051,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3eae800e-9501-4a4d-a212-a5c9f6bb21a5", + "id" : "502957db-db9d-4138-a5e3-90fce22bff15", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -2087,7 +2087,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "f590ef06-e384-409d-bbe7-7802d829464f", + "id" : "9312b6c7-f999-4285-ae9f-e365666066e7", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -2123,7 +2123,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "830f1845-6d89-4de7-9ad2-e5ee4b13d774", + "id" : "31c2af88-b37c-4fdb-8dec-23a5ba145114", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -2152,7 +2152,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6430a3fa-60ce-475d-a512-966c4046ad10", + "id" : "c11ae35d-772a-4a1f-9934-a1945b45d617", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -2167,7 +2167,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6ed62838-778b-48e8-9b0d-ca7a7232ec9e", + "id" : "0485179d-460c-4ad0-b0b2-02bbf57eb012", "alias" : "first broker login", "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId" : "basic-flow", @@ -2190,7 +2190,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "368b31ba-3065-4425-832e-f565336e93f5", + "id" : "aa1b06b4-1c12-4f7c-9d5a-6422ab5bb89a", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -2212,7 +2212,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "32618434-ca5d-4e5f-bcf7-a27f233e6ee2", + "id" : "fc52dc5f-16ee-499e-ad0a-ba04093efc33", "alias" : "http challenge", "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId" : "basic-flow", @@ -2234,7 +2234,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "17dbfb8f-0e6a-4b3c-9a6f-02e7b210ffe1", + "id" : "576fb8d9-96b4-464a-a2b5-655c61755ec0", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -2250,7 +2250,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "aef432b9-ac38-46b6-bb35-210ae9a01828", + "id" : "a026a910-1a04-4eb0-9fe2-6eb72139a39b", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -2286,7 +2286,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "64cff6f9-6931-4b12-a869-bbd6c767ffd7", + "id" : "0d0e32f3-7030-42fa-be83-528281d9eca0", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -2322,7 +2322,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "85c3536d-5812-403a-b7a9-59728f9d2a5f", + "id" : "ec850fcc-5b8c-4109-9cf4-a16f84cd7804", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -2338,13 +2338,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "2f156ed6-ea2d-4ed7-8974-283993cb0b8a", + "id" : "d42b19d3-2efe-4879-9ce0-7c055a3d949e", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "50956254-36cd-4834-bc2e-f258c525309f", + "id" : "f1c71d63-f83f-432d-b9b4-e1a97095b5e1", "alias" : "review profile config", "config" : { "update.profile.on.first.login" : "missing" diff --git a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java index 5176472cd2..841d2d13a5 100644 --- a/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java +++ b/fda-database-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java @@ -98,14 +98,14 @@ public class DatabaseEndpoint { @PutMapping("/{databaseId}/visibility") @Transactional - @PreAuthorize("hasAuthority('modify-database')") + @PreAuthorize("hasAuthority('modify-database-visibility')") @Timed(value = "database.visibility", description = "Time needed to modify a database visibility") @Operation(summary = "Update database", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<DatabaseDto> visibility(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable Long databaseId, @Valid @RequestBody DatabaseModifyVisibilityDto data, @NotNull Principal principal) - throws DatabaseNotFoundException, NotAllowedException { + throws DatabaseNotFoundException { log.debug("endpoint update database, containerId={}, databaseId={}, data={}, principal={}", containerId, databaseId, data, principal); final Database database = databaseService.visibility(containerId, databaseId, data); @@ -117,14 +117,14 @@ public class DatabaseEndpoint { @PutMapping("/{databaseId}/transfer") @Transactional - @PreAuthorize("hasAuthority('transfer-database')") + @PreAuthorize("hasAuthority('modify-database-owner')") @Timed(value = "database.transfer", description = "Time needed to transfer a database ownership") @Operation(summary = "Transfer database", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<DatabaseDto> transfer(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable Long databaseId, @Valid @RequestBody DatabaseTransferDto transferDto, @NotNull Principal principal) - throws DatabaseNotFoundException, NotAllowedException, UserNotFoundException { + throws DatabaseNotFoundException, UserNotFoundException { log.debug("endpoint update database, containerId={}, databaseId={}, transferDto={}, principal={}", containerId, databaseId, transferDto, principal); final Database database = databaseService.transfer(containerId, databaseId, transferDto); diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java index e48292d753..b8a1b241b3 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/container/ContainerBriefDto.java @@ -30,6 +30,7 @@ public class ContainerBriefDto { @Schema(example = "Air Quality") private String name; + @NotNull private UserBriefDto creator; @NotBlank diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java index d91f7ce74d..b6c1807edc 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java @@ -40,6 +40,9 @@ public class DatabaseBriefDto { @Schema(example = "mariadb:10.5") private String engine; + @NotNull + private UserBriefDto owner; + @ToString.Exclude @org.springframework.data.annotation.Transient private ContainerBriefDto container; diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java index 56f48fbc2c..5d55626797 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/DatabaseDto.java @@ -40,9 +40,6 @@ public class DatabaseDto { @Schema(example = "dbrepo/air_quality") private String exchangeName; - @NotNull - private UserBriefDto creator; - private IdentifierDto identifier; @NotBlank @@ -69,6 +66,10 @@ public class DatabaseDto { private List<DatabaseAccessDto> accesses; + @NotNull + private UserBriefDto creator; + + @NotNull private UserBriefDto owner; @Schema(example = "2020-08-04 11:12:00") diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java index fd4c54484f..43d341d894 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserBriefDto.java @@ -23,7 +23,6 @@ public class UserBriefDto { private String id; @NotNull - @JsonProperty("preferred_username") @Schema(example = "jcarberry", description = "Only contains lowercase characters") private String username; diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java index f48aab4e69..793be6f7f6 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/user/UserDto.java @@ -1,7 +1,6 @@ package at.tuwien.api.user; import at.tuwien.api.container.ContainerDto; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @@ -23,7 +22,6 @@ public class UserDto { private String id; @NotNull - @JsonProperty("preferred_username") @Schema(example = "jcarberry", description = "Only contains lowercase characters") private String username; diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java index d5b44de563..b06b38e708 100644 --- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java +++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableColumnEndpoint.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -36,6 +37,7 @@ public class TableColumnEndpoint { @PutMapping @Transactional + @PreAuthorize("hasAuthority('modify-table-column-semantics')") @Timed(value = "semantics.column_update", description = "Time needed to update a table column semantic mapping") @Operation(summary = "Update a table column semantic mapping", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<ColumnDto> update(@NotNull @PathVariable("id") Long containerId, diff --git a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 6c6c1c4577..d0e56e313d 100644 --- a/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/fda-table-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -45,7 +45,6 @@ public class TableEndpoint { @GetMapping @Transactional(readOnly = true) @Timed(value = "table.list", description = "Time needed to list the tables") - @PreAuthorize("hasAuthority('find-tables')") @Operation(summary = "List all tables", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<List<TableBriefDto>> list(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, @@ -87,7 +86,6 @@ public class TableEndpoint { @GetMapping("/{tableId}") @Transactional(readOnly = true) @Timed(value = "table.find", description = "Time needed to find a table") - @PreAuthorize("hasAuthority('find-table')") @Operation(summary = "Get information about table", security = @SecurityRequirement(name = "bearerAuth")) public ResponseEntity<TableDto> findById(@NotNull @PathVariable("id") Long containerId, @NotNull @PathVariable("databaseId") Long databaseId, diff --git a/fda-ui/api/database/index.js b/fda-ui/api/database/index.js index 17d8bece4d..986311fec7 100644 --- a/fda-ui/api/database/index.js +++ b/fda-ui/api/database/index.js @@ -11,3 +11,22 @@ export function createDatabase (token, container) { } }) } + +export function modifyVisibility (token, containerId, databaseId, isPublic) { + const payload = { + is_public: isPublic + } + return axios.put(`/api/container/${containerId}/database/${databaseId}/visibility`, payload, { + headers: { + Authorization: `Bearer ${token}` + } + }) +} + +export function findDatabase (token, containerId, databaseId) { + return axios.get(`/api/container/${containerId}/database/${databaseId}`, { + headers: { + Authorization: `Bearer ${token}` + } + }) +} diff --git a/fda-ui/api/table/index.js b/fda-ui/api/table/index.js new file mode 100644 index 0000000000..d0f5aa0b6f --- /dev/null +++ b/fda-ui/api/table/index.js @@ -0,0 +1,17 @@ +const axios = require('axios/dist/browser/axios.cjs') + +export function listTables (token, containerId, databaseId) { + return axios.get(`/api/container/${containerId}/database/${databaseId}/table`, { + headers: { + Authorization: `Bearer ${token}` + } + }) +} + +export function createTable (token, containerId, databaseId, payload) { + return axios.post(`/api/container/${containerId}/database/${databaseId}/table`, payload, { + headers: { + Authorization: `Bearer ${token}` + } + }) +} diff --git a/fda-ui/components/DBToolbar.vue b/fda-ui/components/DBToolbar.vue index 309001e40b..3da290a9fa 100644 --- a/fda-ui/components/DBToolbar.vue +++ b/fda-ui/components/DBToolbar.vue @@ -86,22 +86,34 @@ export default { return this.$store.state.token }, canImportCsv () { + if (!this.user) { + return false + } return this.user.roles.includes('insert-table-data') }, canCreateSubset () { + if (!this.user) { + return false + } return this.user.roles.includes('execute-query') }, canCreateView () { + if (!this.user) { + return false + } return this.user.roles.includes('create-database-view') }, canCreateTable () { + if (!this.user) { + return false + } return this.user.roles.includes('create-table') }, isOwner () { - if (!this.user || !this.database || !this.database.creator) { + if (!this.database || !this.user) { return false } - return this.database.creator.username === this.user.client_id + return this.database.owner.username === this.user.username }, config () { if (this.token === null) { diff --git a/fda-ui/components/DatabaseList.vue b/fda-ui/components/DatabaseList.vue index 757d2be830..572370fd26 100644 --- a/fda-ui/components/DatabaseList.vue +++ b/fda-ui/components/DatabaseList.vue @@ -12,8 +12,8 @@ <v-card-title v-if="hasDatabase(container)"> <a :href="`/container/${container.id}/database/${container.database.id}`">{{ container.name }}</a> </v-card-title> - <v-card-subtitle v-if="!hasIdentifier(container)" class="db-subtitle" v-text="formatCreator(container.creator)" /> - <v-card-subtitle v-if="hasIdentifier(container)" class="db-subtitle" v-text="formatCreatorz(container)" /> + <v-card-subtitle v-if="!hasIdentifier(container)" class="db-subtitle" v-text="formatOwner(container)" /> + <v-card-subtitle v-if="hasIdentifier(container)" class="db-subtitle" v-text="formatCreators(container)" /> <v-card-text v-if="hasDatabase(container)" class="db-description"> <div class="db-tags"> <v-chip v-if="hasDatabase(container) && container.database.is_public" small color="green" outlined>Public</v-chip> @@ -97,10 +97,13 @@ export default { this.loadContainers() }, methods: { - formatCreator (creator) { - return formatUser(creator) + formatOwner (container) { + if (!('database' in container)) { + return formatUser(container.creator) + } + return formatUser(container.database?.owner) }, - formatCreatorz (container) { + formatCreators (container) { const creators = formatCreators(container) return creators || this.formatCreator(container.creator) }, @@ -108,7 +111,7 @@ export default { if (!this.user) { return false } - if (container.creator.sub !== this.user.id) { + if (container.creator.sub !== this.user.sub) { return false } return !container.database diff --git a/fda-ui/pages/container/_container_id/database/_database_id/query/create.vue b/fda-ui/pages/container/_container_id/database/_database_id/query/create.vue index bc8e51ad13..52ca422a0a 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/query/create.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/query/create.vue @@ -1,13 +1,11 @@ <template> - <div v-if="isResearcher"> + <div v-if="canExecuteQuery"> <QueryBuilder /> <v-breadcrumbs :items="items" class="pa-0 mt-2" /> </div> </template> <script> -import { isResearcher } from '@/utils' - export default { data () { return { @@ -30,8 +28,11 @@ export default { user () { return this.$store.state.user }, - isResearcher () { - return isResearcher(this.user) + canExecuteQuery () { + if (!this.user) { + return false + } + return this.user.roles.includes('execute-query') } } } diff --git a/fda-ui/pages/container/_container_id/database/_database_id/settings.vue b/fda-ui/pages/container/_container_id/database/_database_id/settings.vue index a22d9092ed..85631642ea 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/settings.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/settings.vue @@ -4,19 +4,18 @@ <v-progress-linear v-if="loading" /> <v-tabs-items v-model="tab"> <v-tab-item> - <v-card v-if="isCreator" flat tile> + <v-card v-if="isOwner" flat tile> <v-card-title>Modify database access</v-card-title> <v-card-subtitle>This is a dangerous operation</v-card-subtitle> <v-data-table :headers="headers" :items="database.accesses" :items-per-page="10"> - <template v-if="isCreator" v-slot:item.user="{ item }"> + <template v-slot:item.user="{ item }"> {{ item.user.username }} </template> - <template v-if="isCreator" v-slot:item.action="{ item }"> + <template v-slot:item.action="{ item }"> <v-btn - :disabled="isCreator && item.user.username === user.username" x-small @click="modifyAccess(item)"> Modify @@ -34,7 +33,7 @@ </v-card-text> </v-card> <v-divider /> - <v-card v-if="isCreator" flat tile> + <v-card v-if="canModifyVisibility" flat tile> <v-card-title>Modify database visibility</v-card-title> <v-card-subtitle>This is a dangerous operation</v-card-subtitle> <v-card-text> @@ -78,6 +77,7 @@ <script> import DBToolbar from '@/components/DBToolbar' import EditAccess from '@/components/dialogs/EditAccess' +import { modifyVisibility } from '@/api/database' export default { components: { @@ -139,14 +139,20 @@ export default { user () { return this.$store.state.user }, - isCreator () { + isOwner () { if (!this.database || !this.user) { return false } - if (this.database.creator.username === null || this.user.username === null) { + if (this.database.owner.username === null || this.user.username === null) { return false } - return this.database.creator.username === this.user.username + return this.database.owner.username === this.user.username + }, + canModifyVisibility () { + if (!this.isOwner) { + return false + } + return this.user.roles.includes('modify-database-visibility') } }, watch: { @@ -174,11 +180,12 @@ export default { async updateDatabaseVisibility () { try { this.loading = true - await this.$axios.put(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/visibility`, this.modifyVisibility, this.config) - this.$toast.success('Successfully updated the database') + await modifyVisibility(this.token, this.$route.params.container_id, this.$route.params.database_id, this.modifyVisibility.is_public) + this.$toast.success('Successfully updated the database visibility') location.reload() - } catch (err) { - this.$toast.error('Failed to update database') + } catch (error) { + console.error('Failed to update database visibility', error) + this.$toast.error('Failed to update database visibility') } this.loading = false }, diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue index 7d85701599..7a405ae2ed 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/create.vue @@ -1,9 +1,12 @@ <template> - <div v-if="isResearcher"> + <div v-if="canCreateTable"> <v-toolbar flat> <v-toolbar-title> - <span>Create Table</span> + <v-btn id="back-btn" class="mr-2" :to="`/container/${$route.params.container_id}/database/${$route.params.database_id}/table`"> + <v-icon left>mdi-arrow-left</v-icon> + </v-btn> </v-toolbar-title> + <v-toolbar-title>Create Table</v-toolbar-title> </v-toolbar> <v-stepper v-model="step" vertical flat tile> <v-stepper-step :complete="step > 1" step="1"> @@ -57,7 +60,9 @@ <script> import TableSchema from '@/components/TableSchema' -const { notEmpty, isResearcher } = require('@/utils') +import { notEmpty } from '@/utils' +import { createTable } from '@/api/table' +import { findDatabase } from '@/api/database' export default { components: { @@ -104,8 +109,11 @@ export default { database () { return this.$store.state.database }, - isResearcher () { - return isResearcher(this.user) + canCreateTable () { + if (!this.user) { + return false + } + return this.user.roles.includes('create-table') }, config () { if (this.token === null) { @@ -144,7 +152,7 @@ export default { async createTable () { try { this.loading = true - const res = await this.$axios.post(`/api/container/${this.$route.params.container_id}/database/${this.databaseId}/table`, this.tableCreate, this.config) + const res = await createTable(this.token, this.$route.params.container_id, this.$route.params.database_id, this.tableCreate) if (res.status === 201) { this.error = false this.$toast.success('Table created') @@ -174,11 +182,11 @@ export default { } try { this.loading = true - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}`, this.config) + const res = await findDatabase(this.token, this.$route.params.container_id, this.$route.params.database_id) this.$store.commit('SET_DATABASE', res.data) console.debug('database', this.database) - } catch (err) { - console.error('Could not load database', err) + } catch (error) { + console.error('Could not load database', error) this.$toast.error('Could not load database') } this.loading = false diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue index f889988891..634c92fbed 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/import.vue @@ -1,5 +1,5 @@ <template> - <div v-if="isResearcher"> + <div v-if="canInsertTableData"> <v-toolbar flat> <v-toolbar-title>Create Table Schema (and Import Data) from .csv/.tsv</v-toolbar-title> </v-toolbar> @@ -186,7 +186,8 @@ </template> <script> import TableSchema from '@/components/TableSchema' -const { notEmpty, isNonNegativeInteger, isResearcher } = require('@/utils') +import { notEmpty, isNonNegativeInteger, isResearcher } from '@/utils' +import { listTables } from '@/api/table' export default { name: 'TableFromCSV', @@ -284,6 +285,12 @@ export default { .replace(/\s+/g, '-') .replace(/[^\w-]+/g, '') .replace(/--+/g, '_')) + }, + canInsertTableData () { + if (!this.user) { + return false + } + return this.user.roles.includes('insert-table-data') } }, mounted () { @@ -351,15 +358,13 @@ export default { async listTables () { try { this.loading = true - const res = await this.$axios.get(`/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table`, { - headers: { Authorization: `Bearer ${this.token}` } - }) + const res = await listTables(this.token, this.$route.params.container_id, this.$route.params.database_id) console.debug('tables', res.data) this.tableNames = res.data.map(t => t.internal_name) - } catch (err) { + } catch (error) { this.error = true - console.error('could not list tables', err) - this.$toast.error('Could not list tables') + console.error('Failed to list tables', error) + this.$toast.error('Failed to list tables') } this.loading = false }, diff --git a/fda-ui/pages/container/_container_id/database/_database_id/view/create.vue b/fda-ui/pages/container/_container_id/database/_database_id/view/create.vue index f48a14aa61..a78526e1bc 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/view/create.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/view/create.vue @@ -1,13 +1,11 @@ <template> - <div v-if="isResearcher"> + <div v-if="canCreateView"> <QueryBuilder mode="view" /> <v-breadcrumbs :items="items" class="pa-0 mt-2" /> </div> </template> <script> -import { isResearcher } from '@/utils' - export default { data () { return { @@ -30,8 +28,11 @@ export default { user () { return this.$store.state.user }, - isResearcher () { - return isResearcher(this.user) + canCreateView () { + if (!this.user) { + return false + } + return this.user.roles.includes('create-database-view') } } } diff --git a/fda-ui/utils/index.js b/fda-ui/utils/index.js index ce98464f42..3772dbb7a8 100644 --- a/fda-ui/utils/index.js +++ b/fda-ui/utils/index.js @@ -55,10 +55,7 @@ function formatUser (user) { return null } if (!('firstname' in user) || !('lastname' in user) || user.firstname === null || user.lastname === null) { - if (!('preferred_username' in user)) { - return null - } - return user.preferred_username + return user?.username } return user.firstname + ' ' + user.lastname } -- GitLab