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
commit f76f25be7ff81971a61308edf4d62bd65eea9b75 Author: JB Onofré <[email protected]> AuthorDate: Sat Oct 25 08:56:16 2025 +0200 Bootstrap UI --- ui/package.json | 36 ++++++++++++++++ ui/public/favicon.ico | Bin 0 -> 1150 bytes ui/public/index.html | 17 ++++++++ ui/public/logo.png | Bin 0 -> 283351 bytes ui/public/robots.txt | 3 ++ ui/src/app.css | 43 +++++++++++++++++++ ui/src/app.tsx | 41 ++++++++++++++++++ ui/src/home.tsx | 44 +++++++++++++++++++ ui/src/index.css | 12 ++++++ ui/src/index.tsx | 26 ++++++++++++ ui/src/login.tsx | 82 +++++++++++++++++++++++++++++++++++ ui/src/workspace.tsx | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 419 insertions(+) diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..e168a3a --- /dev/null +++ b/ui/package.json @@ -0,0 +1,36 @@ +{ + "name": "polaris-ui", + "version": "1.3.0-incubating-SNAPSHOT", + "main": "/index.tsx", + "dependencies": { + "@ant-design/icons": "^6.0.0", + "antd": "^5.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-router-dom": "^5.3.0", + "react-scripts": "^5.0.1" + }, + "devDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "typescript": "^5" + }, + "scripts": { + "start": "react-scripts start", + "build": "BUILD_PATH=target/classes/META-INF/resources react-scripts build", + "eject": "react-scripts eject" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "http://localhost:8181" +} diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100644 index 0000000..6ca7eef Binary files /dev/null and b/ui/public/favicon.ico differ diff --git a/ui/public/index.html b/ui/public/index.html new file mode 100644 index 0000000..42c8ffe --- /dev/null +++ b/ui/public/index.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="theme-color" content="#000000" /> + <meta name="description" content="Apache Polaris, the lakehouse catalog" /> + <title>Apache Polaris</title> +</head> + +<body> + <noscript>You need to enable JavaScript to run this app.</noscript> + <div id="root"></div> +</body> + +</html> diff --git a/ui/public/logo.png b/ui/public/logo.png new file mode 100644 index 0000000..6573f7a Binary files /dev/null and b/ui/public/logo.png differ diff --git a/ui/public/robots.txt b/ui/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/ui/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/ui/src/app.css b/ui/src/app.css new file mode 100644 index 0000000..55c68b3 --- /dev/null +++ b/ui/src/app.css @@ -0,0 +1,43 @@ +.logo { + height: 48px; + margin: 16px; + display: flex; + align-items: center; + justify-content: space-evenly; + color: white; + font-size: large; +} + +.logo > a { + text-decoration: none; + color: white; +} + +.site-layout .site-layout-background { + background: #fff; +} +.layout { + min-height: 100vh !important; +} +.site-layout { + padding: 0 50px !important; +} +.ant-layout-header { + padding: 10px; +} +.ant-layout-sider { + background: #fff; +} +.ant-layout-sider-children > svg { + margin: auto; +} +.ant-breadcrumb { + margin: 16px 0px !important; +} +.site-layout-background { + padding: 24px !important; + min-height: 360px !important; +} +.ant-layout-footer { + text-align: center !important; +} diff --git a/ui/src/app.tsx b/ui/src/app.tsx new file mode 100644 index 0000000..df0f2a7 --- /dev/null +++ b/ui/src/app.tsx @@ -0,0 +1,41 @@ +/** + * 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 React, { useState } from 'react'; +import Login from './login.tsx'; +import Workspace from './workspace.tsx'; + +import './app.css'; + +export default function App() { + + const [ user, setUser ] = useState(); + const [ token, setToken ] = useState(); + + if (user) { + return( + <Workspace user={user} setUser={setUser} token={token} /> + ); + } else { + return( + <Login user={user} setUser={setUser} setToken={setToken} /> + ); + } + +} \ No newline at end of file diff --git a/ui/src/home.tsx b/ui/src/home.tsx new file mode 100644 index 0000000..d0f6b93 --- /dev/null +++ b/ui/src/home.tsx @@ -0,0 +1,44 @@ +/** + * 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, Row, Col, Card } from 'antd'; +import { HomeOutlined } from '@ant-design/icons'; + +export default function Home(props) { + + return( + <> + <Breadcrumb items={[ { title: <Link to="/"><HomeOutlined/></Link> } ]} /> + <Row> + <Col> + <Card title="Catalogs Overview"> + Catalogs + </Card> + </Col> + </Row> + <Row> + <Col> + <Card title="Governance Overview"> + Governance + </Card> + </Col> + </Row> + </> + ); +} \ No newline at end of file diff --git a/ui/src/index.css b/ui/src/index.css new file mode 100644 index 0000000..8fc644c --- /dev/null +++ b/ui/src/index.css @@ -0,0 +1,12 @@ +html, body { + padding: 0; + margin: 0; + background: #fff; + height: 100%; +} + +#root { + padding: 0; + background: #fff; + height: 100vh; +} \ No newline at end of file diff --git a/ui/src/index.tsx b/ui/src/index.tsx new file mode 100644 index 0000000..c70874a --- /dev/null +++ b/ui/src/index.tsx @@ -0,0 +1,26 @@ +/** + * 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 React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './app.tsx'; +import './index.css'; + +const root = createRoot(document.getElementById("root")); +root.render(<App />); \ No newline at end of file diff --git a/ui/src/login.tsx b/ui/src/login.tsx new file mode 100644 index 0000000..d06b938 --- /dev/null +++ b/ui/src/login.tsx @@ -0,0 +1,82 @@ +/** + * 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 React, { useState, useEffect } from 'react'; +import { Modal, Form, Button, Input, Space, Checkbox, Image, Spin, message } from 'antd'; + +export default function Login(props) { + + const [ loginForm ] = Form.useForm(); + const [ checked, setChecked ] = useState(true); + + return ( + <Modal centered={true} mask={false} title={<Space><Image width={30} src="/logo.png" preview={false}/> Apache Polaris (incubating) </Space>} open={true} okText="Login" cancelText="Cancel" closable={false} onOk={() => loginForm.submit()} onCancel={() => loginForm.resetFields()}> + <Form name="login" form={loginForm} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} autoComplete="off" onFinish={(values) => { + + console.log(values); + + const fetchUser = () => { + fetch('./api/catalog/v1/oauth/tokens', { + method: 'POST', + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams( + { + client_id: values.username, + client_secret: values.password, + scope: 'PRINCIPAL_ROLE:ALL', + grant_type: 'client_credentials' + } + ) + }) + .then((response) => { + if (!response.ok) { + throw new Error('Authentication failed (' + response.status + ')'); + } + return response.json(); + }) + .then((data) => { + props.setUser(values.username); + props.setToken(data.access_token); + }) + .catch((error) => { + message.error(error.message); + console.error(error); + }) + }; + + useEffect(fetchUser(), []); + + if (!props.token) { + return(<Spin/>); + } + + }} onKeyUp={(event) => { + if (event.keyCode === 13) { + loginForm.submit(); + } + }}> + <Form.Item name="username" label="Username" rules={[{ required: true, message: 'The username is required' }]}><Input allowClear={true} /></Form.Item> + <Form.Item name="password" label="Password" rules={[{ required: true, message: 'The password is required' }]}><Input.Password allowClear={true} /></Form.Item> + </Form> + </Modal> + ); + +} diff --git a/ui/src/workspace.tsx b/ui/src/workspace.tsx new file mode 100644 index 0000000..eb21d99 --- /dev/null +++ b/ui/src/workspace.tsx @@ -0,0 +1,115 @@ +/** + * 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 React, { useState } from 'react'; +import { Layout, Input, Col, Row, Image, Menu, Space } from 'antd'; +import { Route, Switch } from 'react-router'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { Link } from 'react-router-dom'; +import { UserOutlined, BlockOutlined, SettingOutlined, DeleteOutlined, PlayCircleOutlined, LogoutOutlined, ApartmentOutlined, DashboardOutlined, AreaChartOutlined, NotificationOutlined, SafetyOutlined, TeamOutlined, BuildOutlined, CrownOutlined, ProfileOutlined, CheckSquareOutlined, PlusCircleOutlined } from '@ant-design/icons'; +import Home from './home.tsx'; + +function SideMenu() { + + const[ collapsed, setCollapsed ] = useState(false); + + const mainMenu = [ + { key: 'catalogs', label: 'Catalogs', icon: <BlockOutlined/>, children: [ + { key: 'new', label: 'Create catalog', icon: <PlusCircleOutlined/> }, + { key: 'default', label: 'Default', icon: <ApartmentOutlined/> }, + { key: 'my', label: 'My Catalog', icon: <ApartmentOutlined/> } + ] }, + { key: 'governance', label: 'Governance', icon: <SafetyOutlined/>, children: [ + { key: 'principals', label: 'Principals', icon: <UserOutlined/> }, + { key: 'principal_roles', label: 'Principal Roles', icon: <TeamOutlined/> }, + { key: 'catalog_roles', label: 'Catalog Roles', icon: <BuildOutlined/> }, + { key: 'privileges', label: 'Privileges', icon: <CrownOutlined/> } + ]}, + { key: 'policies', label: 'Policies & TMS', icon: <CheckSquareOutlined/> }, + { key: 'observe', label: 'Observability', icon: <DashboardOutlined/>, children: [ + { key: 'metrics', label: 'Metrics', icon: <AreaChartOutlined/> }, + { key: 'events', label: 'Events', icon: <NotificationOutlined/> } + ]}, + { key: 'profiles', label: 'Profiles', icon: <ProfileOutlined/> }, + { key: 'settings', label: 'Settings', icon: <SettingOutlined/>, children: [ + { key: 'bootstrap', label: 'Bootstrap', icon: <PlayCircleOutlined/> }, + { key: 'purge', label: 'Purge', icon: <DeleteOutlined/> } + ] } + ]; + + return( + <Layout.Sider collapsible={true} collapsed={collapsed} onCollapse={newValue => setCollapsed(newValue)}> + <Menu items={mainMenu} mode="inline"/> + </Layout.Sider> + ); + +} + +function Header(props) { + + const { Search } = Input; + + const userMenu = [ + { key: 'user', label: props.user, icon: <UserOutlined/>, children: [ + { key: 'preferences', label: 'Preferences', icon: <SettingOutlined/> }, + { key: 'logout', label: 'Logout', icon: <LogoutOutlined/> } + ] } + ]; + + return( + <Layout.Header style={{ height: "80px", background: "#fff", padding: "5px", margin: "10px" }}> + <Row align="middle" justify="center" wrap="false"> + <Col span={3}><Image src="./logo.png" preview={false} width={60}/></Col> + <Col span={19}><Search /></Col> + <Col span={2}><Menu items={userMenu} onClick={(e) => { + if (e.key === 'logout') { + props.setUser(null); + } + if (e.key === 'preferences') { + console.log(e); + } + }} /></Col> + </Row> + </Layout.Header> + ); + +} + +export default function Workspace(props) { + + return( + <Layout style={{ height: "105vh" }}> + <Header user={props.user} setUser={props.setUser} /> + <Layout hasSider={true}> + <Router> + <SideMenu /> + <Layout.Content style={{ margin: "15px" }}> + <Switch> + <Route path="/" key="home" exact={true}> + <Home user={props.user} token={props.token} /> + </Route> + </Switch> + </Layout.Content> + </Router> + </Layout> + <Layout.Footer>Apache®, Apache Polaris™ are either registered trademarks or trademarks of the Apache Software Foundation in the United States and/or other countries.</Layout.Footer> + </Layout> + ); + +} \ No newline at end of file
