Skip to content

Commit

Permalink
UI layout containers + navigation (#15007)
Browse files Browse the repository at this point in the history
* Adds layout containers to render navigation
- Adds additional placeholder routes/views

* Remove "containers" directory, move files in context w/in views

* Add app container to Login view

* Convert static list to use data array

* Move/separate Icons and SVGs into separate components

* Remove unintention file addition

* Update test contex

* Make the env var more universal
  • Loading branch information
ryanahamilton authored Mar 25, 2021
1 parent f94cf99 commit 645e772
Show file tree
Hide file tree
Showing 39 changed files with 1,456 additions and 119 deletions.
2 changes: 1 addition & 1 deletion airflow/ui/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
API_URL = 'http://127.0.0.1:28080/api/v1/'
WEBSERVER_URL = 'http://127.0.0.1:28080'
2 changes: 1 addition & 1 deletion airflow/ui/.neutrinorc.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module.exports = {
}),
react({
env: [
'API_URL'
'WEBSERVER_URL'
],
html: {
title: 'Apache Airflow',
Expand Down
4 changes: 2 additions & 2 deletions airflow/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ Be sure to allow CORS headers and set up an auth backend on your Airflow instanc
export AIRFLOW__API__AUTH_BACKEND=airflow.api.auth.backend.basic_auth
export AIRFLOW__API__ACCESS_CONTROL_ALLOW_HEADERS=*
export AIRFLOW__API__ACCESS_CONTROL_ALLOW_METHODS=*
export AIRFLOW__API__ACCESS_CONTROL_ALLOW_ORIGIN=http://127.0.0.1:28080/
export AIRFLOW__API__ACCESS_CONTROL_ALLOW_ORIGIN=http://127.0.0.1:28080
```

Create your local environment and adjust the `API_URL` if needed.
Create your local environment and adjust the `WEBSERVER_URL` if needed.

```bash
cp .env.example .env
Expand Down
1 change: 1 addition & 0 deletions airflow/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@emotion/styled": "^11.1.5",
"@neutrinojs/copy": "^9.5.0",
"axios": "^0.21.1",
"dayjs": "^1.10.4",
"dotenv": "^8.2.0",
"framer-motion": "^3.10.0",
"react": "^16",
Expand Down
20 changes: 20 additions & 0 deletions airflow/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,22 @@ import Pipelines from 'views/Pipelines';
import Pipeline from 'views/Pipeline';

import EventLogs from 'views/Activity/EventLogs';
import Runs from 'views/Activity/Runs';
import Jobs from 'views/Activity/Jobs';
import TaskInstances from 'views/Activity/TaskInstances';
import TaskReschedules from 'views/Activity/TaskReschedules';
import SLAMisses from 'views/Activity/SlaMisses';
import XComs from 'views/Activity/XComs';

import Config from 'views/Config';
import Variables from 'views/Config/Variables';
import Connections from 'views/Config/Connections';
import Pools from 'views/Config/Pools';

import Access from 'views/Access';
import Users from 'views/Access/Users';
import Roles from 'views/Access/Roles';
import Permissions from 'views/Access/Permissions';

import Docs from 'views/Docs';
import NotFound from 'views/NotFound';
Expand All @@ -44,15 +54,25 @@ const App = () => (
<PrivateRoute exact path="/pipelines/:dagId" component={Pipeline} />

<PrivateRoute exact path="/activity/event-logs" component={EventLogs} />
<PrivateRoute exact path="/activity/runs" component={Runs} />
<PrivateRoute exact path="/activity/jobs" component={Jobs} />
<PrivateRoute exact path="/activity/task-instances" component={TaskInstances} />
<PrivateRoute exact path="/activity/task-reschedules" component={TaskReschedules} />
<PrivateRoute exact path="/activity/sla-misses" component={SLAMisses} />
<PrivateRoute exact path="/activity/xcoms" component={XComs} />

<PrivateRoute exact path="/config" component={Config} />
<PrivateRoute exact path="/config/variables" component={Variables} />
<PrivateRoute exact path="/config/connections" component={Connections} />
<PrivateRoute exact path="/config/pools" component={Pools} />

<PrivateRoute exact path="/access" component={Access} />
<PrivateRoute exact path="/access/users" component={Users} />
<PrivateRoute exact path="/access/users/new" component={Users} />
<PrivateRoute exact path="/access/users/:username" component={Users} />
<PrivateRoute exact path="/access/users/:username/edit" component={Users} />
<PrivateRoute exact path="/access/roles" component={Roles} />
<PrivateRoute exact path="/access/permissions" component={Permissions} />

<Route exact path="/docs" component={Docs} />

Expand Down
2 changes: 1 addition & 1 deletion airflow/ui/src/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const AuthProvider = ({ children }: Props): ReactElement => {
setError(null);
try {
const authorization = `Basic ${btoa(`${username}:${password}`)}`;
await axios.get(`${process.env.API_URL}config`, {
await axios.get(`${process.env.WEBSERVER_URL}/api/v1/config`, {
headers: {
Authorization: authorization,
},
Expand Down
129 changes: 129 additions & 0 deletions airflow/ui/src/components/AppContainer/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React from 'react';
import { Link } from 'react-router-dom';
import dayjs from 'dayjs';
import {
Avatar,
Box,
Button,
Flex,
Icon,
Menu,
MenuButton,
MenuDivider,
MenuList,
MenuItem,
useColorMode,
useColorModeValue,
Tooltip,
} from '@chakra-ui/react';
import {
MdWbSunny,
MdBrightness2,
MdAccountCircle,
MdExitToApp,
} from 'react-icons/md';

import { useAuthContext } from 'auth/context';

import ApacheAirflowLogo from 'components/icons/ApacheAirflowLogo';

interface Props {
bodyBg: string;
overlayBg: string;
breadcrumb?: React.ReactNode;
}

const AppHeader: React.FC<Props> = ({ bodyBg, overlayBg, breadcrumb }) => {
const { toggleColorMode } = useColorMode();
const now = dayjs();
const headerHeight = '56px';
const { hasValidAuthToken, logout } = useAuthContext();

const handleOpenTZ = () => window.alert('This will open time zone select modal!');

const handleOpenProfile = () => window.alert('This will take you to your user profile view.');

return (
<Flex
as="header"
role="banner"
position="fixed"
width={`calc(100vw - ${headerHeight})`}
height={headerHeight}
zIndex={2}
align="center"
justifyContent="space-between"
py="2"
px="4"
backgroundColor={overlayBg}
borderBottomWidth="1px"
borderBottomColor={bodyBg}
>
{breadcrumb}
{!breadcrumb && (
<Link to="/" aria-label="Back to home">
<ApacheAirflowLogo />
</Link>
)}
{hasValidAuthToken && (
<Flex align="center">
<Tooltip label="Change time zone" hasArrow>
{/* TODO: open modal for time zone update */}
<Button variant="ghost" mr="4" onClick={handleOpenTZ}>
<Box
as="time"
dateTime={now.toString()}
fontSize="md"
>
{now.format('h:mmA Z')}
</Box>
</Button>
</Tooltip>
<Menu>
<MenuButton>
<Avatar name="Ryan Hamilton" size="sm" color="blue.900" bg="blue.200" />
</MenuButton>
<MenuList placement="top-end">
<MenuItem onClick={handleOpenProfile}>
<Icon as={MdAccountCircle} mr="2" />
Your Profile
</MenuItem>
<MenuItem onClick={toggleColorMode}>
<Icon as={useColorModeValue(MdBrightness2, MdWbSunny)} mr="2" />
Set
{useColorModeValue(' Dark ', ' Light ')}
Mode
</MenuItem>
<MenuDivider />
<MenuItem onClick={logout}>
<Icon as={MdExitToApp} mr="2" />
Logout
</MenuItem>
</MenuList>
</Menu>
</Flex>
)}
</Flex>
);
};

export default AppHeader;
111 changes: 111 additions & 0 deletions airflow/ui/src/components/AppContainer/AppNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React from 'react';
import { Link } from 'react-router-dom';
import { Box } from '@chakra-ui/react';
import {
FiActivity,
FiBookOpen,
FiSettings,
FiUsers,
} from 'react-icons/fi';

import { useAuthContext } from 'auth/context';

import PinwheelLogo from 'components/icons/PinwheelLogo';
import PipelineIcon from 'components/icons/PipelineIcon';

import AppNavBtn from './AppNavBtn';

interface Props {
bodyBg: string;
overlayBg: string;
}

const AppNav: React.FC<Props> = ({ bodyBg, overlayBg }) => {
const { hasValidAuthToken } = useAuthContext();

const navItems = [
{
label: 'Pipelines',
icon: PipelineIcon,
path: '/pipelines',
activePath: '/pipelines',
},
{
label: 'Activity',
icon: FiActivity,
path: '/activity/event-logs',
activePath: '/activity',
},
{
label: 'Config',
icon: FiSettings,
path: '/config',
activePath: '/config',
},
{
label: 'access',
icon: FiUsers,
path: '/access',
activePath: '/access',
},
{
label: 'Docs',
icon: FiBookOpen,
path: '/docs',
activePath: '/docs',
},
];

return (
<Box
as="nav"
role="navigation"
width="56px"
backgroundColor={overlayBg}
borderRightWidth="1px"
borderRightColor={bodyBg}
display="flex"
flexDirection="column"
>
<Box
as={Link}
to="/"
aria-label="Back to home"
width="56px"
height="56px"
display="flex"
alignItems="center"
justifyContent="center"
_hover={{
transformOrigin: '28px 28px',
}}
>
<PinwheelLogo />
</Box>
{hasValidAuthToken && navItems.map((item) => (
<AppNavBtn key={item.label} navItem={item} />
))}
</Box>
);
};

export default AppNav;
Loading

0 comments on commit 645e772

Please sign in to comment.