Giter Club home page Giter Club logo

frontend-core's Introduction

Tarantool Front-end Core

It's core module for admin UI. Any UI submodules should be register at core.

It has two parts. Lua part as rock that will be used at your project. JS part that will be compiled at Lua bundle. JS part based on React.

Architecture

Javascript

It's based on CRA. But with some details. Core is just a little React app that can route to correct module.

It can register a module in any time and route to it. This is main purpose.

Module can be described by module namespace, react root component and menu description, which can be described in two way(about it later).

Example of integration register a module:

const core = window.tarantool_enterprise_core;
const RootComponent = () => <div>Module</div>;
core.registerModule({
  namespace: 'module',
  menu: [{label: 'Module', path: '/module/'}],
  RootComponent,
});

window.tarantool_enterprise_core.install()

Register main react element in #root element. In Lua part it's called by default.

window.tarantool_enterprise_core.registerModule({ namespace: string, menu: menuShape, RootComponent: ComponentType, menuMiddleware?: (Object) => void})

namespace

name of namespace. It will be used as namespace of your module. Prepend for routing.

menu

It can be reducer for your menu or some static menu.

Default reducer(you can use something like this):

const matchPath = (path, link) => {
  const point = link.indexOf(path)
  return point === 0 && (link.length === path.length || link[path.length] === '/')
}

const selected = path => menuItem => ({
  ...menuItem,
  expanded: menuItem.items.length > 0
    ?( menuItem.path === path ? !menuItem.expanded : menuItem.expanded)
    : false,
})

const updateLink = path => menuItem => ({
    ...menuItem,
    selected: matchPath(path, menuItem.path)
})

const defaultReducer = (defaultState = []) => (state = defaultState, {type, payload}) => {
  console.log('default reducer', state, type, payload, defaultState)
  switch (type) {
    case constants.SELECT_MENU:
      return state.map(selected(payload.path))
    case '@@router/LOCATION_CHANGE':
      return state.map(updateLink(payload.location.pathname))
    case constants.RESET:
      return defaultState.map(updateLink(payload.path))
    default:
      return state
  }
}

Example of static menu:

[{label: 'Module', path: '/module/'}]

Or can be described in more way:

  label: string,
  path: string,
  selected: boolean,
  expanded: boolean,
  loading: boolean,
  items: [menuItem],

It's full state of menu item.

RootComponent

RootComponent can be any React Component. If you want using routing in module you should using history from core component for menu updating.

Example with react-router-dom:

class Root extends React.Component{
  render() {
    return (
      <div>
        test namespace:
        <Router history={window.tarantool_enterprise_core.history}>
          <Switch>
            <Route path={'/test/test2'}  component={() => <div>2</div>}/>
            <Route path={'/test/test1'} component={() => <div>1</div>}/>
            <Route path={'/'} component={() => <div>Default</div>}/>
          </Switch>
        </Router>
      </div>
    )
  }
}

Route should using same namespace of module. You should start all of your routes with /test.

You can use any technology inside that you want.

menuMiddleware

This is a redux middleware for dispatch some custom events or add reaction on your custom events.

It should be using if you want do async loading of menu elements or some another dynamic changes. Or you want dispatch action on menu events from other modules.

window.tarantool_enterprise_core.register(namespace: string, menu: menuShape, RootComponent: ComponentType, engine?: string, menuMiddleware?: (Object) => void)

Deprecated method. Arguments type same as new method.

menuMiddleware

This is a redux middleware for dispatch some custom events or add reaction on your custom events.

It should be using if you want do async loading of menu elements or some another dynamic changes. Or you want dispatch action on menu events from other modules.

window.tarantool_enterprise_core.checkNamespace(module: string)

Throw error if module is already registered.

window.tarantool_enterprise_core.getModules() : Array<module>

Return registered modules.

window.tarantool_enterprise_core.notify({ title: string, message: string, details?: string, type: 'default' | 'success' | 'error', timeout: number}) : void

Show notification. Title and message are text of notification.

Type influence on view of notification.

Timeout is time in milliseconds for automatic hide notification. If timeout = 0. It's infinite time. You only can close it by your hands.

window.tarantool_enterprise_core.dispatch('setAppName', name: string)

Sets application name.

window.tarantool_enterprise_core.subscribe(eventType: string, callback: Function) : unsubscribe function

Return unsubscribe function.

Subscribe to events of certain type. This can be used for cross module reaction.

callback(event) - event could be anything include null.

Example:

const unwait = this.subscribe('registerModule', () => {
    const modules = this.getModules().filter(x => x.namespace === namespace)
    if (modules.length > 0) {
      unwait();
      resolve(true)
    }
})

window.tarantool_enterprise_core.dispatch(eventType: string, event: any)

It's for cross module reaction. You can dispatch anything that you want.

window.tarantool_enterprise_core.components

Collection of React components working with frontend core.

Example:

AppTitle - sets application title

window.tarantool_enterprise_core.apiMethods.registerAxiosHandler(eventType: 'request' | 'response' | 'requestError' | 'responseError', handler: (any, Array) => any, priority?: number = 0)

Register axios handler. It's use a single responsibility chain inside. Handler should looks like:

(r, next) => next(r + 1)

Handlers with negative priority will be called before handlers without priority.

More about context of usage [https://github.com/axios/axios#interceptors]. So request is request interceptor and requestError it's error on request. Also same for response with interceptors.response.

window.tarantool_enterprise_core.apiMethods.axiosWizard(axiosInstance)

You should use it for your axios instance for using same middlewares.

Example:

window.tarantool_enterprise_core.apiMethods.axiosWizard(axiosInstance)

window.tarantool_enterprise_core.apiMethods.registerApolloHandler(eventType: 'middleware' | 'onError' | 'afterware', handler: (any, Array) => any, priority?: number)

Register handler for Apollo. Handler should looks like it:

(r, next) => next(r + 1)
  • middleware runs before query;

  • afterware handles successful responses;

  • onError handles errors.

Handlers with negative priority will be called before handlers without priority.

More information here: [https://www.apollographql.com/docs/react/networking/network-layer/]

window.tarantool_enterprise_core.apiMethods.apolloLinkMiddleware

Link middleware. Should use for working handlers.

Example of usage:

import { from } from 'apollo-link'

new ApolloClient({
    link: from([
        window.tarantool_enterprise_core.apiMethods.apolloLinkMiddleware,
        yourHttpLink,
    ])
})

window.tarantool_enterprise_core.apiMethods.apolloLinkAfterware

Link afterware. Should use for working handlers.

Example of usage:

import { from } from 'apollo-link'

new ApolloClient({
    link: from([
        window.tarantool_enterprise_core.apiMethods.apolloLinkAfterware,
        yourHttpLink,
    ])
})

window.tarantool_enterprise_core.analyticModule

We add our analytic abstraction module. It support 2 types of event: Pageview and Action. By default it doesn't send any information.

You could send event with sendEvent and add own implementation of analytic by usage effect.

window.tarantool_enterprise_core.analyticModule.sendEvent({
    type: 'pageview',
    url: 'https://tarantool.io'
})

const unsubscribe = window.tarantool_enterprise.analyticModule.effect((event) => {
    axios.post('https://myanalytics.io', { pageview: event.url })
})

unsubscribe() // unsubscribe from effect

Typings:

type Action = {
  type: 'action',
  action: string,
  category: string,
  label?: string,
  value?: number
}

type PageView = {
  type: 'pageview',
  url: string
}

type AnalyticsEvent = Action | PageView

sendEvent(AnalyticsEvent)
effect(Function)

window.tarantool_enterprise_core.pageFilter.registerFilter(MenuItem => boolean): unsubscribeFn

Register page filter and return unsubscribe function.

window.tarantool_enterprise_core.pageFilter.applyFilters(MenuItemType[]): MenuItemType[]

Filter out pages.

window.tarantool_enterprise_core.pageFilter.filterPage(MenuItemType): boolean

Does page pass filters

window.__tarantool_variables

Object contains values passed from lua part.

Core events

dispatchToken - transmits action to core redux store.

Example:

window.tarantool_enterprise_core.dispatch('dispatchToken', { type: 'ADD_CLUSTER_USERS_MENU_ITEM' });

Emittable core events

setHeaderComponent - Header component was passed to frontend core.

core:pageFilter:apply - Emits on modifying page filters before applying. Provides an array of filtering functions.

core:pageFilter:applied - Emits on modifying page filters after applying. Provides an array of filtering functions.

Rock

Core module and other UI modules bundled as Lua files that contains bundle files.

$ tarantoolctl rocks install frontend-core
$ tarantool -l pack-front - build/bundle.json build/bundle.lua

Command above make a bundle. Then it could be included as module at Tarantool Enterprise Admin.

Bundle usually contains a table

{
  [filename] = {
    is_entry = boolean,
    body = string,
    mime = string
  },
  ...
}
  • is_entry - indicate that's JS enter file of your module.
  • body - content of file.
  • mime - mime-type of file.
  • filename - filename or filepath.

Lua

local http = require('http.server')
local httpd = http.new('0.0.0.0', '5050')
httpd:start()

local front = require('frontend-core')
front.init(httpd, {
	enforce_root_redirect = false,
	prefix = '/tarantool',
})

This start a Tarantool Enterprise Admin without modules.

You can register a module in this way:

local my_module_bundle = require('my_module.bundle')
front.add('module_namespace', my_module_bundle)

init(httpd: httpd, { enforce_root_redirect: boolean, prefix: string })

httpd argument - should be an instance of a Tarantool HTTP server.

enforce_root_redirect - optional key which controls redirection to frontend-core app from '/' path, default true.

prefix - optional, adds path prefix to frontend-core app.

Register routes /admin/ and /static/, and redirect from / to /admin/ in httpd.

/admin/ - route for single page application admin module front-end.

/static/ - route for static files that will be used at application.

add(namespace, bundle, replace)

namespace - using for namespace module. Should be same name as your JS namespace module.

bundle - a frontend bundle loaded as a lua table.

replace - if true, will replace exists module.

remove(namespace)

Removes front-end module.

set_variable(key, value)

Passes value from lua to browser's global object __tarantool_variables.

Example:

front.set_variable('authPath', 'https://sso.example.com')
front.set_variable('threshold', 42)
front.set_variable('editorParams', {smarttabs: true, padding: 2})

Development usage

You can use it in your frontend development mode with our npm package @tarantool.io/frontend-core, but you need use external react and react-dom from our package.

Install development package

npm i -s @tarantool.io/frontend-core

Part of webpack config example

  externals: {
    react: 'react',
    'react-dom': 'reactDom',
    '@tarantool.io/frontend-core': 'tarantool_frontend_core_module',
    '@tarantool.io/ui-kit': 'tarantool_frontend_ui_kit_module',
  },

Bundle to lua

We use plugin to bundle our static as Lua. Check it out here: https://github.com/tarantool/lua-bundler-webpack-plugin

Boilerplate

Stub project: https://github.com/tarantool/frontend-stub

frontend-core's People

Contributors

ashelist avatar dependabot[bot] avatar hustonmmmavr avatar iv-ov avatar klond90 avatar lostiks avatar rgbabaev avatar rosik avatar tupikinm avatar usenko-timur avatar ylobankov avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

frontend-core's Issues

RFC: what cross-module interactions to test

RFC: what to test also:

  • current menu item selection happens based on location (on document load)
  • that menu reacts to clicks
  • items are actually hidden according to page filters
  • submenus collapse/expand correctly
  • filters apply to submenu items
  • ... (add more suggestions)

Enable JS syntax highlight in readme

Example from current readme:

const matchPath = (path, link) => {
  const point = link.indexOf(path)
  return point === 0 && (link.length === path.length || link[path.length] === '/')
}

const selected = path => menuItem => ({
  ...menuItem,
  expanded: menuItem.items.length > 0
  ?( menuItem.path === path ? !menuItem.expanded : menuItem.expanded)
  : false,
})
const updateLink = path => menuItem => ({
    ...menuItem,
    selected: matchPath(path, menuItem.path)
})

Let's add a bit more colors:

const matchPath = (path, link) => {
  const point = link.indexOf(path)
  return point === 0 && (link.length === path.length || link[path.length] === '/')
}

const selected = path => menuItem => ({
  ...menuItem,
  expanded: menuItem.items.length > 0
  ?( menuItem.path === path ? !menuItem.expanded : menuItem.expanded)
  : false,
})
const updateLink = path => menuItem => ({
    ...menuItem,
    selected: matchPath(path, menuItem.path)
})

change ``` to ```js

Refactor filters API and method

Now filters is a part of registration module but it have next problems:

  1. registerModule - too complicated method
  2. It depends on first point. It's single mounted point. If you need add filter you must register module
  3. We don't have convenient method to update filter only. Or remove filters.

So my proposal:
Add disposable (return dispose function) method registerPageFilter with next api registerPageFilter(filter).

Add redux event APPLY_FILTERS that will be affect menu state.

With next dispatchable events:
core:pageFilter:apply
core:pageFilter:applied

On applied we could update black list and other things.

Usage example:

const unsubscribeFilter = core.registerPageFilter(m => m.includes('admin'))
// dispatch events
unsubscribeFilter()
// dispatch another events

Add method to menu reducer isBlacklistedPath

Upgrade module registration method

Problem: tarantool_enterprise_core.register method accepts 6 arguments:

window.tarantool_enterprise_core.register(
  PROJECT_NAME,
  menu, // menu items
  App, // root component
  'react', // engine
  null, // menu middleware
  menuItem => false // menu items filter
);

It's too much to comfortable usage. I propose to accept object instead of argument's sequence and save current behavior till next major update.

Example:

type ModuleOptions = {
  moduleName: string,
  menu?: MenuItem[] | menuReducer,
  rootComponent: ComponentType,
  engine?: string, // 'react' by default
  menuMiddleware: Object => void,
  menuFilter: MenuItem => boolean
};

Add version tag to build

Add version to exported object for checking the frontend-core version.

Example:

console.log(tarantol_enterprise_core.version); // 6.5.1

It will help with debug issues.

Update vulnerabile react-dev-utils 9.0.1 to latest 11.0.4

immer <8.0.1
Severity: high
Prototype Pollution - https://npmjs.com/advisories/1603
No fix available
node_modules/immer
react-dev-utils 6.0.6-next.9b4009d7 - 11.0.2
Depends on vulnerable versions of immer
node_modules/@tarantool.io/frontend-core/node_modules/react-dev-utils
node_modules/react-dev-utils
@tarantool.io/frontend-core *
Depends on vulnerable versions of react-dev-utils
node_modules/@tarantool.io/frontend-core

[2pt] [UI] Configuration files: remove redirect when trying download configuration without bootstrap

Предусловие:
Бутстрап не выполнен

Шаги:

  1. Перейти в Configuration files
  2. Нажать кнопку Download

Ожидаемый результат:
Система выдает ошибку о том, что бутстрап не выполнен

Полученный результат:
Происходит редирект на страницу с ошибкой:
http://joxi.ru/Q2KnKo1ILRQgqm

[1pt] [UI] Code: Empty file after editing and deleting

Шаги:

  1. Перейти на страницу Code,
  2. Создать файл,
  3. Нажать кнопку Apply,
  4. Кликнуть по созданному файлу (для того чтоб открыть редактор кода),
  5. Удалить файл,
  6. Внести изменения в редактор,
  7. Нажать кнопку Apply

Ожидаемый результат:
Система на 6 шаге не даст внести изменения в редактор, так как файл был удален

Полученный результат:
Система выдает сообщение, что все сохранено, но содержимое файла пропадает и файл пустой висит в дереве

fronend-core.lua does not compatible with http.server v2

    httpd:route({
        path = '/admin/*any',
        method = 'GET',
    }, index_handler)

This construction should be carefully tested, because breaks compatibility with http v2

local http_server = require('http.server')
local http_router = require('http.router')

local httpd = http_server.new('0.0.0.0', 9999)
local router = http_router.new()
     :route({ path = '/config/admin', method = 'GET' }, function() print('correct') end)
     :route({ path = '/config/*any', method = 'GET' }, function() print('incorrect') end)
httpd:set_router(router)
httpd:start()

then

tarantool> require('http.client').new():get('localhost:9999/config/admin')
GET /config/admin
incorrect
---
- status: 200
  reason: Ok
  proto:
  - 1
  - 1
  headers:
    content-length: '0'
    server: Tarantool http (tarantool v2.2.1-131-gbc6ad173f)
    connection: close
...

If I change '/config/*any ' to '/config/*a' it still works incorrect

May be it's a problem of new version of http.server, but it blocks upgrade to new http.server in cartridge

[1pt] [UI] Code: Change tooltip for FileTreeElement__btns

Шаги:

  1. Перейти на страницу Code
  2. Добавить файл/папку
  3. Навести курсором на одну из кнопок на панели кнопок

Ожидаемый результат:

Во всплывающей подсказке отображается название кнопки

Полученный результат:

Во всплывающей подсказке отображается название файла/папки:
http://joxi.ru/52a1jDBtEdLle2

Customizable route prefix

Frontend core inconditionally sets up redirect "/" -> "/admin", thus making it impossible to use root for anything else. There should be a way to prefix all routes with an arbitrary string and make the root path free for use.

Linter scripts behave strange

  1. npm run lint produces 1.5MB of output. With no reason, in CI too.
  2. If it meant to be --check then it's missing.
  3. npm run fix which is a combination of prettier --write + eslint --fix change files back and forth:
-  it('clear storage', async () => {
+  it('clear storage', async() => {

Proposal: Add passing params from Lua to JS

If we need parameters from backend that's we should use in frontend. Like a we want to set up a key in Lua. Or some other like redirect endpoint without usage an API.

Example:

Lua:

front.set_variable('name', 'value')

It will be translated to the top of <head>

JS:

window.__TNT_PASSED_VARIABLES__.name = JSON.parse('"value"');

Lua 'value' will be transform by json.encode

[2pt] [UI] Code: Add notification when user tries to save code without bootstrap

Шаги:

  1. В системе не выполнен бутстрап
  2. Перейти на страницу Code
  3. Добавить файл
  4. Заполнить содержимое файла

Ожидаемый результат:

Система не дает редактировать файл, пока не будет выполнен бутстрап

Полученный результат:

Система дает редактировать, но не дает сохранить файл

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.