This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch ui
in repository https://gitbox.apache.org/repos/asf/polaris-tools.git
The following commit(s) were added to refs/heads/ui by this push:
new a3f0fb9 Add browse/principals views
a3f0fb9 is described below
commit a3f0fb9f6897ad850405df7d244fe63da5ade8cf
Author: JB Onofré <[email protected]>
AuthorDate: Mon Nov 3 09:26:44 2025 +0100
Add browse/principals views
---
ui/src/browse.tsx | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++
ui/src/catalog.tsx | 65 +++++++++++++++++++++++++++++++++++++------
ui/src/principals.tsx | 71 +++++++++++++++++++++++++++++++++++++++++++++++
ui/src/workspace.tsx | 34 +++++++++++++++--------
4 files changed, 227 insertions(+), 20 deletions(-)
diff --git a/ui/src/browse.tsx b/ui/src/browse.tsx
new file mode 100644
index 0000000..d0bf467
--- /dev/null
+++ b/ui/src/browse.tsx
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+import { useParams } from 'react-router';
+import { Link } from 'react-router-dom';
+import { Breadcrumb, Card, Space, Tree, Row, Col } from 'antd';
+import { HomeOutlined, ApartmentOutlined } from '@ant-design/icons';
+
+export default function Browse(props) {
+
+ const bearer = 'Bearer ' + props.token;
+
+ let { catalogName } = useParams();
+
+ const browse = () => {
+ fetch("/api/catalog/v1/namespaces", {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': bearer
+ }
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(response.status);
+ }
+ })
+ };
+
+ const test = [{
+ title: catalogName,
+ key: catalogName,
+ children: [
+ {
+ title: 'Namespace 1',
+ key: 'nameespace-1',
+ children: [
+ { title: 'Table A', key: 'table-a', children: [] }
+ ]
+ },
+ {
+ title: 'Namespace 2', key: 'namespace-2', children: []
+ }
+ ]
+ }];
+
+ return(
+ <>
+ <Breadcrumb items={[ { title: <Link to="/"><HomeOutlined/></Link> }, {
title: <ApartmentOutlined/> } ]} />
+ <Card title={<Space><ApartmentOutlined/> {catalogName}</Space>}
style={{ width: '100%' }} >
+ <Row>
+ <Col span="12">
+ <Tree treeData={test} />
+ </Col>
+ <Col span="12">
+
+ </Col>
+ </Row>
+ </Card>
+ </>
+ );
+}
\ No newline at end of file
diff --git a/ui/src/catalog.tsx b/ui/src/catalog.tsx
index 0b394ae..2181998 100644
--- a/ui/src/catalog.tsx
+++ b/ui/src/catalog.tsx
@@ -19,8 +19,8 @@
import { useState, useEffect } from 'react';
import { useParams } from 'react-router';
import { Link } from 'react-router-dom';
-import { Breadcrumb, Card, Form, Input, Select, Tabs, Collapse, Divider,
Button, Space, message } from 'antd';
-import { HomeOutlined, ApartmentOutlined, AmazonOutlined, GoogleOutlined,
CloudOutlined, FileSyncOutlined, SaveOutlined, PauseCircleOutlined } from
'@ant-design/icons';
+import { Breadcrumb, Spin, Card, Form, Input, Select, Tabs, Collapse, Divider,
Button, Space, Popconfirm, message } from 'antd';
+import { HomeOutlined, ApartmentOutlined, AmazonOutlined, GoogleOutlined,
CloudOutlined, FileSyncOutlined, SaveOutlined, PauseCircleOutlined,
DeleteOutlined } from '@ant-design/icons';
function S3() {
@@ -149,12 +149,14 @@ export default function Catalog(props) {
const [ catalogDetail, setCatalogDetail ] = useState();
const bearer = 'Bearer ' + props.token;
+ const realmHeader = props.realmHeader;
+ const realm = props.realm;
const [ catalogForm ] = Form.useForm();
let { catalogName } = useParams();
- const onFinish = (values) => {
+ const createCatalog = (values) => {
const request = {
'catalog': {
'name': values.name,
@@ -173,7 +175,7 @@ export default function Catalog(props) {
body: JSON.stringify(request),
headers: {
'Content-Type': 'application/json',
- 'Polaris-Realm': 'POLARIS',
+ realmHeader: realm,
'Authorization': bearer
}
})
@@ -186,7 +188,8 @@ export default function Catalog(props) {
.then((data) => {
console.log(data);
message.info('Catalog ' + data.name + ' created.');
- setCatalogName(data.name);
+ props.fetchCatalogs();
+ catalogName = data.name;
})
.catch((error) => {
message.error('An error occurred: ' + error.message);
@@ -194,6 +197,10 @@ export default function Catalog(props) {
});
};
+ let cardTitle = 'Create Catalog';
+ let onFinish = createCatalog;
+ let deleteButton = null;
+
if (catalogName) {
// get catalog details
const fetchCatalogDetail = () => {
@@ -201,7 +208,7 @@ export default function Catalog(props) {
method: 'GET',
headers: {
'Content-Type': 'application/json',
- 'Polaris-Realm': 'POLARIS',
+ realmHeader: realm,
'Authorization': bearer
}
})
@@ -212,8 +219,13 @@ export default function Catalog(props) {
return response.json();
})
.then((data) => {
- console.log(data);
- setCatalogDetail(data);
+ const catalogDetailValues = {
+ name: data.name,
+ type: data.type,
+ storageType: data.storageConfigInfo.storageType,
+ allowedLocations: data.storageConfigInfo.allowedLocations,
+ };
+ setCatalogDetail(catalogDetailValues);
})
.catch((error) => {
message.error('An error occurred: ' + error.message);
@@ -222,12 +234,46 @@ export default function Catalog(props) {
};
useEffect(fetchCatalogDetail, [catalogName]);
+
+ if (!catalogDetail) {
+ return(<Spin/>);
+ }
+
+ cardTitle = 'Catalog ' + catalogDetail.name;
+ // TODO implement catalog update (PUT)
+ onFinish = () => console.log('Update catalog');
+
+ const deleteCatalog = (catalog) => {
+ fetch('/api/management/v1/catalogs/' + catalog, {
+ method: 'DELETE',
+ headers: {
+ 'Authorization': bearer
+ }
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(response.status);
+ }
+ return response;
+ })
+ .then((data) => {
+ message.info('Catalog ' + catalog + ' has been removed');
+ props.fetchCatalogs();
+ // TODO redirect to home with <Redirect />
+ })
+ .catch((error) => {
+ message.error('An error occurred: ' + error.message);
+ console.error(error);
+ })
+ };
+
+ deleteButton = <Popconfirm title="Delete Catalog" description="Are you
sure you want to delete this catalog ?" okText="Yes" cancelText="No"
onConfirm={() => deleteCatalog(catalogDetail.name) }><Button danger
icon={<DeleteOutlined/>}>Delete</Button></Popconfirm>;
}
return(
<>
<Breadcrumb items={[ { title: <Link to="/"><HomeOutlined/></Link> }, {
title: <ApartmentOutlined/> } ]} />
- <Card title="Create Catalog" style={{ width: '100%' }}>
+ <Card title={cardTitle} style={{ width: '100%' }}>
<Form name="catalog" form={catalogForm} labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ width: '100%' }}
@@ -253,6 +299,7 @@ export default function Catalog(props) {
<Space>
<Button type="primary" icon={<SaveOutlined/>} onClick={()
=> catalogForm.submit()}>Save</Button>
<Button icon={<PauseCircleOutlined/>} onClick={() =>
catalogForm.resetFields()}>Cancel</Button>
+ { deleteButton }
</Space>
</Form.Item>
</Form>
diff --git a/ui/src/principals.tsx b/ui/src/principals.tsx
new file mode 100644
index 0000000..24379b1
--- /dev/null
+++ b/ui/src/principals.tsx
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+import { Link } from 'react-router-dom';
+import { Breadcrumb, Card, Space, Button, Popconfirm } from 'antd';
+import { HomeOutlined, UserOutlined, DeleteOutlined, EditOutlined } from
'@ant-design/icons';
+
+export default function Principals(props) {
+
+ const fetchPrincipals = () => {
+ fetch('/api/management/v1/principals', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+
+ }
+ })
+ };
+
+ const principalColumns = [
+ {
+ title: 'Principal',
+ dataIndex: 'name',
+ key: 'name'
+ },
+ {
+ title: 'Client ID',
+ dataIndex: 'clientId',
+ key: 'clientId'
+ },
+ {
+ title: '',
+ key: 'action',
+ render: (_,record) => (
+ <Space>
+ <Button><EditOutlined/></Button>
+ <Popconfirm title="Delete Principal"
+ description="Are you sure you want to delete this
principal ?"
+ okText="Yes" cancelText="No">
+ <Button danger icon={<DeleteOutlined/>} />
+ </Popconfirm>
+ </Space>
+ )
+ }
+ ];
+
+ return(
+ <>
+ <Breadcrumb items={[ { title: <Link to="/"><HomeOutlined/></Link> }, {
title: <UserOutlined/> } ]} />
+ <Card title="Principals" style={{ width: '100%' }}>
+
+ </Card>
+ </>
+ );
+
+}
\ No newline at end of file
diff --git a/ui/src/workspace.tsx b/ui/src/workspace.tsx
index e23ac64..0cf951c 100644
--- a/ui/src/workspace.tsx
+++ b/ui/src/workspace.tsx
@@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-
import React, { useState, useEffect } from 'react';
import { Layout, Input, Col, Row, Image, Menu, Space, Spin, message } from
'antd';
import { Route, Switch } from 'react-router';
import { BrowserRouter as Router } from 'react-router-dom';
import { Link } from 'react-router-dom';
-import { HomeOutlined, UserOutlined, BlockOutlined, SettingOutlined,
DeleteOutlined, PlayCircleOutlined, LogoutOutlined, ApartmentOutlined,
DashboardOutlined, AreaChartOutlined, NotificationOutlined, SafetyOutlined,
TeamOutlined, BuildOutlined, CrownOutlined, ProfileOutlined,
CheckSquareOutlined, PlusCircleOutlined } from '@ant-design/icons';
+import { HomeOutlined, UserOutlined, BlockOutlined, SettingOutlined,
DeleteOutlined, MenuUnfoldOutlined, PlayCircleOutlined, LogoutOutlined,
ApartmentOutlined, DashboardOutlined, AreaChartOutlined, NotificationOutlined,
SafetyOutlined, TeamOutlined, BuildOutlined, CrownOutlined, ProfileOutlined,
CheckSquareOutlined, PlusCircleOutlined } from '@ant-design/icons';
import Home from './home.tsx';
import Catalog from './catalog.tsx';
+import Browse from './browse.tsx';
+import Principals from './principals.tsx';
import Settings from './settings.tsx';
function SideMenu(props) {
@@ -32,11 +33,16 @@ function SideMenu(props) {
const[ collapsed, setCollapsed ] = useState(false);
const catalogElementsMenu = props.catalogs.map((element) => {
- const link = "/catalog/" + element.name;
+ const configLink = "/catalog/" + element.name;
+ const browseLink = "/browse/" + element.name;
return({
key: element.name,
- label: <Link to={link}>{element.name}</Link>,
- icon: <ApartmentOutlined/>
+ label: element.name,
+ icon: <ApartmentOutlined/>,
+ children: [
+ { key: browseLink, label: <Link to={browseLink}>Browse</Link>,
icon: <MenuUnfoldOutlined/> },
+ { key: configLink, label: <Link
to={configLink}>Configuration</Link>, icon: <SettingOutlined/> }
+ ]
});
});
const newCatalogMenu = [
@@ -53,7 +59,7 @@ function SideMenu(props) {
{ key: 'home', label: <Link to="/">Home</Link>, icon: <HomeOutlined/>
},
{ key: 'catalogs', label: 'Catalogs', icon: <BlockOutlined/>,
children: catalogMenu },
{ key: 'governance', label: 'Governance', icon: <SafetyOutlined/>,
children: [
- { key: 'principals', label: 'Principals', icon: <UserOutlined/> },
+ { key: 'principals', label: <Link
to="/principals">Principals</Link>, icon: <UserOutlined/> },
{ key: 'principal_roles', label: 'Principal Roles', icon:
<TeamOutlined/> },
{ key: 'catalog_roles', label: 'Catalog Roles', icon:
<BuildOutlined/> },
{ key: 'privileges', label: 'Privileges', icon: <CrownOutlined/> }
@@ -140,13 +146,19 @@ export default function Workspace(props) {
<Route path="/" key="home" exact={true}>
<Home catalogs={catalogs} token={props.token}
fetchCatalogs={fetchCatalogs} />
</Route>
- <Route path="/catalog/create" key="catalog-create"
exact={true}>
- <Catalog token={props.token}
fetchCatalogs={fetchCatalogs} />
+ <Route path="/catalog/create" key="catalog-create"
exact>
+ <Catalog token={props.token}
fetchCatalogs={fetchCatalogs} realmHeader={realmHeader} realm={realm} />
+ </Route>
+ <Route path="/catalog/:catalogName"
key="catalogSettings">
+ <Catalog token={props.token}
fetchCatalogs={fetchCatalogs} realmHeader={realmHeader} realm={realm} />
+ </Route>
+ <Route path="/browse/:catalogName" key="catalogBrowse">
+ <Browse token={props.token}
realmHeader={realmHeader} realm={realm} />
</Route>
- <Route path="/catalog/:catalogName" key="catalog">
- <Catalog token={props.token}
fetchCataalogs={fetchCatalogs} />
+ <Route path="/principals" key="principals" exact>
+ <Principals token={props.token}
realmHeader={realmHeader} realm={realm} />
</Route>
- <Route path="/settings" key="settings" exect={true}>
+ <Route path="/settings" key="settings" exact>
<Settings realm={realm} realmHeader={realmHeader}
setRealm={setRealm} setRealmHeader={setRealmHeader} />
</Route>
</Switch>