GithubHelp home page GithubHelp logo

Comments (2)

matthew-chirgwin avatar matthew-chirgwin commented on June 10, 2024

The first thing I have looked into is available libraries/options we could take, being:

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.

matthew-chirgwin avatar matthew-chirgwin commented on June 10, 2024

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 new Pages module
  • This Pages metadata will bind a Panel component to 1 to N usages of that Panel across the UI as a page - referred to as contexts
    • 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.

  1. UI configured/deployed with oauth mechanism - this configuration is known by express, and handles the oauth dance on request
  2. User accesses UI, Oauth dance completes and user is authenticated - express serves index/public content/config in html templated in
  3. 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
  1. 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
  2. Username and canI state stored in context
  3. 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
  4. Introspect logic runs, result stored in context
  5. 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':

  1. predefine standard routes - 404 403, loading etc
  2. if path already processed - error - no duplicate paths allowed. If new, add to set processed for next iteration
  3. if path leaf of /, create Link config entry for it.
  4. looking at all objects and paths provided, see if any leafs of this leaf - calculate and store in meta if so
  5. look at user's authz ability returned vs requiresMinimum.authorizationOf and introspect result for requiresMinimum.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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.