65c3f099fa
Adds complete app password provisioning workflow for multi-user BasicAuth
deployments, allowing users to independently enable background sync by
generating and storing Nextcloud app passwords.
**New Components:**
Backend (PHP):
- CredentialsController: Validates and stores app passwords
* Validates app password format and authenticity via OCS API
* Stores encrypted passwords in oc_preferences
* Provides status and credential management endpoints
- AstrolabeAdminSettings: Admin configuration page for MCP server URL
- AstrolabeAdminSettingsListener: Event listener for admin section
- Updated McpTokenStorage: Added background sync credential methods
Frontend:
- personalSettings.js: Form handling for app password entry
* AJAX submission with error handling
* Shows success/error notifications
* Triggers page reload after successful save
- settings.css: Styling for settings pages
- Updated personal.php template: Two-option UI
* Option 1: OAuth refresh token (future, not yet available)
* Option 2: App password (works today, recommended)
* Shows "Active" badge when provisioned
* Displays credential type and provisioned timestamp
Routes:
- POST /api/v1/background-sync/credentials - Store app password
- GET /api/v1/background-sync/status - Get provisioning status
- DELETE /api/v1/background-sync/credentials - Revoke credentials
- GET /api/v1/background-sync/credentials/{userId} - Admin only
**Testing:**
- test_astrolabe_settings_buttons.py: Integration test for UI buttons
**Workflow:**
1. User generates app password in Nextcloud Security settings
2. User navigates to Astrolabe personal settings
3. User enters app password in "Option 2: App Password" form
4. Backend validates password via OCS API call
5. Password stored encrypted in oc_preferences
6. Page reloads showing "Active" badge with credential details
7. MCP server can now use stored password for background operations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
291 lines
6.4 KiB
CSS
291 lines
6.4 KiB
CSS
/**
|
|
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*
|
|
* Astrolabe settings styles
|
|
* Relies on Nextcloud's core .section class for layout
|
|
*/
|
|
|
|
/* Info tables */
|
|
.mcp-info-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: calc(var(--default-grid-baseline) * 3) 0;
|
|
}
|
|
|
|
.mcp-info-table tr {
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
.mcp-info-table tr:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.mcp-info-table td {
|
|
padding: calc(var(--default-grid-baseline) * 2) 0;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.mcp-info-table td:first-child {
|
|
width: 200px;
|
|
color: var(--color-text-maxcontrast);
|
|
font-weight: 600;
|
|
padding-inline-end: calc(var(--default-grid-baseline) * 4);
|
|
}
|
|
|
|
.mcp-info-table td:last-child {
|
|
color: var(--color-main-text);
|
|
}
|
|
|
|
/* Status badges */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: calc(var(--default-grid-baseline) * 1.5);
|
|
padding: calc(var(--default-grid-baseline) * 1.5) calc(var(--default-grid-baseline) * 3);
|
|
border-radius: calc(var(--border-radius-element) * 1.5);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge-success {
|
|
background: var(--color-success);
|
|
color: var(--color-success-text);
|
|
}
|
|
|
|
.badge-warning {
|
|
background: var(--color-warning);
|
|
color: var(--color-warning-text);
|
|
}
|
|
|
|
.badge-neutral {
|
|
background: var(--color-background-dark);
|
|
color: var(--color-text-maxcontrast);
|
|
}
|
|
|
|
.badge-info {
|
|
background: var(--color-primary-element);
|
|
color: var(--color-primary-element-text);
|
|
}
|
|
|
|
/* Input groups */
|
|
.mcp-input-group {
|
|
display: flex;
|
|
gap: calc(var(--default-grid-baseline) * 2);
|
|
align-items: stretch;
|
|
margin-top: calc(var(--default-grid-baseline) * 2);
|
|
}
|
|
|
|
.mcp-input-group input[type='password'],
|
|
.mcp-input-group input[type='text'] {
|
|
flex: 1;
|
|
font-family: monospace;
|
|
}
|
|
|
|
/* Revoke/warning sections */
|
|
.mcp-revoke-section {
|
|
margin-top: calc(var(--default-grid-baseline) * 4);
|
|
padding: calc(var(--default-grid-baseline) * 4);
|
|
background: var(--color-warning);
|
|
border-radius: var(--border-radius-element);
|
|
border-inline-start: calc(var(--default-grid-baseline)) solid var(--color-warning-text);
|
|
}
|
|
|
|
/* Feature lists */
|
|
.mcp-feature-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: calc(var(--default-grid-baseline) * 3) 0;
|
|
}
|
|
|
|
.mcp-feature-list li {
|
|
display: flex;
|
|
gap: calc(var(--default-grid-baseline) * 3);
|
|
padding: calc(var(--default-grid-baseline) * 2) 0;
|
|
align-items: start;
|
|
}
|
|
|
|
.mcp-feature-list .icon {
|
|
flex-shrink: 0;
|
|
width: 24px;
|
|
height: 24px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.mcp-feature-list div {
|
|
flex: 1;
|
|
}
|
|
|
|
.mcp-feature-list strong {
|
|
display: block;
|
|
font-weight: 600;
|
|
margin-bottom: calc(var(--default-grid-baseline));
|
|
}
|
|
|
|
.mcp-feature-list p {
|
|
margin: 0;
|
|
color: var(--color-text-maxcontrast);
|
|
}
|
|
|
|
/* Responsive tables */
|
|
@media (max-width: 768px) {
|
|
.mcp-info-table td:first-child,
|
|
.mcp-info-table td:last-child {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.mcp-info-table td:first-child {
|
|
padding-bottom: calc(var(--default-grid-baseline));
|
|
}
|
|
|
|
.mcp-info-table td:last-child {
|
|
padding-top: calc(var(--default-grid-baseline));
|
|
}
|
|
}
|
|
|
|
/* Admin settings forms */
|
|
.mcp-settings-form {
|
|
max-width: 600px;
|
|
}
|
|
|
|
.mcp-form-group {
|
|
margin-bottom: calc(var(--default-grid-baseline) * 5);
|
|
}
|
|
|
|
.mcp-form-group label {
|
|
display: block;
|
|
font-weight: 600;
|
|
margin-bottom: calc(var(--default-grid-baseline) * 2);
|
|
}
|
|
|
|
.mcp-range {
|
|
width: 100%;
|
|
margin-top: calc(var(--default-grid-baseline) * 2);
|
|
accent-color: var(--color-primary-element);
|
|
}
|
|
|
|
.mcp-form-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: calc(var(--default-grid-baseline) * 4);
|
|
margin-top: calc(var(--default-grid-baseline) * 6);
|
|
padding-top: calc(var(--default-grid-baseline) * 5);
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
|
|
/* Webhook preset cards */
|
|
.mcp-preset-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: calc(var(--default-grid-baseline) * 4);
|
|
margin: calc(var(--default-grid-baseline) * 4) 0;
|
|
}
|
|
|
|
.mcp-preset-card {
|
|
background: var(--color-background-dark);
|
|
border-radius: var(--border-radius-container);
|
|
padding: calc(var(--default-grid-baseline) * 4);
|
|
border: 2px solid transparent;
|
|
transition: border-color var(--animation-slow), box-shadow var(--animation-slow);
|
|
}
|
|
|
|
.mcp-preset-card:hover {
|
|
border-color: var(--color-border-dark);
|
|
box-shadow: 0 2px 8px var(--color-box-shadow);
|
|
}
|
|
|
|
.mcp-preset-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: calc(var(--default-grid-baseline) * 3);
|
|
}
|
|
|
|
.mcp-preset-header h4 {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mcp-preset-status {
|
|
padding: calc(var(--default-grid-baseline)) calc(var(--default-grid-baseline) * 2.5);
|
|
border-radius: calc(var(--border-radius-element) * 1.5);
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.mcp-status-enabled {
|
|
background: var(--color-success);
|
|
color: var(--color-success-text);
|
|
}
|
|
|
|
.mcp-status-disabled {
|
|
background: var(--color-background-darker);
|
|
color: var(--color-text-maxcontrast);
|
|
}
|
|
|
|
.mcp-preset-description {
|
|
color: var(--color-text-maxcontrast);
|
|
margin-bottom: calc(var(--default-grid-baseline) * 3);
|
|
}
|
|
|
|
.mcp-preset-meta {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding-top: calc(var(--default-grid-baseline) * 3);
|
|
border-top: 1px solid var(--color-border);
|
|
margin-bottom: calc(var(--default-grid-baseline) * 3);
|
|
font-size: 12px;
|
|
color: var(--color-text-maxcontrast);
|
|
}
|
|
|
|
.mcp-preset-actions {
|
|
display: flex;
|
|
gap: calc(var(--default-grid-baseline) * 2);
|
|
}
|
|
|
|
.mcp-preset-toggle {
|
|
flex: 1;
|
|
padding: calc(var(--default-grid-baseline) * 2) calc(var(--default-grid-baseline) * 4);
|
|
border-radius: var(--border-radius-element);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all var(--animation-quick);
|
|
border: none;
|
|
}
|
|
|
|
.mcp-preset-toggle.primary {
|
|
background: var(--color-primary-element);
|
|
color: var(--color-primary-element-text);
|
|
}
|
|
|
|
.mcp-preset-toggle.primary:hover:not(:disabled) {
|
|
background: var(--color-primary-element-hover);
|
|
}
|
|
|
|
.mcp-preset-toggle.secondary {
|
|
background: var(--color-background-darker);
|
|
color: var(--color-main-text);
|
|
border: 1px solid var(--color-border);
|
|
}
|
|
|
|
.mcp-preset-toggle.secondary:hover:not(:disabled) {
|
|
background: var(--color-background-hover);
|
|
border-color: var(--color-border-dark);
|
|
}
|
|
|
|
.mcp-preset-toggle:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.mcp-loading {
|
|
text-align: center;
|
|
padding: calc(var(--default-grid-baseline) * 5);
|
|
color: var(--color-text-maxcontrast);
|
|
font-style: italic;
|
|
}
|