You will be automatically logged out in {remainTime} seconds due to inactivity
+
+
+
+
+
+
+ );
+};
+
+export default InactivityTimeout;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/NavBar.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/NavBar.tsx
new file mode 100644
index 00000000000..7a8a364f601
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/NavBar.tsx
@@ -0,0 +1,116 @@
+/**
+ * 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 { faUser } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {useEffect, useState} from "react";
+import {
+ Container,
+ Navbar,
+ Nav,
+ Dropdown,
+ DropdownDivider,
+ NavDropdown
+} from "react-bootstrap";
+import {
+ decryptData,
+ getFromLocalStorage,
+ parseJSONData} from "./api/Utility.ts";
+import { get }from "lodash";
+import AmbariAboutModal from "./AmbariAboutModal";
+import signOut from "./api/logout.ts";
+
+type NavBarProps = {
+ subPath: string;
+ clusterName: string;
+};
+
+export default function NavBar({ subPath, clusterName }: NavBarProps) {
+ const [showAmbariAboutModal, setShowAmbariAboutModal] = useState(false);
+ const [loginUserName, setLoginUserName] = useState("");
+ const [ambariLsVal, setAmbariLsVal] = useState(null);
+
+ useEffect(() => {
+ let ambariKey = getFromLocalStorage('ambari');
+ if (ambariKey) {
+ setAmbariLsVal(parseJSONData(decryptData(ambariKey)));
+ }
+ }, []);
+
+ useEffect(() => {
+ if (ambariLsVal) {
+ const loginName = get(ambariLsVal, 'app.loginName');
+ if (loginName) {
+ setLoginUserName(loginName);
+ }
+ }
+ }, [ambariLsVal]);
+
+ return (
+
+ );
+}
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/SideBar.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/SideBar.tsx
new file mode 100644
index 00000000000..10ebc88944c
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/SideBar.tsx
@@ -0,0 +1,166 @@
+/**
+ * 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.
+ */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Collapse } from "react-bootstrap";
+import SideItem from "./SideItem";
+import { useContext, useEffect, useState } from "react";
+import { SideItemLabels } from "./SideItemList";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faAngleDoubleLeft,
+ faAngleDoubleRight,
+} from "@fortawesome/free-solid-svg-icons";
+import AppContent from "./context/AppContext";
+import SidebarItem from "./components/SidebarItem";
+import SidebarItemCollapsed from "./components/SidebarItemCollapsed";
+import RoutesList from "./router/RoutesList";
+import getSideItemList from "./SideItemList";
+
+type SideBarProps = {
+ isRoot?: boolean;
+ isSidebarCollapsed:boolean;
+ setIsSidebarCollapsed: React.Dispatch>;
+ clusterExists?: boolean;
+};
+
+const SideBar = ({ clusterExists, isSidebarCollapsed,setIsSidebarCollapsed }: SideBarProps) => {
+ const SideItemList: SideItem[] = getSideItemList(clusterExists ?? false);
+ const [openOptions, setOpenOptions] = useState([
+ SideItemLabels.CLUSTERMANAGEMENT,
+ ]);
+ const { selectedOption, setSelectedOption } = useContext(AppContent);
+ const isElementOpen = (id: string) => {
+ return openOptions.includes(id);
+ };
+
+ useEffect(() => {
+ const currentHash = window.location.hash;
+ const currentPath = currentHash.replace("#", "");
+ const matchedRoute = RoutesList.find(
+ (route: any) => route.path === currentPath
+ );
+ if (matchedRoute) {
+ setSelectedOption(matchedRoute.name);
+ }
+ }, []);
+
+ const handleSideItemClick = (itemId: string) => {
+ if (isElementOpen(itemId)) {
+ setOpenOptions(openOptions.filter((opt) => opt !== itemId));
+ } else {
+ setOpenOptions([...openOptions, itemId]);
+ }
+ };
+ if (!isSidebarCollapsed) {
+ return (
+
+ );
+ }
+};
+
+export default SideBar;
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/SideItem.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/SideItem.tsx
new file mode 100644
index 00000000000..784cb139224
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/SideItem.tsx
@@ -0,0 +1,30 @@
+/**
+ * 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 { ReactNode } from "react";
+
+type SideItem = {
+ id: string;
+ icon: ReactNode;
+ name: ReactNode;
+ path?: string;
+ children: SideItem[];
+ style?: unknown;
+ className?: string;
+};
+
+export default SideItem;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/SideItemList.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/SideItemList.tsx
new file mode 100644
index 00000000000..3103eb328e3
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/SideItemList.tsx
@@ -0,0 +1,134 @@
+/**
+ * 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 SideItem from "./SideItem";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ faTachometerAlt,
+ faCloud,
+ faUsers,
+ faTh,
+} from "@fortawesome/free-solid-svg-icons";
+import { Image } from "react-bootstrap";
+import AmbariLogo from "./assets/img/ambari-logo.png";
+
+export enum SideItemLabels {
+ LOGO = "logo",
+ DASHBOARD = "dashboard",
+ CLUSTERMANAGEMENT = "Cluster Management",
+ CLUSTERINFORMATION = "Cluster Information",
+ VERSIONS = "Versions",
+ REMOTECLUSTERS = "Remote Clusters",
+ USERS = "Users",
+ VIEWS = "Views",
+}
+// START GENAI@CHATGPT4
+const getSideItemList = (clusterExists: boolean): SideItem[] => {
+ const baseList: SideItem[] = [...SideItemList];
+
+ const clusterMgmtItem = baseList.find(
+ (item) => item.id === SideItemLabels.CLUSTERMANAGEMENT
+ );
+
+ if (clusterMgmtItem) {
+ const versionsChildIndex = clusterMgmtItem.children.findIndex(child => child.id === SideItemLabels.VERSIONS);
+
+ if (clusterExists) {
+ // If cluster exists, add the VERSIONS child to CLUSTERMANAGEMENT
+ if (versionsChildIndex === -1) {
+ clusterMgmtItem.children.push({
+ id: SideItemLabels.VERSIONS,
+ icon: null,
+ name: "Versions",
+ path: "/stackVersions",
+ children: [],
+ });
+ }
+ } else {
+ // If cluster does not exist, remove the VERSIONS child from CLUSTERMANAGEMENT
+ if (versionsChildIndex !== -1) {
+ clusterMgmtItem.children.splice(versionsChildIndex, 1);
+ }
+ }
+ }
+
+ return baseList;
+};
+
+const SideItemList: SideItem[] = [
+ {
+ id: SideItemLabels.LOGO,
+ icon: (
+
+ ),
+ name:
Ambari
,
+ path: "/dashboard",
+ children: [],
+ style: { background: "#313d54", height: "60px" },
+ },
+ {
+ id: SideItemLabels.DASHBOARD,
+ icon: ,
+ name: "Dashboard",
+ path: "/dashboard",
+ children: [],
+ },
+ {
+ id: SideItemLabels.CLUSTERMANAGEMENT,
+ icon: ,
+ name: "Cluster Management",
+ children: [
+ {
+ id: SideItemLabels.CLUSTERINFORMATION,
+ icon: null,
+ name: "Cluster Information",
+ path: "/clusterInformation",
+ children: [],
+ },
+ {
+ id: SideItemLabels.VERSIONS,
+ icon: null,
+ name: "Versions",
+ path: "/stackVersions",
+ children: [],
+ },
+ {
+ id: SideItemLabels.REMOTECLUSTERS,
+ icon: null,
+ name: "Remote Clusters",
+ path: "/remoteClusters",
+ children: [],
+ },
+ ],
+ },
+ {
+ id: SideItemLabels.USERS,
+ icon: ,
+ name: "Users",
+ path: "/userManagement?tab=users",
+ children: [],
+ },
+ {
+ id: SideItemLabels.VIEWS,
+ icon: ,
+ name: "Views",
+ path: "/views",
+ children: [],
+ },
+];
+
+export default getSideItemList;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockClusterBluePrintInfo.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockClusterBluePrintInfo.ts
new file mode 100644
index 00000000000..e1f2432d0df
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockClusterBluePrintInfo.ts
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+const mockClusterBluePrintInfo = {};
+export default mockClusterBluePrintInfo;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockEditInstance.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockEditInstance.ts
new file mode 100644
index 00000000000..d0ae5d98f16
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockEditInstance.ts
@@ -0,0 +1,484 @@
+/**
+ * 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 const mockInstanceDetails = {
+ href: 'http://example.com/root',
+ ViewInstanceInfo: {
+ cluster_handle: 123,
+ cluster_type: 'LOCAL_AMBARI',
+ context_path: '/context/path',
+ description: 'A description of the view instance',
+ icon64_path: null,
+ icon_path: null,
+ instance_name: 'Instance1',
+ label: 'Instance Label',
+ short_url: 'http://short.url',
+ short_url_name: 'short-url',
+ static: false,
+ validation_result: {
+ valid: true,
+ detail: 'Validation successful'
+ },
+ version: '1.0',
+ view_name: 'View1',
+ visible: true,
+ instance_data: {},
+ properties: {
+ "hdfs.auth_to_local": {
+ viewInfo: {
+ name: 'hdfs.auth_to_local',
+ description: 'Description for hdfs.auth_to_local',
+ label: 'HDFS Auth To Local',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "hdfs.umask-mode": {
+ viewInfo: {
+ name: 'hdfs.umask-mode',
+ description: 'Description for hdfs.umask-mode',
+ label: 'HDFS Umask Mode',
+ placeholder: null,
+ defaultValue: '022',
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: '022',
+ isSetting: false
+ }
+ },
+ "tmp.dir": {
+ viewInfo: {
+ name: 'tmp.dir',
+ description: 'Description for tmp.dir',
+ label: 'Temporary Directory',
+ placeholder: '/tmp',
+ defaultValue: '/tmp',
+ clusterConfig: null,
+ required: true,
+ masked: false,
+ value: '/tmp',
+ isSetting: false
+ }
+ },
+ "view.conf.keyvalues": {
+ viewInfo: {
+ name: 'view.conf.keyvalues',
+ description: 'Description for view.conf.keyvalues',
+ label: 'View Config Key Values',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: null,
+ required: false,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.auth": {
+ viewInfo: {
+ name: 'webhdfs.auth',
+ description: 'Description for webhdfs.auth',
+ label: 'WebHDFS Auth',
+ placeholder: 'auth-placeholder',
+ defaultValue: null,
+ clusterConfig: null,
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.client.failover.proxy.provider": {
+ viewInfo: {
+ name: 'webhdfs.client.failover.proxy.provider',
+ description: 'Description for webhdfs.client.failover.proxy.provider',
+ label: 'WebHDFS Client Failover Proxy Provider',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.ha.namenode.http-address.list": {
+ viewInfo: {
+ name: 'webhdfs.ha.namenode.http-address.list',
+ description: 'Description for webhdfs.ha.namenode.http-address.list',
+ label: 'WebHDFS HA Namenode HTTP Address List',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.ha.namenode.https-address.list": {
+ viewInfo: {
+ name: 'webhdfs.ha.namenode.https-address.list',
+ description: 'Description for webhdfs.ha.namenode.https-address.list',
+ label: 'WebHDFS HA Namenode HTTPS Address List',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.ha.namenode.rpc-address.list": {
+ viewInfo: {
+ name: 'webhdfs.ha.namenode.rpc-address.list',
+ description: 'Description for webhdfs.ha.namenode.rpc-address.list',
+ label: 'WebHDFS HA Namenode RPC Address List',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.ha.namenodes.list": {
+ viewInfo: {
+ name: 'webhdfs.ha.namenodes.list',
+ description: 'Description for webhdfs.ha.namenodes.list',
+ label: 'WebHDFS HA Namenodes List',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.nameservices": {
+ viewInfo: {
+ name: 'webhdfs.nameservices',
+ description: 'Description for webhdfs.nameservices',
+ label: 'WebHDFS Nameservices',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.url": {
+ viewInfo: {
+ name: 'webhdfs.url',
+ description: 'Description for webhdfs.url',
+ label: 'WebHDFS URL',
+ placeholder: null,
+ defaultValue: null,
+ clusterConfig: 'cluster-config',
+ required: true,
+ masked: false,
+ value: null,
+ isSetting: false
+ }
+ },
+ "webhdfs.username": {
+ viewInfo: {
+ name: 'webhdfs.username',
+ description: 'Description for webhdfs.username',
+ label: 'WebHDFS Username',
+ placeholder: 'username-placeholder',
+ defaultValue: 'default-username',
+ clusterConfig: null,
+ required: true,
+ masked: false,
+ value: 'default-username',
+ isSetting: false
+ }
+ }
+ },
+ property_validation_results: {
+ "hdfs.auth_to_local": {
+ valid: true,
+ detail: 'Validation successful for hdfs.auth_to_local'
+ },
+ "hdfs.umask-mode": {
+ valid: true,
+ detail: 'Validation successful for hdfs.umask-mode'
+ },
+ "tmp.dir": {
+ valid: true,
+ detail: 'Validation successful for tmp.dir'
+ },
+ "view.conf.keyvalues": {
+ valid: true,
+ detail: 'Validation successful for view.conf.keyvalues'
+ },
+ "webhdfs.auth": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.auth'
+ },
+ "webhdfs.client.failover.proxy.provider": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.client.failover.proxy.provider'
+ },
+ "webhdfs.ha.namenode.http-address.list": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.ha.namenode.http-address.list'
+ },
+ "webhdfs.ha.namenode.https-address.list": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.ha.namenode.https-address.list'
+ },
+ "webhdfs.ha.namenode.rpc-address.list": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.ha.namenode.rpc-address.list'
+ },
+ "webhdfs.ha.namenodes.list": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.ha.namenodes.list'
+ },
+ "webhdfs.nameservices": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.nameservices'
+ },
+ "webhdfs.url": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.url'
+ },
+ "webhdfs.username": {
+ valid: true,
+ detail: 'Validation successful for webhdfs.username'
+ }
+ }
+ },
+ privileges: [
+ {
+ href: 'http://example.com/privilege/1',
+ PrivilegeInfo: {
+ instance_name: 'Instance1',
+ permission_label: 'Read',
+ permission_name: 'READ_PRIVILEGE',
+ principal_name: 'User1',
+ principal_type: 'USER',
+ privilege_id: 1,
+ version: '1.0',
+ view_name: 'View1',
+ },
+ },
+ {
+ href: 'http://example.com/privilege/2',
+ PrivilegeInfo: {
+ instance_name: 'Instance1',
+ permission_label: 'Write',
+ permission_name: 'WRITE_PRIVILEGE',
+ principal_name: 'User2',
+ principal_type: 'USER',
+ privilege_id: 2,
+ version: '1.0',
+ view_name: 'View1',
+ },
+ },
+ ],
+ resources: [
+ {
+ href: 'http://example.com/resource/1',
+ instance_name: 'Instance1',
+ name: 'Resource1',
+ version: '1.0',
+ view_name: 'View1',
+ },
+ {
+ href: 'http://example.com/resource/2',
+ instance_name: 'Instance1',
+ name: 'Resource2',
+ version: '1.0',
+ view_name: 'View1',
+ },
+ ],
+};
+
+export const mockGroupData = {
+ href: 'http://example.com',
+ items: [
+ {
+ href: 'http://example.com/group/1',
+ Groups: {
+ group_name: 'Group One',
+ group_type: 'LOCAL',
+ ldap_group: false,
+ },
+ },
+ {
+ href: 'http://example.com/group/2',
+ Groups: {
+ group_name: 'Group Two',
+ group_type: 'LOCAL',
+ ldap_group: true,
+ },
+ },
+ ],
+};
+
+export const mockPrivileges =
+{
+ href: 'http://example.com/root',
+ ViewInstanceInfo: {
+ instance_name: 'Instance1',
+ version: '1.0',
+ view_name: 'View1',
+ },
+ privileges: [
+ {
+ href: 'http://example.com/privilege/1',
+ PrivilegeInfo: {
+ instance_name: 'Instance1',
+ permission_label: 'Read',
+ permission_name: 'READ_PRIVILEGE',
+ principal_name: 'User1',
+ principal_type: 'USER',
+ privilege_id: 1,
+ version: '1.0',
+ view_name: 'View1',
+ },
+ },
+ {
+ href: 'http://example.com/privilege/2',
+ PrivilegeInfo: {
+ instance_name: 'Instance1',
+ permission_label: 'Write',
+ permission_name: 'WRITE_PRIVILEGE',
+ principal_name: 'User2',
+ principal_type: 'USER',
+ privilege_id: 2,
+ version: '1.0',
+ view_name: 'View1',
+ },
+ },
+ {
+ href: 'http://example.com/privilege/2',
+ PrivilegeInfo: {
+ instance_name: 'Instance1',
+ permission_label: 'Write',
+ permission_name: 'WRITE_PRIVILEGE',
+ principal_name: 'Group1',
+ principal_type: 'GROUP',
+ privilege_id: 2,
+ version: '1.0',
+ view_name: 'View1',
+ },
+ },
+ ],
+};
+
+export const mockViewsData = {
+ href: 'http://example.com',
+ ViewVersionInfo: {
+ archive: 'archive.zip',
+ build_number: '100',
+ cluster_configurable: true,
+ description: null,
+ label: 'Label 1',
+ masker_class: null,
+ max_ambari_version: null,
+ min_ambari_version: 'v1.0',
+ parameters: [
+ {
+ name: 'param1',
+ description: 'This is parameter 1',
+ label: 'Label 1',
+ placeholder: 'Placeholder 1',
+ defaultValue: 'Default 1',
+ clusterConfig: 'Config 1',
+ required: true,
+ masked: false,
+ },
+ ],
+ status: 'active',
+ status_detail: 'Detail 1',
+ system: false,
+ version: 'v1.0',
+ view_name: 'View 1',
+ },
+ instances: [
+ {
+ href: 'http://example.com/instance/1',
+ ViewInstanceInfo: {
+ instance_name: 'Instance 1',
+ version: 'v1.0',
+ view_name: 'View 1',
+ },
+ },
+ ],
+ permissions: [
+ {
+ href: 'http://example.com/permission/1',
+ PermissionInfo: {
+ permission_id: 1,
+ version: 'v1.0',
+ view_name: 'View 1',
+ },
+ },
+ ],
+};
+
+export const mockUsersdata = {
+ href: 'http://example.com',
+ items: [
+ {
+ href: 'http://example.com/user/1',
+ Users: {
+ active: true,
+ admin: false,
+ consecutive_failures: 0,
+ created: 1622470423,
+ display_name: 'User One',
+ groups: ['group1', 'group2'],
+ ldap_user: false,
+ local_user_name: 'userone',
+ user_name: 'userone',
+ user_type: 'LOCAL',
+ },
+ },
+ {
+ href: 'http://example.com/user/2',
+ Users: {
+ active: false,
+ admin: true,
+ consecutive_failures: 3,
+ created: 1622470424,
+ display_name: 'User Two',
+ groups: ['group3'],
+ ldap_user: true,
+ local_user_name: 'usertwo',
+ user_name: 'usertwo',
+ user_type: 'LOCAL',
+ },
+ },
+ ],
+};
+
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockHostClusterInfo.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockHostClusterInfo.ts
new file mode 100644
index 00000000000..164a31f0093
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockHostClusterInfo.ts
@@ -0,0 +1,27 @@
+/**
+ * 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.
+ */
+const mockHostClusterInfo = {
+ items: [
+ {
+ Clusters: {
+ provisioning_state: "NOT INSTALLED"
+ }
+ }
+ ]
+}
+export default mockHostClusterInfo;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockUpdateClusterName.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockUpdateClusterName.ts
new file mode 100644
index 00000000000..9718ed7ea28
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/__mocks__/mockUpdateClusterName.ts
@@ -0,0 +1,28 @@
+/**
+ * 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.
+ */
+const mockUpdateClusterName = (clusterName :string, updatedClusterName :string) => {
+ return new Promise((resolve, reject) => {
+ if (!clusterName || !updatedClusterName) {
+ reject(new Error('Both clusterName and updatedClusterName must be truthy'));
+ } else {
+ resolve({ clusterName, updatedClusterName });
+ }
+ });
+};
+
+export default mockUpdateClusterName;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/api/logout.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/api/logout.ts
new file mode 100644
index 00000000000..de867e96fed
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/api/logout.ts
@@ -0,0 +1,53 @@
+/**
+ * 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 {encryptData, decryptData, getFromLocalStorage, parseJSONData, setInLocalStorage} from "./Utility.ts";
+import {adminApi} from "./configs/axiosConfig.ts";
+import { AxiosError } from 'axios';
+
+const signOut = async () => {
+ let ambariKey = getFromLocalStorage('ambari');
+ let data;
+ if (ambariKey) {
+ data = parseJSONData(decryptData(ambariKey));
+ }
+ delete data.app.authenticated;
+ delete data.app.loginName;
+ delete data.app.user;
+
+ //with encrypting set data in LS
+ setInLocalStorage('ambari', encryptData(JSON.stringify(data)));
+
+ const headers = {
+ 'Authorization': 'Basic'
+ };
+
+ try {
+ const url = "/logout"
+ await adminApi.request({
+ url: url,
+ method: 'GET',
+ headers: headers
+ });
+ localStorage.clear();
+ window.location.replace("/#/login");
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ throw new Error(`Logout failed with status: ${axiosError.response?.status}`);
+ }
+}
+export default signOut;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/clusterHost.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/clusterHost.ts
new file mode 100644
index 00000000000..aa150642102
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/clusterHost.ts
@@ -0,0 +1,19 @@
+// export const DEV_VITE_API_PROXY_TARGET="http://sl73tskrapd1164.visa.com:8080"
+// export const PROD_VITE_API_PROXY_TARGET=""
+
+// // # When setting up the project, make sure to set the TOKEN environment variable.
+// // # This should be a Basic Auth token generated from your username and password for the given cluster.
+// // # You can do this by uncommenting the following line and replacing 'your-basic-auth-token' with your actual Basic Auth token. ( eg 'YWRtaW46VmlzYUAxMjM=')
+// export const DEV_VITE_TOKEN="YWRtaW46VmlzYUAxMjM="
+
+
+export const config={
+ development:{
+ VITE_API_PROXY_TARGET:"http://##REPLACE_YOUR_AMBARI_SERVER_URL_HERE",
+ VITE_TOKEN:"##REPLACE_YOUR_AUTH_TOKEN_HERE"
+ },
+ production:{
+ VITE_API_PROXY_TARGET:"",
+ VITE_TOKEN:""
+ }
+ }
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/AddVersionModal.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/AddVersionModal.tsx
new file mode 100644
index 00000000000..ffafb0403d9
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/AddVersionModal.tsx
@@ -0,0 +1,134 @@
+/**
+ * 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.
+ */
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+/* eslint-disable @typescript-eslint/ban-types */
+import { useState } from "react";
+import { Button, Form, FormControl, Modal } from "react-bootstrap";
+import DefaultButton from "./DefaultButton";
+import { ReadOptions } from "../constants";
+import VersionsApi from "../api/versions";
+import toast from "react-hot-toast";
+import { get } from "lodash";
+
+type ModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ onReadVersion: Function;
+};
+
+const AddVersionModal = ({ isOpen, onClose, onReadVersion }: ModalProps) => {
+ const [uploadOption, setUploadOption] = useState(ReadOptions.FILE);
+ const [file, setFile] = useState();
+ const [fileUrl, setFileUrl] = useState("");
+ const readVersionInfo = async () => {
+ try {
+ if (uploadOption === ReadOptions.FILE && file) {
+ const reader = new FileReader();
+ reader.onload = async function (event) {
+ const fileContents = get(event, "target.result", undefined);
+ const versionResources = await VersionsApi.readVersionInfo(
+ fileContents,
+ {
+ "Content-Type": "text/xml",
+ }
+ );
+ onReadVersion(versionResources, fileContents, null);
+ };
+ reader.readAsText(file);
+ } else if (uploadOption === ReadOptions.URL && fileUrl) {
+ // Fetch the content from the URL
+ const versionResources = await VersionsApi.readVersionInfo({
+ VersionDefinition: {
+ version_url: fileUrl,
+ },
+ });
+ onReadVersion(versionResources, null, fileUrl);
+ }
+ } catch (err) {
+ toast.error("Could not read version defintion");
+ }
+ };
+ const handleClose = () => {
+ setFile(undefined);
+ setFileUrl("");
+ onClose();
+ };
+ return (
+
+
+ Add Version
+
+
+ {
+ setUploadOption(ReadOptions.FILE);
+ }}
+ label="Upload Version Definition File"
+ className="px-0"
+ />
+ {
+ //@ts-ignore
+ setFile((event?.target as HTMLInputElement)?.files?.[0]);
+ }}
+ disabled={uploadOption !== ReadOptions.FILE}
+ />
+ {
+ setUploadOption(ReadOptions.URL);
+ }}
+ type="radio"
+ label="Version Definition File URL"
+ className="px-0 mt-3"
+ />
+ {
+ setFileUrl(e.target.value);
+ }}
+ />
+
+
+
+ CANCEL
+
+
+
+
+ );
+};
+
+export default AddVersionModal;
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ClusterInformationNavigate.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ClusterInformationNavigate.tsx
new file mode 100644
index 00000000000..6912ddfab16
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ClusterInformationNavigate.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 { useEffect } from 'react';
+
+interface ClusterInformationNavigateProps {
+ setInstallWizardLaunched: (value: boolean) => void;
+}
+
+const ClusterInformationNavigate = ({ setInstallWizardLaunched }: ClusterInformationNavigateProps) => {
+
+ useEffect(() => {
+ setInstallWizardLaunched(false);
+ }, []);
+
+ return null; // Remember to return something to avoid runtime error
+};
+
+export default ClusterInformationNavigate;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ComboSearch.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ComboSearch.tsx
new file mode 100644
index 00000000000..f3bf86b48fa
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ComboSearch.tsx
@@ -0,0 +1,223 @@
+/**
+ * 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.
+ */
+/* eslint-disable @typescript-eslint/ban-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useEffect, useState } from "react";
+import Select from "react-select";
+import { get } from "lodash";
+import { Badge, Button } from "react-bootstrap";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faClose } from "@fortawesome/free-solid-svg-icons";
+type FilterField = { label: string; value: string };
+type PropTypes = {
+ fields: FilterField[];
+ data: any;
+ valueMappings?: { [key: string]: string };
+ searchCallback: Function;
+};
+function ComboSearch({ fields, data, searchCallback }: PropTypes) {
+ const [selectedFilters, setSelectedFilters] = useState<
+ { field: FilterField; value: FilterField }[]
+ >([]);
+ const [selectedField, setSelectedField] = useState(
+ {} as FilterField
+ );
+ const [selectedValue, setSelectedValue] = useState(
+ {} as FilterField
+ );
+ const [valueOptions, setValueOptions] = useState([]);
+ function getCorrespondingValues(){
+ const mappedKey = selectedField?.value;
+ let allValues: any[] = [];
+ data.forEach((item: any) => {
+ const value = get(item, mappedKey, "");
+ if (!value) {
+ // Ignore empty value
+ return;
+ }
+ if (Array.isArray(value)) {
+ allValues = [...allValues, ...value];
+ } else if (typeof value !== "object") {
+ allValues.push(value);
+ }
+ });
+ const uniqueValues = [...new Set(allValues)];
+ console.log("Unique values", uniqueValues);
+ const correspondingValues = uniqueValues.filter(item=>{
+ return selectedValue?.value!==item&&!!!selectedFilters?.find(fil=>fil?.value?.value===item)
+ }).map((item: any) => {
+ return {
+ label: item,
+ value: item,
+ };
+ });
+ return correspondingValues;
+ }
+ // START GENAI@CHATGPT4
+ useEffect(() => {
+ if (selectedField) {
+ const correspondingValues = getCorrespondingValues();
+ setSelectedValue({} as FilterField);
+ setValueOptions(correspondingValues);
+ }
+ }, [selectedField]);
+
+ useEffect(()=>{
+ if(selectedValue){
+ const correspondingValues=getCorrespondingValues();
+ setValueOptions(correspondingValues);
+ }
+ },[selectedValue,selectedFilters])
+
+ const filterData = (data: any) => {
+ const categoryFilters: { [key: string]: any } = {};
+
+ selectedFilters.forEach((filter) => {
+ const category = filter?.field?.value;
+ if (!categoryFilters[category]) {
+ categoryFilters[category] = [];
+ }
+ categoryFilters[category].push(filter?.value?.value);
+ });
+
+ const filteredData = data.filter((item: any) => {
+ return Object.keys(categoryFilters).every((category) => {
+ // Apply OR logic within the same category
+ const itemValue = get(item, category);
+ return Array.isArray(itemValue)
+ ? itemValue.some((v) => categoryFilters[category].includes(v))
+ : categoryFilters[category].includes(itemValue);
+ });
+ });
+ return filteredData;
+ };
+ useEffect(() => {
+ if (selectedFilters.length) {
+ searchCallback(filterData(data));
+ } else {
+ searchCallback(data);
+ }
+ console.log("Selected Filters", selectedFilters);
+ }, [selectedFilters.length]);
+ function addFilter(e: any) {
+ e.preventDefault();
+ const newFilter = { field: selectedField, value: selectedValue };
+ if (
+ !selectedFilters.some(
+ (filter) =>
+ filter?.field?.value === newFilter?.field?.value &&
+ filter?.value?.value === newFilter?.value?.value
+ )
+ ) {
+ setSelectedFilters([...selectedFilters, newFilter]);
+ setSelectedField(null as any);
+ setSelectedValue(null as any)
+ }
+ }
+ function deleteFilter(filterToDelete: {
+ field: { label: string; value: any };
+ value: { label: string; value: any };
+ }) {
+ setSelectedFilters((prevFilters) => {
+ return prevFilters.filter((filter) => {
+ return !(
+ filter?.field?.value === filterToDelete?.field?.value &&
+ filter?.value?.value === filterToDelete?.value?.value
+ );
+ });
+ });
+ }
+ function resetFilters() {
+ setSelectedField(null as any);
+ setSelectedValue(null as any);
+ setSelectedFilters([]);
+ }
+ return (
+
+
+ Select filter(s) to tailor your search. Records update immediately to
+ reflect your preferences.
+
+ );
+}
+
+export default ComboSearch;
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ConfirmationModal.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ConfirmationModal.tsx
new file mode 100644
index 00000000000..3d2b9279214
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ConfirmationModal.tsx
@@ -0,0 +1,72 @@
+/**
+ * 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 { Button, Modal } from "react-bootstrap";
+import DefaultButton from "./DefaultButton";
+
+type ConfirmationModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ modalTitle: string;
+ modalBody: string;
+ successCallback: () => void;
+ buttonVariant?: string;
+};
+
+export default function ConfirmationModal({
+ isOpen,
+ onClose,
+ modalTitle,
+ modalBody,
+ successCallback,
+ buttonVariant = "success",
+}: ConfirmationModalProps) {
+ return (
+
+
+
+
{modalTitle}
+
+
+ {modalBody}
+
+
+ CANCEL
+
+
+
+
+ );
+}
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/DefaultButton.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/DefaultButton.tsx
new file mode 100644
index 00000000000..9bc09cfa866
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/DefaultButton.tsx
@@ -0,0 +1,25 @@
+/**
+ * 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 { Button } from "react-bootstrap";
+
+function DefaultButton({ className, ...props }: any) {
+ className = className ? className + " btn-default" : "btn-default";
+ return ;
+}
+
+export default DefaultButton;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ErrorOverlay.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ErrorOverlay.tsx
new file mode 100644
index 00000000000..cd39597484d
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/ErrorOverlay.tsx
@@ -0,0 +1,38 @@
+/**
+ * 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 { Overlay, Tooltip } from "react-bootstrap";
+
+type ErrorOverlayProps = {
+ target: React.RefObject;
+ showTooltip: boolean;
+ errorMessage: string;
+}
+
+export default function ErrorOverlay({target, showTooltip, errorMessage}: ErrorOverlayProps) {
+ return
+ {(props) => (
+
+ {errorMessage}
+
+ )}
+
+}
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/InstallClusterButton.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/InstallClusterButton.tsx
new file mode 100644
index 00000000000..ece3a1d41b0
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/InstallClusterButton.tsx
@@ -0,0 +1,39 @@
+/**
+ * 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 {Button} from "react-bootstrap";
+
+interface InstallClusterButtonProps {
+ onButtonClick: () => void;
+ setInstallWizardLaunched: (value: boolean) => void;
+}
+
+const InstallClusterButton: React.FC = ({ onButtonClick, setInstallWizardLaunched }) => {
+ const handleRedirectToInstallCluster = () => {
+ setInstallWizardLaunched(false);
+ window.location.href = "/#/installer/step0";
+ onButtonClick();
+ };
+
+ return (
+
+ );
+}
+
+export default InstallClusterButton;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/LostNetworkModal.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/LostNetworkModal.tsx
new file mode 100644
index 00000000000..62dc353c47f
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/LostNetworkModal.tsx
@@ -0,0 +1,56 @@
+/**
+ * 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 { Button, Modal } from "react-bootstrap";
+
+type PropTypes = {
+ onClose: () => void;
+ isOpen: boolean;
+};
+
+const LostNetworkModal = ({ onClose, isOpen }: PropTypes) => {
+ const options = [
+ "Configure your hosts for access to the Internet.",
+ " If you are using an Internet Proxy, refer to the Ambari Documentation on how to configure Ambari to use the Internet Proxy.",
+ "Use the Local Repository option.",
+ ];
+ return (
+
+
+ Public Repository Option Disabled
+
+
+ Ambari does not have access to the Internet and cannot use the Public
+ Repository for installing the software. Your Options:
+
+ {options.map((opt:string)=>{
+ return
+ {opt}
+
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default LostNetworkModal;
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Paginator.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Paginator.tsx
new file mode 100644
index 00000000000..b38ba54c1da
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Paginator.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 { Dropdown, DropdownButton, Pagination } from "react-bootstrap";
+
+type PaginatorProps = {
+ currentPage: number;
+ maxPage: number;
+ changePage: (pageNumber: number) => void;
+ itemsPerPage: number;
+ setItemsPerPage: (recordsCount: number) => void;
+ totalItems: number;
+};
+
+const Paginator = ({
+ currentPage,
+ maxPage,
+ changePage,
+ itemsPerPage,
+ setItemsPerPage,
+ totalItems,
+}: PaginatorProps) => {
+ const items = [];
+ const perPageOptions = [10, 25, 50, 100];
+ for (let number = 1; number <= maxPage; number++) {
+ if (
+ number === currentPage - 1 ||
+ number === currentPage ||
+ number === currentPage + 1 ||
+ number === 1 ||
+ number === maxPage
+ ) {
+ items.push(
+ changePage(number)}
+ className="pagination-btn"
+ >
+ {number}
+
+ );
+ } else if (number === currentPage - 2 || number === currentPage + 2) {
+ items.push();
+ }
+ }
+ const firstItemIndex = (currentPage - 1) * itemsPerPage + 1;
+ const lastItemIndex = Math.min(currentPage * itemsPerPage, totalItems);
+ return (
+ <>
+ {totalItems > 10 ? (
+
+
+
+ Showing {firstItemIndex}-{lastItemIndex} of {totalItems} items
+
+ ) : null}
+ >
+ );
+};
+
+export default Paginator;
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/RedhatSatelliteInfoModal.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/RedhatSatelliteInfoModal.tsx
new file mode 100644
index 00000000000..4169cba959a
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/RedhatSatelliteInfoModal.tsx
@@ -0,0 +1,56 @@
+/**
+ * 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 { Button, Modal } from "react-bootstrap";
+import DefaultButton from "./DefaultButton";
+
+type PropTypes = {
+ isOpen: boolean;
+ onClose: () => void;
+ onCancel: () => void;
+};
+
+const RedhatSatelliteUsageInfo = ({ isOpen, onClose, onCancel }: PropTypes) => {
+ return (
+
+
+ Use RedHat Satellite/Spacewalk
+
+
+ In order for Ambari to install packages from the right repositories, it
+ is recommended that you edit the names of the repo's for each operating
+ system so they match the channel names in your RedHat
+ Satellite/Spacewalk instance.
+
+
+
+ Cancel
+
+
+
+
+ );
+};
+
+export default RedhatSatelliteUsageInfo;
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/SidebarItem.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/SidebarItem.tsx
new file mode 100644
index 00000000000..74c2b866917
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/SidebarItem.tsx
@@ -0,0 +1,66 @@
+/**
+ * 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 { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { MouseEventHandler } from "react";
+import { Link } from "react-router-dom";
+
+const SidebarItem = ({
+ ele,
+ onClick,
+ isSelected,
+ isOpen = false,
+ hasChildren = false,
+ }: {
+ ele: any;
+ onClick?: MouseEventHandler;
+ isSelected: boolean;
+ isOpen?: boolean;
+ hasChildren?: boolean;
+ }) => {
+ return (
+
+
+
+
{ele.icon}
+
{ele.name}
+
+ {hasChildren ? (
+
+ ) : null}
+
+
+ );
+ };
+
+ export default SidebarItem
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/SidebarItemCollapsed.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/SidebarItemCollapsed.tsx
new file mode 100644
index 00000000000..560e9833171
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/SidebarItemCollapsed.tsx
@@ -0,0 +1,109 @@
+/**
+ * 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 { MouseEventHandler, useState } from "react";
+import { Dropdown } from "react-bootstrap";
+import SidebarItem from "./SidebarItem";
+
+const SidebarItemCollapsed = ({
+ ele,
+ onClick,
+ isSelected,
+ childElements,
+ selectedOption,
+ setSelectedOption,
+ }: {
+ ele: any;
+ onClick?: MouseEventHandler;
+ isSelected: boolean;
+ isOpen?: boolean;
+ hasChildren?: boolean;
+ childElements?: any[];
+ selectedOption?: string;
+ setSelectedOption?: any;
+ }) => {
+ const [showDropdown, setShowDropdown] = useState(false);
+ if (childElements?.length) {
+ return (
+ setShowDropdown(false)}
+ onMouseOver={() => setShowDropdown(true)}
+ // style={{ width: "166px" }}
+ >
+
+
+ );
+ }
+ };
+
+ export default SidebarItemCollapsed
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Spinner.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Spinner.tsx
new file mode 100644
index 00000000000..c4a47c2ea24
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Spinner.tsx
@@ -0,0 +1,26 @@
+/**
+ * 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 { Spinner as DefaultSpinner } from "react-bootstrap";
+
+export default function Spinner() {
+ return (
+
+
+
+ );
+}
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Table.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Table.tsx
new file mode 100644
index 00000000000..6b8bb09dc89
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/Table.tsx
@@ -0,0 +1,158 @@
+/**
+ * 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.
+ */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import React from "react";
+import {
+ ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ getSortedRowModel,
+ OnChangeFn,
+ SortingState,
+ useReactTable,
+} from "@tanstack/react-table";
+import { Table as BootstrapTable } from "react-bootstrap";
+import "./style.css";
+import { get } from "lodash";
+interface TableProps {
+ columns: ColumnDef[];
+ data: unknown[];
+ onSortingChange?: OnChangeFn;
+ sorting?: SortingState;
+ className?: string;
+ restProps?: any;
+ striped?: boolean;
+ bordered?: boolean;
+ hover?: boolean;
+ entityName?: string;
+}
+
+const Table: React.FC = ({
+ columns,
+ data,
+ sorting,
+ onSortingChange,
+ entityName,
+ ...restProps
+}) => {
+ const table = useReactTable({
+ columns,
+ data,
+ debugTable: true,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(), //client-side sorting
+ onSortingChange,
+ state: {
+ sorting,
+ },
+ });
+
+ if (!data.length && entityName) {
+ return (
+
+ );
+};
+
+export default Table;
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/components/style.css b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/style.css
new file mode 100644
index 00000000000..8a88e1150f4
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/components/style.css
@@ -0,0 +1,36 @@
+/**
+ * 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.
+ */
+.table thead,
+.table thead th {
+ color: #999 !important;
+ font-weight: 700 !important;
+}
+.table tbody td {
+ color: #666 !important;
+}
+.table tbody td:hover {
+ cursor: default;
+}
+.table > thead > tr > th,
+.table > tbody > tr > th,
+.table > tfoot > tr > th,
+.table > thead > tr > td,
+.table > tbody > tr > td,
+.table > tfoot > tr > td {
+ padding: 12px!important;
+}
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/hooks/usePolling.ts b/ambari-admin/src/main/resources/ui/ambari-admin/src/hooks/usePolling.ts
new file mode 100644
index 00000000000..40bf1ec7b8f
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/hooks/usePolling.ts
@@ -0,0 +1,26 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import { useEffect, useRef } from 'react';
+
+function usePolling(apiFunction:Function, interval=10000) {
+ const savedCallback = useRef();
+
+ // Remember the latest callback.
+ useEffect(() => {
+ savedCallback.current = apiFunction;
+ }, [apiFunction]);
+
+// Set up the interval.
+ useEffect(() => {
+ function tick() {
+ if (savedCallback.current) {
+ savedCallback.current?.();
+ }
+ }
+ if (interval !== null) {
+ const id = setInterval(tick, interval);
+ return () => clearInterval(id);
+ }
+ }, [interval]);
+}
+
+export default usePolling
\ No newline at end of file
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/router/RoutesList.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/router/RoutesList.tsx
index c49cc2dbad7..888ba5dbfb1 100644
--- a/ambari-admin/src/main/resources/ui/ambari-admin/src/router/RoutesList.tsx
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/router/RoutesList.tsx
@@ -15,11 +15,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Redirect } from "react-router-dom";
+import ClusterInformation from "../screens/ClusterManagement/ClusterInformation";
+import RemoteClusters from "../screens/ClusterManagement/RemoteClusters";
+import RegisterRemoteCluster from "../screens/ClusterManagement/RemoteClusters/RegisterRemoteCluster";
+import EditRemoteCluster from "../screens/ClusterManagement/RemoteClusters/EditRemoteCluster";
import Users from "../screens/Users";
-import WIP from "../components/WIP";
-import Register from "../screens/StackVersions/Register";
-import { VersionsList } from "../screens/StackVersions";
+import EditGroup from "../screens/Users/EditGroup";
+import EditUser from "../screens/Users/EditUser";
+import Views from "../screens/Views";
+import EditInstance from "../screens/Views/EditInstance";
+import CreateShortUrl from "../screens/Views/CreateShortUrl";
+import Dashboard from "../screens/ClusterManagement/Dasboard";
+import { Redirect } from "react-router-dom";
+import { Register, VersionsList } from "../screens/ClusterManagement/StackVersions";
@@ -27,19 +35,19 @@ export default [
{
path: "/main/dashboard",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Home",
},
{
path: "/dashboard",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Dashboard",
},
{
path: "/clusterInformation",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Cluster Information",
},
{
@@ -63,19 +71,19 @@ export default [
{
path: "/remoteClusters/:clusterName/edit",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Edit Remote Cluster",
},
{
path: "/remoteClusters/create",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Register Remote Cluster",
},
{
path: "/remoteClusters",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Remote Clusters",
},
{
@@ -87,31 +95,31 @@ export default [
{
path: "/users/:userName/edit",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Users",
},
{
path: "/groups/:groupName/edit",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Groups",
},
{
path:`/views/:viewName/versions/:version/instances/:instanceName/edit`,
exact:true,
- Element:()=>,
+ Element:()=>,
name:"Views",
},
{
path: "/views",
exact: true,
- Element: () => ,
+ Element: () => ,
name: "Views",
},
{
path: '/urls/link/:view_name/:version/:instance_name',
exact: true,
- Element: () => ,
+ Element: () => ,
name: 'Views'
},
{
diff --git a/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/ClusterManagement/ClusterInformation/index.tsx b/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/ClusterManagement/ClusterInformation/index.tsx
new file mode 100644
index 00000000000..ed5ae4b7db0
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/ambari-admin/src/screens/ClusterManagement/ClusterInformation/index.tsx
@@ -0,0 +1,195 @@
+/**
+ * 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.
+ */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useEffect, useState } from "react";
+import {Form, OverlayTrigger, Tooltip} from "react-bootstrap";
+import DefaultButton from "../../../components/DefaultButton";
+import AppContent from "../../../context/AppContext";
+import ClusterApi from "../../../api/clusterApi";
+import Spinner from "../../../components/Spinner";
+import toast from "react-hot-toast";
+import { cloneDeep } from "lodash";
+import ConfirmationModal from "../../../components/ConfirmationModal";
+import { useContext } from "react";
+
+export default function ClusterInformation() {
+ const [infoData, setInfoData] = useState({});
+ const [loading, setLoading] = useState(false);
+ const {
+ setClusterInfo,
+ cluster,
+ cluster: { cluster_name: clusterName },
+ } = useContext(AppContent);
+ const [clusterNameInput, setClusterNameInput] = useState(clusterName);
+ const [clusterNameError, setClusterNameError] = useState("");
+ const [showConfirmationModal, setShowConfirmationModal] = useState(false);
+ console.log("Clutser is", cluster);
+ const { clusterExists, setSelectedOption } = useContext(AppContent);
+ const [showTooltip, setShowTooltip] = useState(false);
+ const handleFocus = () => setShowTooltip(true);
+ const handleBlur = () => setShowTooltip(false);
+
+ useEffect(() => {
+ setSelectedOption("Cluster Information");
+ }, []);
+
+ useEffect(() => {
+ setClusterNameInput(clusterName);
+ }, [clusterName]);
+
+ useEffect(() => {
+ if (!clusterNameInput) {
+ setClusterNameError("Cluster Name is required");
+ } else if (clusterNameInput.length > 80) {
+ setClusterNameError("Cluster Name should be less than 80 characters");
+ }
+ //Should contain only alphanumeric characters
+ else if (!/^[a-zA-Z0-9_]*$/.test(clusterNameInput)) {
+ setClusterNameError(
+ "Cluster Name should contain \n only alphanumeric characters"
+ );
+ } else {
+ setClusterNameError("");
+ }
+ }, [clusterNameInput]);
+
+ async function getClusterInfoData(requiredClusterName: string = clusterName) {
+ setLoading(true);
+ const data = await ClusterApi.blueprintInfo(requiredClusterName);
+ setInfoData(data as any);
+ setLoading(false);
+ }
+
+ useEffect(() => {
+ if (clusterName) getClusterInfoData();
+ }, [clusterName]);
+
+ function downloadBlueprint() {
+ const dataStr =
+ "data:text/json;charset=utf-8," +
+ encodeURIComponent(JSON.stringify(infoData, null, 4));
+ const downloadAnchorNode = document.createElement("a");
+ downloadAnchorNode.setAttribute("href", dataStr);
+ downloadAnchorNode.setAttribute("download", "blueprint.json");
+ document.body.appendChild(downloadAnchorNode);
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+ }
+
+ const handleInputChange = (event: any) => {
+ setClusterNameInput(event.target.value);
+ };
+
+ const saveNewClusterName = async () => {
+ try {
+ await ClusterApi.updateClusterName(clusterName, clusterNameInput);
+ const clusterInfoCopy = cloneDeep(cluster);
+ clusterInfoCopy.cluster_name = clusterNameInput;
+ setClusterInfo(clusterInfoCopy);
+ } catch (err) {
+ console.log("Error is", err);
+ toast.error("Could not update cluster name");
+ } finally {
+ setShowConfirmationModal(false);
+ }
+ };
+
+ return clusterExists ? (
+
+ {
+ setShowConfirmationModal(false);
+ }}
+ modalTitle="Confirm Cluster Name Change"
+ modalBody={`Are you sure you want to change the cluster name to ${clusterNameInput}?`}
+ />
+
+ Cluster Name*
+
+
+
+
+ Only alpha-numeric characters, up to 80 characters
+
{
+ handleModalVisibility(true);
+ }}
+ >
+ Why is this disabled?
+
+ ) : null}
+
+
+
+
+
+
+
Repositories
+
+
+
+ Provide Base URLs for the Operating Systems you are configuring.
+
+
+ {showRepoValidationBanner ? (
+
+ Some of the repositories failed validation. Make changes to the
+ base url or skip validation if you are sure that urls are correct
+
+ ) : null}
+
+ {osListHeaders.map((header) => {
+ return
+ Skip Repository Base URL validation (Advanced)
+
+ Warning! This is for advanced users only. Use this
+ optionif you want to skip validation for Repository Base
+ URLs.
+
+ }
+ >
+
+
+
Local Cluster Permissons
+
+
+ The ability to inherit view Use permission based on Cluster
+ Roles is only available when using a Local Cluster
+ configuration.
+
+