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 REMOTE_ACCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The startup banner will show all access URLs automatically:
📍 API Endpoints:
• HTTP: http://localhost:5153
• Remote: http://192.168.1.50:5153
Swagger: http://localhost:5153/swagger
API Docs: http://localhost:5153/scalar/v1
```

### 2. Access from Your Laptop
Expand Down
7 changes: 4 additions & 3 deletions apps/web/src/components/layout/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ export function MainLayout() {
const subscriptionName = searchParams.get('subscription');
const isMessagesPage = window.location.pathname === '/messages';

// Resolve current namespace to check environment
// Resolve current namespace to check environment and permissions
const { data: namespaces } = useNamespaces();
const currentNamespace = namespaces?.find(ns => ns.id === namespaceId);
const isProd = currentNamespace?.environment === 'Prod';
const canUseFab = !isProd && currentNamespace?.hasSendPermission !== false;

// Determine entity type and names for FAB
const entityType: 'queue' | 'topic' = topicName ? 'topic' : 'queue';
Expand Down Expand Up @@ -78,8 +79,8 @@ export function MainLayout() {
</main>
</div>

{/* FAB - Only show on messages page and NOT in production */}
{isMessagesPage && !isProd && (
{/* FAB - Only show on messages page, NOT in production, and only with Send permission */}
{isMessagesPage && canUseFab && (
<MessageFAB
namespaceId={namespaceId}
queueName={entityName}
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/components/messages/MessageDetailPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function ActionButtons({ message, namespaceId, forensicSafety }: ActionButtonsPr
const { data: namespaces } = useNamespaces();
const currentNs = namespaces?.find(ns => ns.id === namespaceId);
const isProd = currentNs?.environment === 'Prod';
const hasSendPermission = currentNs?.hasSendPermission !== false;
// const purgeMessage = usePurgeMessage(); // Removed - Azure Service Bus limitation
const [searchParams] = useSearchParams();

Expand Down Expand Up @@ -226,6 +227,21 @@ function ActionButtons({ message, namespaceId, forensicSafety }: ActionButtonsPr
);
}

// Send permission guard — block replay without Manage SAS policy
if (!hasSendPermission) {
return (
<button
disabled
title="Replay requires a SAS policy with Manage permission. Update your connection string to enable replay."
className="inline-flex items-center gap-2 px-4 py-2 bg-amber-100 text-amber-400 rounded-lg font-medium cursor-not-allowed border border-amber-200"
aria-label="Replay blocked — insufficient permissions"
>
<Play size={16} />
Replay — Manage Required
</button>
);
}

if (!isFromDeadLetter) {
return (
<>
Expand Down
58 changes: 45 additions & 13 deletions apps/web/src/lib/helpContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const glossary: Record<string, { term: string; definition: string }> = {
environment: {
term: 'Environment',
definition:
'Classifies a namespace as Development, UAT, or Production. Production namespaces have safety guards — send and replay actions are disabled to prevent accidental data modification.',
'Classifies a namespace as Development, UAT, or Production. Production namespaces have safety guards — the Quick Actions button (FAB), send, dead-letter, and replay actions are all disabled to prevent accidental data modification.',
},
};

Expand All @@ -66,13 +66,13 @@ export const tooltips = {
connectionString: {
text: 'Azure Service Bus connection string',
detail:
'Paste your Service Bus connection string from the Azure Portal → Shared access policies. We recommend a "Listen-only" policy for read access. An admin key (RootManageSharedAccessKey) will trigger a security warning.',
'Paste your Service Bus connection string from the Azure Portal → Shared access policies. Use a Listen-only policy for read-only browsing, or a Manage policy for full Quick Actions (FAB) functionality including sending messages, generating test data, and dead-lettering.',
action: 'Go to Azure Portal → Service Bus → Shared access policies → Copy the connection string.',
} as TooltipContent,
environment: {
text: "Classify this namespace\u2019s environment",
detail:
'Production namespaces have safety guards: the send-message and replay actions are disabled to prevent accidental data changes. Dev and UAT allow full operations.',
'Production namespaces have safety guards: the Quick Actions button (FAB) is hidden, and send, generate, dead-letter, and replay actions are all disabled. Dev and UAT allow full operations when the SAS policy has Manage permission.',
action: 'Select "Prod" for live namespaces, "Dev" or "Uat" for lower environments.',
} as TooltipContent,
savedConnections: {
Expand Down Expand Up @@ -217,19 +217,19 @@ export const tooltips = {
mainButton: {
text: 'Quick actions menu',
detail:
'Open the floating action menu to send test messages, generate sample data, move messages to the DLQ for testing, or refresh all data. This menu is hidden in Production environments for safety.',
'Open the floating action menu to send test messages, generate sample data, move messages to the DLQ for testing, or refresh all data. Hidden in Production environments and when the SAS policy lacks Manage permission.',
} as TooltipContent,
sendMessage: {
text: 'Send a test message to this queue/topic',
detail: 'Compose and send a JSON message for testing. Only available in Dev/UAT.',
detail: 'Compose and send a JSON message for testing. Requires Manage SAS permission. Only available in Dev/UAT.',
} as TooltipContent,
generateMessages: {
text: 'Generate random sample messages',
detail: 'Creates multiple test messages with realistic payloads for load testing and demo purposes.',
detail: 'Creates multiple test messages with realistic payloads for load testing and demo purposes. Requires Manage SAS permission.',
} as TooltipContent,
testDlq: {
text: 'Move messages to dead-letter queue',
detail: 'Dead-letters up to 3 active messages — useful for testing DLQ monitoring and auto-replay rules.',
detail: 'Dead-letters up to 3 active messages — useful for testing DLQ monitoring and auto-replay rules. Requires Manage SAS permission.',
} as TooltipContent,
},

Expand Down Expand Up @@ -329,12 +329,12 @@ export const helpSections: HelpSection[] = [
{
question: 'What connection string format is required?',
answer:
'The format is: Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=<policy>;SharedAccessKey=<key>. We recommend a "Listen-only" policy for safety.',
'The format is: Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=<policy>;SharedAccessKey=<key>. Use a Listen-only policy for read-only browsing, or a Manage policy for full Quick Actions (FAB) functionality.',
},
{
question: 'What does the environment selector do?',
answer:
'It classifies the namespace as Dev, UAT, or Prod. Production namespaces have safety guards — the send-message, generate-messages, and replay actions are all disabled to prevent accidental data modification.',
'It classifies the namespace as Dev, UAT, or Prod. Production namespaces have strict safety guards — the Quick Actions button (FAB) is completely hidden, and send, generate, dead-letter, and replay actions are all disabled. Dev and UAT allow full operations when the SAS policy has Manage permission.',
},
{
question: 'How do I navigate between namespaces?',
Expand Down Expand Up @@ -371,7 +371,7 @@ export const helpSections: HelpSection[] = [
{
question: 'How does Replay / Resubmit work?',
answer:
'Replay sends a dead-lettered message back to the original queue for reprocessing. It copies the body and properties. Only available in Dev/UAT — Production blocks replays.',
'Replay sends a dead-lettered message back to the original queue for reprocessing. It copies the body and properties. Requires a SAS policy with Send permission. Only available in Dev/UAT — Production blocks replays.',
},
],
},
Expand Down Expand Up @@ -442,17 +442,17 @@ export const helpSections: HelpSection[] = [
{
question: 'Where is the + button / quick actions menu?',
answer:
'The floating action button (FAB) appears in the bottom-right corner on the Messages page. It\'s hidden in Production environments as a safety measure.',
'The floating action button (FAB) appears in the bottom-right corner on the Messages page. It requires: (1) a non-Production namespace, and (2) a SAS policy with Manage permission. If either condition is not met, the FAB is hidden.',
},
{
question: 'Why don\'t I see the + button?',
answer:
'The FAB is hidden in two cases: (1) you\'re not on the Messages page, or (2) your namespace is classified as Production. Switch to a Dev/UAT namespace to see it.',
'The FAB is hidden in three cases: (1) you\'re not on the Messages page, (2) your namespace is classified as Production, or (3) your SAS policy lacks Manage permission. Switch to a Dev/UAT namespace with a Manage policy to see it.',
},
{
question: 'What can I do from the FAB?',
answer:
'Send a test message, generate random sample messages, move messages to the DLQ for testing, or refresh all data. These actions are for development and testing only.',
'Send a test message, generate random sample messages (realistic business scenarios), move messages to the DLQ for testing, or refresh all data. These actions require a Manage SAS policy and are disabled in Production.',
},
],
},
Expand All @@ -473,6 +473,38 @@ export const helpSections: HelpSection[] = [
},
],
},
{
id: 'permissions',
title: 'Permissions & Security',
icon: '🔒',
items: [
{
question: 'What SAS permissions does ServiceHub need?',
answer:
'It depends on your use case. Listen-only is sufficient for browsing messages, inspecting DLQ, and viewing metrics. Manage permission enables the Quick Actions (FAB) button for sending messages, generating test data, dead-lettering, and replay operations.',
},
{
question: 'Why is the Quick Actions (FAB) button hidden?',
answer:
'The FAB requires two conditions: (1) the namespace must be Dev or UAT (not Production), and (2) the SAS policy must have Manage permission. This prevents accidental modification of production data and ensures only authorized users can perform write operations.',
},
{
question: 'What is the difference between Listen, Send, and Manage?',
answer:
'Listen: Read messages without removing them (peek). Send: Write messages to queues/topics and replay from DLQ. Manage: Full control including Send + Listen + administrative operations. ServiceHub recommends Manage for Dev/UAT and Listen-only for Production.',
},
{
question: 'Is it safe to use ServiceHub with production namespaces?',
answer:
'Yes. When a namespace is classified as Production, ServiceHub operates in strict read-only mode — the FAB is hidden, send/dead-letter/replay actions are all blocked both in the UI and at the API level. A Listen-only SAS policy is all you need for Production.',
},
{
question: 'What is Scalar API Docs (/scalar/v1)?',
answer:
'Scalar is an interactive API documentation viewer, only available in Development mode. It lets developers explore and test all API endpoints. It is automatically disabled in Production deployments for security.',
},
],
},
{
id: 'glossary',
title: 'Azure Service Bus Glossary',
Expand Down
31 changes: 18 additions & 13 deletions apps/web/src/pages/ConnectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,17 @@ export function ConnectPage() {
// The backend attempts to detect permissions from the SAS policy name, but this is not always accurate
// since Azure doesn't enforce naming conventions. We show a warning if permissions appear limited.
if (createdNamespace.hasManagePermission === false && createdNamespace.hasSendPermission === false) {
// Listen-only — this is the recommended setup for read-only inspection
// Listen-only — read-only inspection mode
toast.success(
'✓ Connected with Listen-only access. Perfect for DLQ inspection and message browsing. ' +
'Note: Replay operations require a policy with Send permission.',
'Quick Actions (FAB) require a Manage policy for send, generate, and dead-letter operations.',
{ duration: 8000 }
);
} else if (createdNamespace.hasManagePermission === false) {
// Has Listen + Send but not Manage — good enough for most operations
// Has Listen + Send but not Manage
toast(
'✓ Connected. All DLQ inspection and replay features are available. ' +
'Some administrative operations (queue creation) require Manage permission.',
'✓ Connected with Send + Listen access. Replay and send operations are available. ' +
'Some Quick Actions may require Manage permission.',
{
duration: 6000,
style: { background: '#f0fdf4', color: '#166534', border: '1px solid #86efac' },
Expand Down Expand Up @@ -254,21 +254,26 @@ export function ConnectPage() {
<div className="mt-2 space-y-2">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<p className="text-xs text-blue-800 font-medium mb-1">
Recommended: Create a dedicated Listen-only policy (60 seconds, no admin required)
Create a dedicated SAS policy for ServiceHub
</p>
<ol className="text-xs text-blue-700 space-y-0.5 list-decimal list-inside">
<li>Azure Portal → your Service Bus namespace</li>
<li>Shared access policies → + Add policy</li>
<li>Name it <code className="bg-blue-100 px-1 rounded">servicehub</code> → tick <strong>Listen</strong> only</li>
<li>Name it <code className="bg-blue-100 px-1 rounded">servicehub</code></li>
<li>Save → copy Primary Connection String → paste above</li>
</ol>
<p className="text-xs text-blue-600 mt-1">
Listen permission is all ServiceHub needs. No Manage, no admin role, no IT ticket required.
<div className="mt-2 space-y-1">
<p className="text-xs text-blue-700">
<strong>Listen only</strong> — Browse messages, inspect DLQ, view metrics (read-only)
</p>
<p className="text-xs text-blue-700">
<strong>Manage</strong> — Full access: send messages, generate test data, dead-letter, replay (Dev/UAT only)
</p>
</div>
<p className="text-xs text-blue-600 mt-1.5">
⚠️ The Quick Actions button (FAB) requires a policy with <strong>Manage</strong> permission. Production namespaces always hide the FAB for safety.
</p>
</div>
<p className="text-xs text-gray-400">
Also accepts Manage + Send + Listen policies if you already have one configured.
</p>
</div>
</div>

Expand All @@ -287,7 +292,7 @@ export function ConnectPage() {
<option value="Prod">PROD — Production</option>
</select>
<p className="text-xs text-gray-500 mt-1">
Production namespaces have additional safety guards — message sending and dead-lettering are disabled.
Production namespaces have safety guards — Quick Actions (FAB), message sending, dead-lettering, and replay are all disabled.
</p>
</div>

Expand Down
2 changes: 1 addition & 1 deletion services/api/DEPLOYMENT_OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ dotnet watch run --project src/ServiceHub.Api/ServiceHub.Api.csproj

# 4. Access API
# http://localhost:5000
# http://localhost:5000/swagger
# http://localhost:5153/scalar/v1
```

---
Expand Down
2 changes: 1 addition & 1 deletion services/api/DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ A: See [DEPLOYMENT_OPERATIONS.md](./DEPLOYMENT_OPERATIONS.md) Performance Tuning
A: See [ARCHITECTURE.md](./ARCHITECTURE.md) diagram 6 (Security Architecture) and [IMPLEMENTATION_PATTERNS.md](./IMPLEMENTATION_PATTERNS.md) section 5 (Security Implementation).

**Q: Are there example requests?**
A: Yes! See [README.md](./README.md) section "First API Calls" with curl examples, or use Swagger UI at `/swagger`
A: Yes! See [README.md](./README.md) section "First API Calls" with curl examples, or use the API docs at `/scalar/v1`

---

Expand Down
2 changes: 1 addition & 1 deletion services/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dotnet watch --project src/ServiceHub.Api/ServiceHub.Api.csproj
### Access Points

- **API**: http://localhost:5000
- **Swagger UI**: http://localhost:5000/swagger
- **API Docs (Scalar)**: http://localhost:5153/scalar/v1
- **Health Check**: http://localhost:5000/health
- **Ready Check**: http://localhost:5000/health/ready
- **Live Check**: http://localhost:5000/health/live
Expand Down
2 changes: 1 addition & 1 deletion services/api/run-api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ echo -e "${YELLOW}API will be available at:${NC}"
echo -e " • ${GREEN}http://localhost:5153${NC} (local)"
echo -e " • ${GREEN}http://0.0.0.0:5153${NC} (all interfaces)"
echo ""
echo -e "${YELLOW}Swagger UI: ${GREEN}http://localhost:5153/swagger${NC}"
echo -e "${YELLOW}API Docs: ${GREEN}http://localhost:5153/scalar/v1${NC}"
echo ""
echo -e "${YELLOW}Ctrl+C to stop the server${NC}"
echo ""
Expand Down
Loading
Loading