feat:adds barbican panel(named Key-Manager)

This change adds barbican panel for secrets and  list similar to other panels and subpanel. Has Creation , Detail Secret Page included

Change-Id: I33a7c4a4845529c5a6d44060728956701f50dccf
Signed-off-by: Reet Srivastava <reet.srivastava@rackspace.com>
This commit is contained in:
Reet Srivastava
2025-08-26 13:05:47 +05:30
parent 873174265e
commit fa2959a2b6
16 changed files with 1030 additions and 19 deletions

View File

@@ -37,6 +37,11 @@ export class BarbicanClient extends Base {
key: 'containers',
responseKey: 'container',
},
{
name: 'orders',
key: 'orders',
responseKey: 'order',
},
];
}
}

View File

@@ -315,7 +315,6 @@ export default class BaseForm extends React.Component {
onOk = (values, containerProps, callback) => {
// eslint-disable-next-line no-console
console.log('onOk', values);
this.values = values;
if (this.codeError) {
return;

View File

@@ -618,6 +618,29 @@ const renderMenu = (t) => {
},
],
},
{
path: '/key-manager',
name: t('Key Manager'),
key: 'keyManager',
icon: <ContainerOutlined />,
children: [
{
path: '/key-manager/secret',
name: t('Secrets'),
key: 'keyManagerSecret',
level: 1,
children: [
{
path: /^\/key-manager\/secret\/detail\/.[^/]+$/,
name: t('Secret Detail'),
key: 'secretDetail',
level: 2,
routePath: '/key-manager/secret/detail/:id',
},
],
},
],
},
// {
// path: '/management',
// name: t('Maintenance'),

View File

@@ -0,0 +1,20 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import renderRoutes from 'utils/RouterConfig';
import routes from './routes';
const App = (props) => renderRoutes(routes, props);
export default App;

View File

@@ -0,0 +1,50 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import { inject, observer } from 'mobx-react';
import Base from 'containers/BaseDetail';
export class BaseDetail extends Base {
get leftCardsStyle() {
return {
flex: 1,
};
}
get leftCards() {
const cards = [this.contentCard];
return cards;
}
get contentCard() {
const { payload } = this.props.detail;
const options = [
{
content: <pre>{payload}</pre>,
copyable: {
text: payload,
},
},
];
return {
title: t('Secret Content'),
labelCol: 0,
contentCol: 24,
options,
};
}
}
export default inject('rootStore')(observer(BaseDetail));

View File

@@ -0,0 +1,104 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import { inject, observer } from 'mobx-react';
import Base from 'containers/TabDetail';
import { SecretsStore } from 'stores/barbican/secrets';
import { Badge } from 'antd';
import actionConfigs from '../actions';
import BaseDetail from './BaseDetail';
export class SecretDetail extends Base {
init() {
this.store = new SecretsStore();
}
get policy() {
return 'secret:get';
}
get name() {
return t('Secret Detail');
}
get listUrl() {
return this.getRoutePath('keyManagerSecret');
}
get actionConfigs() {
return actionConfigs;
}
get detailInfos() {
return [
{
title: t('ID'),
dataIndex: 'id',
},
{
title: t('Name'),
dataIndex: 'name',
},
{
title: t('Status'),
dataIndex: 'expiration',
valueRender: 'toLocalTime',
render: (value) => {
if (value) {
const isExpired = value && new Date(value) < new Date();
const statusText = t(isExpired ? 'Expired' : 'Active');
const statusColor = isExpired ? '#D32F45' : '#3C9E6C';
return <Badge color={statusColor} text={statusText} />;
}
return <Badge color="#3C9E6C" text="Active" />;
},
},
{
title: t('Secret Type'),
dataIndex: 'secret_type',
},
{
title: t('Algorithm'),
dataIndex: 'algorithm',
},
{
title: t('Mode'),
dataIndex: 'mode',
},
{
title: t('Expiration'),
dataIndex: 'expiration',
valueRender: 'toLocalTime',
},
{
title: t('Created At'),
dataIndex: 'created',
valueRender: 'toLocalTime',
},
];
}
get tabs() {
return [
{
title: t('Detail Info'),
key: 'detail_info',
component: BaseDetail,
},
];
}
}
export default inject('rootStore')(observer(SecretDetail));

View File

@@ -0,0 +1,462 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { inject, observer } from 'mobx-react';
import { ModalAction } from 'containers/Action';
import globalSecretsStore from 'stores/barbican/secrets';
import globalOrdersStore from 'stores/barbican/orders';
import moment from 'moment';
// Constants for secret configuration
const SECRET_CONFIG = {
KEY: {
BIT_LENGTH: 256,
ALGORITHM: 'aes',
},
ASYMMETRIC: {
BIT_LENGTH: 2048,
ALGORITHM: 'rsa',
},
};
export class CreateSecret extends ModalAction {
static id = 'create-secret';
static title = t('Create Secret');
static policy = ['secrets:post', 'orders:post'];
static allowed = () => Promise.resolve(true);
static get modalSize() {
return 'large';
}
static CONTENT_TYPE_OPTIONS = [
{ label: 'text/plain', value: 'text/plain' },
{ label: 'application/octet-stream', value: 'application/octet-stream' },
{ label: 'application/x-pkcs12', value: 'application/x-pkcs12' },
{ label: 'application/x-pem-file', value: 'application/x-pem-file' },
];
static ALGORITHM_OPTIONS = [
{ label: 'AES', value: 'aes' },
{ label: 'DES', value: 'des' },
{ label: '3DES', value: '3des' },
{ label: 'RSA', value: 'rsa' },
{ label: 'DSA', value: 'dsa' },
{ label: 'EC', value: 'ec' },
];
static ASYMMETRIC_ALGORITHM_OPTIONS = [
{ label: 'RSA', value: 'rsa' },
{ label: 'DSA', value: 'dsa' },
];
static BIT_LENGTH_OPTIONS = [
{ label: '128', value: 128 },
{ label: '192', value: 192 },
{ label: '256', value: 256 },
{ label: '1024', value: 1024 },
{ label: '2048', value: 2048 },
{ label: '4096', value: 4096 },
];
static MODE_OPTIONS = [
{ label: 'CBC', value: 'cbc' },
{ label: 'CTR', value: 'ctr' },
];
init() {
this.secretsStore = globalSecretsStore;
this.ordersStore = globalOrdersStore;
}
get name() {
return t('Create Secret');
}
get nameForStateUpdate() {
return ['creationType', 'secret_type', 'request_type', 'algorithm'];
}
onSecretTypeChange = (value) => {
const { creationType } = this.state;
if (!this.formRef.current) {
return;
}
if (creationType === 'direct') {
this.formRef.current.setFieldsValue({
secret_type: value,
});
return;
}
const config =
value === 'key'
? SECRET_CONFIG.KEY
: value === 'asymmetric'
? SECRET_CONFIG.ASYMMETRIC
: { BIT_LENGTH: undefined, ALGORITHM: undefined };
this.formRef.current.setFieldsValue({
request_type: value,
bit_length: config.BIT_LENGTH,
algorithm: config.ALGORITHM,
});
};
get defaultValue() {
return {
creationType: 'direct',
name: '',
payload: '',
payload_content_type: 'text/plain',
payload_content_encoding: '',
algorithm: 'aes',
bit_length: 256,
mode: 'cbc',
secret_type: 'opaque',
request_type: 'key',
expiration: '',
};
}
get creationTypeOptions() {
return [
{
label: t('Create Secret Directly'),
value: 'direct',
tip: t('Create a secret with your own payload content'),
},
{
label: t('Create Secret via Order'),
value: 'order',
tip: t('Create a secret through order (for certificates, keys, etc.)'),
},
];
}
get requestTypeOptions() {
return [
{
label: t('Key'),
value: 'key',
tip: t('Symmetric keys, private keys, and passphrases'),
},
{
label: t('Asymmetric Key'),
value: 'asymmetric',
tip: t('Asymmetric key pairs (public/private)'),
},
];
}
get secretTypeOptionsForDirect() {
return [
{
label: t('Opaque'),
value: 'opaque',
tip: t('Default secret type for arbitrary data'),
},
{
label: t('Symmetric'),
value: 'symmetric',
tip: t('Symmetric keys'),
},
{
label: t('Public'),
value: 'public',
tip: t('Public keys'),
},
{
label: t('Private'),
value: 'private',
tip: t('Private keys'),
},
{
label: t('Certificate'),
value: 'certificate',
tip: t('Certificates'),
},
{
label: t('Passphrase'),
value: 'passphrase',
tip: t('Passphrases'),
},
];
}
get algorithmOptions() {
const { secret_type, request_type } = this.state;
const currentType = secret_type || request_type;
if (currentType === 'asymmetric') {
return CreateSecret.ASYMMETRIC_ALGORITHM_OPTIONS;
}
return CreateSecret.ALGORITHM_OPTIONS;
}
get bitLengthOptions() {
return CreateSecret.BIT_LENGTH_OPTIONS;
}
get modeOptions() {
return CreateSecret.MODE_OPTIONS;
}
get formItems() {
const { creationType } = this.state;
const isDirect = creationType === 'direct';
const isOrder = creationType === 'order';
const baseItems = this.getBaseFormItems();
if (isDirect) {
return [...baseItems, ...this.getDirectFormItems()];
}
if (isOrder) {
return [...baseItems, ...this.getOrderFormItems()];
}
return baseItems;
}
getBaseFormItems() {
const { creationType } = this.state;
const isDirect = creationType === 'direct';
const isOrder = creationType === 'order';
return [
{
name: 'creationType',
label: t('Creation Method'),
type: 'radio',
options: this.creationTypeOptions,
required: true,
},
{
name: 'name',
label: t('Secret Name'),
type: 'input-name',
required: false,
withoutChinese: true,
tip: isOrder
? t('Optional. If not provided, a name will be auto-generated')
: t('Required for direct creation'),
rules: isDirect
? [
{
required: true,
message: t('Secret name is required for direct creation'),
},
]
: [],
},
];
}
getDirectFormItems() {
return [
{
name: 'payload',
label: t('Secret Payload'),
type: 'textarea',
required: true,
rows: 6,
placeholder: t('Enter the secret content'),
tip: t('The actual secret data to be stored'),
},
{
name: 'payload_content_type',
label: t('Payload Content Type'),
type: 'select',
options: CreateSecret.CONTENT_TYPE_OPTIONS,
required: true,
tip: t('Required when payload is supplied'),
},
{
name: 'payload_content_encoding',
label: t('Payload Content Encoding'),
type: 'input',
required: false,
tip: t(
'Required if payload content type is "application/octet-stream"'
),
},
{
name: 'secret_type',
label: t('Secret Type'),
type: 'select',
options: [
{
label: t('Select Secret Type'),
value: '',
},
...this.secretTypeOptionsForDirect,
],
required: false,
tip: t('Optional. Type of secret being stored (default: opaque)'),
},
{
name: 'algorithm',
label: t('Algorithm'),
type: 'select',
options: [
{
label: t('Select Algorithm'),
value: '',
},
...this.algorithmOptions,
],
required: false,
tip: t('Optional. Algorithm used by the secret (default: aes)'),
},
{
name: 'bit_length',
label: t('Bit Length'),
type: 'select',
options: [
{
label: t('Select Bit Length'),
value: '',
},
...this.bitLengthOptions,
],
required: false,
tip: t('Optional. Bit length of the secret (default: 256)'),
},
{
name: 'mode',
label: t('Mode'),
type: 'select',
options: [
{
label: t('Select Mode'),
value: '',
},
...this.modeOptions,
],
required: false,
tip: t(
'Optional. Algorithm mode, used only for reference (default: cbc)'
),
},
{
name: 'expiration',
label: t('Expiration Date'),
type: 'date-picker',
showToday: false,
disabledDate: (current) => current && current <= moment().endOf('d'),
tip: t('Optional. When the secret should expire (ISO 8601 format)'),
},
];
}
getOrderFormItems() {
return [
{
name: 'request_type',
label: t('Request Type'),
type: 'select',
options: this.requestTypeOptions,
required: true,
onChange: this.onSecretTypeChange,
},
{
name: 'algorithm',
label: t('Algorithm'),
type: 'select',
options: this.algorithmOptions,
required: false,
tip: t(
'Optional. Algorithm to be used with the requested key (default: aes)'
),
},
{
name: 'bit_length',
label: t('Bit Length'),
type: 'select',
options: this.bitLengthOptions,
required: false,
tip: t(
'Optional. Bit length of the requested secret key (default: 256)'
),
},
{
name: 'mode',
label: t('Mode'),
type: 'select',
options: this.modeOptions,
required: false,
tip: t(
'Optional. Algorithm mode to be used with the requested key (default: cbc)'
),
},
{
name: 'payload_content_type',
label: t('Payload Content Type'),
type: 'select',
options: [
{
label: 'application/octet-stream',
value: 'application/octet-stream',
},
{ label: 'text/plain', value: 'text/plain' },
{ label: 'application/x-pkcs12', value: 'application/x-pkcs12' },
{
label: 'application/x-pem-file',
value: 'application/x-pem-file',
},
],
required: false,
tip: t(
'Optional. Type/format of the secret to be generated (default: application/octet-stream)'
),
},
{
name: 'expiration',
label: t('Expiration Date'),
type: 'date-picker',
showToday: false,
disabledDate: (current) => current && current <= moment().endOf('d'),
tip: t('Optional. Expiration time for the secret in ISO 8601 format'),
},
];
}
onClickCancel = () => {
this.onCancel();
};
onSubmit = (values) => {
const { creationType, ...rest } = values;
if (creationType === 'direct') {
return this.secretsStore.create(rest);
}
if (creationType === 'order') {
return this.ordersStore.create(rest);
}
return Promise.reject(new Error('Invalid creation type'));
};
}
export default inject('rootStore')(observer(CreateSecret));

View File

@@ -0,0 +1,46 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ConfirmAction } from 'containers/Action';
import globalSecretsStore from 'stores/barbican/secrets';
export default class DeleteAction extends ConfirmAction {
static policy = 'secret:delete';
static allowed = () => Promise.resolve(true);
get id() {
return 'delete';
}
get title() {
return t('Delete Secret');
}
get isDanger() {
return true;
}
get buttonText() {
return t('Delete');
}
get actionName() {
return t('delete secret');
}
onSubmit = (data) => {
return globalSecretsStore.delete(data);
};
}

View File

@@ -0,0 +1,13 @@
import CreateAction from './Create';
import DeleteAction from './Delete';
const actionConfigs = {
rowActions: {
firstAction: DeleteAction,
moreActions: [],
},
batchActions: [DeleteAction],
primaryActions: [CreateAction],
};
export default actionConfigs;

View File

@@ -0,0 +1,91 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import { observer, inject } from 'mobx-react';
import BaseList from 'containers/List';
import globalSecretsStore from 'stores/barbican/secrets';
import { Badge } from 'antd';
import actionConfigs from './actions';
export class SecretList extends BaseList {
init() {
this.store = globalSecretsStore;
}
get name() {
return t('Secrets');
}
get policy() {
return 'barbican:secret:get';
}
getColumns() {
return [
{
title: t('ID/Name'),
dataIndex: 'name',
routeName: this.getRouteName('secretDetail'),
},
{
title: t('Status'),
dataIndex: 'expiration',
valueRender: 'toLocalTime',
render: (value) => {
if (value) {
const isExpired = value && new Date(value) < new Date();
const statusText = t(isExpired ? 'Expired' : 'Active');
const statusColor = isExpired ? '#D32F45' : '#3C9E6C';
return <Badge color={statusColor} text={statusText} />;
}
return <Badge color="#3C9E6C" text="Active" />;
},
},
{
title: t('Algorithm'),
dataIndex: 'algorithm',
},
{
title: t('Expiration'),
dataIndex: 'expiration',
valueRender: 'toLocalTime',
},
{
title: t('Created At'),
dataIndex: 'created',
valueRender: 'toLocalTime',
},
];
}
get actionConfigs() {
return actionConfigs;
}
get searchFilters() {
return [
{
label: t('Name'),
name: 'name',
},
{
label: t('Algorithm'),
name: 'algorithm',
},
];
}
}
export default inject('rootStore')(observer(SecretList));

View File

@@ -0,0 +1,35 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import BaseLayout from 'layouts/Basic';
import E404 from 'pages/base/containers/404';
import SecretList from '../containers/Secret';
import SecretDetail from '../containers/Secret/Detail';
const PATH = '/key-manager';
export default [
{
path: PATH,
component: BaseLayout,
routes: [
{ path: `${PATH}/secret`, component: SecretList, exact: true },
{
path: `${PATH}/secret/detail/:id`,
component: SecretDetail,
exact: true,
},
{ path: '*', component: E404 },
],
},
];

View File

@@ -50,6 +50,9 @@ const Database = lazy(() =>
const Share = lazy(() =>
import(/* webpackChunkName: "share" */ 'pages/share/App')
);
const Barbican = lazy(() =>
import(/* webpackChunkName: "barbican" */ 'pages/barbican/App')
);
const ContainerInfra = lazy(() =>
import(/* webpackChunkName: "container-infra" */ 'pages/container-infra/App')
);
@@ -111,6 +114,10 @@ export default [
path: `/share`,
component: Share,
},
{
path: `/key-manager`,
component: Barbican,
},
{
path: `/container-infra`,
component: ContainerInfra,

View File

@@ -71,6 +71,8 @@ export const policyMap = {
'secret:decrypt',
'secret:delete',
'containers:post',
'secrets:post',
'orders:post',
],
zun: ['capsule:', 'container:', 'host:get'],
panko: ['segregation', 'telemetry:events:index'],

View File

@@ -150,11 +150,45 @@ export class ContainersStore extends Base {
});
// Determine if the certificate is used in the listener
this.updateItem(item, listeners);
// Fetch secrets payload
// Fetch secrets payload with proper decoding
const payloads = await Promise.all(
secretIds.map((id) =>
this.payloadClient.list(id, {}, { headers: { Accept: 'text/plain' } })
)
secretIds.map(async (id) => {
try {
const payload = await this.payloadClient.list(id, null, {
headers: {
Accept: '*/*',
},
responseType: 'arraybuffer',
});
// Decode payload if it's binary
if (payload instanceof ArrayBuffer) {
const bytes = new Uint8Array(payload);
// Check if it's actually text by trying to decode it
const textDecoder = new TextDecoder('utf-8', { fatal: false });
const decodedText = textDecoder.decode(bytes);
// Check if the decoded text contains valid printable characters
const isValidText = /^[\x20-\x7E\n\r\t]*$/.test(decodedText);
if (isValidText) {
// It's valid text, use it
return decodedText;
}
// It's binary data, convert to base64
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
return payload;
} catch (error) {
console.error(`Failed to fetch payload for secret ${id}:`, error);
return null;
}
})
);
(payloads || []).forEach((it, index) => {
secret_refs[index].secret_info.payload = it;

View File

@@ -0,0 +1,56 @@
// Copyright 2025 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Base from 'stores/base';
import client from 'client';
import { action } from 'mobx';
export class OrdersStore extends Base {
get client() {
return client.barbican.orders;
}
get responseKey() {
return 'order';
}
@action
async create(data) {
const {
expiration,
request_type,
algorithm,
bit_length,
name,
payload_content_type,
mode,
} = data;
const body = {
type: request_type,
meta: {
...(algorithm && { algorithm }),
...(bit_length && { bit_length }),
...(mode && { mode }),
...(payload_content_type && { payload_content_type }),
...(name && { name }),
...(expiration && { expiration }),
},
};
return this.client.create(body);
}
}
const globalOrdersStore = new OrdersStore();
export default globalOrdersStore;

View File

@@ -43,14 +43,21 @@ export class SecretsStore extends Base {
get mapper() {
return (data) => {
const { secret_ref, algorithm } = data;
const { secret_ref, algorithm, expiration } = data;
const [, uuid] = secret_ref.split('/secrets/');
const { domain, expiration } = algorithm ? JSON.parse(algorithm) : {};
let extractedExpiration = expiration;
if (algorithm && algorithm.startsWith('{')) {
try {
const parsed = JSON.parse(algorithm);
extractedExpiration = parsed.expiration || expiration;
} catch {
// Do nothing, Keep original expiration if parsing fails
}
}
return {
...data,
id: uuid,
domain,
expiration,
expiration: extractedExpiration,
};
};
}
@@ -97,11 +104,58 @@ export class SecretsStore extends Base {
this.isLoading = true;
}
const [item, payload, listeners] = await Promise.all([
this.client.show(id, {}, { headers: { Accept: 'application/json' } }),
this.payloadClient.list(id, {}, { headers: { Accept: 'text/plain' } }),
this.client.show(id, null, {
headers: {
Accept: 'application/json',
},
}),
this.payloadClient.list(id, null, {
headers: {
Accept: '*/*',
},
responseType: 'arraybuffer',
}),
globalListenerStore.fetchList(),
]);
item.payload = payload;
let decodedPayload = payload;
if (payload) {
try {
if (payload instanceof ArrayBuffer) {
const bytes = new Uint8Array(payload);
const contentType = item.payload_content_type || 'text/plain';
const textDecoder = new TextDecoder('utf-8', { fatal: false });
const decodedText = textDecoder.decode(bytes);
const isValidText = /^[\x20-\x7E\n\r\t]*$/.test(decodedText);
if (
isValidText &&
(contentType.includes('text') ||
contentType.includes('plain') ||
contentType.includes('json'))
) {
decodedPayload = decodedText;
} else {
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
decodedPayload = btoa(binary);
}
} else if (typeof payload === 'string') {
decodedPayload = payload;
} else {
decodedPayload = JSON.stringify(payload, null, 2);
}
} catch (error) {
console.error('Error decoding payload:', error);
decodedPayload = 'Error decoding payload';
}
}
item.payload = decodedPayload;
// Determine if the certificate is used in the listener
this.updateItem(item, listeners);
const detail = this.mapper(item || {});
@@ -124,15 +178,25 @@ export class SecretsStore extends Base {
@action
async create(data) {
const { expiration, domain, algorithm, ...rest } = data;
const {
expiration,
secret_type,
algorithm,
bit_length,
mode,
payload_content_encoding,
...rest
} = data;
const body = {
...rest,
algorithm:
algorithm ||
JSON.stringify({
domain,
expiration,
}),
...(secret_type && secret_type.trim() !== '' && { secret_type }),
...(algorithm && algorithm.trim() !== '' && { algorithm }),
...(bit_length && { bit_length }),
...(mode && mode.trim() !== '' && { mode }),
...(payload_content_encoding &&
payload_content_encoding.trim() !== '' && { payload_content_encoding }),
...(expiration && { expiration }),
};
return this.client.create(body);
}