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
1 change: 1 addition & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
API_KEY=your_secret_key_here
4 changes: 3 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The left sidebar provides access to all main features:
- **Snapshots** - View and manage saved snapshots
- **PV Browser** - Browse and search for PVs
- **Tags** - Manage tag groups for organizing PVs
- **API Keys** _(Admin only)_ - Create and manage API keys for programmatic access

### Main Content Area

Expand All @@ -38,10 +39,11 @@ The main area displays the content for the selected feature. Most views include:

### Admin Mode

Some features require admin privileges. If you have admin access, you can toggle admin mode using the switch in the sidebar.
Some features require admin privileges. If you have admin access, you can toggle admin mode using the switch in the sidebar. With admin mode enabled, additional items appear in the sidebar — including **API Keys**.

## Next Steps

- Learn about [Snapshots](user-guide/snapshots.md) - the core feature for saving PV states
- Explore the [PV Browser](user-guide/pv-browser.md) to find and manage PVs
- Set up [Tags](user-guide/tags.md) to organize your PVs
- Manage [API Keys](user-guide/api-keys.md) for programmatic access (admin only)
Binary file added docs/images/api_key_creation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/api_key_token.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/api_keys_page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions docs/user-guide/api-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# API Keys

API keys allow programmatic access to the Squirrel backend. They are used to authenticate requests made outside the browser UI (e.g., scripts, integrations, or other tools). Each key carries independent read and/or write permissions.

> **Admin only** — the API Keys page is only visible when admin mode is enabled.

## Accessing the API Keys Page

1. Enable admin mode using the toggle in the sidebar.
2. Click **Manage API Keys** in the sidebar (under the admin section).

![API Keys page](../images/api_keys_page.png)

The page shows a table of all existing keys with their status, permissions, and available actions.

## Creating an API Key

1. Click the **Create Key** button in the top-right corner of the page.
2. Enter an **Application Name** to identify what the key will be used for.
3. Check at least one permission:
- **Read Access** — allows read operations
- **Write Access** — allows write operations
4. Click **CREATE**.

![Create key dialog](../images/api_key_creation.png)

## Copying Your Token

After the key is created, the generated token is displayed **once**. You must copy it immediately — it will not be shown again.

![Token display dialog](../images/api_key_token.png)

- Click the copy icon to copy the token to your clipboard.
- Store it somewhere secure, such as a password manager.
- Click **I'VE COPIED MY KEY** to dismiss the dialog.

If you lose the token, you will need to deactivate the key and create a new one.

## Deactivating a Key

In the API Keys table, click the **Deactivate** button in the Actions column for the key you want to revoke. The key's status will change to **Inactive** and it will no longer authenticate requests.

Deactivation is permanent — a deactivated key cannot be re-activated.

## Bootstrap Key

If no API keys exist yet, a **Bootstrap Key** option is available. This creates an initial key so you can get started without needing an existing key to authenticate.

## API Key Error Banner

If Squirrel detects that the configured API key is missing or invalid, a banner appears at the top of the page:

> **API KEY INVALID OR EXPIRED**

Navigate to the API Keys page to create a new key or verify that a valid key is in use.
5 changes: 5 additions & 0 deletions docs/user-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ This guide covers all the features of Squirrel in detail.
5. **[PV Details](pv-details.md)** - View detailed information about individual PVs
6. **[Tags](tags.md)** - Organize PVs into groups for easier filtering

### Admin

7. **[API Keys](api-keys.md)** - Create and manage API keys for programmatic access (admin only)

## Quick Reference

| Task | Where to Go |
Expand All @@ -30,3 +34,4 @@ This guide covers all the features of Squirrel in detail.
| Add new PVs | PV Browser → Add PV or Import CSV |
| Restore saved values | Snapshot Details → Select PVs → Restore |
| Filter PVs by tag | Any PV table → Tag filter dropdown |
| Create/manage API keys | API Keys (admin mode required) |
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ nav:
- PV Details: user-guide/pv-details.md
- Tags: user-guide/tags.md
- Restore: user-guide/restore.md
- API Keys: user-guide/api-keys.md
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"devDependencies": {
"@tanstack/router-devtools": "^1.166.11",
"@tanstack/router-vite-plugin": "^1.166.19",
"@types/node": "^25.3.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^7.18.0",
Expand Down
36 changes: 26 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/components/ApiKeyErrorBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// src/components/ApiKeyErrorBanner.tsx
import { Alert, Collapse, Typography } from '@mui/material';
import { VpnKey as KeyIcon } from '@mui/icons-material';
import { useApiKeyError } from '../contexts/ApiKeyErrorContext';

export function ApiKeyErrorBanner() {
const { hasApiKeyError } = useApiKeyError();

return (
<Collapse in={hasApiKeyError}>
<Alert severity="error" icon={<KeyIcon />} sx={{ borderRadius: 0 }}>
<Typography variant="body1" fontWeight="bold">
API KEY INVALID OR EXPIRED
</Typography>
<Typography variant="body2" sx={{ mt: 0.5 }}>
Requests are being rejected. Check your API key configuration.
</Typography>
</Alert>
</Collapse>
);
}
4 changes: 4 additions & 0 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Sidebar } from './Sidebar';
import { CreateSnapshotDialog } from './CreateSnapshotDialog';
import { LiveDataWarningBanner } from './LiveDataWarningBanner';
import { useAdminMode } from '../contexts/AdminModeContext';
import { ApiKeyErrorBanner } from './ApiKeyErrorBanner';

interface LayoutProps {
children: ReactNode;
Expand Down Expand Up @@ -100,6 +101,9 @@ export function Layout({ children }: LayoutProps) {
</Toolbar>
</AppBar>

{/* Warning banner - shows when API Key is missing or invalid */}
<ApiKeyErrorBanner />

{/* Warning banner - shows when PV monitor is dead */}
<LiveDataWarningBanner />

Expand Down
41 changes: 41 additions & 0 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import {
PhotoCamera as CameraIcon,
Search as SearchIcon,
Label as LabelIcon,
Key as KeyIcon,
ChevronLeft as ChevronLeftIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
} from '@mui/icons-material';
import { Link, useLocation, useNavigate } from '@tanstack/react-router';
import { useSnapshot } from '../contexts';
import { useAdminMode } from '../contexts/AdminModeContext';

const SIDEBAR_WIDTH_EXPANDED = 240;
const SIDEBAR_WIDTH_COLLAPSED = 60;
Expand Down Expand Up @@ -65,13 +67,16 @@ export function Sidebar({ open, onToggle, onSaveSnapshot }: SidebarProps) {
const location = useLocation();
const navigate = useNavigate();
const { snapshotProgress, clearSnapshot } = useSnapshot();
const { isAdminMode } = useAdminMode();

const menuItems = [
{ title: 'View Snapshots', icon: <ApertureIcon />, path: '/snapshots' },
{ title: 'Browse PVs', icon: <SearchIcon />, path: '/pv-browser' },
{ title: 'Configure Tags', icon: <LabelIcon />, path: '/tags' },
];

const adminMenuItems = [{ title: 'Manage API Keys', icon: <KeyIcon />, path: '/api-keys' }];

const isActive = (path: string) =>
location.pathname === path || location.pathname.startsWith(`${path}/`);

Expand Down Expand Up @@ -184,6 +189,42 @@ export function Sidebar({ open, onToggle, onSaveSnapshot }: SidebarProps) {
</ListItemButton>
</ListItem>
))}

{/* Admin only Navigation Menu Items */}
{isAdminMode && <Divider sx={{ backgroundColor: 'rgba(255, 255, 255, 0.12)', my: 1 }} />}
{isAdminMode &&
adminMenuItems.map((item) => (
<ListItem key={item.path} disablePadding sx={{ mb: 0.5 }}>
<ListItemButton
component={Link}
to={item.path}
sx={{
borderRadius: 1,
minHeight: 48,
justifyContent: open ? 'initial' : 'center',
px: 2,
backgroundColor: isActive(item.path)
? 'rgba(255, 255, 255, 0.15)'
: 'transparent',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
},
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: open ? 2 : 'auto',
justifyContent: 'center',
color: 'white',
}}
>
{item.icon}
</ListItemIcon>
{open && <ListItemText primary={item.title} sx={{ color: 'white' }} />}
</ListItemButton>
</ListItem>
))}
</List>

{/* Snapshot Status and Save Button at Bottom */}
Expand Down
11 changes: 11 additions & 0 deletions src/config/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const API_CONFIG = {
pvs: '/v1/pvs',
tags: '/v1/tags',
jobs: '/v1/jobs',
apiKeys: '/v1/api-keys',
},
timeout: 30000, // 30 seconds
};
Expand All @@ -33,3 +34,13 @@ export interface PagedResultDTO<T> {
continuationToken?: string;
totalCount?: number;
}

/**
* Thrown when the backend rejects a request due to an invalid or missing API key.
*/
export class ApiKeyError extends Error {
constructor(public status: 401 | 403) {
super(status === 401 ? 'API key is missing or invalid' : 'API key does not have access');
this.name = 'ApiKeyError';
}
}
Loading
Loading