Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] New Web UI #116

Merged
merged 1 commit into from
Mar 6, 2024
Merged

[Feature] New Web UI #116

merged 1 commit into from
Mar 6, 2024

Conversation

ytwp
Copy link
Member

@ytwp ytwp commented Dec 21, 2023

New Web UI

  • Developed a new Web UI based on Semi UI, Vite, React, TypeScript, and Echarts frameworks.
  • Implemented user interface design and layout using components and styles provided by Semi UI.
  • Utilized Vite build tool for fast project development and building.
  • Created components and handled interactive logic using React framework.
  • Enhanced code safety and maintainability with TypeScript.
  • Integrated Echarts charting library for data visualization.

Screenshot

FireShot Capture 013 - Trino Gateway - localhost

FireShot Capture 006 - Trino Gateway - localhost

FireShot Capture 011 - Trino Gateway - localhost

FireShot Capture 010 - Trino Gateway - localhost

FireShot Capture 009 - Trino Gateway - localhost

FireShot Capture 008 - Trino Gateway - localhost

FireShot Capture 007 - Trino Gateway - localhost

@cla-bot cla-bot bot added the cla-signed label Dec 21, 2023
@mosabua
Copy link
Member

mosabua commented Dec 21, 2023

Wow .. super impressive. Given the large size of the PR we will have to discuss this as a team and figure in detail how we proceed. Couple of initial questions

  • Is this a replacement, an upgrade or an additional UI to the existing UI
  • are all libraries used transitively Apache licensed or in a similar fashion compatible
  • could you change to the new logo?

There is a lot more to discuss in review and in general. Please be patient over the holidays .. maybe we can arrange a meeting in early January for a core group of project maintainers as next step.

@andythsu
Copy link
Member

this is super awesome!!

@ytwp
Copy link
Member Author

ytwp commented Dec 22, 2023

Wow .. super impressive. Given the large size of the PR we will have to discuss this as a team and figure in detail how we proceed. Couple of initial questions

  • Is this a replacement, an upgrade or an additional UI to the existing UI
  • are all libraries used transitively Apache licensed or in a similar fashion compatible
  • could you change to the new logo?

There is a lot more to discuss in review and in general. Please be patient over the holidays .. maybe we can arrange a meeting in early January for a core group of project maintainers as next step.

  1. This is a replacement to the existing UI.

    • But fully compatible with existing UI. Please note that 'privileges' can only be a combination of ADMIN, USER, and API, with '_' used for segmentation.

    • The new UI can be launched either independently or together with the 'gateway-ha', depending on the user's preference.

  2. All libraries used Apache licensed or MIT licensed.

@Chaho12
Copy link
Member

Chaho12 commented Dec 22, 2023

The new UI can be launched either independently or together with the 'gateway-ha', depending on the user's preference.

What do you mean launched independently? you mean that you can launch gateway server withouth web ui?

@ytwp
Copy link
Member Author

ytwp commented Dec 22, 2023

The new UI can be launched either independently or together with the 'gateway-ha', depending on the user's preference.

What do you mean launched independently? you mean that you can launch gateway server withouth web ui?

This is a front-end and back-end separated architecture, where the pages can be deployed separately on servers like nginx. Just forward the front-end requests to '/webapp/', which would be relatively more secure, but I suppose not many people would choose to do so, you can just ignore it.

Alternatively, you can choose to package the front-end static resources into 'gateway' and access them directly through the 'gateway'. This is the default and recommended approach.

@mosabua
Copy link
Member

mosabua commented Jan 9, 2024

Please rebase, squash commits, and let us know when you are ready for us to review/test. So far this is looking very nice!

@ytwp
Copy link
Member Author

ytwp commented Jan 16, 2024

Please rebase, squash commits, and let us know when you are ready for us to review/test. So far this is looking very nice!

I have completed squashing commits. I am ready for review and testing. Thank you for the feedback!

Copy link
Contributor

@willmostly willmostly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are limited to the Java portions of this PR. Looks great, just a few items to address.

{
try {
connectionManager.open();
String sql = "select * from query_history where 1=1";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use JDBI instead of building the queries manually. @ebyhr is in the midst of replacing ActiveJDBC with JDBI in #145, so you will probably need to coordinate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to ignore #145. It's just a maintanance PR which should not bock other PRs.

*/
public class GlobalPropertyRequest
{
private String useSchema;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the useSchema field used for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refer to the concept of "useScheme" in the following link: https://github.com/trinodb/trino-gateway/blob/main/docs/resource-groups-api.md.

gateway-ha/src/main/java/io/trino/gateway/ha/domain/R.java Outdated Show resolved Hide resolved
webapp/.env.production Outdated Show resolved Hide resolved
webapp/src/api/webapp/dashboard.ts Outdated Show resolved Hide resolved
webapp/src/components/layout.module.scss Outdated Show resolved Hide resolved
webapp/src/store/access.ts Outdated Show resolved Hide resolved
LocalCacheClean: "Clear All Cache",
},
Dashboard: {
QPH: "QPH",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QPH = Query Per Hour? Then is it average too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not an average value because the configuration could be "queryHistoryHoursRetention:1", so there is no design to display average QPH.

webapp/src/locales/en_US.ts Outdated Show resolved Hide resolved
webapp/src/locales/zh_CN.ts Outdated Show resolved Hide resolved
@andythsu
Copy link
Member

Just a heads up that there should be a new column to display the health status of the cluster. See #80.

@vishalya
Copy link
Member

Nice contribution!
I was wondering what's the request module do in general and if you could provide a few lines for each of the request/response it will help us understand the implementation better.

@andythsu
Copy link
Member

andythsu commented Jan 24, 2024

image
The format for this text is inconsistent with the rest

The rest of the font has font-family: Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;

But this one is font-family: PingFang SC;

@andythsu
Copy link
Member

andythsu commented Jan 24, 2024

Any idea why I keep getting this error when I run this app locally?

image

I'm using ./mvnw clean install -P webapp to package and ./mvnw test-compile exec:java -pl gateway-ha -Dexec.classpathScope=test -Dexec.mainClass="io.trino.gateway.TrinoGatewayRunner" to run the app

Update: This is the error trace:

ERROR [2024-01-24 19:47:53,306] io.dropwizard.jersey.errors.LoggingExceptionMapper: Error handling a request: 70d25d9eb4f3fd65
! org.postgresql.util.PSQLException: ERROR: syntax error at or near "minute"
!   Position: 36
! at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2713)
! at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2401)
! at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:368)
! at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:498)
! at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:415)
! at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:190)
! at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:134)
! at org.javalite.activejdbc.DB.find(DB.java:535)
! ... 73 common frames omitted
! Causing: org.javalite.activejdbc.DBException: org.postgresql.util.PSQLException: ERROR: syntax error at or near "minute"
!   Position: 36, query: select FLOOR(created / 1000 / 60)  minute,
!        backend_url                 ,
!        count(1)                    query_count
! from query_history
! where created > 1706122073293
! group by FLOOR(created / 1000 / 60), backend_url
! 
! at org.javalite.activejdbc.DB.find(DB.java:538)
! at org.javalite.activejdbc.DB.findAll(DB.java:497)
! at org.javalite.activejdbc.Base.findAll(Base.java:226)
! at io.trino.gateway.ha.router.HaQueryHistoryManager.findDistribution(HaQueryHistoryManager.java:135)
! at io.trino.gateway.ha.resource.GatewayWebAppResource.getDistribution(GatewayWebAppResource.java:134)
! at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
! at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
! at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
! at java.base/java.lang.reflect.Method.invoke(Method.java:568)
! at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
! at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:146)
! at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:189)
! at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176)
! at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:93)
! at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
! at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
! at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
! at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:256)
! at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
! at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
! at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
! at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
! at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
! at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
! at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:235)
! at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
! at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394)
! at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346)
! at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:358)
! at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:311)
! at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205)
! at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:764)
! at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1665)
! at io.dropwizard.servlets.ThreadNameFilter.doFilter(ThreadNameFilter.java:36)
! at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
! at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
! at io.dropwizard.jersey.filter.AllowedMethodsFilter.handle(AllowedMethodsFilter.java:46)
! at io.dropwizard.jersey.filter.AllowedMethodsFilter.doFilter(AllowedMethodsFilter.java:40)
! at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
! at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
! at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:527)
! at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
! at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1381)
! at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
! at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484)
! at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
! at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1303)
! at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
! at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
! at io.dropwizard.metrics.jetty11.InstrumentedHandler.handle(InstrumentedHandler.java:310)
! at io.dropwizard.jetty.RoutingHandler.handle(RoutingHandler.java:52)
! at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
! at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:822)
! at io.dropwizard.jetty.ZipExceptionHandlingGzipHandler.handle(ZipExceptionHandlingGzipHandler.java:26)
! at org.eclipse.jetty.server.handler.RequestLogHandler.handle(RequestLogHandler.java:46)
! at org.eclipse.jetty.server.handler.StatisticsHandler.handle(StatisticsHandler.java:173)
! at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
! at org.eclipse.jetty.server.Server.handle(Server.java:563)
! at org.eclipse.jetty.server.HttpChannel$RequestDispatchable.dispatch(HttpChannel.java:1598)
! at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:753)
! at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:501)
! at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
! at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
! at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
! at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
! at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:421)
! at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:390)
! at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:277)
! at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:199)
! at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
! at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
! at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
! at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
! at java.base/java.lang.Thread.run(Thread.java:840)

@andythsu
Copy link
Member

andythsu commented Jan 24, 2024

Wow .. super impressive. Given the large size of the PR we will have to discuss this as a team and figure in detail how we proceed. Couple of initial questions

  • Is this a replacement, an upgrade or an additional UI to the existing UI
  • are all libraries used transitively Apache licensed or in a similar fashion compatible
  • could you change to the new logo?

There is a lot more to discuss in review and in general. Please be patient over the holidays .. maybe we can arrange a meeting in early January for a core group of project maintainers as next step.

  1. This is a replacement to the existing UI.

    • But fully compatible with existing UI. Please note that 'privileges' can only be a combination of ADMIN, USER, and API, with '_' used for segmentation.
    • The new UI can be launched either independently or together with the 'gateway-ha', depending on the user's preference.
  2. All libraries used Apache licensed or MIT licensed.

Please fix security.md doc to include Please note that 'privileges' can only be a combination of ADMIN, USER, and API, with '_' used for segmentation.

@@ -45,6 +45,7 @@ authentication:
authorizationEndpoint:
jwkEndpoint:
redirectUrl:
redirectWebUrl:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where/how is this property used?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the web UI is started independently, you can specify the redirect path.

By default, this configuration does not need to be specified.

webapp/src/components/login.module.scss Outdated Show resolved Hide resolved
webapp/src/components/cluster.tsx Outdated Show resolved Hide resolved
children: React.ReactNode;
}

export class ErrorBoundary extends React.Component<IErrorBoundaryProps, IErrorBoundaryState> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should change this to functional component to stay consistent with the rest of the components

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because function components don't provide 'componentDidCatch', so here I am using class components directly without introducing any third-party libraries to implement it.

webapp/src/components/history.module.scss Outdated Show resolved Hide resolved
webapp/src/components/resource-group.module.scss Outdated Show resolved Hide resolved
webapp/src/router.tsx Outdated Show resolved Hide resolved
webapp/src/locales/index.ts Outdated Show resolved Hide resolved
webapp/src/locales/index.ts Outdated Show resolved Hide resolved
Copy link
Member

@Chaho12 Chaho12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For those who are testing, please note that

  • UI requires AUTHORIZATION, so docs/quickstart-config.yaml doesnt' work properly unless presetUsers and authentication is set
    • Can this be optional?

I've got some suggestions for improvements in UI

  • In login screen, can you fix so that enter works to login? kinda bothersome to click on sign in button everytime
    image

  • In history tab, how about adding <all> or sth placeholder for RoutedTo and QueryId which means all (just for the ui part like if none is selected, it means all in history)
    image

  • add hyperlink at Backends to link cluster in dashboard for convenience?
    image

  • add a button next to QPH/Avg. QPM/Avg. QPS to explain how each value is calculated?
    image

@vishalya
Copy link
Member

+1 for making the authentication making optional.

@ytwp
Copy link
Member Author

ytwp commented Feb 14, 2024

For those who are testing, please note that

  • UI requires AUTHORIZATION, so docs/quickstart-config.yaml doesnt' work properly unless presetUsers and authentication is set

    • Can this be optional?

I've got some suggestions for improvements in UI

  • In login screen, can you fix so that enter works to login? kinda bothersome to click on sign in button everytime
    image
  • In history tab, how about adding <all> or sth placeholder for RoutedTo and QueryId which means all (just for the ui part like if none is selected, it means all in history)
    image
  • add hyperlink at Backends to link cluster in dashboard for convenience?
    image
  • add a button next to QPH/Avg. QPM/Avg. QPS to explain how each value is calculated?
    image

👍 Great suggestion, currently under adjustment.

@ytwp
Copy link
Member Author

ytwp commented Feb 14, 2024

Hi, @Chaho12 @vishalya

If authentication is made optional, what permissions should be granted?
My suggestion is to add a default administrator account in the default configuration file.

@mosabua
Copy link
Member

mosabua commented Feb 14, 2024

How about using the same approach as in the Trino UI .. username is required but can be random and you get full access.

@mosabua
Copy link
Member

mosabua commented Feb 21, 2024

Fyi @ytwp ... in our dev sync earlier today we discussed the PR. We decided that we want to go ahead towards merge. A next step would be for you to rebase so its ready and then we can review. We will just get it to a good enough state for merge and plan on iterating on it as necessary together as well. The one thing we need to make sure is that we remove the requirement for authentication similar to how it is optional in Trino.

Ping us on slack if you want to chat or let us know here.

@ytwp ytwp force-pushed the webapp2 branch 2 times, most recently from 80a9ed4 to 55cea9e Compare February 26, 2024 02:21
Copy link
Member

@Chaho12 Chaho12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@ebyhr
Copy link
Member

ebyhr commented Feb 27, 2024

[Feature] New Web UI

Please update the commit title as "Modernize Web UI" or something. Also, please add the commit body to describe the changes.
https://github.com/trinodb/trino/blob/master/.github/DEVELOPMENT.md#format-git-commit-messages

Developed a brand-new Web UI based on Semi UI, Vite, React, TypeScript, and E-charts framework to replace the old one.
Copy link
Member

@mosabua mosabua left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran a bunch of tests with this and looked over the whole codebase changes. This is a very significant improvement from the legacy UI.

It creates a base for further improvements that we can work on in follow up PRs where necessary, but the current state is already good for merge.

We will do further testing after merge, also with other features merged, and in the build up to the next release.

@mosabua
Copy link
Member

mosabua commented Mar 6, 2024

I will update the commit message as part of the merge.

@mosabua mosabua merged commit ae39d96 into trinodb:main Mar 6, 2024
2 checks passed
@github-actions github-actions bot added this to the 7 milestone Mar 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

7 participants