diff --git a/client/src/components/chart.js b/client/src/components/chart.js index 048f5b2..3d35ae2 100644 --- a/client/src/components/chart.js +++ b/client/src/components/chart.js @@ -10,7 +10,9 @@ const options = { showLabel: false, }; -export default function Chart({used = 0, usedSuffix, available = 0, availableSuffix, pending = 0, decimals = 1}) { // eslint-disable-line max-len +export default function Chart(props) { + const {used = 0, usedSuffix, available = 0, availableSuffix, pending = 0, decimals = 1} = props; + const fixedUsed = _.round(used, decimals); const fixedPending = _.round(pending, decimals); const fixedAvailable = _.round(available, decimals); diff --git a/client/src/components/cpuChart.js b/client/src/components/cpuChart.js index b46f70a..5923493 100644 --- a/client/src/components/cpuChart.js +++ b/client/src/components/cpuChart.js @@ -6,15 +6,20 @@ import {parseCpu, TO_ONE_CPU} from '../utils/unitHelpers'; export default function CpuChart({items, metrics}) { const totals = getPodCpuTotals(items, metrics); + const decimals = totals && totals.used > 10 ? 1 : 2; return (
{totals ? ( - + ) : ( )} -
Requested Cpu Use
+
Cores Used
); } diff --git a/client/src/components/podsPanel.js b/client/src/components/podsPanel.js index e94490b..1a1bf90 100644 --- a/client/src/components/podsPanel.js +++ b/client/src/components/podsPanel.js @@ -7,6 +7,24 @@ import Sorter from './sorter'; import {parseRam, unparseRam, parseCpu, unparseCpu} from '../utils/unitHelpers'; export default class PodsPanel extends Base { + constructor(props) { + super(props); + this.sortByCpu = this.sortByCpu.bind(this); + this.sortByRam = this.sortByRam.bind(this); + } + + sortByCpu(item) { + const actual = getCpuUsage(item, this.props.metrics); + const requested = getCpuRequest(item); + return actual / requested; + } + + sortByRam(item) { + const actual = getRamUsage(item, this.props.metrics); + const requested = getRamRequest(item); + return actual / requested; + } + render() { const {items, metrics, sort, filter, skipNamespace, skipNodeName} = this.props; const col = 8 + !skipNamespace + !skipNodeName; @@ -23,19 +41,21 @@ export default class PodsPanel extends Base { Status Containers Restarts - - {/* TODO: support sorting by metrics */} - Cpu -
- actual vs. request -
+ + Cpu +
+ actual vs. request +
+
- Ram -
- actual vs. request -
+ + Ram +
+ actual vs. request +
+
@@ -71,47 +91,37 @@ function getRestartCount({status}) { } function Cpu({item, metrics}) { - if (!item || !metrics) return null; - - const {containers} = metrics[item.metadata.name] || {}; + const containers = getContainerMetrics(item, metrics); if (!containers) return null; - const actual = _.sumBy(containers, x => parseCpu(x.usage.cpu)); - const podContainers = item.spec.containers.filter(x => x.resources && x.resources.requests); - const requested = _.sumBy(podContainers, x => parseCpu(x.resources.requests.cpu)); + const actual = getCpuUsage(item, metrics); + const requested = getCpuRequest(item); + return ; +} - const isWarning = requested && (actual > requested); - const actualCpu = unparseCpu(actual); - const requestedCpu = unparseCpu(requested); - const percent = requested ? _.round(actual / requested * 100, 1) : 100; +function getCpuUsage(item, metrics) { + const containers = getContainerMetrics(item, metrics); + return _.sumBy(containers, x => parseCpu(x.usage.cpu)); +} - return ( -
-
{requested ? `${percent}%` : 'N/A'}
-
- {actualCpu.value}{actualCpu.unit} - of - {requestedCpu.value}{requestedCpu.unit} -
-
- ); +function getCpuRequest(item) { + const podContainers = _.filter(item.spec.containers, x => x.resources && x.resources.requests); + return _.sumBy(podContainers, x => parseCpu(x.resources.requests.cpu)); } function Ram({item, metrics}) { - if (!item || !metrics) return null; - - const {containers} = metrics[item.metadata.name] || {}; + const containers = getContainerMetrics(item, metrics); if (!containers) return null; - const actual = _.sumBy(containers, x => parseRam(x.usage.memory)); - const requested = _.sumBy(item.spec.containers, (x) => { - if (!x.resources.requests) return 0; - return parseRam(x.resources.requests.memory); - }); + const actual = getRamUsage(item, metrics); + const requested = getRamRequest(item); + return ; +} +function Chart({actual, requested, unparser}) { const isWarning = requested && (actual > requested); - const actualRam = unparseRam(actual); - const requestedRam = unparseRam(requested); + const actualRam = unparser(actual); + const requestedRam = unparser(requested); const percent = requested ? _.round(actual / requested * 100, 1) : 100; return ( @@ -125,3 +135,21 @@ function Ram({item, metrics}) { ); } + +function getRamUsage(item, metrics) { + const containers = getContainerMetrics(item, metrics); + return _.sumBy(containers, x => parseRam(x.usage.memory)); +} + +function getRamRequest(item) { + return _.sumBy(item.spec.containers, (x) => { + if (!x.resources.requests) return 0; + return parseRam(x.resources.requests.memory); + }); +} + +function getContainerMetrics(item, metrics) { + if (!item || !metrics) return null; + const metric = metrics[item.metadata.name] || {}; + return metric.containers; +} diff --git a/client/src/components/ramChart.js b/client/src/components/ramChart.js index ee8e08f..4b46c08 100644 --- a/client/src/components/ramChart.js +++ b/client/src/components/ramChart.js @@ -6,15 +6,22 @@ import {parseRam, TO_GB} from '../utils/unitHelpers'; export default function RamChart({items, metrics}) { const totals = getPodRamTotals(items, metrics); + const decimals = totals && totals.used > 10 ? 1 : 2; return (
{totals ? ( - + ) : ( )} -
Requested Ram Use
+
Ram Used
); } diff --git a/client/src/services/api.js b/client/src/services/api.js index a2d7bbf..e01079e 100644 --- a/client/src/services/api.js +++ b/client/src/services/api.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import {request, stream, streamResult, streamResults, getToken} from './apiProxy'; +import {request, stream, streamResult, streamResults} from './apiProxy'; import log from '../utils/log'; const JSON_HEADERS = {Accept: 'application/json', 'Content-Type': 'application/json'}; @@ -40,15 +40,9 @@ const apis = { roleBinding: apiFactoryWithNamespace('/apis/rbac.authorization.k8s.io/v1', 'rolebindings'), }; -function testAuth() { - const token = getToken(); - const body = { - apiVerstion: 'authentication.k8s.io/v1', - kind: 'TokenReview', - spec: {token}, - }; - - return post('/apis/authentication.k8s.io/v1/tokenreviews', body, false); +async function testAuth() { + const spec = {resourceAttributes: {}}; + return post('apis/authorization.k8s.io/v1/selfsubjectaccessreviews', {spec}, false); } async function apply(body) { diff --git a/client/src/utils/unitHelpers.js b/client/src/utils/unitHelpers.js index b519462..22565ed 100644 --- a/client/src/utils/unitHelpers.js +++ b/client/src/utils/unitHelpers.js @@ -52,6 +52,7 @@ export function parseCpu(value) { const number = parseInt(value, 10); if (value.endsWith('n')) return number; + if (value.endsWith('u')) return number * 1000; if (value.endsWith('m')) return number * 1000 * 1000; return number * 1000 * 1000 * 1000; } diff --git a/client/src/views/auth.js b/client/src/views/auth.js index 317a65a..86a939d 100644 --- a/client/src/views/auth.js +++ b/client/src/views/auth.js @@ -14,6 +14,10 @@ const LOGIN_MESSAGE = 'Invalid credentials'; const ERROR_MESSAGE = 'Error occured attempting to login'; export default class Auth extends Base { + state = { + token: '', + }; + async componentDidMount() { const url = new URL(window.location.href); const code = url.searchParams.get('code'); @@ -46,10 +50,10 @@ export default class Auth extends Base { className='auth_input' placeholder='Enter your auth token here...' spellCheck='false' - defaultValue={token} + value={token} onChange={x => this.setState({token: x.target.value})} /> - @@ -95,7 +99,14 @@ async function oidcLogin(code, returnedState) { async function login(token) { try { setToken(token); - await api.testAuth(); + + const result = await api.testAuth(); + log.info('Auth', result); + + if (!result || !result.status || !result.status.allowed) { + throw new Error('Invalid login'); + } + window.location.reload(); } catch (err) { log.error('Login Failed', err); diff --git a/client/src/views/node.js b/client/src/views/node.js index 3566d7e..3ad46d0 100644 --- a/client/src/views/node.js +++ b/client/src/views/node.js @@ -1,5 +1,6 @@ import _ from 'lodash'; import React from 'react'; +import moment from 'moment'; import api from '../services/api'; import Base from '../components/base'; import Chart from '../components/chart'; @@ -11,6 +12,7 @@ import PodsPanel from '../components/podsPanel'; import {defaultSortInfo} from '../components/sorter'; import {parseCpu, parseRam, TO_GB, TO_ONE_M_CPU} from '../utils/unitHelpers'; import PodStatusChart from '../components/podStatusChart'; +import Field from '../components/field'; export default class Node extends Base { state = { @@ -51,10 +53,45 @@ export default class Node extends Base { {!item ? : (
+ + + + + + +
)} +
+ {!item ? : ( + + + + + + + + + + + + {_.map(item.status.conditions, x => ( + + + + + + + + ))} + +
ConditionStatusTransitionReasonMessage
{x.type}{x.status}{moment(x.lastTransitionTime).fromNow()}{x.reason}{x.message}
+ )} +
+ + Ready Cpu Ram - - {/* TODO: support sorting by cpu/ram - Cpu - Ram */} + {/* Cpu + Ram */} @@ -81,6 +79,23 @@ export default class Nodes extends Base { } } +// TODO: get sortying by cpu/ram working +// function sortByCpu(item) { +// const used = getCpuUsed(item, filteredMetrics); +// if (used == null) return null; + +// const available = getCpuAvailable(item); +// return used / available; +// } + +// function sortByRam(item) { +// const used = getRamUsed(item, filteredMetrics); +// if (used == null) return null; + +// const available = getRamAvailable(item); +// return used / available; +// } + function getReadyStatus({status}) { if (!status.conditions) return null; @@ -117,31 +132,54 @@ function RamTotalsChart({items, metrics}) { } function CpuChart({item, metrics}) { - if (!item || !metrics) return null; + const used = getCpuUsed(item, metrics); + if (used == null) return null; - const {usage} = metrics[item.metadata.name] || {}; + const available = getCpuAvailable(item); + return ; +} + +function getCpuUsed(item, metrics) { + const usage = getUsage(item, metrics); if (!usage) return null; - const totalUsed = parseCpu(usage.cpu) / 1000000; - const totalAvailable = parseCpu(item.status.capacity.cpu) / 1000000; - const percent = _.round(totalUsed / totalAvailable * 100, 1); + return parseCpu(usage.cpu) / 1000000; +} - return ({`${percent}%`}); +function getCpuAvailable(item) { + return parseCpu(item.status.capacity.cpu) / 1000000; } function RamChart({item, metrics}) { - if (!item || !metrics) return null; + const used = getRamUsed(item, metrics); + if (used == null) return null; - const {usage} = metrics[item.metadata.name] || {}; + const available = getRamAvailable(item); + return ; +} + +function getRamUsed(item, metrics) { + const usage = getUsage(item, metrics); if (!usage) return null; - const totalUsed = parseRam(usage.memory); - const totalAvailable = parseRam(item.status.capacity.memory); - const percent = _.round(totalUsed / totalAvailable * 100, 1); + return parseRam(usage.memory); +} +function getRamAvailable(item) { + return parseRam(item.status.capacity.memory); +} + +function Percent({used, available}) { + const percent = _.round(used / available * 100, 1); return ({`${percent}%`}); } +function getUsage(item, metrics) { + if (!item || !metrics) return null; + const result = metrics[item.metadata.name] || {}; + return result && result.usage; +} + function getNodeMetrics(nodes, metrics) { if (!nodes || !metrics) return null; diff --git a/server/index.js b/server/index.js index 28e8729..58f189b 100644 --- a/server/index.js +++ b/server/index.js @@ -27,9 +27,11 @@ const target = kc.getCurrentCluster().server; const agent = new https.Agent({ca: opts.ca}); const proxySettings = {target, agent, secure: false, ws: true, onError}; +logClusterInfo(); + const app = express(); app.disable('x-powered-by'); // for security reasons, best not to tell attackers too much about our backend -app.use(cors()); +app.use(cors()); // TODO: remove in production builds app.use(logging); app.use('/', express.static('public')); @@ -79,11 +81,11 @@ function handleErrors(err, req, res, next) { next(); } -async function getOidcEndpoint(redirectUri) { // TODO: gotta pass this in from +async function getOidcEndpoint() { if (!OIDC_URL) return; const provider = await getOidcProvider(); - return provider.authorizationUrl({redirect_uri: redirectUri, scope: OIDC_SCOPES}); + return provider.authorizationUrl({scope: OIDC_SCOPES}); } async function oidcAuthenticate(code, redirectUri) { @@ -96,3 +98,20 @@ async function getOidcProvider() { const issuer = await Issuer.discover(OIDC_URL); return new issuer.Client({client_id: OIDC_CLIENT_ID, client_secret: OIDC_SECRET}); } + +async function logClusterInfo() { + try { + const versionClient = kc.makeApiClient(k8s.VersionApi); + const versionResponse = await versionClient.getCode(); + const versionJson = JSON.stringify(versionResponse.body, null, 4); + console.log('Version Info: ', versionJson); + + const apisClient = kc.makeApiClient(k8s.ApisApi); + const apisResponse = await apisClient.getAPIVersions(); + const apis = apisResponse.body.groups.map(x => x.preferredVersion.groupVersion).sort(); + const apisJson = JSON.stringify(apis, null, 4); + console.log('Available APIs:', apisJson); + } catch (err) { + console.error('Error getting cluster info', err); + } +}