Implement get all installed extensions (#6391)

Introduce new table in the control plane view of the dashboard to display installed extensions.

Signed-off-by: Sanni Michael <sannimichaelse@gmail.com>
This commit is contained in:
Sanni Michael 2021-08-16 10:51:29 +01:00 committed by GitHub
parent 8c9930b4ac
commit fb0b1c898e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 9 deletions

View File

@ -34,6 +34,17 @@ const styles = {
},
};
const installedExtensionsColumn = [
{
title: <Trans>columnTitleName</Trans>,
dataIndex: 'name',
},
{
title: <Trans>columnTitleNamespace</Trans>,
dataIndex: 'namespace',
},
];
const serviceMeshDetailsColumns = [
{
title: <Trans>columnTitleName</Trans>,
@ -72,6 +83,7 @@ class ServiceMesh extends React.Component {
this.state = {
pollingInterval: 2000,
components: [],
extensions: [],
nsStatuses: [],
pendingRequests: false,
loaded: false,
@ -81,6 +93,7 @@ class ServiceMesh extends React.Component {
componentDidMount() {
this.startServerPolling();
this.fetchAllInstalledExtensions();
}
componentDidUpdate(prevProps) {
@ -109,6 +122,12 @@ class ServiceMesh extends React.Component {
];
}
getInstalledExtensions() {
const { extensions } = this.state;
const extensionList = !_isEmpty(extensions.extensions) ? extensions.extensions : [];
return extensionList;
}
getControllerComponentData = podData => {
const podDataByDeploy = _groupBy(_filter(podData.pods, d => d.controlPlane), p => p.deployment);
const byDeployName = _mapKeys(podDataByDeploy, (_pods, dep) => dep.split('/')[1]);
@ -190,6 +209,15 @@ class ServiceMesh extends React.Component {
.catch(this.handleApiError);
}
fetchAllInstalledExtensions() {
this.api.setCurrentRequests([this.api.fetchExtension()]);
this.serverPromise = Promise.all(this.api.getCurrentPromises())
.then(([extensions]) => {
this.setState({ extensions });
})
.catch(this.handleApiError);
}
handleApiError(e) {
if (e.isCanceled) {
return;
@ -234,6 +262,23 @@ class ServiceMesh extends React.Component {
);
}
renderInstalledExtensions() {
return (
<React.Fragment>
<Grid container justify="space-between">
<Grid item xs={3}>
<Typography variant="h6"><Trans>Installed Extensions</Trans></Typography>
</Grid>
</Grid>
<BaseTable
tableClassName="metric-table"
tableRows={this.getInstalledExtensions()}
tableColumns={installedExtensionsColumn}
rowKey={d => d.uid} />
</React.Fragment>
);
}
renderServiceMeshDetails() {
return (
<React.Fragment>
@ -298,6 +343,7 @@ class ServiceMesh extends React.Component {
<Grid container spacing={3}>
<Grid item xs={8} container direction="column">
<Grid item>{this.renderControlPlaneDetails()}</Grid>
<Grid item>{this.renderInstalledExtensions()}</Grid>
<Grid item>
<MeshedStatusTable tableRows={nsStatuses} />
</Grid>

View File

@ -61,7 +61,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
const servicesPath = '/api/services';
const edgesPath = '/api/edges';
const gatewaysPath = '/api/gateways';
const l5dExtensionsPath = '/api/extension';
const l5dExtensionsPath = '/api/extensions';
const validMetricsWindows = {
'10s': '10 minutes',
@ -147,7 +147,11 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
};
const fetchExtension = name => {
return apiFetch(`${l5dExtensionsPath}?extension_name=${name}`);
let extensionPath = l5dExtensionsPath;
if (name) {
extensionPath += `?extension_name=${name}`;
}
return apiFetch(extensionPath);
};
const fetchResourceDefinition = (namespace, resourceType, resourceName) => {

View File

@ -434,23 +434,54 @@ func (h *handler) handleAPIResourceDefinition(w http.ResponseWriter, req *http.R
w.Write(resourceDefinition)
}
func (h *handler) handleGetExtension(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
func (h *handler) handleGetExtensions(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
ctx := req.Context()
extensionName := req.FormValue("extension_name")
type Extension struct {
Name string `json:"name"`
UID string `json:"uid"`
Namespace string `json:"namespace"`
}
resp := map[string]interface{}{}
ns, err := h.k8sAPI.GetNamespaceWithExtensionLabel(ctx, extensionName)
if err != nil && strings.HasPrefix(err.Error(), "could not find") {
if extensionName != "" {
ns, err := h.k8sAPI.GetNamespaceWithExtensionLabel(ctx, extensionName)
if err != nil && strings.HasPrefix(err.Error(), "could not find") {
renderJSON(w, resp)
return
} else if err != nil {
renderJSONError(w, err, http.StatusInternalServerError)
return
}
resp["data"] = Extension{
UID: string(ns.UID),
Name: ns.GetLabels()[k8s.LinkerdExtensionLabel],
Namespace: ns.Name,
}
renderJSON(w, resp)
return
} else if err != nil {
}
installedExtensions, err := h.k8sAPI.GetAllNamespacesWithExtensionLabel(ctx)
if err != nil {
renderJSONError(w, err, http.StatusInternalServerError)
return
}
resp["extensionName"] = ns.GetLabels()[k8s.LinkerdExtensionLabel]
resp["namespace"] = ns.Name
extensionList := make([]Extension, len(installedExtensions))
for i, installedExtension := range installedExtensions {
extensionList[i] = Extension{
UID: string(installedExtension.GetObjectMeta().GetUID()),
Name: installedExtension.GetLabels()[k8s.LinkerdExtensionLabel],
Namespace: installedExtension.GetName(),
}
}
resp["extensions"] = extensionList
renderJSON(w, resp)
}

View File

@ -196,7 +196,7 @@ func NewServer(
server.router.GET("/api/check", handler.handleAPICheck)
server.router.GET("/api/resource-definition", handler.handleAPIResourceDefinition)
server.router.GET("/api/gateways", handler.handleAPIGateways)
server.router.GET("/api/extension", handler.handleGetExtension)
server.router.GET("/api/extensions", handler.handleGetExtensions)
// grafana proxy
server.handleAllOperationsForPath("/grafana/*grafanapath", handler.handleGrafana)