Lens restructure (#540)

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman authored and GitHub committed 2020-06-30 14:35:16 +03:00
1 parent e810eab39f
commit b7974827d2
786 files changed
+14567 -24066

No files matched your search

@@ -0,0 +1,7 @@
.EventDetails {
.type {
.warning {
color: $colorError;
}
}
}
@@ -0,0 +1,79 @@
import "./event-details.scss";
import React from "react";
import kebabCase from "lodash/kebabCase";
import { Trans } from "@lingui/macro";
import { DrawerItem, DrawerTitle } from "../drawer";
import { Link } from "react-router-dom";
import { observer } from "mobx-react";
import { KubeObjectDetailsProps } from "../kube-object";
import { eventApi, KubeEvent } from "../../api/endpoints/events.api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { getDetailsUrl } from "../../navigation";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { lookupApiLink } from "../../api/kube-api";
interface Props extends KubeObjectDetailsProps<KubeEvent> {
}
@observer
export class EventDetails extends React.Component<Props> {
render() {
const { object: event } = this.props;
if (!event) return;
const { message, reason, count, type, involvedObject } = event;
const { kind, name, namespace, fieldPath } = involvedObject;
return (
<div className="EventDetails">
<KubeObjectMeta object={event}/>
<DrawerItem name={<Trans>Message</Trans>}>
{message}
</DrawerItem>
<DrawerItem name={<Trans>Reason</Trans>}>
{reason}
</DrawerItem>
<DrawerItem name={<Trans>Source</Trans>}>
{event.getSource()}
</DrawerItem>
<DrawerItem name={<Trans>First seen</Trans>}>
{event.getFirstSeenTime()} <Trans>ago</Trans> {event.firstTimestamp}
</DrawerItem>
<DrawerItem name={<Trans>Last seen</Trans>}>
{event.getLastSeenTime()} <Trans>ago</Trans> {event.lastTimestamp}
</DrawerItem>
<DrawerItem name={<Trans>Count</Trans>}>
{count}
</DrawerItem>
<DrawerItem name={<Trans>Type</Trans>} className="type">
<span className={kebabCase(type)}>{type}</span>
</DrawerItem>
<DrawerTitle title={<Trans>Involved object</Trans>}/>
<Table>
<TableHead>
<TableCell><Trans>Name</Trans></TableCell>
<TableCell><Trans>Namespace</Trans></TableCell>
<TableCell><Trans>Kind</Trans></TableCell>
<TableCell><Trans>Field Path</Trans></TableCell>
</TableHead>
<TableRow>
<TableCell>
<Link to={getDetailsUrl(lookupApiLink(involvedObject, event))}>
{name}
</Link>
</TableCell>
<TableCell>{namespace}</TableCell>
<TableCell>{kind}</TableCell>
<TableCell>{fieldPath}</TableCell>
</TableRow>
</Table>
</div>
)
}
}
apiManager.registerViews(eventApi, {
Details: EventDetails,
});
@@ -0,0 +1,52 @@
import groupBy from "lodash/groupBy";
import compact from "lodash/compact";
import { KubeObjectStore } from "../../kube-object.store";
import { autobind } from "../../utils";
import { eventApi, KubeEvent } from "../../api/endpoints/events.api";
import { KubeObject } from "../../api/kube-object";
import { Pod } from "../../api/endpoints/pods.api";
import { podsStore } from "../+workloads-pods/pods.store";
import { apiManager } from "../../api/api-manager";
@autobind()
export class EventStore extends KubeObjectStore<KubeEvent> {
api = eventApi
limit = 1000
protected bindWatchEventsUpdater() {
return super.bindWatchEventsUpdater(5000);
}
protected sortItems(items: KubeEvent[]) {
return super.sortItems(items, [
event => event.metadata.creationTimestamp
], "desc")
}
getEventsByObject(obj: KubeObject): KubeEvent[] {
return this.items.filter(evt => {
if(obj.kind == "Node") {
return obj.getName() == evt.involvedObject.uid && evt.involvedObject.kind == "Node";
}
return obj.getId() == evt.involvedObject.uid;
});
}
getWarnings() {
const warnings = this.items.filter(event => event.type == "Warning");
const groupsByInvolvedObject = groupBy(warnings, warning => warning.involvedObject.uid);
const eventsWithError = Object.values(groupsByInvolvedObject).map(events => {
const recent = events[0];
const { kind, uid } = recent.involvedObject;
if (kind == Pod.kind) { // Wipe out running pods
const pod = podsStore.items.find(pod => pod.getId() == uid);
if (!pod || (!pod.hasIssues() && pod.spec.priority < 500000)) return;
}
return recent;
});
return compact(eventsWithError);
}
}
export const eventStore = new EventStore();
apiManager.registerStore(eventApi, eventStore);
@@ -0,0 +1,8 @@
import { RouteProps } from "react-router";
import { buildURL } from "../../navigation";
export const eventRoute: RouteProps = {
path: "/events"
}
export const eventsURL = buildURL(eventRoute.path);
@@ -0,0 +1,32 @@
.Events {
.Table {
.TableCell {
&.message {
flex-grow: 3;
&.warning {
color: $colorError;
}
}
&.object {
flex-grow: 3;
text-overflow: ellipsis;
white-space: nowrap;
}
&.source {
flex-grow: 1.5;
}
}
}
.help-icon {
margin-left: $margin / 2;
}
.show-all {
margin: $padding * 2 auto;
min-width: 150px;
}
}
+130
View File
@@ -0,0 +1,130 @@
import "./events.scss";
import React from "react";
import { observer } from "mobx-react";
import { MainLayout } from "../layout/main-layout";
import { eventStore } from "./event.store";
import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
import { Trans } from "@lingui/macro";
import { KubeEvent } from "../../api/endpoints/events.api";
import { Tooltip, TooltipContent } from "../tooltip";
import { Link } from "react-router-dom";
import { cssNames, IClassName, stopPropagation } from "../../utils";
import { Icon } from "../icon";
import { getDetailsUrl } from "../../navigation";
import { lookupApiLink } from "../../api/kube-api";
enum sortBy {
namespace = "namespace",
object = "object",
type = "type",
count = "count",
age = "age",
}
interface Props extends Partial<KubeObjectListLayoutProps> {
className?: IClassName;
compact?: boolean;
compactLimit?: number;
}
const defaultProps: Partial<Props> = {
compactLimit: 10,
};
@observer
export class Events extends React.Component<Props> {
static defaultProps = defaultProps as object;
render() {
const { compact, compactLimit, className, ...layoutProps } = this.props;
const events = (
<KubeObjectListLayout
{...layoutProps}
className={cssNames("Events", className, { compact })}
store={eventStore}
isSelectable={false}
sortingCallbacks={{
[sortBy.namespace]: (event: KubeEvent) => event.getNs(),
[sortBy.type]: (event: KubeEvent) => event.involvedObject.kind,
[sortBy.object]: (event: KubeEvent) => event.involvedObject.name,
[sortBy.count]: (event: KubeEvent) => event.count,
[sortBy.age]: (event: KubeEvent) => event.metadata.creationTimestamp,
}}
searchFilters={[
(event: KubeEvent) => event.getSearchFields(),
(event: KubeEvent) => event.message,
(event: KubeEvent) => event.getSource(),
(event: KubeEvent) => event.involvedObject.name,
]}
renderHeaderTitle={<Trans>Events</Trans>}
customizeHeader={({ title, info }) => (
compact ? title : ({
info: (
<>
{info}
<Icon
small
material="help_outline"
className="help-icon"
tooltip={<Trans>Limited to {eventStore.limit}</Trans>}
/>
</>
)
})
)}
renderTableHeader={[
{ title: <Trans>Message</Trans>, className: "message" },
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
{ title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type },
{ title: <Trans>Involved Object</Trans>, className: "object", sortBy: sortBy.object },
{ title: <Trans>Source</Trans>, className: "source" },
{ title: <Trans>Count</Trans>, className: "count", sortBy: sortBy.count },
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
]}
renderTableContents={(event: KubeEvent) => {
const { involvedObject, type, message } = event;
const { kind, name } = involvedObject;
const tooltipId = `message-${event.getId()}`;
const isWarning = type === "Warning";
const detailsUrl = getDetailsUrl(lookupApiLink(involvedObject, event));
return [
{
className: {
warning: isWarning
},
title: (
<>
<span id={tooltipId}>{message}</span>
<Tooltip htmlFor={tooltipId} following>
<TooltipContent narrow warning={isWarning}>
{message}
</TooltipContent>
</Tooltip>
</>
)
},
event.getNs(),
kind,
<Link to={detailsUrl} title={name} onClick={stopPropagation}>{name}</Link>,
event.getSource(),
event.count,
event.getAge(),
]
}}
virtual={!compact}
filterItems={[
items => compact ? items.slice(0, compactLimit) : items,
]}
/>
)
if (compact) {
return events;
}
return (
<MainLayout>
{events}
</MainLayout>
)
}
}
+3
View File
@@ -0,0 +1,3 @@
export * from "./events";
export * from "./events.route";
export * from "./event-details";
@@ -0,0 +1,24 @@
.KubeEventDetails {
.event {
margin-bottom: $margin * 4;
&:last-child {
margin-bottom: 0;
}
.title {
margin-top: $margin * 2;
margin-bottom: $margin;
font-weight: bold;
word-break: break-word;
&.warning {
color: $colorError;
}
}
}
.no-items {
text-align: center;
}
}
@@ -0,0 +1,64 @@
import "./kube-event-details.scss";
import React from "react";
import { observer } from "mobx-react";
import { Trans } from "@lingui/macro";
import { KubeObject } from "../../api/kube-object";
import { DrawerItem, DrawerTitle } from "../drawer";
import { cssNames } from "../../utils";
import { Icon } from "../icon";
import { eventStore } from "./event.store";
interface Props {
object: KubeObject;
}
@observer
export class KubeEventDetails extends React.Component<Props> {
async componentDidMount() {
eventStore.loadAll();
}
render() {
const { object } = this.props;
const events = eventStore.getEventsByObject(object);
if (!events.length) {
return (
<DrawerTitle className="flex gaps align-center">
<span><Trans>Events</Trans></span>
</DrawerTitle>
)
}
return (
<>
<DrawerTitle className="flex gaps align-center">
<span><Trans>Events</Trans></span>
</DrawerTitle>
<div className="KubeEventDetails">
{events.map(evt => {
const { message, count, lastTimestamp, involvedObject } = evt
return (
<div className="event" key={evt.getId()}>
<div className={cssNames("title", { warning: evt.isWarning() })}>
{message}
</div>
<DrawerItem name={<Trans>Source</Trans>}>
{evt.getSource()}
</DrawerItem>
<DrawerItem name={<Trans>Count</Trans>}>
{count}
</DrawerItem>
<DrawerItem name={<Trans>Sub-object</Trans>}>
{involvedObject.fieldPath}
</DrawerItem>
<DrawerItem name={<Trans>Last seen</Trans>}>
{lastTimestamp}
</DrawerItem>
</div>
)
})}
</div>
</>
)
}
}
@@ -0,0 +1,17 @@
.KubeEventIcon {
&.warning {
color: $golden;
}
}
.KubeEventTooltip {
white-space: normal;
.age {
margin-top: $margin;
.Icon {
margin-right: $margin;
}
}
}
@@ -0,0 +1,49 @@
import "./kube-event-icon.scss";
import React from "react";
import { Icon } from "../icon";
import { TooltipContent } from "../tooltip";
import { KubeObject } from "../../api/kube-object";
import { eventStore } from "./event.store";
import { cssNames } from "../../utils";
import { KubeEvent } from "../../api/endpoints/events.api";
interface Props {
object: KubeObject;
showWarningsOnly?: boolean;
filterEvents?: (events: KubeEvent[]) => KubeEvent[];
}
const defaultProps: Partial<Props> = {
showWarningsOnly: true,
};
export class KubeEventIcon extends React.Component<Props> {
static defaultProps = defaultProps as object;
render() {
const { object, showWarningsOnly, filterEvents } = this.props;
const events = eventStore.getEventsByObject(object);
let warnings = events.filter(evt => evt.isWarning());
if (filterEvents) warnings = filterEvents(warnings);
if (!events.length || (showWarningsOnly && !warnings.length)) {
return null;
}
const event = [...warnings, ...events][0]; // get latest event
return (
<Icon
material="warning"
className={cssNames("KubeEventIcon", { warning: event.isWarning() })}
tooltip={(
<TooltipContent className="KubeEventTooltip">
{event.message}
<div className="age">
<Icon material="access_time"/>
{event.getAge(undefined, undefined, true)}
</div>
</TooltipContent>
)}
/>
)
}
}