From 84bd92e8d6690c16d6c5fc6087674edd007ef484 Mon Sep 17 00:00:00 2001 From: Tin Geber <tin@greenhost.nl> Date: Mon, 29 May 2023 17:27:43 +0200 Subject: [PATCH] added version info, pretty icons, style tweaks --- backend/areas/__init__.py | 56 +++++++++++++++++++ backend/helpers/kubernetes.py | 27 +++++++++ frontend/src/modules/dashboard/Dashboard.tsx | 15 +++++ .../DashboardCard/DashboardCard.tsx | 7 +-- 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/backend/areas/__init__.py b/backend/areas/__init__.py index b90dfee2..f14dde52 100644 --- a/backend/areas/__init__.py +++ b/backend/areas/__init__.py @@ -1,6 +1,9 @@ from flask import Blueprint, jsonify +import yaml from config import * +import helpers.kubernetes as k8s +import requests api_v1 = Blueprint("api_v1", __name__, url_prefix="/api/v1") @@ -17,3 +20,56 @@ def api_environment(): "KRATOS_PUBLIC_URL": KRATOS_PUBLIC_URL, } return jsonify(environment) + +# We want to know if +# 1. A release has happened recently and is already deployed on this cluster. +# 2. A release has happened recently but has not yet been deployed on this +# cluster -- that will then probably happen automatically during the next +# maintenance window. +# +# To get the last release, we get the contents of the `VERSION` file from +# the main branch. The `VERSION` file is only updated as part of the release +# process. +# +# To find out how long ago the currently running version was deployed, we look +# at the `lastUpdateTime` of the stackspin `GitRepo` object on the cluster. +@api_v1.route("/info") +def api_info(): + # Get static info from configmap on cluster. + static_info = k8s.get_kubernetes_config_map_data( + "stackspin-static-info", + "flux-system", + ) + results = static_info + + # Get app versions from apps configmaps on cluster. + results['appVersions'] = {} + apps = k8s.get_kubernetes_config_map_data( + "stackspin-apps", + "flux-system", + ) + for app, app_data in apps.items(): + data = yaml.safe_load(app_data) + if 'version' in data: + results['appVersions'][app] = data['version'] + apps_custom = k8s.get_kubernetes_config_map_data( + "stackspin-apps-custom", + "flux-system", + ) + if apps_custom is not None: + for app, app_data in apps_custom.items(): + data = yaml.safe_load(app_data) + if 'version' in data: + results['appVersions'][app] = data['version'] + + # Get latest released version from gitlab. + git_release = requests.get("https://open.greenhost.net/stackspin/stackspin/-/raw/main/VERSION").text.rstrip() + results['lastRelease'] = git_release + + # Get last update time of stackspin GitRepo object on the cluster; that + # tells us when flux last updated the cluster based on changes in the + # stackspin git repo. + stackspin_repo = k8s.get_gitrepo('stackspin') + results['lastUpdated'] = stackspin_repo['status']['artifact']['lastUpdateTime'] + + return jsonify(results) diff --git a/backend/helpers/kubernetes.py b/backend/helpers/kubernetes.py index cba600ef..1d54caaa 100644 --- a/backend/helpers/kubernetes.py +++ b/backend/helpers/kubernetes.py @@ -382,3 +382,30 @@ def get_kustomization(name, namespace='flux-system'): # Raise all non-404 errors raise error return resource + +def get_gitrepo(name, namespace='flux-system'): + """ + Returns all info on a Flux GitRepo. + + :param name: Name of the gitrepo + :type name: string + :param namespace: Namespace of the gitrepo + :type namespace: string + :return: gitrepo as returned by the API + :rtype: dict + """ + api = client.CustomObjectsApi() + try: + resource = api.get_namespaced_custom_object( + group="source.toolkit.fluxcd.io", + version="v1beta2", + name=name, + namespace=namespace, + plural="gitrepositories", + ) + except client.exceptions.ApiException as error: + if error.status == 404: + return None + # Raise all non-404 errors + raise error + return resource diff --git a/frontend/src/modules/dashboard/Dashboard.tsx b/frontend/src/modules/dashboard/Dashboard.tsx index 04f16bc4..1616b7ab 100644 --- a/frontend/src/modules/dashboard/Dashboard.tsx +++ b/frontend/src/modules/dashboard/Dashboard.tsx @@ -9,12 +9,20 @@ import React, { useEffect } from 'react'; import { useApps } from 'src/services/apps'; import { AppStatusEnum } from 'src/services/apps/types'; +import { ExclamationCircleIcon, CheckCircleIcon, NewspaperIcon } from '@heroicons/react/outline'; import systemInfo from './systemInfo.json'; import { DashboardCard, DashboardUtility } from './components'; import { DASHBOARD_QUICK_ACCESS, HIDDEN_APPS, UTILITY_APPS } from './consts'; const appVersion = systemInfo.appVersions; +const versionStatusIcon = + systemInfo.lastRelease > systemInfo.version ? ( + <ExclamationCircleIcon className="h-4 w-4 text-yellow-500" /> + ) : ( + <CheckCircleIcon className="h-4 w-4 text-primary-800" /> + ); + export const Dashboard: React.FC = () => { const host = window.location.hostname; const splitedDomain = host.split('.'); @@ -33,6 +41,13 @@ export const Dashboard: React.FC = () => { <div className="max-w-7xl mx-auto py-4 px-3 sm:px-6 lg:px-8"> <div className="mt-6 pb-5 border-b border-gray-200 sm:flex sm:items-center sm:justify-between"> <h1 className="text-3xl leading-6 font-bold text-gray-900">Dashboard</h1> + <div className="system-status flex items-center text-sm font-medium text-gray-500 gap-1"> + <p className="">Version {systemInfo.version} </p> + <span>{versionStatusIcon}</span> + <a className="hover:text-primary-500 underline" href={systemInfo.releaseNotesUrl}> + (Changelog) + </a> + </div> </div> </div> diff --git a/frontend/src/modules/dashboard/components/DashboardCard/DashboardCard.tsx b/frontend/src/modules/dashboard/components/DashboardCard/DashboardCard.tsx index cf6464b3..ca3b4b92 100644 --- a/frontend/src/modules/dashboard/components/DashboardCard/DashboardCard.tsx +++ b/frontend/src/modules/dashboard/components/DashboardCard/DashboardCard.tsx @@ -3,7 +3,6 @@ import { Modal } from 'src/components'; import ReactMarkdown from 'react-markdown'; import matter from 'gray-matter'; import { QuestionMarkCircleIcon } from '@heroicons/react/outline'; -// import { array } from 'yup'; type DashboardCardProps = { app: any; @@ -31,7 +30,7 @@ export const DashboardCard = ({ app, version }: DashboardCardProps) => { return ( <> <div - className="bg-white relative overflow-hidden shadow rounded-lg divide-y divide-gray-100 mb-4 md:mb-0" + className="bg-white overflow-hidden shadow rounded-lg divide-y divide-gray-100 mb-4 md:mb-0 flex flex-col justify-between relative" key={app.name} > <div className="flex items-end justify-end absolute top-2 right-2"> @@ -47,11 +46,11 @@ export const DashboardCard = ({ app, version }: DashboardCardProps) => { alt={app.name} /> - <div> + <div className="flex justify-between gap-1 items-center"> <h2 className="text-xl leading-8 font-bold">{app.name}</h2> + <p className="text-xs text-gray-400">{version}</p> </div> </div> - <p className="text-xs">{version} is the version</p> <p className="text-gray-500 mt-2 text-sm leading-5 font-normal">{appDescription.data.tileExcerpt}</p> </div> <div className="px-2.5 py-2.5 sm:px-4 flex justify-end"> -- GitLab