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
2 changes: 1 addition & 1 deletion docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Tale connects to AI models through **providers** — OpenAI-compatible API endpo
Providers are managed in **Settings > Providers** in the management UI. Admins can:

- **Add a provider** with a name, display name, base URL, API key, and one or more models
- **Edit a provider** to update its configuration or add/remove models
- **Edit a provider** to update its display name, description, base URL, and default models. The description is shown in the provider list to help users understand what the provider is for. Default models let you pre-select which model is used for chat, vision, and embedding when users pick this provider.
- **Delete a provider** to remove it entirely

Each model definition includes an ID (must match the model name expected by the API), a display name, and one or more tags (`chat`, `vision`, `embedding`) that control where the model appears in the platform.
Expand Down
24 changes: 24 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ Customize the look of the platform for your organization. Admin only. Available

Available to all users. Change your password here. If you signed up via SSO, you can also set a regular password from this page to enable direct login.

## Governance

Admin only. Configure organization-wide AI policies and controls. The governance page is organized into three groups accessible from a left-hand navigation:

### Content & Models

- **System Prompt**: set a global system prompt prepended to every AI conversation in the organization.
- **Default Models**: choose the default chat, vision, and embedding models used when users don't pick one explicitly.
- **Model Access**: control which models are available to specific teams or users.

### Policies & Limits

- **Budgets**: set spending limits per user, team, or the entire organization with configurable periods and thresholds.
- **Upload Policy**: restrict file uploads by type, size, or count.
- **Retention**: configure how long conversations and files are kept before automatic deletion.
- **Feature Controls**: toggle platform features (e.g., file uploads, web search, image generation) on or off organization-wide.

### Security & Monitoring

- **PII Detection**: enable automatic detection and masking (or blocking) of personally identifiable information in messages. Supports built-in patterns and custom regex rules.
- **Usage Dashboard**: view token consumption, cost breakdowns, and usage trends across the organization.

## Audit logs

Admin only. A time-ordered record of significant actions taken in the organization. Categories include authentication events, member changes, data operations, integration updates, workflow publications, security events, and admin actions. Useful for compliance and troubleshooting.

Admins can export audit logs as **CSV** or **JSON** using the export buttons above the log table. Exports respect the currently active category filter.
19 changes: 7 additions & 12 deletions examples/providers/openrouter.secrets.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
{
"apiKey": "ENC[AES256_GCM,data:MJKxyEKDQFk2Y+E3R7eK13LwIxO9FQ6TxAYc93NlRc56As9XMaLTx0CNfM82MyoHyQCduKgCf3a8RK9r2bWVgo8NnVnd2YzGOA==,iv:OqTfslCeZbiYkEQqAI/Jj0NLuJW1h0hdZPus8Sel3cU=,tag:yaBSdC8+gmPdEcfLmECXOg==,type:str]",
"apiKey": "ENC[AES256_GCM,data:Os9IU5ZlUhe7QWMhqrhsVIFNKVD2FzszIkHGOb9l8Oa8z57AZUek+XK7qM//OyY60sMQQjVkbnygU7175lgO05lXlWRLqWiksA==,iv:2rpxkfK022BbwBCDwhu2YvlbF+HXv+KPaLPu6dQWX/k=,tag:gqMQcSCc9Q2wGJaer5V3jw==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age1xsc5y9x0dref9kd6fwv2356pw2zl5s7gp5v6jam9h4q7mv6fm9aqumvqhj",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPYnV2WjVHR3ZFSGRlYmxR\ncnUybXVRcWNrVFkrMVlnTkZ1bFhNRmk3WjBVCmtSZ2RsbFZCdWY5dk94RlhhU1dN\nQnZnejFvYW4vVTBOUWVBUHJRaEovQmcKLS0tIENlVUJmTjFDcXZjWjV4RitnZHlv\nN09UeFo5Y2xnZXRwQ3RabEc2QVgyckUK7u0Avecl9B628T56Np6gGxQ1+yCSRjXa\n8HBlTjMa4g1OR8d4isfZ9VE+lETdNaVlultd9F1GWEvgv/p7WxpeUg==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age18ylfcfvf9we4rc5hpza6n9tvhwuw55jfun9wdpd5uhux5rs3qf4q95y2y4",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByUU1FMndnUDAzeTZySW1x\nMFJwNEhuYXF4UlZZWGRQaS85SjNkbmVpMG44CjVhTEh4c1BNZ1VXZXN3R2cxeFBt\nVjB6Sm5BOXc1NFdCcGMzS1BEWDJHeWMKLS0tIEFUekUrOHFmMSsraFdMOVU4d3FJ\nTzdwWWtiNytmTVNRQW5MTExRVEk3MmcKHmSVxvF3Nz/yMGZ3lFY+oDcryUdoOT7/\nmxnY/U4ZX3sCkLphYdUVuAA6nqstfzVn57X1ND8vqaFXPn2lhVc00Q==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-04-05T11:39:10Z",
"mac": "ENC[AES256_GCM,data:2OYyLhDFsCM/spi9pp6bUXwuShtuqumEv7brGMrcRMdziaX8tQTnEL8Kcd774ne3BFDg+sfAnX2SH7e66qUxXd1qMXJWNN96lkjStc422rilpXWvq29RDKjatuAf7qSZoF0rWFNK9sAeQ0J+6bHytQ5FT3DXnS0SoXozMxCvz3A=,iv:8fx48QGvx8Nudqo1ByyWnPsebKn6BpAHQzw5rvj7zdg=,tag:21dyPzSJgJNpnStSvxs+yA==,type:str]",
"pgp": null,
"lastmodified": "2026-04-12T18:34:34Z",
"mac": "ENC[AES256_GCM,data:dTQzeY02n+I8wUXpfhlHBAQ8EZgMmPo1lMKZFtvbyEl9VWgYc9Q9+Bde0g6hmpXJNKqNx4VNhiQV6NFl0oyQQNpaN6yAu9Ka1gs64eymLUwuiBigSvSoiBOoICGaHx9rNhdgu55vU3xCGpp1C4lwGdj6lpKYJ7Pefdc0ZS1rnLo=,iv:ZBoTH20q8nPOp5H11WQiiH/VrKMao2RcaBiu9TeSVx4=,tag:HiqFbmIQMpO5Id0S7ls6Gw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.9.4"
"version": "3.12.2"
Comment on lines +2 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only checks for potential key-material coupling and accidental secret-key commits.

set -euo pipefail

echo "1) Locate AGE private key patterns in repository (should be none):"
rg -n --hidden --no-ignore -P 'AGE-SECRET-KEY-1[0-9A-Z]+' || true

echo
echo "2) Locate references to this specific AGE recipient:"
rg -n --hidden --no-ignore 'age18ylfcfvf9we4rc5hpza6n9tvhwuw55jfun9wdpd5uhux5rs3qf4q95y2y4' || true

echo
echo "3) List committed provider secrets example files for manual provenance audit:"
fd -H '.*\.secrets\.json$' examples providers services || true

Repository: tale-project/tale

Length of output: 504


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Check git history for this file (last 5 commits) ==="
git log --oneline -5 -- examples/providers/openrouter.secrets.json 2>/dev/null || echo "No git history found"

echo
echo "=== 2) Check for README or documentation about secrets management ==="
fd -H -i 'readme|doc.*secret|secret.*doc' examples/ || true

echo
echo "=== 3) Search for comments in the examples directory about test/dummy secrets ==="
rg -n -i 'dummy|placeholder|test.*secret|example.*key' examples/ --type json --type md || true

echo
echo "=== 4) Check if sops/decryption is tested or documented ==="
fd -H -i 'sops\|secret' . --path '*/test*' --path '*/spec*' -o --path '*/*.test.*' --path '*/*.spec.*' | head -20 || true

echo
echo "=== 5) Look for any .env.example or pattern files ==="
fd -H -e 'example' -e 'template' examples/ || true

Repository: tale-project/tale

Length of output: 4746


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Full content of openrouter.secrets.json ==="
cat examples/providers/openrouter.secrets.json

echo
echo "=== 2) Check for any README or documentation in examples/providers ==="
ls -la examples/providers/ | grep -i readme || true

echo
echo "=== 3) Check for SOPS keys directory or configuration ==="
find . -type f -name '.sops.yaml' -o -name 'sops.yaml' -o -name '.sops.*' 2>/dev/null | head -20 || true

echo
echo "=== 4) Git show full commit details for the last change ==="
git show 62053de7 --stat | head -40 || true

echo
echo "=== 5) Search for test/development AGE keys or SOPS documentation ==="
rg -n -i 'age.*key\|sops.*test\|test.*sops' . --type md --type txt --type yaml 2>/dev/null | head -20 || true

Repository: tale-project/tale

Length of output: 3882


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Check how provider secrets are loaded/used ==="
rg -n -A 5 -B 5 'openrouter.secrets.json\|provider\.secrets\.apiKey' . --type ts --type tsx --type js --type jsx 2>/dev/null | head -40 || true

echo
echo "=== 2) Search for test fixtures or test setup with AGE keys ==="
rg -n -i 'test.*age\|fixture.*secret\|mock.*secret' . --type ts --type tsx --type js --type json 2>/dev/null | head -30 || true

echo
echo "=== 3) Look for file_actions.ts and how it loads secrets ==="
fd -H 'file_actions.ts' . -exec head -60 {} \; 2>/dev/null || true

echo
echo "=== 4) Check if there are integration tests that use this provider ==="
fd -H '.test.ts\|.spec.ts' . --path '*openrouter*' -o --path '*provider*' -exec wc -l {} \; 2>/dev/null | head -10 || true

echo
echo "=== 5) Search for any unencrypted example of what the API key looks like ==="
rg -n -i 'sk-\|openrouter\|api.key' examples/ --type json --type md 2>/dev/null | head -20 || true

Repository: tale-project/tale

Length of output: 391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Verify repository structure ==="
find . -type d -name 'services' -o -type d -name 'convex' -o -type d -name 'providers' | head -20

echo
echo "=== 2) Search for ANY file that references .secrets.json ==="
rg -l 'secrets\.json' . --type ts --type tsx --type js --type json 2>/dev/null | head -20 || true

echo
echo "=== 3) List all files in examples/providers ==="
find examples/providers -type f 2>/dev/null || true

echo
echo "=== 4) Search for any provider-related code ==="
rg -l 'provider' . --type ts --type tsx --type js 2>/dev/null | head -20 || true

echo
echo "=== 5) Check .gitignore for secrets handling ==="
grep -i 'secret\|\.env' .gitignore 2>/dev/null || true

Repository: tale-project/tale

Length of output: 961


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Read openrouter.json (non-encrypted counterpart) ==="
cat examples/providers/openrouter.json

echo
echo "=== 2) Search for any documentation about example files ==="
rg -n -B 2 -A 2 'example\|openrouter' examples/providers/ --type json --type md 2>/dev/null || true

echo
echo "=== 3) Check if .secrets.json should be in .gitignore ==="
rg -n '\.secrets|secrets\.json' .gitignore || echo "Not found in .gitignore"

echo
echo "=== 4) Check services/platform/convex/providers for actual usage ==="
ls -la services/platform/convex/providers/ 2>/dev/null | head -20 || true

echo
echo "=== 5) Search for SOPS or encryption-related code ==="
rg -n 'sops\|decrypt\|encrypt' services/platform/convex/ --type ts --type tsx --type js 2>/dev/null | head -30 || true

Repository: tale-project/tale

Length of output: 7228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Search for secret or apiKey references in file_actions.ts ==="
rg -n -i 'secret\|apikey\|credential' services/platform/convex/providers/file_actions.ts | head -20 || true

echo
echo "=== 2) Read sections of file_actions.ts that mention provider ==="
rg -n -B 3 -A 10 'provider\|secret' services/platform/convex/providers/file_actions.ts 2>/dev/null | head -80 || true

echo
echo "=== 3) Check if there's documentation about how to set up providers ==="
find . -name '*README*' -o -name '*SETUP*' -o -name '*GUIDE*' 2>/dev/null | xargs grep -l 'provider\|secret\|openrouter' 2>/dev/null | head -10 || true

echo
echo "=== 4) Check if tests use or mock the secrets file ==="
rg -n 'openrouter\|\.secrets\|provider.*test' . --type ts --type tsx 2>/dev/null | head -20 || true

Repository: tale-project/tale

Length of output: 337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Check README for provider/secret setup documentation ==="
rg -n -B 2 -A 5 'provider\|secret\|openrouter' ./README.md 2>/dev/null | head -40 || true

echo
echo "=== 2) Read first 100 lines of file_actions.ts to understand its purpose ==="
head -100 services/platform/convex/providers/file_actions.ts

echo
echo "=== 3) Look for imports in file_actions.ts ==="
head -30 services/platform/convex/providers/file_actions.ts | grep -E 'import|require'

echo
echo "=== 4) Search for any reference to json files in provider loading ==="
rg -n '\.json\|load.*file\|readFile' services/platform/convex/providers/ --type ts --type tsx 2>/dev/null | head -20 || true

Repository: tale-project/tale

Length of output: 4025


🏁 Script executed:

#!/bin/bash
set -euco pipefail

echo "=== 1) Read more of file_actions.ts to see secrets loading ==="
sed -n '80,150p' services/platform/convex/providers/file_actions.ts

echo
echo "=== 2) Look at the decryptSecretsFile function ==="
cat services/platform/convex/lib/sops.ts 2>/dev/null | head -80

echo
echo "=== 3) Check resolveProviderSecretsPath implementation ==="
rg -n -A 5 'resolveProviderSecretsPath' services/platform/convex/providers/file_utils.ts 2>/dev/null | head -20

echo
echo "=== 4) Search for how the example files in examples/providers are used ==="
rg -n 'examples/providers' . --type ts --type tsx --type md 2>/dev/null | head -20 || true

echo
echo "=== 5) Check if there's a test AGE key or setup instructions ==="
rg -n -i 'age.*test\|test.*key\|setup.*provider' . --type md --type txt --type sh 2>/dev/null | head -20 || true

Repository: tale-project/tale

Length of output: 4612


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Verify examples/providers is NOT the actual config directory ==="
rg -n 'TALE_CONFIG_DIR\|providers.*dir' . --type ts --type tsx --type md 2>/dev/null | head -20 || true

echo
echo "=== 2) Check what happens if decryption fails - is example used at all? ==="
sed -n '100,140p' services/platform/convex/providers/file_actions.ts

echo
echo "=== 3) Verify the example file cannot be decrypted without the AGE private key ==="
echo "Checking: Does the repo contain any AGE private keys?"
rg -n 'AGE-SECRET-KEY\|age.*private' . --type txt --type sh --type env 2>/dev/null || echo "No AGE private keys found"

echo
echo "=== 4) Search for documentation on setting up local development with providers ==="
find . -name '*.md' -o -name '*.txt' 2>/dev/null | xargs grep -l 'provider.*setup\|TALE_CONFIG_DIR\|local.*develop' 2>/dev/null | head -5 || true

Repository: tale-project/tale

Length of output: 1927


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Check docs/environment-reference.md for TALE_CONFIG_DIR ==="
grep -A 10 -B 2 'TALE_CONFIG_DIR\|providers' ./docs/environment-reference.md 2>/dev/null | head -40 || true

echo
echo "=== 2) Check README for provider setup instructions ==="
grep -A 10 -B 2 'provider\|TALE_CONFIG_DIR' ./README.md 2>/dev/null | head -50 || true

echo
echo "=== 3) Verify examples/ directory is documentation only ==="
ls -la examples/

echo
echo "=== 4) Confirm: the code loads from TALE_CONFIG_DIR, not examples/ ==="
rg -n 'TALE_CONFIG_DIR' services/platform/convex/providers/file_utils.ts 2>/dev/null | head -10 || true

Repository: tale-project/tale

Length of output: 2333


Add documentation clarifying that openrouter.secrets.json is a template example.

This file cannot be decrypted or used by the application without a matching AGE private key (which is not stored in the repository), so it poses no immediate security risk. However, since any encrypted secrets committed to a repository can be a source of confusion, consider adding an inline comment or updating docs/environment-reference.md to explicitly state that examples/providers/openrouter.secrets.json is a template showing the expected SOPS-encrypted structure, and that users should generate their own via tale init.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/providers/openrouter.secrets.json` around lines 2 - 13, Add an
explicit inline comment at the top of examples/providers/openrouter.secrets.json
stating it is a template SOPS-encrypted example (not decryptable without the AGE
private key) and not intended for direct use, and also update
docs/environment-reference.md to include a short note referencing that example
file and instructing users to generate their own encrypted secrets via the tale
init command (and never commit private keys).

}
}
}
2 changes: 1 addition & 1 deletion services/platform/app/components/ui/forms/input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ describe('Input', () => {
it('sets autocomplete for password', () => {
render(<Input type="password" label="Password" />);
const input = screen.getByLabelText('Password');
expect(input).toHaveAttribute('autocomplete', 'current-password');
expect(input).toHaveAttribute('autocomplete', 'off');
});

it('allows custom autocomplete', () => {
Expand Down
2 changes: 1 addition & 1 deletion services/platform/app/components/ui/forms/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const Input = forwardRef<HTMLInputElement, BaseProps>(
const [showShake, setShowShake] = useState(false);
const inputType = isPassword ? (show ? 'text' : 'password') : type;
const resolvedAutoComplete =
autoComplete ?? (isPassword ? 'current-password' : undefined);
autoComplete ?? (isPassword ? 'off' : undefined);
const hasError = !!errorMessage;
const showInvalid = hasError || !!isInvalid;
const describedBy =
Expand Down
42 changes: 24 additions & 18 deletions services/platform/app/components/ui/navigation/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface TabsProps {
onValueChange?: (value: string) => void;
className?: string;
listClassName?: string;
/** Optional actions rendered to the right of the tab list */
actions?: ReactNode;
}

export function Tabs({
Expand All @@ -28,6 +30,7 @@ export function Tabs({
onValueChange,
className,
listClassName,
actions,
}: TabsProps) {
return (
<TabsPrimitive.Root
Expand All @@ -36,30 +39,33 @@ export function Tabs({
onValueChange={onValueChange}
className={className}
>
<TabsPrimitive.List
className={cn(
'scrollbar-hide inline-flex items-center overflow-x-auto bg-muted p-1 text-muted-foreground rounded-lg',
listClassName,
)}
>
{items.map((item) => (
<TabsPrimitive.Trigger
key={item.value}
value={item.value}
disabled={item.disabled}
className="ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm"
>
{item.label}
</TabsPrimitive.Trigger>
))}
</TabsPrimitive.List>
<div className="flex items-center justify-between gap-4">
<TabsPrimitive.List
className={cn(
'scrollbar-hide inline-flex items-center overflow-x-auto bg-muted p-1 text-muted-foreground rounded-lg',
listClassName,
)}
>
{items.map((item) => (
<TabsPrimitive.Trigger
key={item.value}
value={item.value}
disabled={item.disabled}
className="ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm"
>
{item.label}
</TabsPrimitive.Trigger>
))}
</TabsPrimitive.List>
{actions && <div className="shrink-0">{actions}</div>}
</div>
{items.map(
(item) =>
item.content && (
<TabsPrimitive.Content
key={item.value}
value={item.value}
className="ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
className="ring-offset-background focus-visible:ring-ring mt-2 flex min-h-0 flex-1 flex-col focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
>
{item.content}
</TabsPrimitive.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ function MessageBubbleComponent({
</div>
)}
{!isUser && !isAssistantStreaming && !!displayContent && (
<div className="flex items-center pt-2">
<div className="flex items-start pt-2">
<Tooltip
content={isCopied ? t('actions.copied') : t('actions.copy')}
side="bottom"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ function ChangePasswordDialog({ open, onOpenChange }: PasswordDialogProps) {
<Input
id="current-password"
type="password"
autoComplete="current-password"
label={tAuth('changePassword.currentPassword')}
placeholder={tAuth('changePassword.placeholder.current')}
disabled={isSubmitting}
Expand All @@ -281,6 +282,7 @@ function ChangePasswordDialog({ open, onOpenChange }: PasswordDialogProps) {
<Input
id="new-password"
type="password"
autoComplete="new-password"
label={tAuth('changePassword.newPassword')}
placeholder={tAuth('changePassword.placeholder.new')}
disabled={isSubmitting}
Expand All @@ -298,6 +300,7 @@ function ChangePasswordDialog({ open, onOpenChange }: PasswordDialogProps) {
<Input
id="confirm-password"
type="password"
autoComplete="new-password"
label={tAuth('changePassword.confirmPassword')}
placeholder={tAuth('changePassword.placeholder.confirm')}
disabled={isSubmitting}
Expand Down Expand Up @@ -398,6 +401,7 @@ function SetPasswordDialog({ open, onOpenChange }: PasswordDialogProps) {
<Input
id="new-password"
type="password"
autoComplete="new-password"
label={tAuth('setPassword.newPassword')}
placeholder={tAuth('changePassword.placeholder.new')}
disabled={isSubmitting}
Expand All @@ -415,6 +419,7 @@ function SetPasswordDialog({ open, onOpenChange }: PasswordDialogProps) {
<Input
id="confirm-password"
type="password"
autoComplete="new-password"
label={tAuth('setPassword.confirmPassword')}
placeholder={tAuth('changePassword.placeholder.confirm')}
disabled={isSubmitting}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@ import { useCallback, useMemo, useState } from 'react';
import { DataTable } from '@/app/components/ui/data-table/data-table';
import { Dialog } from '@/app/components/ui/dialog/dialog';
import { Stack } from '@/app/components/ui/layout/layout';
import { Button } from '@/app/components/ui/primitives/button';
import { Text } from '@/app/components/ui/typography/text';
import { useConvexAction } from '@/app/hooks/use-convex-action';
import { useFormatDate } from '@/app/hooks/use-format-date';
import { useListPage } from '@/app/hooks/use-list-page';
import { useToast } from '@/app/hooks/use-toast';
import { api } from '@/convex/_generated/api';
import type { Doc } from '@/convex/_generated/dataModel';
import { useT } from '@/lib/i18n/client';
import { cn } from '@/lib/utils/cn';
Expand All @@ -26,21 +22,18 @@ interface AuditLogTableProps {
organizationId: string;
paginatedResult: UsePaginatedQueryResult<AuditLog>;
category?: string;
isAdmin?: boolean;
userEmailMap?: Map<string, string>;
}

export function AuditLogTable({
organizationId,
paginatedResult,
category,
isAdmin: isAdminUser = false,
userEmailMap,
}: AuditLogTableProps) {
const navigate = useNavigate();
const { formatDate } = useFormatDate();
const { t } = useT('settings');
const { toast } = useToast();
const [selectedLog, setSelectedLog] = useState<AuditLog | null>(null);

const resolveEmail = useCallback(
Expand All @@ -53,35 +46,6 @@ export function AuditLogTable({
resolveEmail,
});

const exportAction = useConvexAction(api.audit_logs.actions.requestExport, {
onSuccess: (data) => {
if (data.url) {
window.open(data.url, '_blank', 'noopener,noreferrer');
}
toast({
title: t('logs.audit.export.complete'),
description: data.fileName,
});
},
onError: () => {
toast({
title: t('logs.audit.export.error'),
variant: 'destructive',
});
},
});

const handleExport = useCallback(
(format: 'csv' | 'json') => {
exportAction.mutate({
organizationId,
format,
filter: category ? { category } : undefined,
});
},
[organizationId, category, exportAction],
);

const handleCategoryChange = useCallback(
(values: string[]) => {
void navigate({
Expand Down Expand Up @@ -146,33 +110,6 @@ export function AuditLogTable({

return (
<>
{isAdminUser && (
<div className="mb-4 flex items-center gap-2">
<Button
variant="secondary"
size="sm"
onClick={() => handleExport('csv')}
disabled={exportAction.isPending}
aria-label={t('logs.audit.export.csvLabel')}
>
{exportAction.isPending
? t('logs.audit.export.inProgress')
: t('logs.audit.export.csv')}
</Button>
<Button
variant="secondary"
size="sm"
onClick={() => handleExport('json')}
disabled={exportAction.isPending}
aria-label={t('logs.audit.export.jsonLabel')}
>
{exportAction.isPending
? t('logs.audit.export.inProgress')
: t('logs.audit.export.json')}
</Button>
</div>
)}

<DataTable
columns={columns}
caption={t('logs.audit.tableCaption')}
Expand Down
Loading
Loading