diff --git a/Environment/mock/package.json b/Environment/mock/package.json index 5f246094c..2c1e726bf 100644 --- a/Environment/mock/package.json +++ b/Environment/mock/package.json @@ -8,13 +8,18 @@ "start": "node index.js" }, "devDependencies": { - "express": "4.18.1", + "@botchris/grpc-web-mock": "0.0.7", + "@dolittle/contracts.web": "7.5.0-boromir.0", + "@dolittle/rudiments": "6.0.0", "@grafana/lezer-logql": "0.1.0", - "@prometheus-io/lezer-promql": "0.37.1", - "@lezer/lr": "1.2.3", "@lezer/highlight": "1.1.0", + "@lezer/lr": "1.2.3", + "@prometheus-io/lezer-promql": "0.37.1", + "express": "4.18.1", "express-ws": "5.0.2", - "node-pty": "0.10.1", - "jabber": "1.4.0" + "jabber": "1.4.0", + "node-pty": "0.10.1" + }, + "dependencies": { } } diff --git a/Environment/mock/proxy/index.js b/Environment/mock/proxy/index.js index d1829cf1a..c90b76ae9 100644 --- a/Environment/mock/proxy/index.js +++ b/Environment/mock/proxy/index.js @@ -4,9 +4,11 @@ const { Router } = require('express'); const shell = require('./shell'); +const runtimeManagement = require('./runtimeManagement'); const ports = Router({ mergeParams: true }); ports.use('/shell', shell); +ports.use('/runtime-management', runtimeManagement); ports.get('/:port/*', (req, res) => { const { applicationId, environment, microserviceId, port } = req.params; diff --git a/Environment/mock/proxy/runtimeManagement/artifacts.js b/Environment/mock/proxy/runtimeManagement/artifacts.js new file mode 100644 index 000000000..5b75e4f5b --- /dev/null +++ b/Environment/mock/proxy/runtimeManagement/artifacts.js @@ -0,0 +1,13 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +const { Artifact } = require('@dolittle/contracts.web/Artifacts/Artifact_pb'); +const { toUuid } = require('./guids') +module.exports = { + pbArtifact: (guidString, generation = 1) => { + const artifact = new Artifact(); + artifact.setId(toUuid(guidString)); + artifact.setGeneration(generation); + return artifact; + } +} \ No newline at end of file diff --git a/Environment/mock/proxy/runtimeManagement/client/buildResults.js b/Environment/mock/proxy/runtimeManagement/client/buildResults.js new file mode 100644 index 000000000..e7d6be896 --- /dev/null +++ b/Environment/mock/proxy/runtimeManagement/client/buildResults.js @@ -0,0 +1,35 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +const { ArtifactBuildResult, BuildResult, BuildResults } = require('@dolittle/contracts.web/Runtime/Client/BuildResult_pb'); +const {GetBuildResultsResponse } = require('@dolittle/contracts.web/Runtime/Management/Client/Client_pb'); +const { pbArtifact } = require('../artifacts') +const { respondWith } = require('../responses') +module.exports = { + emptyBuildResults: () => { + return new BuildResults(); + }, + artifactBuildResult: (idString, alias = '', generation = 1, buildResult = undefined) => { + const result = new ArtifactBuildResult(); + result.setAritfact(pbArtifact(idString, generation)); + result.setAlias(alias); + result.setBuildresult(buildResult); + return result; + }, + buildResult: (message, type) => { + const result = new BuildResult(); + result.setMessage(message); + result.setType(type); + return result; + }, + respondWith: (res, buildResults) => { + const response = new GetBuildResultsResponse(); + response.setBuildresults(buildResults); + respondWith(res, response); + }, + type: { + Information: BuildResult.Type.INFORMATION, + Failure: BuildResult.Type.FAILURE, + Error: BuildResult.Type.ERROR, + } +} \ No newline at end of file diff --git a/Environment/mock/proxy/runtimeManagement/client/index.js b/Environment/mock/proxy/runtimeManagement/client/index.js new file mode 100644 index 000000000..327cbc9c7 --- /dev/null +++ b/Environment/mock/proxy/runtimeManagement/client/index.js @@ -0,0 +1,57 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +const { Router } = require('express'); +const { artifactBuildResult, buildResult, emptyBuildResults, respondWith, type } = require('./buildResults'); +const routes = module.exports = Router({ mergeParams: true }); + +const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +function generateString(length) { + let result = ' '; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + + return result; +} + +const messages = { + short: () => generateString(20), + long: () => generateString(200) +} + +const createArtifactBuildResults = () => [ + artifactBuildResult('72426059-f47c-4311-86c8-2a4e47fa2a4a', 'Some Alias', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('72426059-f47c-4311-86c8-2a4e47fa2a4a', 'Some Alias', 1, buildResult(messages.long(), type.Information)), + artifactBuildResult('b46d86d7-7c10-40dd-a4ee-d9d05259c676', 'With Error', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('b46d86d7-7c10-40dd-a4ee-d9d05259c676', 'With Error', 1, buildResult(messages.long(), type.Error)), + artifactBuildResult('6eec65ea-71ac-4fe4-b74c-a42de5f9c4b9', 'With Failure', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('6eec65ea-71ac-4fe4-b74c-a42de5f9c4b9', 'With Failure', 1, buildResult(messages.long(), type.Failure)), + artifactBuildResult('bc226a57-5699-4923-9600-67da7e153784', 'Same Alias As Another', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('e62f7431-19b3-41f0-a6e3-bced556e510d', 'Same Alias As Another', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('5b79b500-b43a-4805-84cb-14740317b45d', 'Different Generations', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('5b79b500-b43a-4805-84cb-14740317b45d', 'Different Generations', 2, buildResult(messages.short(), type.Information)), + artifactBuildResult('31e18f24-aa1a-448a-b0a2-a82f1b825edf', 'Different Aliases1', 1, buildResult(messages.short(), type.Information)), + artifactBuildResult('31e18f24-aa1a-448a-b0a2-a82f1b825edf', 'Different Aliases2', 1, buildResult(messages.short(), type.Information)), +]; + +const other = [ + buildResult(messages.short(), type.Information), + buildResult(messages.short(), type.Error), + buildResult(messages.short(), type.Failure), + buildResult(messages.long(), type.Information), + buildResult(messages.long(), type.Error), + buildResult(messages.long(), type.Failure), +]; +routes.post('/GetBuildResults', (req, res) => { + const buildResults = emptyBuildResults(); + buildResults.setEventtypesList(createArtifactBuildResults()); + buildResults.setAggregaterootsList(createArtifactBuildResults()); + buildResults.setEmbeddingsList(createArtifactBuildResults()); + buildResults.setEventhandlersList(createArtifactBuildResults()); + buildResults.setFiltersList(createArtifactBuildResults()); + buildResults.setProjectionsList(createArtifactBuildResults()); + buildResults.setOtherList(other); + respondWith(res, buildResults); +}); diff --git a/Environment/mock/proxy/runtimeManagement/guids.js b/Environment/mock/proxy/runtimeManagement/guids.js new file mode 100644 index 000000000..e2ef75f7f --- /dev/null +++ b/Environment/mock/proxy/runtimeManagement/guids.js @@ -0,0 +1,13 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +const { Uuid } = require('@dolittle/contracts.web/Protobuf/Uuid_pb'); +const { Guid } = require('@dolittle/rudiments'); + +module.exports = { + toUuid: (guidString) => { + const uuid = new Uuid(); + uuid.setValue(new Uint8Array(Guid.parse(guidString).bytes)); + return uuid; + } +} \ No newline at end of file diff --git a/Environment/mock/proxy/runtimeManagement/index.js b/Environment/mock/proxy/runtimeManagement/index.js new file mode 100644 index 000000000..0d68f5557 --- /dev/null +++ b/Environment/mock/proxy/runtimeManagement/index.js @@ -0,0 +1,15 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +const { Router } = require('express'); +const routes = module.exports = Router({ mergeParams: true }); +const { ToTextResponse } = require('@botchris/grpc-web-mock'); +const client = require('./client'); + +routes.get('/*', (req, res) => { + console.log('Getting RUntime Management Proxy'); + res.status(200).end(); +}) + +routes.use('/dolittle.runtime.client.management.Client', client); + diff --git a/Environment/mock/proxy/runtimeManagement/responses.js b/Environment/mock/proxy/runtimeManagement/responses.js new file mode 100644 index 000000000..58e5f6cca --- /dev/null +++ b/Environment/mock/proxy/runtimeManagement/responses.js @@ -0,0 +1,13 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +const { ToTextResponse } = require('@botchris/grpc-web-mock'); + +module.exports = { + respondWith: (res, protobuf) => { + const result = ToTextResponse(protobuf); + res.set(result.headers) + .status(result.statusCode) + .send(result.body); + } +} \ No newline at end of file diff --git a/Source/DesignSystem/atoms/Tabs/Tabs.stories.tsx b/Source/DesignSystem/atoms/Tabs/Tabs.stories.tsx index 4d6f9e197..0fd7e010b 100644 --- a/Source/DesignSystem/atoms/Tabs/Tabs.stories.tsx +++ b/Source/DesignSystem/atoms/Tabs/Tabs.stories.tsx @@ -11,6 +11,7 @@ const { metadata, createStory } = componentStories(Tabs); export default metadata; export const Normal = createStory({ + id: 'Tabs', tabs: [ { label: 'First tab', diff --git a/Source/DesignSystem/atoms/Tabs/Tabs.tsx b/Source/DesignSystem/atoms/Tabs/Tabs.tsx index e5f55023b..faea24d75 100644 --- a/Source/DesignSystem/atoms/Tabs/Tabs.tsx +++ b/Source/DesignSystem/atoms/Tabs/Tabs.tsx @@ -40,20 +40,26 @@ export type Tab = { export type TabsProps = { tabs: Tab[]; + id?: string, sx?: any; }; export const Tabs = (props: TabsProps) => { + const getCacheKey = () => `selectedTab#${props.id}`; const [currentTab, setCurrentTab] = useState(0); - useEffect(() => { - const storedSelectedOption = parseInt(sessionStorage.getItem('selectedTab') || '0'); - setCurrentTab(storedSelectedOption); - }, []); + if (!!props.id) { + useEffect(() => { + const storedSelectedOption = parseInt(sessionStorage.getItem(getCacheKey()) || '0'); + setCurrentTab(storedSelectedOption); + }, []); + } const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { setCurrentTab(newValue); - sessionStorage.setItem('selectedTab', newValue.toString()); + if (!!props.id) { + sessionStorage.setItem(getCacheKey(), newValue.toString()); + } }; return ( diff --git a/Source/SelfService/Web/microservice/microserviceView/management/Protobuf.ts b/Source/SelfService/Web/microservice/microserviceView/management/Protobuf.ts new file mode 100644 index 000000000..910213ce4 --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/Protobuf.ts @@ -0,0 +1,21 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { Uuid } from '@dolittle/contracts.web/Protobuf/Uuid_pb'; +import { Artifact as PbArtifact } from '@dolittle/contracts.web/Artifacts/Artifact_pb'; +import { Guid } from '@dolittle/rudiments'; +import { Artifact } from './Types'; + +export function toGuid(uuid: Uuid) { + return new Guid(uuid.getValue_asU8()); +} + +export function toUuid(guid: Guid | string) { + const uuid = new Uuid(); + uuid.setValue(new Uint8Array(Guid.as(guid).bytes)); + return uuid; +} + +export function toArtifact(artifact: PbArtifact): Artifact { + return {generation: artifact.getGeneration(), id: toGuid(artifact.getId()!).toString()}; +} diff --git a/Source/SelfService/Web/microservice/microserviceView/management/Types.ts b/Source/SelfService/Web/microservice/microserviceView/management/Types.ts new file mode 100644 index 000000000..06b369e6e --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/Types.ts @@ -0,0 +1,7 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +export type Artifact = { + id: string, + generation: number +}; diff --git a/Source/SelfService/Web/microservice/microserviceView/management/View.tsx b/Source/SelfService/Web/microservice/microserviceView/management/View.tsx new file mode 100644 index 000000000..de02ed2d4 --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/View.tsx @@ -0,0 +1,25 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import React, {} from 'react'; + +import { View as BuildResults} from './buildResults/View'; +import { useRuntimeManagementAvailable } from './useRuntimeManagement'; + +export type ViewProps = { + applicationId: string; + environment: string; + microserviceId: string; +}; + +export const View = (props: ViewProps) => { + const buildResultsAvailable = useRuntimeManagementAvailable(props.applicationId, props.environment, props.microserviceId); + return ( + <> + {buildResultsAvailable + ? + :

There are not any management endpoints on your microservice.

+ } + + ); +}; diff --git a/Source/SelfService/Web/microservice/microserviceView/management/buildResults/ArtifactsResults.tsx b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/ArtifactsResults.tsx new file mode 100644 index 000000000..8611878ac --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/ArtifactsResults.tsx @@ -0,0 +1,97 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import React, {} from 'react'; +import { Typography, IconButton } from '@mui/material'; +import { KeyboardArrowUp, KeyboardArrowDown } from '@mui/icons-material'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; + +import { ArtifactResults, BuildResult } from './BuildResults'; + +export type ArtifactsResultsProps = { + results: ArtifactResults, + type: string +}; + +function toCombinedKey(id: string, alias: string, generation: number) { + return `${id}/${alias}/${generation}`; +} + +function toGroupedResults(results: ArtifactResults) { + const result = new Map(); + results.forEach(_ => { + const combinedKey = toCombinedKey(_.artifact.id, _.alias, _.artifact.generation); + let item = result.get(combinedKey); + if (item === undefined) { + item = {id: _.artifact.id, alias: _.alias, generation: _.artifact.generation, buildResults: []}; + } + item.buildResults.push(_.buildResult); + result.set(combinedKey, item); + }); + return result; +} + +const ExpandableRow = ({children, buildMessages: buildResults}: {children: JSX.Element[], buildMessages: BuildResult[]}) => { + const [isExpanded, setIsExpanded] = React.useState(false); + return ( + <> + + + setIsExpanded(!isExpanded)}> + {isExpanded ? : } + + + {children} + + {isExpanded && buildResults.map((result, i) => ( + + + {`${result.type}: ${result.message}`} + + ))} + + ); +}; + +export const ArtifactsResultsView = (props: ArtifactsResultsProps) => { + if (!props.results?.length) { + return <>; + } + + const results = toGroupedResults(props.results); + return ( + <> + {props.type} + + + + + + Alias + Id + Generation + + + + {Array.from(results, ([combinedKey, {id, alias, generation, buildResults}]) => ( + + _.isFailed) === -1 ? undefined : 'red'}} align="left">{alias ?? 'No Alias'} + {id} + {generation} + + ))} + +
+
+ + ); +}; + diff --git a/Source/SelfService/Web/microservice/microserviceView/management/buildResults/BuildResults.ts b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/BuildResults.ts new file mode 100644 index 000000000..ceab8e23f --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/BuildResults.ts @@ -0,0 +1,58 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { Artifact } from '../Types'; +import { getBuildResults as pbGetBuildResults, toArtifactResult, toBuildResult } from './Protobuf'; + +export type BuildResults = { + other: OtherResults; + eventTypes: ArtifactResults; + aggregateRoots: ArtifactResults; + eventHandlers: ArtifactResults; + projections: ArtifactResults; + embeddings: ArtifactResults; + filters: ArtifactResults; +}; + +export type BuildResult = { + type: string, + message: string, + isFailed: boolean +}; + +export type OtherResults = BuildResult[]; + +export type ArtifactResult = { + alias: string | '', + buildResult: BuildResult, + artifact: Artifact +}; + +export type ArtifactResults = ArtifactResult[]; + +export const emptyBuildResults: BuildResults = { + other: [], + aggregateRoots: [], + embeddings: [], + eventHandlers: [], + eventTypes: [], + filters: [], + projections: [] +}; + +export async function getBuildResults(url: string): Promise { + const response = await pbGetBuildResults(url); + const results = response.getBuildresults(); + if (results === undefined) { + throw new Error('Build results is empty'); + } + return { + other: results.getOtherList().map(toBuildResult), + aggregateRoots: results.getAggregaterootsList().map(toArtifactResult), + embeddings: results.getEmbeddingsList().map(toArtifactResult), + eventHandlers: results.getEventhandlersList().map(toArtifactResult), + eventTypes: results.getEventtypesList().map(toArtifactResult), + filters: results.getFiltersList().map(toArtifactResult), + projections: results.getProjectionsList().map(toArtifactResult), + }; +} diff --git a/Source/SelfService/Web/microservice/microserviceView/management/buildResults/OtherResults.tsx b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/OtherResults.tsx new file mode 100644 index 000000000..9067bafc6 --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/OtherResults.tsx @@ -0,0 +1,38 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +import React from 'react'; +import { Typography } from '@mui/material'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableRow from '@mui/material/TableRow'; +import { BuildResult } from './BuildResults'; + +export type OtherResultsProps = { + results: BuildResult[] +}; + +export const OtherResultsView = (props: OtherResultsProps) => { + if (!props.results?.length) { + return <>; + } + const results = props.results; + return ( + <> + Other + + + + {results.map((result, i) => ( + + {`${result.type}: ${result.message}`} + + ))} + +
+
+ + ); +}; + diff --git a/Source/SelfService/Web/microservice/microserviceView/management/buildResults/Protobuf.ts b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/Protobuf.ts new file mode 100644 index 000000000..51060f703 --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/Protobuf.ts @@ -0,0 +1,37 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { BuildResult as PbBuildResult, ArtifactBuildResult as PbArtifactBuildResult, } from '@dolittle/contracts.web/Runtime/Client/BuildResult_pb'; +import { ClientPromiseClient } from '@dolittle/contracts.web/Runtime/Management/Client/Client_grpc_web_pb'; +import { GetBuildResultsRequest, GetBuildResultsResponse } from '@dolittle/contracts.web/Runtime/Management/Client/Client_pb'; +import { toArtifact } from '../Protobuf'; +import { ArtifactResult, BuildResult } from './BuildResults'; + +const buildResultsResponseCache: Map = new Map(); + +export async function getBuildResults(url: string): Promise { + if (buildResultsResponseCache.has(url)) { + return buildResultsResponseCache.get(url)!; + } + + const response = await new ClientPromiseClient(url).getBuildResults(new GetBuildResultsRequest()); + buildResultsResponseCache.set(url, response); + return response; +} + +export function toBuildResult(pb: PbBuildResult): BuildResult { + const typeString = Object.entries(PbBuildResult.Type).find(_ => _[1] === pb.getType())?.[0]!; + return { + message: pb.getMessage(), + type: typeString, + isFailed: pb.getType() !== PbBuildResult.Type.INFORMATION + }; +} + +export function toArtifactResult(pb: PbArtifactBuildResult): ArtifactResult { + return { + buildResult: toBuildResult(pb.getBuildresult()!), + alias: pb.getAlias(), + artifact: toArtifact(pb.getAritfact()!) + }; +} diff --git a/Source/SelfService/Web/microservice/microserviceView/management/buildResults/View.tsx b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/View.tsx new file mode 100644 index 000000000..c9cabd10f --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/View.tsx @@ -0,0 +1,29 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import React from 'react'; + +import { useBuildResults } from './useBuildResults'; +import { ArtifactsResultsView } from './ArtifactsResults'; +import { OtherResultsView } from './OtherResults'; + +export type ViewProps = { + applicationId: string; + environment: string; + microserviceId: string; +}; + +export const View = (props: ViewProps) => { + const buildResults = useBuildResults(props.applicationId, props.environment, props.microserviceId); + return ( + <> + + + + + + + + + ); +}; diff --git a/Source/SelfService/Web/microservice/microserviceView/management/buildResults/useBuildResults.ts b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/useBuildResults.ts new file mode 100644 index 000000000..badfe92da --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/buildResults/useBuildResults.ts @@ -0,0 +1,46 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { useState, useEffect } from 'react'; +import { useRuntimeManagementUrl } from '../useRuntimeManagement'; +import { BuildResults, emptyBuildResults, getBuildResults } from './BuildResults'; +import { getBuildResults as pbGetBuildResults } from './Protobuf'; + +export const useBuildResults = (applicationId: string, environment: string, microserviceId: string): BuildResults => { + const url = useRuntimeManagementUrl(applicationId, environment, microserviceId); + const [buildResults, setBuildResults] = useState({} as BuildResults); + useEffect(() => { + getBuildResults(url) + .then(resp => { + setBuildResults(resp); + }) + .catch(err => { + console.error(err); + setBuildResults(emptyBuildResults); + }); + }, [url]); + + return buildResults; +}; + + +export const useBuildResultsAvailable = (applicationId: string, environment: string, microserviceId: string): boolean => { + const url = useRuntimeManagementUrl(applicationId, environment, microserviceId); + const [available, setAvailable] = useState(false); + useEffect(() => { + let aborted = false; + pbGetBuildResults(url) + .then(response => { + if (aborted) return; + setAvailable(!!response.getBuildresults() && true); + }) + .catch(() => { + if (aborted) return; + setAvailable(false); + }); + + return () => { aborted = true; }; + }, [url]); + + return available; +}; diff --git a/Source/SelfService/Web/microservice/microserviceView/management/useRuntimeManagement.ts b/Source/SelfService/Web/microservice/microserviceView/management/useRuntimeManagement.ts new file mode 100644 index 000000000..8bec91042 --- /dev/null +++ b/Source/SelfService/Web/microservice/microserviceView/management/useRuntimeManagement.ts @@ -0,0 +1,17 @@ +// Copyright (c) Dolittle. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { useMemo } from 'react'; +import { useBuildResultsAvailable } from './buildResults/useBuildResults'; + +export const useRuntimeManagementUrl = (applicationId: string, environment: string, microserviceId: string): string => { + const url = useMemo( + () => `/proxy/${applicationId}/${environment}/${microserviceId}/runtime-management`, + [applicationId, environment, microserviceId]); + + return url; +}; + +export const useRuntimeManagementAvailable = (applicationId: string, environment: string, microserviceId: string): boolean => { + return useBuildResultsAvailable(applicationId, environment, microserviceId); +}; diff --git a/Source/SelfService/Web/microservice/microserviceView/microserviceView.tsx b/Source/SelfService/Web/microservice/microserviceView/microserviceView.tsx index f92281812..611c3b87a 100644 --- a/Source/SelfService/Web/microservice/microserviceView/microserviceView.tsx +++ b/Source/SelfService/Web/microservice/microserviceView/microserviceView.tsx @@ -17,6 +17,8 @@ import { HealthStatus } from './healthStatus/healthStatus'; import { ContainerHealthStatus } from '../microserviceStatus'; import { useTerminalAvailable } from './terminal/useTerminal'; import { View as Terminal } from './terminal/View'; +import { View as Management } from './management/View'; +import { useRuntimeManagementAvailable } from './management/useRuntimeManagement'; type MicroserviceViewProps = { application: HttpResponseApplication; @@ -108,9 +110,21 @@ export const MicroserviceView = ({ application, microserviceId, environment, pod microserviceId={microserviceId} data={podsData} /> - } + }, ]; + const managementAvailable = useRuntimeManagementAvailable(applicationId, environment, microserviceId); + if (managementAvailable) { + tabs.push({ + label: 'Management', + render: () => + }); + } + const terminalAvailable = useTerminalAvailable(applicationId, environment, microserviceId); if (terminalAvailable) { tabs.push( @@ -132,7 +146,7 @@ export const MicroserviceView = ({ application, microserviceId, environment, pod - + ); }; diff --git a/Source/SelfService/Web/package.json b/Source/SelfService/Web/package.json index cecf97018..c92e24e07 100644 --- a/Source/SelfService/Web/package.json +++ b/Source/SelfService/Web/package.json @@ -18,6 +18,7 @@ "@date-io/date-fns": "2.15.0", "@dolittle/design-system": "1.0.0", "@dolittle/rudiments": "5.0.1", + "@dolittle/contracts.web": "7.5.0-boromir.0", "@monaco-editor/react": "4.4.5", "@shared/styling": "1.0.0", "@shared/web": "1.0.0", diff --git a/Source/SelfService/Web/webpack.config.ts b/Source/SelfService/Web/webpack.config.ts index 5abb8361b..d77518614 100644 --- a/Source/SelfService/Web/webpack.config.ts +++ b/Source/SelfService/Web/webpack.config.ts @@ -101,10 +101,15 @@ function webpack(env: Args, argv: Args) { }, ws: true, }, + // '/proxy/11b6cf47-5d9f-438f-8116-0d9828654657/Dev/7e78b802-e246-467b-9946-1deabf8042ef/runtime-management': { + // target: 'http://localhost:51152', + // pathRewrite: { '^/proxy/11b6cf47-5d9f-438f-8116-0d9828654657/Dev/7e78b802-e246-467b-9946-1deabf8042ef/runtime-management': '' }, + // }, '/proxy': { target: 'http://localhost:3007', ws: true, - } + }, + }, before: (app, server, compiler) => {} },