|
|
@@ -1,143 +1,160 @@
|
|
|
-import React, { Component } from "react";
|
|
|
-import styled from "styled-components";
|
|
|
+import React, { useContext, useEffect, useState } from "react";
|
|
|
import { Context } from "shared/Context";
|
|
|
-import { RouteComponentProps, withRouter } from "react-router";
|
|
|
+import { useHistory, useLocation, useRouteMatch } from "react-router";
|
|
|
|
|
|
import {
|
|
|
- ResourceType,
|
|
|
ChartType,
|
|
|
StorageType,
|
|
|
- ClusterType,
|
|
|
} from "shared/types";
|
|
|
import api from "shared/api";
|
|
|
-import { PorterUrl, pushQueryParams, pushFiltered } from "shared/routing";
|
|
|
+import { pushFiltered } from "shared/routing";
|
|
|
import ExpandedJobChart from "./ExpandedJobChart";
|
|
|
import ExpandedChart from "./ExpandedChart";
|
|
|
import Loading from "components/Loading";
|
|
|
import PageNotFound from "components/PageNotFound";
|
|
|
+import { useWebsockets } from "shared/hooks/useWebsockets";
|
|
|
|
|
|
-type PropsType = RouteComponentProps & {
|
|
|
+
|
|
|
+type Props = {
|
|
|
setSidebar: (x: boolean) => void;
|
|
|
isMetricsInstalled: boolean;
|
|
|
-};
|
|
|
-
|
|
|
-type StateType = {
|
|
|
- loading: boolean;
|
|
|
- currentChart: ChartType;
|
|
|
-};
|
|
|
-
|
|
|
-class ExpandedChartWrapper extends Component<PropsType, StateType> {
|
|
|
- state = {
|
|
|
- loading: true,
|
|
|
- currentChart: null as ChartType,
|
|
|
- };
|
|
|
-
|
|
|
- // Retrieve full chart data (includes form and values)
|
|
|
- getChartData = () => {
|
|
|
- let { match } = this.props;
|
|
|
- let { namespace, chartName } = match.params as any;
|
|
|
- let { currentProject, currentCluster } = this.context;
|
|
|
- if (currentProject && currentCluster) {
|
|
|
- // TODO: add query for retrieving max revision #
|
|
|
- api
|
|
|
- .getRevisions(
|
|
|
+}
|
|
|
+
|
|
|
+const ExpandedChartWrapper: React.FunctionComponent<Props> = ({ setSidebar, isMetricsInstalled }) => {
|
|
|
+ // Router based state
|
|
|
+ const location = useLocation();
|
|
|
+ const history = useHistory();
|
|
|
+ const { params: {baseRoute, namespace, chartName}} = useRouteMatch<{baseRoute: string, namespace: string, chartName: string}>();
|
|
|
+
|
|
|
+ // Context hooks
|
|
|
+ const { currentCluster, currentProject} = useContext(Context)
|
|
|
+
|
|
|
+ // Component state
|
|
|
+ const [revisions, setRevisions] = useState<ChartType[]>([]);
|
|
|
+ const [currentChart, setCurrentChart] = useState<ChartType | null>(null);
|
|
|
+ const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
|
+
|
|
|
+ // Websocket hook
|
|
|
+ const { newWebsocket, openWebsocket, closeAllWebsockets, closeWebsocket } = useWebsockets();
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ getRevisions()
|
|
|
+ .then(() => {
|
|
|
+ connectToChartLiveUpdates();
|
|
|
+ setIsLoading(false);
|
|
|
+ })
|
|
|
+
|
|
|
+ return () => closeAllWebsockets();
|
|
|
+ }, [currentCluster.id, currentProject.id])
|
|
|
+
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ setCurrentChart(revisions[0])
|
|
|
+ }, [revisions])
|
|
|
+
|
|
|
+ const getRevisions = async () => {
|
|
|
+ try {
|
|
|
+ if (currentProject && currentCluster) {
|
|
|
+ const res = await api.getRevisions(
|
|
|
"<token>",
|
|
|
- {
|
|
|
- namespace: namespace,
|
|
|
- cluster_id: currentCluster.id,
|
|
|
- storage: StorageType.Secret,
|
|
|
- },
|
|
|
- { id: currentProject.id, name: chartName }
|
|
|
- )
|
|
|
- .then((res) => {
|
|
|
- res.data.sort((a: ChartType, b: ChartType) => {
|
|
|
- return -(a.version - b.version);
|
|
|
- });
|
|
|
- let maxVersion = res.data[0].version;
|
|
|
- api
|
|
|
- .getChart(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- namespace: namespace,
|
|
|
- cluster_id: currentCluster.id,
|
|
|
- storage: StorageType.Secret,
|
|
|
- },
|
|
|
- {
|
|
|
- name: chartName,
|
|
|
- revision: maxVersion,
|
|
|
- id: currentProject.id,
|
|
|
- }
|
|
|
- )
|
|
|
- .then((res) => {
|
|
|
- this.setState({ currentChart: res.data, loading: false });
|
|
|
- })
|
|
|
- .catch((err) => {
|
|
|
- console.log("err", err.response.data);
|
|
|
- this.setState({ loading: false });
|
|
|
- });
|
|
|
- })
|
|
|
- .catch((err) => {
|
|
|
- console.log("err", err.response.data);
|
|
|
- this.setState({ loading: false });
|
|
|
+ {
|
|
|
+ namespace: namespace,
|
|
|
+ cluster_id: currentCluster.id,
|
|
|
+ storage: StorageType.Secret,
|
|
|
+ },
|
|
|
+ { id: currentProject.id, name: chartName }
|
|
|
+ );
|
|
|
+
|
|
|
+ res.data.sort((a: ChartType, b: ChartType) => {
|
|
|
+ return -(a.version - b.version);
|
|
|
});
|
|
|
- }
|
|
|
- };
|
|
|
|
|
|
- componentDidMount() {
|
|
|
- this.setState({ loading: true });
|
|
|
- this.getChartData();
|
|
|
+ setRevisions(res.data);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log("err", error.response.data);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- render() {
|
|
|
- let { setSidebar, location, match } = this.props;
|
|
|
- let { baseRoute, namespace } = match.params as any;
|
|
|
- let { loading, currentChart } = this.state;
|
|
|
- if (loading) {
|
|
|
- return <Loading />;
|
|
|
- } else if (currentChart && baseRoute === "jobs") {
|
|
|
- return (
|
|
|
- <ExpandedJobChart
|
|
|
- namespace={namespace}
|
|
|
- currentChart={currentChart}
|
|
|
- currentCluster={this.context.currentCluster}
|
|
|
- closeChart={() =>
|
|
|
- pushFiltered(this.props, "/jobs", ["project_id"], {
|
|
|
- cluster: this.context.currentCluster.name,
|
|
|
- namespace: namespace,
|
|
|
- })
|
|
|
- }
|
|
|
- setSidebar={setSidebar}
|
|
|
- />
|
|
|
- );
|
|
|
- } else if (currentChart && baseRoute === "applications") {
|
|
|
- return (
|
|
|
- <ExpandedChart
|
|
|
- namespace={namespace}
|
|
|
- isMetricsInstalled={this.props.isMetricsInstalled}
|
|
|
- currentChart={currentChart}
|
|
|
- currentCluster={this.context.currentCluster}
|
|
|
- closeChart={() =>
|
|
|
- pushFiltered(this.props, "/applications", ["project_id"], {
|
|
|
- cluster: this.context.currentCluster.name,
|
|
|
- namespace: namespace,
|
|
|
- })
|
|
|
- }
|
|
|
- setSidebar={setSidebar}
|
|
|
- />
|
|
|
- );
|
|
|
+ const connectToChartLiveUpdates = () => {
|
|
|
+ const apiPath = `/api/projects/${currentProject.id}/k8s/helm_releases?cluster_id=${currentCluster.id}&charts=${chartName}`;
|
|
|
+
|
|
|
+ const wsConfig = {
|
|
|
+ onopen: () => console.log("connected to live chart updates websocket"),
|
|
|
+ onmessage: (evt: MessageEvent) => {
|
|
|
+ const event = JSON.parse(evt.data);
|
|
|
+ // We ignore ADD events as the initial fetch of revisions will get all of those
|
|
|
+ if (event.event_type === "UPDATE") {
|
|
|
+ const object = event.Object as ChartType;
|
|
|
+ setRevisions((oldRevisions) => {
|
|
|
+ // Copy old array to clean up references
|
|
|
+ const prevRevisions = [...oldRevisions];
|
|
|
+
|
|
|
+ // Check if it's an update of a revision or if it's a new one
|
|
|
+ const revisionIndex = prevRevisions.findIndex((rev) => {
|
|
|
+ if (rev.version === object.version) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Place new one at top of the array or update the old one
|
|
|
+ if (revisionIndex > -1) {
|
|
|
+ prevRevisions.splice(revisionIndex, 1, object);
|
|
|
+ } else {
|
|
|
+ return [object, ...prevRevisions];
|
|
|
+ }
|
|
|
+
|
|
|
+ return prevRevisions;
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onclose: () => console.log("closing live chart updates websocket"),
|
|
|
+ onerror: (err: ErrorEvent) => {
|
|
|
+ console.log(err);
|
|
|
+ closeWebsocket(chartName);
|
|
|
+ }
|
|
|
}
|
|
|
- return <PageNotFound />;
|
|
|
+
|
|
|
+ newWebsocket(chartName, apiPath, wsConfig);
|
|
|
+ openWebsocket(chartName);
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-ExpandedChartWrapper.contextType = Context;
|
|
|
|
|
|
-export default withRouter(ExpandedChartWrapper);
|
|
|
+ if (isLoading) {
|
|
|
+ return <Loading />;
|
|
|
+ } else if (currentChart && baseRoute === "jobs") {
|
|
|
+ return (
|
|
|
+ <ExpandedJobChart
|
|
|
+ namespace={namespace}
|
|
|
+ currentChart={currentChart}
|
|
|
+ currentCluster={currentCluster}
|
|
|
+ closeChart={() =>
|
|
|
+ pushFiltered({location, history}, "/jobs", ["project_id"], {
|
|
|
+ cluster: currentCluster.name,
|
|
|
+ namespace: namespace,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ setSidebar={setSidebar}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ } else if (currentChart && baseRoute === "applications") {
|
|
|
+ return (
|
|
|
+ <ExpandedChart
|
|
|
+ namespace={namespace}
|
|
|
+ isMetricsInstalled={isMetricsInstalled}
|
|
|
+ currentChart={currentChart}
|
|
|
+ currentCluster={currentCluster}
|
|
|
+ closeChart={() =>
|
|
|
+ pushFiltered({location, history}, "/applications", ["project_id"], {
|
|
|
+ cluster: currentCluster.name,
|
|
|
+ namespace: namespace,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ setSidebar={setSidebar}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return <PageNotFound />;
|
|
|
+}
|
|
|
|
|
|
-const NotFoundPlaceholder = styled.div`
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
-`;
|
|
|
+export default ExpandedChartWrapper;
|