diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index de2c7316adaaf..26070b7c2b510 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -563,18 +563,6 @@ repos:
language_version: python3
additional_dependencies: ['flynt']
files: \.py$
- - id: ui-lint
- name: Lint React UI
- language: node
- entry: bash
- args: ["-c", "cd airflow/ui && yarn install && yarn lint"]
- files: ^airflow/ui/.*\.(ts|tsx)$
- - id: ui-test
- name: Test React UI
- language: node
- entry: bash
- args: ["-c", "cd airflow/ui && yarn install && yarn test"]
- files: ^airflow/ui/.*\.(ts|tsx)$
## ADD MOST PRE-COMMITS ABOVE THAT LINE
# The below pre-commits are those requiring CI image to be built
- id: build
diff --git a/BREEZE.rst b/BREEZE.rst
index 315c85b8b79ba..55cc46a26e222 100644
--- a/BREEZE.rst
+++ b/BREEZE.rst
@@ -2298,8 +2298,8 @@ This is the current syntax for `./breeze <./breeze>`_:
pre-commit-hook-names provide-create-sessions providers-init-file provider-yamls
pydevd pydocstyle pylint pylint-tests python-no-log-warn pyupgrade
restrict-start_date rst-backticks setup-order setup-extra-packages shellcheck
- sort-in-the-wild sort-spelling-wordlist stylelint trailing-whitespace ui-lint
- ui-test update-breeze-file update-extras update-local-yml-file update-setup-cfg-file
+ sort-in-the-wild sort-spelling-wordlist stylelint trailing-whitespace
+ update-breeze-file update-extras update-local-yml-file update-setup-cfg-file
version-sync yamllint
You can pass extra arguments including options to the pre-commit framework as
diff --git a/STATIC_CODE_CHECKS.rst b/STATIC_CODE_CHECKS.rst
index 5b14a8597a40d..69bc52a05e74c 100644
--- a/STATIC_CODE_CHECKS.rst
+++ b/STATIC_CODE_CHECKS.rst
@@ -170,10 +170,6 @@ require Breeze Docker images to be installed locally:
----------------------------------- ---------------------------------------------------------------- ------------
``trailing-whitespace`` Removes trailing whitespace at end of line
----------------------------------- ---------------------------------------------------------------- ------------
-``ui-lint`` Lint the React UI
------------------------------------ ---------------------------------------------------------------- ------------
-``ui-test`` Test the React UI
------------------------------------ ---------------------------------------------------------------- ------------
``update-breeze-file`` Update output of breeze command in BREEZE.rst
----------------------------------- ---------------------------------------------------------------- ------------
``update-extras`` Updates extras in the documentation
diff --git a/airflow/providers/microsoft/azure/log/wasb_task_handler.py b/airflow/providers/microsoft/azure/log/wasb_task_handler.py
index ade1da238388c..2416322826dc6 100644
--- a/airflow/providers/microsoft/azure/log/wasb_task_handler.py
+++ b/airflow/providers/microsoft/azure/log/wasb_task_handler.py
@@ -51,6 +51,7 @@ def __init__(
self.remote_base = wasb_log_folder
self.log_relative_path = ''
self._hook = None
+ self.encoding = "UTF-8"
self.closed = False
self.upload_on_close = True
self.delete_local_copy = delete_local_copy
@@ -159,7 +160,7 @@ def wasb_read(self, remote_log_location: str, return_error: bool = False):
:type return_error: bool
"""
try:
- return self.hook.read_file(self.wasb_container, remote_log_location)
+ return self.hook.read_file(self.wasb_container, remote_log_location).decode(self.encoding)
except AzureHttpError as e:
msg = f'Could not read logs from {remote_log_location}'
self.log.exception("Message: '%s', exception '%s'", msg, e)
@@ -181,11 +182,10 @@ def wasb_write(self, log: str, remote_log_location: str, append: bool = True) ->
the new log is appended to any existing logs.
:type append: bool
"""
- if append and self.wasb_log_exists(remote_log_location):
- old_log = self.wasb_read(remote_log_location)
- log = '\n'.join([old_log, log]) if old_log else log
-
try:
+ if append and self.wasb_log_exists(remote_log_location):
+ old_log = self.wasb_read(remote_log_location)
+ log = '\n'.join([old_log, log]) if old_log else log
self.hook.load_string(
log,
self.wasb_container,
diff --git a/airflow/ui/.env.example b/airflow/ui/.env.example
new file mode 100644
index 0000000000000..2e5267dc4b219
--- /dev/null
+++ b/airflow/ui/.env.example
@@ -0,0 +1 @@
+API_URL = 'http://127.0.0.1:28080/api/v1/'
diff --git a/airflow/ui/.neutrinorc.js b/airflow/ui/.neutrinorc.js
index ad38d0f3294b4..f6a699e33a861 100644
--- a/airflow/ui/.neutrinorc.js
+++ b/airflow/ui/.neutrinorc.js
@@ -20,6 +20,7 @@
/*
Config for running and building the app
*/
+require('dotenv').config();
const typescript = require('neutrinojs-typescript');
const typescriptLint = require('neutrinojs-typescript-eslint');
const react = require('@neutrinojs/react');
@@ -37,6 +38,10 @@ module.exports = {
// Aliases for internal modules
neutrino.config.resolve.alias.set('root', resolve(__dirname));
neutrino.config.resolve.alias.set('src', resolve(__dirname, 'src'));
+ neutrino.config.resolve.alias.set('views', resolve(__dirname, 'src/views'));
+ neutrino.config.resolve.alias.set('utils', resolve(__dirname, 'src/utils'));
+ neutrino.config.resolve.alias.set('auth', resolve(__dirname, 'src/auth'));
+ neutrino.config.resolve.alias.set('components', resolve(__dirname, 'src/components'));
},
typescript(),
// Modify typescript config in .tsconfig.json
@@ -51,6 +56,9 @@ module.exports = {
moduleDirectories: ['node_modules', 'src'],
}),
react({
+ env: [
+ 'API_URL'
+ ],
html: {
title: 'Apache Airflow',
}
diff --git a/airflow/ui/README.md b/airflow/ui/README.md
index 940469667d5fe..586614ecf7c89 100644
--- a/airflow/ui/README.md
+++ b/airflow/ui/README.md
@@ -27,6 +27,25 @@
- [Chakra UI](https://chakra-ui.com/) - a simple, modular and accessible component library that gives you all the building blocks you need to build your React applications.
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) - write tests that focus on functionality instead of implementation
+## Environment variables
+
+To communicate with the API you need to adjust some environment variables for the webserver and this UI.
+
+Be sure to allow CORS headers and set up an auth backend on your Airflow instance.
+
+```
+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/
+```
+
+Create your local environment and adjust the `API_URL` if needed.
+
+```bash
+cp .env.example .env
+```
+
## Installation
Clone the repository and use the package manager [yarn](https://yarnpkg.com) to install dependencies and get the project running.
diff --git a/airflow/ui/package.json b/airflow/ui/package.json
index 42741264c0075..d4f0cad3dc4c8 100644
--- a/airflow/ui/package.json
+++ b/airflow/ui/package.json
@@ -13,10 +13,15 @@
"@emotion/react": "^11.1.5",
"@emotion/styled": "^11.1.5",
"@neutrinojs/copy": "^9.5.0",
+ "axios": "^0.21.1",
+ "dotenv": "^8.2.0",
"framer-motion": "^3.10.0",
"react": "^16",
"react-dom": "^16",
- "react-hot-loader": "^4"
+ "react-hot-loader": "^4",
+ "react-icons": "^4.2.0",
+ "react-query": "^3.12.3",
+ "react-router-dom": "^5.2.0"
},
"devDependencies": {
"@neutrinojs/eslint": "^9.5.0",
@@ -27,14 +32,18 @@
"@types/jest": "^26.0.20",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.2",
+ "@types/react-router-dom": "^5.1.7",
"eslint": "^7",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
+ "history": "^5.0.0",
"jest": "^26",
"neutrino": "^9.5.0",
"neutrinojs-typescript": "^1.1.6",
"neutrinojs-typescript-eslint": "^1.3.1",
+ "nock": "^13.0.11",
+ "react-test-renderer": "^17.0.1",
"typescript": "^4.2.3",
"webpack": "^4",
"webpack-cli": "^3",
diff --git a/airflow/ui/src/App.tsx b/airflow/ui/src/App.tsx
index 286350d8ecffd..b6d2f35f13df3 100644
--- a/airflow/ui/src/App.tsx
+++ b/airflow/ui/src/App.tsx
@@ -19,12 +19,45 @@
import { hot } from 'react-hot-loader';
import React from 'react';
-import { Center, Heading } from '@chakra-ui/react';
+import { Route, Redirect, Switch } from 'react-router-dom';
+
+import PrivateRoute from 'auth/PrivateRoute';
+
+import Pipelines from 'views/Pipelines';
+import Pipeline from 'views/Pipeline';
+
+import EventLogs from 'views/Activity/EventLogs';
+
+import Config from 'views/Config';
+
+import Access from 'views/Access';
+import Users from 'views/Access/Users';
+import Roles from 'views/Access/Roles';
+
+import Docs from 'views/Docs';
+import NotFound from 'views/NotFound';
const App = () => (
-
- Apache Airflow new UI
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
export default hot(module)(App);
diff --git a/airflow/ui/src/auth/AuthProvider.tsx b/airflow/ui/src/auth/AuthProvider.tsx
new file mode 100644
index 0000000000000..8e52fae1f4675
--- /dev/null
+++ b/airflow/ui/src/auth/AuthProvider.tsx
@@ -0,0 +1,113 @@
+/*!
+ * 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, {
+ useState, useEffect, useCallback, ReactNode, ReactElement,
+} from 'react';
+import axios from 'axios';
+import { useQueryClient } from 'react-query';
+
+import {
+ checkExpire, clearAuth, get, set,
+} from 'utils/localStorage';
+import { AuthContext } from './context';
+
+type Props = {
+ children: ReactNode;
+};
+
+const AuthProvider = ({ children }: Props): ReactElement => {
+ const [hasValidAuthToken, setHasValidAuthToken] = useState(false);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const queryClient = useQueryClient();
+
+ const clearData = useCallback(() => {
+ setHasValidAuthToken(false);
+ clearAuth();
+ queryClient.clear();
+ axios.defaults.headers.common.Authorization = null;
+ }, [queryClient]);
+
+ const logout = () => clearData();
+
+ // intercept responses and logout on unauthorized error
+ axios.interceptors.response.use(
+ (res) => res,
+ (err) => {
+ if (err && err.response && err.response.status === 401) {
+ logout();
+ }
+ return Promise.reject(err);
+ },
+ );
+
+ useEffect(() => {
+ const token = get('token');
+ const isExpired = checkExpire('token');
+ if (token && !isExpired) {
+ axios.defaults.headers.common.Authorization = token;
+ setHasValidAuthToken(true);
+ } else if (token) {
+ clearData();
+ setError(new Error('Token invalid, please reauthenticate.'));
+ } else {
+ setHasValidAuthToken(false);
+ }
+ setLoading(false);
+ }, [clearData]);
+
+ // Login with basic auth.
+ // There is no actual auth endpoint yet, so we check against a generic endpoint
+ const login = async (username: string, password: string) => {
+ setLoading(true);
+ setError(null);
+ try {
+ const authorization = `Basic ${btoa(`${username}:${password}`)}`;
+ await axios.get(`${process.env.API_URL}config`, {
+ headers: {
+ Authorization: authorization,
+ },
+ });
+ set('token', authorization);
+ axios.defaults.headers.common.Authorization = authorization;
+ setLoading(false);
+ setHasValidAuthToken(true);
+ } catch (e) {
+ setLoading(false);
+ setError(e);
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default AuthProvider;
diff --git a/airflow/ui/src/auth/PrivateRoute.tsx b/airflow/ui/src/auth/PrivateRoute.tsx
new file mode 100644
index 0000000000000..6258ffe3c7787
--- /dev/null
+++ b/airflow/ui/src/auth/PrivateRoute.tsx
@@ -0,0 +1,33 @@
+/*!
+ * 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, {
+ FC,
+} from 'react';
+import { Route, RouteProps } from 'react-router-dom';
+
+import Login from 'views/Login';
+import { useAuthContext } from './context';
+
+const PrivateRoute: FC = (props) => {
+ const { hasValidAuthToken } = useAuthContext();
+ return hasValidAuthToken ? : ;
+};
+
+export default PrivateRoute;
diff --git a/airflow/ui/src/auth/context.ts b/airflow/ui/src/auth/context.ts
new file mode 100644
index 0000000000000..da741c64bf684
--- /dev/null
+++ b/airflow/ui/src/auth/context.ts
@@ -0,0 +1,43 @@
+/*!
+ * 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 {
+ createContext, useContext,
+} from 'react';
+
+// todo: eventually replace hasValidAuthToken with a user object
+interface AuthContextData {
+ hasValidAuthToken: boolean;
+ login: (username: string, password: string) => void;
+ logout: () => void;
+ loading: boolean;
+ error: Error | null;
+}
+
+export const authContextDefaultValue: AuthContextData = {
+ hasValidAuthToken: false,
+ login: () => null,
+ logout: () => null,
+ loading: true,
+ error: null,
+};
+
+export const AuthContext = createContext(authContextDefaultValue);
+
+export const useAuthContext = () => useContext(AuthContext);
diff --git a/airflow/ui/src/components/AppHeader.tsx b/airflow/ui/src/components/AppHeader.tsx
new file mode 100644
index 0000000000000..b3193bb82135a
--- /dev/null
+++ b/airflow/ui/src/components/AppHeader.tsx
@@ -0,0 +1,57 @@
+/*!
+ * 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 {
+ Button,
+ Flex,
+ Box,
+ Icon,
+} from '@chakra-ui/react';
+import { MdExitToApp } from 'react-icons/md';
+import { useAuthContext } from 'auth/context';
+
+const AppHeader: React.FC = ({ children }) => {
+ const { logout } = useAuthContext();
+
+ return (
+
+
+
+
+
+ {children}
+
+ );
+};
+
+export default AppHeader;
diff --git a/airflow/ui/src/index.tsx b/airflow/ui/src/index.tsx
index 1ac3ef3b7ee71..7cba9ab7472da 100644
--- a/airflow/ui/src/index.tsx
+++ b/airflow/ui/src/index.tsx
@@ -19,14 +19,29 @@
import React from 'react';
import { render } from 'react-dom';
+import { BrowserRouter } from 'react-router-dom';
import { ChakraProvider } from '@chakra-ui/react';
+import {
+ QueryClient,
+ QueryClientProvider,
+} from 'react-query';
+
+import AuthProvider from 'auth/AuthProvider';
import App from './App';
import theme from './theme';
+const queryClient = new QueryClient();
+
render(
-
-
- ,
+
+
+
+
+
+
+
+
+ ,
document.getElementById('root'),
);
diff --git a/airflow/ui/src/utils/localStorage.ts b/airflow/ui/src/utils/localStorage.ts
new file mode 100644
index 0000000000000..d07166ee10f1c
--- /dev/null
+++ b/airflow/ui/src/utils/localStorage.ts
@@ -0,0 +1,55 @@
+/*!
+ * 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.
+ */
+
+export function set(key: string, value:string): Record {
+ localStorage[key] = value;
+
+ // Set session expiry (24hrs)
+ if (key === 'token') {
+ const date = new Date();
+ localStorage[`${key}-expire`] = new Date(date.getTime() + 86400000);
+ }
+
+ return localStorage[key];
+}
+
+export function get(key: string, defaultValue = undefined): string {
+ const value = localStorage[key] || defaultValue;
+ return value;
+}
+
+export function clear(): void {
+ return localStorage.clear();
+}
+
+export function remove(key: string): void {
+ return localStorage.removeItem(key);
+}
+
+export function checkExpire(key: string): boolean {
+ const sessExpire = get(`${key}-expire`);
+ const sess = get(key);
+ if (!sessExpire || !sess) return true;
+ return new Date() > new Date(sessExpire);
+}
+
+export function clearAuth(): void {
+ localStorage.removeItem('token');
+ localStorage.removeItem('token-expire');
+}
diff --git a/airflow/ui/src/views/Access/Roles.tsx b/airflow/ui/src/views/Access/Roles.tsx
new file mode 100644
index 0000000000000..023f3fca72aba
--- /dev/null
+++ b/airflow/ui/src/views/Access/Roles.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const Roles: React.FC = () => (
+
+ Roles
+
+);
+
+export default Roles;
diff --git a/airflow/ui/src/views/Access/Users.tsx b/airflow/ui/src/views/Access/Users.tsx
new file mode 100644
index 0000000000000..60aed7765bc40
--- /dev/null
+++ b/airflow/ui/src/views/Access/Users.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const Users: React.FC = () => (
+
+ Users
+
+);
+
+export default Users;
diff --git a/airflow/ui/src/views/Access/index.tsx b/airflow/ui/src/views/Access/index.tsx
new file mode 100644
index 0000000000000..b5593bac500c3
--- /dev/null
+++ b/airflow/ui/src/views/Access/index.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const Access: React.FC = () => (
+
+ Access
+
+);
+
+export default Access;
diff --git a/airflow/ui/src/views/Activity/EventLogs.tsx b/airflow/ui/src/views/Activity/EventLogs.tsx
new file mode 100644
index 0000000000000..82b50e0b76991
--- /dev/null
+++ b/airflow/ui/src/views/Activity/EventLogs.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const EventLogs: React.FC = () => (
+
+ Event Logs
+
+);
+
+export default EventLogs;
diff --git a/airflow/ui/src/views/Config/index.tsx b/airflow/ui/src/views/Config/index.tsx
new file mode 100644
index 0000000000000..bfb5ad873fc52
--- /dev/null
+++ b/airflow/ui/src/views/Config/index.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const Config: React.FC = () => (
+
+ Config
+
+);
+
+export default Config;
diff --git a/airflow/ui/src/views/Docs.tsx b/airflow/ui/src/views/Docs.tsx
new file mode 100644
index 0000000000000..9aaae2c365cca
--- /dev/null
+++ b/airflow/ui/src/views/Docs.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const Docs: React.FC = () => (
+
+ Docs
+
+);
+
+export default Docs;
diff --git a/airflow/ui/src/views/Login.tsx b/airflow/ui/src/views/Login.tsx
new file mode 100644
index 0000000000000..45f4cc95f7b84
--- /dev/null
+++ b/airflow/ui/src/views/Login.tsx
@@ -0,0 +1,106 @@
+/*!
+ * 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, { useState, FormEvent } from 'react';
+import {
+ Box,
+ Button,
+ FormLabel,
+ FormControl,
+ Icon,
+ Input,
+ InputGroup,
+ InputLeftElement,
+ Spinner,
+ Alert,
+ AlertIcon,
+} from '@chakra-ui/react';
+import { MdLock, MdPerson } from 'react-icons/md';
+
+import { useAuthContext } from 'auth/context';
+
+const Login: React.FC = () => {
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const { login, error, loading } = useAuthContext();
+
+ const onSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ login(username, password);
+ };
+
+ return (
+
+
+
+ Username
+
+
+
+
+ setUsername(e.target.value)}
+ isRequired
+ />
+
+
+
+ Password
+
+
+
+
+ setPassword(e.target.value)}
+ isRequired
+ />
+
+
+
+ {error && (
+
+
+ {error.message}
+
+ )}
+
+
+ );
+};
+
+export default Login;
diff --git a/airflow/ui/src/views/NotFound.tsx b/airflow/ui/src/views/NotFound.tsx
new file mode 100644
index 0000000000000..a6191e724f749
--- /dev/null
+++ b/airflow/ui/src/views/NotFound.tsx
@@ -0,0 +1,44 @@
+/*!
+ * 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 as RouterLink } from 'react-router-dom';
+import {
+ Center,
+ Box,
+ Heading,
+ Link,
+} from '@chakra-ui/react';
+
+const NotFound: React.FC = () => (
+
+
+ Page not found
+
+ Return to the main page
+
+
+
+);
+
+export default NotFound;
diff --git a/airflow/ui/src/views/Pipeline/index.tsx b/airflow/ui/src/views/Pipeline/index.tsx
new file mode 100644
index 0000000000000..92741b3f44948
--- /dev/null
+++ b/airflow/ui/src/views/Pipeline/index.tsx
@@ -0,0 +1,29 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+
+const Pipeline: React.FC = () => (
+
+ Pipeline
+
+);
+
+export default Pipeline;
diff --git a/airflow/ui/src/views/Pipelines.tsx b/airflow/ui/src/views/Pipelines.tsx
new file mode 100644
index 0000000000000..a9afa5b65412f
--- /dev/null
+++ b/airflow/ui/src/views/Pipelines.tsx
@@ -0,0 +1,32 @@
+/*!
+ * 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 { Center, Heading } from '@chakra-ui/react';
+import AppHeader from 'components/AppHeader';
+
+const Pipelines: React.FC = () => (
+
+