Skip to content
Closed
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
111 changes: 104 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,126 @@
import React from "react";

import * as protos from "src/js/protos";
import _ from "lodash";
import Long from "long";
import moment from "moment";
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> {
const { store } = this.props;
const rawStatus = store.encryption_status;
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),
];
}

decodeEncryptionStatus(data: Uint8Array): protos.cockroach.ccl.storageccl.engineccl.enginepbccl.EncryptionStatus {
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(data);
} catch (e) {
console.log("Error decoding protobuf: ", e);
return null;
}
return decodedStatus;
}

getEncryptionRows() {
const { store } = this.props;
const rawStatus = store.encryption_status;
if (_.isEmpty(rawStatus)) {
return null;
}

const decodedStatus = this.decodeEncryptionStatus(rawStatus);
if (decodedStatus == null) {
return null;
}

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;
}
}
93 changes: 54 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,47 @@ 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}
>
{this.renderContent()}
</Loading>
</div>
);
}
}

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

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

const selectSortedStores = createSelector(
selectStoresState,
(stores) => (
_.sortBy(stores.data.stores, (store) => store.store_id)
),
);

const selectStoresLastError = createSelector(
selectStoresState,
(stores) => (
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

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