Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 95 additions & 7 deletions pkg/ui/ccl/src/views/reports/containers/stores/encryption.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,117 @@
import React from "react";
import _ from "lodash";
import Long from "long";
import moment from "moment";

import * as protos from "src/js/protos";
import { EncryptionStatusProps } from "oss/src/views/reports/containers/stores/encryption";
import { Bytes } from "src/util/format";
import { FixLong } from "src/util/fixLong";

export default class EncryptionStatus extends React.Component<EncryptionStatusProps, {}> {
const dateFormat = "Y-MM-DD HH:mm:ss";

export default class EncryptionStatus {
props: EncryptionStatusProps;

constructor(props: EncryptionStatusProps) {
this.props = props;
}

renderHeaderRow(header: string) {
return (
<tr className="stores-table__row">
<td colSpan={2} className="stores-table__cell stores-table__cell--header--row">{header}</td>
</tr>
);
}

renderSimpleRow(header: string, value: string) {
return (
<tr className="stores-table__row">
<th className="stores-table__cell stores-table__cell--header">{header}</th>
<td className="stores-table__cell" title={value}><pre>{value}</pre></td>
<td className="stores-table__cell" title={value}>{value}</td>
</tr>
);
}

render(): React.ReactElement<any> {
renderStoreKey(key: protos.cockroach.ccl.storageccl.engineccl.enginepbccl.IKeyInfo) {
// Get the enum name from its value (eg: "AES128_CTR" for 1).
const encryptionType = protos.cockroach.ccl.storageccl.engineccl.enginepbccl.EncryptionType[key.encryption_type];
const createdAt = moment.unix(FixLong(key.creation_time).toNumber()).utc().format(dateFormat);

return [
this.renderHeaderRow("Active Store Key: user specified"),
this.renderSimpleRow("Algorithm", encryptionType),
this.renderSimpleRow("Key ID", key.key_id),
this.renderSimpleRow("Created", createdAt),
this.renderSimpleRow("Source", key.source),
];
}

renderDataKey(key: protos.cockroach.ccl.storageccl.engineccl.enginepbccl.IKeyInfo) {
// Get the enum name from its value (eg: "AES128_CTR" for 1).
const encryptionType = protos.cockroach.ccl.storageccl.engineccl.enginepbccl.EncryptionType[key.encryption_type];
const createdAt = moment.unix(key.creation_time.toNumber()).utc().format(dateFormat);

return [
this.renderHeaderRow("Active Data Key: automatically generated"),
this.renderSimpleRow("Algorithm", encryptionType),
this.renderSimpleRow("Key ID", key.key_id),
this.renderSimpleRow("Created", createdAt),
this.renderSimpleRow("Parent Key ID", key.parent_key_id),
];
}

calculatePercentage(active: Long, total: Long): number {
if (active.eq(total)) {
return 100;
}
return Long.fromInt(100).mul(active).toNumber() / total.toNumber();
}

renderFileStats(stats: protos.cockroach.server.serverpb.IStoreDetails) {
const totalFiles = FixLong(stats.total_files);
const totalBytes = FixLong(stats.total_bytes);
if (totalFiles.eq(0) && totalBytes.eq(0)) {
return null;
}

const activeFiles = FixLong(stats.active_key_files);
const activeBytes = FixLong(stats.active_key_bytes);

let fileDetails = this.calculatePercentage(activeFiles, totalFiles).toFixed(2) + "%";
fileDetails += " (" + activeFiles + "/" + totalFiles + ")";

let byteDetails = this.calculatePercentage(activeBytes, totalBytes).toFixed(2) + "%";
byteDetails += " (" + Bytes(activeBytes.toNumber()) + "/" + Bytes(totalBytes.toNumber()) + ")";

return [
this.renderHeaderRow("Encryption Progress: fraction encrypted using the active data key"),
this.renderSimpleRow("Files", fileDetails),
this.renderSimpleRow("Bytes", byteDetails),
];
}

getEncryptionRows() {
const { store } = this.props;
const rawStatus = store.encryption_status;
if (_.isEmpty(rawStatus)) {
return [ this.renderSimpleRow("Encryption status", "Not encrypted") ];
}

let decodedStatus;

// Attempt to decode protobuf.
try {
const decodedStatus = protos.cockroach.ccl.storageccl.engineccl.enginepbccl.EncryptionStatus.decode(rawStatus);
return this.renderSimpleRow("Encryption Status", JSON.stringify(decodedStatus.toJSON(), null, 2));
decodedStatus = protos.cockroach.ccl.storageccl.engineccl.enginepbccl.EncryptionStatus.decode(rawStatus);
} catch (e) {
console.log("Error decoding protobuf: ", e);
return null;
return [ this.renderSimpleRow("Encryption status", "Error decoding protobuf: " + e.toString()) ];
}

return [
this.renderStoreKey(decodedStatus.active_store_key),
this.renderDataKey(decodedStatus.active_data_key),
this.renderFileStats(store),
];
}
}
5 changes: 2 additions & 3 deletions pkg/ui/src/views/reports/containers/stores/encryption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ export interface EncryptionStatusProps {
store: protos.cockroach.server.serverpb.IStoreDetails;
}

export default class EncryptionStatus extends React.Component<EncryptionStatusProps, {}> {

render(): React.ReactElement<any> {
export default class EncryptionStatus {
getEncryptionRows(): React.ReactElement<any> {
return null;
}
}
100 changes: 61 additions & 39 deletions pkg/ui/src/views/reports/containers/stores/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import React from "react";
import { Helmet } from "react-helmet";
import { connect } from "react-redux";
import { RouterState } from "react-router";
import { createSelector } from "reselect";

import * as protos from "src/js/protos";
import { storesRequestKey, refreshStores } from "src/redux/apiReducers";
import { AdminUIState } from "src/redux/state";
import { nodeIDAttr } from "src/util/constants";
import EncryptionStatus from "src/views/reports/containers/stores/encryption";
import Loading from "src/views/shared/components/loading";

import "./stores.styl";
import spinner from "assets/spinner.gif";

interface StoresOwnProps {
stores: protos.cockroach.server.serverpb.StoresResponse;
stores: protos.cockroach.server.serverpb.IStoreDetails[];
loading: boolean;
lastError: Error;
refreshStores: typeof refreshStores;
}
Expand Down Expand Up @@ -58,55 +61,41 @@ class Stores extends React.Component<StoresProps, {}> {
);
}

renderStore(store: protos.cockroach.server.serverpb.IStoreDetails, key: number) {
renderStore = (store: protos.cockroach.server.serverpb.IStoreDetails) => {
return (
<table key={key} className="stores-table">
<table key={store.store_id} className="stores-table">
<tbody>
{ this.renderSimpleRow("Store ID", store.store_id.toString()) }
<EncryptionStatus store={store} />
{ new EncryptionStatus({store: store}).getEncryptionRows() }
</tbody>
</table>
);
}

render() {
renderContent = () => {
const nodeID = this.props.params[nodeIDAttr];
if (!_.isNil(this.props.lastError)) {
return (
<div className="section">
<Helmet>
<title>Stores | Debug</title>
</Helmet>
<h1>Stores</h1>
<h2>Error loading stores for node {nodeID}</h2>
</div>
<h2>Error loading stores for node {nodeID}</h2>
);
}

const { stores } = this.props;
if (_.isEmpty(stores)) {
return (
<div className="section">
<Helmet>
<title>Stores | Debug</title>
</Helmet>
<h1>Stores</h1>
<h2>Loading cluster status...</h2>
</div>
<h2>No stores were found on node {nodeID}.</h2>
);
}

if (_.isEmpty(stores.stores)) {
return (
<div className="section">
<Helmet>
<title>Stores | Debug</title>
</Helmet>
<h1>Stores</h1>
<h2>No stores were found on node {this.props.params[nodeIDAttr]}.</h2>
</div>
);
}
return (
<React.Fragment>
{ _.map(this.props.stores, this.renderStore) }
</React.Fragment>
);
}

render() {
const nodeID = this.props.params[nodeIDAttr];
let header: string = null;
if (_.isNaN(parseInt(nodeID, 10))) {
header = "Local Node";
Expand All @@ -121,21 +110,54 @@ class Stores extends React.Component<StoresProps, {}> {
</Helmet>
<h1>Stores</h1>
<h2>{header} stores</h2>
{
_.map(stores.stores, (store, key) => (
this.renderStore(store, key)
))
}
<Loading
loading={this.props.loading}
className="loading-image loading-image__spinner"
image={spinner}
render={this.renderContent}
/>
</div>
);
}
}

function mapStateToProps(state: AdminUIState, props: StoresProps) {
function selectStoresState(state: AdminUIState, props: StoresProps) {
const nodeIDKey = storesRequestKey(storesRequestFromProps(props));
return state.cachedData.stores[nodeIDKey];
}

const selectStoresLoading = createSelector(
selectStoresState,
(stores) => _.isEmpty(stores) || _.isEmpty(stores.data),
);

const selectSortedStores = createSelector(
selectStoresLoading,
selectStoresState,
(loading, stores) => {
if (loading) {
return null;
}
return _.sortBy(stores.data.stores, (store) => store.store_id);
},
);

const selectStoresLastError = createSelector(
selectStoresLoading,
selectStoresState,
(loading, stores) => {
if (loading) {
return null;
}
return stores.lastError;
},
);

function mapStateToProps(state: AdminUIState, props: StoresProps) {
return {
stores: state.cachedData.stores[nodeIDKey] && state.cachedData.stores[nodeIDKey].data,
lastError: state.cachedData.stores[nodeIDKey] && state.cachedData.stores[nodeIDKey].lastError,
stores: selectSortedStores(state, props),
loading: selectStoresLoading(state, props),
lastError: selectStoresLastError(state, props),
};
}

Expand Down
8 changes: 0 additions & 8 deletions pkg/ui/src/views/reports/containers/stores/stores.styl

This file was deleted.

21 changes: 21 additions & 0 deletions pkg/ui/styl/pages/reports.styl
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,27 @@ $reports-table
margin 0
padding 0

.stores-table
@extend $reports-table

&__cell
background-color white
padding 6px 12px
max-width none
width 100%

&--header
background-color $link-color
text-align right
width 150px
min-width 150px

&--row
background-color $link-color
text-align center
color white
font-weight 900

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BramGruneir is there a reason that all the debug reports have their styles in here, whereas top-level pages have their own style files?

.connections-table
@extend $reports-table
font-size 12px
Expand Down