mirror of
https://github.com/skooner-k8s/skooner.git
synced 2026-08-24 04:16:18 +00:00
Merge branch 'dev' into rules
This commit is contained in:
+2
-2
@@ -1,5 +1,5 @@
|
||||
# Stage 1 - the build react app
|
||||
FROM node:11.10.0-alpine as build-deps
|
||||
FROM node:12.4.0-alpine as build-deps
|
||||
WORKDIR /usr/src/app
|
||||
COPY client/package.json client/package-lock.json ./
|
||||
RUN npm i
|
||||
@@ -8,7 +8,7 @@ COPY client/ ./
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2 - the production environment
|
||||
FROM node:11.10.0-alpine
|
||||
FROM node:12.4.0-alpine
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
ENV NODE_ENV production
|
||||
|
||||
@@ -49,6 +49,12 @@ spec:
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
# kubectl proxy
|
||||
Unfortunately, `kubectl proxy` can not be used to access k8dash. According to the information at [https://github.com/kubernetes/kubernetes/issues/38775#issuecomment-277915961](https://github.com/kubernetes/kubernetes/issues/38775#issuecomment-277915961), it seems that `kubectl proxy` strips the Authorization header when it proxies requests. From that link:
|
||||
|
||||
> this is working as expected. "proxying" through the apiserver will not get you standard proxy behavior (preserving Authorization headers end-to-end), because the API is not being used as a standard proxy
|
||||
|
||||
|
||||
|
||||
# Logging in
|
||||
There are multiple options logging into the dashboard.
|
||||
|
||||
Generated
+3874
-8926
File diff suppressed because it is too large
Load Diff
+15
-13
@@ -4,19 +4,21 @@
|
||||
"private": true,
|
||||
"homepage": ".",
|
||||
"dependencies": {
|
||||
"ansi-to-react": "^5.0.0",
|
||||
"js-base64": "^2.5.1",
|
||||
"js-cookie": "^2.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.24.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"react": "^16.8.2",
|
||||
"react-dom": "^16.8.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-modal": "^3.8.1",
|
||||
"react-scripts": "2.1.5",
|
||||
"react-select": "^2.4.2",
|
||||
"react-swipeable": "^5.1.0",
|
||||
"react-switch": "^4.1.0",
|
||||
"swagger-parser": "^6.0.5",
|
||||
"uuid": "^3.3.2",
|
||||
"xterm": "^3.12.0",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-select": "^3.0.4",
|
||||
"react-swipeable": "^5.2.3",
|
||||
"react-switch": "^5.0.0",
|
||||
"swagger-parser": "^7.0.0",
|
||||
"time-ago": "^0.2.1",
|
||||
"xterm": "^3.14.2",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -35,10 +37,10 @@
|
||||
"not op_mini all"
|
||||
],
|
||||
"devDependencies": {
|
||||
"eslint": "^5.12.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.16.0",
|
||||
"eslint-plugin-import": "^2.17.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||
"eslint-plugin-react": "^7.12.4"
|
||||
"eslint-plugin-react": "^7.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@ export default class Button extends Base {
|
||||
|
||||
render() {
|
||||
const {working} = this.state || {};
|
||||
const {
|
||||
children, disabled, className, title,
|
||||
} = this.props;
|
||||
const {children, disabled, className, title} = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import fromNow from '../utils/dates';
|
||||
import {TableBody} from './listViewHelpers';
|
||||
import log from '../utils/log';
|
||||
import ResourceSvg from '../art/resourceSvg';
|
||||
@@ -46,9 +46,9 @@ export default function EventsPanel({items, filter, shortList, sort}) {
|
||||
<div className='td_iconLabel'>{x.involvedObject.kind}</div>
|
||||
</td>
|
||||
{!shortList && (
|
||||
<td className='wrapped_name'>{x.involvedObject.namespace}:{x.involvedObject.name}</td>
|
||||
<td className='wrapped_name'>{getName(x.involvedObject)}</td>
|
||||
)}
|
||||
<td>{moment(x.metadata.creationTimestamp).fromNow(true)}</td>
|
||||
<td>{fromNow(x.metadata.creationTimestamp)}</td>
|
||||
<td className='optional_small'>{x.reason}</td>
|
||||
<td>{x.message}</td>
|
||||
</tr>
|
||||
@@ -59,23 +59,50 @@ export default function EventsPanel({items, filter, shortList, sort}) {
|
||||
);
|
||||
}
|
||||
|
||||
function getName({kind, namespace, name}) {
|
||||
const text = namespace ? `${namespace}:${name}` : name;
|
||||
const href = getHref(kind, namespace, name);
|
||||
return href ? (<a href={href}>{text}</a>) : text;
|
||||
}
|
||||
|
||||
function getHref(kind, namespace, name) {
|
||||
switch (kind) {
|
||||
case 'ClusterRole': return `/#!clusterrole/${name}`;
|
||||
case 'ClusterRoleBinding': return `/#!clusterrolebinding/${name}`;
|
||||
case 'ConfigMap': return `/#!configmap/${namespace}/${name}`;
|
||||
case 'DaemonSet': return `/#!workload/daemonset/${namespace}/${name}`;
|
||||
case 'Deployment': return `/#!workload/deployment/${namespace}/${name}`;
|
||||
case 'Ingress': return `/#!ingress/${namespace}/${name}`;
|
||||
case 'Node': return `/#!node/${name}`;
|
||||
case 'PersistentVolume': return `/#!persistentvolume/${name}`;
|
||||
case 'PersistentVolumeClaim': return `/#!persistentvolumeclaim/${namespace}/${name}`;
|
||||
case 'Pod': return `/#!pod/${namespace}/${name}`;
|
||||
case 'ReplicaSet': return `/#!replicaset/${namespace}/${name}`;
|
||||
case 'Role': return `/#!role/${namespace}/${name}`;
|
||||
case 'RoleBinding': return `/#!rolebinding/${namespace}/${name}`;
|
||||
case 'Secret': return `/#!secret/${namespace}/${name}`;
|
||||
case 'Service': return `/#!service/${namespace}/${name}`;
|
||||
case 'ServiceAccount': return `/#!serviceaccount/${namespace}/${name}`;
|
||||
case 'StatefulSet': return `/#!workload/statefulset/${namespace}/${name}`;
|
||||
case 'StorageClass': return `/#!storageclass/${name}`;
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function sortByName({involvedObject}) {
|
||||
return `${involvedObject.namespace}:${involvedObject.name}`;
|
||||
}
|
||||
|
||||
function getTypeClass(type) {
|
||||
switch (type) {
|
||||
case 'Normal':
|
||||
return '';
|
||||
|
||||
case 'Warning':
|
||||
case 'Error':
|
||||
return 'svg_error';
|
||||
case 'Normal': return undefined;
|
||||
case 'Warning': return 'svg_warn';
|
||||
case 'Error': return 'svg_error';
|
||||
|
||||
default: {
|
||||
const error = new Error('Unexpected event type');
|
||||
log.error('Unexpected event type', {error, type});
|
||||
return '';
|
||||
return 'svg_neutral';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import fromNow from '../utils/dates';
|
||||
import Loading from './loading';
|
||||
import Sorter, {sortByDate} from './sorter';
|
||||
import ResourceSvg from '../art/resourceSvg';
|
||||
@@ -49,13 +49,13 @@ export function MetadataHeaders({includeNamespace, sort}) {
|
||||
);
|
||||
}
|
||||
|
||||
export function MetadataColumns({item, href, includeNamespace, isError}) {
|
||||
export function MetadataColumns({item, href, includeNamespace, resourceClass}) {
|
||||
return (
|
||||
<>
|
||||
<td className='td_icon optional_small'>
|
||||
<ResourceSvg
|
||||
resource={item.kind}
|
||||
className={isError ? 'svg_error' : undefined}
|
||||
className={resourceClass}
|
||||
/>
|
||||
<div className='td_iconLabel'>{item.kind}</div>
|
||||
</td>
|
||||
@@ -70,7 +70,7 @@ export function MetadataColumns({item, href, includeNamespace, isError}) {
|
||||
)}
|
||||
|
||||
<td className='optional_medium'>
|
||||
{moment(item.metadata.creationTimestamp).fromNow(true)}
|
||||
{fromNow(item.metadata.creationTimestamp)}
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import Field from './field';
|
||||
import formatDate from '../utils/dates';
|
||||
import {objectMap} from './listViewHelpers';
|
||||
|
||||
const MetadataFields = ({item}) => (
|
||||
@@ -13,7 +12,7 @@ const MetadataFields = ({item}) => (
|
||||
)}
|
||||
|
||||
<Field name='Created'>
|
||||
{formatDate(item.metadata.creationTimestamp)}
|
||||
{new Date(item.metadata.creationTimestamp).toLocaleString()}
|
||||
</Field>
|
||||
|
||||
{item.metadata.labels && (
|
||||
|
||||
@@ -4,12 +4,12 @@ import Chart from './chart';
|
||||
import LoadingChart from './loadingChart';
|
||||
|
||||
export default function NodeStatusChart({items}) {
|
||||
const ready = _.filter(items, x => getReadyStatus(x) === 'True');
|
||||
const readyCount = _.sumBy(items, x => getReadyStatus(x) === 'True' ? 1 : 0); // eslint-disable-line no-confusing-arrow
|
||||
|
||||
return (
|
||||
<div className='charts_item'>
|
||||
{items ? (
|
||||
<Chart used={ready.length} available={items.length} />
|
||||
<Chart used={readyCount} available={items.length} />
|
||||
) : (
|
||||
<LoadingChart />
|
||||
)}
|
||||
|
||||
@@ -5,12 +5,12 @@ import LoadingChart from './loadingChart';
|
||||
|
||||
export default function PodStatusChart({items}) {
|
||||
const available = items && items.length;
|
||||
const current = _.filter(items, x => x.status.phase === 'Running').length;
|
||||
const count = _.sumBy(items, x => x.status.phase === 'Running' ? 1 : 0); // eslint-disable-line no-confusing-arrow
|
||||
|
||||
return (
|
||||
<div className='charts_item'>
|
||||
{items ? (
|
||||
<Chart used={current} pending={available - current} available={available} />
|
||||
<Chart used={count} pending={available - count} available={available} />
|
||||
) : (
|
||||
<LoadingChart />
|
||||
)}
|
||||
|
||||
@@ -74,7 +74,7 @@ export default class PodsPanel extends Base {
|
||||
<tr key={x.metadata.uid}>
|
||||
<MetadataColumns
|
||||
item={x}
|
||||
isError={x.status.phase !== 'Running'}
|
||||
resourceClass={getPhaseStyle(x.status.phase)}
|
||||
includeNamespace={!skipNamespace}
|
||||
href={`#!pod/${x.metadata.namespace}/${x.metadata.name}`}
|
||||
/>
|
||||
@@ -89,6 +89,25 @@ export default class PodsPanel extends Base {
|
||||
}
|
||||
}
|
||||
|
||||
function getPhaseStyle(phase) {
|
||||
switch (phase) {
|
||||
case 'Pending':
|
||||
return 'svg_warn';
|
||||
|
||||
case 'Running':
|
||||
return undefined;
|
||||
|
||||
case 'Failed':
|
||||
case 'Unknown':
|
||||
return 'svg_error';
|
||||
|
||||
case 'Succeeded':
|
||||
case 'Completed':
|
||||
default:
|
||||
return 'svg_neutral';
|
||||
}
|
||||
}
|
||||
|
||||
function sortBy(item, metrics, resource, type) {
|
||||
const result = getPodResourcePercent(item, metrics, resource, type);
|
||||
return Number.isFinite(result) ? result : -1;
|
||||
|
||||
@@ -5,6 +5,14 @@ svg {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.svg_neutral {
|
||||
fill: $color-neutral;
|
||||
}
|
||||
|
||||
.svg_warn {
|
||||
fill: $color-warn;
|
||||
}
|
||||
|
||||
.svg_error {
|
||||
fill: $color-error;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ $color-light: #AAA;
|
||||
$color-dark: #888098;
|
||||
$color-darkest: #344055;
|
||||
|
||||
$color-accent: #326ce5;
|
||||
$color-accent: #326CE5;
|
||||
$color-warn: #aaa810;
|
||||
$color-error: #B9314F;
|
||||
$color-neutral: $color-dark;
|
||||
|
||||
$color-font: $color-darkest;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import {Base64} from 'js-base64';
|
||||
import {request, stream, streamResult, streamResults} from './apiProxy';
|
||||
import log from '../utils/log';
|
||||
|
||||
@@ -157,20 +158,26 @@ function swagger() {
|
||||
|
||||
function exec(namespace, name, container, cb) {
|
||||
const url = `/api/v1/namespaces/${namespace}/pods/${name}/exec?container=${container}&command=sh&stdin=1&stderr=1&stdout=1&tty=1`;
|
||||
const protocols = ['v4.channel.k8s.io', 'v3.channel.k8s.io', 'v2.channel.k8s.io', 'channel.k8s.io'];
|
||||
return stream(url, cb, false, protocols);
|
||||
const additionalProtocols = ['v4.channel.k8s.io', 'v3.channel.k8s.io', 'v2.channel.k8s.io', 'channel.k8s.io'];
|
||||
return stream(url, cb, {additionalProtocols, isJson: false});
|
||||
}
|
||||
|
||||
function logs(namespace, name, container, showPrevious, cb) {
|
||||
const url = `/api/v1/namespaces/${namespace}/pods/${name}/log?container=${container}&previous=${showPrevious}&tailLines=2000&follow=true`;
|
||||
const {cancel} = stream(url, transformer, false);
|
||||
function logs(namespace, name, container, tailLines, showPrevious, cb) {
|
||||
const items = [];
|
||||
const url = `/api/v1/namespaces/${namespace}/pods/${name}/log?container=${container}&previous=${showPrevious}&tailLines=${tailLines}&follow=true`;
|
||||
const {cancel} = stream(url, transformer, {isJson: false, connectCb});
|
||||
return cancel;
|
||||
|
||||
function connectCb() {
|
||||
items.length = 0;
|
||||
}
|
||||
|
||||
function transformer(item) {
|
||||
if (!item) return; // For some reason, this api returns a lot of empty strings
|
||||
|
||||
const message = atob(item);
|
||||
cb(message);
|
||||
const message = Base64.decode(item);
|
||||
items.push(message);
|
||||
cb(items);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as cookie from 'js-cookie';
|
||||
import log from '../utils/log';
|
||||
|
||||
const {host, href, hash, search} = window.location;
|
||||
@@ -6,6 +7,12 @@ const isDev = process.env.NODE_ENV !== 'production';
|
||||
const BASE_HTTP_URL = isDev && host === 'localhost:4653' ? 'http://localhost:4654' : nonHashedUrl;
|
||||
const BASE_WS_URL = BASE_HTTP_URL.replace('http', 'ws');
|
||||
|
||||
const authorizationCookie = cookie.get('Authorization');
|
||||
if (authorizationCookie) {
|
||||
setToken(authorizationCookie);
|
||||
cookie.remove('Authorization');
|
||||
}
|
||||
|
||||
export function getToken() {
|
||||
return localStorage.authToken;
|
||||
}
|
||||
@@ -36,7 +43,7 @@ export async function request(path, params, autoLogoutOnAuthError = true) {
|
||||
const opts = Object.assign({headers: {}}, params);
|
||||
|
||||
const token = getToken();
|
||||
if (token) opts.headers.Authorization = `Bearer ${token}`;
|
||||
if (token) opts.headers.Authorization = token;
|
||||
|
||||
const url = combinePath(BASE_HTTP_URL, path);
|
||||
const response = await fetch(url, opts);
|
||||
@@ -81,7 +88,7 @@ export async function streamResult(url, name, cb, errCb) {
|
||||
const fieldSelector = encodeURIComponent(`metadata.name=${name}`);
|
||||
const watchUrl = `${url}?watch=1&fieldSelector=${fieldSelector}`;
|
||||
|
||||
socket = stream(watchUrl, x => cb(x.object));
|
||||
socket = stream(watchUrl, x => cb(x.object), {isJson: true});
|
||||
} catch (err) {
|
||||
log.error('Error in api request', {err, url});
|
||||
if (errCb) errCb(err);
|
||||
@@ -112,7 +119,7 @@ export async function streamResults(url, cb, errCb) {
|
||||
add(items, kind);
|
||||
|
||||
const watchUrl = `${url}?watch=1&resourceVersion=${metadata.resourceVersion}`;
|
||||
socket = stream(watchUrl, update);
|
||||
socket = stream(watchUrl, update, {isJson: true});
|
||||
} catch (err) {
|
||||
log.error('Error in api request', {err, url});
|
||||
if (errCb) errCb(err);
|
||||
@@ -177,9 +184,10 @@ export async function streamResults(url, cb, errCb) {
|
||||
}
|
||||
}
|
||||
|
||||
export function stream(url, cb, isJson = true, additionalProtocols) {
|
||||
export function stream(url, cb, args) {
|
||||
let connection;
|
||||
let isCancelled;
|
||||
const {isJson, additionalProtocols, connectCb} = args;
|
||||
|
||||
connect();
|
||||
|
||||
@@ -195,6 +203,7 @@ export function stream(url, cb, isJson = true, additionalProtocols) {
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (connectCb) connectCb();
|
||||
connection = connectStream(url, cb, onFail, isJson, additionalProtocols);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import moment from 'moment';
|
||||
import timeAgo from 'time-ago';
|
||||
|
||||
export default function formatDate(value) {
|
||||
const date = moment(value);
|
||||
const format = date.isSame(moment(), 'day') ? 'h:mma' : 'M/D/YY h:mma';
|
||||
return date.format(format);
|
||||
export default function fromNow(value) {
|
||||
return timeAgo.ago(value, true);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import './auth.scss';
|
||||
import React from 'react';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import {addUserNotification} from '../components/notifier';
|
||||
import {setToken, deleteToken} from '../services/apiProxy';
|
||||
import api from '../services/api';
|
||||
@@ -64,7 +63,7 @@ export default class Auth extends Base {
|
||||
}
|
||||
|
||||
async function redirectToOidc(authEndpoint) {
|
||||
const state = uuidv4();
|
||||
const state = window.location.href;
|
||||
const redirectUri = window.location.href.replace(window.location.hash, '');
|
||||
sessionStorage.oidc = JSON.stringify({state, redirectUri});
|
||||
|
||||
@@ -88,7 +87,7 @@ async function oidcLogin(code, returnedState) {
|
||||
|
||||
try {
|
||||
const {token} = await api.oidc.post(code, redirectUri);
|
||||
login(token);
|
||||
login(token, state);
|
||||
} catch (err) {
|
||||
log.error('OICD login failed', {err});
|
||||
addUserNotification('Login failed.', true);
|
||||
@@ -96,11 +95,16 @@ async function oidcLogin(code, returnedState) {
|
||||
}
|
||||
}
|
||||
|
||||
async function login(token) {
|
||||
async function login(token, redirectUri) {
|
||||
try {
|
||||
setToken(token);
|
||||
setToken(`Bearer ${token}`);
|
||||
await api.testAuth();
|
||||
window.location.reload();
|
||||
|
||||
if (redirectUri) {
|
||||
window.location = redirectUri;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (err) {
|
||||
log.error('Login Failed', err);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import Switch from 'react-switch';
|
||||
import Select from 'react-select';
|
||||
import Ansi from 'ansi-to-react';
|
||||
import Base from '../components/base';
|
||||
import InputFilter from '../components/inputFilter';
|
||||
import Loading from '../components/loading';
|
||||
@@ -21,7 +22,7 @@ export default class Logs extends Base {
|
||||
// on the trailing edge of the timeout only if the debounced function is invoked more
|
||||
// than once during the wait timeout."
|
||||
const options = {leading: true, trailing: true, maxWait: 1000};
|
||||
this.debouncedRefreshLogs = _.debounce(this.refreshLogs.bind(this), 1000, options);
|
||||
this.debouncedSetState = _.debounce(this.setState.bind(this), 100, options);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -51,28 +52,17 @@ export default class Logs extends Base {
|
||||
startLogsStream(container, showPrevious) {
|
||||
if (!container) return;
|
||||
|
||||
const {namespace, name} = this.props;
|
||||
this.setState({container, showPrevious, items: []});
|
||||
|
||||
const {namespace, name} = this.props;
|
||||
this.registerApi({
|
||||
items: api.logs(namespace, name, container, showPrevious, items => this.onLogs(items)),
|
||||
items: api.logs(namespace, name, container, 1000, showPrevious, items => this.debouncedSetState({items})), // eslint-disable-line max-len
|
||||
});
|
||||
}
|
||||
|
||||
onLogs(log) {
|
||||
const {items} = this.state;
|
||||
items.push(log);
|
||||
this.debouncedRefreshLogs(items);
|
||||
}
|
||||
|
||||
refreshLogs(logs) {
|
||||
const items = logs.slice(-1000);
|
||||
this.setState({items});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {namespace, name} = this.props;
|
||||
const {items, container, containers = [], filter = '', showPrevious = false} = this.state || {};
|
||||
const {items, container, containers = [], filter = '', showPrevious = false} = this.state;
|
||||
|
||||
const lowercaseFilter = filter.toLowerCase();
|
||||
const filteredLogs = items.filter(x => x.toLowerCase().includes(lowercaseFilter));
|
||||
@@ -111,13 +101,14 @@ export default class Logs extends Base {
|
||||
filter={filter}
|
||||
onChange={x => this.setState({filter: x})}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='contentPanel'>
|
||||
{!items ? <Loading /> : (
|
||||
<pre>
|
||||
{filteredLogs.join('')}
|
||||
{filteredLogs.map((x, i) => (
|
||||
<Ansi key={i}>{x}</Ansi>
|
||||
))}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import fromNow from '../utils/dates';
|
||||
import api from '../services/api';
|
||||
import Base from '../components/base';
|
||||
import ItemHeader from '../components/itemHeader';
|
||||
@@ -48,11 +48,11 @@ export default class Node extends Base {
|
||||
<ChartsContainer>
|
||||
<div className='charts_item'>
|
||||
{item ? (
|
||||
<span className='charts_number'>{getUpTime(item)}</span>
|
||||
<span className='charts_number'>{getUptime(item)}</span>
|
||||
) : (
|
||||
<LoadingChart />
|
||||
)}
|
||||
<div className='charts_itemLabel'>Days Up</div>
|
||||
<div className='charts_itemLabel'>Uptime</div>
|
||||
</div>
|
||||
<NodeCpuChart items={item && [item]} metrics={metrics && [metrics]} />
|
||||
<NodeRamChart items={item && [item]} metrics={metrics && [metrics]} />
|
||||
@@ -97,7 +97,7 @@ export default class Node extends Base {
|
||||
<tr key={x.type}>
|
||||
<td>{x.type}</td>
|
||||
<td>{x.status}</td>
|
||||
<td className='optional_medium'>{moment(x.lastTransitionTime).fromNow()}</td>
|
||||
<td className='optional_medium'>{fromNow(x.lastTransitionTime)}</td>
|
||||
<td className='optional_small'>{x.reason}</td>
|
||||
<td className='optional_small'>{x.message}</td>
|
||||
</tr>
|
||||
@@ -118,12 +118,9 @@ export default class Node extends Base {
|
||||
}
|
||||
}
|
||||
|
||||
function getUpTime({status}) {
|
||||
function getUptime({status}) {
|
||||
const ready = status.conditions.find(y => y.type === 'Ready');
|
||||
if (!ready) return 'N/A';
|
||||
|
||||
const last = moment(ready.lastTransitionTime);
|
||||
const diff = moment().diff(last);
|
||||
const hours = moment.duration(diff).asDays();
|
||||
return _.round(hours);
|
||||
return fromNow(ready.lastTransitionTime);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,10 @@ export default class Pod extends Base {
|
||||
<ContainersPanel spec={item && item.spec} />
|
||||
|
||||
<div className='contentPanel_header'>Events</div>
|
||||
<EventsPanel items={filteredEvents} />
|
||||
<EventsPanel
|
||||
shortList={true}
|
||||
items={filteredEvents}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
||||
pullPolicy: IfNotPresent
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: "25m"
|
||||
memory: "100Mi"
|
||||
limits:
|
||||
cpu: "100m"
|
||||
memory: "200Mi"
|
||||
memory: "200Mi"
|
||||
|
||||
@@ -25,6 +25,8 @@ spec:
|
||||
port: 4654
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 30
|
||||
nodeSelector:
|
||||
'beta.kubernetes.io/os': linux
|
||||
|
||||
---
|
||||
kind: Service
|
||||
|
||||
@@ -41,6 +41,8 @@ spec:
|
||||
secretKeyRef:
|
||||
name: k8dash
|
||||
key: secret
|
||||
nodeSelector:
|
||||
'beta.kubernetes.io/os': linux
|
||||
|
||||
---
|
||||
kind: Service
|
||||
|
||||
@@ -25,6 +25,8 @@ spec:
|
||||
port: 4654
|
||||
initialDelaySeconds: 30
|
||||
timeoutSeconds: 30
|
||||
nodeSelector:
|
||||
'beta.kubernetes.io/os': linux
|
||||
|
||||
---
|
||||
kind: Service
|
||||
|
||||
+13
-1
@@ -44,7 +44,7 @@ const app = express();
|
||||
app.disable('x-powered-by'); // for security reasons, best not to tell attackers too much about our backend
|
||||
app.use(logging);
|
||||
if (NODE_ENV !== 'production') app.use(cors());
|
||||
app.use('/', express.static('public'));
|
||||
app.use('/', preAuth, express.static('public'));
|
||||
app.get('/oidc', getOidc);
|
||||
app.post('/oidc', postOidc);
|
||||
app.use('/*', proxy(proxySettings));
|
||||
@@ -53,6 +53,18 @@ app.use(handleErrors);
|
||||
http.createServer(app).listen(4654);
|
||||
console.log('Server started');
|
||||
|
||||
function preAuth(req, res, next) {
|
||||
const auth = req.header('Authorization');
|
||||
|
||||
// If the request already contains an authorization header, pass it through to the client (as a cookie)
|
||||
if (auth) {
|
||||
res.cookie('Authorization', auth, {maxAge: 60, httpOnly: false});
|
||||
console.log('Authorization header found. Passing through to client.');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
function logging(req, res, next) {
|
||||
res.once('finish', () => console.log(req.method, req.url, res.statusCode));
|
||||
next();
|
||||
|
||||
Generated
+301
-320
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -10,16 +10,16 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@kubernetes/client-node": "^0.8.1",
|
||||
"@kubernetes/client-node": "^0.8.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.16.4",
|
||||
"express": "^4.17.1",
|
||||
"http-proxy-middleware": "^0.19.1",
|
||||
"openid-client": "^2.4.5",
|
||||
"openid-client": "^2.5.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"stream-to-string": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.18.10"
|
||||
"nodemon": "^1.19.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user