Comments (2)
The first thing I have looked into is available libraries/options we could take, being:
- Roll our own/use browser history
- Reach router
- Remix router
- React router
Thoughts so far:
- Our expected usage does not require anything bespoke - ie we should not need to (nor should we when libraries exist) write our own implementation
- Remix is pre release, and has an interesting licence. It has some really nice features, but requires code to be layed out in a particular manner to work. The fact it has a non apache/MIT licence and is pre release for me means we should not use it for the Strimzi ui
- Reach has been somewhat superceeded by react router - https://reacttraining.com/blog/reach-react-router-future/ - while maintained, focus is on React router going forward.
Current proposal:
- Use the React Router library for routing implementation.
Next step for me is to consider how we could make use of GQL Introspection, and the metadata around a page. With some thought, a set of metadata could describe a page, and have parts of the page (or no page at all) be shown depending on what the backend can support. This meta can then be used to generate the routing/navigation implementation. Details to follow in my next comment.
from strimzi-ui.
Summary:
In considering the wider navigation, metadata to drive it and introspection, I have inadvertently dived down much lower into how the UI fundamentally could work than I was expecting. In doing so I have touched upon the intended metadata and introspection areas, but also authentication, authorization, feature flagging, client side configuration, security and access, translation, areas of global state as well as identifying a few pages/states/components the navigation would need available to be implemented. I have implemented a rough prototype to validate the approach where required, and confirmed it is viable. Below is a more detailed write up of my findings and what I have prototyped, but the short version is:
- A change to keep
Panels
components a just component implementation. They still will implement a page, but the metadata for them will be split out into a newPages
module - This
Pages
metadata will bind aPanel
component to 1 to N usages of thatPanel
across the UI as a page - referred to ascontexts
Panels
are bound directly (via JSX) or asynchronously (via React suspense/lazy and error boundaries - https://www.npmjs.com/package/react-error-boundary). This will impact bundling and build time - allowing pages which are only needed in particular cases to be served when required.
contexts
define a path at which the page will appear, its name, a feature flag for it, as well as a 'declarative' description of the type of page it is (along with other visual metadata). This is then used to render the navigation as required. It also details what is required to access it, from an authz point of view, but also from a backend implementation- The backend implementation is determined via GraphQL introspection. It maps the served Schema to Entities shown in the UI, and the ability to CRUD them. This logic is included in the UI Bootstrap code
- A call will be made to the backend by the UI to discover what a user can do (authorization) across all backend entities
- Both the backend support and authorization ability of a user will be represented in a simple CRUD type model, abstracting the implementation details of how these are provided from the wider UI
- At runtime, Navigation code will 'discover' and process the page metadata, building a routing structure with respect to what has been configured to show, what the backend supports and what the user can access. If a page is not accessible by a user, they will be shown an appropriate error screen if they try to access it
- Pages will be asynchronously loaded as a user (can and does) access them. This means the user only ever has the client side code they need (faster loading times)/should have access to. Support for this is already available in React, and our existing strimzi ui build. It will mean there may be a small loading state while the page is retrieved for the first time.
Next steps:
Discuss all this, and write up. Comments very welcome!
Full write up:
Security on example - user to provide username/password via oauth. If Security off, no challenge is made, and user can access/do everything (response from userauth
call mentioned below). This security on state should be the more involved flow.
- UI configured/deployed with oauth mechanism - this configuration is known by express, and handles the oauth dance on request
- User accesses UI, Oauth dance completes and user is authenticated - express serves index/public content/config in html templated in
- Bootstrap code inits - including Navigation
- Config in index.html parsed and stored in context (configContext)
- Core pages load as needed:
/
(while next phase occurs)- error pages (404, 403 etc) as well as 'no pages to show' state
- Bootstrap calls express
userauth
endpoint, which replies with the user's name, and details of what they can/cannot do against authorized resources - eg topics, groups, cluster etc. Express (based on auth mechanism) to abstract how this info gathered depending on oauth/scram etc, and return UI specific entity/crud response (abstract the backing authz mechanism). Result referred to from now on as canI - Username and canI state stored in context
- Bootstrap starts GQL Introspect - a GQL Query is made to the admin-api, and the response compared/mapped to what the UI requires to operate. loading state is shown. If error, error state shown
- Introspect logic runs, result stored in context
- When we have authz result and introspect results, we trigger the navigation to look at all the pages defined, and determine what we will show the user.
The navigation logic could work as follows:
import * as pages from 'Pages';
Where:
New alias - Pages => client/pages/
We deliberately do not hard code or reference a single page - this is meant to be dynamic. client/pages/
hosts a barrel file, which references a number of configuration modules.
Eg (Topic) Panel page.js:
export const Topic {
contentComponent: () => import('Panel/Topic') // example of async import of view from a panel representing the whole topic page - must be exported as default to work in this case - will be wrapped by suspense/lazy in nav code
contexts: [ // all the contexts or scenarios that panel component is used in
{
name: TRANSLATION_KEY, // name of page - key used in translation
path: '/path/to/page', // path for page - used for id (encoded) - must be unique
feature_flag: PAGE.TOPICS, // feature flag for page
order: 0, // simple numeric value to enable sorting if more than one peers are available
Icon: <Icon />, // optional - a page may wish to have an icon to identify it in top level navigation, or at sub levels when there are many peers. If not required, should be undefined
pageType: NORMAL, // type of page this is (and thus what supporting UI nav elements are rendered). Suggested types: NORMAL (1st level nav, 2nd level nav with title, breadcrumb if not top level page (based on route)), CREATE (back breadcrumb item only), HOME (1st level only)
requiresMinimum: { // minimum requirements of this page - if one element missing, will not render component - instead will be 404 (backendSupportFor) or 403 (authorizationOf) page
backendSupportFor: {
Topic: {create, read, update, delete} // this page needs all these things supported by the backend for Entity 'Topic' - from GQL Introspect check
},
authorizationOf: {
Topic: {create, read, update, delete}, // this page needs a user to be able to CRUD a kafka topic - from authz check on login
Group: { read } // this page needs a user to be able to read kafka consumer groups - from authz check on login
}
},
... // other keys here as appropriate
}
]
}
Note the content of Topic and Group above. Deliberately the values are generic CRUD type values, and do not map to actual backing implementation (eg Kafka ACLS). This keeps the format the same for both authz and introspection.
We use these pages
metadata shapes when we have both authz and introspect results as mentioned in a registerPages function:
const routes = registerPages(Object.values(pages));
registerPages returns object, being:
{
links: [] // Configuration for React Router `Link` components for all pages which are leafs of / .
routes: [] // Configuration for React Router `Route` objects for all pages - the components they map to a result of the reduction of requiresMinimum.
meta: { // metadata - a map of route to meta, such as the page name and type, as well as how pages relate.
'/foo': {
name: 'TRANSLATION_KEY',
pageType: NORMAL,
order: 0,
icon: <Icon/>,
isTopLevel: true, // specifies if a top level page (or not) - determined/derived by paths
leaves: [{ // list child pages of this page - determined/derived by paths
path: '/foo/bar',
name: 'BAR_TRANSLATION_KEY'
},
...]
}
}
}
determined by iterating over each 'page':
- predefine standard routes - 404 403, loading etc
- if path already processed - error - no duplicate paths allowed. If new, add to set processed for next iteration
- if path leaf of
/
, createLink
config entry for it. - looking at all objects and paths provided, see if any leafs of this leaf - calculate and store in meta if so
- look at user's authz ability returned vs
requiresMinimum.authorizationOf
and introspect result forrequiresMinimum.backendSupportFor
from context.
- if all present, Route contains path, and component = React.lazy call to
component
for this page - if
requiresMinimum.authorizationOf
not met, Route contains path, but component is custom 403 page (as user does not have all required rights) - if
requiresMinimum.backendSupportFor
not met, Route contains path, but component is custom 404 page (as backend cannot support request)
Resulting links and routes iterated and rendered by nav view logic:
- Processed meta for current page provided via model to render (or not) sections of navigation UI, titles, icons etc
- React router links and routes rendered to configured components, as per metadata
- Nav logic will pass props and path parameters to panels rendered (current Entity name, forward/back functions etc)
In short, to fully enable all this we will need:
-Translation approach
-Loading state component
-Error state component
-404/403 page, loading page, error page, 'no pages' page (if user cannot access any pages, or if the backend cannot support any pages)
-User/auth context on client side - userauth
express call which returns username and what they can do
-Entity available/introspect context and backing model logic
-General config context
-Nav component
- Discovers (via alias) all 'Pages'
- Renders routes for reduced set based on what the user can access and the backend can support
- Renders supporting navigation page components (TBD)
- Renders the following when async getting panel components on access:
-Loading state component (via suspense)
-Error state component (via error boundary - https://www.npmjs.com/package/react-error-boundary)
from strimzi-ui.
Related Issues (20)
- Bundle Analyzer GHA crashes on webpack 5 output HOT 5
- Where to report security issues? HOT 4
- Production build of UI browser bundle is bunding test libraries HOT 1
- Production server is not rendering index.html HOT 1
- Revisit commenter HOT 1
- Refactor code to make use of Lerna and Yarn Projects HOT 1
- Implement Update Topics Page. HOT 4
- Implement the Delete Topics feature for the UI HOT 4
- Implement the Read Only View for Topics HOT 3
- Implement Search for Topics listing Page HOT 2
- Implement empty topics listing page HOT 3
- Implement Error Handling for Topics HOT 1
- Implement masthead and page navigation. HOT 3
- Create consumer group view HOT 1
- Handle errors for consumer groups HOT 1
- REST API for consumer group HOT 1
- Delete a consumer group HOT 1
- Create Consumer group table HOT 1
- Updated consumer group view HOT 1
- Create mock data for REST HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from strimzi-ui.