mirror of https://github.com/linkerd/linkerd2.git
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:
parent
8c9930b4ac
commit
fb0b1c898e
|
@ -34,6 +34,17 @@ const styles = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const installedExtensionsColumn = [
|
||||||
|
{
|
||||||
|
title: <Trans>columnTitleName</Trans>,
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <Trans>columnTitleNamespace</Trans>,
|
||||||
|
dataIndex: 'namespace',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const serviceMeshDetailsColumns = [
|
const serviceMeshDetailsColumns = [
|
||||||
{
|
{
|
||||||
title: <Trans>columnTitleName</Trans>,
|
title: <Trans>columnTitleName</Trans>,
|
||||||
|
@ -72,6 +83,7 @@ class ServiceMesh extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
pollingInterval: 2000,
|
pollingInterval: 2000,
|
||||||
components: [],
|
components: [],
|
||||||
|
extensions: [],
|
||||||
nsStatuses: [],
|
nsStatuses: [],
|
||||||
pendingRequests: false,
|
pendingRequests: false,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
|
@ -81,6 +93,7 @@ class ServiceMesh extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.startServerPolling();
|
this.startServerPolling();
|
||||||
|
this.fetchAllInstalledExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
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 => {
|
getControllerComponentData = podData => {
|
||||||
const podDataByDeploy = _groupBy(_filter(podData.pods, d => d.controlPlane), p => p.deployment);
|
const podDataByDeploy = _groupBy(_filter(podData.pods, d => d.controlPlane), p => p.deployment);
|
||||||
const byDeployName = _mapKeys(podDataByDeploy, (_pods, dep) => dep.split('/')[1]);
|
const byDeployName = _mapKeys(podDataByDeploy, (_pods, dep) => dep.split('/')[1]);
|
||||||
|
@ -190,6 +209,15 @@ class ServiceMesh extends React.Component {
|
||||||
.catch(this.handleApiError);
|
.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) {
|
handleApiError(e) {
|
||||||
if (e.isCanceled) {
|
if (e.isCanceled) {
|
||||||
return;
|
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() {
|
renderServiceMeshDetails() {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -298,6 +343,7 @@ class ServiceMesh extends React.Component {
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={8} container direction="column">
|
<Grid item xs={8} container direction="column">
|
||||||
<Grid item>{this.renderControlPlaneDetails()}</Grid>
|
<Grid item>{this.renderControlPlaneDetails()}</Grid>
|
||||||
|
<Grid item>{this.renderInstalledExtensions()}</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<MeshedStatusTable tableRows={nsStatuses} />
|
<MeshedStatusTable tableRows={nsStatuses} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -61,7 +61,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
||||||
const servicesPath = '/api/services';
|
const servicesPath = '/api/services';
|
||||||
const edgesPath = '/api/edges';
|
const edgesPath = '/api/edges';
|
||||||
const gatewaysPath = '/api/gateways';
|
const gatewaysPath = '/api/gateways';
|
||||||
const l5dExtensionsPath = '/api/extension';
|
const l5dExtensionsPath = '/api/extensions';
|
||||||
|
|
||||||
const validMetricsWindows = {
|
const validMetricsWindows = {
|
||||||
'10s': '10 minutes',
|
'10s': '10 minutes',
|
||||||
|
@ -147,7 +147,11 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchExtension = name => {
|
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) => {
|
const fetchResourceDefinition = (namespace, resourceType, resourceName) => {
|
||||||
|
|
|
@ -434,23 +434,54 @@ func (h *handler) handleAPIResourceDefinition(w http.ResponseWriter, req *http.R
|
||||||
w.Write(resourceDefinition)
|
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()
|
ctx := req.Context()
|
||||||
extensionName := req.FormValue("extension_name")
|
extensionName := req.FormValue("extension_name")
|
||||||
|
|
||||||
|
type Extension struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
resp := map[string]interface{}{}
|
resp := map[string]interface{}{}
|
||||||
ns, err := h.k8sAPI.GetNamespaceWithExtensionLabel(ctx, extensionName)
|
if extensionName != "" {
|
||||||
if err != nil && strings.HasPrefix(err.Error(), "could not find") {
|
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)
|
renderJSON(w, resp)
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
}
|
||||||
|
|
||||||
|
installedExtensions, err := h.k8sAPI.GetAllNamespacesWithExtensionLabel(ctx)
|
||||||
|
if err != nil {
|
||||||
renderJSONError(w, err, http.StatusInternalServerError)
|
renderJSONError(w, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp["extensionName"] = ns.GetLabels()[k8s.LinkerdExtensionLabel]
|
extensionList := make([]Extension, len(installedExtensions))
|
||||||
resp["namespace"] = ns.Name
|
|
||||||
|
|
||||||
|
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)
|
renderJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,7 @@ func NewServer(
|
||||||
server.router.GET("/api/check", handler.handleAPICheck)
|
server.router.GET("/api/check", handler.handleAPICheck)
|
||||||
server.router.GET("/api/resource-definition", handler.handleAPIResourceDefinition)
|
server.router.GET("/api/resource-definition", handler.handleAPIResourceDefinition)
|
||||||
server.router.GET("/api/gateways", handler.handleAPIGateways)
|
server.router.GET("/api/gateways", handler.handleAPIGateways)
|
||||||
server.router.GET("/api/extension", handler.handleGetExtension)
|
server.router.GET("/api/extensions", handler.handleGetExtensions)
|
||||||
|
|
||||||
// grafana proxy
|
// grafana proxy
|
||||||
server.handleAllOperationsForPath("/grafana/*grafanapath", handler.handleGrafana)
|
server.handleAllOperationsForPath("/grafana/*grafanapath", handler.handleGrafana)
|
||||||
|
|
Loading…
Reference in New Issue