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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ name: CI

on:
push:
branches: ["**"]
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
secrets:
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ To learn more about the war and how you can help, [click here](https://war.ukrai
- Browse Secrets, Keys, Certificates
- Secret metadata + explicit value fetch flow
- Secret CRUD lifecycle (set/delete/recover/purge)
- Import secrets from JSON file
- Bulk delete safety flow:
- typed confirmation (`delete`)
- collapsible list of selected secrets
Expand Down Expand Up @@ -118,6 +119,46 @@ Optional mock mode for UI development:
VITE_ENABLE_MOCK_MODE=true npm run dev
```

## Import Secrets from JSON

Use the **Import JSON** button in the Secrets toolbar, or run **Import Secrets from JSON** from the command palette.

Accepted formats:

```json
[
{
"name": "my-secret",
"value": "secret-value",
"contentType": "text/plain",
"enabled": true,
"expires": "2030-01-01T00:00:00Z",
"notBefore": "2026-01-01T00:00:00Z",
"tags": {
"env": "prod"
}
}
]
```

or:

```json
{
"secrets": [
{ "name": "my-secret", "value": "secret-value" }
]
}
```

Rules:
- `name` and `value` are required.
- `name` supports letters, numbers, and dashes only.
- `tags` must be an object with string values.
- `expires` and `notBefore` must be valid date strings.

Sample file: [`examples/secrets-import.example.json`](./examples/secrets-import.example.json)

## Quality Gates

Frontend:
Expand Down
21 changes: 21 additions & 0 deletions examples/secrets-import.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"secrets": [
{
"name": "api-key-prod",
"value": "replace-me",
"contentType": "text/plain",
"enabled": true,
"tags": {
"env": "prod",
"team": "platform"
}
},
{
"name": "app-config-json",
"value": "{\"featureFlag\":true}",
"contentType": "application/json",
"expires": "2030-01-01T00:00:00Z",
"notBefore": "2026-01-01T00:00:00Z"
}
]
}
10 changes: 3 additions & 7 deletions src/components/auth/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
Card,
CardFooter,
CardHeader,
makeStyles,
Spinner,
Text,
Tooltip,
makeStyles,
tokens,
} from '@fluentui/react-components';
import {
Expand Down Expand Up @@ -243,9 +243,7 @@ export function SignIn() {
<div className={`azv-shell ${classes.shellCenterWithPadding}`}>
<Card className={`azv-pane ${classes.card}`}>
<CardHeader
image={
<ShieldLock24Regular className={classes.headerIcon} />
}
image={<ShieldLock24Regular className={classes.headerIcon} />}
header={
<div>
<Text weight="bold" size={500} className={classes.titleLetterSpacing}>
Expand Down Expand Up @@ -338,9 +336,7 @@ export function SignIn() {
/>
</Tooltip>
</div>
<p className={classes.terminalHint}>
Then click Connect below to verify the session.
</p>
<p className={classes.terminalHint}>Then click Connect below to verify the session.</p>
</div>

{/* Error */}
Expand Down
2 changes: 1 addition & 1 deletion src/components/certificates/CertificateDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {
Badge,
Button,
Field,
Text,
makeStyles,
mergeClasses,
Text,
tokens,
} from '@fluentui/react-components';
import { Certificate24Regular, Copy24Regular, Dismiss24Regular } from '@fluentui/react-icons';
Expand Down
14 changes: 2 additions & 12 deletions src/components/certificates/CertificatesList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
Button,
Input,
Text,
makeStyles,
mergeClasses,
tokens,
} from '@fluentui/react-components';
import { Button, Input, makeStyles, mergeClasses, Text, tokens } from '@fluentui/react-components';
import { Search24Regular } from '@fluentui/react-icons';
import { useQuery } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
Expand Down Expand Up @@ -161,10 +154,7 @@ export function CertificatesList() {
);
const expired = new Date(item.expires) < new Date();
return (
<Text
size={200}
style={{ color: expired ? 'var(--azv-danger)' : undefined }}
>
<Text size={200} style={{ color: expired ? 'var(--azv-danger)' : undefined }}>
{renderDate(item.expires)}
</Text>
);
Expand Down
15 changes: 9 additions & 6 deletions src/components/command-palette/CommandPalette.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Input, Text, makeStyles, tokens } from '@fluentui/react-components';
import { Input, makeStyles, Text, tokens } from '@fluentui/react-components';
import { Search24Regular } from '@fluentui/react-icons';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useAppStore } from '../../stores/appStore';
Expand Down Expand Up @@ -133,11 +133,7 @@ export function CommandPalette() {
onClick={() => execute(result.item)}
onMouseEnter={() => setActiveIndex(i)}
>
{result.item.icon && (
<span className={classes.itemIcon}>
{result.item.icon}
</span>
)}
{result.item.icon && <span className={classes.itemIcon}>{result.item.icon}</span>}
<span>
<Text size={200}>{result.item.label}</Text>
<Text size={100} className={classes.categoryText}>
Expand Down Expand Up @@ -245,6 +241,13 @@ function useCommands(): PaletteCommand[] {
execute: () => window.dispatchEvent(new CustomEvent('azv:new-secret')),
when: () => !!store.selectedVaultName,
},
{
id: 'import-secrets-json',
label: 'Import Secrets from JSON',
category: 'action',
execute: () => window.dispatchEvent(new CustomEvent('azv:import-secrets')),
when: () => !!store.selectedVaultName,
},
{
id: 'select-all',
label: 'Select All Items',
Expand Down
6 changes: 2 additions & 4 deletions src/components/common/DangerConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
DialogSurface,
DialogTitle,
Input,
makeStyles,
Spinner,
Text,
makeStyles,
tokens,
} from '@fluentui/react-components';
import { Warning24Regular } from '@fluentui/react-icons';
Expand Down Expand Up @@ -99,9 +99,7 @@ export function DangerConfirmDialog({
</DialogTitle>
<DialogContent>
{isCritical && (
<div className={classes.criticalBanner}>
This action is irreversible.
</div>
<div className={classes.criticalBanner}>This action is irreversible.</div>
)}

<Text size={200} className={classes.descriptionText}>
Expand Down
9 changes: 7 additions & 2 deletions src/components/common/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Text, makeStyles, tokens } from '@fluentui/react-components';
import { Button, makeStyles, Text, tokens } from '@fluentui/react-components';

interface EmptyStateProps {
icon?: React.ReactNode;
Expand Down Expand Up @@ -37,7 +37,12 @@ export function EmptyState({ icon, title, description, action }: EmptyStateProps
</Text>
)}
{action && (
<Button appearance="primary" size="small" onClick={action.onClick} className={classes.actionBtn}>
<Button
appearance="primary"
size="small"
onClick={action.onClick}
className={classes.actionBtn}
>
{action.label}
</Button>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Text, makeStyles, tokens } from '@fluentui/react-components';
import { Button, makeStyles, Text, tokens } from '@fluentui/react-components';
import { DismissCircle24Regular } from '@fluentui/react-icons';
import type { UserFacingError } from '../../types';

Expand Down
14 changes: 11 additions & 3 deletions src/components/common/ItemTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import {
Badge,
Checkbox,
makeStyles,
Spinner,
Table,
TableBody,
TableCell,
TableHeader,
TableRow,
Text,
makeStyles,
tokens,
} from '@fluentui/react-components';
import { format } from 'date-fns';
Expand Down Expand Up @@ -117,7 +117,9 @@ export function ItemTable<T>({
/>
</th>
)}
<th className="azv-th" style={{ width: 46 }}>#</th>
<th className="azv-th" style={{ width: 46 }}>
#
</th>
{columns.map((col) => (
<th className="azv-th" key={col.key} style={{ width: col.width }}>
{col.label}
Expand Down Expand Up @@ -210,7 +212,13 @@ export function renderTags(tags: Record<string, string> | null) {
{Object.entries(tags)
.slice(0, 3)
.map(([k, v]) => (
<Badge key={k} size="small" appearance="outline" className="azv-tag-pill" title={`${k}=${v}`}>
<Badge
key={k}
size="small"
appearance="outline"
className="azv-tag-pill"
title={`${k}=${v}`}
>
<span className="azv-tag-text">
{k}={v}
</span>
Expand Down
8 changes: 2 additions & 6 deletions src/components/keys/KeyDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {
Button,
Divider,
Field,
Text,
makeStyles,
Text,
tokens,
} from '@fluentui/react-components';
import { Dismiss24Regular, LockClosed24Regular } from '@fluentui/react-icons';
Expand Down Expand Up @@ -206,11 +206,7 @@ function MetaField({ label, value, mono }: { label: string; value: string; mono?
const classes = useStyles();
return (
<Field label={label}>
<Text
size={200}
font={mono ? 'monospace' : undefined}
className={classes.metaFieldValue}
>
<Text size={200} font={mono ? 'monospace' : undefined} className={classes.metaFieldValue}>
{value}
</Text>
</Field>
Expand Down
14 changes: 2 additions & 12 deletions src/components/keys/KeysList.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
Badge,
Button,
Input,
Text,
makeStyles,
tokens,
} from '@fluentui/react-components';
import { Badge, Button, Input, makeStyles, Text, tokens } from '@fluentui/react-components';
import { Search24Regular } from '@fluentui/react-icons';
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
Expand Down Expand Up @@ -92,10 +85,7 @@ function ExpiresCell({ item }: { item: KeyItem }) {
);
const expired = new Date(item.expires) < new Date();
return (
<Text
size={200}
style={{ color: expired ? 'var(--azv-danger)' : undefined }}
>
<Text size={200} style={{ color: expired ? 'var(--azv-danger)' : undefined }}>
{renderDate(item.expires)}
</Text>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/ContentTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tab, TabList, makeStyles, tokens } from '@fluentui/react-components';
import { makeStyles, Tab, TabList, tokens } from '@fluentui/react-components';
import {
Certificate24Regular,
ClipboardTextLtr24Regular,
Expand Down
22 changes: 19 additions & 3 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { Badge, Button, Text, Tooltip, makeStyles, mergeClasses, tokens } from '@fluentui/react-components';
import {
Badge,
Button,
makeStyles,
mergeClasses,
Text,
Tooltip,
tokens,
} from '@fluentui/react-components';
import {
Certificate24Regular,
ClipboardTextLtr24Regular,
Expand All @@ -25,10 +33,18 @@ interface NavItem {
const navIconStyle = { fontSize: 16 } as const;

const VAULT_NAV: NavItem[] = [
{ id: 'dashboard', label: 'Dashboard', icon: <TextBulletListSquare24Regular style={navIconStyle} /> },
{
id: 'dashboard',
label: 'Dashboard',
icon: <TextBulletListSquare24Regular style={navIconStyle} />,
},
{ id: 'secrets', label: 'Secrets', icon: <Key24Regular style={navIconStyle} /> },
{ id: 'keys', label: 'Keys', icon: <LockClosed24Regular style={navIconStyle} /> },
{ id: 'certificates', label: 'Certificates', icon: <Certificate24Regular style={navIconStyle} /> },
{
id: 'certificates',
label: 'Certificates',
icon: <Certificate24Regular style={navIconStyle} />,
},
{ id: 'logs', label: 'Audit Log', icon: <ClipboardTextLtr24Regular style={navIconStyle} /> },
];

Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/StatusBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Badge, Text, makeStyles, tokens } from '@fluentui/react-components';
import { Badge, makeStyles, Text, tokens } from '@fluentui/react-components';
import { useAppStore } from '../../stores/appStore';

const useStyles = makeStyles({
Expand Down
8 changes: 6 additions & 2 deletions src/components/layout/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
MenuList,
MenuPopover,
MenuTrigger,
Text,
makeStyles,
Text,
tokens,
} from '@fluentui/react-components';
import {
Expand Down Expand Up @@ -120,7 +120,11 @@ export function TopBar() {

<WorkspaceSwitcher />

<button type="button" onClick={() => setCommandPaletteOpen(true)} className={classes.paletteBtn}>
<button
type="button"
onClick={() => setCommandPaletteOpen(true)}
className={classes.paletteBtn}
>
<Search24Regular className={classes.searchIcon} />
<span className={classes.paletteBtnText}>Search or run a command...</span>
<span className="azv-kbd">{shortcutLabel}</span>
Expand Down
Loading