Skip to content
Open
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
22 changes: 17 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ vue/src/ # Frontend source

## PATTERNS TO ALWAYS USE
2. **Use Traits**: ManageMedias, HasMedias, MediasTrait etc.
3. **Register in ServiceProvider**: Every new feature if necessary
3. **Feature ServiceProvider**: New feature singletons/bindings go in a dedicated `src/Providers/{Feature}ServiceProvider.php` (e.g. `RemoteApiServiceProvider`, `ArtisanRunnerServiceProvider`, `CoverageServiceProvider`) — register it from `ModularousProvider::$providers`. Do **not** dump feature bindings into `BaseServiceProvider`.
4. **Write Tests**: tests/$FOLDERNAME
5. **Type Hints**: Always use PHP 8.1+ type hints
6. **Config-Driven**: Use config('modularous.xxx') (under merges folder)
Expand All @@ -45,10 +45,22 @@ vue/src/ # Frontend source
- Use Vuetify 3 components (not plain HTML)

## WHEN ADDING FEATURES
1. Create class in appropriate src/ subdirectory
2. Register in ModularousServiceProvider
3. Write unit + feature tests
4. Update documentation
1. Create classes under the feature folder (e.g. `src/Services/{Feature}/`)
2. Create `src/Providers/{Feature}ServiceProvider.php` for that feature’s singletons, aliases, log channels, and feature-specific boot logic
3. Register the provider in `ModularousProvider::$providers`
4. Write unit + feature tests
5. Update documentation

## FEATURE SERVICE PROVIDERS

| Feature | Provider |
|---------|----------|
| Coverage | `CoverageServiceProvider` |
| Security | `SecurityServiceProvider` |
| RemoteApi | `RemoteApiServiceProvider` |
| ArtisanRunner | `ArtisanRunnerServiceProvider` |

`BaseServiceProvider` stays for package-wide core bindings only (Modularous, navigation, cache, filepond, etc.).

## FORBIDDEN
- ❌ Business logic in controllers
Expand Down
5 changes: 5 additions & 0 deletions config/defers/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
'icon' => '$header',
],
...Navigation::systemMenu(),
'_artisan_runner' => [
'name' => 'Artisan Runner',
'icon' => 'mdi-console',
'route_name' => 'admin.artisan-runner',
],
'media_library' => [
'name' => 'Media Library',
'icon' => '$media',
Expand Down
38 changes: 37 additions & 1 deletion config/defers/ui_settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,43 @@
],
'dashboard' => [
'blocks' => [
'system-console' => [
'widget' => 'SystemConsoleWidget',
'widgetCol' => [
'cols' => 12,
'lg' => 6,
],
'widgetAttributes' => [
'class' => 'h-50',
'style' => 'min-height: 160px',
],
'allowedRoles' => ['superadmin'],
'attributes' => [
'class' => 'h-100',
'title' => 'System Console',
'subtitle' => 'Maintenance mode and cache / optimize commands.',
'elevation' => 2,
],
],
'artisan-runner' => [
'widget' => 'ArtisanRunnerWidget',
'widgetCol' => [
'cols' => 12,
'lg' => 6,
],
'widgetAttributes' => [
'class' => 'h-50',
'style' => 'min-height: 160px',
],
'allowedRoles' => ['superadmin'],
'attributes' => [
'class' => 'h-100',
'title' => 'Artisan Runner',
'subtitle' => 'Select a command to configure and run.',
'elevation' => 2,
],
],

],
],
]
];
65 changes: 65 additions & 0 deletions config/merges/artisan_runner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

return [
'enabled' => env('MODULAROUS_ARTISAN_RUNNER_ENABLED', false),

/**
* Who can open the ArtisanRunner panel (page + API).
*
* @var list<string>
*/
'allowed_roles' => array_values(array_filter(array_map(
'trim',
explode(',', env('MODULAROUS_ARTISAN_RUNNER_ALLOWED_ROLES', 'superadmin'))
))),

/**
* Command allowlist for non-superadmin users in allowed_roles.
* Superadmin always bypasses this and sees/runs all Artisan commands.
* Supports exact names and globs (e.g. modularous:cache:*).
*
* SystemConsoleWidget uses down, up, route:clear, route:cache, optimize,
* and optimize:clear via the same runner. If you grant admin (non-superadmin)
* access to that widget, include those command names in this allowlist.
*
* @var list<string>
*/
'allowlist' => array_values(array_filter(array_map(
'trim',
explode(',', env('MODULAROUS_ARTISAN_RUNNER_ALLOWLIST', ''))
))),

/**
* How ArtisanRunner executes commands:
* - auto: in-process Kernel::handle, except subprocess_commands (CLI parity)
* - in_process: always Kernel::handle (interactive prompts work)
* - subprocess: always spawn `php artisan …` (no BridgedQuestionHelper)
*/
'execution' => env('MODULAROUS_ARTISAN_RUNNER_EXECUTION', 'auto'),

/**
* Commands that must run as a real CLI subprocess when execution=auto.
*
* route:cache (and optimize) bootstrap a fresh app to serialize routes. Under
* PHP-FPM, runningInConsole() is false and request()/isPanelUrl() still reflect
* the panel host, so Modularous skips CMS public catch-alls and may omit module
* routes — producing an incomplete route cache vs terminal `php artisan`.
*
* Supports exact names and globs (via AllowlistMatcher).
*
* @var list<string>
*/
'subprocess_commands' => array_values(array_filter(array_map(
'trim',
explode(',', env(
'MODULAROUS_ARTISAN_RUNNER_SUBPROCESS_COMMANDS',
'route:cache,route:clear,config:cache,config:clear,event:cache,event:clear,view:cache,view:clear,optimize,optimize:clear'
))
))),

'timeout' => (int) env('MODULAROUS_ARTISAN_RUNNER_TIMEOUT', 120),

'prompt_timeout' => (int) env('MODULAROUS_ARTISAN_RUNNER_PROMPT_TIMEOUT', 60),

'max_output_bytes' => (int) env('MODULAROUS_ARTISAN_RUNNER_MAX_OUTPUT_BYTES', 1_048_576),
];
75 changes: 75 additions & 0 deletions config/merges/system_console.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

return [
/**
* Named presets for `php artisan down`.
* Option keys map to Artisan option names (without leading --).
* Empty / null option values are omitted when running.
*
* System Settings → General → `down_presets` (render/retry/refresh/secret)
* overrides the `default` preset options when non-empty; otherwise these
* config values (including MODULAROUS_MAINTENANCE_SECRET) are used.
*
* Example equivalent:
* php artisan down --render='modularous::maintenance' --retry=60 --refresh=30 --secret="…"
*
* @var array<string, array{label: string, options: array<string, mixed>}>
*/
'down_presets' => [
'default' => [
'label' => 'Default maintenance',
'options' => [
'render' => 'modularous::maintenance',
'retry' => 60,
'refresh' => 30,
'secret' => env('MODULAROUS_MAINTENANCE_SECRET'),
],
],
],

'default_down_preset' => 'default',

/**
* Granular cache / optimize actions shown in SystemConsoleWidget.
* Commands must be runnable via ArtisanRunner (superadmin or allowlist).
*
* @var list<array{
* command: string,
* label: string,
* confirm?: bool,
* color?: string,
* variant?: string,
* icon?: string
* }>
*/
'cache_commands' => [
'route:clear' => [
'command' => 'route:clear',
'label' => 'Route Clear',
'confirm' => false,
'color' => 'warning',
'icon' => 'mdi-routes',
],
'route:cache' => [
'command' => 'route:cache',
'label' => 'Route Cache',
'confirm' => true,
'color' => 'primary',
'icon' => 'mdi-routes-clock',
],
'optimize:clear' => [
'command' => 'optimize:clear',
'label' => 'Optimize Clear',
'confirm' => true,
'color' => 'warning',
'icon' => 'mdi-cached',
],
'optimize' => [
'command' => 'optimize',
'label' => 'Optimize',
'confirm' => true,
'color' => 'primary',
'icon' => 'mdi-rocket-launch',
],
],
];
1 change: 1 addition & 0 deletions config/merges/system_settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
'mail_override_enabled' => (bool) env('MODULAROUS_SYSTEM_SETTINGS_MAIL_OVERRIDE', false),
'sensitive_keys' => [
'smtp.password',
'down_presets.secret',
],
'extra_inputs' => [],
'input_overrides' => [],
Expand Down
5 changes: 5 additions & 0 deletions config/navigation.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
'icon' => '$header',
],
...Navigation::systemMenu(),
'_artisan_runner' => [
'name' => 'Artisan Runner',
'icon' => 'mdi-console',
'route_name' => 'admin.artisan-runner',
],
'media_library' => [
'name' => 'Media Library',
'icon' => '$media',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Backend / system source of truth for global settings. Full usage guide: [Setting

## Sections

`site`, `social`, `contact`, `seo`, `smtp`, `analytics`, `locale`
`site`, `social`, `contact`, `seo`, `smtp`, `analytics`, `locale`, `down_presets`

## Methods

Expand Down
6 changes: 0 additions & 6 deletions modules/Cms/Routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,6 @@
? 'modularous.security.step_up:' . $sitemapCommitAbility
: null;

$siteSeoSave = Route::post('site-seo', [SiteSeoSettingsController::class, 'update'])
->name('siteSeo.save');

if ($siteSeoStepUpMiddleware) {
$siteSeoSave->middleware($siteSeoStepUpMiddleware);
}

$sitemapDryRun = Route::post('sitemap/dry-run', [SitemapController::class, 'dryRun'])
->name('sitemap.dryRun.web');
Expand Down
44 changes: 44 additions & 0 deletions modules/SystemSetting/Config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,50 @@
['name' => 'ga_measurement_id', 'type' => 'text', 'label' => 'GA measurement ID', 'placeholder' => 'G-XXXXXXXXXX', 'rules' => 'nullable|string|max:64', 'col' => ['cols' => 12, 'lg' => 6]],
],
],
[
'type' => '@collapsible_group',
'typeIntTitle' => 'Maintenance down presets',
'name' => 'down_presets',
'col' => ['cols' => 12],
'schema' => [
[
'name' => 'render',
'type' => 'text',
'label' => 'Render view',
'placeholder' => 'modularous::maintenance',
'default' => 'modularous::maintenance',
'hint' => 'Blade view passed to artisan down --render',
'rules' => 'nullable|string|max:255',
'col' => ['cols' => 12, 'lg' => 6],
],
[
'name' => 'retry',
'type' => 'number-input',
'label' => 'Retry (seconds)',
'placeholder' => 60,
'default' => 60,
'hint' => 'Retry-After header for clients',
'col' => ['cols' => 12, 'lg' => 3],
],
[
'name' => 'refresh',
'type' => 'number-input',
'label' => 'Refresh (seconds)',
'placeholder' => 30,
'default' => 30,
'hint' => 'Meta refresh interval on the maintenance page',
'col' => ['cols' => 12, 'lg' => 3],
],
[
'name' => 'secret',
'type' => 'password',
'label' => 'Bypass secret',
'hint' => 'Secret token for artisan down --secret (overrides MODULAROUS_MAINTENANCE_SECRET when set)',
'rules' => 'nullable|string|max:255',
'col' => ['cols' => 12, 'lg' => 6],
],
],
],

[
'type' => '@collapsible_group',
Expand Down
4 changes: 3 additions & 1 deletion modules/SystemSetting/Entities/General.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class General extends Model
'smtp',
'analytics',
'locale',
'down_presets',
];

/**
Expand All @@ -38,6 +39,7 @@ class General extends Model
'smtp' => 'array',
'analytics' => 'array',
'locale' => 'array',
'down_presets' => 'array',
'published' => 'boolean',
];

Expand All @@ -46,6 +48,6 @@ class General extends Model
*/
public static function settingsSections(): array
{
return ['site', 'social', 'contact', 'seo', 'smtp', 'analytics', 'locale'];
return ['site', 'social', 'contact', 'seo', 'smtp', 'analytics', 'locale', 'down_presets'];
}
}
Loading
Loading