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>

Reply via email to