mirror of
https://github.com/getredash/redash.git
synced 2025-12-20 01:47:39 -05:00
Compare commits
9 Commits
query-base
...
ts-migrate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc50415687 | ||
|
|
8817113f4a | ||
|
|
6ce2896b0b | ||
|
|
fdf636a393 | ||
|
|
88c13868a3 | ||
|
|
aab11dc79b | ||
|
|
00c77cf36e | ||
|
|
6e2631dec2 | ||
|
|
4b88959341 |
@@ -1,10 +1,10 @@
|
||||
import { first } from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import Button from "antd/lib/button";
|
||||
import React, { useMemo } from "react";
|
||||
import { first, includes } from "lodash";
|
||||
import Menu from "antd/lib/menu";
|
||||
import Link from "@/components/Link";
|
||||
import HelpTrigger from "@/components/HelpTrigger";
|
||||
import CreateDashboardDialog from "@/components/dashboards/CreateDashboardDialog";
|
||||
import { useCurrentRoute } from "@/components/ApplicationArea/Router";
|
||||
import { Auth, currentUser } from "@/services/auth";
|
||||
import settingsMenu from "@/services/settingsMenu";
|
||||
import logoUrl from "@/assets/images/redash_icon_small.png";
|
||||
@@ -15,37 +15,58 @@ import AlertOutlinedIcon from "@ant-design/icons/AlertOutlined";
|
||||
import PlusOutlinedIcon from "@ant-design/icons/PlusOutlined";
|
||||
import QuestionCircleOutlinedIcon from "@ant-design/icons/QuestionCircleOutlined";
|
||||
import SettingOutlinedIcon from "@ant-design/icons/SettingOutlined";
|
||||
import MenuUnfoldOutlinedIcon from "@ant-design/icons/MenuUnfoldOutlined";
|
||||
import MenuFoldOutlinedIcon from "@ant-design/icons/MenuFoldOutlined";
|
||||
|
||||
import VersionInfo from "./VersionInfo";
|
||||
import "./DesktopNavbar.less";
|
||||
|
||||
function NavbarSection({ inlineCollapsed, children, ...props }) {
|
||||
function NavbarSection({ children, ...props }) {
|
||||
return (
|
||||
<Menu
|
||||
selectable={false}
|
||||
mode={inlineCollapsed ? "inline" : "vertical"}
|
||||
inlineCollapsed={inlineCollapsed}
|
||||
theme="dark"
|
||||
{...props}>
|
||||
<Menu selectable={false} mode="vertical" theme="dark" {...props}>
|
||||
{children}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DesktopNavbar() {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
function useNavbarActiveState() {
|
||||
const currentRoute = useCurrentRoute();
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
dashboards: includes(
|
||||
["Dashboards.List", "Dashboards.Favorites", "Dashboards.ViewOrEdit", "Dashboards.LegacyViewOrEdit"],
|
||||
currentRoute.id
|
||||
),
|
||||
queries: includes(
|
||||
[
|
||||
"Queries.List",
|
||||
"Queries.Favorites",
|
||||
"Queries.Archived",
|
||||
"Queries.My",
|
||||
"Queries.View",
|
||||
"Queries.New",
|
||||
"Queries.Edit",
|
||||
],
|
||||
currentRoute.id
|
||||
),
|
||||
dataSources: includes(["DataSources.List"], currentRoute.id),
|
||||
alerts: includes(["Alerts.List", "Alerts.New", "Alerts.View", "Alerts.Edit"], currentRoute.id),
|
||||
}),
|
||||
[currentRoute.id]
|
||||
);
|
||||
}
|
||||
|
||||
export default function DesktopNavbar() {
|
||||
const firstSettingsTab = first(settingsMenu.getAvailableItems());
|
||||
|
||||
const activeState = useNavbarActiveState();
|
||||
|
||||
const canCreateQuery = currentUser.hasPermission("create_query");
|
||||
const canCreateDashboard = currentUser.hasPermission("create_dashboard");
|
||||
const canCreateAlert = currentUser.hasPermission("list_alerts");
|
||||
|
||||
return (
|
||||
<div className="desktop-navbar">
|
||||
<NavbarSection inlineCollapsed={collapsed} className="desktop-navbar-logo">
|
||||
<NavbarSection className="desktop-navbar-logo">
|
||||
<div>
|
||||
<Link href="./">
|
||||
<img src={logoUrl} alt="Redash" />
|
||||
@@ -53,45 +74,43 @@ export default function DesktopNavbar() {
|
||||
</div>
|
||||
</NavbarSection>
|
||||
|
||||
<NavbarSection inlineCollapsed={collapsed}>
|
||||
<NavbarSection>
|
||||
{currentUser.hasPermission("list_dashboards") && (
|
||||
<Menu.Item key="dashboards">
|
||||
<Menu.Item key="dashboards" className={activeState.dashboards ? "navbar-active-item" : null}>
|
||||
<Link href="dashboards">
|
||||
<DesktopOutlinedIcon />
|
||||
<span>Dashboards</span>
|
||||
<span className="desktop-navbar-label">Dashboards</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{currentUser.hasPermission("view_query") && (
|
||||
<Menu.Item key="queries">
|
||||
<Menu.Item key="queries" className={activeState.queries ? "navbar-active-item" : null}>
|
||||
<Link href="queries">
|
||||
<CodeOutlinedIcon />
|
||||
<span>Queries</span>
|
||||
<span className="desktop-navbar-label">Queries</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{currentUser.hasPermission("list_alerts") && (
|
||||
<Menu.Item key="alerts">
|
||||
<Menu.Item key="alerts" className={activeState.alerts ? "navbar-active-item" : null}>
|
||||
<Link href="alerts">
|
||||
<AlertOutlinedIcon />
|
||||
<span>Alerts</span>
|
||||
<span className="desktop-navbar-label">Alerts</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
</NavbarSection>
|
||||
|
||||
<NavbarSection inlineCollapsed={collapsed} className="desktop-navbar-spacer">
|
||||
{(canCreateQuery || canCreateDashboard || canCreateAlert) && <Menu.Divider />}
|
||||
<NavbarSection className="desktop-navbar-spacer">
|
||||
{(canCreateQuery || canCreateDashboard || canCreateAlert) && (
|
||||
<Menu.SubMenu
|
||||
key="create"
|
||||
popupClassName="desktop-navbar-submenu"
|
||||
data-test="CreateButton"
|
||||
title={
|
||||
<React.Fragment>
|
||||
<span data-test="CreateButton">
|
||||
<PlusOutlinedIcon />
|
||||
<span>Create</span>
|
||||
</span>
|
||||
<span className="desktop-navbar-label">Create</span>
|
||||
</React.Fragment>
|
||||
}>
|
||||
{canCreateQuery && (
|
||||
@@ -119,32 +138,30 @@ export default function DesktopNavbar() {
|
||||
)}
|
||||
</NavbarSection>
|
||||
|
||||
<NavbarSection inlineCollapsed={collapsed}>
|
||||
<NavbarSection>
|
||||
<Menu.Item key="help">
|
||||
<HelpTrigger showTooltip={false} type="HOME">
|
||||
<QuestionCircleOutlinedIcon />
|
||||
<span>Help</span>
|
||||
<span className="desktop-navbar-label">Help</span>
|
||||
</HelpTrigger>
|
||||
</Menu.Item>
|
||||
{firstSettingsTab && (
|
||||
<Menu.Item key="settings">
|
||||
<Menu.Item key="settings" className={activeState.dataSources ? "navbar-active-item" : null}>
|
||||
<Link href={firstSettingsTab.path} data-test="SettingsLink">
|
||||
<SettingOutlinedIcon />
|
||||
<span>Settings</span>
|
||||
<span className="desktop-navbar-label">Settings</span>
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
</NavbarSection>
|
||||
|
||||
<NavbarSection inlineCollapsed={collapsed} className="desktop-navbar-profile-menu">
|
||||
<NavbarSection className="desktop-navbar-profile-menu">
|
||||
<Menu.SubMenu
|
||||
key="profile"
|
||||
popupClassName="desktop-navbar-submenu"
|
||||
title={
|
||||
<span data-test="ProfileDropdown" className="desktop-navbar-profile-menu-title">
|
||||
<img className="profile__image_thumb" src={currentUser.profile_image_url} alt={currentUser.name} />
|
||||
<span>{currentUser.name}</span>
|
||||
</span>
|
||||
}>
|
||||
<Menu.Item key="profile">
|
||||
@@ -167,10 +184,6 @@ export default function DesktopNavbar() {
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
</NavbarSection>
|
||||
|
||||
<Button onClick={() => setCollapsed(!collapsed)} className="desktop-navbar-collapse-button">
|
||||
{collapsed ? <MenuUnfoldOutlinedIcon /> : <MenuFoldOutlinedIcon />}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
@backgroundColor: #001529;
|
||||
@dividerColor: rgba(255, 255, 255, 0.5);
|
||||
@textColor: rgba(255, 255, 255, 0.75);
|
||||
@brandColor: #ff7964; // Redash logo color
|
||||
@activeItemColor: @brandColor;
|
||||
@iconSize: 26px;
|
||||
|
||||
.desktop-navbar {
|
||||
background: @backgroundColor;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 80px;
|
||||
overflow: hidden;
|
||||
|
||||
&-spacer {
|
||||
flex: 1 1 auto;
|
||||
@@ -21,12 +26,6 @@
|
||||
height: 40px;
|
||||
transition: all 270ms;
|
||||
}
|
||||
|
||||
&.ant-menu-inline-collapsed {
|
||||
img {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-trigger {
|
||||
@@ -34,26 +33,19 @@
|
||||
}
|
||||
|
||||
.ant-menu {
|
||||
&:not(.ant-menu-inline-collapsed) {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
&.ant-menu-inline-collapsed > .ant-menu-submenu-title span img + span,
|
||||
&.ant-menu-inline-collapsed > .ant-menu-item i + span {
|
||||
display: inline-block;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ant-menu-item-divider {
|
||||
background: @dividerColor;
|
||||
}
|
||||
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu {
|
||||
font-weight: 500;
|
||||
color: @textColor;
|
||||
|
||||
&.navbar-active-item {
|
||||
box-shadow: inset 3px 0 0 @activeItemColor;
|
||||
|
||||
.anticon {
|
||||
color: @activeItemColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-menu-submenu-open,
|
||||
&.ant-menu-submenu-active,
|
||||
&:hover,
|
||||
@@ -61,6 +53,16 @@
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
font-size: @iconSize;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.desktop-navbar-label {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a,
|
||||
span,
|
||||
.anticon {
|
||||
@@ -71,21 +73,33 @@
|
||||
.ant-menu-submenu-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-menu-item,
|
||||
.ant-menu-submenu {
|
||||
padding: 0;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ant-btn.desktop-navbar-collapse-button {
|
||||
background-color: @backgroundColor;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
color: @textColor;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: #fff;
|
||||
.ant-menu-submenu-title {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:after {
|
||||
animation: 0s !important;
|
||||
a,
|
||||
&.ant-menu-vertical > .ant-menu-submenu > .ant-menu-submenu-title,
|
||||
.ant-menu-submenu-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: normal;
|
||||
height: auto;
|
||||
background: none;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,37 +113,8 @@
|
||||
.profile__image_thumb {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.profile__image_thumb + span {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
margin-left: 10px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
||||
// styles from Antd
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
margin-left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-menu-inline-collapsed {
|
||||
.ant-menu-submenu-title {
|
||||
padding-left: 16px !important;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
|
||||
.desktop-navbar-profile-menu-title {
|
||||
.profile__image_thumb + span {
|
||||
opacity: 0;
|
||||
max-width: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
width: @iconSize;
|
||||
height: @iconSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function MenuButton({ doDelete, canEdit, mute, unmute, muted }) {
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<a onClick={confirmDelete}>Delete Alert</a>
|
||||
<a onClick={confirmDelete}>Delete</a>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}>
|
||||
|
||||
@@ -29,7 +29,7 @@ export function QuerySourceDropdown(props) {
|
||||
|
||||
QuerySourceDropdown.propTypes = {
|
||||
dataSources: PropTypes.any,
|
||||
value: PropTypes.string,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
disabled: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
|
||||
@@ -15,7 +15,7 @@ export function QuerySourceDropdownItem({ dataSource, children }) {
|
||||
QuerySourceDropdownItem.propTypes = {
|
||||
dataSource: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
type: PropTypes.string,
|
||||
}).isRequired,
|
||||
children: PropTypes.element,
|
||||
|
||||
3365
package-lock.json
generated
3365
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -67,9 +67,9 @@
|
||||
"path-to-regexp": "^3.1.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"query-string": "^6.9.0",
|
||||
"react": "^16.13.1",
|
||||
"react": "^16.14.0",
|
||||
"react-ace": "^9.1.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-grid-layout": "^0.18.2",
|
||||
"react-resizable": "^1.10.1",
|
||||
"react-virtualized": "^9.21.2",
|
||||
@@ -89,6 +89,7 @@
|
||||
"@cypress/code-coverage": "^3.8.1",
|
||||
"@percy/agent": "0.24.3",
|
||||
"@percy/cypress": "^2.3.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/lodash": "^4.14.157",
|
||||
@@ -137,16 +138,19 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^1.19.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-refresh": "^0.9.0",
|
||||
"react-test-renderer": "^16.5.2",
|
||||
"request": "^2.88.0",
|
||||
"request-cookies": "^1.1.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-migrate": "^0.1.10",
|
||||
"typescript": "^3.9.6",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-build-notifier": "^0.1.30",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.1.9",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-manifest-plugin": "^2.0.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -9,7 +9,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const LessPluginAutoPrefix = require("less-plugin-autoprefix");
|
||||
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
|
||||
.BundleAnalyzerPlugin;
|
||||
const fs = require("fs");
|
||||
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
|
||||
|
||||
const path = require("path");
|
||||
|
||||
@@ -30,6 +30,9 @@ function optionalRequire(module, defaultReturn = undefined) {
|
||||
const CONFIG = optionalRequire("./scripts/config", {});
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const isDevelopment = !isProduction;
|
||||
const isHotReloadingEnabled =
|
||||
isDevelopment && process.env.HOT_RELOAD === "true";
|
||||
|
||||
const redashBackend = process.env.REDASH_BACKEND || "http://localhost:5000";
|
||||
const baseHref = CONFIG.baseHref || "/";
|
||||
@@ -45,7 +48,8 @@ const extensionPath = path.join(__dirname, extensionsRelativePath);
|
||||
|
||||
// Function to apply configuration overrides (see scripts/README)
|
||||
function maybeApplyOverrides(config) {
|
||||
const overridesLocation = process.env.REDASH_WEBPACK_OVERRIDES || "./scripts/webpack/overrides";
|
||||
const overridesLocation =
|
||||
process.env.REDASH_WEBPACK_OVERRIDES || "./scripts/webpack/overrides";
|
||||
const applyOverrides = optionalRequire(overridesLocation);
|
||||
if (!applyOverrides) {
|
||||
return config;
|
||||
@@ -97,6 +101,7 @@ const config = {
|
||||
filename: "multi_org.html",
|
||||
excludeChunks: ["server"]
|
||||
}),
|
||||
isProduction &&
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "[name].[chunkhash].css"
|
||||
}),
|
||||
@@ -110,8 +115,9 @@ const config = {
|
||||
{ from: "client/app/unsupportedRedirect.js" },
|
||||
{ from: "client/app/assets/css/*.css", to: "styles/", flatten: true },
|
||||
{ from: "client/app/assets/fonts", to: "fonts/" }
|
||||
])
|
||||
],
|
||||
]),
|
||||
isHotReloadingEnabled && new ReactRefreshWebpackPlugin({ overlay: false })
|
||||
].filter(Boolean),
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: chunk => {
|
||||
@@ -124,7 +130,17 @@ const config = {
|
||||
{
|
||||
test: /\.(t|j)sx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: ["babel-loader", "eslint-loader"]
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
plugins: [
|
||||
isHotReloadingEnabled && require.resolve("react-refresh/babel")
|
||||
].filter(Boolean)
|
||||
}
|
||||
},
|
||||
require.resolve("eslint-loader")
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
@@ -139,7 +155,7 @@ const config = {
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader
|
||||
loader: isProduction ? MiniCssExtractPlugin.loader : "style-loader"
|
||||
},
|
||||
{
|
||||
loader: "css-loader",
|
||||
@@ -153,12 +169,12 @@ const config = {
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader
|
||||
loader: isProduction ? MiniCssExtractPlugin.loader : "style-loader"
|
||||
},
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
minimize: process.env.NODE_ENV === "production"
|
||||
minimize: isProduction
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -258,7 +274,8 @@ const config = {
|
||||
stats: {
|
||||
modules: false,
|
||||
chunkModules: false
|
||||
}
|
||||
},
|
||||
hot: isHotReloadingEnabled
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
|
||||
Reference in New Issue
Block a user