Skip to content

Commit

Permalink
feat: UI polish incl nodes connection labels, nodes connection in jso…
Browse files Browse the repository at this point in the history
…n format removes auth metadata, agent status strips key and cert, and adds some nice padding
  • Loading branch information
addievo committed Oct 26, 2023
1 parent b4aa03b commit 652094d
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 20 deletions.
2 changes: 0 additions & 2 deletions src/agent/CommandStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@ class CommandStatus extends CommandPolykey {
clientPort: response.clientPort,
agentHost: response.agentHost,
agentPort: response.agentPort,
publicKeyJWK: response.publicKeyJwk,
certChainPEM: response.certChainPEM,
},
}),
);
Expand Down
56 changes: 53 additions & 3 deletions src/nodes/CommandConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,70 @@ class CommandAdd extends CommandPolykey {
});
const connectionEntries: Array<NodeConnectionMessage> = [];
for await (const connection of connections) {
connectionEntries.push(connection);
const toAdd = {
host: connection.host,
hostname: connection.hostname,
nodeIdEncoded: connection.nodeIdEncoded,
port: connection.port,
timeout: connection.timeout,
usageCount: connection.usageCount,
};
connectionEntries.push(toAdd);
}
return connectionEntries;
}, auth);
if (options.format === 'human') {
const output: Array<string> = [];

// Initialize variables to hold the maximum length for each column
let maxHostStringLength = 'NodeHost'.length;
let maxUsageCountLength = 'UsageCount'.length;
let maxTimeoutLength = 'Timeout'.length;

// Loop through the connections to find the maximum length for each column
for (const connection of connections) {
const hostnameString =
connection.hostname === '' ? '' : `(${connection.hostname})`;
const hostString = `${connection.nodeIdEncoded}@${connection.host}${hostnameString}:${connection.port}`;
const usageCount = connection.usageCount;
const usageCount = connection.usageCount.toString();
const timeout =
connection.timeout === -1 ? 'NA' : `${connection.timeout}`;
const outputLine = `${hostString}\t${usageCount}\t${timeout}`;

if (hostString.length > maxHostStringLength) {
maxHostStringLength = hostString.length;
}
if (usageCount.length > maxUsageCountLength) {
maxUsageCountLength = usageCount.length;
}
if (timeout.length > maxTimeoutLength) {
maxTimeoutLength = timeout.length;
}
}

// Create the header line with proper padding
const headerLine =
'NodeHost'.padEnd(maxHostStringLength) +
'\t' +
'UsageCount'.padEnd(maxUsageCountLength) +
'\t' +
'Timeout'.padEnd(maxTimeoutLength);
output.push(headerLine);

// Create the data lines with proper padding
for (const connection of connections) {
const hostnameString =
connection.hostname === '' ? '' : `(${connection.hostname})`;
const hostString = `${connection.nodeIdEncoded}@${connection.host}${hostnameString}:${connection.port}`;
const usageCount = connection.usageCount.toString();
const timeout =
connection.timeout === -1 ? 'NA' : `${connection.timeout}`;

const outputLine =
hostString.padEnd(maxHostStringLength) +
'\t' +
usageCount.padEnd(maxUsageCountLength) +
'\t' +
timeout.padEnd(maxTimeoutLength);
output.push(outputLine);
}
process.stdout.write(
Expand Down
22 changes: 16 additions & 6 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,30 @@ function outputFormatter(msg: OutputObject): string | Uint8Array {
output = output.substring(0, output.length - 1) + `\n`;
}
} else if (msg.type === 'dict') {
// Step 1: Find the length of the longest key
let maxKeyLength = 0;
for (const key in msg.data) {
if (key.length > maxKeyLength) {
maxKeyLength = key.length;
}
}

// Step 2: Use the maxKeyLength to align the keys and values
for (const key in msg.data) {
let value = msg.data[key];
// Empty string for null or undefined values
if (value == null) {
value = '';
}
value = JSON.stringify(value);
value = JSON.stringify(value).replace(/"/g, '');
// Remove the last line terminator if it exists
// This may exist if the value is multi-line string
value = value.replace(/(?:\r\n|\n)$/, '');
// If the string has line terminators internally
// Then we must append `\t` separator after each line terminator
value = value.replace(/(\r\n|\n)/g, '$1\t');
output += `${key}\t${value}\n`;

// Add spaces to make the keys equidistant
const padding = ' '.repeat(maxKeyLength - key.length);

// Append the padded key and value to the output
output += `${key}${padding}\t${value}\n`;
}
} else if (msg.type === 'json') {
output = JSON.stringify(msg.data, standardErrorReplacer);
Expand Down
4 changes: 0 additions & 4 deletions tests/agent/status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,6 @@ describe('status', () => {
clientPort: statusInfo.data.clientPort,
agentHost: statusInfo.data.agentHost,
agentPort: statusInfo.data.agentPort,
publicKeyJWK: expect.any(Object),
certChainPEM: expect.any(String),
});
});
testUtils.testIf(
Expand Down Expand Up @@ -229,8 +227,6 @@ describe('status', () => {
clientPort: statusInfo.data.clientPort,
agentHost: statusInfo.data.agentHost,
agentPort: statusInfo.data.agentPort,
publicKeyJWK: expect.any(Object),
certChainPEM: expect.any(String),
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions tests/keys/cert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('cert', () => {
});
const certCommand = JSON.parse(stdout).cert;
({ exitCode, stdout } = await testUtils.pkExec(
['agent', 'status', '--format', 'json'],
['keys', 'cert', '--format', 'json'],
{
env: {
PK_NODE_PATH: agentDir,
Expand All @@ -44,7 +44,7 @@ describe('cert', () => {
},
));
expect(exitCode).toBe(0);
const certStatus = JSON.parse(stdout).certChainPEM;
const certStatus = JSON.parse(stdout).cert;
expect(certCommand).toBe(certStatus);
});
});
129 changes: 129 additions & 0 deletions tests/nodes/connections.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { NodeId, NodeIdEncoded } from 'polykey/dist/ids/types';
import path from 'path';
import fs from 'fs';
import Logger, { LogLevel, StreamHandler } from '@matrixai/logger';
import PolykeyAgent from 'polykey/dist/PolykeyAgent';
import * as nodesUtils from 'polykey/dist/nodes/utils';
import * as keysUtils from 'polykey/dist/keys/utils';
import * as testUtils from '../utils';

describe('connections', () => {
const logger = new Logger('connections test', LogLevel.WARN, [
new StreamHandler(),
]);
const password = 'helloworld';
let dataDir: string;
let nodePath: string;
let pkAgent: PolykeyAgent;
let remoteNode: PolykeyAgent;
let localId: NodeId;
let remoteId: NodeId;
let remoteIdEncoded: NodeIdEncoded;
beforeEach(async () => {
dataDir = await fs.promises.mkdtemp(
path.join(globalThis.tmpDir, 'polykey-test-'),
);
nodePath = path.join(dataDir, 'keynode');
pkAgent = await PolykeyAgent.createPolykeyAgent({
password,
options: {
seedNodes: {}, // Explicitly no seed nodes on startup
nodePath,
agentServiceHost: '127.0.0.1',
clientServiceHost: '127.0.0.1',
agentServicePort: 55555,
clientServicePort: 55554,
keys: {
passwordOpsLimit: keysUtils.passwordOpsLimits.min,
passwordMemLimit: keysUtils.passwordMemLimits.min,
strictMemoryLock: false,
},
},
logger,
});
localId = pkAgent.keyRing.getNodeId();
// Setting up a remote keynode
remoteNode = await PolykeyAgent.createPolykeyAgent({
password,
options: {
seedNodes: {}, // Explicitly no seed nodes on startup
nodePath: path.join(dataDir, 'remoteNode'),
agentServiceHost: '127.0.0.1',
clientServiceHost: '127.0.0.1',
agentServicePort: 55553,
clientServicePort: 55552,
keys: {
passwordOpsLimit: keysUtils.passwordOpsLimits.min,
passwordMemLimit: keysUtils.passwordMemLimits.min,
strictMemoryLock: false,
},
},
logger,
});
remoteId = remoteNode.keyRing.getNodeId();
remoteIdEncoded = nodesUtils.encodeNodeId(remoteId);
await testUtils.nodesConnect(pkAgent, remoteNode);
await pkAgent.acl.setNodePerm(remoteId, {
gestalt: {
notify: null,
claim: null,
},
vaults: {},
});
await remoteNode.acl.setNodePerm(localId, {
gestalt: {
notify: null,
claim: null,
},
vaults: {},
});
});
afterEach(async () => {
await pkAgent.stop();
await remoteNode.stop();
await fs.promises.rm(dataDir, {
force: true,
recursive: true,
});
});
test('Correctly list connection information, and not list auth data', async () => {
await remoteNode.notificationsManager.sendNotification(localId, {
type: 'GestaltInvite',
});
const { exitCode } = await testUtils.pkStdio(
['nodes', 'claim', remoteIdEncoded, '--force-invite'],
{
env: {
PK_NODE_PATH: nodePath,
PK_PASSWORD: password,
},
cwd: dataDir,
},
);
const { stdout } = await testUtils.pkStdio(
['nodes', 'connections', '--format', 'json'],
{
env: {
PK_NODE_PATH: nodePath,
PK_PASSWORD: password,
},
cwd: dataDir,
},
);
expect(exitCode).toBe(0);
expect(JSON.parse(stdout)).toEqual(
expect.arrayContaining([
expect.objectContaining({
host: remoteNode.agentServiceHost,
hostname: '',
nodeIdEncoded: nodesUtils.encodeNodeId(
remoteNode.keyRing.getNodeId(),
),
port: remoteNode.agentServicePort,
timeout: expect.any(Number),
usageCount: 0,
}),
]),
);
});
});
6 changes: 3 additions & 3 deletions tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ describe('bin/utils', () => {
type: 'dict',
data: { key1: 'value1', key2: 'value2' },
}),
).toBe('key1\t"value1"\nkey2\t"value2"\n');
).toBe('key1\tvalue1\nkey2\tvalue2\n');
expect(
binUtils.outputFormatter({
type: 'dict',
data: { key1: 'first\nsecond', key2: 'first\nsecond\n' },
}),
).toBe('key1\t"first\\nsecond"\nkey2\t"first\\nsecond\\n"\n');
).toBe('key1\tfirst\\nsecond\nkey2\tfirst\\nsecond\\n\n');
expect(
binUtils.outputFormatter({
type: 'dict',
data: { key1: null, key2: undefined },
}),
).toBe('key1\t""\nkey2\t""\n');
).toBe('key1\t\nkey2\t\n');
// JSON
expect(
binUtils.outputFormatter({
Expand Down

0 comments on commit 652094d

Please sign in to comment.