diff --git a/src/app.scss b/src/app.scss index 1f7e08f83..6937e231c 100644 --- a/src/app.scss +++ b/src/app.scss @@ -22,6 +22,7 @@ // page styles @import 'assets/styles/pages/instances'; +@import 'assets/styles/pages/browse'; @import 'assets/styles/pages/organizations'; @import 'assets/styles/pages/organization'; @import 'assets/styles/pages/auth'; diff --git a/src/assets/styles/components/_web-ide.scss b/src/assets/styles/components/_web-ide.scss index 313f90172..5fd2b2148 100644 --- a/src/assets/styles/components/_web-ide.scss +++ b/src/assets/styles/components/_web-ide.scss @@ -19,7 +19,6 @@ } .name-input { - .validation-message { color: $color-danger; } @@ -68,7 +67,6 @@ } .purple { - button[disabled]:hover, button[disabled] { color: #5e5e5e !important; @@ -94,6 +92,8 @@ color: white; + color: rgb(105, 105, 105); + .name-input { .name-input-container { input.invalid:focus { @@ -101,9 +101,10 @@ outline-color: #ea4c89 !important; } } + .validation-message { - margin-top: 8px; color: $color-danger; + margin-top: 8px; } & > input.invalid { @@ -125,6 +126,7 @@ .editor-window .cancel-button { color: rgb(105, 105, 105);; } + .editor-window .cancel-button:hover { color: white; } @@ -137,8 +139,6 @@ border-bottom: 1px solid lightgray; } - color: rgb(105, 105, 105); - .default-window-option { span { @@ -199,13 +199,11 @@ .akamai, .light { - .editor-window { border: 1px solid $color-lightergrey; } .name-input { - .validation-message { color: $color-danger; } @@ -295,8 +293,6 @@ } .web-ide { - - .cancel-button { margin-left: 10px; } @@ -577,10 +573,10 @@ } .name-input-container { + align-items: center; display: flex; flex-direction: row; - align-items: center; .invalid-project-name { margin-left: 15px; @@ -735,16 +731,16 @@ } .package-install-window { - .install-package-button { - width: 105px; - display: inline-flex; align-items: center; + display: inline-flex; justify-content: center; + width: 105px; .install-package-status-icon { } } + &.github { line-height:0.5em; } @@ -807,8 +803,8 @@ .project-name-invalid-text { color: $color-danger !important; - margin-bottom: 20px; display: block; + margin-bottom: 20px; } diff --git a/src/assets/styles/pages/_browse.scss b/src/assets/styles/pages/_browse.scss new file mode 100644 index 000000000..0bb934802 --- /dev/null +++ b/src/assets/styles/pages/_browse.scss @@ -0,0 +1,33 @@ +#browse { + .missing-primary-key { + align-items: center; + display: flex; + flex-direction: column; + height: calc(100vh - 262px) !important; + justify-content: center; + line-height: 1.5 !important; + + > .warning-text { + color: $color-warning !important; + } + + &.narrow { + padding-left: 25%; + padding-right: 25%; + } + + .card-body { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1.5 !important; + padding-left: 25%; + padding-right: 25%; + + .fa.fa-warning { + color: $color-danger; + } + } + } +} diff --git a/src/components/instance/browse/BrowseDatatable.js b/src/components/instance/browse/BrowseDatatable.js index 89947fe5c..58e699f06 100644 --- a/src/components/instance/browse/BrowseDatatable.js +++ b/src/components/instance/browse/BrowseDatatable.js @@ -115,42 +115,42 @@ function BrowseDatatable({ tableState, setTableState, activeTable }) { return ( <> - setLastUpdate(Date.now())} - toggleAutoRefresh={() => setTableState({ ...tableState, autoRefresh: !tableState.autoRefresh })} - toggleFilter={() => setTableState({ ...tableState, showFilter: !tableState.showFilter })} - /> - - - { - setTableState({ ...tableState, page: 0, filtered: value }) - }} - onSortedChange={(value) => setTableState({ ...tableState, page: 0, sorted: value })} - onPageChange={(value) => setTableState({ ...tableState, page: value })} - onPageSizeChange={(value) => setTableState({ ...tableState, page: 0, pageSize: value })} - onRowClick={(rowData) => { - const hashValue = rowData[tableState.hashAttribute]; - navigate(`/o/${customer_id}/i/${compute_stack_id}/browse/${schema}/${table}/edit/${rowData[tableState.hashAttribute]}`, { - state: { hashValue } - }) - }} - /> - - + setLastUpdate(Date.now())} + toggleAutoRefresh={() => setTableState({ ...tableState, autoRefresh: !tableState.autoRefresh })} + toggleFilter={() => setTableState({ ...tableState, showFilter: !tableState.showFilter })} + /> + + + { + setTableState({ ...tableState, page: 0, filtered: value }) + }} + onSortedChange={(value) => setTableState({ ...tableState, page: 0, sorted: value })} + onPageChange={(value) => setTableState({ ...tableState, page: value })} + onPageSizeChange={(value) => setTableState({ ...tableState, page: 0, pageSize: value })} + onRowClick={(rowData) => { + const hashValue = rowData[tableState.hashAttribute]; + navigate(`/o/${customer_id}/i/${compute_stack_id}/browse/${schema}/${table}/edit/${rowData[tableState.hashAttribute]}`, { + state: { hashValue } + }) + }} + /> + + ); } diff --git a/src/components/instance/browse/index.js b/src/components/instance/browse/index.js index 0f3368e02..197a4020d 100644 --- a/src/components/instance/browse/index.js +++ b/src/components/instance/browse/index.js @@ -1,10 +1,11 @@ import React, { useState, useEffect, lazy } from 'react'; -import { Row, Col } from 'reactstrap'; +import { Row, Col, Card, CardBody, CardTitle } from 'reactstrap'; import { useLocation, useParams, useNavigate } from 'react-router-dom'; import { useStoreState } from 'pullstate'; import { ErrorBoundary } from 'react-error-boundary'; import instanceState from '../../../functions/state/instanceState'; +import describeTable from '../../../functions/api/instance/describeTable'; import ErrorFallback from '../../shared/ErrorFallback'; import addError from '../../../functions/api/lms/addError'; @@ -31,6 +32,20 @@ const defaultTableState = { hashAttribute: false, }; +function NoPrimaryKeyMessage({ table }) { + return ( + + + No Primary Key + + + The table { `'${table}'` } does not have a primary key. The HarperDB Studio does not currently support tables wihtout a primary key defined. Please see the HarperDB documention to see the standard HarperDB querying options. + + + + ); +} + function BrowseIndex() { const navigate = useNavigate(); const location = useLocation(); @@ -51,16 +66,30 @@ function BrowseIndex() { const emptyPromptMessage = showForm ? `Please ${(schema && entities.tables && !entities.tables.length) || !entities.schemas.length ? 'create' : 'choose'} a ${schema ? 'table' : `${versionAsFloat >= 4.2 ? 'database' : 'schema'}` }` : "This user has not been granted access to any tables. A super-user must update this user's role."; + const [hasHashAttr, setHasHashAttr] = useState(true); const syncInstanceStructure = () => { buildInstanceStructure({ auth, url }); } + const checkForHashAttribute = () => { + async function check() { + if (table) { + const result = await describeTable({ auth, url, schema, table }); + setHasHashAttr(Boolean(result.hash_attribute)); + } + } + + check(); + }; + + useEffect(checkForHashAttribute, [auth, url, schema, table]); + const validate = () => { if (structure) { /* - * FIXME: There is a fair amount of logic scattered throughout this + * FIXME: There is a fair amount of logic scattered throughout this * page that could be put in a router-level validation function. * * Splitting the browse endpoint into /schema/ and /schema/table heirarchy @@ -102,7 +131,7 @@ function BrowseIndex() { useEffect(syncInstanceStructure, [auth, url, schema, table]); return ( - + addError({ error: { message: error.message, componentStack } })} @@ -132,15 +161,20 @@ function BrowseIndex() { addError({ error: { message: error.message, componentStack } })} FallbackComponent={ErrorFallback}> - {schema && table && action === 'csv' && entities.activeTable ? ( - - ) : schema && table && action && entities.activeTable ? ( - - ) : schema && table && entities.activeTable ? ( - - ) : ( - } /> - )} + { + hasHashAttr ? ( + schema && table && action === 'csv' && entities.activeTable ? ( + + ) : schema && table && action && entities.activeTable ? ( + + ) : schema && table && entities.activeTable ? ( + + ) : ( + } /> + ) + ) : + + } diff --git a/src/functions/instance/getTableData.js b/src/functions/instance/getTableData.js index d0e21c5a5..00a0b2d55 100644 --- a/src/functions/instance/getTableData.js +++ b/src/functions/instance/getTableData.js @@ -86,7 +86,7 @@ export default async ({ schema, table, filtered, pageSize, sorted, page, auth, u if (allAttributes.includes('__createdtime__')) orderedColumns.push('__createdtime__'); if (allAttributes.includes('__updatedtime__')) orderedColumns.push('__updatedtime__'); - const dataTableColumns = [ hashAttribute, ...orderedColumns ].map((k) => ({ + const dataTableColumns = (hashAttribute ? [ hashAttribute, ...orderedColumns ] : [ ...orderedColumns ]).map((k) => ({ Header: k.toString(), accessor: (row) => row[k.toString()], }));