@a9s/cli 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/App.js +30 -12
- package/dist/src/adapters/backStackUtils.js +14 -0
- package/dist/src/components/DetailPanel.js +3 -5
- package/dist/src/components/DiffViewer.js +3 -5
- package/dist/src/components/ErrorStatePanel.js +1 -1
- package/dist/src/components/HUD.js +2 -2
- package/dist/src/components/HelpPanel.js +3 -9
- package/dist/src/components/Table/index.js +1 -14
- package/dist/src/components/TableSkeleton.js +1 -5
- package/dist/src/components/YankHelpPanel.js +13 -4
- package/dist/src/features/AppMainView.integration.test.js +1 -1
- package/dist/src/features/AppMainView.js +4 -4
- package/dist/src/hooks/useAppController.js +7 -29
- package/dist/src/hooks/useAppData.js +2 -9
- package/dist/src/hooks/useHelpPanel.js +8 -4
- package/dist/src/hooks/usePickerManager.js +1 -9
- package/dist/src/hooks/usePickerTable.js +2 -9
- package/dist/src/hooks/useTimedFeedback.js +31 -0
- package/dist/src/hooks/useYankMode.js +5 -14
- package/dist/src/utils/aws.js +4 -0
- package/dist/src/utils/errorHelpers.js +11 -0
- package/dist/src/utils/rowUtils.js +10 -0
- package/dist/src/utils/scrollUtils.js +11 -0
- package/dist/src/utils/textUtils.js +16 -0
- package/dist/src/views/dynamodb/adapter.js +5 -13
- package/dist/src/views/dynamodb/capabilities/detailCapability.js +2 -2
- package/dist/src/views/dynamodb/capabilities/yankCapability.js +1 -1
- package/dist/src/views/iam/adapter.js +28 -23
- package/dist/src/views/route53/adapter.js +5 -13
- package/dist/src/views/route53/capabilities/detailCapability.js +2 -2
- package/dist/src/views/route53/capabilities/yankCapability.js +1 -1
- package/dist/src/views/s3/adapter.js +2 -10
- package/dist/src/views/s3/capabilities/actionCapability.js +1 -11
- package/dist/src/views/secretsmanager/adapter.js +6 -19
- package/dist/src/views/secretsmanager/capabilities/actionCapability.js +4 -35
- package/dist/src/views/secretsmanager/capabilities/detailCapability.js +2 -9
- package/dist/src/views/secretsmanager/capabilities/editCapability.js +5 -39
- package/dist/src/views/secretsmanager/capabilities/yankOptions.js +2 -9
- package/dist/src/views/secretsmanager/client.js +31 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Truncate a string to maxLen, padding with spaces if shorter. */
|
|
2
|
+
export function truncate(str, maxLen) {
|
|
3
|
+
if (str.length <= maxLen)
|
|
4
|
+
return str.padEnd(maxLen);
|
|
5
|
+
return str.slice(0, maxLen - 1) + "…";
|
|
6
|
+
}
|
|
7
|
+
/** Truncate a string to maxLen without padding. */
|
|
8
|
+
export function truncateNoPad(str, maxLen) {
|
|
9
|
+
if (maxLen <= 0)
|
|
10
|
+
return "";
|
|
11
|
+
if (str.length <= maxLen)
|
|
12
|
+
return str;
|
|
13
|
+
if (maxLen === 1)
|
|
14
|
+
return "…";
|
|
15
|
+
return str.slice(0, maxLen - 1) + "…";
|
|
16
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { textCell } from "../../types.js";
|
|
2
|
-
import { runAwsJsonAsync } from "../../utils/aws.js";
|
|
2
|
+
import { runAwsJsonAsync, buildRegionArgs } from "../../utils/aws.js";
|
|
3
|
+
import { createBackStackHelpers } from "../../adapters/backStackUtils.js";
|
|
3
4
|
import { atom } from "jotai";
|
|
4
5
|
import { getDefaultStore } from "jotai";
|
|
5
6
|
import { unwrapDynamoValue, formatBillingMode, formatDynamoValue, getDynamoType, extractPkValue, extractSkValue, } from "./utils.js";
|
|
@@ -14,7 +15,7 @@ const tableDescriptionCache = new Map();
|
|
|
14
15
|
const itemsCache = new Map();
|
|
15
16
|
export function createDynamoDBServiceAdapter(endpointUrl, region) {
|
|
16
17
|
const store = getDefaultStore();
|
|
17
|
-
const regionArgs = region
|
|
18
|
+
const regionArgs = buildRegionArgs(region);
|
|
18
19
|
const getLevel = () => store.get(dynamoDBLevelAtom);
|
|
19
20
|
const setLevel = (level) => store.set(dynamoDBLevelAtom, level);
|
|
20
21
|
const getBackStack = () => store.get(dynamoDBBackStackAtom);
|
|
@@ -272,16 +273,7 @@ export function createDynamoDBServiceAdapter(endpointUrl, region) {
|
|
|
272
273
|
// item-fields level: leaf, no drill-down
|
|
273
274
|
return { action: "none" };
|
|
274
275
|
};
|
|
275
|
-
const canGoBack = (
|
|
276
|
-
const goBack = () => {
|
|
277
|
-
const backStack = getBackStack();
|
|
278
|
-
if (backStack.length > 0) {
|
|
279
|
-
const newStack = backStack.slice(0, -1);
|
|
280
|
-
const frame = backStack[backStack.length - 1];
|
|
281
|
-
setBackStack(newStack);
|
|
282
|
-
setLevel(frame.level);
|
|
283
|
-
}
|
|
284
|
-
};
|
|
276
|
+
const { canGoBack, goBack } = createBackStackHelpers(getLevel, setLevel, getBackStack, setBackStack);
|
|
285
277
|
const getPath = () => {
|
|
286
278
|
const level = getLevel();
|
|
287
279
|
if (level.kind === "tables")
|
|
@@ -300,7 +292,7 @@ export function createDynamoDBServiceAdapter(endpointUrl, region) {
|
|
|
300
292
|
};
|
|
301
293
|
// Compose capabilities
|
|
302
294
|
const detailCapability = createDynamoDBDetailCapability(region, getLevel);
|
|
303
|
-
const yankCapability = createDynamoDBYankCapability(
|
|
295
|
+
const yankCapability = createDynamoDBYankCapability();
|
|
304
296
|
return {
|
|
305
297
|
id: "dynamodb",
|
|
306
298
|
label: "DynamoDB",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { runAwsJsonAsync } from "../../../utils/aws.js";
|
|
1
|
+
import { runAwsJsonAsync, buildRegionArgs } from "../../../utils/aws.js";
|
|
2
2
|
import { formatBillingMode, formatKeySchema } from "../utils.js";
|
|
3
3
|
export function createDynamoDBDetailCapability(region, getLevel) {
|
|
4
|
-
const regionArgs = region
|
|
4
|
+
const regionArgs = buildRegionArgs(region);
|
|
5
5
|
const getDetails = async (row) => {
|
|
6
6
|
const meta = row.meta;
|
|
7
7
|
if (!meta) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createYankCapability } from "../../../adapters/capabilities/YankCapability.js";
|
|
2
2
|
import { DynamoDBRowMetaSchema } from "../schema.js";
|
|
3
3
|
import { DynamoDBYankOptions } from "./yankOptions.js";
|
|
4
|
-
export function createDynamoDBYankCapability(
|
|
4
|
+
export function createDynamoDBYankCapability() {
|
|
5
5
|
return createYankCapability(DynamoDBYankOptions, DynamoDBRowMetaSchema, {});
|
|
6
6
|
}
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { runAwsJsonAsync } from "../../utils/aws.js";
|
|
2
2
|
import { textCell } from "../../types.js";
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
import { getDefaultStore } from "jotai";
|
|
3
5
|
import { formatDate } from "./utils.js";
|
|
4
6
|
import { createIamEditCapability } from "./capabilities/editCapability.js";
|
|
5
7
|
import { createIamDetailCapability } from "./capabilities/detailCapability.js";
|
|
6
8
|
import { createIamYankCapability } from "./capabilities/yankCapability.js";
|
|
7
9
|
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
10
|
+
import { createBackStackHelpers } from "../../adapters/backStackUtils.js";
|
|
11
|
+
export const iamLevelAtom = atom({ kind: "root" });
|
|
12
|
+
export const iamBackStackAtom = atom([]);
|
|
8
13
|
function getIamMeta(row) {
|
|
9
14
|
return row.meta;
|
|
10
15
|
}
|
|
11
16
|
export function createIamServiceAdapter() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
const getBackStack = () => backStack;
|
|
19
|
-
const setBackStack = (newStack) => {
|
|
20
|
-
backStack = newStack;
|
|
21
|
-
};
|
|
17
|
+
const store = getDefaultStore();
|
|
18
|
+
const getLevel = () => store.get(iamLevelAtom);
|
|
19
|
+
const setLevel = (newLevel) => store.set(iamLevelAtom, newLevel);
|
|
20
|
+
const getBackStack = () => store.get(iamBackStackAtom);
|
|
21
|
+
const setBackStack = (newStack) => store.set(iamBackStackAtom, newStack);
|
|
22
22
|
const getColumns = () => {
|
|
23
|
+
const level = getLevel();
|
|
23
24
|
switch (level.kind) {
|
|
24
25
|
case "root":
|
|
25
26
|
case "role-menu":
|
|
@@ -44,6 +45,7 @@ export function createIamServiceAdapter() {
|
|
|
44
45
|
}
|
|
45
46
|
};
|
|
46
47
|
const getRows = async () => {
|
|
48
|
+
const level = getLevel();
|
|
47
49
|
switch (level.kind) {
|
|
48
50
|
case "root":
|
|
49
51
|
return [
|
|
@@ -70,19 +72,20 @@ export function createIamServiceAdapter() {
|
|
|
70
72
|
meta: { type: "role", roleName: role.RoleName, arn: role.Arn },
|
|
71
73
|
}));
|
|
72
74
|
}
|
|
73
|
-
case "role-menu":
|
|
75
|
+
case "role-menu": {
|
|
76
|
+
const { roleName } = level;
|
|
74
77
|
return [
|
|
75
78
|
{
|
|
76
|
-
id: `${
|
|
79
|
+
id: `${roleName}::inline`,
|
|
77
80
|
cells: { name: textCell("Inline Policies"), type: textCell("Role Inline Policies") },
|
|
78
81
|
meta: {
|
|
79
82
|
type: "menu",
|
|
80
83
|
kind: "role-inline-policies",
|
|
81
|
-
roleName
|
|
84
|
+
roleName,
|
|
82
85
|
},
|
|
83
86
|
},
|
|
84
87
|
{
|
|
85
|
-
id: `${
|
|
88
|
+
id: `${roleName}::attached`,
|
|
86
89
|
cells: {
|
|
87
90
|
name: textCell("Attached Policies"),
|
|
88
91
|
type: textCell("Role Attached Policies"),
|
|
@@ -90,10 +93,11 @@ export function createIamServiceAdapter() {
|
|
|
90
93
|
meta: {
|
|
91
94
|
type: "menu",
|
|
92
95
|
kind: "role-attached-policies",
|
|
93
|
-
roleName
|
|
96
|
+
roleName,
|
|
94
97
|
},
|
|
95
98
|
},
|
|
96
99
|
];
|
|
100
|
+
}
|
|
97
101
|
case "role-inline-policies": {
|
|
98
102
|
const { roleName } = level;
|
|
99
103
|
const data = await runAwsJsonAsync([
|
|
@@ -162,6 +166,8 @@ export function createIamServiceAdapter() {
|
|
|
162
166
|
}
|
|
163
167
|
};
|
|
164
168
|
const onSelect = async (row) => {
|
|
169
|
+
const level = getLevel();
|
|
170
|
+
const backStack = getBackStack();
|
|
165
171
|
const nextBackStack = [...backStack, { level, selectedIndex: 0 }];
|
|
166
172
|
const meta = getIamMeta(row);
|
|
167
173
|
if (level.kind === "root" && meta?.type === "menu" && meta.kind === "roles") {
|
|
@@ -195,15 +201,9 @@ export function createIamServiceAdapter() {
|
|
|
195
201
|
}
|
|
196
202
|
return { action: "none" };
|
|
197
203
|
};
|
|
198
|
-
const canGoBack = (
|
|
199
|
-
const goBack = () => {
|
|
200
|
-
const frame = getBackStack()[getBackStack().length - 1];
|
|
201
|
-
if (!frame)
|
|
202
|
-
return;
|
|
203
|
-
setBackStack(getBackStack().slice(0, -1));
|
|
204
|
-
setLevel(frame.level);
|
|
205
|
-
};
|
|
204
|
+
const { canGoBack, goBack } = createBackStackHelpers(getLevel, setLevel, getBackStack, setBackStack);
|
|
206
205
|
const getPath = () => {
|
|
206
|
+
const level = getLevel();
|
|
207
207
|
switch (level.kind) {
|
|
208
208
|
case "root":
|
|
209
209
|
return "iam://";
|
|
@@ -220,6 +220,7 @@ export function createIamServiceAdapter() {
|
|
|
220
220
|
}
|
|
221
221
|
};
|
|
222
222
|
const getContextLabel = () => {
|
|
223
|
+
const level = getLevel();
|
|
223
224
|
switch (level.kind) {
|
|
224
225
|
case "root":
|
|
225
226
|
return "🔐 IAM Resources";
|
|
@@ -250,6 +251,10 @@ export function createIamServiceAdapter() {
|
|
|
250
251
|
goBack,
|
|
251
252
|
getPath,
|
|
252
253
|
getContextLabel,
|
|
254
|
+
reset() {
|
|
255
|
+
setLevel({ kind: "root" });
|
|
256
|
+
setBackStack([]);
|
|
257
|
+
},
|
|
253
258
|
capabilities: {
|
|
254
259
|
edit: editCapability,
|
|
255
260
|
detail: detailCapability,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { textCell } from "../../types.js";
|
|
2
|
-
import { runAwsJsonAsync } from "../../utils/aws.js";
|
|
2
|
+
import { runAwsJsonAsync, buildRegionArgs } from "../../utils/aws.js";
|
|
3
|
+
import { createBackStackHelpers } from "../../adapters/backStackUtils.js";
|
|
3
4
|
import { atom } from "jotai";
|
|
4
5
|
import { getDefaultStore } from "jotai";
|
|
5
6
|
import { createRoute53DetailCapability } from "./capabilities/detailCapability.js";
|
|
@@ -9,7 +10,7 @@ export const route53LevelAtom = atom({ kind: "zones" });
|
|
|
9
10
|
export const route53BackStackAtom = atom([]);
|
|
10
11
|
export function createRoute53ServiceAdapter(endpointUrl, region) {
|
|
11
12
|
const store = getDefaultStore();
|
|
12
|
-
const regionArgs = region
|
|
13
|
+
const regionArgs = buildRegionArgs(region);
|
|
13
14
|
const getLevel = () => store.get(route53LevelAtom);
|
|
14
15
|
const setLevel = (level) => store.set(route53LevelAtom, level);
|
|
15
16
|
const getBackStack = () => store.get(route53BackStackAtom);
|
|
@@ -131,16 +132,7 @@ export function createRoute53ServiceAdapter(endpointUrl, region) {
|
|
|
131
132
|
// records level: leaf, no drill-down
|
|
132
133
|
return { action: "none" };
|
|
133
134
|
};
|
|
134
|
-
const canGoBack = (
|
|
135
|
-
const goBack = () => {
|
|
136
|
-
const backStack = getBackStack();
|
|
137
|
-
if (backStack.length > 0) {
|
|
138
|
-
const newStack = backStack.slice(0, -1);
|
|
139
|
-
const frame = backStack[backStack.length - 1];
|
|
140
|
-
setBackStack(newStack);
|
|
141
|
-
setLevel(frame.level);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
135
|
+
const { canGoBack, goBack } = createBackStackHelpers(getLevel, setLevel, getBackStack, setBackStack);
|
|
144
136
|
const getPath = () => {
|
|
145
137
|
const level = getLevel();
|
|
146
138
|
if (level.kind === "zones")
|
|
@@ -155,7 +147,7 @@ export function createRoute53ServiceAdapter(endpointUrl, region) {
|
|
|
155
147
|
};
|
|
156
148
|
// Compose capabilities
|
|
157
149
|
const detailCapability = createRoute53DetailCapability(region, getLevel);
|
|
158
|
-
const yankCapability = createRoute53YankCapability(
|
|
150
|
+
const yankCapability = createRoute53YankCapability();
|
|
159
151
|
return {
|
|
160
152
|
id: "route53",
|
|
161
153
|
label: "Route53",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { runAwsJsonAsync } from "../../../utils/aws.js";
|
|
1
|
+
import { runAwsJsonAsync, buildRegionArgs } from "../../../utils/aws.js";
|
|
2
2
|
export function createRoute53DetailCapability(region, getLevel) {
|
|
3
|
-
const regionArgs = region
|
|
3
|
+
const regionArgs = buildRegionArgs(region);
|
|
4
4
|
const getDetails = async (row) => {
|
|
5
5
|
const meta = row.meta;
|
|
6
6
|
if (!meta) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createYankCapability } from "../../../adapters/capabilities/YankCapability.js";
|
|
2
2
|
import { Route53RowMetaSchema } from "../schema.js";
|
|
3
3
|
import { Route53YankOptions } from "./yankOptions.js";
|
|
4
|
-
export function createRoute53YankCapability(
|
|
4
|
+
export function createRoute53YankCapability() {
|
|
5
5
|
return createYankCapability(Route53YankOptions, Route53RowMetaSchema, {});
|
|
6
6
|
}
|
|
@@ -3,6 +3,7 @@ import { createS3Client } from "./client.js";
|
|
|
3
3
|
import { fetchBuckets, fetchObjects, downloadObject } from "./fetcher.js";
|
|
4
4
|
import { atom } from "jotai";
|
|
5
5
|
import { getDefaultStore } from "jotai";
|
|
6
|
+
import { createBackStackHelpers } from "../../adapters/backStackUtils.js";
|
|
6
7
|
import { formatSize } from "./utils.js";
|
|
7
8
|
import { createS3EditCapability } from "./capabilities/editCapability.js";
|
|
8
9
|
import { createS3DetailCapability } from "./capabilities/detailCapability.js";
|
|
@@ -98,16 +99,7 @@ export function createS3ServiceAdapter(endpointUrl, region) {
|
|
|
98
99
|
}
|
|
99
100
|
return { action: "none" };
|
|
100
101
|
};
|
|
101
|
-
const canGoBack = (
|
|
102
|
-
const goBack = () => {
|
|
103
|
-
const backStack = getBackStack();
|
|
104
|
-
if (backStack.length > 0) {
|
|
105
|
-
const newStack = backStack.slice(0, -1);
|
|
106
|
-
const frame = backStack[backStack.length - 1];
|
|
107
|
-
setBackStack(newStack);
|
|
108
|
-
setLevel(frame.level);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
102
|
+
const { canGoBack, goBack } = createBackStackHelpers(getLevel, setLevel, getBackStack, setBackStack);
|
|
111
103
|
const getPath = () => {
|
|
112
104
|
const level = getLevel();
|
|
113
105
|
if (level.kind === "buckets")
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { downloadObjectToPath } from "../fetcher.js";
|
|
2
2
|
import { toParentPrefix } from "../utils.js";
|
|
3
|
-
|
|
4
|
-
if (error instanceof Error)
|
|
5
|
-
return error.message;
|
|
6
|
-
return String(error);
|
|
7
|
-
}
|
|
8
|
-
function hasCode(error, code) {
|
|
9
|
-
return Boolean(typeof error === "object" &&
|
|
10
|
-
error !== null &&
|
|
11
|
-
"code" in error &&
|
|
12
|
-
error.code === code);
|
|
13
|
-
}
|
|
3
|
+
import { toErrorMessage, hasCode } from "../../../utils/errorHelpers.js";
|
|
14
4
|
export function createS3ActionCapability(client, getLevel, getBackStack, setBackStack, setLevel) {
|
|
15
5
|
const getKeybindings = () => [
|
|
16
6
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { textCell, secretCell } from "../../types.js";
|
|
2
|
-
import { runAwsJsonAsync } from "../../utils/aws.js";
|
|
2
|
+
import { runAwsJsonAsync, buildRegionArgs } from "../../utils/aws.js";
|
|
3
3
|
import { atom } from "jotai";
|
|
4
4
|
import { getDefaultStore } from "jotai";
|
|
5
5
|
import { revealSecretsAtom } from "../../state/atoms.js";
|
|
@@ -7,6 +7,8 @@ import { createSecretsManagerDetailCapability } from "./capabilities/detailCapab
|
|
|
7
7
|
import { createSecretsManagerYankCapability } from "./capabilities/yankCapability.js";
|
|
8
8
|
import { createSecretsManagerActionCapability } from "./capabilities/actionCapability.js";
|
|
9
9
|
import { createSecretsManagerEditCapability } from "./capabilities/editCapability.js";
|
|
10
|
+
import { getSecretValue } from "./client.js";
|
|
11
|
+
import { createBackStackHelpers } from "../../adapters/backStackUtils.js";
|
|
10
12
|
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
11
13
|
export const secretLevelAtom = atom({ kind: "secrets" });
|
|
12
14
|
export const secretBackStackAtom = atom([]);
|
|
@@ -24,7 +26,7 @@ function tryParseFields(secretString) {
|
|
|
24
26
|
}
|
|
25
27
|
export function createSecretsManagerServiceAdapter(endpointUrl, region) {
|
|
26
28
|
const store = getDefaultStore();
|
|
27
|
-
const regionArgs = region
|
|
29
|
+
const regionArgs = buildRegionArgs(region);
|
|
28
30
|
// Getters and setters for level/backStack from atoms
|
|
29
31
|
const getLevel = () => store.get(secretLevelAtom);
|
|
30
32
|
const setLevel = (level) => store.set(secretLevelAtom, level);
|
|
@@ -76,13 +78,7 @@ export function createSecretsManagerServiceAdapter(endpointUrl, region) {
|
|
|
76
78
|
// secret-fields level
|
|
77
79
|
const { secretArn, secretName } = level;
|
|
78
80
|
try {
|
|
79
|
-
const secretData = await
|
|
80
|
-
"secretsmanager",
|
|
81
|
-
"get-secret-value",
|
|
82
|
-
"--secret-id",
|
|
83
|
-
secretArn,
|
|
84
|
-
...regionArgs,
|
|
85
|
-
]);
|
|
81
|
+
const secretData = await getSecretValue(secretArn, region);
|
|
86
82
|
const secretString = secretData.SecretString || "";
|
|
87
83
|
const fields = [];
|
|
88
84
|
// Always add $RAW field (the whole secret value)
|
|
@@ -137,16 +133,7 @@ export function createSecretsManagerServiceAdapter(endpointUrl, region) {
|
|
|
137
133
|
// secret-fields level: do nothing (use "e" key to edit)
|
|
138
134
|
return { action: "none" };
|
|
139
135
|
};
|
|
140
|
-
const canGoBack = (
|
|
141
|
-
const goBack = () => {
|
|
142
|
-
const backStack = getBackStack();
|
|
143
|
-
if (backStack.length > 0) {
|
|
144
|
-
const newStack = backStack.slice(0, -1);
|
|
145
|
-
const frame = backStack[backStack.length - 1];
|
|
146
|
-
setBackStack(newStack);
|
|
147
|
-
setLevel(frame.level);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
136
|
+
const { canGoBack, goBack } = createBackStackHelpers(getLevel, setLevel, getBackStack, setBackStack);
|
|
150
137
|
const getPath = () => {
|
|
151
138
|
const level = getLevel();
|
|
152
139
|
if (level.kind === "secrets")
|
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toErrorMessage, hasCode } from "../../../utils/errorHelpers.js";
|
|
2
|
+
import { getSecretValue } from "../client.js";
|
|
2
3
|
import { writeFile, stat, mkdir } from "fs/promises";
|
|
3
4
|
import { resolve, join } from "path";
|
|
4
|
-
function toErrorMessage(error) {
|
|
5
|
-
if (error instanceof Error)
|
|
6
|
-
return error.message;
|
|
7
|
-
return String(error);
|
|
8
|
-
}
|
|
9
|
-
function hasCode(error, code) {
|
|
10
|
-
return Boolean(typeof error === "object" &&
|
|
11
|
-
error !== null &&
|
|
12
|
-
"code" in error &&
|
|
13
|
-
error.code === code);
|
|
14
|
-
}
|
|
15
5
|
export function createSecretsManagerActionCapability(region, getLevel) {
|
|
16
|
-
const regionArgs = region ? ["--region", region] : [];
|
|
17
6
|
const getKeybindings = () => [
|
|
18
7
|
{
|
|
19
8
|
trigger: { type: "key", char: "f" },
|
|
@@ -60,14 +49,7 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
60
49
|
let defaultFileName = "data.txt";
|
|
61
50
|
try {
|
|
62
51
|
if (level?.kind === "secrets" && meta.type === "secret") {
|
|
63
|
-
|
|
64
|
-
const secretData = await runAwsJsonAsync([
|
|
65
|
-
"secretsmanager",
|
|
66
|
-
"get-secret-value",
|
|
67
|
-
"--secret-id",
|
|
68
|
-
meta.arn,
|
|
69
|
-
...regionArgs,
|
|
70
|
-
]);
|
|
52
|
+
const secretData = await getSecretValue(meta.arn, region);
|
|
71
53
|
content =
|
|
72
54
|
secretData.SecretString ||
|
|
73
55
|
(secretData.SecretBinary
|
|
@@ -76,7 +58,6 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
76
58
|
defaultFileName = `${meta.name}.txt`;
|
|
77
59
|
}
|
|
78
60
|
else if (level?.kind === "secret-fields" && meta.type === "secret-field") {
|
|
79
|
-
// Use field value directly
|
|
80
61
|
content = meta.value || "";
|
|
81
62
|
defaultFileName = `${meta.key}.txt`;
|
|
82
63
|
}
|
|
@@ -84,7 +65,6 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
84
65
|
return { type: "error", message: "Invalid item type" };
|
|
85
66
|
}
|
|
86
67
|
const destinationPath = context.data.path;
|
|
87
|
-
// Handle destination path
|
|
88
68
|
const rawTarget = destinationPath.trim();
|
|
89
69
|
const absTarget = resolve(rawTarget);
|
|
90
70
|
const endsWithSlash = rawTarget.endsWith("/") || rawTarget.endsWith("\\");
|
|
@@ -103,7 +83,6 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
103
83
|
throw new Error("EEXIST_FILE:" + finalPath);
|
|
104
84
|
}
|
|
105
85
|
}
|
|
106
|
-
// Ensure parent directory exists
|
|
107
86
|
await mkdir(resolve(finalPath, ".."), { recursive: true });
|
|
108
87
|
await writeFile(finalPath, content, { mode: 0o600 });
|
|
109
88
|
return {
|
|
@@ -112,7 +91,6 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
112
91
|
};
|
|
113
92
|
}
|
|
114
93
|
catch (err) {
|
|
115
|
-
// Check for file exists error
|
|
116
94
|
if (hasCode(err, "EEXIST")) {
|
|
117
95
|
const destinationPath = context.data.path;
|
|
118
96
|
return {
|
|
@@ -138,7 +116,6 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
138
116
|
}
|
|
139
117
|
}
|
|
140
118
|
if (actionId === "fetch:overwrite") {
|
|
141
|
-
// User confirmed overwrite
|
|
142
119
|
if (!context.row || !context.data?.path) {
|
|
143
120
|
return { type: "error", message: "Invalid path" };
|
|
144
121
|
}
|
|
@@ -150,14 +127,7 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
150
127
|
try {
|
|
151
128
|
let content = "";
|
|
152
129
|
if (level?.kind === "secrets" && meta.type === "secret") {
|
|
153
|
-
|
|
154
|
-
const secretData = await runAwsJsonAsync([
|
|
155
|
-
"secretsmanager",
|
|
156
|
-
"get-secret-value",
|
|
157
|
-
"--secret-id",
|
|
158
|
-
meta.arn,
|
|
159
|
-
...regionArgs,
|
|
160
|
-
]);
|
|
130
|
+
const secretData = await getSecretValue(meta.arn, region);
|
|
161
131
|
content =
|
|
162
132
|
secretData.SecretString ||
|
|
163
133
|
(secretData.SecretBinary
|
|
@@ -165,7 +135,6 @@ export function createSecretsManagerActionCapability(region, getLevel) {
|
|
|
165
135
|
: "");
|
|
166
136
|
}
|
|
167
137
|
else if (level?.kind === "secret-fields" && meta.type === "secret-field") {
|
|
168
|
-
// Use field value directly
|
|
169
138
|
content = meta.value || "";
|
|
170
139
|
}
|
|
171
140
|
else {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describeSecret } from "../client.js";
|
|
2
2
|
export function createSecretsManagerDetailCapability(region, getLevel) {
|
|
3
|
-
const regionArgs = region ? ["--region", region] : [];
|
|
4
3
|
const getDetails = async (row) => {
|
|
5
4
|
const meta = row.meta;
|
|
6
5
|
if (!meta) {
|
|
@@ -9,13 +8,7 @@ export function createSecretsManagerDetailCapability(region, getLevel) {
|
|
|
9
8
|
const level = getLevel?.();
|
|
10
9
|
// Level 1: Secret details
|
|
11
10
|
if (level?.kind === "secrets" && meta.type === "secret") {
|
|
12
|
-
const data = await
|
|
13
|
-
"secretsmanager",
|
|
14
|
-
"describe-secret",
|
|
15
|
-
"--secret-id",
|
|
16
|
-
meta.arn,
|
|
17
|
-
...regionArgs,
|
|
18
|
-
]);
|
|
11
|
+
const data = await describeSecret(meta.arn, region);
|
|
19
12
|
const fields = [
|
|
20
13
|
{ label: "Name", value: String(data.Name ?? "-") },
|
|
21
14
|
{ label: "ARN", value: String(data.ARN ?? "-") },
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { runAwsJsonAsync } from "../../../utils/aws.js";
|
|
2
1
|
import { readFile, writeFile } from "fs/promises";
|
|
3
2
|
import { tmpdir } from "os";
|
|
4
3
|
import { join } from "path";
|
|
4
|
+
import { getSecretValue, putSecretValue } from "../client.js";
|
|
5
5
|
export function createSecretsManagerEditCapability(region, getLevel) {
|
|
6
|
-
const regionArgs = region ? ["--region", region] : [];
|
|
7
6
|
const onEdit = async (row) => {
|
|
8
7
|
const level = getLevel?.();
|
|
9
8
|
const meta = row.meta;
|
|
@@ -12,14 +11,7 @@ export function createSecretsManagerEditCapability(region, getLevel) {
|
|
|
12
11
|
}
|
|
13
12
|
// Level 1: open secret in editor (both JSON and non-JSON)
|
|
14
13
|
if (level?.kind === "secrets" && meta.type === "secret") {
|
|
15
|
-
|
|
16
|
-
const secretData = await runAwsJsonAsync([
|
|
17
|
-
"secretsmanager",
|
|
18
|
-
"get-secret-value",
|
|
19
|
-
"--secret-id",
|
|
20
|
-
meta.arn,
|
|
21
|
-
...regionArgs,
|
|
22
|
-
]);
|
|
14
|
+
const secretData = await getSecretValue(meta.arn, region);
|
|
23
15
|
const secretString = secretData.SecretString || "";
|
|
24
16
|
const safeName = meta.name.replace(/[^a-z0-9_-]/gi, "_");
|
|
25
17
|
const filePath = join(tmpdir(), `a9s_secret_${safeName}`);
|
|
@@ -58,14 +50,7 @@ export function createSecretsManagerEditCapability(region, getLevel) {
|
|
|
58
50
|
// Level 2: Update field in JSON secret
|
|
59
51
|
if (fieldKey && secretArn) {
|
|
60
52
|
const newContent = await readFile(filePath, "utf-8");
|
|
61
|
-
|
|
62
|
-
const secretData = await runAwsJsonAsync([
|
|
63
|
-
"secretsmanager",
|
|
64
|
-
"get-secret-value",
|
|
65
|
-
"--secret-id",
|
|
66
|
-
secretArn,
|
|
67
|
-
...regionArgs,
|
|
68
|
-
]);
|
|
53
|
+
const secretData = await getSecretValue(secretArn, region);
|
|
69
54
|
const secretString = secretData.SecretString || "";
|
|
70
55
|
let parsed;
|
|
71
56
|
try {
|
|
@@ -74,33 +59,14 @@ export function createSecretsManagerEditCapability(region, getLevel) {
|
|
|
74
59
|
catch {
|
|
75
60
|
throw new Error("Failed to parse secret JSON");
|
|
76
61
|
}
|
|
77
|
-
// Update field
|
|
78
62
|
parsed[fieldKey] = newContent;
|
|
79
|
-
|
|
80
|
-
const updatedSecretString = JSON.stringify(parsed);
|
|
81
|
-
await runAwsJsonAsync([
|
|
82
|
-
"secretsmanager",
|
|
83
|
-
"put-secret-value",
|
|
84
|
-
"--secret-id",
|
|
85
|
-
secretArn,
|
|
86
|
-
"--secret-string",
|
|
87
|
-
updatedSecretString,
|
|
88
|
-
...regionArgs,
|
|
89
|
-
]);
|
|
63
|
+
await putSecretValue(secretArn, JSON.stringify(parsed), region);
|
|
90
64
|
return;
|
|
91
65
|
}
|
|
92
66
|
// Level 1: Update entire secret (JSON or non-JSON)
|
|
93
67
|
if (secretArn && !fieldKey) {
|
|
94
68
|
const newContent = await readFile(filePath, "utf-8");
|
|
95
|
-
await
|
|
96
|
-
"secretsmanager",
|
|
97
|
-
"put-secret-value",
|
|
98
|
-
"--secret-id",
|
|
99
|
-
secretArn,
|
|
100
|
-
"--secret-string",
|
|
101
|
-
newContent,
|
|
102
|
-
...regionArgs,
|
|
103
|
-
]);
|
|
69
|
+
await putSecretValue(secretArn, newContent, region);
|
|
104
70
|
return;
|
|
105
71
|
}
|
|
106
72
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getSecretValue } from "../client.js";
|
|
2
2
|
const isSecret = (row) => row.meta.type === "secret";
|
|
3
3
|
const isSecretField = (row) => row.meta.type === "secret-field";
|
|
4
4
|
export const secretYankOptions = [
|
|
@@ -29,15 +29,8 @@ export const secretYankOptions = [
|
|
|
29
29
|
headerKey: "name",
|
|
30
30
|
isRelevant: isSecret,
|
|
31
31
|
resolve: async (row, ctx) => {
|
|
32
|
-
const regionArgs = ctx.region ? ["--region", ctx.region] : [];
|
|
33
32
|
try {
|
|
34
|
-
const secretData = await
|
|
35
|
-
"secretsmanager",
|
|
36
|
-
"get-secret-value",
|
|
37
|
-
"--secret-id",
|
|
38
|
-
row.meta.arn,
|
|
39
|
-
...regionArgs,
|
|
40
|
-
]);
|
|
33
|
+
const secretData = await getSecretValue(row.meta.arn, ctx.region);
|
|
41
34
|
return secretData.SecretString || null;
|
|
42
35
|
}
|
|
43
36
|
catch {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runAwsJsonAsync } from "../../utils/aws.js";
|
|
2
|
+
import { buildRegionArgs } from "../../utils/aws.js";
|
|
3
|
+
export async function getSecretValue(secretId, region) {
|
|
4
|
+
return runAwsJsonAsync([
|
|
5
|
+
"secretsmanager",
|
|
6
|
+
"get-secret-value",
|
|
7
|
+
"--secret-id",
|
|
8
|
+
secretId,
|
|
9
|
+
...buildRegionArgs(region),
|
|
10
|
+
]);
|
|
11
|
+
}
|
|
12
|
+
export async function describeSecret(secretId, region) {
|
|
13
|
+
return runAwsJsonAsync([
|
|
14
|
+
"secretsmanager",
|
|
15
|
+
"describe-secret",
|
|
16
|
+
"--secret-id",
|
|
17
|
+
secretId,
|
|
18
|
+
...buildRegionArgs(region),
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
export async function putSecretValue(secretId, value, region) {
|
|
22
|
+
await runAwsJsonAsync([
|
|
23
|
+
"secretsmanager",
|
|
24
|
+
"put-secret-value",
|
|
25
|
+
"--secret-id",
|
|
26
|
+
secretId,
|
|
27
|
+
"--secret-string",
|
|
28
|
+
value,
|
|
29
|
+
...buildRegionArgs(region),
|
|
30
|
+
]);
|
|
31
|
+
}
|