├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── .readme ├── CustomFields.md ├── ModifyingTemplates.md ├── ModulesReadme.md ├── excalidraw │ └── FileCopyingGraph.excalidraw └── images │ ├── codeDownloadExample.png │ ├── createCrudButton.png │ ├── createCrudForm.png │ ├── createPanelForm.png │ ├── crudEditor.png │ ├── crudFieldFormExample.png │ ├── crudFormExample.png │ ├── crudListExample.png │ ├── exampleGraphOfFilesToCopy.png │ ├── generateCodePage.png │ ├── generateDownloadCode.png │ ├── loginScreen.png │ ├── mainDashboard.png │ ├── newCrudFieldButton.png │ ├── newCrudFieldModal.png │ ├── panelDropdown.png │ └── relatedCrudField.png ├── README.md ├── app ├── Enums │ ├── CrudFieldTypes.php │ ├── CrudFieldValidation.php │ ├── CrudTypes.php │ ├── HeroIcons.php │ └── PanelTypes.php ├── Filament │ ├── Pages │ │ ├── CreatePanelPage.php │ │ ├── PanelDeploymentPage.php │ │ └── PanelModuleManagement.php │ └── Resources │ │ ├── CrudResource.php │ │ └── CrudResource │ │ ├── Pages │ │ ├── CreateCrud.php │ │ ├── EditCrud.php │ │ └── ListCruds.php │ │ └── RelationManagers │ │ └── FieldsRelationManager.php ├── Http │ ├── Controllers │ │ └── Controller.php │ └── Responses │ │ └── LoginResponse.php ├── Interfaces │ └── ModuleBase.php ├── Jobs │ └── Generator │ │ ├── GeneratePanelCodeJob.php │ │ └── PanelCreatedJob.php ├── Models │ ├── Crud.php │ ├── CrudField.php │ ├── CrudFieldOptions.php │ ├── Module.php │ ├── Panel.php │ ├── PanelDeployment.php │ ├── PanelFile.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── Filament │ │ └── BuilderPanelProvider.php │ ├── HorizonServiceProvider.php │ └── TelescopeServiceProvider.php └── Services │ ├── ModuleService.php │ └── PanelService.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── cache.php ├── database.php ├── filament.php ├── filesystems.php ├── horizon.php ├── logging.php ├── mail.php ├── queue.php ├── reverb.php ├── services.php ├── session.php └── telescope.php ├── database ├── .gitignore ├── factories │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 2024_03_28_095305_create_panels_table.php │ ├── 2024_04_02_141033_create_cruds_table.php │ ├── 2024_04_02_141038_create_crud_fields_table.php │ ├── 2024_04_03_105626_create_crud_field_options_table.php │ ├── 2024_04_04_154557_create_panel_files_table.php │ ├── 2024_04_04_155618_create_modules_table.php │ ├── 2024_04_04_162604_create_module_panel_table.php │ ├── 2024_04_09_153641_create_panel_deployments_table.php │ └── 2024_04_18_124339_create_telescope_entries_table.php └── seeders │ └── DatabaseSeeder.php ├── package-lock.json ├── package.json ├── phpstan.neon ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── css │ └── filament │ │ ├── filament │ │ └── app.css │ │ ├── forms │ │ └── forms.css │ │ └── support │ │ └── support.css ├── favicon.ico ├── index.php ├── js │ └── filament │ │ ├── filament │ │ ├── app.js │ │ └── echo.js │ │ ├── forms │ │ └── components │ │ │ ├── color-picker.js │ │ │ ├── date-time-picker.js │ │ │ ├── file-upload.js │ │ │ ├── key-value.js │ │ │ ├── markdown-editor.js │ │ │ ├── rich-editor.js │ │ │ ├── select.js │ │ │ ├── tags-input.js │ │ │ └── textarea.js │ │ ├── notifications │ │ └── notifications.js │ │ ├── support │ │ ├── async-alpine.js │ │ └── support.js │ │ ├── tables │ │ └── components │ │ │ └── table.js │ │ └── widgets │ │ └── components │ │ ├── chart.js │ │ └── stats-overview │ │ └── stat │ │ └── chart.js ├── robots.txt └── vendor │ ├── horizon │ ├── app-dark.css │ ├── app.css │ ├── app.js │ ├── favicon.png │ ├── img │ │ ├── favicon.png │ │ ├── horizon.svg │ │ └── sprite.svg │ ├── manifest.json │ ├── mix-manifest.json │ ├── styles-dark.css │ └── styles.css │ └── telescope │ ├── app-dark.css │ ├── app.css │ ├── app.js │ ├── favicon.ico │ └── mix-manifest.json ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ ├── bootstrap.js │ └── echo.js └── views │ └── filament │ ├── footer.blade.php │ └── pages │ ├── create-panel-page.blade.php │ ├── panel-deployment-page.blade.php │ ├── panel-module-management.blade.php │ ├── panels-create-panel.blade.php │ ├── panels-edit-panel.blade.php │ ├── panels-panels-list.blade.php │ └── panels │ └── edit-panel.blade.php ├── routes ├── auth.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── debugbar │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── systems └── generators │ ├── filament3 │ ├── composer.json │ └── src │ │ ├── Filament3ServiceProvider.php │ │ ├── Generators │ │ ├── Fields │ │ │ ├── BaseField.php │ │ │ ├── BelongsToField.php │ │ │ ├── BelongsToManyField.php │ │ │ ├── CheckboxField.php │ │ │ ├── DateField.php │ │ │ ├── DateTimeField.php │ │ │ ├── EmailField.php │ │ │ ├── FileField.php │ │ │ ├── FloatField.php │ │ │ ├── IdField.php │ │ │ ├── ImageField.php │ │ │ ├── MoneyField.php │ │ │ ├── PasswordField.php │ │ │ ├── RetrieveGeneratorForField.php │ │ │ ├── TextAreaField.php │ │ │ └── TextField.php │ │ └── Files │ │ │ ├── CreateFile.php │ │ │ ├── EditFile.php │ │ │ ├── FileBase.php │ │ │ ├── FileReplacements.php │ │ │ ├── ListFile.php │ │ │ └── ResourceFile.php │ │ ├── IndentsLines.php │ │ ├── Jobs │ │ ├── CreateCreateFileJob.php │ │ ├── CreateCrudJob.php │ │ ├── CreateEditFileJob.php │ │ ├── CreateListFileJob.php │ │ └── CreateResourceFileJob.php │ │ ├── Modules │ │ ├── AssetManagementModule.php │ │ ├── BaseModule.php │ │ ├── ClientManagementModule.php │ │ └── ModuleManager.php │ │ └── templates │ │ ├── createPage.blade.php │ │ ├── editPage.blade.php │ │ ├── listPage.blade.php │ │ └── resource.blade.php │ └── laravel11 │ ├── composer.json │ └── src │ ├── Generators │ ├── MigrationGenerator.php │ ├── MigrationLineGenerator.php │ └── ModelGenerator.php │ ├── Jobs │ ├── CreateManyToManyMigrationJob.php │ ├── CreateMigrationJob.php │ └── CreateModelJob.php │ ├── Laravel11ServiceProvider.php │ └── templates │ ├── cacheTable.blade.php │ ├── jobsTable.blade.php │ ├── migration.blade.php │ ├── model.blade.php │ └── sessionTable.blade.php ├── tailwind.config.js ├── tests ├── Feature │ ├── Crud │ │ ├── CrudFieldsTest.php │ │ └── CrudTest.php │ ├── Filament3 │ │ ├── Fields │ │ │ ├── BelongsToFieldTest.php │ │ │ ├── BelongsToManyFieldTest.php │ │ │ ├── CheckboxFieldTest.php │ │ │ ├── DateFieldTest.php │ │ │ ├── DateTimeFieldTest.php │ │ │ ├── EmailFieldTest.php │ │ │ ├── FileFieldTest.php │ │ │ ├── FloatFieldTest.php │ │ │ ├── IdFieldTest.php │ │ │ ├── ImageFieldTest.php │ │ │ ├── MoneyFieldTest.php │ │ │ ├── PasswordFieldTest.php │ │ │ ├── TextFieldTest.php │ │ │ └── TextareaFieldTest.php │ │ └── Files │ │ │ ├── CreateFileTest.php │ │ │ ├── EditFileTest.php │ │ │ ├── FileReplacementsTest.php │ │ │ ├── ListFileTest.php │ │ │ └── ResourceFileTest.php │ ├── Laravel11 │ │ └── Files │ │ │ ├── MigrationTest.php │ │ │ └── ModelTest.php │ └── Panel │ │ └── PanelTest.php ├── Pest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=FilaStart 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=sqlite 23 | # DB_HOST=127.0.0.1 24 | # DB_PORT=3306 25 | # DB_DATABASE=laravel 26 | # DB_USERNAME=root 27 | # DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | BROADCAST_CONNECTION=log 36 | FILESYSTEM_DISK=local 37 | QUEUE_CONNECTION=sync 38 | 39 | CACHE_STORE=database 40 | CACHE_PREFIX= 41 | 42 | MEMCACHED_HOST=127.0.0.1 43 | 44 | REDIS_CLIENT=phpredis 45 | REDIS_HOST=127.0.0.1 46 | REDIS_PASSWORD=null 47 | REDIS_PORT=6379 48 | 49 | MAIL_MAILER=log 50 | MAIL_HOST=127.0.0.1 51 | MAIL_PORT=2525 52 | MAIL_USERNAME=null 53 | MAIL_PASSWORD=null 54 | MAIL_ENCRYPTION=null 55 | MAIL_FROM_ADDRESS="hello@example.com" 56 | MAIL_FROM_NAME="${APP_NAME}" 57 | 58 | AWS_ACCESS_KEY_ID= 59 | AWS_SECRET_ACCESS_KEY= 60 | AWS_DEFAULT_REGION=us-east-1 61 | AWS_BUCKET= 62 | AWS_USE_PATH_STYLE_ENDPOINT=false 63 | 64 | VITE_APP_NAME="${APP_NAME}" 65 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpunit.result.cache 12 | Homestead.json 13 | Homestead.yaml 14 | auth.json 15 | npm-debug.log 16 | yarn-error.log 17 | /.fleet 18 | /.idea 19 | /.vscode 20 | dump.rdb 21 | -------------------------------------------------------------------------------- /.readme/CustomFields.md: -------------------------------------------------------------------------------- 1 | FilaStart is built with customization in mind. You can create your own custom fields to suit your needs. Here's a quick guide on how to make a custom field: 2 | 3 | ## Defining the Field 4 | 5 | First, we need to tell our system that this field exists. To do this, open up: 6 | 7 | **app/Enums/CrudFieldTypes.php** 8 | 9 | And add a new field type: 10 | 11 | ```php 12 | // ... 13 | const CUSTOM_FIELD = 'custom_field'; 14 | // ... 15 | ``` 16 | 17 | Of course, remember to add it to the `getLabel()` method, as it will automatically populate the select field. 18 | 19 | ## Creating the Field Class 20 | 21 | Next, we need a new class for our field. Create a new file in: 22 | 23 | **systems/generators/filament3/src/Generators/Fields** 24 | 25 | **Note:** You can copy and modify one of the existing fields to suit your needs. 26 | 27 | Once that is done - you can override methods as you need. But here's a few important ones: 28 | 29 | ```php 30 | // Class to use in the form 31 | protected string $formComponentClass = 'DatePicker'; 32 | 33 | // Class to use in the table 34 | protected string $tableColumnClass = 'TextColumn'; 35 | 36 | // The key to use in the form 37 | protected function resolveFormComponent(): void 38 | { 39 | $this->formKey = $this->field->key; 40 | } 41 | 42 | // The key to use in the table 43 | protected function resolveTableColumn(): void 44 | { 45 | $this->tableKey = $this->field->key; 46 | } 47 | ``` 48 | 49 | Once this is done, we have another step to take - register the field in the generator. 50 | 51 | Open up `systems/generators/filament3/src/Generators/Fields/RetrieveGeneratorForField.php` and add your field to the match statement. 52 | 53 | ## Using the Field 54 | 55 | You can now use your field in the CRUD editor. Select the "Custom Field" type and fill in the form as you would with any other field. 56 | 57 | ## Testing 58 | 59 | We strongly recommend testing your field before using it in production. To do this, create a new file in `tests/Feature/Filament3/Fields` and make a test for your field. 60 | 61 | ## Other Customizations 62 | 63 | When creating a new field, remember to look at `systems/generators/filament3/src/Generators/Fields/BaseField.php`, as this is the base class for all fields. You can override any method you need in your custom field. -------------------------------------------------------------------------------- /.readme/ModifyingTemplates.md: -------------------------------------------------------------------------------- 1 | While our generator covers most basic things, you should add different stuff to your files. To do that, you can modify the templates we use: 2 | 3 | ## Modifying Filament Templates 4 | 5 | Our Filament file templates are in the `systems/generators/filament3/src/templates` directory. 6 | 7 | You can modify the files in that directory to change the generated code. 8 | 9 | **Note:** Sometimes, you might need to modify the generators themselves. Search for the file name in the `systems/generators/filament3/src/Generators` directory and modify the file. 10 | 11 | ## Modifying Laravel Templates 12 | 13 | Our Laravel file templates are in the `systems/generators/laravel11/src/templates` directory. 14 | 15 | As with Filament, you can modify the files in that directory to change the generated code. 16 | 17 | **Note:** Sometimes, you might need to modify the generators themselves. Search for the file name in the `systems/generators/laravel11/src/Generators` directory and modify the file. 18 | 19 | --- 20 | 21 | File templates are written in Blade, so you can use all its features. -------------------------------------------------------------------------------- /.readme/ModulesReadme.md: -------------------------------------------------------------------------------- 1 | A quick overview of how Modules work. 2 | 3 | ## What is a Module? 4 | 5 | In this system, a module is a set of pre-defined CRUD details. For example, a module can contain more information for a `User` CRUD. 6 | 7 | This module will have the following details: 8 | 9 | - Unlimited amount of CRUDs inside 10 | - Each CRUD will have its own details 11 | - Each CRUD will have its own fields 12 | - Each field will have its own details 13 | 14 | These modules are quickly installed using `app/Interfaces/ModuleBase.php` methods `install()` and `uninstall()`. 15 | 16 | Each module, if needed, can override those methods. 17 | 18 | --- 19 | 20 | ## How to create a Module? 21 | 22 | To create a module, you have a few options: 23 | 24 | 1. Start from scratch 25 | 2. Copy one of the existing modules and modify it 26 | 27 | In both cases, the result should be the same: 28 | 29 | 1. A file in the `systems/generators/filament3/src/Modules` folder 30 | 2. A-line with unique `slug` in `systems/generators/filament3/src/Modules/ModuleManager.php.` 31 | 32 | Now, each module should have an implementation of the `getCruds()` method. This method should return an array of CRUDs. For example, take from `BaseModule`: 33 | 34 | ```php 35 | public function getCruds(): array 36 | { 37 | return [ 38 | (new Crud([ 39 | 'type' => CrudTypes::PARENT, 40 | 'title' => str('User Management')->singular()->studly(), 41 | 'visual_title' => 'User Management', 42 | 'icon' => 'heroicon-o-users', 43 | 'menu_order' => 1, 44 | 'is_hidden' => false, 45 | 'module_crud' => true, 46 | 'system' => true, 47 | ])), 48 | (new Crud([ 49 | 'parent_id' => str('User Management')->singular()->studly(), 50 | 'type' => CrudTypes::CRUD, 51 | 'title' => str('Permissions')->singular()->studly(), 52 | 'visual_title' => 'Permissions', 53 | 'icon' => '', 54 | 'menu_order' => 1, 55 | 'is_hidden' => false, 56 | 'module_crud' => true, 57 | 'system' => true, 58 | ])) 59 | ->setRelation('fields', [ 60 | $this->getIDField(), 61 | new CrudField([ 62 | 'type' => CrudFieldTypes::TEXT, 63 | 'key' => str('Title')->lower() 64 | ->snake() 65 | ->toString(), 66 | 'label' => 'Title', 67 | 'validation' => 'required', 68 | 'in_list' => true, 69 | 'in_show' => true, 70 | 'in_create' => true, 71 | 'in_edit' => true, 72 | 'nullable' => false, 73 | 'tooltip' => null, 74 | 'system' => true, 75 | 'enabled' => true, 76 | 'order' => 2, 77 | ]), 78 | $this->getCreatedAtField(3), 79 | $this->getUpdatedAtField(4), 80 | $this->getDeletedAtField(5), 81 | ] 82 | ), 83 | ]; 84 | } 85 | ``` 86 | 87 | Everything is done via new Module instances. We don't save them to the database; we use them as DTOs. 88 | 89 | --- 90 | 91 | ## Installing / Removing a Module 92 | 93 | Installation/Removal is handled automatically in the system. It is done via: 94 | 95 | ```php 96 | ModuleService::getModuleClass(App\Models\Panel, 'module-slug-goes-here') 97 | ->install(App\Models\Panel); 98 | ``` 99 | 100 | And: 101 | 102 | ```php 103 | ModuleService::getModuleClass(App\Models\Panel, 'module-slug-goes-here') 104 | ->uninstall(App\Models\Panel); 105 | ``` 106 | 107 | Both methods accept a `Panel` (the current admin panel) as a parameter and a module SLUG. 108 | 109 | --- -------------------------------------------------------------------------------- /.readme/images/codeDownloadExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/codeDownloadExample.png -------------------------------------------------------------------------------- /.readme/images/createCrudButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/createCrudButton.png -------------------------------------------------------------------------------- /.readme/images/createCrudForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/createCrudForm.png -------------------------------------------------------------------------------- /.readme/images/createPanelForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/createPanelForm.png -------------------------------------------------------------------------------- /.readme/images/crudEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/crudEditor.png -------------------------------------------------------------------------------- /.readme/images/crudFieldFormExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/crudFieldFormExample.png -------------------------------------------------------------------------------- /.readme/images/crudFormExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/crudFormExample.png -------------------------------------------------------------------------------- /.readme/images/crudListExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/crudListExample.png -------------------------------------------------------------------------------- /.readme/images/exampleGraphOfFilesToCopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/exampleGraphOfFilesToCopy.png -------------------------------------------------------------------------------- /.readme/images/generateCodePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/generateCodePage.png -------------------------------------------------------------------------------- /.readme/images/generateDownloadCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/generateDownloadCode.png -------------------------------------------------------------------------------- /.readme/images/loginScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/loginScreen.png -------------------------------------------------------------------------------- /.readme/images/mainDashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/mainDashboard.png -------------------------------------------------------------------------------- /.readme/images/newCrudFieldButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/newCrudFieldButton.png -------------------------------------------------------------------------------- /.readme/images/newCrudFieldModal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/newCrudFieldModal.png -------------------------------------------------------------------------------- /.readme/images/panelDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/panelDropdown.png -------------------------------------------------------------------------------- /.readme/images/relatedCrudField.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/.readme/images/relatedCrudField.png -------------------------------------------------------------------------------- /app/Enums/CrudFieldTypes.php: -------------------------------------------------------------------------------- 1 | 'ID', 30 | self::TEXT => 'Text', 31 | self::DATE_TIME => 'Date Time', 32 | self::BELONGS_TO_MANY => 'Belongs To Many', 33 | self::BELONGS_TO => 'Belongs To', 34 | self::PASSWORD => 'Password', 35 | self::IMAGE => 'Image', 36 | self::TEXTAREA => 'Textarea', 37 | self::CHECKBOX => 'Checkbox', 38 | self::FLOAT => 'Float', 39 | self::EMAIL => 'Email', 40 | self::DATE => 'Date', 41 | self::MONEY => 'Money', 42 | self::FILE => 'File', 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Enums/CrudFieldValidation.php: -------------------------------------------------------------------------------- 1 | 'Required', 16 | self::NULLABLE => 'Optional', 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Enums/CrudTypes.php: -------------------------------------------------------------------------------- 1 | 'CRUD', 17 | self::PARENT => 'Parent', 18 | self::NON_CRUD => 'Non-CRUD', 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Enums/PanelTypes.php: -------------------------------------------------------------------------------- 1 | schema([ 23 | TextInput::make('name'), 24 | // ... 25 | ]); 26 | } 27 | 28 | protected function handleRegistration(array $data): Panel 29 | { 30 | $panel = Panel::create([ 31 | ...$data, 32 | 'user_id' => auth()->id(), 33 | 'type' => PanelTypes::FILAMENT3, 34 | ]); 35 | 36 | dispatch(new PanelCreatedJob($panel)); 37 | 38 | return $panel; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Filament/Pages/PanelDeploymentPage.php: -------------------------------------------------------------------------------- 1 | deployment = $panel->panelDeployments()->latest()->first(); 34 | } 35 | 36 | protected function getViewData(): array 37 | { 38 | /** @var Panel $panel */ 39 | $panel = Filament::getTenant(); 40 | return [ 41 | 'crudsCount' => $panel->cruds()->count(), 42 | ]; 43 | } 44 | 45 | public function startGeneration(): void 46 | { 47 | // TODO: Clean this mess up and make the UI better. 48 | // UI REALLY SUCKS ATM 49 | 50 | /** @var Panel $panel */ 51 | $panel = Filament::getTenant(); 52 | 53 | $this->deployment = $panel->panelDeployments()->create([ 54 | 'status' => 'pending', 55 | 'deployment_id' => Uuid::uuid4(), 56 | ]); 57 | 58 | $this->deployment->addNewMessage('Generation started at ' . now()->toDateTimeString() . PHP_EOL); 59 | 60 | Bus::batch([ 61 | new GeneratePanelCodeJob($panel->id, $this->deployment->id), 62 | ]) 63 | ->name($this->deployment->deployment_id) 64 | ->catch(function () { 65 | $this->deployment?->addNewMessage('Generation has failed...' . PHP_EOL); 66 | 67 | $this->deployment?->update([ 68 | 'status' => 'failed', 69 | ]); 70 | }) 71 | ->then(function () use ($panel) { 72 | $service = new PanelService($panel); 73 | $filePath = $service->zipFiles(); 74 | 75 | $this->deployment?->update([ 76 | 'status' => 'success', 77 | 'file_path' => $filePath, 78 | ]); 79 | 80 | $this->deployment?->addNewMessage('Generation completed at ' . now()->toDateTimeString() . PHP_EOL); 81 | }) 82 | ->dispatch(); 83 | 84 | $this->deployment = $this->deployment->fresh(); 85 | 86 | $this->dispatch('$refresh'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/Filament/Pages/PanelModuleManagement.php: -------------------------------------------------------------------------------- 1 | Filament::getTenant()?->load(['modules']), 23 | 'modules' => Module::query() 24 | ->where('slug', '!=', 'base-module') 25 | ->pluck('title', 'slug'), 26 | ]; 27 | } 28 | 29 | public function install(string $moduleSlug): void 30 | { 31 | /** @var Panel $panel */ 32 | $panel = Filament::getTenant(); 33 | $module = Module::where('slug', $moduleSlug)->firstOrFail(); 34 | 35 | if (! $panel->modules->contains($module->id)) { 36 | $panel->modules()->attach($module->id); 37 | 38 | ModuleService::getModuleClass($panel, $module->slug) 39 | ->install($panel); 40 | } 41 | 42 | $this->dispatch('$refresh'); 43 | } 44 | 45 | public function uninstall(string $moduleSlug): void 46 | { 47 | /** @var Panel $panel */ 48 | $panel = Filament::getTenant(); 49 | $module = Module::where('slug', $moduleSlug)->firstOrFail(); 50 | 51 | if ($panel->modules->contains($module->id)) { 52 | $panel->modules()->detach($module->id); 53 | 54 | ModuleService::getModuleClass($panel, $module->slug) 55 | ->uninstall($panel); 56 | } 57 | 58 | $this->dispatch('$refresh'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Filament/Resources/CrudResource/Pages/EditCrud.php: -------------------------------------------------------------------------------- 1 | user()->getTenants(filament()->getCurrentPanel())->first()) { 15 | // @phpstan-ignore-next-line 16 | return redirect()->to(CrudResource::getUrl(tenant: $tenant)); 17 | } 18 | 19 | return parent::toResponse($request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Jobs/Generator/GeneratePanelCodeJob.php: -------------------------------------------------------------------------------- 1 | panel = Panel::find($panelID); 28 | $this->deployment = PanelDeployment::find($deploymentID); 29 | } 30 | 31 | public function handle(): void 32 | { 33 | if (! $this->panel || ! $this->deployment) { 34 | return; 35 | } 36 | 37 | $this->deployment->addNewMessage('Generation Processing...'.PHP_EOL); 38 | 39 | $this->panel->load([ 40 | 'cruds', 41 | ]); 42 | 43 | $service = new PanelService($this->panel); 44 | 45 | foreach ($this->panel->panelFiles()->where('path', 'like', '%database/migrations%')->get() as $file) { 46 | $service->deleteFile($file); 47 | } 48 | 49 | $panelService = new PanelService($this->panel); 50 | 51 | $migrations = [ 52 | // In this array, you can add the migrations you want to create for the panel. 53 | // These migrations will be added to ALL panels 54 | // '0000_00_00_000000_create_cache_table' => 'render(), 55 | // '0000_00_00_000000_create_sessions_table' => 'render(), 56 | // '0000_00_00_000000_create_jobs_table' => 'render(), 57 | ]; 58 | 59 | foreach ($migrations as $migrationName => $content) { 60 | $migrationPath = 'database/migrations/'.$migrationName.'.php'; 61 | $panelService->writeFile($migrationPath, $content); 62 | $this->panel->panelFiles()->updateOrCreate([ 63 | 'path' => $migrationPath, 64 | 'panel_id' => $this->panel->id, 65 | ], [ 66 | 'path' => $migrationPath, 67 | 'panel_id' => $this->panel->id, 68 | ]); 69 | } 70 | 71 | foreach ($this->panel->cruds as $crud) { 72 | $this->deployment->addNewMessage("Preparing $crud->title for generation...".PHP_EOL); 73 | 74 | switch ($this->panel->type) { 75 | case PanelTypes::FILAMENT3: 76 | $this->batch() 77 | ?->add([ 78 | new \Generators\Filament3\Jobs\CreateCrudJob($this->panel, $crud, $this->deployment), 79 | ]); 80 | break; 81 | default: 82 | $this->deployment->addNewMessage('Panel type not supported for generation.'.PHP_EOL); 83 | break; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Jobs/Generator/PanelCreatedJob.php: -------------------------------------------------------------------------------- 1 | 'boolean', 50 | 'system' => 'boolean', 51 | 'module_crud' => 'boolean', 52 | 'type' => CrudTypes::class, 53 | 'icon' => HeroIcons::class, 54 | ]; 55 | } 56 | 57 | protected static function booted(): void 58 | { 59 | self::creating(static function (Crud $crud) { 60 | if (! $crud->title) { 61 | $crud->title = str($crud->visual_title) 62 | ->camel() 63 | ->singular() 64 | ->ucfirst() 65 | ->toString(); 66 | } 67 | }); 68 | } 69 | 70 | protected function icon(): Attribute 71 | { 72 | return Attribute::make( 73 | set: fn (HeroIcons|string|null $value) => ! $value ? HeroIcons::O_RECTANGLE_STACK : $value, 74 | ); 75 | } 76 | 77 | public function scopeParent(Builder $query): Builder 78 | { 79 | return $query->where('type', CrudTypes::PARENT); 80 | } 81 | 82 | public function panel(): BelongsTo 83 | { 84 | return $this->belongsTo(Panel::class); 85 | } 86 | 87 | public function parent(): BelongsTo 88 | { 89 | return $this->belongsTo(Crud::class); 90 | } 91 | 92 | public function fields(): HasMany 93 | { 94 | return $this->hasMany(CrudField::class); 95 | } 96 | 97 | public function panelFiles(): HasMany 98 | { 99 | return $this->hasMany(PanelFile::class); 100 | } 101 | 102 | protected function modelClassName(): Attribute 103 | { 104 | return Attribute::make( 105 | get: fn () => str($this->title)->singular()->studly()->toString(), 106 | ); 107 | } 108 | 109 | protected function modelSnakePluralClassName(): Attribute 110 | { 111 | return Attribute::make( 112 | get: fn () => str($this->title)->plural()->snake()->toString(), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/Models/CrudField.php: -------------------------------------------------------------------------------- 1 | CrudFieldTypes::class, 55 | 'in_list' => 'boolean', 56 | 'in_show' => 'boolean', 57 | 'in_create' => 'boolean', 58 | 'in_edit' => 'boolean', 59 | 'nullable' => 'boolean', 60 | 'system' => 'boolean', 61 | 'enabled' => 'boolean', 62 | ]; 63 | } 64 | 65 | protected static function booted(): void 66 | { 67 | self::creating(function (CrudField $field) { 68 | if ($field->type === CrudFieldTypes::BELONGS_TO) { 69 | $field->key = str($field->label) 70 | ->lower() 71 | ->snake() 72 | ->toString().'_id'; 73 | } else { 74 | $field->key = str($field->label) 75 | ->lower() 76 | ->snake() 77 | ->toString(); 78 | } 79 | }); 80 | } 81 | 82 | public function crud(): BelongsTo 83 | { 84 | return $this->belongsTo(Crud::class); 85 | } 86 | 87 | public function panel(): BelongsTo 88 | { 89 | return $this->belongsTo(Panel::class); 90 | } 91 | 92 | public function crudFieldOptions(): HasOne 93 | { 94 | return $this->hasOne(CrudFieldOptions::class); 95 | } 96 | 97 | public function panelFiles(): HasMany 98 | { 99 | return $this->hasMany(PanelFile::class); 100 | } 101 | 102 | protected function formKeyName(): Attribute 103 | { 104 | return Attribute::make( 105 | get: function (): string { 106 | if (! str($this->key)->endsWith('_id') && $this->type === CrudFieldTypes::BELONGS_TO) { 107 | return str($this->key.'_id')->lower()->snake(); 108 | } 109 | 110 | return str($this->key)->lower()->snake(); 111 | }, 112 | ); 113 | } 114 | 115 | protected function tableKeyName(): Attribute 116 | { 117 | return Attribute::make( 118 | get: function (): string { 119 | $crudFieldOptions = $this->crudFieldOptions; 120 | 121 | if (! $crudFieldOptions) { 122 | return str($this->key)->lower()->snake(); 123 | } 124 | 125 | return sprintf( 126 | '%s.%s', 127 | $crudFieldOptions->relationship, 128 | $crudFieldOptions->relatedCrudField->key 129 | ); 130 | }, 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/Models/CrudFieldOptions.php: -------------------------------------------------------------------------------- 1 | crudField) { 29 | if ($fieldOptions->crudField->type === CrudFieldTypes::BELONGS_TO_MANY) { 30 | $fieldOptions->relationship = str($fieldOptions->crud?->title)->snake()->plural()->toString(); 31 | } else { 32 | $fieldOptions->relationship = str($fieldOptions->crud?->title)->snake()->singular()->toString(); 33 | } 34 | } 35 | }); 36 | } 37 | 38 | public function crudField(): BelongsTo 39 | { 40 | return $this->belongsTo(CrudField::class); 41 | } 42 | 43 | public function relatedCrudField(): BelongsTo 44 | { 45 | return $this->belongsTo(CrudField::class, 'related_crud_field_id'); 46 | } 47 | 48 | public function crud(): BelongsTo 49 | { 50 | return $this->belongsTo(Crud::class); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Models/Module.php: -------------------------------------------------------------------------------- 1 | PanelTypes::class, 33 | ]; 34 | } 35 | 36 | public function scopeForUser(Builder $query, User $user): Builder 37 | { 38 | return $query->where('user_id', $user->id); 39 | } 40 | 41 | public function user(): BelongsTo 42 | { 43 | return $this->belongsTo(User::class); 44 | } 45 | 46 | public function cruds(): HasMany 47 | { 48 | return $this->hasMany(Crud::class); 49 | } 50 | 51 | public function panelFiles(): HasMany 52 | { 53 | return $this->hasMany(PanelFile::class); 54 | } 55 | 56 | public function modules(): BelongsToMany 57 | { 58 | return $this->belongsToMany(Module::class); 59 | } 60 | 61 | public function panelDeployments(): HasMany 62 | { 63 | return $this->hasMany(PanelDeployment::class); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Models/PanelDeployment.php: -------------------------------------------------------------------------------- 1 | belongsTo(Panel::class); 24 | } 25 | 26 | public function addNewMessage(string $message): void 27 | { 28 | $log = $this->fresh(); 29 | 30 | if (! $log) { 31 | return; 32 | } 33 | 34 | $log->deployment_log .= $message; 35 | $log->save(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Models/PanelFile.php: -------------------------------------------------------------------------------- 1 | belongsTo(Panel::class); 24 | } 25 | 26 | public function crud(): BelongsTo 27 | { 28 | return $this->belongsTo(Crud::class); 29 | } 30 | 31 | public function crudField(): BelongsTo 32 | { 33 | return $this->belongsTo(CrudField::class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | protected $fillable = [ 26 | 'name', 27 | 'email', 28 | 'password', 29 | ]; 30 | 31 | /** 32 | * The attributes that should be hidden for serialization. 33 | * 34 | * @var array 35 | */ 36 | protected $hidden = [ 37 | 'password', 38 | 'remember_token', 39 | ]; 40 | 41 | /** 42 | * Get the attributes that should be cast. 43 | * 44 | * @return array 45 | */ 46 | protected function casts(): array 47 | { 48 | return [ 49 | 'email_verified_at' => 'datetime', 50 | 'password' => 'hashed', 51 | ]; 52 | } 53 | 54 | public function panels(): HasMany 55 | { 56 | return $this->hasMany(Panel::class); 57 | } 58 | 59 | public function canAccessPanel(\Filament\Panel $panel): bool 60 | { 61 | return true; // TODO: Add logic to check if user can access panel builder 62 | } 63 | 64 | public function canAccessTenant(Model $tenant): bool 65 | { 66 | return $this->panels()->whereKey($tenant)->exists(); 67 | } 68 | 69 | public function getTenants(\Filament\Panel $panel): array|Collection 70 | { 71 | return $this->panels; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton( 15 | \Filament\Http\Responses\Auth\Contracts\LoginResponse::class, 16 | \App\Http\Responses\LoginResponse::class 17 | ); 18 | } 19 | 20 | /** 21 | * Bootstrap any application services. 22 | */ 23 | public function boot(): void 24 | { 25 | // 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Providers/Filament/BuilderPanelProvider.php: -------------------------------------------------------------------------------- 1 | tenant(\App\Models\Panel::class) 27 | ->tenantRegistration(CreatePanelPage::class) 28 | ->default() 29 | ->id('builder') 30 | ->path('builder') 31 | ->login() 32 | ->colors([ 33 | 'primary' => Color::Amber, 34 | ]) 35 | ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') 36 | ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') 37 | ->pages([ 38 | // 39 | ]) 40 | ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') 41 | ->widgets([ 42 | Widgets\AccountWidget::class, 43 | Widgets\FilamentInfoWidget::class, 44 | ]) 45 | ->middleware([ 46 | EncryptCookies::class, 47 | AddQueuedCookiesToResponse::class, 48 | StartSession::class, 49 | AuthenticateSession::class, 50 | ShareErrorsFromSession::class, 51 | VerifyCsrfToken::class, 52 | SubstituteBindings::class, 53 | DisableBladeIconComponents::class, 54 | DispatchServingFilamentEvent::class, 55 | ]) 56 | ->authMiddleware([ 57 | Authenticate::class, 58 | ]) 59 | ->renderHook( 60 | 'panels::body.end', 61 | fn () => view('filament.footer'), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Providers/HorizonServiceProvider.php: -------------------------------------------------------------------------------- 1 | email, [ 33 | // 34 | ]); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Providers/TelescopeServiceProvider.php: -------------------------------------------------------------------------------- 1 | hideSensitiveRequestDetails(); 21 | 22 | $isLocal = $this->app->environment('local'); 23 | 24 | Telescope::filter(function (IncomingEntry $entry) use ($isLocal) { 25 | return $isLocal || 26 | $entry->isReportableException() || 27 | $entry->isFailedRequest() || 28 | $entry->isFailedJob() || 29 | $entry->isScheduledTask() || 30 | $entry->hasMonitoredTag(); 31 | }); 32 | } 33 | 34 | /** 35 | * Prevent sensitive request details from being logged by Telescope. 36 | */ 37 | protected function hideSensitiveRequestDetails(): void 38 | { 39 | if ($this->app->environment('local')) { 40 | return; 41 | } 42 | 43 | Telescope::hideRequestParameters(['_token']); 44 | 45 | Telescope::hideRequestHeaders([ 46 | 'cookie', 47 | 'x-csrf-token', 48 | 'x-xsrf-token', 49 | ]); 50 | } 51 | 52 | /** 53 | * Register the Telescope gate. 54 | * 55 | * This gate determines who can access Telescope in non-local environments. 56 | */ 57 | protected function gate(): void 58 | { 59 | Gate::define('viewTelescope', function (User $user) { 60 | return in_array($user->email, [ 61 | // 62 | ]); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Services/ModuleService.php: -------------------------------------------------------------------------------- 1 | 'Panel Base', 19 | 'asset-management' => 'Asset Management', 20 | 'client-management' => 'Client Management', 21 | ]; 22 | } 23 | 24 | public static function getModuleClass(Panel $panel, string $moduleSlug): ModuleBase 25 | { 26 | // TODO: Think about a way to pass panel to the module directly here, to avoid passing it into the install 27 | return match ($panel->type) { 28 | PanelTypes::FILAMENT3 => ModuleManager::getModule($moduleSlug) 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Services/PanelService.php: -------------------------------------------------------------------------------- 1 | panel->id.' - '.$this->panel->name)->slug() 23 | ) 24 | ); 25 | } 26 | 27 | public function writeFile(string $path, string $contents): void 28 | { 29 | $path = $this->getStoragePath().DIRECTORY_SEPARATOR.$path; 30 | 31 | $filesystem = app(Filesystem::class); 32 | 33 | $filesystem->ensureDirectoryExists( 34 | pathinfo($path, PATHINFO_DIRNAME), 35 | ); 36 | 37 | $filesystem->put($path, $contents); 38 | } 39 | 40 | public function deleteFile(PanelFile $file): void 41 | { 42 | $path = $this->getStoragePath().DIRECTORY_SEPARATOR.$file->path; 43 | 44 | $filesystem = app(Filesystem::class); 45 | 46 | // TODO: This should also delete empty directories 47 | 48 | $filesystem->delete($path); 49 | } 50 | 51 | public function zipFiles(): string 52 | { 53 | $zipPath = $this->getStoragePath().DIRECTORY_SEPARATOR.'panel.zip'; 54 | 55 | $filesystem = app(Filesystem::class); 56 | 57 | $filesystem->ensureDirectoryExists( 58 | pathinfo($zipPath, PATHINFO_DIRNAME), 59 | ); 60 | 61 | $zip = new ZipArchive(); 62 | 63 | $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); 64 | 65 | $files = $this->panel->panelFiles; 66 | 67 | foreach ($files as $file) { 68 | if (! $filesystem->exists($this->getStoragePath().DIRECTORY_SEPARATOR.$file->path)) { 69 | continue; 70 | } 71 | 72 | $zip->addFile( 73 | $this->getStoragePath().DIRECTORY_SEPARATOR.$file->path, 74 | $file->path, 75 | ); 76 | } 77 | 78 | $zip->close(); 79 | 80 | $filesystem->move($zipPath, storage_path(sprintf('app/public/%s.zip', 81 | str($this->panel->id.' - '.$this->panel->name)->slug() 82 | ))); 83 | 84 | return Storage::url(sprintf('%s.zip', 85 | str($this->panel->id.' - '.$this->panel->name)->slug() 86 | )); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 9 | web: __DIR__.'/../routes/web.php', 10 | commands: __DIR__.'/../routes/console.php', 11 | health: '/up', 12 | ) 13 | ->withMiddleware(function (Middleware $middleware) { 14 | // 15 | }) 16 | ->withExceptions(function (Exceptions $exceptions) { 17 | // 18 | })->create(); 19 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => env('AUTH_GUARD', 'web'), 18 | 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | which utilizes session storage plus the Eloquent user provider. 29 | | 30 | | All authentication guards have a user provider, which defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | system used by the application. Typically, Eloquent is utilized. 33 | | 34 | | Supported: "session" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | ], 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | User Providers 48 | |-------------------------------------------------------------------------- 49 | | 50 | | All authentication guards have a user provider, which defines how the 51 | | users are actually retrieved out of your database or other storage 52 | | system used by the application. Typically, Eloquent is utilized. 53 | | 54 | | If you have multiple user tables or models you may configure multiple 55 | | providers to represent the model / table. These providers may then 56 | | be assigned to any extra authentication guards you have defined. 57 | | 58 | | Supported: "database", "eloquent" 59 | | 60 | */ 61 | 62 | 'providers' => [ 63 | 'users' => [ 64 | 'driver' => 'eloquent', 65 | 'model' => env('AUTH_MODEL', App\Models\User::class), 66 | ], 67 | 68 | // 'users' => [ 69 | // 'driver' => 'database', 70 | // 'table' => 'users', 71 | // ], 72 | ], 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Resetting Passwords 77 | |-------------------------------------------------------------------------- 78 | | 79 | | These configuration options specify the behavior of Laravel's password 80 | | reset functionality, including the table utilized for token storage 81 | | and the user provider that is invoked to actually retrieve users. 82 | | 83 | | The expiry time is the number of minutes that each reset token will be 84 | | considered valid. This security feature keeps tokens short-lived so 85 | | they have less time to be guessed. You may change this as needed. 86 | | 87 | | The throttle setting is the number of seconds a user must wait before 88 | | generating more password reset tokens. This prevents the user from 89 | | quickly generating a very large amount of password reset tokens. 90 | | 91 | */ 92 | 93 | 'passwords' => [ 94 | 'users' => [ 95 | 'provider' => 'users', 96 | 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 97 | 'expire' => 60, 98 | 'throttle' => 60, 99 | ], 100 | ], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Password Confirmation Timeout 105 | |-------------------------------------------------------------------------- 106 | | 107 | | Here you may define the amount of seconds before a password confirmation 108 | | window expires and users are asked to re-enter their password via the 109 | | confirmation screen. By default, the timeout lasts for three hours. 110 | | 111 | */ 112 | 113 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_STORE', 'database'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "apc", "array", "database", "file", "memcached", 30 | | "redis", "dynamodb", "octane", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'array' => [ 37 | 'driver' => 'array', 38 | 'serialize' => false, 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => env('DB_CACHE_TABLE', 'cache'), 44 | 'connection' => env('DB_CACHE_CONNECTION'), 45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), 46 | ], 47 | 48 | 'file' => [ 49 | 'driver' => 'file', 50 | 'path' => storage_path('framework/cache/data'), 51 | 'lock_path' => storage_path('framework/cache/data'), 52 | ], 53 | 54 | 'memcached' => [ 55 | 'driver' => 'memcached', 56 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 57 | 'sasl' => [ 58 | env('MEMCACHED_USERNAME'), 59 | env('MEMCACHED_PASSWORD'), 60 | ], 61 | 'options' => [ 62 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 63 | ], 64 | 'servers' => [ 65 | [ 66 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 67 | 'port' => env('MEMCACHED_PORT', 11211), 68 | 'weight' => 100, 69 | ], 70 | ], 71 | ], 72 | 73 | 'redis' => [ 74 | 'driver' => 'redis', 75 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 76 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), 77 | ], 78 | 79 | 'dynamodb' => [ 80 | 'driver' => 'dynamodb', 81 | 'key' => env('AWS_ACCESS_KEY_ID'), 82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 85 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 86 | ], 87 | 88 | 'octane' => [ 89 | 'driver' => 'octane', 90 | ], 91 | 92 | ], 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Cache Key Prefix 97 | |-------------------------------------------------------------------------- 98 | | 99 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache 100 | | stores, there might be other applications using the same cache. For 101 | | that reason, you may prefix every cache key to avoid collisions. 102 | | 103 | */ 104 | 105 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /config/filament.php: -------------------------------------------------------------------------------- 1 | [ 18 | 19 | // 'echo' => [ 20 | // 'broadcaster' => 'reverb', 21 | // 'key' => env('VITE_REVERB_APP_KEY'), 22 | // 'cluster' => env('VITE_REVERB_APP_CLUSTER'), 23 | // 'wsHost' => env('VITE_REVERB_HOST'), 24 | // 'wsPort' => env('VITE_REVERB_PORT'), 25 | // 'wssPort' => env('VITE_REVERB_PORT'), 26 | // 'authEndpoint' => '/broadcasting/auth', 27 | // 'disableStats' => true, 28 | // 'encrypted' => true, 29 | // 'forceTLS' => false, // This is needed 30 | // ], 31 | 32 | ], 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Default Filesystem Disk 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This is the storage disk Filament will use to store files. You may use 40 | | any of the disks defined in the `config/filesystems.php`. 41 | | 42 | */ 43 | 44 | 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Assets Path 49 | |-------------------------------------------------------------------------- 50 | | 51 | | This is the directory where Filament's assets will be published to. It 52 | | is relative to the `public` directory of your Laravel application. 53 | | 54 | | After changing the path, you should run `php artisan filament:assets`. 55 | | 56 | */ 57 | 58 | 'assets_path' => null, 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Cache Path 63 | |-------------------------------------------------------------------------- 64 | | 65 | | This is the directory that Filament will use to store cache files that 66 | | are used to optimize the registration of components. 67 | | 68 | | After changing the path, you should run `php artisan filament:cache-components`. 69 | | 70 | */ 71 | 72 | 'cache_path' => base_path('bootstrap/cache/filament'), 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Livewire Loading Delay 77 | |-------------------------------------------------------------------------- 78 | | 79 | | This sets the delay before loading indicators appear. 80 | | 81 | | Setting this to 'none' makes indicators appear immediately, which can be 82 | | desirable for high-latency connections. Setting it to 'default' applies 83 | | Livewire's standard 200ms delay. 84 | | 85 | */ 86 | 87 | 'livewire_loading_delay' => 'default', 88 | 89 | ]; 90 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL').'/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'log'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Mailer Configurations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may configure all of the mailers used by your application plus 25 | | their respective settings. Several examples have been configured for 26 | | you and you are free to add your own as your application requires. 27 | | 28 | | Laravel supports a variety of mail "transport" drivers that can be used 29 | | when delivering an email. You may specify which one you're using for 30 | | your mailers below. You may also add additional mailers if needed. 31 | | 32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 33 | | "postmark", "log", "array", "failover", "roundrobin" 34 | | 35 | */ 36 | 37 | 'mailers' => [ 38 | 39 | 'smtp' => [ 40 | 'transport' => 'smtp', 41 | 'url' => env('MAIL_URL'), 42 | 'host' => env('MAIL_HOST', '127.0.0.1'), 43 | 'port' => env('MAIL_PORT', 2525), 44 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 45 | 'username' => env('MAIL_USERNAME'), 46 | 'password' => env('MAIL_PASSWORD'), 47 | 'timeout' => null, 48 | 'local_domain' => env('MAIL_EHLO_DOMAIN'), 49 | ], 50 | 51 | 'ses' => [ 52 | 'transport' => 'ses', 53 | ], 54 | 55 | 'postmark' => [ 56 | 'transport' => 'postmark', 57 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), 58 | // 'client' => [ 59 | // 'timeout' => 5, 60 | // ], 61 | ], 62 | 63 | 'sendmail' => [ 64 | 'transport' => 'sendmail', 65 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 66 | ], 67 | 68 | 'log' => [ 69 | 'transport' => 'log', 70 | 'channel' => env('MAIL_LOG_CHANNEL'), 71 | ], 72 | 73 | 'array' => [ 74 | 'transport' => 'array', 75 | ], 76 | 77 | 'failover' => [ 78 | 'transport' => 'failover', 79 | 'mailers' => [ 80 | 'smtp', 81 | 'log', 82 | ], 83 | ], 84 | 85 | ], 86 | 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Global "From" Address 90 | |-------------------------------------------------------------------------- 91 | | 92 | | You may wish for all emails sent by your application to be sent from 93 | | the same address. Here you may specify a name and address that is 94 | | used globally for all emails that are sent by your application. 95 | | 96 | */ 97 | 98 | 'from' => [ 99 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 100 | 'name' => env('MAIL_FROM_NAME', 'Example'), 101 | ], 102 | 103 | ]; 104 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'database'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection options for every queue backend 24 | | used by your application. An example configuration is provided for 25 | | each backend supported by Laravel. You're also free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'connection' => env('DB_QUEUE_CONNECTION'), 40 | 'table' => env('DB_QUEUE_TABLE', 'jobs'), 41 | 'queue' => env('DB_QUEUE', 'default'), 42 | 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 43 | 'after_commit' => false, 44 | ], 45 | 46 | 'beanstalkd' => [ 47 | 'driver' => 'beanstalkd', 48 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 49 | 'queue' => env('BEANSTALKD_QUEUE', 'default'), 50 | 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 51 | 'block_for' => 0, 52 | 'after_commit' => false, 53 | ], 54 | 55 | 'sqs' => [ 56 | 'driver' => 'sqs', 57 | 'key' => env('AWS_ACCESS_KEY_ID'), 58 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 59 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 60 | 'queue' => env('SQS_QUEUE', 'default'), 61 | 'suffix' => env('SQS_SUFFIX'), 62 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 63 | 'after_commit' => false, 64 | ], 65 | 66 | 'redis' => [ 67 | 'driver' => 'redis', 68 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 69 | 'queue' => env('REDIS_QUEUE', 'default'), 70 | 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 71 | 'block_for' => null, 72 | 'after_commit' => false, 73 | ], 74 | 75 | ], 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Job Batching 80 | |-------------------------------------------------------------------------- 81 | | 82 | | The following options configure the database and table that store job 83 | | batching information. These options can be updated to any database 84 | | connection and table which has been defined by your application. 85 | | 86 | */ 87 | 88 | 'batching' => [ 89 | 'database' => env('DB_CONNECTION', 'sqlite'), 90 | 'table' => 'job_batches', 91 | ], 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Failed Queue Jobs 96 | |-------------------------------------------------------------------------- 97 | | 98 | | These options configure the behavior of failed queue job logging so you 99 | | can control how and where failed jobs are stored. Laravel ships with 100 | | support for storing failed jobs in a simple file or in a database. 101 | | 102 | | Supported drivers: "database-uuids", "dynamodb", "file", "null" 103 | | 104 | */ 105 | 106 | 'failed' => [ 107 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 108 | 'database' => env('DB_CONNECTION', 'sqlite'), 109 | 'table' => 'failed_jobs', 110 | ], 111 | 112 | ]; 113 | -------------------------------------------------------------------------------- /config/reverb.php: -------------------------------------------------------------------------------- 1 | env('REVERB_SERVER', 'reverb'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Reverb Servers 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define details for each of the supported Reverb servers. 24 | | Each server has its own configuration options that are defined in 25 | | the array below. You should ensure all the options are present. 26 | | 27 | */ 28 | 29 | 'servers' => [ 30 | 31 | 'reverb' => [ 32 | 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'), 33 | 'port' => env('REVERB_SERVER_PORT', 8080), 34 | 'hostname' => env('REVERB_HOST'), 35 | 'options' => [ 36 | 'tls' => [], 37 | ], 38 | 'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000), 39 | 'scaling' => [ 40 | 'enabled' => env('REVERB_SCALING_ENABLED', false), 41 | 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'), 42 | ], 43 | 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15), 44 | ], 45 | 46 | ], 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Reverb Applications 51 | |-------------------------------------------------------------------------- 52 | | 53 | | Here you may define how Reverb applications are managed. If you choose 54 | | to use the "config" provider, you may define an array of apps which 55 | | your server will support, including their connection credentials. 56 | | 57 | */ 58 | 59 | 'apps' => [ 60 | 61 | 'provider' => 'config', 62 | 63 | 'apps' => [ 64 | [ 65 | 'key' => env('REVERB_APP_KEY'), 66 | 'secret' => env('REVERB_APP_SECRET'), 67 | 'app_id' => env('REVERB_APP_ID'), 68 | 'options' => [ 69 | 'host' => env('REVERB_HOST'), 70 | 'port' => env('REVERB_PORT', 443), 71 | 'scheme' => env('REVERB_SCHEME', 'https'), 72 | 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', 73 | ], 74 | 'allowed_origins' => ['*'], 75 | 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60), 76 | 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000), 77 | ], 78 | ], 79 | 80 | ], 81 | 82 | ]; 83 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'token' => env('POSTMARK_TOKEN'), 19 | ], 20 | 21 | 'ses' => [ 22 | 'key' => env('AWS_ACCESS_KEY_ID'), 23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 25 | ], 26 | 27 | 'slack' => [ 28 | 'notifications' => [ 29 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 30 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 31 | ], 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UserFactory extends Factory 13 | { 14 | /** 15 | * The current password being used by the factory. 16 | */ 17 | protected static ?string $password; 18 | 19 | /** 20 | * Define the model's default state. 21 | * 22 | * @return array 23 | */ 24 | public function definition(): array 25 | { 26 | return [ 27 | 'name' => fake()->name(), 28 | 'email' => fake()->unique()->safeEmail(), 29 | 'email_verified_at' => now(), 30 | 'password' => static::$password ??= Hash::make('password'), 31 | 'remember_token' => Str::random(10), 32 | ]; 33 | } 34 | 35 | /** 36 | * Indicate that the model's email address should be unverified. 37 | */ 38 | public function unverified(): static 39 | { 40 | return $this->state(fn (array $attributes) => [ 41 | 'email_verified_at' => null, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::create('password_reset_tokens', function (Blueprint $table) { 25 | $table->string('email')->primary(); 26 | $table->string('token'); 27 | $table->timestamp('created_at')->nullable(); 28 | }); 29 | 30 | Schema::create('sessions', function (Blueprint $table) { 31 | $table->string('id')->primary(); 32 | $table->foreignId('user_id')->nullable()->index(); 33 | $table->string('ip_address', 45)->nullable(); 34 | $table->text('user_agent')->nullable(); 35 | $table->longText('payload'); 36 | $table->integer('last_activity')->index(); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | */ 43 | public function down(): void 44 | { 45 | Schema::dropIfExists('users'); 46 | Schema::dropIfExists('password_reset_tokens'); 47 | Schema::dropIfExists('sessions'); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('job_batches', function (Blueprint $table) { 25 | $table->string('id')->primary(); 26 | $table->string('name'); 27 | $table->integer('total_jobs'); 28 | $table->integer('pending_jobs'); 29 | $table->integer('failed_jobs'); 30 | $table->longText('failed_job_ids'); 31 | $table->mediumText('options')->nullable(); 32 | $table->integer('cancelled_at')->nullable(); 33 | $table->integer('created_at'); 34 | $table->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('failed_jobs', function (Blueprint $table) { 38 | $table->id(); 39 | $table->string('uuid')->unique(); 40 | $table->text('connection'); 41 | $table->text('queue'); 42 | $table->longText('payload'); 43 | $table->longText('exception'); 44 | $table->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('jobs'); 54 | Schema::dropIfExists('job_batches'); 55 | Schema::dropIfExists('failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /database/migrations/2024_03_28_095305_create_panels_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('user_id')->constrained(); 14 | $table->string('name'); 15 | $table->string('type'); 16 | $table->timestamps(); 17 | $table->softDeletes(); 18 | }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /database/migrations/2024_04_02_141033_create_cruds_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('panel_id')->constrained()->cascadeOnDelete(); 14 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 15 | $table->foreignId('parent_id')->nullable()->constrained('cruds'); 16 | $table->string('type'); 17 | $table->string('title'); 18 | $table->string('visual_title'); 19 | $table->string('icon')->nullable(); 20 | $table->integer('menu_order'); 21 | $table->boolean('is_hidden')->default(false); 22 | $table->boolean('module_crud')->default(false); 23 | $table->string('module_slug')->nullable(); 24 | $table->integer('module_order')->nullable(); 25 | $table->boolean('system')->default(false); 26 | $table->timestamps(); 27 | $table->softDeletes(); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2024_04_02_141038_create_crud_fields_table.php: -------------------------------------------------------------------------------- 1 | id(); 14 | $table->foreignId('panel_id')->constrained()->cascadeOnDelete(); 15 | $table->foreignId('crud_id')->constrained()->cascadeOnDelete(); 16 | $table->string('type'); 17 | $table->string('key'); 18 | $table->string('label'); 19 | $table->string('validation')->default(CrudFieldValidation::NULLABLE->value); 20 | $table->boolean('in_list')->default(true); 21 | $table->boolean('in_show')->default(true); 22 | $table->boolean('in_create')->default(false); 23 | $table->boolean('in_edit')->default(false); 24 | $table->boolean('nullable'); 25 | $table->string('tooltip')->nullable(); 26 | $table->boolean('system')->default(false); 27 | $table->boolean('enabled')->default(true); 28 | $table->integer('order'); 29 | $table->timestamps(); 30 | $table->softDeletes(); 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_04_03_105626_create_crud_field_options_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('crud_field_id')->constrained('crud_fields')->cascadeOnDelete(); 14 | $table->foreignId('crud_id')->constrained('cruds')->cascadeOnDelete(); 15 | $table->foreignId('related_crud_field_id')->constrained('crud_fields')->cascadeOnDelete(); 16 | $table->string('relationship')->nullable(); 17 | $table->timestamps(); 18 | $table->softDeletes(); 19 | }); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /database/migrations/2024_04_04_154557_create_panel_files_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('panel_id')->constrained(); 14 | $table->foreignId('crud_id')->nullable()->constrained(); 15 | $table->foreignId('crud_field_id')->nullable()->constrained(); 16 | 17 | $table->text('path'); 18 | 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /database/migrations/2024_04_04_155618_create_modules_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('title'); 14 | $table->string('slug')->unique(); 15 | $table->timestamps(); 16 | $table->softDeletes(); 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /database/migrations/2024_04_04_162604_create_module_panel_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | $table->foreignId('panel_id')->constrained(); 15 | $table->foreignId('module_id')->constrained(); 16 | 17 | $table->timestamps(); 18 | }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /database/migrations/2024_04_09_153641_create_panel_deployments_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->foreignId('panel_id')->constrained()->cascadeOnDelete(); 14 | $table->string('deployment_id'); 15 | $table->string('status'); 16 | $table->string('file_path')->nullable(); 17 | $table->longText('deployment_log')->nullable(); 18 | $table->timestamps(); 19 | $table->softDeletes(); 20 | }); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /database/migrations/2024_04_18_124339_create_telescope_entries_table.php: -------------------------------------------------------------------------------- 1 | getConnection()); 23 | 24 | $schema->create('telescope_entries', function (Blueprint $table) { 25 | $table->bigIncrements('sequence'); 26 | $table->uuid('uuid'); 27 | $table->uuid('batch_id'); 28 | $table->string('family_hash')->nullable(); 29 | $table->boolean('should_display_on_index')->default(true); 30 | $table->string('type', 20); 31 | $table->longText('content'); 32 | $table->dateTime('created_at')->nullable(); 33 | 34 | $table->unique('uuid'); 35 | $table->index('batch_id'); 36 | $table->index('family_hash'); 37 | $table->index('created_at'); 38 | $table->index(['type', 'should_display_on_index']); 39 | }); 40 | 41 | $schema->create('telescope_entries_tags', function (Blueprint $table) { 42 | $table->uuid('entry_uuid'); 43 | $table->string('tag'); 44 | 45 | $table->primary(['entry_uuid', 'tag']); 46 | $table->index('tag'); 47 | 48 | $table->foreign('entry_uuid') 49 | ->references('uuid') 50 | ->on('telescope_entries') 51 | ->onDelete('cascade'); 52 | }); 53 | 54 | $schema->create('telescope_monitoring', function (Blueprint $table) { 55 | $table->string('tag')->primary(); 56 | }); 57 | } 58 | 59 | /** 60 | * Reverse the migrations. 61 | */ 62 | public function down(): void 63 | { 64 | $schema = Schema::connection($this->getConnection()); 65 | 66 | $schema->dropIfExists('telescope_entries_tags'); 67 | $schema->dropIfExists('telescope_entries'); 68 | $schema->dropIfExists('telescope_monitoring'); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create([ 20 | 'name' => 'Test Admin', 21 | 'email' => 'admin@admin.com', 22 | ]); 23 | 24 | $modulesList = (new ModuleService())->listModules(); 25 | 26 | foreach ($modulesList as $slug => $title) { 27 | 28 | Module::create([ 29 | 'title' => $title, 30 | 'slug' => $slug, 31 | ]); 32 | 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "@tailwindcss/forms": "^0.5.2", 10 | "alpinejs": "^3.4.2", 11 | "autoprefixer": "^10.4.2", 12 | "axios": "^1.6.4", 13 | "laravel-vite-plugin": "^1.0", 14 | "postcss": "^8.4.31", 15 | "tailwindcss": "^3.1.0", 16 | "vite": "^5.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/larastan/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - app/ 8 | - systems/ 9 | 10 | # Level 9 is the highest level 11 | level: 9 12 | 13 | checkGenericClassInNonGenericObjectType: false 14 | 15 | excludePaths: 16 | - ./app/Http/Controllers/Auth/*.php 17 | - ./app/Http/Requests/Auth/*.php 18 | - ./app/Http/Controllers/ProfileController.php 19 | - ./app/Http/Requests/ProfileUpdateRequest.php 20 | - ./systems/generators/*/tests/*.php 21 | 22 | 23 | # ignoreErrors: 24 | # - '#PHPDoc tag @var#' 25 | # 26 | # 27 | # checkMissingIterableValueType: false -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | systems/generators/filament3/src 19 | systems/generators/laravel11/src 20 | 21 | 22 | systems/generators/filament3/src/templates 23 | systems/generators/laravel11/src/templates 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/css/filament/support/support.css: -------------------------------------------------------------------------------- 1 | .fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3} -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function r({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=o=>o===null?0:Array.isArray(o)?o.length:typeof o!="object"?0:Object.keys(o).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{["x-on:blur"]:"createTag()",["x-model"]:"newTag",["x-on:keydown"](t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},["x-on:paste"](){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function t({initialHeight:e}){return{init:function(){this.render()},render:function(){this.$el.scrollHeight>0&&(this.$el.style.height=e+"rem",this.$el.style.height=this.$el.scrollHeight+"px")}}}export{t as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/table.js: -------------------------------------------------------------------------------- 1 | function c(){return{collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,init:function(){this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1})},mountAction:function(e,s=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,s)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let s=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])t.dataset.group===e&&s.push(t.value);return s},getRecordsOnPage:function(){let e=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(s.value);return e},selectRecords:function(e){for(let s of e)this.isRecordSelected(s)||this.selectedRecords.push(s)},deselectRecords:function(e){for(let s of e){let t=this.selectedRecords.indexOf(s);t!==-1&&this.selectedRecords.splice(t,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(s=>this.isRecordSelected(s))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]}}}export{c as default}; 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/vendor/horizon/app.css: -------------------------------------------------------------------------------- 1 | .vjs-tree-brackets{cursor:pointer}.vjs-tree-brackets:hover{color:#1890ff}.vjs-check-controller{position:absolute;left:0}.vjs-check-controller.is-checked .vjs-check-controller-inner{background-color:#1890ff;border-color:#0076e4}.vjs-check-controller.is-checked .vjs-check-controller-inner.is-checkbox:after{transform:rotate(45deg) scaleY(1)}.vjs-check-controller.is-checked .vjs-check-controller-inner.is-radio:after{transform:translate(-50%,-50%) scale(1)}.vjs-check-controller .vjs-check-controller-inner{display:inline-block;position:relative;border:1px solid #bfcbd9;border-radius:2px;vertical-align:middle;box-sizing:border-box;width:16px;height:16px;background-color:#fff;z-index:1;cursor:pointer;transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.vjs-check-controller .vjs-check-controller-inner:after{box-sizing:content-box;content:"";border:2px solid #fff;border-left:0;border-top:0;height:8px;left:4px;position:absolute;top:1px;transform:rotate(45deg) scaleY(0);width:4px;transition:transform .15s cubic-bezier(.71,-.46,.88,.6) .05s;transform-origin:center}.vjs-check-controller .vjs-check-controller-inner.is-radio{border-radius:100%}.vjs-check-controller .vjs-check-controller-inner.is-radio:after{border-radius:100%;height:4px;background-color:#fff;left:50%;top:50%}.vjs-check-controller .vjs-check-controller-original{opacity:0;outline:none;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.vjs-carets{position:absolute;right:0;cursor:pointer}.vjs-carets svg{transition:transform .3s}.vjs-carets:hover{color:#1890ff}.vjs-carets-close{transform:rotate(-90deg)}.vjs-tree-node{display:flex;position:relative;line-height:20px}.vjs-tree-node.has-carets{padding-left:15px}.vjs-tree-node.has-carets.has-selector,.vjs-tree-node.has-selector{padding-left:30px}.vjs-tree-node.is-highlight,.vjs-tree-node:hover{background-color:#e6f7ff}.vjs-tree-node .vjs-indent{display:flex;position:relative}.vjs-tree-node .vjs-indent-unit{width:1em}.vjs-tree-node .vjs-indent-unit.has-line{border-left:1px dashed #bfcbd9}.vjs-node-index{position:absolute;right:100%;margin-right:4px;-webkit-user-select:none;user-select:none}.vjs-colon{white-space:pre}.vjs-comment{color:#bfcbd9}.vjs-value{word-break:break-word}.vjs-value-null,.vjs-value-undefined{color:#d55fde}.vjs-value-boolean,.vjs-value-number{color:#1d8ce0}.vjs-value-string{color:#13ce66}.vjs-tree{font-family:Monaco,Menlo,Consolas,Bitstream Vera Sans Mono,monospace;font-size:14px;text-align:left}.vjs-tree.is-virtual{overflow:auto}.vjs-tree.is-virtual .vjs-tree-node{white-space:nowrap}#alertModal{z-index:99999;background:#00000080}#alertModal svg{display:block;margin:0 auto;width:4rem;height:4rem} 2 | -------------------------------------------------------------------------------- /public/vendor/horizon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/public/vendor/horizon/favicon.png -------------------------------------------------------------------------------- /public/vendor/horizon/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/public/vendor/horizon/img/favicon.png -------------------------------------------------------------------------------- /public/vendor/horizon/img/horizon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/vendor/horizon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/img/favicon.png": { 3 | "file": "favicon.png", 4 | "src": "resources/img/favicon.png", 5 | "integrity": "sha384-tqnRilkeRgqFt3SUYaxuaQs14WOwuU8Gvk3sqRZmnyWZVhr1Kk19Ecr7dFMb4HZo" 6 | }, 7 | "resources/js/app.js": { 8 | "file": "app.js", 9 | "name": "app", 10 | "src": "resources/js/app.js", 11 | "isEntry": true, 12 | "css": [ 13 | "app.css" 14 | ], 15 | "integrity": "sha384-EV5vlraT2g7leIzueltC7I+UzR7uBT4ndQF5b1G9I+kUrQ4XL0DREuRw/XiU/U3P" 16 | }, 17 | "resources/sass/styles-dark.scss": { 18 | "file": "styles-dark.css", 19 | "src": "resources/sass/styles-dark.scss", 20 | "isEntry": true, 21 | "integrity": "sha384-/sLOxh+NTFEdcZ8svIuVTv/lSL65X3QGIXhExXAhntQYWjiez1CQbv4ICbtwRfd8" 22 | }, 23 | "resources/sass/styles.scss": { 24 | "file": "styles.css", 25 | "src": "resources/sass/styles.scss", 26 | "isEntry": true, 27 | "integrity": "sha384-4HOmv1E51xgqbUYzCYXaRXPRja5nEho6eq/L/CKs0LlidzTGNTk81VtCAHqLiYSC" 28 | } 29 | } -------------------------------------------------------------------------------- /public/vendor/horizon/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=4999da9248177ed487693daec2a7d3fe", 3 | "/app-dark.css": "/app-dark.css?id=dcaca44a9f0f1d019e3cd3d76c3cb8fd", 4 | "/app.css": "/app.css?id=14e3bcd1f1b1cf88e63e945529c4d0ce", 5 | "/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f", 6 | "/img/horizon.svg": "/img/horizon.svg?id=904d5b5185fefb09035384e15bfca765", 7 | "/img/sprite.svg": "/img/sprite.svg?id=afc4952b74895bdef3ab4ebe9adb746f" 8 | } 9 | -------------------------------------------------------------------------------- /public/vendor/telescope/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaravelDaily/FilaStart/c1a8ba98dbc922194f28501a814975d03bc990cc/public/vendor/telescope/favicon.ico -------------------------------------------------------------------------------- /public/vendor/telescope/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=7049e92a398e816f8cd53a915eaea592", 3 | "/app-dark.css": "/app-dark.css?id=1ea407db56c5163ae29311f1f38eb7b9", 4 | "/app.css": "/app.css?id=de4c978567bfd90b38d186937dee5ccf" 5 | } 6 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | 3 | import Alpine from 'alpinejs'; 4 | 5 | window.Alpine = Alpine; 6 | 7 | Alpine.start(); 8 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | window.axios = axios; 3 | 4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 5 | 6 | /** 7 | * Echo exposes an expressive API for subscribing to channels and listening 8 | * for events that are broadcast by Laravel. Echo and event broadcasting 9 | * allow your team to quickly build robust real-time web applications. 10 | */ 11 | 12 | import './echo'; 13 | -------------------------------------------------------------------------------- /resources/js/echo.js: -------------------------------------------------------------------------------- 1 | import Echo from 'laravel-echo'; 2 | 3 | import Pusher from 'pusher-js'; 4 | window.Pusher = Pusher; 5 | 6 | window.Echo = new Echo({ 7 | broadcaster: 'reverb', 8 | key: import.meta.env.VITE_REVERB_APP_KEY, 9 | wsHost: import.meta.env.VITE_REVERB_HOST, 10 | wsPort: import.meta.env.VITE_REVERB_PORT ?? 80, 11 | wssPort: import.meta.env.VITE_REVERB_PORT ?? 443, 12 | forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', 13 | enabledTransports: ['ws', 'wss'], 14 | }); 15 | -------------------------------------------------------------------------------- /resources/views/filament/footer.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /resources/views/filament/pages/create-panel-page.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/filament/pages/panel-deployment-page.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | @if(!$deployment) 5 |
6 | @if($crudsCount > 0) 7 | Start Generation 8 | @else 9 |
10 |

No CRUDs found

11 |

Please create a CRUD to generate the panel

12 |
13 | @endif 14 |
15 | @else 16 |
17 | @if($crudsCount > 0) 18 | Start Generation 19 | @else 20 |
21 |

No CRUDs found

22 |

Please create a CRUD to generate the panel

23 |
24 | @endif 25 |
26 | 27 | @if($deployment->status == 'pending') 28 |
29 | Generation is pending 30 |
31 | @elseif($deployment->status == 'failed') 32 |
33 | Generation failed 34 |
35 | @elseif($deployment->status == 'success') 36 |
37 | 38 |
39 | Generation successful 40 | 41 | 42 | Download 43 | 44 |
45 |
46 | @endif 47 | @endif 48 |
49 | 50 | @if($deployment) 51 |
status !== 'success') 54 | wire:poll.1000ms 55 | @endif 56 | id="deployment-log" 57 | > 58 | {!! nl2br($deployment->deployment_log) !!} 59 |
60 | 61 | @script 62 | 66 | @endscript 67 | @endif 68 |
69 | -------------------------------------------------------------------------------- /resources/views/filament/pages/panel-module-management.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | @foreach($modules as $slug => $title) 4 | @php($installed = $panel->modules->contains('slug', $slug)) 5 |
6 | 9 | {{ $title }} 10 | @if(!$installed) 11 | (Install) 12 | @else 13 | (Uninstall) 14 | @endif 15 | 16 |
17 | @endforeach 18 |
19 |
-------------------------------------------------------------------------------- /resources/views/filament/pages/panels-create-panel.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/filament/pages/panels-edit-panel.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/filament/pages/panels-panels-list.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | @foreach($panels as $panel) 5 | 12 | @endforeach 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /resources/views/filament/pages/panels/edit-panel.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /routes/auth.php: -------------------------------------------------------------------------------- 1 | group(function () { 15 | Route::get('register', [RegisteredUserController::class, 'create']) 16 | ->name('register'); 17 | 18 | Route::post('register', [RegisteredUserController::class, 'store']); 19 | 20 | Route::get('login', [AuthenticatedSessionController::class, 'create']) 21 | ->name('login'); 22 | 23 | Route::post('login', [AuthenticatedSessionController::class, 'store']); 24 | 25 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) 26 | ->name('password.request'); 27 | 28 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) 29 | ->name('password.email'); 30 | 31 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) 32 | ->name('password.reset'); 33 | 34 | Route::post('reset-password', [NewPasswordController::class, 'store']) 35 | ->name('password.store'); 36 | }); 37 | 38 | Route::middleware('auth')->group(function () { 39 | Route::get('verify-email', EmailVerificationPromptController::class) 40 | ->name('verification.notice'); 41 | 42 | Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) 43 | ->middleware(['signed', 'throttle:6,1']) 44 | ->name('verification.verify'); 45 | 46 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) 47 | ->middleware('throttle:6,1') 48 | ->name('verification.send'); 49 | 50 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show']) 51 | ->name('password.confirm'); 52 | 53 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); 54 | 55 | Route::put('password', [PasswordController::class, 'update'])->name('password.update'); 56 | 57 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) 58 | ->name('logout'); 59 | }); 60 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote')->hourly(); 9 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/templates', 'filament3'); 12 | } 13 | 14 | public function boot(): void 15 | { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/BelongsToField.php: -------------------------------------------------------------------------------- 1 | field->key; 17 | 18 | if (! str($key)->endsWith('_id')) { 19 | $key .= '_id'; 20 | } 21 | 22 | $this->formKey = $key; 23 | } 24 | 25 | protected function resolveTableColumn(): void 26 | { 27 | $crudFieldOptions = $this->field->crudFieldOptions; 28 | 29 | if (! $crudFieldOptions) { 30 | throw new Exception("Crud field options not found for field {$this->field->key}"); 31 | } 32 | 33 | $this->tableKey = sprintf( 34 | '%s.%s', 35 | $crudFieldOptions->relationship, 36 | $crudFieldOptions->relatedCrudField->key 37 | ); 38 | } 39 | 40 | protected function resolveFormOptions(): string 41 | { 42 | $crudFieldOptions = $this->field->crudFieldOptions; 43 | 44 | if (! $crudFieldOptions) { 45 | throw new Exception("Crud field options not found for field {$this->field->key}"); 46 | } 47 | 48 | // Add relationship to form options 49 | $options = PHP_EOL; 50 | $options .= sprintf( 51 | ' ->relationship(\'%s\', \'%s\')', 52 | $crudFieldOptions->relationship, 53 | $crudFieldOptions->relatedCrudField->key 54 | ); 55 | 56 | return $options.parent::resolveFormOptions(); 57 | } 58 | 59 | public function getMigrationLine(): string 60 | { 61 | if (! $this->field->crudFieldOptions) { 62 | throw new Exception("Crud field options not found for field {$this->field->key}"); 63 | } 64 | 65 | if (! $this->field->crudFieldOptions->crud) { 66 | throw new Exception("Related crud not found for field {$this->field->key}"); 67 | } 68 | 69 | // TODO: Look at the implementation, it might need a better one 70 | $key = $this->field->key; 71 | 72 | if (! str($key)->endsWith('_id')) { 73 | $key .= '_id'; 74 | } 75 | 76 | return (new MigrationLineGenerator()) 77 | ->setType('foreignId') 78 | ->setKey($key) 79 | ->constrained($this->field->crudFieldOptions->crud->model_snake_plural_class_name) 80 | ->toString(); 81 | } 82 | 83 | public function modelRelationships(): string 84 | { 85 | if (! $this->field->crudFieldOptions) { 86 | throw new Exception("Crud field options not found for field {$this->field->key}"); 87 | } 88 | 89 | if (! $this->field->crudFieldOptions->crud) { 90 | throw new Exception("Related crud not found for field {$this->field->key}"); 91 | } 92 | 93 | $template = ' public function %s() 94 | { 95 | return $this->belongsTo(%s::class); 96 | }'; 97 | 98 | return sprintf( 99 | $template, 100 | $this->field->crudFieldOptions->relationship, 101 | $this->field->crudFieldOptions->crud->model_class_name, 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/BelongsToManyField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 17 | } 18 | 19 | protected function resolveTableColumn(): void 20 | { 21 | $crudFieldOptions = $this->field->crudFieldOptions; 22 | 23 | if (! $crudFieldOptions) { 24 | throw new Exception("Crud field options not found for field {$this->field->key}"); 25 | } 26 | 27 | $this->tableKey = sprintf( 28 | '%s.%s', 29 | $crudFieldOptions->relationship, 30 | $crudFieldOptions->relatedCrudField->key 31 | ); 32 | } 33 | 34 | protected function resolveFormOptions(): string 35 | { 36 | $crudFieldOptions = $this->field->crudFieldOptions; 37 | 38 | if (! $crudFieldOptions) { 39 | throw new Exception("Crud field options not found for field {$this->field->key}"); 40 | } 41 | 42 | // Add relationship to form options 43 | $options = PHP_EOL; 44 | $options .= sprintf( 45 | ' ->relationship(\'%s\', \'%s\')', 46 | $crudFieldOptions->relationship, 47 | $crudFieldOptions->relatedCrudField->key 48 | ); 49 | 50 | // Add multiple 51 | $options .= PHP_EOL; 52 | $options .= ' ->multiple()'; 53 | 54 | return $options.parent::resolveFormOptions(); 55 | } 56 | 57 | public function getMigrationLine(): string 58 | { 59 | if (! $this->field->crudFieldOptions) { 60 | throw new Exception("Crud field options not found for field {$this->field->key}"); 61 | } 62 | 63 | if (! $this->field->crudFieldOptions->crud) { 64 | throw new Exception("Related crud not found for field {$this->field->key}"); 65 | } 66 | 67 | $currentCrudKey = str($this->field->crud?->title)->snake()->singular()->toString().'_id'; 68 | $currentCrudTable = str($this->field->crud?->title)->snake()->plural()->toString(); 69 | 70 | $relatedCrudKey = str($this->field->crudFieldOptions->crud->title)->snake()->singular()->toString().'_id'; 71 | $relatedCrudTable = str($this->field->crudFieldOptions->crud->title)->snake()->plural()->toString(); 72 | 73 | return (new MigrationLineGenerator()) 74 | ->setType('foreignId') 75 | ->setKey($currentCrudKey) 76 | ->constrained($currentCrudTable) 77 | ->toString(). 78 | PHP_EOL. 79 | (new MigrationLineGenerator()) 80 | ->setType('foreignId') 81 | ->setKey($relatedCrudKey) 82 | ->constrained($relatedCrudTable) 83 | ->toString(); 84 | } 85 | 86 | public function modelRelationships(): string 87 | { 88 | if (! $this->field->crudFieldOptions) { 89 | throw new Exception("Crud field options not found for field {$this->field->key}"); 90 | } 91 | 92 | if (! $this->field->crudFieldOptions->crud) { 93 | throw new Exception("Related crud not found for field {$this->field->key}"); 94 | } 95 | 96 | $template = ' public function %s() 97 | { 98 | return $this->belongsToMany(%s::class); 99 | }'; 100 | 101 | return sprintf( 102 | $template, 103 | $this->field->crudFieldOptions->relationship, 104 | $this->field->crudFieldOptions->crud->model_class_name, 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/CheckboxField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | public function getMigrationLine(): string 24 | { 25 | return (new MigrationLineGenerator()) 26 | ->setType('boolean') 27 | ->setKey($this->field->key) 28 | ->setDefault(false) 29 | ->toString(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/DateField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | protected function resolveTableOptions(): string 24 | { 25 | $options = PHP_EOL; 26 | // TODO: This needs format to be added 27 | $options .= ' ->date()'; 28 | 29 | return $options.parent::resolveTableOptions(); 30 | } 31 | 32 | public function getMigrationLine(): string 33 | { 34 | return (new MigrationLineGenerator()) 35 | ->setType('date') 36 | ->setKey($this->field->key) 37 | ->toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/DateTimeField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | protected function resolveTableOptions(): string 24 | { 25 | $options = PHP_EOL; 26 | // TODO: This needs format to be added 27 | $options .= ' ->dateTime()'; 28 | 29 | return $options.parent::resolveTableOptions(); 30 | } 31 | 32 | public function getMigrationLine(): string 33 | { 34 | return (new MigrationLineGenerator()) 35 | ->setType('datetime') 36 | ->setKey($this->field->key) 37 | ->toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/EmailField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | public function getMigrationLine(): string 24 | { 25 | return (new MigrationLineGenerator()) 26 | ->setType('string') 27 | ->setKey($this->field->key) 28 | ->toString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/FileField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | public function getMigrationLine(): string 24 | { 25 | return (new MigrationLineGenerator()) 26 | ->setType('string') 27 | ->setKey($this->field->key) 28 | ->toString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/FloatField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | protected function resolveFormOptions(): string 24 | { 25 | $options = PHP_EOL; 26 | // TODO: This needs format to be added 27 | $options .= ' ->numeric()'; 28 | 29 | return $options.parent::resolveFormOptions(); 30 | } 31 | 32 | protected function resolveTableOptions(): string 33 | { 34 | $options = PHP_EOL; 35 | // TODO: This needs format to be added 36 | $options .= ' ->numeric()'; 37 | 38 | return $options.parent::resolveTableOptions(); 39 | } 40 | 41 | public function getMigrationLine(): string 42 | { 43 | return (new MigrationLineGenerator()) 44 | ->setType('double') 45 | ->setKey($this->field->key) 46 | ->toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/IdField.php: -------------------------------------------------------------------------------- 1 | tableKey = $this->field->key; 14 | } 15 | 16 | public function formComponent(): string 17 | { 18 | return ''; // We don't want to have ID field on forms. We might change this 19 | } 20 | 21 | public function getMigrationLine(): string 22 | { 23 | return (new MigrationLineGenerator()) 24 | ->setType('id') 25 | ->toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/ImageField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | public function getMigrationLine(): string 24 | { 25 | return (new MigrationLineGenerator()) 26 | ->setType('string') 27 | ->setKey($this->field->key) 28 | ->toString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/MoneyField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | protected function resolveTableOptions(): string 24 | { 25 | $options = PHP_EOL; 26 | // TODO: This needs format to be added 27 | $options .= ' ->money()'; 28 | 29 | return $options.parent::resolveTableOptions(); 30 | } 31 | 32 | public function getMigrationLine(): string 33 | { 34 | return (new MigrationLineGenerator()) 35 | ->setType('string') // TODO: This might be wrong. 36 | ->setKey($this->field->key) 37 | ->toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/PasswordField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 14 | } 15 | 16 | public function tableColumn(): string 17 | { 18 | return ''; // Password field should never be displayed on a table! 19 | } 20 | 21 | protected function resolveFormOptions(): string 22 | { 23 | $options = PHP_EOL; 24 | $options .= ' ->password()'; 25 | 26 | return $options.parent::resolveFormOptions(); 27 | } 28 | 29 | public function getMigrationLine(): string 30 | { 31 | return (new MigrationLineGenerator()) 32 | ->setType('string') 33 | ->setKey($this->field->key) 34 | ->toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/RetrieveGeneratorForField.php: -------------------------------------------------------------------------------- 1 | type) { 13 | CrudFieldTypes::ID => new IdField($field), 14 | CrudFieldTypes::TEXT => new TextField($field), 15 | CrudFieldTypes::DATE_TIME => new DateTimeField($field), 16 | CrudFieldTypes::BELONGS_TO_MANY => new BelongsToManyField($field), 17 | CrudFieldTypes::PASSWORD => new PasswordField($field), 18 | CrudFieldTypes::BELONGS_TO => new BelongsToField($field), 19 | CrudFieldTypes::IMAGE => new ImageField($field), 20 | CrudFieldTypes::TEXTAREA => new TextAreaField($field), 21 | CrudFieldTypes::CHECKBOX => new CheckboxField($field), 22 | CrudFieldTypes::FLOAT => new FloatField($field), 23 | CrudFieldTypes::EMAIL => new EmailField($field), 24 | CrudFieldTypes::DATE => new DateField($field), 25 | CrudFieldTypes::MONEY => new MoneyField($field), 26 | CrudFieldTypes::FILE => new FileField($field), 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/TextAreaField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | public function getMigrationLine(): string 24 | { 25 | return (new MigrationLineGenerator()) 26 | ->setType('text') 27 | ->setKey($this->field->key) 28 | ->toString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Fields/TextField.php: -------------------------------------------------------------------------------- 1 | formKey = $this->field->key; 16 | } 17 | 18 | protected function resolveTableColumn(): void 19 | { 20 | $this->tableKey = $this->field->key; 21 | } 22 | 23 | public function getMigrationLine(): string 24 | { 25 | return (new MigrationLineGenerator()) 26 | ->setType('string') 27 | ->setKey($this->field->key) 28 | ->toString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Files/CreateFile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | private array $replacements; 11 | 12 | public function generate(): string 13 | { 14 | $view = view('filament3::createPage', $this->replacements)->render(); 15 | 16 | return 'replacements = $replacements; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Files/EditFile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | private array $replacements; 11 | 12 | public function generate(): string 13 | { 14 | $view = view('filament3::editPage', $this->replacements)->render(); 15 | 16 | return 'replacements = $replacements; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Files/FileBase.php: -------------------------------------------------------------------------------- 1 | $replacements 11 | */ 12 | public function setReplacements(array $replacements): void; 13 | } 14 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Files/ListFile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | private array $replacements; 11 | 12 | public function generate(): string 13 | { 14 | $view = view('filament3::listPage', $this->replacements)->render(); 15 | 16 | return 'replacements = $replacements; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Generators/Files/ResourceFile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | private array $replacements; 11 | 12 | public function generate(): string 13 | { 14 | $view = view('filament3::resource', $this->replacements)->render(); 15 | 16 | return 'replacements = $replacements; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/IndentsLines.php: -------------------------------------------------------------------------------- 1 | ($line !== '') ? (str_repeat(' ', $level).$line) : '', 13 | explode(PHP_EOL, $string), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Jobs/CreateCreateFileJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 32 | return; 33 | } 34 | 35 | $this->deployment->addNewMessage('Started generating Create file for '.$this->crudData->title.' CRUD'.PHP_EOL); 36 | 37 | $panelService = new PanelService($this->panel); 38 | 39 | $generator = new FileReplacements($this->crudData); 40 | $names = $generator->generateNames(); 41 | 42 | $create = $generator->retrieveFileGenerator('create'); 43 | $create->setReplacements($generator->getReplacementsForCreatePage()); 44 | $createPath = 'app/Filament/Resources/'.$names['resourceName'].'/Pages/'.$names['createName'].'.php'; 45 | $panelService->writeFile($createPath, $create->generate()); 46 | $this->crudData->panelFiles()->updateOrCreate([ 47 | 'path' => $createPath, 48 | 'panel_id' => $this->panel->id, 49 | ], [ 50 | 'path' => $createPath, 51 | 'panel_id' => $this->panel->id, 52 | ]); 53 | 54 | $this->deployment->addNewMessage('Create file for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Jobs/CreateCrudJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 33 | return; 34 | } 35 | 36 | $this->deployment->addNewMessage('Constructed variables for '.$this->crudData->title.' CRUD'.PHP_EOL); 37 | 38 | // TODO: All of the params should be ID only. Fix that!!! 39 | $this->batch() 40 | ?->add([ 41 | // Laravel Generators 42 | new CreateModelJob($this->panel, $this->crudData, $this->deployment), 43 | new CreateMigrationJob($this->panel, $this->crudData, $this->deployment), 44 | new CreateManyToManyMigrationJob($this->panel, $this->crudData, $this->deployment), 45 | 46 | // Filament Specific Generators 47 | new CreateResourceFileJob($this->panel, $this->crudData, $this->deployment), 48 | new CreateCreateFileJob($this->panel, $this->crudData, $this->deployment), 49 | new CreateEditFileJob($this->panel, $this->crudData, $this->deployment), 50 | new CreateListFileJob($this->panel, $this->crudData, $this->deployment), 51 | ]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Jobs/CreateEditFileJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 32 | return; 33 | } 34 | 35 | $this->deployment->addNewMessage('Started generating Edit file for '.$this->crudData->title.' CRUD'.PHP_EOL); 36 | 37 | $panelService = new PanelService($this->panel); 38 | 39 | $generator = new FileReplacements($this->crudData); 40 | $names = $generator->generateNames(); 41 | 42 | $edit = $generator->retrieveFileGenerator('edit'); 43 | $edit->setReplacements($generator->getReplacementsForEditPage()); 44 | $editPath = 'app/Filament/Resources/'.$names['resourceName'].'/Pages/'.$names['editName'].'.php'; 45 | $panelService->writeFile($editPath, $edit->generate()); 46 | $this->crudData->panelFiles()->updateOrCreate([ 47 | 'path' => $editPath, 48 | 'panel_id' => $this->panel->id, 49 | ], [ 50 | 'path' => $editPath, 51 | 'panel_id' => $this->panel->id, 52 | ]); 53 | 54 | $this->deployment->addNewMessage('Edit file for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Jobs/CreateListFileJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 32 | return; 33 | } 34 | 35 | $this->deployment->addNewMessage('Started generating List file for '.$this->crudData->title.' CRUD'.PHP_EOL); 36 | 37 | $panelService = new PanelService($this->panel); 38 | 39 | $generator = new FileReplacements($this->crudData); 40 | $names = $generator->generateNames(); 41 | 42 | $list = $generator->retrieveFileGenerator('list'); 43 | $list->setReplacements($generator->getReplacementsForListPage()); 44 | $listPath = 'app/Filament/Resources/'.$names['resourceName'].'/Pages/'.$names['listName'].'.php'; 45 | $panelService->writeFile($listPath, $list->generate()); 46 | $this->crudData->panelFiles()->updateOrCreate([ 47 | 'path' => $listPath, 48 | 'panel_id' => $this->panel->id, 49 | ], [ 50 | 'path' => $listPath, 51 | 'panel_id' => $this->panel->id, 52 | ]); 53 | 54 | $this->deployment->addNewMessage('List file for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Jobs/CreateResourceFileJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 32 | return; 33 | } 34 | 35 | $this->deployment->addNewMessage('Started generating Resource file for '.$this->crudData->title.' CRUD'.PHP_EOL); 36 | 37 | $panelService = new PanelService($this->panel); 38 | 39 | $generator = new FileReplacements($this->crudData); 40 | $names = $generator->generateNames(); 41 | 42 | $resource = $generator->retrieveFileGenerator('resource'); 43 | $resource->setReplacements($generator->getReplacementsForResource()); 44 | $resourcePath = 'app/Filament/Resources/'.$names['resourceName'].'.php'; 45 | $panelService->writeFile($resourcePath, $resource->generate()); 46 | $this->crudData->panelFiles()->updateOrCreate([ 47 | 'path' => $resourcePath, 48 | 'panel_id' => $this->panel->id, 49 | ], [ 50 | 'path' => $resourcePath, 51 | 'panel_id' => $this->panel->id, 52 | ]); 53 | 54 | $this->deployment->addNewMessage('Resource file for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/Modules/ModuleManager.php: -------------------------------------------------------------------------------- 1 | new BaseModule(), 14 | 'asset-management' => new AssetManagementModule(), 15 | 'client-management' => new ClientManagementModule(), 16 | default => throw new NotImplementedException("Module $moduleSlug not found"), 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/templates/createPage.blade.php: -------------------------------------------------------------------------------- 1 | 2 | namespace {!! $namespace !!}; 3 | 4 | use {!! $resource !!}; 5 | use Filament\Actions; 6 | use {!! $baseResourcePage !!}; 7 | 8 | class {!! $resourcePageClass !!} extends {!! $baseResourcePageClass !!} 9 | { 10 | protected static string $resource = {!! $resourceClass !!}::class; 11 | } 12 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/templates/editPage.blade.php: -------------------------------------------------------------------------------- 1 | 2 | namespace {!! $namespace !!}; 3 | 4 | use {!! $resource !!}; 5 | use Filament\Actions; 6 | use {!! $baseResourcePage !!}; 7 | 8 | class {!! $resourcePageClass !!} extends {!! $baseResourcePageClass !!} 9 | { 10 | protected static string $resource = {!! $resourceClass !!}::class; 11 | 12 | protected function getHeaderActions(): array 13 | { 14 | return [ 15 | {!! $actions !!} 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/templates/listPage.blade.php: -------------------------------------------------------------------------------- 1 | 2 | namespace {!! $namespace !!}; 3 | 4 | use {!! $resource !!}; 5 | use Filament\Actions; 6 | use {!! $baseResourcePage !!}; 7 | 8 | class {!! $resourcePageClass !!} extends {!! $baseResourcePageClass !!} 9 | { 10 | protected static string $resource = {!! $resourceClass !!}::class; 11 | 12 | protected function getHeaderActions(): array 13 | { 14 | return [ 15 | Actions\CreateAction::make(), 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /systems/generators/filament3/src/templates/resource.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace {!! $namespace !!}; 4 | 5 | use {!! $resource !!}\Pages; 6 | use {!! $resource !!}\RelationManagers; 7 | use App\Models\{!! $model !!}; 8 | use Filament\Forms; 9 | use Filament\Forms\Form; 10 | use Filament\Resources\Resource; 11 | use Filament\Tables; 12 | use Filament\Tables\Table; 13 | use Illuminate\Database\Eloquent\Builder; 14 | use Illuminate\Database\Eloquent\SoftDeletingScope; 15 | 16 | class {!! $resourceClass !!} extends Resource 17 | { 18 | protected static ?string $model = {!! $modelClass !!}::class;{!! $icon !!} 19 | {!! $navigationGroup !!} 20 | public static function form(Form $form): Form 21 | { 22 | return $form 23 | ->schema([ 24 | {!! $formSchema !!} 25 | ]); 26 | } 27 | 28 | public static function table(Table $table): Table 29 | { 30 | return $table 31 | ->columns([ 32 | {!! $tableColumns !!} 33 | ]) 34 | ->filters([ 35 | {!! $tableFilters !!} 36 | ]) 37 | ->actions([ 38 | {!! $tableActions !!} 39 | ]) 40 | ->bulkActions([ 41 | Tables\Actions\BulkActionGroup::make([ 42 | {!! $tableBulkActions !!} 43 | ]), 44 | ]); 45 | } 46 | {!! $relations !!} 47 | public static function getPages(): array 48 | { 49 | return [ 50 | {!! $pages !!} 51 | ]; 52 | }{!! $eloquentQuery !!} 53 | } 54 | -------------------------------------------------------------------------------- /systems/generators/laravel11/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generators/laravel11", 3 | "type": "project", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "LaravelDaily", 8 | "email": "info@laraveldaily.com" 9 | } 10 | ], 11 | "minimum-stability": "stable", 12 | "require": {}, 13 | "extra": { 14 | "laravel": { 15 | "providers": [ 16 | "Generators\\Laravel11\\Laravel11ServiceProvider" 17 | ] 18 | } 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Generators\\Laravel11\\": "src/" 23 | }, 24 | "files": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/Generators/MigrationLineGenerator.php: -------------------------------------------------------------------------------- 1 | getMigrationOptionsOrder(); 26 | 27 | return '$table'.implode('', $properties).';'; 28 | } 29 | 30 | public function setType(string $type): self 31 | { 32 | $this->type = $type; 33 | 34 | return $this; 35 | } 36 | 37 | public function setKey(string $key): self 38 | { 39 | $this->key = $key; 40 | 41 | return $this; 42 | } 43 | 44 | public function constrained(?string $table = null, ?string $column = null): self 45 | { 46 | $this->constrained = true; 47 | $this->constrainedTable = $table; 48 | $this->constrainedColumn = $column; 49 | 50 | return $this; 51 | } 52 | 53 | public function nullable(): self 54 | { 55 | $this->nullable = true; 56 | 57 | return $this; 58 | } 59 | 60 | public function index(): self 61 | { 62 | $this->index = true; 63 | 64 | return $this; 65 | } 66 | 67 | public function setDefault(bool $default): self 68 | { 69 | $this->default = $default; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @return string[] 76 | */ 77 | protected function getMigrationOptionsOrder(): array 78 | { 79 | return [ 80 | $this->buildKeyAndMethod(), 81 | $this->checkForDefault(), 82 | $this->checkForNullable(), 83 | $this->checkForConstrained(), 84 | $this->checkForIndex(), 85 | ]; 86 | } 87 | 88 | private function buildKeyAndMethod(): string 89 | { 90 | $output = '->'.$this->type; 91 | 92 | if ($this->key) { 93 | $output .= '(\''.$this->key.'\')'; 94 | } else { 95 | $output .= '()'; 96 | } 97 | 98 | return $output; 99 | } 100 | 101 | private function checkForConstrained(): string 102 | { 103 | if ($this->constrained) { 104 | $output = '->constrained'; 105 | 106 | if ($this->constrainedTable) { 107 | $output .= '(\''.$this->constrainedTable.'\''; 108 | 109 | if ($this->constrainedColumn) { 110 | $output .= ', \''.$this->constrainedColumn.'\''; 111 | } 112 | 113 | $output .= ')'; 114 | 115 | return $output; 116 | } 117 | 118 | return $output.'()'; 119 | } 120 | 121 | return ''; 122 | } 123 | 124 | private function checkForNullable(): string 125 | { 126 | if ($this->nullable) { 127 | return '->nullable()'; 128 | } 129 | 130 | return ''; 131 | } 132 | 133 | private function checkForIndex(): string 134 | { 135 | if ($this->index) { 136 | return '->index()'; 137 | } 138 | 139 | return ''; 140 | } 141 | 142 | private function checkForDefault(): string 143 | { 144 | if (! is_null($this->default)) { 145 | return '->default('.($this->default ? 'true' : 'false').')'; 146 | } 147 | 148 | return ''; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/Jobs/CreateManyToManyMigrationJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 33 | return; 34 | } 35 | 36 | // Check if there should be many to many migration created 37 | 38 | $manyToMayFields = $this->crudData->fields()->whereIn('type', [ 39 | CrudFieldTypes::BELONGS_TO_MANY, 40 | ]); 41 | 42 | if ($manyToMayFields->count() === 0) { 43 | return; 44 | } 45 | 46 | $manyToMayFields = $manyToMayFields->with(['crudFieldOptions', 'crudFieldOptions.crud', 'crudFieldOptions.relatedCrudField'])->get(); 47 | 48 | $this->deployment->addNewMessage('Started Generating a many to many migration for '.$this->crudData->title.' CRUD'.PHP_EOL); 49 | 50 | foreach ($manyToMayFields as $field) { 51 | if (! $field->crudFieldOptions) { 52 | // TODO: Add logging that something is wrong with the field 53 | continue; 54 | } 55 | 56 | if (! $field->crudFieldOptions->crud) { 57 | // TODO: Add logging that something is wrong with the field 58 | continue; 59 | } 60 | 61 | $panelService = new PanelService($this->panel); 62 | $migration = new MigrationGenerator($this->crudData); 63 | 64 | $migrationName = $migration->getManyToManyName($this->panel->cruds()->max('menu_order') + 10, $this->crudData, $field->crudFieldOptions->crud); 65 | $migrationPath = 'database/migrations/'.$migrationName.'.php'; 66 | $panelService->writeFile($migrationPath, $migration->generateManyToMany($this->crudData, $field)); 67 | 68 | $this->crudData->panelFiles()->updateOrCreate([ 69 | 'path' => $migrationPath, 70 | 'panel_id' => $this->panel->id, 71 | ], [ 72 | 'path' => $migrationPath, 73 | 'panel_id' => $this->panel->id, 74 | ]); 75 | 76 | } 77 | 78 | $this->deployment->addNewMessage('Migration for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/Jobs/CreateMigrationJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 32 | return; 33 | } 34 | 35 | $this->deployment->addNewMessage('Started Generating a migration for '.$this->crudData->title.' CRUD'.PHP_EOL); 36 | 37 | $panelService = new PanelService($this->panel); 38 | 39 | $migration = new MigrationGenerator($this->crudData); 40 | $migrationName = $migration->getName(); 41 | $migrationPath = 'database/migrations/'.$migrationName.'.php'; 42 | $panelService->writeFile($migrationPath, $migration->generate()); 43 | 44 | $this->crudData->panelFiles()->updateOrCreate([ 45 | 'path' => $migrationPath, 46 | 'panel_id' => $this->panel->id, 47 | ], [ 48 | 'path' => $migrationPath, 49 | 'panel_id' => $this->panel->id, 50 | ]); 51 | 52 | $this->deployment->addNewMessage('Migration for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/Jobs/CreateModelJob.php: -------------------------------------------------------------------------------- 1 | crudData->type !== CrudTypes::CRUD) { 32 | return; 33 | } 34 | 35 | $this->deployment->addNewMessage('Started Generating a model for '.$this->crudData->title.' CRUD'.PHP_EOL); 36 | 37 | $panelService = new PanelService($this->panel); 38 | 39 | $model = new ModelGenerator($this->crudData); 40 | $modelName = $model->getName(); 41 | $modelPath = 'app/Models/'.$modelName.'.php'; 42 | $panelService->writeFile($modelPath, $model->generate()); 43 | 44 | $this->crudData->panelFiles()->updateOrCreate([ 45 | 'path' => $modelPath, 46 | 'panel_id' => $this->panel->id, 47 | ], [ 48 | 'path' => $modelPath, 49 | 'panel_id' => $this->panel->id, 50 | ]); 51 | 52 | $this->deployment->addNewMessage('Model for '.$this->crudData->title.' CRUD generated successfully'.PHP_EOL); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/Laravel11ServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/templates', 'laravel11'); 12 | } 13 | 14 | public function boot(): void 15 | { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/templates/cacheTable.blade.php: -------------------------------------------------------------------------------- 1 | 2 | use Illuminate\Database\Migrations\Migration; 3 | use Illuminate\Database\Schema\Blueprint; 4 | use Illuminate\Support\Facades\Schema; 5 | 6 | return new class extends Migration 7 | { 8 | /** 9 | * Run the migrations. 10 | */ 11 | public function up(): void 12 | { 13 | Schema::create('cache', function (Blueprint $table) { 14 | $table->string('key')->primary(); 15 | $table->mediumText('value'); 16 | $table->integer('expiration'); 17 | }); 18 | 19 | Schema::create('cache_locks', function (Blueprint $table) { 20 | $table->string('key')->primary(); 21 | $table->string('owner'); 22 | $table->integer('expiration'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('cache'); 32 | Schema::dropIfExists('cache_locks'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/templates/jobsTable.blade.php: -------------------------------------------------------------------------------- 1 | 2 | use Illuminate\Database\Migrations\Migration; 3 | use Illuminate\Database\Schema\Blueprint; 4 | use Illuminate\Support\Facades\Schema; 5 | 6 | return new class extends Migration 7 | { 8 | /** 9 | * Run the migrations. 10 | */ 11 | public function up(): void 12 | { 13 | Schema::create('jobs', function (Blueprint $table) { 14 | $table->id(); 15 | $table->string('queue')->index(); 16 | $table->longText('payload'); 17 | $table->unsignedTinyInteger('attempts'); 18 | $table->unsignedInteger('reserved_at')->nullable(); 19 | $table->unsignedInteger('available_at'); 20 | $table->unsignedInteger('created_at'); 21 | }); 22 | 23 | Schema::create('job_batches', function (Blueprint $table) { 24 | $table->string('id')->primary(); 25 | $table->string('name'); 26 | $table->integer('total_jobs'); 27 | $table->integer('pending_jobs'); 28 | $table->integer('failed_jobs'); 29 | $table->longText('failed_job_ids'); 30 | $table->mediumText('options')->nullable(); 31 | $table->integer('cancelled_at')->nullable(); 32 | $table->integer('created_at'); 33 | $table->integer('finished_at')->nullable(); 34 | }); 35 | 36 | Schema::create('failed_jobs', function (Blueprint $table) { 37 | $table->id(); 38 | $table->string('uuid')->unique(); 39 | $table->text('connection'); 40 | $table->text('queue'); 41 | $table->longText('payload'); 42 | $table->longText('exception'); 43 | $table->timestamp('failed_at')->useCurrent(); 44 | }); 45 | } 46 | 47 | /** 48 | * Reverse the migrations. 49 | */ 50 | public function down(): void 51 | { 52 | Schema::dropIfExists('jobs'); 53 | Schema::dropIfExists('job_batches'); 54 | Schema::dropIfExists('failed_jobs'); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/templates/migration.blade.php: -------------------------------------------------------------------------------- 1 | use Illuminate\Database\Migrations\Migration; 2 | use Illuminate\Database\Schema\Blueprint; 3 | use Illuminate\Support\Facades\Schema;{{ $uses }} 4 | 5 | return new class extends Migration 6 | { 7 | public function up(): void 8 | { 9 | Schema::create('{{ $tableName }}', static function (Blueprint $table) { 10 | {!! $tableColumns !!} 11 | }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /systems/generators/laravel11/src/templates/model.blade.php: -------------------------------------------------------------------------------- 1 | namespace App\Models; 2 | 3 | use Illuminate\Database\Eloquent\Model; 4 | use Illuminate\Database\Eloquent\SoftDeletes;{{ $uses }} 5 | 6 | class {{ $modelName }} extends {{ $extends }} 7 | { 8 | use SoftDeletes;{{ $traits }} 9 | 10 | protected $fillable = [ 11 | {!! $fillable !!} 12 | ]; 13 | {!! $casts !!}{!! $relationships !!}{!! $methods !!} 14 | } -------------------------------------------------------------------------------- /systems/generators/laravel11/src/templates/sessionTable.blade.php: -------------------------------------------------------------------------------- 1 | 2 | use Illuminate\Database\Migrations\Migration; 3 | use Illuminate\Database\Schema\Blueprint; 4 | use Illuminate\Support\Facades\Schema; 5 | 6 | return new class extends Migration { 7 | public function up(): void 8 | { 9 | 10 | Schema::create('password_reset_tokens', function (Blueprint $table) { 11 | $table->string('email')->primary(); 12 | $table->string('token'); 13 | $table->timestamp('created_at')->nullable(); 14 | }); 15 | 16 | Schema::create('sessions', function (Blueprint $table) { 17 | $table->string('id')->primary(); 18 | $table->foreignId('user_id')->nullable()->index(); 19 | $table->string('ip_address', 45)->nullable(); 20 | $table->text('user_agent')->nullable(); 21 | $table->longText('payload'); 22 | $table->integer('last_activity')->index(); 23 | }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from 'tailwindcss/defaultTheme'; 2 | import forms from '@tailwindcss/forms'; 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | export default { 6 | content: [ 7 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 8 | './storage/framework/views/*.php', 9 | './resources/views/**/*.blade.php', 10 | ], 11 | 12 | theme: { 13 | extend: { 14 | fontFamily: { 15 | sans: ['Figtree', ...defaultTheme.fontFamily.sans], 16 | }, 17 | }, 18 | }, 19 | 20 | plugins: [forms], 21 | }; 22 | -------------------------------------------------------------------------------- /tests/Feature/Crud/CrudFieldsTest.php: -------------------------------------------------------------------------------- 1 | create(); 19 | 20 | actingAs($user); 21 | 22 | artisan('db:seed'); 23 | 24 | $panel = Panel::create([ 25 | 'name' => 'Test Panel', 26 | 'user_id' => auth()->id(), 27 | 'type' => PanelTypes::FILAMENT3, 28 | ]); 29 | 30 | dispatch(new PanelCreatedJob($panel)); 31 | 32 | $crud = $panel->cruds()->create([ 33 | 'title' => 'Test CRUD', 34 | 'visual_title' => 'Test CRUD', 35 | 'type' => CrudTypes::CRUD, 36 | 'user_id' => $user->id, 37 | 'menu_order' => 10, 38 | ]); 39 | 40 | livewire(FieldsRelationManager::class, [ 41 | 'ownerRecord' => $crud, 42 | 'pageClass' => EditCrud::class, 43 | ]) 44 | ->assertTableActionExists('create') 45 | ->callTableAction( 46 | 'create', 47 | $crud, 48 | [ 49 | 'type' => CrudFieldTypes::TEXT, 50 | 'validation' => CrudFieldValidation::REQUIRED, 51 | 'label' => 'Test Field', 52 | 'tooltip' => 'Test Tooltip', 53 | 'in_create' => true, 54 | 'in_edit' => true, 55 | 'in_list' => true, 56 | ] 57 | ) 58 | ->callMountedTableAction() 59 | ->assertHasNoTableActionErrors() 60 | ->assertSuccessful(); 61 | 62 | $crud = $crud->fresh(); 63 | 64 | $field = $crud->fields()->where('label', 'Test Field')->first(); 65 | 66 | expect($field)->not->toBeNull(); 67 | })->skip('This test is incomplete due to the lack of documentation'); 68 | 69 | it('can edit CRUD fields', function () { 70 | $user = User::factory()->create(); 71 | 72 | actingAs($user); 73 | 74 | artisan('db:seed'); 75 | 76 | $panel = Panel::create([ 77 | 'name' => 'Test Panel', 78 | 'user_id' => auth()->id(), 79 | 'type' => PanelTypes::FILAMENT3, 80 | ]); 81 | 82 | dispatch(new PanelCreatedJob($panel)); 83 | 84 | $panel = $panel->fresh(); 85 | })->skip('This test is incomplete due to the lack of documentation'); 86 | 87 | it('can delete CRUD fields', function () { 88 | $user = User::factory()->create(); 89 | 90 | actingAs($user); 91 | 92 | artisan('db:seed'); 93 | 94 | $panel = Panel::create([ 95 | 'name' => 'Test Panel', 96 | 'user_id' => auth()->id(), 97 | 'type' => PanelTypes::FILAMENT3, 98 | ]); 99 | 100 | dispatch(new PanelCreatedJob($panel)); 101 | 102 | $panel = $panel->fresh(); 103 | })->skip('This test is incomplete due to the lack of documentation'); 104 | 105 | it('can reorder CRUD fields', function () { 106 | $user = User::factory()->create(); 107 | 108 | actingAs($user); 109 | 110 | artisan('db:seed'); 111 | 112 | $panel = Panel::create([ 113 | 'name' => 'Test Panel', 114 | 'user_id' => auth()->id(), 115 | 'type' => PanelTypes::FILAMENT3, 116 | ]); 117 | 118 | dispatch(new PanelCreatedJob($panel)); 119 | 120 | $panel = $panel->fresh(); 121 | })->skip('This test is incomplete due to the lack of documentation'); 122 | 123 | it('can create various CRUD field types', function () { 124 | // TODO: This needs data-provider to work 125 | $user = User::factory()->create(); 126 | 127 | actingAs($user); 128 | 129 | artisan('db:seed'); 130 | 131 | $panel = Panel::create([ 132 | 'name' => 'Test Panel', 133 | 'user_id' => auth()->id(), 134 | 'type' => PanelTypes::FILAMENT3, 135 | ]); 136 | 137 | dispatch(new PanelCreatedJob($panel)); 138 | 139 | $panel = $panel->fresh(); 140 | })->skip('This test is incomplete due to the lack of documentation'); 141 | -------------------------------------------------------------------------------- /tests/Feature/Filament3/Fields/IdFieldTest.php: -------------------------------------------------------------------------------- 1 | 'id', 10 | 'label' => 'ID', 11 | 'type' => CrudFieldTypes::ID, 12 | 'in_create' => true, 13 | 'in_edit' => true, 14 | ]); 15 | 16 | $generator = RetrieveGeneratorForField::for($field); 17 | $output = $generator->formComponent(); 18 | 19 | expect($output) 20 | ->toBeString() 21 | ->toEqual(''); 22 | }); 23 | 24 | test('id field can be required', function () { 25 | $field = new CrudField([ 26 | 'key' => 'id', 27 | 'label' => 'ID', 28 | 'type' => CrudFieldTypes::ID, 29 | 'in_create' => true, 30 | 'in_edit' => true, 31 | 'validation' => 'required', 32 | ]); 33 | 34 | $generator = RetrieveGeneratorForField::for($field); 35 | $output = $generator->formComponent(); 36 | 37 | expect($output) 38 | ->toBeString() 39 | ->toEqual(''); 40 | }); 41 | 42 | test('id field can be displayed on a table', function () { 43 | $field = new CrudField([ 44 | 'key' => 'id', 45 | 'label' => 'ID', 46 | 'type' => CrudFieldTypes::ID, 47 | 'in_list' => true, 48 | ]); 49 | 50 | $generator = RetrieveGeneratorForField::for($field); 51 | $output = $generator->tableColumn(); 52 | 53 | expect($output) 54 | ->toBeString() 55 | ->toContain('TextColumn::make(\'id\')'); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/Feature/Filament3/Fields/ImageFieldTest.php: -------------------------------------------------------------------------------- 1 | 'name', 10 | 'label' => 'Name', 11 | 'type' => CrudFieldTypes::IMAGE, 12 | 'in_create' => true, 13 | 'in_edit' => true, 14 | ]); 15 | 16 | $generator = RetrieveGeneratorForField::for($field); 17 | $output = $generator->formComponent(); 18 | 19 | expect($output) 20 | ->toBeString() 21 | ->toContain('FileUpload::make(\'name\')'); 22 | }); 23 | 24 | test('image field can be hidden on edit', function () { 25 | $field = new CrudField([ 26 | 'key' => 'name', 27 | 'label' => 'Name', 28 | 'type' => CrudFieldTypes::IMAGE, 29 | 'in_create' => true, 30 | 'in_edit' => false, 31 | ]); 32 | 33 | $generator = RetrieveGeneratorForField::for($field); 34 | $output = $generator->formComponent(); 35 | 36 | expect($output) 37 | ->toBeString() 38 | ->toContain('->hiddenOn(\'edit\')'); 39 | }); 40 | 41 | test('image field can be hidden on create', function () { 42 | $field = new CrudField([ 43 | 'key' => 'name', 44 | 'label' => 'Name', 45 | 'type' => CrudFieldTypes::IMAGE, 46 | 'in_create' => false, 47 | 'in_edit' => true, 48 | ]); 49 | 50 | $generator = RetrieveGeneratorForField::for($field); 51 | $output = $generator->formComponent(); 52 | 53 | expect($output) 54 | ->toBeString() 55 | ->toContain('->hiddenOn(\'create\')'); 56 | }); 57 | 58 | test('image field can be required', function () { 59 | $field = new CrudField([ 60 | 'key' => 'name', 61 | 'label' => 'Name', 62 | 'type' => CrudFieldTypes::IMAGE, 63 | 'in_create' => true, 64 | 'in_edit' => true, 65 | 'validation' => 'required', 66 | ]); 67 | 68 | $generator = RetrieveGeneratorForField::for($field); 69 | $output = $generator->formComponent(); 70 | 71 | expect($output) 72 | ->toBeString() 73 | ->toContain('->required(true)'); 74 | }); 75 | 76 | test('image field can have placeholder', function () { 77 | $field = new CrudField([ 78 | 'key' => 'name', 79 | 'label' => 'Name', 80 | 'type' => CrudFieldTypes::IMAGE, 81 | 'in_create' => true, 82 | 'in_edit' => true, 83 | 'tooltip' => 'Upload avatar here', 84 | ]); 85 | 86 | $generator = RetrieveGeneratorForField::for($field); 87 | $output = $generator->formComponent(); 88 | 89 | expect($output) 90 | ->toBeString() 91 | ->toContain('->placeholder(\'Upload avatar here\')'); 92 | }); 93 | 94 | test('image field can have various names', function ($key, $label, $expected) { 95 | $field = new CrudField([ 96 | 'key' => $key, 97 | 'label' => $label, 98 | 'type' => CrudFieldTypes::IMAGE, 99 | 'in_create' => true, 100 | 'in_edit' => true, 101 | ]); 102 | 103 | $generator = RetrieveGeneratorForField::for($field); 104 | $output = $generator->formComponent(); 105 | 106 | expect($output) 107 | ->toBeString() 108 | ->toContain('FileUpload::make(\''.$expected.'\')'); 109 | })->with([ 110 | [ 111 | 'first_name', 112 | 'First Name', 113 | 'first_name', 114 | ], 115 | [ 116 | 'randomString', 117 | 'Random String', 118 | 'randomstring', 119 | ], 120 | [ // We expect the key to not be mutated from the CRUD 121 | 'some gaps', 122 | 'Some Gaps', 123 | 'some_gaps', 124 | ], 125 | [ 126 | 'related.dot', 127 | 'Related Dot', 128 | 'related.dot', 129 | ], 130 | ]); 131 | 132 | test('image field cant be displayed on a table', function () { 133 | $field = new CrudField([ 134 | 'key' => 'name', 135 | 'label' => 'Name', 136 | 'type' => CrudFieldTypes::IMAGE, 137 | 'in_list' => true, 138 | ]); 139 | 140 | $generator = RetrieveGeneratorForField::for($field); 141 | $output = $generator->tableColumn(); 142 | 143 | expect($output) 144 | ->toBeString() 145 | ->toContain('ImageColumn::make(\'name\')'); 146 | }); 147 | -------------------------------------------------------------------------------- /tests/Feature/Filament3/Files/CreateFileTest.php: -------------------------------------------------------------------------------- 1 | $model, 11 | ])) 12 | ->setRelation('fields', [ 13 | new CrudField([ 14 | 'key' => 'name', 15 | 'label' => 'Name', 16 | 'type' => CrudFieldTypes::TEXT, 17 | 'in_create' => true, 18 | 'in_edit' => true, 19 | 'in_list' => true, 20 | ]), 21 | ]); 22 | 23 | $generator = new FileReplacements($crud); 24 | 25 | $resource = $generator->retrieveFileGenerator('create'); 26 | 27 | $resource->setReplacements($generator->getReplacementsForCreatePage()); 28 | 29 | $model = str($model)->studly(); 30 | 31 | expect($resource->generate()) 32 | ->toContain('App\\Filament\\Resources\\'.$model.'Resource\\Pages;') 33 | ->toContain("class Create{$model} extends CreateRecord") 34 | ->toContain("protected static string \$resource = {$model}Resource::class;"); 35 | })->with([ 36 | 'Post', 37 | 'Item', 38 | 'Country Type', 39 | 'Country_type', 40 | ]); 41 | -------------------------------------------------------------------------------- /tests/Feature/Filament3/Files/EditFileTest.php: -------------------------------------------------------------------------------- 1 | $model, 11 | ])) 12 | ->setRelation('fields', [ 13 | new CrudField([ 14 | 'key' => 'name', 15 | 'label' => 'Name', 16 | 'type' => CrudFieldTypes::TEXT, 17 | 'in_create' => true, 18 | 'in_edit' => true, 19 | 'in_list' => true, 20 | ]), 21 | ]); 22 | 23 | $generator = new FileReplacements($crud); 24 | 25 | $resource = $generator->retrieveFileGenerator('edit'); 26 | 27 | $resource->setReplacements($generator->getReplacementsForEditPage()); 28 | 29 | $model = str($model)->studly(); 30 | 31 | expect($resource->generate()) 32 | ->toContain('App\\Filament\\Resources\\'.$model.'Resource\\Pages;') 33 | ->toContain("class Edit{$model} extends EditRecord") 34 | ->toContain("protected static string \$resource = {$model}Resource::class;"); 35 | })->with([ 36 | 'Post', 37 | 'Item', 38 | 'Country Type', 39 | 'Country_type', 40 | ]); 41 | -------------------------------------------------------------------------------- /tests/Feature/Filament3/Files/ListFileTest.php: -------------------------------------------------------------------------------- 1 | $model, 11 | ])) 12 | ->setRelation('fields', [ 13 | new CrudField([ 14 | 'key' => 'name', 15 | 'label' => 'Name', 16 | 'type' => CrudFieldTypes::TEXT, 17 | 'in_create' => true, 18 | 'in_edit' => true, 19 | 'in_list' => true, 20 | ]), 21 | ]); 22 | 23 | $generator = new FileReplacements($crud); 24 | 25 | $resource = $generator->retrieveFileGenerator('list'); 26 | 27 | $resource->setReplacements($generator->getReplacementsForListPage()); 28 | 29 | $model = str($model)->studly(); 30 | 31 | expect($resource->generate()) 32 | ->toContain('App\\Filament\\Resources\\'.$model.'Resource\\Pages;') 33 | ->toContain("class List{$model}s extends ListRecord") 34 | ->toContain("protected static string \$resource = {$model}Resource::class;") 35 | ->toContain("Actions\CreateAction::make(),"); 36 | })->with([ 37 | 'Post', 38 | 'Item', 39 | 'Country Type', 40 | 'Country_type', 41 | ]); 42 | -------------------------------------------------------------------------------- /tests/Feature/Filament3/Files/ResourceFileTest.php: -------------------------------------------------------------------------------- 1 | $model, 11 | ])) 12 | ->setRelation('fields', [ 13 | new CrudField([ 14 | 'key' => $field, 15 | 'label' => str($field)->ucfirst(), 16 | 'type' => CrudFieldTypes::TEXT, 17 | 'in_create' => true, 18 | 'in_edit' => true, 19 | 'in_list' => true, 20 | ]), 21 | ]); 22 | 23 | $generator = new FileReplacements($crud); 24 | 25 | $resource = $generator->retrieveFileGenerator('resource'); 26 | 27 | $resource->setReplacements($generator->getReplacementsForResource()); 28 | 29 | $model = str($model)->studly(); 30 | 31 | expect($resource->generate()) 32 | ->toContain('namespace App\\Filament\\Resources;') 33 | ->toContain("use App\\Models\\{$model};") 34 | ->toContain("class {$model}Resource extends Resource") 35 | ->toContain("protected static ?string \$model = {$model}::class;") 36 | ->toContain("Forms\Components\TextInput::make('{$field}')") 37 | ->toContain("TextInput::make('{$field}')") 38 | ->not->toContain('protected static ?string $navigationIcon'); 39 | })->with([ 40 | [ 41 | 'Post', 42 | 'title', 43 | ], 44 | [ 45 | 'Item', 46 | 'name', 47 | ], 48 | [ 49 | 'Country Type', 50 | 'name', 51 | ], 52 | [ 53 | 'Country_type', 54 | 'name', 55 | ], 56 | ]); 57 | 58 | it('can assign icon to the resource', function ($model, $field) { 59 | $crud = (new Crud([ 60 | 'title' => $model, 61 | 'icon' => \App\Enums\HeroIcons::O_USERS, 62 | ])) 63 | ->setRelation('fields', [ 64 | new CrudField([ 65 | 'key' => $field, 66 | 'label' => str($field)->ucfirst(), 67 | 'type' => CrudFieldTypes::TEXT, 68 | 'in_create' => true, 69 | 'in_edit' => true, 70 | 'in_list' => true, 71 | ]), 72 | ]); 73 | 74 | $generator = new FileReplacements($crud); 75 | 76 | $resource = $generator->retrieveFileGenerator('resource'); 77 | 78 | $resource->setReplacements($generator->getReplacementsForResource()); 79 | 80 | expect($resource->generate()) 81 | ->toContain('protected static ?string $navigationIcon = \''.\App\Enums\HeroIcons::O_USERS->value.'\''); 82 | })->with([ 83 | [ 84 | 'Post', 85 | 'title', 86 | ], 87 | [ 88 | 'Item', 89 | 'name', 90 | ], 91 | [ 92 | 'Country Type', 93 | 'name', 94 | ], 95 | [ 96 | 'Country_type', 97 | 'name', 98 | ], 99 | ]); 100 | -------------------------------------------------------------------------------- /tests/Feature/Panel/PanelTest.php: -------------------------------------------------------------------------------- 1 | create(); 14 | 15 | actingAs($user); 16 | 17 | artisan('db:seed'); 18 | 19 | livewire(CreatePanelPage::class) 20 | ->fillForm([ 21 | 'name' => 'Test Panel', 22 | ]) 23 | ->assertHasNoFormErrors() 24 | ->call('register'); 25 | 26 | expect(Panel::where('name', 'Test Panel')->exists()) 27 | ->toBeTrue(); 28 | }); 29 | 30 | it('panel created job installs base module correctly', function () { 31 | $user = User::factory()->create(); 32 | 33 | actingAs($user); 34 | 35 | artisan('db:seed'); 36 | 37 | $panel = Panel::create([ 38 | 'name' => 'Test Panel', 39 | 'user_id' => auth()->id(), 40 | 'type' => PanelTypes::FILAMENT3, 41 | ]); 42 | 43 | dispatch(new PanelCreatedJob($panel)); 44 | 45 | $panel = $panel->fresh(); 46 | 47 | expect($panel->modules()->count())->toBe(0) 48 | ->and($panel->cruds()->count())->toBe(0); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Expectations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | When you're writing tests, you often need to check that values meet certain conditions. The 25 | | "expect()" function gives you access to a set of "expectations" methods that you can use 26 | | to assert different things. Of course, you may extend the Expectation API at any time. 27 | | 28 | */ 29 | 30 | expect()->extend('toBeOne', function () { 31 | return $this->toBe(1); 32 | }); 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Functions 37 | |-------------------------------------------------------------------------- 38 | | 39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 40 | | project that you don't want to repeat in every file. Here you can also expose helpers as 41 | | global functions to help you to reduce the number of lines of code in your test files. 42 | | 43 | */ 44 | 45 | function something() 46 | { 47 | // .. 48 | } 49 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 5 | }); 6 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: [ 8 | 'resources/css/app.css', 9 | 'resources/js/app.js', 10 | ], 11 | refresh: true, 12 | }), 13 | ], 14 | }); 15 | --------------------------------------------------------------------------------