Compare commits

...

477 Commits
1.0.2 ... 3.0.5

Author SHA1 Message Date
Dennis
2af546643e Merge branch 'develop' 2022-09-19 11:07:42 +02:00
Dennis
e63b13e5fd package updates 2022-09-19 11:07:35 +02:00
Dennis
36385c2242 wip 2022-09-11 08:41:00 +02:00
Dennis
ed67a44f5f wip 2022-09-10 16:51:00 +02:00
Dennis
fb8b2fa935 Merge branch 'develop' 2022-09-10 16:50:21 +02:00
Dennis
6e1b7613e4 bugfix 2022-09-10 16:50:08 +02:00
Dennis
175b104ebc wip 2022-08-30 10:09:06 +02:00
Dennis
a72a2466ef Merge branch 'develop' 2022-08-30 09:57:53 +02:00
Dennis
9952e2226d Fix 2022-08-30 09:57:47 +02:00
Dennis
5fa9c334ee prod 2022-08-27 20:04:00 +02:00
Dennis
d1f50c8dd9 prod 2022-08-27 20:03:25 +02:00
Dennis
557cff5e05 Merge branch 'develop' 2022-08-27 20:02:51 +02:00
Dennis
fd1d31d347 fix 2022-08-27 20:02:31 +02:00
Dennis
71d5521ca8 wip 2022-08-19 10:34:24 +02:00
Dennis
489d39099f wip 2022-08-19 10:16:54 +02:00
Dennis
cbd6a227ee Merge branch 'develop'
# Conflicts:
#	public/build/assets/app.1ce28008.css
#	public/js/1535.js
#	public/js/1587.js
#	public/js/1603.js
#	public/js/1970.js
#	public/js/1984.js
#	public/js/2035.js
#	public/js/2502.js
#	public/js/2669.js
#	public/js/2741.js
#	public/js/3067.js
#	public/js/344.js
#	public/js/3495.js
#	public/js/3506.js
#	public/js/3686.js
#	public/js/4104.js
#	public/js/4282.js
#	public/js/4482.js
#	public/js/4598.js
#	public/js/4627.js
#	public/js/4710.js
#	public/js/4967.js
#	public/js/5035.js
#	public/js/5168.js
#	public/js/547.js
#	public/js/5503.js
#	public/js/5597.js
#	public/js/5727.js
#	public/js/5867.js
#	public/js/630.js
#	public/js/6341.js
#	public/js/6366.js
#	public/js/6658.js
#	public/js/6734.js
#	public/js/6887.js
#	public/js/696.js
#	public/js/6974.js
#	public/js/7007.js
#	public/js/7022.js
#	public/js/7319.js
#	public/js/7414.js
#	public/js/7644.js
#	public/js/7790.js
#	public/js/7942.js
#	public/js/8162.js
#	public/js/8399.js
#	public/js/8502.js
#	public/js/8665.js
#	public/js/8899.js
#	public/js/8968.js
#	public/js/9039.js
#	public/js/9049.js
#	public/js/9053.js
#	public/js/917.js
#	public/js/9202.js
#	public/js/9488.js
#	public/js/9617.js
#	public/js/9737.js
#	public/js/9972.js
#	public/js/9989.js
#	public/js/9991.js
#	public/js/app.js
2022-08-19 09:39:27 +02:00
Dennis
b3d2b2ec7a demo fix 2022-08-19 09:38:37 +02:00
Dennis
2889e46685 wip 2022-08-19 09:31:12 +02:00
Dennis
761878e1b6 w 2022-08-19 09:30:44 +02:00
Dennis
4169b07b39 wip 2022-08-19 09:28:50 +02:00
Dennis
8c001cf984 wip 2022-08-19 09:25:01 +02:00
Dennis
a73adb9acf wip 2022-08-18 13:34:40 +02:00
Dennis
6ed9867f6b make this 8.1 default 2022-08-17 10:45:29 +02:00
Dennis
b1667ff445 wip 2022-08-17 09:59:02 +02:00
Dennis
db4a25fb5c fix 2022-08-17 09:55:29 +02:00
Dennis
ee21924253 fix tests 2022-08-17 09:53:08 +02:00
Dennis
37ea8aa6b0 remove old files 2022-08-17 09:33:12 +02:00
Dennis
d4c495eaee wip 2022-08-17 09:32:33 +02:00
Dennis
434046cd3e PSR formatting 2022-08-17 08:20:40 +02:00
Ralph J. Smit
eee61494d0 Fix policies interfering with admin 2022-08-16 14:27:35 +02:00
Ralph J. Smit
053864a589 Clarify that servers are also deleted 2022-08-16 13:20:55 +02:00
Ralph J. Smit
dc87d0d415 Allow completely deleting a user with all the sites etc. 2022-08-16 13:20:23 +02:00
Ralph J. Smit
ae1b41e068 Add language and requires_pasword_for_ftp to User API endpoints 2022-08-16 13:19:53 +02:00
Ralph J. Smit
277aae4bca Fix polling resets didn't preserve the form state 2022-08-16 13:19:20 +02:00
Dennis
f727c4cddb wip 2022-08-16 08:22:29 +02:00
Dennis
58c91fe7bc wip 2022-08-16 08:15:05 +02:00
Dennis
cc29f729d3 Force 8.1 2022-08-15 13:48:37 +02:00
Dennis
ff8982c2ee w 2022-08-15 10:06:14 +02:00
Dennis
aab1e86eee Wip 2022-08-15 09:29:47 +02:00
Ralph J. Smit
3388b4fdec Add packages API endpoint 2022-08-12 22:20:56 +02:00
Ralph J. Smit
8ef6e2b64c Update ServerResource.php 2022-08-12 21:14:59 +02:00
Ralph J. Smit
dd5c074976 Rework title components 2022-08-12 21:11:23 +02:00
Ralph J. Smit
9861ff3a9b Rescue Horizon exceptions 2022-08-09 11:06:39 +02:00
Ralph J. Smit
b4607d8e01 Update SiteResource.php 2022-08-09 11:00:44 +02:00
Dennis
7a98295853 wip 2022-08-09 09:23:31 +02:00
Dennis
95e7682cc2 wip 2022-08-09 09:18:37 +02:00
Dennis
251c29b4bd wip 2022-08-09 09:13:42 +02:00
Ralph J. Smit
96587db6a2 Update synchronize actions in main index pages, hide for busy servers/sites to prevent exceptions 2022-08-08 17:31:18 +02:00
Ralph J. Smit
06aecba93d Add a site relation managers 2022-08-08 17:26:42 +02:00
Ralph J. Smit
bcadd716fe Extract synchronization logic into separate pages 2022-08-08 17:21:58 +02:00
Ralph J. Smit
b2e1ee9e24 Finish reset db password 2022-08-08 12:07:01 +02:00
Ralph J. Smit
e3612ebf23 Merge branch 'V3' of https://github.com/ploi-deploy/ploi-core into V3 2022-08-08 11:57:48 +02:00
Ralph J. Smit
25d5a9617d WIP password reset 2022-08-08 11:57:37 +02:00
Dennis
b6e25806ea Merge branch 'V3' of https://github.com/ploi-deploy/ploi-core into V3 2022-08-08 11:53:59 +02:00
Dennis
f0a8e5e318 wip 2022-08-08 11:53:55 +02:00
Ralph J. Smit
c5dbfa2c4b Reset TwoFactorAuthentication 2022-08-08 11:09:13 +02:00
Ralph J. Smit
93273b5a45 Refactor to $touches 2022-08-08 10:20:08 +02:00
Ralph J. Smit
751449de5e Add Support ticket badge 2022-08-08 10:19:49 +02:00
Ralph J. Smit
e601222b4f Tweak system page design 2022-08-06 17:32:12 +02:00
Ralph J. Smit
30685c4595 Remove files related to V2 admin panel 2022-08-06 17:24:24 +02:00
Ralph J. Smit
60c951a1f8 Build files 2022-08-06 17:20:41 +02:00
Ralph J. Smit
ccc09f0967 Replace inertia link to dashboard with regular link 2022-08-06 17:19:08 +02:00
Ralph J. Smit
de9834d6a7 Upgrade to Vue 3 & use correct Inertia createApp 2022-08-06 17:11:57 +02:00
Ralph J. Smit
09dd3db506 Update GitHub actions script for Filament stylesheet 2022-08-06 15:46:06 +02:00
Ralph J. Smit
560100e592 Build assets 2022-08-06 15:45:28 +02:00
Ralph J. Smit
0f5ef71936 Create manifest.json 2022-08-06 15:43:19 +02:00
Ralph J. Smit
84b2f36440 Fix markdown support ticket 2022-08-06 15:37:37 +02:00
Ralph J. Smit
92c605d9df Cleanup 2022-08-06 15:36:36 +02:00
Ralph J. Smit
0fddf4f348 Support ticket system 2022-08-06 15:36:27 +02:00
Ralph J. Smit
ef64bdd7b3 Update DocumentationItemResource.php 2022-08-06 15:35:57 +02:00
Ralph J. Smit
465b2a524b Update setting() function to support storing multiple settings at the same time 2022-08-06 15:35:45 +02:00
Ralph J. Smit
f2c47ba2f3 Update icons and order 2022-08-05 18:29:18 +02:00
Ralph J. Smit
eab8c45f57 Update DocumentationCategory.php 2022-08-05 18:28:21 +02:00
Ralph J. Smit
2215e12717 Fix empty logo 2022-08-05 18:28:18 +02:00
Ralph J. Smit
f76e5b4d7b Add documentation functionality 2022-08-05 18:28:10 +02:00
Ralph J. Smit
9d1b12b0a3 Admin panel v1 2022-08-05 18:11:23 +02:00
Ralph J. Smit
d89482c4aa WIP 2022-08-01 20:54:52 +02:00
Ralph J. Smit
488808a7a2 WIP 2022-07-30 20:48:39 +02:00
Dennis
8e788dfd8e wip 2022-07-29 08:53:10 +02:00
Dennis
200d2bc44d wip 2022-07-29 08:47:02 +02:00
Dennis
f9424781c0 w 2022-07-29 08:42:34 +02:00
Dennis
f2441990cd w 2022-07-27 13:39:11 +02:00
Dennis
369d205d6a wip 2022-07-27 13:36:18 +02:00
Dennis
b63d4e753b Merge branch '120-move-mix-to-vite' into V3
# Conflicts:
#	composer.lock
#	package.json
#	public/css/app.css
#	public/js/app.js
#	resources/deprecated/Admin/Alerts/Create.vue
#	resources/deprecated/Admin/Alerts/Edit.vue
#	resources/deprecated/Admin/Alerts/Index.vue
#	resources/deprecated/Admin/Documentation/Articles/Create.vue
#	resources/deprecated/Admin/Documentation/Articles/Edit.vue
#	resources/deprecated/Admin/Documentation/Articles/Index.vue
#	resources/deprecated/Admin/Documentation/Create.vue
#	resources/deprecated/Admin/Documentation/Edit.vue
#	resources/deprecated/Admin/Documentation/Index.vue
#	resources/deprecated/Admin/Packages/Create.vue
#	resources/deprecated/Admin/Packages/Edit.vue
#	resources/deprecated/Admin/Packages/Index.vue
#	resources/deprecated/Admin/Servers/Index.vue
#	resources/deprecated/Admin/Services/Index.vue
#	resources/deprecated/Admin/Services/Provider/Edit.vue
#	resources/deprecated/Admin/Services/Providers.vue
#	resources/deprecated/Admin/Services/Server/Edit.vue
#	resources/deprecated/Admin/Services/Servers.vue
#	resources/deprecated/Admin/Services/Site/Edit.vue
#	resources/deprecated/Admin/Services/Sites.vue
#	resources/deprecated/Admin/Sites/Index.vue
#	resources/deprecated/Admin/Support/Index.vue
#	resources/deprecated/Admin/Support/Show.vue
#	resources/deprecated/Admin/Terms.vue
#	resources/deprecated/Admin/Users/Create.vue
#	resources/deprecated/Admin/Users/Edit.vue
#	resources/deprecated/Admin/Users/Index.vue
#	resources/deprecated/Admin/Users/Show.vue
#	resources/js/Pages/Sites/Dns.vue
2022-07-27 13:30:56 +02:00
Dennis
fbe7641cb7 wip 2022-07-27 13:30:13 +02:00
Ralph J. Smit
1ebcf75806 Improve DNS editor 2022-07-27 12:31:15 +02:00
Ralph J. Smit
88b324b47a WIP 2022-07-26 21:32:56 +02:00
Dennis
50768d5648 Merge branch 'develop' 2022-07-24 12:51:08 +02:00
Ralph J. Smit
7cfe8d64c4 Updates to endpoints 2022-07-23 16:42:23 +02:00
Ralph J. Smit
f6f5385751 Update .gitignore 2022-07-23 16:41:58 +02:00
Dennis Smink
62084f590e wip 2022-07-21 15:24:59 +02:00
Dennis Smink
c7edf262f6 Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/1042.js
#	public/js/1304.js
#	public/js/1438.js
#	public/js/1582.js
#	public/js/1973.js
#	public/js/2232.js
#	public/js/2306.js
#	public/js/2426.js
#	public/js/2658.js
#	public/js/2668.js
#	public/js/2693.js
#	public/js/2940.js
#	public/js/2999.js
#	public/js/3038.js
#	public/js/3292.js
#	public/js/3739.js
#	public/js/4144.js
#	public/js/43.js
#	public/js/4379.js
#	public/js/4485.js
#	public/js/4596.js
#	public/js/4695.js
#	public/js/47.js
#	public/js/4710.js
#	public/js/4766.js
#	public/js/4791.js
#	public/js/4804.js
#	public/js/4946.js
#	public/js/5641.js
#	public/js/565.js
#	public/js/5766.js
#	public/js/5865.js
#	public/js/6038.js
#	public/js/6282.js
#	public/js/6289.js
#	public/js/6340.js
#	public/js/6424.js
#	public/js/6511.js
#	public/js/6545.js
#	public/js/684.js
#	public/js/6861.js
#	public/js/701.js
#	public/js/7054.js
#	public/js/7210.js
#	public/js/7399.js
#	public/js/7611.js
#	public/js/7767.js
#	public/js/8027.js
#	public/js/8309.js
#	public/js/8371.js
#	public/js/8607.js
#	public/js/8786.js
#	public/js/883.js
#	public/js/908.js
#	public/js/9195.js
#	public/js/9281.js
#	public/js/9353.js
#	public/js/app.js
2022-07-21 15:24:23 +02:00
Ralph J. Smit
4294377160 WIP 2022-07-20 16:53:18 +02:00
Ralph J. Smit
1ae9519594 WIP 2022-07-20 16:47:01 +02:00
Ralph J. Smit
c0f7d572cb Update GitHub Actions trigger 2022-07-20 16:06:01 +02:00
Ralph J. Smit
247fd27368 Merge branch 'develop-test' of https://github.com/ploi-deploy/ploi-core into develop-test 2022-07-20 16:05:13 +02:00
GitHub Actions
39e5d20444 Run Laravel Mix en build front-end assets 2022-07-20 14:04:52 +00:00
Ralph J. Smit
8900e5088a Update master.yml 2022-07-20 16:03:59 +02:00
Ralph J. Smit
e52cc6205a Revert "Dummy change to trigger GH Action"
This reverts commit bffaadec1b.
2022-07-20 16:03:15 +02:00
GitHub Actions
a85be2d666 Run Laravel Mix en build front-end assets 2022-07-20 14:02:51 +00:00
Ralph J. Smit
bffaadec1b Dummy change to trigger GH Action 2022-07-20 16:01:22 +02:00
GitHub Actions
0c7398c7ac Run Laravel Mix en build front-end assets 2022-07-20 13:59:07 +00:00
Ralph J. Smit
a44ce140fc Fix test 2022-07-20 15:57:25 +02:00
Ralph J. Smit
8afe483cee Update PHP 8.0 compatible lock file 2022-07-20 15:55:09 +02:00
Ralph J. Smit
9bd3107303 Node 14 for Vite 2022-07-20 15:53:30 +02:00
Ralph J. Smit
5b124cc433 Update master.yml 2022-07-20 15:52:36 +02:00
Ralph J. Smit
661426668e Update .gitignore 2022-07-20 15:52:34 +02:00
Ralph J. Smit
2df37131d1 Build assets #1 2022-07-20 15:49:48 +02:00
Ralph J. Smit
c55b2f1f85 Merge 2022-07-20 15:49:25 +02:00
Ralph J. Smit
705739d2d3 Merge branch '120-move-mix-to-vite' into develop-test 2022-07-20 15:47:01 +02:00
Ralph J. Smit
35bf27097a Update app.blade.php 2022-07-20 15:44:46 +02:00
Ralph J. Smit
59d60a5b03 Import CSS in JS 2022-07-20 15:40:40 +02:00
Ralph J. Smit
0cd7c7e4f6 Remove Mix files 2022-07-20 15:37:55 +02:00
Ralph J. Smit
254dbaf2ec Fix page title 2022-07-20 15:36:49 +02:00
Ralph J. Smit
705104524e Fix typo 2022-07-20 15:36:46 +02:00
Ralph J. Smit
a7da48a5dd Fix undefined TabBars error 2022-07-20 15:36:35 +02:00
Ralph J. Smit
df87d541cd Fix Inertia, except TabBars 2022-07-20 15:27:47 +02:00
Ralph J. Smit
07ba298c5e WIP Vite 2022-07-20 14:50:11 +02:00
Ralph J. Smit
1ce6e8cace Fix test 2022-07-20 10:51:43 +02:00
Dennis Smink
57f783490b ww 2022-07-20 08:26:14 +02:00
Dennis Smink
eaab262629 Merge branch '61-ploi-core-design' into develop
# Conflicts:
#	public/js/app.js
2022-07-20 08:26:10 +02:00
Dennis Smink
e2e05f9cbf wip 2022-07-20 08:25:33 +02:00
Dennis Smink
24ce8bc60d Merge branch '26-duplicate-check' into develop
# Conflicts:
#	app/Http/Controllers/SiteController.php
#	package-lock.json
#	public/js/app.js
2022-07-20 08:17:59 +02:00
Dennis Smink
75592aaeb2 Do this check server sided 2022-07-20 08:16:42 +02:00
Dennis Smink
c11ad19220 w 2022-07-20 08:08:09 +02:00
Dennis Smink
db799a7d6a w 2022-07-20 08:06:53 +02:00
Dennis Smink
7fea371857 Merge branch '25-add-ssl-for-alias-domains-automatically' into develop
# Conflicts:
#	public/js/app.js
2022-07-20 08:06:48 +02:00
Dennis Smink
d7b3899e71 Few fixes 2022-07-20 08:06:37 +02:00
Dennis Smink
e2886fb67e Merge branch '131-set-up-github-action-for-ploi-core' into develop 2022-07-20 07:56:19 +02:00
Ralph J. Smit
8936e4c2d5 Update package-lock.json 2022-07-19 21:46:38 +02:00
Ralph J. Smit
92bb321a68 WIP 2022-07-19 21:46:33 +02:00
Ralph J. Smit
57c8997dd0 Fix 2022-07-19 21:32:14 +02:00
Ralph J. Smit
2df031a60f Check whether domain already exists when creating site 2022-07-19 21:17:21 +02:00
Ralph J. Smit
77384a1abe Request new certificate automatically after creating new alias 2022-07-19 19:04:01 +02:00
Ralph J. Smit
7b20082537 Implement real usernames for site system users and database name site prefix 2022-07-19 15:58:50 +02:00
Ralph J. Smit
3531e4b296 Update master.yml 2022-07-19 15:21:03 +02:00
Ralph J. Smit
4e92501985 Update master.yml 2022-07-19 15:17:20 +02:00
GitHub Actions
d7632d8289 Run Laravel Mix en build front-end assets 2022-07-19 13:12:48 +00:00
Ralph J. Smit
f404b3e9c6 Update master.yml 2022-07-19 15:11:01 +02:00
Ralph J. Smit
730f9b7451 Update run-tests.yml 2022-07-19 15:09:40 +02:00
Ralph J. Smit
2fe5fd70c9 PHP 8.0 support 2022-07-19 15:07:38 +02:00
Ralph J. Smit
6afe8738df Consistent scripts 2022-07-19 14:50:28 +02:00
Ralph J. Smit
761a940abd Add support for PHP 8.0 back 2022-07-19 14:49:08 +02:00
Ralph J. Smit
f87c1dd5ee Update run-tests.yml 2022-07-19 14:45:06 +02:00
Ralph J. Smit
de70310c90 Update run-tests.yml 2022-07-19 14:43:50 +02:00
Ralph J. Smit
11f9b1ed48 Update run-tests.yml 2022-07-19 14:42:28 +02:00
Ralph J. Smit
060a6b72a7 Update master.yml 2022-07-19 14:42:00 +02:00
Ralph J. Smit
70cc81f110 Update run-tests.yml 2022-07-19 14:41:26 +02:00
Ralph J. Smit
bcc1a9b9a8 Update master.yml 2022-07-19 14:39:10 +02:00
Ralph J. Smit
1c601a6efd Update master.yml 2022-07-19 14:36:42 +02:00
Ralph J. Smit
fee31d03a7 Update master.yml 2022-07-19 14:35:14 +02:00
Ralph J. Smit
b09dc1ba9d Update run-tests.yml 2022-07-19 14:35:11 +02:00
Ralph J. Smit
dae15c620b Update run-tests.yml 2022-07-19 14:31:36 +02:00
Ralph J. Smit
996a048a76 Update phpunit.xml 2022-07-19 14:30:46 +02:00
Ralph J. Smit
8c20f23dfd Update Pest.php 2022-07-19 14:27:27 +02:00
Ralph J. Smit
c80818df4c Update phpunit.xml 2022-07-19 14:19:10 +02:00
Ralph J. Smit
d5e77ae31f Update master.yml 2022-07-19 13:34:03 +02:00
Ralph J. Smit
a14d2c44a1 wip 2022-07-19 13:33:24 +02:00
Ralph J. Smit
3048747ed6 WIP 2022-07-18 22:45:22 +02:00
Ralph J. Smit
20bf6c4784 Compatibility with Http-facade instead of Guzzle 2022-07-18 22:42:24 +02:00
Ralph J. Smit
9b02be5be1 Update SiteController.php 2022-07-18 22:42:04 +02:00
Ralph J. Smit
d141503b6f Remove unused class 2022-07-18 22:26:31 +02:00
Ralph J. Smit
6a8e4e8edf Move Http-tests to Tests/Feature 2022-07-18 22:20:42 +02:00
Ralph J. Smit
89bbf44b3b Finish API-endpoints & tests 2022-07-18 22:19:45 +02:00
Ralph J. Smit
db1f40bf6f WIP 2022-07-16 21:44:33 +02:00
Ralph J. Smit
5933a06dd3 WIP 2022-07-05 18:50:34 +02:00
Ralph J. Smit
3c510906ee General stuff 2022-07-01 21:31:35 +02:00
Ralph J. Smit
b43f4cf292 Server testing & endpoints, general 2022-07-01 21:31:27 +02:00
Ralph J. Smit
9cc046eeed Site testing & endpoints 2022-07-01 21:30:36 +02:00
Ralph J. Smit
8291ac6714 Test and update UserController endpoint 2022-07-01 21:17:53 +02:00
Ralph J. Smit
c578ee70c0 Prepare Pest 2022-07-01 21:17:22 +02:00
Ralph J. Smit
90501e37fd Prepare data objects (hopefully remove some code after PR accepted) 2022-07-01 21:15:59 +02:00
Ralph J. Smit
ec45b0dac0 Update RouteServiceProvider.php 2022-07-01 12:01:33 +02:00
Ralph J. Smit
34b838c259 Implement & test API-authentication, simplify Api-routes 2022-07-01 12:00:16 +02:00
Dennis
28ffc8e240 Merge branch '129-add-user-friendly-404-pages' into develop 2022-07-01 07:53:44 +02:00
Dennis
c9179fbf90 proper mix 2022-07-01 07:52:51 +02:00
Dennis
fe5268971a Merge branch '114-refresh-system-version-in-system-tab' into develop
# Conflicts:
#	public/js/app.js
2022-07-01 07:52:21 +02:00
Dennis
8c246e2dba wip tests 2022-07-01 07:51:27 +02:00
Dennis
81fcfac803 Merge branch '76-the-use-of-html-tags-or-markdown-in-alert-messages' into develop
# Conflicts:
#	public/js/app.js
2022-07-01 07:50:17 +02:00
Ralph J. Smit
ff22b96a8d Fix 404 and 403 Vue error pages 2022-06-30 18:47:11 +02:00
Ralph J. Smit
4a2faf0bce Build files 2022-06-30 18:18:34 +02:00
Ralph J. Smit
5c39d07bf5 Allow version refresh in System 2022-06-30 18:14:54 +02:00
Ralph J. Smit
9870aec79f Support markdown & line breaks in Alert messages 2022-06-30 16:11:18 +02:00
Dennis
833a03e992 dev 2022-06-30 16:02:11 +02:00
Ralph J. Smit
e074ab5be4 Implement two-factor authentication 2022-06-30 15:26:44 +02:00
Dennis
b5963693e6 Prod mix 2022-06-27 10:26:40 +02:00
Dennis
1b7ea67fde Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2022-06-27 10:25:39 +02:00
Dennis
d4f2b9839e package updates 2022-06-27 10:25:10 +02:00
Dennis
817f6a175c wip 2022-06-27 10:23:57 +02:00
Dennis
b3619e5941 wip 2022-06-20 09:14:56 +02:00
Dennis
33784410e5 wip trial 2022-06-07 13:36:49 +02:00
Dennis
6ecf7904fe wip roadmap 2022-06-07 11:31:39 +02:00
Dennis
21986f2394 add ability to run octane 2022-06-07 11:00:56 +02:00
Dennis
4d8212e56f package updates and add aws for ses emailing 2022-06-07 10:58:38 +02:00
Dennis
865f2958cf Merge branch 'develop' 2022-05-23 16:43:39 +02:00
Dennis
17890d13ad Merge branch 'develop' of https://github.com/ploi-deploy/ploi-core into develop 2022-05-23 16:43:28 +02:00
Dennis
2d33455731 Closes #1 2022-05-23 16:43:15 +02:00
Dennis
49481f9b6a Prod mix 2022-04-14 12:39:01 +02:00
Dennis
7bb800cc0a Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2022-04-14 12:38:06 +02:00
Dennis
1b8c2c764f fx 2022-04-14 12:37:39 +02:00
Dennis
cb1a1c4c06 w 2022-04-12 10:18:47 +02:00
Dennis
ddd80a8687 site email 2022-04-11 19:13:40 +02:00
Dennis
010d4569c2 package updates 2022-04-11 18:54:44 +02:00
Dennis
62ae0f8299 wip 2022-04-09 19:06:17 +02:00
Dennis
2a3d9cabd0 wip 2022-04-09 07:54:47 +02:00
Dennis
e2a58cf2df tweaks 2022-04-07 14:48:45 +02:00
Dennis
0fd6db251b remove these 2022-04-07 14:47:31 +02:00
Dennis
94d50c11ef wip 2022-04-07 14:46:56 +02:00
Dennis
14c6faafa2 wip 2022-04-07 14:21:57 +02:00
Dennis
ea21076eda wip 2022-04-07 11:15:06 +02:00
Dennis
d378323602 wip 2022-04-07 10:52:42 +02:00
Dennis
f074dee990 wip 2022-04-05 14:59:27 +02:00
Dennis
7bd2917ec4 wip 2022-04-01 10:50:05 +02:00
Dennis
823a39ffa2 prod mix 2022-03-23 09:58:27 +01:00
Dennis
6953a8d2b2 Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2022-03-23 09:58:01 +01:00
Dennis
fb40b450b6 BRL 2022-03-23 09:54:11 +01:00
Dennis
a28c053945 wip 2022-03-23 09:40:29 +01:00
Dennis
ea0c4ed66d wip 2022-03-23 09:06:28 +01:00
Dennis
7a0716959a Laravel 9 upgrade 2022-03-01 11:21:58 +01:00
Dennis
da322d7b1e prod mix 2022-01-21 13:57:39 +01:00
Dennis
bbff8a5403 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2022-01-21 13:57:04 +01:00
Dennis
c1ad600042 Ability to select database type, package updates, bugfixes 2022-01-21 13:56:48 +01:00
Dennis
5fc32759fd prod mix 2022-01-19 14:29:36 +01:00
Dennis
59cfd8d71b Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2022-01-19 14:28:00 +01:00
Dennis
51d5b89df7 wip 2022-01-19 14:26:08 +01:00
Dennis
7c0dcbeb88 TW3.0 upgrade 2021-12-24 16:30:41 +01:00
Dennis
b4467f8d5b Package updates 2021-12-21 09:04:54 +01:00
Dennis
363f4ed801 default to predis here 2021-12-21 08:58:25 +01:00
Dennis
1b6b950fb5 Bugfixing 2021-12-21 08:57:55 +01:00
Dennis
90988f1538 Prod mix 2021-12-19 21:58:01 +01:00
Dennis
139ba793d0 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-12-19 21:57:32 +01:00
Dennis
c505dd0924 PSR 2021-12-19 21:57:23 +01:00
Dennis
ca5ee33978 Bugfix in terms page, made system available in demo 2021-12-19 21:57:10 +01:00
Dennis
c2fd5e3fa9 Prod mix 2021-12-07 15:20:10 +01:00
Dennis
b04176ce48 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-12-07 15:19:25 +01:00
Dennis
5136a4b9f8 Bugfix 2021-12-07 15:18:35 +01:00
Dennis
dcee703aa1 wip 2021-11-30 13:30:21 +01:00
Dennis
7c504339d9 Added favicon if logo is uploaded 2021-11-30 13:18:10 +01:00
Dennis
5ea0761fe9 prod mix 2021-11-24 11:25:14 +01:00
Dennis
c9125c3be8 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-11-24 11:22:00 +01:00
Dennis
84503c19db Ability to revoke card & DNS fix on site show 2021-11-24 11:20:24 +01:00
Dennis
cfd9eba5d7 Ability to remove logo and pagination to system logs 2021-11-19 14:14:20 +01:00
Dennis
10689d3d12 Ability to rotate logs in system 2021-11-19 13:52:32 +01:00
Dennis
e190fb7805 prod mix 2021-11-02 11:44:32 +01:00
Dennis
c1351f7d28 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-11-02 11:43:24 +01:00
Dennis
01f5469e86 Generic fixes 2021-11-02 11:43:10 +01:00
Dennis
8c5c86eb6a Preserve scroll by default 2021-11-02 11:26:53 +01:00
Dennis
995ada46aa Prod mix 2021-09-27 09:51:05 +02:00
Dennis
2e79381872 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-09-27 09:50:24 +02:00
Dennis
d1c7b9a418 Fixes 2021-09-27 09:49:42 +02:00
Dennis
34da2f563d Ability to configure per page number 2021-09-23 14:22:01 +02:00
Dennis
5cf77fde1c Package updates 2021-09-23 08:28:54 +02:00
Dennis
aabf6f27ac Ran PSR formatter 2021-09-23 08:27:27 +02:00
Dennis
fbcaee3bdc prod mix 2021-09-23 08:26:39 +02:00
Dennis
3082c10cdb Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-09-23 08:26:03 +02:00
Dennis
a4d90f0017 OG image updatee readme 2021-09-23 08:25:52 +02:00
Dennis
8adfc9837e fixes 2021-09-23 08:20:22 +02:00
Dennis
aa20c8a42b prod mix 2021-09-16 09:51:17 +02:00
Dennis
0f100d6159 Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2021-09-16 09:50:07 +02:00
Dennis
7c896243a5 wip 2021-09-16 09:49:53 +02:00
Dennis
954fef7c3e Dynamic servers tab, fix for domain uppercase 2021-09-16 09:19:41 +02:00
Dennis
105126e498 prod mix 2021-08-25 09:20:18 +02:00
Dennis
7aa5a8949d Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-08-25 09:19:45 +02:00
Dennis
27079928a7 added demo quick-login support 2021-08-25 09:19:10 +02:00
Dennis
99968e57ec wip 2021-08-17 13:00:47 +02:00
Dennis
f75bc1a551 Production mix 2021-08-12 11:25:19 +02:00
Dennis
ef347f9381 Merge branch 'develop' 2021-08-12 11:24:30 +02:00
Dennis
f1aace3d8f Ability to select allowed regions/plans per provider 2021-08-12 11:23:01 +02:00
Dennis
b2bec62766 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-08-12 11:03:51 +02:00
Dennis
3a8682bbed Textual change 2021-08-12 11:03:25 +02:00
Dennis
5cfc1f97fe Logo fix 2021-08-12 10:26:29 +02:00
Dennis
cfc8220f8e Wip 2021-08-12 10:23:23 +02:00
Dennis
ac5ffefaed Horizon status to system settings tab 2021-08-10 14:04:52 +02:00
Dennis
cbd2b8e0e9 Wip on terms & privacy page configurator 2021-08-10 13:51:37 +02:00
Dennis
f0f427a7bb General improvements 2021-08-05 11:31:38 +02:00
Dennis
7b6f651015 Consistent Ploi class instantiate 2021-08-05 11:14:16 +02:00
Dennis
c793daa79a wip 2021-08-05 11:08:19 +02:00
Dennis
71b436aebe Production mix 2021-08-04 10:17:42 +02:00
Dennis
06e108da5b Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-08-04 10:17:03 +02:00
Dennis
5677c58dd2 Package updates 2021-08-04 10:07:36 +02:00
Dennis
58ca801e30 PSR formatting 2021-08-04 09:49:47 +02:00
Dennis
e750d7caba Cleaning 2021-08-04 09:49:24 +02:00
Dennis
3c0964ef0e Bugfixes in server settings, site creation 2021-08-04 09:46:11 +02:00
Dennis
33613cdf1c Sync all sites & servers, bugfix in pagination providers 2021-08-04 09:36:28 +02:00
Dennis
abfe174825 Autofocus on login page 2021-08-04 09:19:40 +02:00
Dennis
c750f469bb Updates
- Generic demo check in base controller
- Site synchronization
- User show pagination preserve scroll
2021-08-03 11:58:38 +02:00
Dennis
c7ac56d5cc Wip custom certificates 2021-07-29 09:59:20 +02:00
Dennis
6601f44013 wip custom certificates 2021-07-29 09:07:06 +02:00
Dennis
b7baf862b6 prod mix 2021-07-27 08:33:16 +02:00
Dennis
ca79f4cf21 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-07-27 08:32:39 +02:00
Dennis
58ffbe8c74 URL fixes 2021-07-27 08:32:30 +02:00
Dennis
dfa7b995fc wip 2021-07-23 13:46:53 +02:00
Dennis
59c65fe6ee prod 2021-07-18 10:12:47 +02:00
Dennis
087c042c14 Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-07-18 10:12:15 +02:00
Dennis
d1ee488ffd fixes 2021-07-18 10:11:50 +02:00
Dennis
34100bc580 prod mix 2021-07-14 09:51:24 +02:00
Dennis
22d91517fb Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-07-14 09:50:10 +02:00
Dennis
1c088bd4e0 Bugfixes and improvements 2021-07-14 09:50:00 +02:00
Dennis
fd5bbb7f5d Merge branch 'develop' 2021-07-13 14:49:03 +02:00
Dennis
426d39bec0 wip 2021-07-13 14:48:56 +02:00
Dennis
8244c2dfb2 Merge branch 'develop' 2021-07-13 14:23:54 +02:00
Dennis
e7f9d32f68 wip 2021-07-13 14:23:49 +02:00
Dennis
71158a0030 prod 2021-07-12 12:33:07 +02:00
Dennis
573ce14b7a Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/app.js
2021-07-12 12:31:37 +02:00
Dennis
00ef1f470f add debugging 2021-07-12 12:31:19 +02:00
Dennis
a9357034c8 THB currency, annual billing added 2021-07-12 12:26:21 +02:00
Dennis
a45f5157d4 wip 2021-07-09 11:48:12 +02:00
Dennis
0d1009b8db horizon config update 2021-07-09 11:45:57 +02:00
Dennis
4f0416cd45 Added user show, pagination simplified, package updates 2021-07-09 09:08:09 +02:00
Dennis
76655d76d5 Installation command improvements 2021-07-08 14:26:28 +02:00
Dennis
3b38cbe9ac Merge branch 'develop' 2021-06-17 09:18:34 +02:00
Dennis
acf37b8850 Suppport PHP 8 2021-06-17 09:18:28 +02:00
Dennis
f47a0699d3 Merge branch 'develop' 2021-06-07 09:18:21 +02:00
Dennis
8ae429b06b Tweak 2021-06-07 09:18:14 +02:00
Dennis
626dbbcb49 Fix 2021-06-05 20:30:22 +02:00
Dennis
b623dd80fd Merge branch 'develop' 2021-05-31 15:55:32 +02:00
Dennis
3978a7c9f7 User deleting fix 2021-05-31 15:53:58 +02:00
Dennis
7d2acb7438 Merge branch 'develop' 2021-05-31 08:55:21 +02:00
Dennis
096032301c Fix when deleting user with support tickets 2021-05-31 08:55:15 +02:00
Dennis
c3e99bf2ff Merge branch 'develop' 2021-05-25 07:37:34 +02:00
Dennis
9795642bc7 Bugfix 2021-05-25 07:37:28 +02:00
Dennis
4281a432fb Merge branch 'develop' 2021-05-22 09:36:29 +02:00
Dennis
18df5589b1 Fix title 2021-05-22 09:36:25 +02:00
Dennis
79536eac2e Fresh mix 2021-05-19 09:38:13 +02:00
Dennis
6df82fca04 Clean push 2021-05-19 09:36:22 +02:00
Dennis
cc14123d27 Merge branch 'develop' 2021-05-19 09:36:04 +02:00
Dennis
bd5e7b87ff Package update && password uncomprimised 2021-05-19 09:35:54 +02:00
Dennis
42568916d6 prod mix 2021-05-18 12:17:24 +02:00
Dennis
180803cd8a Merge branch 'develop'
# Conflicts:
#	public/js/app.js
2021-05-17 12:10:01 +02:00
Dennis
08fcb0ce01 Transform currencies properly 2021-05-17 12:07:52 +02:00
Dennis
120b4c9df7 prod mix 2021-05-13 20:37:41 +02:00
Dennis
0eb66c10f6 Merge branch 'develop' 2021-05-13 20:37:09 +02:00
Dennis
1a6b14a250 Ran psr formatting 2021-05-13 20:37:03 +02:00
Dennis
072c8569eb added indian rupee 2021-05-13 20:36:41 +02:00
Dennis
5b9378255b Loading state to select country dropdown 2021-05-11 14:59:34 +02:00
Dennis
ac3cf16377 Production mix 2021-05-11 11:55:08 +02:00
Dennis
31154d20f6 wip 2021-05-11 11:54:43 +02:00
Dennis
f8031ac71a Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/2035.js
#	public/js/2407.js
#	public/js/2741.js
#	public/js/4104.js
#	public/js/4627.js
#	public/js/547.js
#	public/js/5727.js
#	public/js/630.js
#	public/js/6341.js
#	public/js/6675.js
#	public/js/6974.js
#	public/js/7438.js
#	public/js/9039.js
#	public/js/9202.js
#	public/js/9737.js
#	public/js/9989.js
#	public/js/app.js
#	public/js/app.js.LICENSE.txt
2021-05-11 11:54:09 +02:00
Dennis
898ec6a454 Stripe billing details used for india regulations 2021-05-11 11:50:41 +02:00
Dennis
9dbd54fdf6 wip 2021-05-07 11:07:48 +02:00
Dennis
934017384d wip 2021-05-07 11:04:38 +02:00
Dennis
aee3dc0d93 Merge branch 'develop' 2021-05-05 15:50:42 +02:00
Dennis
8046687ae7 Bugfix for settings 2021-05-05 15:49:05 +02:00
Dennis
a89ee796c1 Prod mix 2021-05-05 11:25:07 +02:00
Dennis
af1d3c3edc wip 2021-05-05 11:24:19 +02:00
Dennis
e1a54c2781 wip 2021-05-05 11:23:54 +02:00
Dennis
2d9f7d49b5 Merge branch 'develop' 2021-05-05 11:22:23 +02:00
Dennis
d10b046033 New features
- Few bugfixes
- Updated to TW2.0
- Updated Laravel Mix
- Documentation is now working
2021-05-05 11:21:48 +02:00
Dennis
1725c0ff65 Merge branch 'develop' 2021-05-01 08:25:59 +02:00
Dennis
b852756c82 Bugfix 2021-05-01 08:25:46 +02:00
Dennis
f164d878d7 Linking storage 2021-04-01 09:49:44 +02:00
Dennis
ecc10fffcb Merge branch 'develop' 2021-03-15 10:51:30 +01:00
Dennis
5c75b015ba Register fix 2021-03-15 10:51:23 +01:00
Dennis
65f7dc697e Production mix 2021-03-12 14:20:30 +01:00
Dennis
d981f0f899 Merge branch 'develop'
# Conflicts:
#	public/js/13.js
#	public/js/23.js
#	public/js/app.js
2021-03-12 14:20:12 +01:00
Dennis
ab50beefab Package updates & GBP currency 2021-03-12 14:20:00 +01:00
Dennis
58d1215fd6 Production mix 2021-02-10 13:56:20 +01:00
Dennis
1dc137c314 Merge branch 'develop'
# Conflicts:
#	package-lock.json
#	public/js/10.js
#	public/js/11.js
#	public/js/12.js
#	public/js/13.js
#	public/js/14.js
#	public/js/15.js
#	public/js/16.js
#	public/js/17.js
#	public/js/18.js
#	public/js/19.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/28.js
#	public/js/29.js
#	public/js/30.js
#	public/js/31.js
#	public/js/32.js
#	public/js/33.js
#	public/js/58.js
#	public/js/62.js
#	public/js/63.js
#	public/js/64.js
#	public/js/65.js
#	public/js/66.js
#	public/js/67.js
#	public/js/68.js
#	public/js/69.js
#	public/js/70.js
#	public/js/71.js
#	public/js/72.js
#	public/js/73.js
#	public/js/74.js
#	public/js/75.js
#	public/js/76.js
#	public/js/77.js
#	public/js/78.js
#	public/js/79.js
#	public/js/8.js
#	public/js/80.js
#	public/js/9.js
#	public/js/app.js
2021-02-10 13:55:43 +01:00
Dennis
ca51e9bf5f package updates 2021-02-10 13:55:18 +01:00
Dennis
c588583dfc Few tweaks 2021-02-10 13:54:30 +01:00
Dennis
57c7c53eae Added spanish 2021-01-20 09:18:21 +01:00
Dennis
76a62d9992 rm 2021-01-20 09:16:43 +01:00
Dennis
575aa1c6b1 wip 2020-12-29 08:39:05 +01:00
Dennis
4867a61fd0 Production mixed 2020-12-14 12:55:09 +01:00
Dennis
a63e8f350b Merge branch 'develop'
# Conflicts:
#	public/js/52.js
#	public/js/app.js
2020-12-14 12:54:39 +01:00
Dennis
6c1b4f28af Portuguese added 2020-12-14 12:54:22 +01:00
Dennis
09adccf752 Flush version data 2020-12-02 11:00:28 +01:00
Dennis
79bb522dee Don’t allow horizon in production 2020-12-02 10:58:39 +01:00
Dennis
2adc4bc7ca Production mixed 2020-11-28 11:41:34 +01:00
Dennis
0a81d58051 Merge branch 'develop'
# Conflicts:
#	public/css/app.css
#	public/js/0.js
#	public/js/1.js
#	public/js/18.js
#	public/js/19.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/31.js
#	public/js/49.js
#	public/js/5.js
#	public/js/50.js
#	public/js/51.js
#	public/js/52.js
#	public/js/61.js
#	public/js/71.js
#	public/js/72.js
#	public/js/73.js
#	public/js/74.js
#	public/js/app.js
2020-11-28 11:41:00 +01:00
Dennis
cb84438778 Improve ping command 2020-11-28 11:12:54 +01:00
Dennis
b0a76d311c Change this 2020-11-28 10:23:26 +01:00
Dennis
5604503d26 wip 2020-11-28 09:48:50 +01:00
Dennis
bf55092b3a wip 2020-11-28 09:13:42 +01:00
Dennis
8286e7f9af customizing 2020-11-27 19:46:53 +01:00
Dennis
1255221550 wip 2020-11-26 15:59:48 +01:00
Dennis
f4062cd6e7 wip 2020-11-26 15:23:38 +01:00
Dennis
93377ae753 Ability to allow custom styling 2020-11-25 15:56:16 +01:00
Dennis
a43cd19efd production mixing 2020-11-24 14:49:44 +01:00
Dennis
e5eec000d3 Merge branch 'develop'
# Conflicts:
#	public/js/0.js
#	public/js/1.js
#	public/js/10.js
#	public/js/11.js
#	public/js/12.js
#	public/js/13.js
#	public/js/14.js
#	public/js/15.js
#	public/js/16.js
#	public/js/17.js
#	public/js/18.js
#	public/js/19.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/28.js
#	public/js/29.js
#	public/js/30.js
#	public/js/31.js
#	public/js/32.js
#	public/js/33.js
#	public/js/34.js
#	public/js/35.js
#	public/js/36.js
#	public/js/37.js
#	public/js/38.js
#	public/js/39.js
#	public/js/40.js
#	public/js/41.js
#	public/js/42.js
#	public/js/43.js
#	public/js/44.js
#	public/js/45.js
#	public/js/46.js
#	public/js/47.js
#	public/js/48.js
#	public/js/49.js
#	public/js/50.js
#	public/js/51.js
#	public/js/52.js
#	public/js/53.js
#	public/js/54.js
#	public/js/55.js
#	public/js/56.js
#	public/js/57.js
#	public/js/58.js
#	public/js/59.js
#	public/js/60.js
#	public/js/61.js
#	public/js/62.js
#	public/js/63.js
#	public/js/64.js
#	public/js/65.js
#	public/js/66.js
#	public/js/67.js
#	public/js/68.js
#	public/js/69.js
#	public/js/70.js
#	public/js/71.js
#	public/js/72.js
#	public/js/73.js
#	public/js/74.js
#	public/js/75.js
#	public/js/9.js
#	public/js/app.js
2020-11-24 14:48:46 +01:00
Dennis
e5c8a62b32 wip 2020-11-24 14:48:02 +01:00
Dennis
9ae1c145b6 wip 2020-11-24 13:35:48 +01:00
Dennis
eb8b75e4f9 wip 2020-11-19 14:49:46 +01:00
Dennis
7378b82adf wip 2020-11-19 14:27:37 +01:00
Dennis
6f3b588f3d fx 2020-11-19 11:48:22 +01:00
Dennis
a2154cf37c wip 2020-11-18 12:06:34 +01:00
Dennis
9dab5f8093 wip 2020-11-18 11:22:44 +01:00
Dennis
9c9469d2f6 Title fixes 2020-11-17 13:29:35 +01:00
Dennis
94acc313b1 Alerts added 2020-11-17 12:40:39 +01:00
Dennis
448398322f Production mix 2020-11-12 20:42:40 +01:00
Dennis
3a78339396 Merge branch 'develop'
# Conflicts:
#	public/js/10.js
#	public/js/app.js
2020-11-12 20:42:04 +01:00
Dennis
a925a70448 Providers fix 2020-11-12 20:41:41 +01:00
Dennis
e283eacaa3 Production mixed 2020-11-12 13:34:27 +01:00
Dennis
3df6b6baed Merge branch 'develop'
# Conflicts:
#	public/js/0.js
#	public/js/10.js
#	public/js/11.js
#	public/js/12.js
#	public/js/13.js
#	public/js/14.js
#	public/js/15.js
#	public/js/16.js
#	public/js/17.js
#	public/js/18.js
#	public/js/19.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/28.js
#	public/js/29.js
#	public/js/30.js
#	public/js/31.js
#	public/js/32.js
#	public/js/33.js
#	public/js/34.js
#	public/js/35.js
#	public/js/36.js
#	public/js/37.js
#	public/js/38.js
#	public/js/39.js
#	public/js/40.js
#	public/js/41.js
#	public/js/42.js
#	public/js/43.js
#	public/js/44.js
#	public/js/45.js
#	public/js/46.js
#	public/js/47.js
#	public/js/48.js
#	public/js/49.js
#	public/js/50.js
#	public/js/52.js
#	public/js/67.js
#	public/js/68.js
#	public/js/69.js
#	public/js/75.js
#	public/js/8.js
#	public/js/9.js
#	public/js/app.js
2020-11-12 13:33:57 +01:00
Dennis
b65526e040 Stripe coupon fix && general updates 2020-11-12 13:33:34 +01:00
Dennis
673bbf73be Responsive progress 2020-11-11 16:27:16 +01:00
Dennis
094d22eaa8 Added API link here 2020-11-11 14:10:29 +01:00
Dennis
0fdba5fdec default to en here 2020-11-11 13:42:05 +01:00
Dennis
cf0730be89 Production mix 2020-11-11 13:09:48 +01:00
Dennis
221e67fd12 Merge branch 'develop' 2020-11-11 13:06:15 +01:00
Dennis
f9074309d1 Added coupon feature 2020-11-11 12:30:33 +01:00
Dennis
b2bdbb9e30 Allow sorting in billing 2020-11-11 12:07:06 +01:00
Dennis
63d0cb9626 Default language setting & fixed norwegian keys 2020-11-11 10:58:19 +01:00
Dennis
e3bb3ae4d1 Merge branch 'develop' 2020-11-06 08:42:09 +01:00
Dennis
31890005ac Fix 2020-11-06 08:41:52 +01:00
Dennis
04a216dee1 Merge branch 'develop' 2020-11-05 11:21:40 +01:00
Dennis
5d403c1202 Fix currency displayer 2020-11-05 11:21:29 +01:00
Dennis
a2d92c67b3 Merge branch 'develop' 2020-11-04 14:00:41 +01:00
Dennis
0fec3d82a3 Ugly bug 2020-11-04 14:00:36 +01:00
Dennis
6530a64f97 Production mixed 2020-11-04 13:42:50 +01:00
Dennis
656f02d652 Merge branch 'develop'
# Conflicts:
#	public/js/0.js
#	public/js/13.js
#	public/js/14.js
#	public/js/18.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/28.js
#	public/js/29.js
#	public/js/30.js
#	public/js/31.js
#	public/js/32.js
#	public/js/33.js
#	public/js/34.js
#	public/js/35.js
#	public/js/36.js
#	public/js/37.js
#	public/js/38.js
#	public/js/39.js
#	public/js/40.js
#	public/js/41.js
#	public/js/42.js
#	public/js/43.js
#	public/js/44.js
#	public/js/45.js
#	public/js/46.js
#	public/js/47.js
#	public/js/48.js
#	public/js/49.js
#	public/js/50.js
#	public/js/51.js
#	public/js/52.js
#	public/js/53.js
#	public/js/54.js
#	public/js/55.js
#	public/js/56.js
#	public/js/57.js
#	public/js/58.js
#	public/js/59.js
#	public/js/60.js
#	public/js/61.js
#	public/js/62.js
#	public/js/63.js
#	public/js/64.js
#	public/js/65.js
#	public/js/66.js
#	public/js/67.js
#	public/js/68.js
#	public/js/69.js
#	public/js/70.js
#	public/js/app.js
2020-11-04 13:42:17 +01:00
Dennis
0dbf3bba4d Wip 2020-11-04 13:41:46 +01:00
Dennis
ea47c0c3c6 wip 2020-11-04 12:15:19 +01:00
Dennis
b4072c7892 wip 2020-10-27 10:05:52 +01:00
Dennis
e80cd1990a Added more currencies 2020-10-27 08:40:26 +01:00
Dennis
2585cc1db4 Production mix 2020-10-26 07:51:05 +01:00
Dennis
0225828445 Add Norwegian language 2020-10-26 07:49:58 +01:00
Dennis
63af93592d Keyboard shortcuts disable-able 2020-10-22 14:06:23 +02:00
Dennis
b87fcd0f25 Danish language 2020-10-22 11:54:27 +02:00
Dennis
23a6b3cc55 Tweaks 2020-10-21 09:39:44 +02:00
Dennis
dac3d229fd Support MariaDB better 2020-10-21 09:03:35 +02:00
Dennis
e360d9c5df Add update script 2020-10-21 08:50:48 +02:00
Dennis
8f100fc027 wip 2020-10-21 08:47:06 +02:00
Dennis
06fb331ae4 Sorting 2020-10-20 15:53:59 +02:00
Dennis
2bcf02a779 Production mixed 2020-10-20 15:47:32 +02:00
Dennis
1b24664b60 Merge branch 'feature/server-management'
# Conflicts:
#	public/css/app.css
#	public/js/0.js
#	public/js/1.js
#	public/js/10.js
#	public/js/11.js
#	public/js/12.js
#	public/js/13.js
#	public/js/14.js
#	public/js/15.js
#	public/js/16.js
#	public/js/17.js
#	public/js/18.js
#	public/js/19.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/28.js
#	public/js/29.js
#	public/js/30.js
#	public/js/31.js
#	public/js/32.js
#	public/js/33.js
#	public/js/34.js
#	public/js/35.js
#	public/js/36.js
#	public/js/37.js
#	public/js/38.js
#	public/js/39.js
#	public/js/4.js
#	public/js/40.js
#	public/js/41.js
#	public/js/42.js
#	public/js/43.js
#	public/js/44.js
#	public/js/45.js
#	public/js/46.js
#	public/js/47.js
#	public/js/48.js
#	public/js/49.js
#	public/js/50.js
#	public/js/51.js
#	public/js/52.js
#	public/js/53.js
#	public/js/54.js
#	public/js/55.js
#	public/js/56.js
#	public/js/57.js
#	public/js/58.js
#	public/js/59.js
#	public/js/60.js
#	public/js/61.js
#	public/js/62.js
#	public/js/63.js
#	public/js/64.js
#	public/js/65.js
#	public/js/66.js
#	public/js/67.js
#	public/js/68.js
#	public/js/8.js
#	public/js/9.js
#	public/js/app.js
2020-10-20 15:46:59 +02:00
Dennis
2ce96a2062 wip 2020-10-20 15:46:18 +02:00
Dennis
5d737ba328 wip 2020-10-20 13:54:27 +02:00
Dennis
bafe8ba780 wip 2020-10-20 13:43:26 +02:00
Dennis
b7ff40fd72 wip 2020-10-20 12:30:21 +02:00
Dennis
8e036b1c01 wip 2020-10-20 12:21:13 +02:00
Dennis
b016c18880 wip 2020-10-20 12:16:40 +02:00
Dennis
d17ae49155 PSR formatting 2020-10-20 12:03:44 +02:00
Dennis
6ef5cd25f5 wip 2020-10-20 12:03:19 +02:00
Dennis
c98f077a0e wip 2020-10-20 11:25:32 +02:00
Dennis
a1f58c8e13 wip 2020-10-20 10:21:00 +02:00
Dennis
37281b01e4 wip 2020-10-14 13:19:24 +02:00
Dennis
65b0a768af Progress 2020-10-14 12:25:15 +02:00
Dennis
39af06d3b2 Tailwind 1.9 2020-10-13 15:12:21 +02:00
Dennis Smink
7d137dd612 wip 2020-10-09 12:51:29 +02:00
Dennis Smink
ce5e6c18f0 wip 2020-10-08 14:56:48 +02:00
Dennis Smink
3d445ca61a Allow attaching providers to packages 2020-10-06 12:47:20 +02:00
Dennis Smink
8566eaaa6c wip 2020-10-05 14:57:18 +02:00
Dennis Smink
db1647569c wip 2020-10-05 14:56:44 +02:00
Dennis Smink
204afb7eb1 wip 2020-10-05 14:34:24 +02:00
Dennis Smink
37f82d4579 Production mixed 2020-09-28 14:08:11 +02:00
Dennis Smink
2ed440e65f Merge branch 'develop'
# Conflicts:
#	public/js/1.js
#	public/js/10.js
#	public/js/11.js
#	public/js/12.js
#	public/js/13.js
#	public/js/14.js
#	public/js/15.js
#	public/js/16.js
#	public/js/17.js
#	public/js/18.js
#	public/js/19.js
#	public/js/2.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/24.js
#	public/js/25.js
#	public/js/26.js
#	public/js/27.js
#	public/js/28.js
#	public/js/29.js
#	public/js/3.js
#	public/js/30.js
#	public/js/31.js
#	public/js/32.js
#	public/js/33.js
#	public/js/34.js
#	public/js/35.js
#	public/js/36.js
#	public/js/37.js
#	public/js/38.js
#	public/js/39.js
#	public/js/4.js
#	public/js/40.js
#	public/js/41.js
#	public/js/42.js
#	public/js/43.js
#	public/js/44.js
#	public/js/45.js
#	public/js/46.js
#	public/js/47.js
#	public/js/48.js
#	public/js/49.js
#	public/js/50.js
#	public/js/51.js
#	public/js/52.js
#	public/js/53.js
#	public/js/54.js
#	public/js/55.js
#	public/js/56.js
#	public/js/57.js
#	public/js/58.js
#	public/js/59.js
#	public/js/6.js
#	public/js/60.js
#	public/js/61.js
#	public/js/62.js
#	public/js/63.js
#	public/js/64.js
#	public/js/65.js
#	public/js/66.js
#	public/js/8.js
#	public/js/9.js
#	public/js/app.js
2020-09-28 14:07:31 +02:00
Dennis Smink
57a5ec2206 Mixes 2020-09-28 13:56:23 +02:00
Dennis Smink
f14bc0494b finalize 2020-09-28 13:53:30 +02:00
Dennis Smink
c4147f0125 wip 2020-09-28 13:35:54 +02:00
Dennis Smink
cfd0c3cbe9 wip 2020-09-28 11:11:37 +02:00
Dennis Smink
0d0e2732b5 wip 2020-09-28 10:45:05 +02:00
Dennis Smink
0a7b072eeb wip 2020-09-27 19:06:33 +02:00
Dennis Smink
af6e12ca01 wip 2020-09-27 13:30:52 +02:00
Dennis Smink
5eba94fd9b wip 2020-09-25 20:44:18 +02:00
Dennis Smink
e9756494d9 wip 2020-09-25 15:17:18 +02:00
Dennis Smink
f7f919b5de Update columns 2020-09-25 15:00:04 +02:00
Dennis Smink
6f434f3b07 Initial start on stripe 2020-09-25 14:56:01 +02:00
Dennis Smink
8820851afa wip 2020-09-25 14:42:20 +02:00
Dennis Smink
4582e955d0 wip 2020-09-25 14:05:37 +02:00
Dennis Smink
e07395b3d5 Wip 2020-09-25 09:45:59 +02:00
Dennis Smink
3ad7d06976 Less logging days 2020-09-25 09:37:54 +02:00
Dennis Smink
f57cbb76e3 Ability to read up into laravel logs 2020-09-25 09:29:57 +02:00
Dennis Smink
bb3151a2fe Progress 2020-09-24 16:06:56 +02:00
Dennis Smink
5b48d204a0 Wip 2020-09-24 15:26:26 +02:00
Dennis Smink
680d96882a Progress 2020-09-24 15:09:09 +02:00
Dennis Smink
7347356646 Progress on improved permissions 2020-09-24 11:49:00 +02:00
Dennis Smink
2652e7ed71 Improve installation 2020-09-23 21:48:27 +02:00
Dennis Smink
a068da2a54 Production mixed 2020-09-22 14:11:52 +02:00
Dennis Smink
de69a2687a Merge branch 'develop'
# Conflicts:
#	public/js/41.js
#	public/js/42.js
#	public/js/43.js
#	public/js/44.js
#	public/js/45.js
#	public/js/46.js
#	public/js/47.js
#	public/js/48.js
#	public/js/49.js
#	public/js/50.js
#	public/js/51.js
#	public/js/52.js
#	public/js/53.js
#	public/js/54.js
#	public/js/55.js
#	public/js/56.js
#	public/js/57.js
#	public/js/58.js
#	public/js/59.js
#	public/js/6.js
#	public/js/60.js
#	public/js/61.js
#	public/js/62.js
#	public/js/63.js
#	public/js/app.js
2020-09-22 14:11:20 +02:00
Dennis Smink
59552cf8e5 Progress on server management for users 2020-09-22 13:56:34 +02:00
Dennis Smink
8d830db88c Production mixed 2020-09-21 15:07:22 +02:00
Dennis Smink
157ea41e6d Merge branch 'develop'
# Conflicts:
#	public/js/10.js
#	public/js/11.js
#	public/js/13.js
#	public/js/14.js
#	public/js/2.js
#	public/js/20.js
#	public/js/21.js
#	public/js/22.js
#	public/js/23.js
#	public/js/3.js
#	public/js/8.js
#	public/js/9.js
#	public/js/app.js
2020-09-21 15:06:26 +02:00
Dennis Smink
10771165a7 Merge branch 'feature/api' into develop 2020-09-21 15:05:44 +02:00
Dennis Smink
52ff6ab96f Finished API feature 2020-09-21 15:05:25 +02:00
Dennis Smink
dfc342ac2c Progress on api 2020-09-21 13:55:40 +02:00
Dennis Smink
ac1778b9e9 Fix InertiaJS middleware sorting & env example 2020-09-18 09:28:43 +02:00
816 changed files with 73305 additions and 28372 deletions

View File

@@ -1,9 +1,10 @@
APP_NAME=Laravel
APP_ENV=local
APP_ENV=production
APP_KEY=
APP_DEBUG=true
APP_DEBUG=false
APP_URL=http://localhost
APP_DEMO=false
APP_DATE_TIME_FORMAT="Y-m-d H:i:s"
PLOI_TOKEN=
PLOI_CORE_TOKEN=
@@ -26,6 +27,7 @@ SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_CLIENT=predis
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
@@ -36,6 +38,10 @@ MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
CASHIER_MODEL=App\Models\User
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
@@ -46,5 +52,5 @@ PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

42
.github/workflows/master.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Run tests & build files
on:
push:
branches:
- master
jobs:
test:
name: Run tests
uses: ./.github/workflows/run-tests.yml
deploy:
needs: test
name: Prepare build assets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP with PECL extension
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: pcntl
- run: composer install
name: Install dependencies
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: '14.x'
- run: npm install
- run: npm run prod
- run: npm run prod:filament
- name: Commit build assets
run: |
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
git add .
git commit -m "Updated build assets"
git push origin

33
.github/workflows/run-tests.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: "Run tests"
on:
push:
workflow_call:
jobs:
test:
name: Pest (PHP ${{ matrix.php }} ${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.1]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, mysql
coverage: none
- name: Install dependencies
run: |
composer install
- name: Execute tests
run: vendor/bin/pest

9
.gitignore vendored
View File

@@ -1,8 +1,10 @@
/node_modules
/node_modules.nosync
/public/hot
/public/storage
/storage/*.key
/vendor
/vendor.nosync
.env
.env.backup
.phpunit.result.cache
@@ -12,3 +14,10 @@ npm-debug.log
yarn-error.log
.idea
.php_cs.cache
.php-cs-fixer.cache
/public/js/resources*.js
/storage/views/header.blade.php
/storage/views/footer.blade.php
rr
.rr.yaml
.DS_Store

View File

@@ -8,11 +8,11 @@ $finder = Symfony\Component\Finder\Finder::create()
->name('*.php')
->notName('*.blade.php');
return PhpCsFixer\Config::create()
return (new PhpCsFixer\Config)
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sortAlgorithm' => 'length'],
'ordered_imports' => ['sort_algorithm' => 'length'],
'no_unused_imports' => true,
])
->setFinder($finder);

View File

@@ -9,5 +9,5 @@ php:
js:
finder:
not-name:
- webpack.mix.js
- vite.config.js
css: true

View File

@@ -3,7 +3,7 @@
With Ploi Core, you'll power-launch your webhosting company.
Using the ploi.io system as backbone you will be able to serve your customers your custom panel & feeling.
<p align="center"><img src="https://ploi-core.io/images/featured.png" width="100%"></p>
<p align="center"><img src="https://ploi-core.io/images/og.jpg" width="100%"></p>
## Documentation

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Actions\Provider;
use App\Models\Provider;
use App\Services\Ploi\Ploi;
class SynchronizeProviderAction
{
public function execute(int $ploiProviderId): Provider
{
$ploiProvider = Ploi::make()->user()->serverProviders($ploiProviderId)->getData();
$provider = Provider::updateOrCreate([
'ploi_id' => $ploiProvider->id,
], [
'label' => $ploiProvider->label,
'name' => $ploiProvider->name,
]);
foreach ($ploiProvider->provider->plans as $plan) {
$provider->plans()->updateOrCreate([
'plan_id' => $plan->id,
], [
'label' => $plan->name,
]);
}
foreach ($ploiProvider->provider->regions as $region) {
$provider->regions()->updateOrCreate([
'region_id' => $region->id,
], [
'label' => $region->name,
]);
}
return $provider;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Actions\Server;
use App\Models\User;
use App\Models\Server;
use App\Jobs\Servers\CreateServer;
use Illuminate\Support\Facades\Mail;
use App\DataTransferObjects\ServerData;
use App\Mail\Admin\Server\AdminServerCreatedEmail;
class CreateServerAction
{
public function execute(ServerData $serverData): Server
{
[$provider, $providerRegion, $providerPlan] = $this->determineProviderRegionPlan($serverData);
$server = $serverData->getUser()->servers()->create([
'name' => $serverData->name,
'database_type' => $serverData->database_type,
'os' => setting('default_os', Server::OS_UBUNTU_22)
]);
$server->provider()->associate($provider);
$server->providerRegion()->associate($providerRegion);
$server->providerPlan()->associate($providerPlan);
$server->save();
dispatch(new CreateServer($server));
$this->sendAdminServerCreatedEmails($server);
return $server;
}
protected function determineProviderRegionPlan(ServerData $serverData): array
{
$provider = $serverData->getUser()->package->providers()->findOrFail($serverData->provider_id);
$region = $provider->regions()->findOrFail($serverData->provider_region_id);
$plan = $provider->plans()->findOrFail($serverData->provider_plan_id);
return [$provider, $region, $plan];
}
protected function sendAdminServerCreatedEmails(Server $server): void
{
if (! setting('receive_email_on_server_creation')) {
return;
}
$admins = User::query()->where('role', User::ADMIN)->get();
foreach ($admins as $admin) {
Mail::to($admin)->send(new AdminServerCreatedEmail(auth()->user(), $server));
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use App\Services\Ploi\Ploi;
use Filament\Notifications\Notification;
class SynchronizeServerAction
{
public function execute(int $ploiServerId): Server
{
$serverData = Ploi::make()->server()->get($ploiServerId)->getData();
$server = Server::query()
->updateOrCreate([
'ploi_id' => $serverData->id,
], [
'status' => $serverData->status,
'name' => $serverData->name,
'ip' => $serverData->ip_address,
'ssh_port' => $serverData->ssh_port,
'internal_ip' => $serverData->internal_ip,
'available_php_versions' => $serverData->installed_php_versions,
]);
Notification::make()
->body(__('Server :server synchronized successfully.', ['server' => $server->name]))
->success()
->send();
return $server;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use App\Models\User;
use App\Models\Server;
use App\Jobs\Sites\CreateSite;
use Illuminate\Support\Facades\Mail;
use App\DataTransferObjects\SiteData;
use Illuminate\Database\Eloquent\Model;
use App\Mail\Admin\Site\AdminSiteCreatedEmail;
class CreateSiteAction
{
public function execute(SiteData $siteData): ?Site
{
$server = $this->determineServer($siteData);
if (! $server) {
return null;
}
$site = $server->sites()->create($siteData->toArray());
$siteData->getUser()->sites()->save($site);
dispatch(new CreateSite($site));
$siteData->getUser()->systemLogs()->create([
'title' => 'New site :site created',
'description' => 'A new site has been created',
])->model()->associate($site)->save();
$this->sendAdminSiteCreatedEmails($server, $site, $siteData->getUser());
return $site;
}
protected function determineServer(SiteData $siteData): ?Server
{
if ($siteData->server_id) {
return $siteData->getUser()->servers()->findOrFail($siteData->server_id);
}
$server = Server::query()
->where('maximum_sites', '>', 0)
->where(function ($query) use ($siteData) {
return $query
->where(fn ($query) => $query->whereHas('users', fn ($query) => $query->where('user_id', $siteData->getUser()->id)))
->orWhere(function ($query) {
return $query->doesntHave('users');
});
})
->withCount('sites')
->inRandomOrder()
->first();
return $server && $server->sites_count < $server->maximum_sites
? $server
: null;
}
protected function sendAdminSiteCreatedEmails(Server $server, Model|Site $site, User $user): void
{
if (! setting('receive_email_on_site_creation')) {
return;
}
$admins = User::where('role', User::ADMIN)->get();
foreach ($admins as $admin) {
Mail::to($admin)->send(new AdminSiteCreatedEmail(user: $user, server: $server, site: $site));
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use App\Models\Server;
use App\Services\Ploi\Ploi;
use Filament\Notifications\Notification;
class SynchronizeSiteAction
{
public function execute(int $ploiServerId, int $ploiSiteId): Site
{
$siteData = Ploi::make()->server($ploiServerId)->sites()->get($ploiSiteId)->getData();
$server = Server::query()
->where('ploi_id', $siteData->server_id)
->firstOrFail();
$site = Site::query()
->updateOrCreate([
'ploi_id' => $siteData->id,
], [
'domain' => $siteData->domain,
'php_version' => $siteData->php_version,
'project' => $siteData->project_type,
]);
$site->status = $siteData->status;
$site->server_id = $server->id;
$site->save();
$certificates = Ploi::make()->server($siteData->server_id)->sites($siteData->id)->certificates()->get()->getData();
if ($certificates) {
foreach ($certificates as $certificate) {
$site->certificates()->updateOrCreate([
'ploi_id' => $certificate->id,
], [
'status' => $certificate->status,
'ploi_id' => $certificate->id,
'domain' => $certificate->domain,
'type' => $certificate->type,
]);
}
}
Notification::make()
->body(__('Site :site synchronized successfully.', ['site' => $site->domain]))
->success()
->send();
return $site;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Actions\User;
use App\Models\Site;
use App\Models\User;
use App\Models\Server;
use App\Jobs\Sites\DeleteSite;
use App\Jobs\Servers\DeleteServer;
class DeleteUserAction
{
public function execute(User $user, bool $removeAllData): void
{
if ($removeAllData) {
$this->removeAllData($user);
}
// The next items are already being deleted by the "deleting" event:
// systemLogs, servers detached, sites detached, supportTickets, supportTicketReplies, userProviders
$user->delete();
}
protected function removeAllData(User $user): void
{
$user
->sites()
->withCount('users')
->get()
->filter(fn (Site $site) => $site->users_count === 1)
->each(function (Site $site) {
dispatch(new DeleteSite($site->server->ploi_id, $site->ploi_id));
// Deletes databases, redirects, cronjobs, certificates.
$site->delete();
});
$user
->servers()
->withCount('users')
->get()
->filter(fn (Server $server) => $server->users_count === 1)
->each(function (Server $server) {
dispatch(new DeleteServer($server->ploi_id));
// Deletes databases, redirects, cronjobs, certificates.
$server->delete();
});
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class PermissionCast implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*/
public function get($model, $key, $value, $attributes)
{
if (!$value) {
return [
'create' => false,
'update' => false,
'delete' => false,
];
}
return json_decode($value, true);
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return mixed
*/
public function set($model, $key, $value, $attributes)
{
return json_encode($value);
}
}

26
app/Casts/SiteAlias.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class SiteAlias implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
if (!$value) {
return [];
}
$data = json_decode($value, true);
sort($data);
return $data;
}
public function set($model, string $key, $value, array $attributes)
{
return json_encode($value);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands\Core;
use App\Models\SystemLog;
use Illuminate\Console\Command;
class Cleanup extends Command
{
protected $signature = 'core:cleanup';
protected $description = 'Clean up any old logs';
public function handle()
{
if (!setting('rotate_logs_after')) {
return Command::SUCCESS;
}
$rotationDate = $this->getRotationDate();
$rotated = SystemLog::query()
->where('created_at', '<', $rotationDate)
->delete();
$this->info('Rotated ' . $rotated . ' system logs!');
return Command::SUCCESS;
}
protected function getRotationDate()
{
switch (setting('rotate_logs_after')) {
case 'weeks-1':
return now()->subWeek();
break;
case 'months-1':
return now()->subMonth();
break;
case 'months-3':
return now()->subMonths(3);
break;
case 'months-6':
return now()->subMonths(6);
break;
case 'years-1':
return now()->subYear();
break;
case 'years-2':
return now()->subYears(2);
break;
case 'years-3':
return now()->subYears(3);
break;
case 'years-4':
return now()->subYears(4);
break;
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Console\Commands\Core;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
class Css extends Command
{
protected $signature = 'core:css';
protected $description = 'Generates an theme.css file for you to customize';
public function handle()
{
if (file_exists(storage_path('app/public/theme.css')) && !$this->confirm('You seem to already have a theme.css published, are you sure you want to overwrite?')) {
$this->warn('Aborted publishing of theme.css.');
return 0;
}
$this->info('Publishing theme.css file..');
(new Filesystem)->copy(
__DIR__ . '/stubs/theme.css',
storage_path('app/public/theme.css')
);
$this->info('Done! You can edit the theme.css file inside storage/public/theme.css');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Console\Commands\Core;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
class CssBackup extends Command
{
protected $signature = 'core:css-backup';
protected $description = 'Creates a backup from your own created theme.css';
public function handle()
{
if (!file_exists(storage_path('app/public/theme.css'))) {
$this->warn('There is no custom theme.css, aborting backup.');
return 0;
}
$this->info('Backing up theme.css file..');
(new Filesystem)->copy(
storage_path('app/public/theme.css'),
storage_path('app/public/theme-backup.css')
);
$this->info('Done! You can find the CSS backup file here storage/public/theme-backup.css');
}
}

View File

@@ -7,29 +7,25 @@ use App\Models\User;
use RuntimeException;
use App\Models\Package;
use App\Services\Ploi\Ploi;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Console\Command;
use App\Services\VersionChecker;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Artisan;
class Install extends Command
{
protected $company;
protected $signature = 'core:install';
protected $signature = 'core:install {--force}';
protected $description = 'Installation command for Ploi Core';
protected $versionChecker;
protected $installationFile = 'app/installation';
public function __construct()
{
parent::__construct();
$this->versionChecker = (new VersionChecker)->getVersions();
}
public function handle()
{
$this->init();
$this->intro();
$this->isInstalled();
$this->checkApplicationKey();
@@ -40,35 +36,53 @@ class Install extends Command
$this->askAboutDefaultPackages();
$this->checkApplicationUrl();
$this->createInstallationFile();
$this->linkStorage();
$this->info('Succes! Installation has finished.');
$this->info('Success! Installation has finished.');
$this->line(' ');
$this->writeSeparationLine();
$this->info('Make sure to also setup emailing, the cronjob and the queue worker.');
$this->line(' ');
$this->info('Setting up emailing: https://docs.ploi-core.io/getting-started/setting-up-email');
$this->info('Setting up cronjob & queue worker: https://docs.ploi-core.io/getting-started/installation');
$this->writeSeparationLine();
$this->line(' ');
$this->info('Visit your platform at ' . env('APP_URL'));
}
protected function init()
{
$this->versionChecker = (new VersionChecker)->getVersions();
}
protected function askAboutAdministrationAccount()
{
$this->info('Let\'s start by setting up your administration account.');
if (!User::query()->where('role', User::ADMIN)->count()) {
$this->info('Let\'s start by setting up your administration account.');
$name = $this->ask('What is your name', $this->company['user_name']);
$email = $this->ask('What is your e-mail address', $this->company['email']);
$password = $this->secret('What password do you desire');
$name = $this->ask('What is your name', $this->company['user_name']);
$email = $this->ask('What is your e-mail address', $this->company['email']);
$password = $this->secret('What password do you desire');
$check = User::where('email', $email)->count();
$check = User::where('email', $email)->count();
if ($check) {
$this->line('');
$this->comment('This user is already present in your system, please refresh your database or use different credentials.');
$this->comment('Aborting installation..');
if ($check) {
$this->line('');
$this->comment('This user is already present in your system, please refresh your database or use different credentials.');
$this->comment('Aborting installation..');
exit();
exit();
}
User::forceCreate([
'name' => $name,
'email' => $email,
'password' => $password,
'role' => User::ADMIN
]);
} else {
$this->line('Already found a administrator user in your system. Use that user to login.');
}
User::forceCreate([
'name' => $name,
'email' => $email,
'password' => $password,
'role' => User::ADMIN
]);
}
protected function askAboutDefaultPackages()
@@ -85,16 +99,46 @@ class Install extends Command
Package::create([
'name' => 'Basic',
'maximum_sites' => 5,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
Package::create([
'name' => 'Professional',
'maximum_sites' => 5,
'maximum_sites' => 30,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
Package::create([
'name' => 'Unlimited',
'maximum_sites' => 0,
'site_permissions' => [
'create' => true,
'update' => true,
'delete' => true
],
'server_permissions' => [
'create' => false,
'update' => false,
'delete' => false
]
]);
}
@@ -109,7 +153,9 @@ class Install extends Command
->get((new Ploi)->url . 'ping');
if (!$response->ok() || !$response->json()) {
return false;
return [
'error' => Arr::get($response->json(), 'message', 'An unknown error has occurred.')
];
}
return $response->json();
@@ -124,7 +170,7 @@ class Install extends Command
protected function intro()
{
$this->info('*---------------------------------------------------------------------------*');
$this->writeSeparationLine();
$this->line('Ploi Core Installation');
$this->line('Ploi Core version: ' . $this->versionChecker->currentVersion);
$this->line('Ploi Core remote: ' . $this->versionChecker->remoteVersion);
@@ -134,13 +180,13 @@ class Install extends Command
$this->line('Website: https://ploi-core.io');
$this->line('E-mail: core@ploi.io');
$this->line('Terms of service: https://ploi-core.io/terms');
$this->info('*---------------------------------------------------------------------------*');
$this->writeSeparationLine();
$this->line('');
}
protected function isInstalled()
{
if (file_exists(storage_path($this->installationFile))) {
if (file_exists(storage_path($this->installationFile)) && !$this->option('force')) {
$this->line('');
$this->comment('Ploi Core has already been installed before.');
$this->comment('If you still want to start installation, remove this file to continue: ./storage/' . $this->installationFile);
@@ -156,9 +202,9 @@ class Install extends Command
{
if (!config('app.key')) {
$this->call('key:generate');
}
$this->info('Application key has been set');
$this->info('Application key has been set');
}
}
protected function checkApplicationUrl()
@@ -174,6 +220,11 @@ class Install extends Command
file_put_contents(storage_path($this->installationFile), json_encode($this->getInstallationPayload(), JSON_PRETTY_PRINT));
}
protected function linkStorage()
{
Artisan::call('storage:link');
}
protected function createDatabaseCredentials(): bool
{
$storeCredentials = $this->confirm(
@@ -240,6 +291,18 @@ class Install extends Command
exit();
}
if (isset($this->company['error'])) {
$this->error($this->company['error']);
exit();
}
if ($this->company['user']['subscription'] !== 'unlimited') {
$this->error('Your subscription does not cover the usage of Ploi Core. Please upgrade your subscription to Unlimited.');
exit();
}
$this->writeToEnvironmentFile('PLOI_TOKEN', $ploiApiToken);
$this->writeToEnvironmentFile('PLOI_CORE_TOKEN', $ploiCoreKey);
@@ -252,7 +315,7 @@ class Install extends Command
protected function runDatabaseMigrations()
{
$this->info('Running database migrations..');
$this->call('migrate');
$this->call('migrate', ['--force' => true]);
$this->info('Database migrations successful');
}
@@ -343,4 +406,9 @@ class Install extends Command
{
$this->laravel['config'][$key] = $value;
}
protected function writeSeparationLine()
{
$this->info('*---------------------------------------------------------------------------*');
}
}

View File

@@ -9,7 +9,7 @@ class Synchronize extends Command
{
protected $signature = 'core:synchronize';
protected $description = 'Synchronze data';
protected $description = 'Synchronize data';
public function handle()
{

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Console\Commands\Core;
use App\Models\User;
use Illuminate\Console\Command;
class Trial extends Command
{
protected $signature = 'core:trial';
protected $description = 'Check for expired trials';
public function handle()
{
User::query()
->where('trial_ends_at', '<', now())
->each(function (User $user) {
$user->trial_ends_at = null;
$user->package_id = setting('default_package');
$user->save();
});
}
}

View File

@@ -0,0 +1,78 @@
:root {
--font-body: 'Inter', sans-serif;
--color-white: #fff;
--color-gray-1: #f7f7f7;
--color-gray-2: #e6e6e6;
--color-gray-3: #cacaca;
--color-gray-4: #888;
--color-gray-5: #666;
--color-gray-6: #2f2f2f;
--color-gray-7: #1b1a1a;
--color-gray-8: #101010;
--color-primary: #1b8ae8;
--color-success: #17b35d;
--color-warning: #f5a623;
--color-danger: #c90c4c;
--color-text-high-emphasis: var(--color-gray-8);
--color-text-medium-emphasis: var(--color-gray-5);
--color-text-low-emphasis: var(--color-gray-3);
--color-text-on-primary: var(--color-white);
--color-text-on-success: var(--color-gray-8);
--color-text-on-warning: var(--color-white);
--color-text-on-danger: var(--color-white);
--color-border-high-emphasis: var(--color-gray-4);
--color-border-medium-emphasis: var(--color-gray-3);
--color-border-low-emphasis: var(--color-gray-2);
--color-backdrop: rgba(0, 0, 0, 0.5);
--color-overlay: rgba(255, 255, 255, 0.8);
--color-surface-1: var(--color-white);
--color-surface-2: var(--color-gray-1);
--color-surface-3: var(--color-white);
--border-radius: 0.5rem;
--border-radius-avatar: 4rem;
--border-radius-circle: 100%;
--top-bar-container: 64rem;
--top-bar-logo-height: 3.5rem;
--top-bar-background-color: var(--color-surface-1);
--top-bar-text-color: var(--color-text-medium-emphasis);
--tab-bar-background-color: var(--color-surface-2);
--tab-bar-item-active-background-color: var(--color-surface-1);
--tab-bar-item-text-color: var(--color-text-medium-emphasis);
--tab-bar-item-active-text-color: var(--color-text-high-emphasis);
--breadcrumbs-text-color: var(--color-text-medium-emphasis);
}
.theme--dark {
--color-primary: #63a6f5;
--color-success: #50e3c2;
--color-warning: #f5a623;
--color-danger: #d4667c;
--color-text-high-emphasis: var(--color-white);
--color-text-medium-emphasis: var(--color-gray-3);
--color-text-low-emphasis: var(--color-gray-5);
--color-text-on-primary: var(--color-gray-7);
--color-text-on-success: var(--color-gray-7);
--color-text-on-warning: var(--color-gray-7);
--color-text-on-danger: var(--color-gray-7);
--color-border-high-emphasis: var(--color-gray-4);
--color-border-medium-emphasis: var(--color-gray-5);
--color-border-low-emphasis: var(--color-gray-6);
--color-surface-1: var(--color-gray-7);
--color-surface-2: var(--color-gray-6);
--color-surface-3: var(--color-gray-6);
--color-overlay: rgba(0, 0, 0, 0.8);
}

View File

@@ -3,7 +3,11 @@
namespace App\Console;
use App\Jobs\Core\Ping;
use App\Console\Commands\Core\Css;
use App\Console\Commands\Core\Trial;
use App\Console\Commands\Core\Cleanup;
use App\Console\Commands\Core\Install;
use App\Console\Commands\Core\CssBackup;
use App\Console\Commands\Core\Synchronize;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -11,8 +15,12 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected $commands = [
Css::class,
CssBackup::class,
Install::class,
Synchronize::class,
Cleanup::class,
Trial::class,
];
protected function schedule(Schedule $schedule)
@@ -20,5 +28,8 @@ class Kernel extends ConsoleKernel
$schedule->call(function () {
dispatch(new Ping())->delay(now()->addMinutes(rand(1, 30)));
})->dailyAt('02:00');
$schedule->command('core:cleanup')->daily();
$schedule->command('core:trial')->dailyAt('10:00');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\DataTransferObjects;
use Illuminate\Support\Carbon;
use App\DataTransferObjects\Support\Data;
class PackageData extends Data
{
public function __construct(
// Add validation attributes to this class if we add additional API-endpoints.
public int $id,
public ?string $name,
public ?int $maximum_servers,
public ?int $maximum_sites,
public float $price_hourly,
public float $price_monthly,
public float $price_yearly,
public ?string $stripe_plan_id,
public string $currency,
public array $server_permissions,
public array $site_permissions,
public Carbon $created_at,
) {
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\DataTransferObjects;
use App\Models\User;
use App\Models\Server;
use App\Models\Provider;
use App\Models\ProviderPlan;
use App\Models\ProviderRegion;
use Illuminate\Support\Carbon;
use App\DataTransferObjects\Support\Data;
use Spatie\LaravelData\Attributes\Validation\In;
use Spatie\LaravelData\Attributes\Validation\Max;
use Spatie\LaravelData\Attributes\Validation\NotIn;
use Spatie\LaravelData\Attributes\Validation\Exists;
use Spatie\LaravelData\Attributes\Validation\AlphaDash;
use Spatie\LaravelData\Attributes\Validation\StringType;
use Spatie\LaravelData\Attributes\Validation\IntegerType;
use App\DataTransferObjects\Support\Concerns\BelongsToUser;
class ServerData extends Data
{
use BelongsToUser;
public function __construct(
public ?int $id = null,
#[StringType]
public ?string $status = null,
#[StringType,
AlphaDash,
Max(40)]
public string $name,
#[NotIn(0),
Exists(Provider::class, 'id')]
public int $provider_id,
#[NotIn(0),
Exists(ProviderRegion::class, 'id')]
public int $provider_region_id,
#[NotIn(0),
Exists(ProviderPlan::class, 'id')]
public int $provider_plan_id,
#[StringType,
In(['mysql', 'mariadb', 'postgresql', 'postgresql13'])]
public string $database_type,
#[Exists(User::class, 'id'),
IntegerType]
public ?int $user_id = null,
public ?Carbon $created_at = null,
) {
}
public static function fromModel(Server $server): static
{
return static::from(array_merge($server->toArray(), ['user_id' => $server->user->id]));
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\DataTransferObjects;
use App\Models\Site;
use App\Models\User;
use App\Models\Server;
use App\Rules\Hostname;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use App\Rules\ValidateMaximumSites;
use App\DataTransferObjects\Support\Data;
use Spatie\LaravelData\Attributes\Validation\Exists;
use App\DataTransferObjects\Support\Rules\CustomRule;
use Spatie\LaravelData\Attributes\Validation\StringType;
use Spatie\LaravelData\Attributes\Validation\IntegerType;
use App\DataTransferObjects\Support\Concerns\BelongsToUser;
class SiteData extends Data
{
use BelongsToUser;
public function __construct(
public ?int $id = null,
public ?string $status = null,
#[Exists(Server::class, 'id'),
IntegerType]
public ?int $server_id = null,
#[StringType,
CustomRule(Hostname::class, ValidateMaximumSites::class)]
public ?string $domain = null,
#[Exists(User::class, 'id'),
IntegerType]
public ?int $user_id = null,
public ?Carbon $created_at = null,
) {
}
public static function authorize(): bool
{
if (auth()->guest()) {
return true;
}
return auth()->user()->can('create', Site::class);
}
public static function fromModel(Site $site): static
{
return static::from(array_merge($site->toArray(), ['user_id' => $site->user->id]));
}
public function toArray(): array
{
return Arr::only(parent::toArray(), [
'id',
'status',
'server_id',
'domain',
'user_id',
'created_at',
]);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\DataTransferObjects\Support\Casts;
use Illuminate\Support\Carbon;
use Spatie\LaravelData\Casts\Cast;
use Spatie\LaravelData\Support\DataProperty;
class CarbonCast implements Cast
{
public function cast(DataProperty $property, mixed $value): mixed
{
return Carbon::parse($value);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\DataTransferObjects\Support\Concerns;
use App\Models\User;
trait BelongsToUser
{
public function getUser(): ?User
{
return User::find($this->user_id);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\DataTransferObjects\Support;
use Illuminate\Support\Enumerable;
use Spatie\LaravelData\DataCollection;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Pagination\AbstractCursorPaginator;
class Data extends \Spatie\LaravelData\Data
{
public static function collection(Paginator|Enumerable|array|AbstractCursorPaginator|DataCollection|AbstractPaginator $items): \App\DataTransferObjects\Support\DataCollection
{
return new \App\DataTransferObjects\Support\DataCollection(static::class, $items);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\DataTransferObjects\Support;
use Spatie\LaravelData\Support\TransformationType;
class DataCollection extends \Spatie\LaravelData\DataCollection
{
public function transform(TransformationType $type): array
{
$transformer = new DataCollectionTransformer(
$this->dataClass,
$type,
$this->getInclusionTree(),
$this->getExclusionTree(),
$this->items,
$this->through,
$this->filter
);
return $transformer->transform();
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\DataTransferObjects\Support;
use Illuminate\Support\Arr;
class DataCollectionTransformer extends \Spatie\LaravelData\Transformers\DataCollectionTransformer
{
protected function wrapPaginatedArray(array $paginated): array
{
return [
'data' => $paginated['data'],
'links' => [
'first' => $paginated['first_page_url'],
'last' => $paginated['last_page_url'],
'prev' => $paginated['prev_page_url'],
'next' => $paginated['next_page_url'],
],
'meta' => [
'current_page' => $paginated['current_page'],
'from' => $paginated['from'],
'last_page' => $paginated['last_page'],
'links' => $paginated['links'],
'path' => $paginated['path'],
'per_page' => $paginated['per_page'],
'to' => $paginated['to'],
'total' => $paginated['total'],
],
];
return [
'data' => $paginated['data'],
'links' => $paginated['links'] ?? [],
'meta' => Arr::except($paginated, [
'data',
'links',
]),
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\DataTransferObjects\Support\Rules;
use Attribute;
use Spatie\LaravelData\Attributes\Validation\ValidationAttribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class CustomRule extends ValidationAttribute
{
protected array $rules = [];
public function __construct(...$rules)
{
$this->rules = $rules;
}
public function getRules(): array
{
return collect($this->rules)
->map(fn (string $rule) => new $rule())
->all();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\DataTransferObjects\Support\Transformers;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Transformers\Transformer;
class CarbonTransformer implements Transformer
{
public function transform(DataProperty $property, mixed $value): mixed
{
return $value->toISOString();
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\DataTransferObjects;
use App\Models\User;
use App\Models\Package;
use Illuminate\Support\Carbon;
use App\DataTransferObjects\Support\Data;
use Spatie\LaravelData\Attributes\Validation\Max;
use Spatie\LaravelData\Attributes\Validation\Email;
use Spatie\LaravelData\Attributes\Validation\Exists;
use Spatie\LaravelData\Attributes\Validation\Unique;
use Spatie\LaravelData\Attributes\Validation\StringType;
use Spatie\LaravelData\Attributes\Validation\BooleanType;
use Spatie\LaravelData\Attributes\Validation\IntegerType;
class UserData extends Data
{
public function __construct(
public ?int $id = null,
public ?string $avatar = null,
#[StringType,
Max(255)]
public ?string $name = null,
#[StringType,
Email,
Max(255),
Unique(User::class)]
public ?string $email = null,
#[Exists(Package::class, 'id'),
IntegerType]
public ?int $package_id = null,
#[StringType]
public ?string $blocked = null,
#[StringType]
public ?string $language = 'en',
#[BooleanType]
public ?bool $requires_password_for_ftp = true,
public ?Carbon $created_at = null,
) {
}
}

View File

@@ -2,7 +2,12 @@
namespace App\Exceptions;
use Exception;
use Throwable;
use Illuminate\Http\Request;
use App\Http\Middleware\SetLocale;
use App\Http\Middleware\HandleInertiaRequests;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
@@ -29,10 +34,10 @@ class Handler extends ExceptionHandler
/**
* Report or log an exception.
*
* @param \Throwable $exception
* @param Throwable $exception
* @return void
*
* @throws \Exception
* @throws Exception
*/
public function report(Throwable $exception)
{
@@ -42,24 +47,25 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $exception
* @return \Symfony\Component\HttpFoundation\Response
* @param Request $request
* @param Throwable $exception
* @return Response
*
* @throws \Throwable
* @throws Throwable
*/
public function render($request, Throwable $exception)
{
$response = parent::render($request, $exception);
if (in_array($response->status(), [404, 403])) {
\Route::any($request->path(), function () use ($exception, $request) {
return parent::render($request, $exception);
})->middleware('web');
// Only return an Inertia-response when there are special Vue-templates (403 and 404) and when it isn't an API request.
if (in_array($response->status(), [403, 404]) && ! $request->routeIs('api.*')) {
inertia()->share([
'translations' => SetLocale::getTranslations()
]);
return inertia()->render('Errors/' . $response->status(), ['status' => $response->status()])
->toResponse($request)
->setStatusCode($response->status());
return app(HandleInertiaRequests::class)
->handle($request, fn () => inertia()->render('Errors/' . $response->status(), ['status' => $response->status()])
->toResponse($request));
}
return $response;

View File

@@ -0,0 +1,188 @@
<?php
namespace App\Filament\Pages;
use App\Models\Server;
use App\Models\Package;
use Filament\Pages\Page;
use Illuminate\Support\Str;
use Illuminate\Support\HtmlString;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;
use Illuminate\Support\Facades\Storage;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Actions\Action;
class Settings extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-cog';
protected static string $view = 'filament.pages.settings';
protected static ?string $navigationGroup = 'Settings';
protected static ?int $navigationSort = 1;
public array $data = [];
public function mount(): void
{
cache()->forget('core.settings');
$this->form->fill([
'logo' => setting('logo'),
'name' => setting('name'),
'email' => setting('email'),
'support_emails' => setting('support_emails'),
'default_package' => setting('default_package'),
'default_language' => setting('default_language'),
'rotate_logs_after' => setting('rotate_logs_after'),
'trial' => (bool) setting('trial'),
'support' => (bool) setting('support'),
'documentation' => (bool) setting('documentation'),
'allow_registration' => (bool) setting('allow_registration'),
'receive_email_on_server_creation' => (bool) setting('receive_email_on_server_creation'),
'receive_email_on_site_creation' => (bool) setting('receive_email_on_site_creation'),
'enable_api' => (bool) setting('enable_api'),
'api_token' => setting('api_token'),
'isolate_per_site_per_user' => (bool) setting('isolate_per_site_per_user'),
'default_os' => setting('default_os', Server::OS_UBUNTU_22),
]);
}
public function getFormSchema(): array
{
return [
Grid::make(2)
->schema([
Grid::make(1)
->schema([
TextInput::make('name')
->label(__('Company name'))
->required(),
TextInput::make('email')
->label(__('E-mail address'))
->email(),
TextInput::make('support_emails')
->label(__('Support email address'))
->helperText('Separate by comma to allow more email addresses'),
])
->columnSpan(2),
Select::make('default_package')
->options(fn () => Package::orderBy('name')->get()->mapWithKeys(fn (Package $package) => [$package->id => $package->name]))
->label(__('Select default package'))
->helperText(__('Select the default package a user should get when you create or they register')),
Select::make('default_language')
->options(collect(languages())->mapWithKeys(fn (string $language) => [$language => $language]))
->label('Select default language')
->helperText('Select the default language a user should get when you create or they register'),
FileUpload::make('logo')
->label(__('Logo'))
->disk('logos')
->columnSpan(2),
Select::make('rotate_logs_after')
->label(__('This will rotate any logs older than selected, this helps cleanup your database'))
->options([
null => __("Don't rotate logs"),
'weeks-1' => __('Older than 1 week'),
'months-1' => __('Older than 1 month'),
'months-3' => __('Older than 3 months'),
'months-6' => __('Older than 6 months'),
'years-1' => __('Older than 1 year'),
'years-2' => __('Older than 2 years'),
'years-3' => __('Older than 3 years'),
'years-4' => __('Older than 4 years'),
])
->columnSpan(1),
Select::make('default_os')
->label(__('Select the default OS that should be used when users create a server'))
->default(Server::OS_UBUNTU_22)
->options([
Server::OS_UBUNTU_18 => __('Ubuntu 18'),
Server::OS_UBUNTU_20 => __('Ubuntu 20'),
Server::OS_UBUNTU_22 => __('Ubuntu 22'),
])
->columnSpan(1),
Toggle::make('trial')
->label(__('Enable trial'))
->helperText(__('This will allow you to have users with trials.')),
Toggle::make('allow_registration')
->label(__('Allow registration'))
->helperText(__('This will allow your customers to make support requests to you.')),
Toggle::make('support')
->label(__('Enable support platform'))
->helperText(__('This will allow your customers to make support requests to you.')),
Toggle::make('documentation')
->label(__('Enable documentation platform'))
->helperText(__('This will allow you to create articles for your users to look at.')),
Toggle::make('receive_email_on_server_creation')
->label(__('Receive email when customers create server'))
->helperText(__('This will send an email to all admins notifying them about a new server installation.')),
Toggle::make('receive_email_on_site_creation')
->label(__('Receive email when customers create site'))
->helperText(__('This will send an email to all admins notifying them about a new site installation.')),
Toggle::make('enable_api')
->label(__('Enable API'))
->helperText(new HtmlString(__('This will allow you to interact with your system via the API. ') . '<a href="https://docs.ploi-core.io/core-api/introduction" target="_blank" class="text-primary-600">' . __('More information') . '</a>')),
TextInput::make('api_token')
->label(__('API token'))
->afterStateHydrated(function (?string $state, TextInput $component) {
$state = filled($state) ? decrypt($state) : null;
$component->state($state);
})
->dehydrateStateUsing(function (?string $state) {
return filled($state) ? encrypt($state) : null;
})
->registerActions([
'generate' => $generateAction = Action::make('generate')
->label(__('Generate'))
->icon('heroicon-o-key')
->action(function (TextInput $component) {
$component->state(Str::random(20));
})
->tooltip('Generate'),
])
->suffixAction($generateAction),
Toggle::make('isolate_per_site_per_user')
->label(__('Enable site isolation per site & user'))
->helperText(__('This will make sure each site created by one user is always isolated from another.')),
]),
];
}
public function getFormStatePath(): ?string
{
return 'data';
}
public function save(): void
{
$state = $this->form->getState();
$oldLogo = setting('logo');
$oldDocumentation = setting('documentation');
$oldSupport = setting('support');
if ($state['logo'] === null && $oldLogo) {
Storage::disk('logos')->delete($oldLogo);
}
setting($state);
cache()->forget('core.settings');
Notification::make()
->success()
->body(__('Settings saved.'))
->send();
if ($state['logo'] !== $oldLogo || $state['documentation'] !== $oldDocumentation || $state['support'] !== $oldSupport) {
$this->redirectRoute('filament.pages.settings');
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Page;
use App\Services\VersionChecker;
use Filament\Notifications\Notification;
use Laravel\Horizon\Contracts\MasterSupervisorRepository;
class System extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-adjustments';
protected static string $view = 'filament.pages.system';
protected static ?string $navigationGroup = 'Settings';
protected static ?int $navigationSort = 2;
public function getCurrentVersion(): string
{
return app(VersionChecker::class)->getVersions()->currentVersion;
}
public function getRemoteVersion(): string
{
return app(VersionChecker::class)->getVersions()->remoteVersion;
}
public function refreshRemoteVersion(): void
{
app(VersionChecker::class)->flushVersionData();
Notification::make()
->success()
->body(__('Refreshed versions'))
->send();
}
public function getHorizonWorkerStatus(): bool
{
return rescue(fn () => (bool) app(MasterSupervisorRepository::class)->all(), false, false);
}
public function hasAvailableUpdate(): bool
{
return app(VersionChecker::class)->getVersions()->isOutOfDate();
}
protected static function getNavigationBadge(): ?string
{
$systemChecker = app(VersionChecker::class);
if ($systemChecker->isOutOfDate()) {
return 'Update available';
}
return null;
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Page;
use Illuminate\Support\Str;
use Filament\Pages\Actions\Action;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Forms\Components\MarkdownEditor;
class Terms extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.terms';
protected static ?string $navigationGroup = 'Settings';
protected static ?int $navigationSort = 3;
public array $data = [];
public function mount(): void
{
cache()->forget('core.settings');
$this->form->fill([
'accept_terms_required' => (bool) setting('accept_terms_required'),
'terms' => setting('terms'),
'privacy' => setting('privacy'),
]);
}
protected function getFormSchema(): array
{
return [
Toggle::make('accept_terms_required')
->label(__(' Require users to accept terms of service on registration'))
->helperText(__('This will require newly registered users to accept the terms of service.')),
MarkdownEditor::make('terms')
->label(__('Content Terms Of Service')),
MarkdownEditor::make('privacy')
->label(__('Content Privacy Policy')),
];
}
protected function getActions(): array
{
return [
Action::make('load_terms_template')
->label(__('Load Terms Of Service Template'))
->action(function (self $livewire) {
$template = Str::of(file_get_contents(storage_path('templates/terms-of-service.md')))
->replace([
'{NAME}',
'{WEBSITE}',
'{DATE}',
], [
setting('name'),
config('app.url'),
date('Y-m-d'),
])
->value();
$livewire->data['terms'] = $template;
Notification::make()
->success()
->body(__('Loaded Terms Of Service Template'))
->send();
}),
];
}
protected function getFormStatePath(): ?string
{
return 'data';
}
public function save(): void
{
$state = $this->form->getState();
setting($state);
cache()->forget('core.settings');
Notification::make()
->success()
->body(__('Terms saved.'))
->send();
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Filament\Resources;
use App\Models\Alert;
use Illuminate\Support\Str;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Illuminate\Support\HtmlString;
use Filament\Forms\Components\Select;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\BadgeColumn;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\MarkdownEditor;
use App\Filament\Resources\AlertResource\Pages;
class AlertResource extends Resource
{
protected static ?string $model = Alert::class;
protected static ?string $navigationIcon = 'heroicon-o-bell';
protected static ?string $navigationGroup = 'Settings';
protected static ?int $navigationSort = 4;
protected static ?string $recordTitleAttribute = 'message';
public static function form(Form $form): Form
{
return $form
->schema([
MarkdownEditor::make('message')
->label(__('Content'))
->columnSpan(2)
->required(),
Select::make('type')
->label(__('Type'))
->options([
Alert::TYPE_INFO => __('Informational'),
Alert::TYPE_WARNING => __('Warning'),
Alert::TYPE_DANGER => __('Danger'),
])
->required(),
DateTimePicker::make('expires_at')
->label(__('Expires at'))
->withoutSeconds(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('message')
->label(__('Content'))
->formatStateUsing(fn (?string $state) => new HtmlString(Str::markdown($state))),
BadgeColumn::make('type')
->label(__('Type'))
->enum([
Alert::TYPE_INFO => __('Informational'),
Alert::TYPE_WARNING => __('Warning'),
Alert::TYPE_DANGER => __('Danger'),
])
->colors([
'primary' => Alert::TYPE_INFO,
'warning' => Alert::TYPE_WARNING,
'danger' => Alert::TYPE_DANGER,
]),
TextColumn::make('expires_at')
->label('Expires Date')
->formatStateUsing(fn (?string $state) => filled($state) ? $state : '-'),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListAlerts::route('/'),
'create' => Pages\CreateAlert::route('/create'),
'edit' => Pages\EditAlert::route('/{record}/edit'),
];
}
public static function getGloballySearchableAttributes(): array
{
return [];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\AlertResource\Pages;
use App\Filament\Resources\AlertResource;
use Filament\Resources\Pages\CreateRecord;
class CreateAlert extends CreateRecord
{
protected static string $resource = AlertResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\AlertResource\Pages;
use Filament\Pages\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\AlertResource;
class EditAlert extends EditRecord
{
protected static string $resource = AlertResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\AlertResource\Pages;
use Filament\Pages\Actions\CreateAction;
use App\Filament\Resources\AlertResource;
use Filament\Resources\Pages\ListRecords;
class ListAlerts extends ListRecords
{
protected static string $resource = AlertResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Filament\Resources;
use Filament\Forms;
use Filament\Tables;
use App\Models\Certificate;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Builder;
use App\Filament\Resources\CertificateResource\Pages;
use Illuminate\Support\HtmlString;
class CertificateResource extends Resource
{
protected static ?string $model = Certificate::class;
protected static ?string $navigationIcon = 'heroicon-o-annotation';
protected static ?string $navigationGroup = 'Site management';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('site.name'),
Forms\Components\TextInput::make('server_id'),
Forms\Components\TextInput::make('status')
->maxLength(255),
Forms\Components\TextInput::make('ploi_id'),
Forms\Components\TextInput::make('domain')
->maxLength(255),
Forms\Components\Textarea::make('certificate'),
Forms\Components\Textarea::make('private'),
Forms\Components\TextInput::make('type')
->maxLength(255),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('server.name')
->searchable()
->label(__('Server')),
Tables\Columns\TextColumn::make('site.domain')
->searchable()
->label(__('Main domain')),
Tables\Columns\TextColumn::make('type')
->label('Type'),
Tables\Columns\BadgeColumn::make('status')
->enum([
Certificate::STATUS_BUSY => __('Busy'),
Certificate::STATUS_ACTIVE => __('Active'),
])
->colors([
'warning' => Certificate::STATUS_BUSY,
'success' => Certificate::STATUS_ACTIVE,
])
->label(__('Status')),
Tables\Columns\TextColumn::make('domain')
->searchable()
->wrap()
->getStateUsing(function (Certificate $record) {
$state = str($record->domain)->explode(',')->implode(', ');
return new HtmlString($state);
})
->label('Domains & aliases'),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->sortable()
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('created_at', 'desc');
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->orderBy('domain');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListCertificates::route('/'),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\CertificateResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\CertificateResource;
class ListCertificates extends ListRecords
{
protected static string $resource = CertificateResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use App\Models\Cronjob;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use App\Filament\Resources\CronjobResource\Pages;
class CronjobResource extends Resource
{
protected static ?string $model = Cronjob::class;
protected static ?string $navigationIcon = 'heroicon-o-clock';
protected static ?string $navigationGroup = 'Site management';
protected static ?int $navigationSort = 3;
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('site.domain')
->searchable()
->label(__('Site')),
Tables\Columns\BadgeColumn::make('status')
->enum([
Cronjob::STATUS_BUSY => __('Busy'),
Cronjob::STATUS_ACTIVE => __('Active'),
])
->colors([
'warning' => Cronjob::STATUS_BUSY,
'success' => Cronjob::STATUS_ACTIVE,
])
->label(__('Status')),
Tables\Columns\TextColumn::make('server.name')
->searchable()
->label(__('Server')),
Tables\Columns\TextColumn::make('command')
->searchable()
->label(__('Command')),
Tables\Columns\TextColumn::make('frequency')
->label(__('Frequency')),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->sortable()
->dateTime(),
])
->filters([
//
])
->actions([
//
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListCronjobs::route('/'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\CronjobResource\Pages;
use Filament\Resources\Pages\CreateRecord;
use App\Filament\Resources\CronjobResource;
class CreateCronjob extends CreateRecord
{
protected static string $resource = CronjobResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\CronjobResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\CronjobResource;
class EditCronjob extends EditRecord
{
protected static string $resource = CronjobResource::class;
protected function getActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\CronjobResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\CronjobResource;
class ListCronjobs extends ListRecords
{
protected static string $resource = CronjobResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use App\Models\Database;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use App\Filament\Resources\DatabaseResource\Pages;
class DatabaseResource extends Resource
{
protected static ?string $model = Database::class;
protected static ?string $navigationIcon = 'heroicon-o-database';
protected static ?string $navigationGroup = 'Site management';
protected static ?int $navigationSort = 4;
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->label(__('Name'))
->searchable(),
Tables\Columns\TextColumn::make('server.name')
->label(__('Server'))
->searchable(),
Tables\Columns\TextColumn::make('site.domain')
->label(__('Site'))
->searchable(),
Tables\Columns\BadgeColumn::make('status')
->enum([
Database::STATUS_BUSY => __('Busy'),
Database::STATUS_ACTIVE => __('Active'),
])
->colors([
'warning' => Database::STATUS_BUSY,
'success' => Database::STATUS_ACTIVE,
])
->label(__('Status')),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->sortable()
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListDatabases::route('/'),
'edit' => Pages\EditDatabase::route('/{record}'),
];
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Filament\Resources\DatabaseResource\Pages;
use App\Models\Database;
use App\Services\Ploi\Ploi;
use Filament\Resources\Pages\Page;
use App\Mail\Database\PasswordReset;
use Illuminate\Support\Facades\Mail;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use App\Filament\Resources\DatabaseResource;
class EditDatabase extends Page
{
protected $listeners = [
'$refresh',
];
protected static string $resource = DatabaseResource::class;
protected static string $view = 'filament.resources.database-resource.pages.edit-database';
public Database $record;
protected ?string $recentlyUpdatedPassword = null;
public function mount(): void
{
$this->resetDatabasePasswordForm->fill();
}
public function getForms(): array
{
return [
'resetDatabasePasswordForm' => $this->makeForm()
->schema([
Toggle::make('send_new_password_to_user')
->label(__('Email new password to user')),
])
->model($this->record),
];
}
public function resetDatabasePassword(): void
{
$state = $this->resetDatabasePasswordForm->getState();
$data = Ploi::make()
->server($this->record->server->ploi_id)
->databases($this->record->ploi_id)
->users($this->record->users->first()->ploi_id)
->resetPassword()
->getData();
$this->recentlyUpdatedPassword = $data->new_password;
Notification::make()
->body(__('Successfully reset database password.'))
->success()
->send();
if ($state['send_new_password_to_user']) {
Mail::to($this->record->site->users)->send(new PasswordReset($this->record, $this->recentlyUpdatedPassword));
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DatabaseResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\DatabaseResource;
class ListDatabases extends ListRecords
{
protected static string $resource = DatabaseResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Filament\Resources;
use Illuminate\Support\Str;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Illuminate\Support\HtmlString;
use App\Models\DocumentationCategory;
use Filament\Tables\Columns\TextColumn;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\MarkdownEditor;
use App\Filament\Resources\DocumentationCategoryResource\Pages;
use App\Filament\Resources\DocumentationCategoryResource\RelationManagers\DocumentationItemsRelationManager;
class DocumentationCategoryResource extends Resource
{
protected static ?string $model = DocumentationCategory::class;
protected static ?string $navigationIcon = 'heroicon-o-tag';
protected static ?string $navigationGroup = 'Documentation';
protected static ?int $navigationSort = 2;
protected static ?string $pluralLabel = 'Categories';
protected static ?string $label = 'Category';
protected static function shouldRegisterNavigation(): bool
{
return (bool) setting('documentation');
}
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('title')
->label(__('Title'))
->unique(table: DocumentationCategory::class, column: 'title', ignoreRecord: true)
->required()
->columnSpan(2),
MarkdownEditor::make('description')
->label(__('Description'))
->required()
->columnSpan(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->searchable()
->sortable()
->label(__('Title')),
TextColumn::make('description')
->label(__('Description'))
->formatStateUsing(fn (string $state) => new HtmlString(Str::markdown($state))),
]);
}
public static function getRelations(): array
{
return [
DocumentationItemsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListDocumentationCategories::route('/'),
'create' => Pages\CreateDocumentationCategory::route('/create'),
'edit' => Pages\EditDocumentationCategory::route('/{record}/edit'),
];
}
public static function getGloballySearchableAttributes(): array
{
return ['title'];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\DocumentationCategoryResource\Pages;
use Filament\Resources\Pages\CreateRecord;
use App\Filament\Resources\DocumentationCategoryResource;
class CreateDocumentationCategory extends CreateRecord
{
protected static string $resource = DocumentationCategoryResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DocumentationCategoryResource\Pages;
use Filament\Pages\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\DocumentationCategoryResource;
class EditDocumentationCategory extends EditRecord
{
protected static string $resource = DocumentationCategoryResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DocumentationCategoryResource\Pages;
use Filament\Pages\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\DocumentationCategoryResource;
class ListDocumentationCategories extends ListRecords
{
protected static string $resource = DocumentationCategoryResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Filament\Resources\DocumentationCategoryResource\RelationManagers;
use Filament\Forms;
use Filament\Tables;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\RelationManagers\RelationManager;
class DocumentationItemsRelationManager extends RelationManager
{
protected static string $relationship = 'items';
protected static ?string $recordTitleAttribute = 'title';
protected static ?string $label = 'Article';
protected static ?string $pluralLabel = 'Articles';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('title')
->required()
->maxLength(255),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title'),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Filament\Resources;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use App\Models\DocumentationItem;
use Filament\Forms\Components\Select;
use Filament\Tables\Columns\TextColumn;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\MarkdownEditor;
use App\Filament\Resources\DocumentationItemResource\Pages;
class DocumentationItemResource extends Resource
{
protected static ?string $model = DocumentationItem::class;
protected static ?string $navigationIcon = 'heroicon-o-document-duplicate';
protected static ?string $navigationGroup = 'Documentation';
protected static ?int $navigationSort = 1;
protected static ?string $pluralLabel = 'Articles';
protected static ?string $label = 'Article';
protected static function shouldRegisterNavigation(): bool
{
return (bool) setting('documentation');
}
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('title')
->label(__('Title'))
->required(),
Select::make('documentation_category_id')
->relationship('category', 'title')
->searchable()
->preload(),
MarkdownEditor::make('content')
->label(__('Content'))
->required()
->columnSpan(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->searchable()
->sortable(),
TextColumn::make('category.title')
->searchable()
->sortable(),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListDocumentationItems::route('/'),
'create' => Pages\CreateDocumentationItem::route('/create'),
'edit' => Pages\EditDocumentationItem::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\DocumentationItemResource\Pages;
use Filament\Resources\Pages\CreateRecord;
use App\Filament\Resources\DocumentationItemResource;
class CreateDocumentationItem extends CreateRecord
{
protected static string $resource = DocumentationItemResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DocumentationItemResource\Pages;
use Filament\Pages\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\DocumentationItemResource;
class EditDocumentationItem extends EditRecord
{
protected static string $resource = DocumentationItemResource::class;
protected function getActions(): array
{
return [
DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DocumentationItemResource\Pages;
use Filament\Pages\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\DocumentationItemResource;
class ListDocumentationItems extends ListRecords
{
protected static string $resource = DocumentationItemResource::class;
protected function getActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use App\Models\Package;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\CheckboxList;
use App\Filament\Resources\PackageResource\Pages;
use App\Filament\Resources\PackageResource\RelationManagers;
class PackageResource extends Resource
{
protected static ?string $model = Package::class;
protected static ?string $navigationIcon = 'heroicon-o-color-swatch';
protected static ?int $navigationSort = 3;
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('name')
->label(__('Name'))
->required()
->columnSpan(2),
TextInput::make('maximum_sites')
->helperText(__('Set to 0 for unlimited'))
->integer()
->required(),
TextInput::make('maximum_servers')
->helperText(__('Set to 0 for unlimited'))
->integer()
->required(),
TextInput::make('stripe_plan_id')
->helperText(__('Enter the pricing ID from Stripe here') . ' - <a href="https://docs.ploi-core.io/digging-deeper/using-stripe" target="ploi-docs-stripe" class="text-primary-500">How does this work?</a>')
->label(__('Stripe ID'))
->columnSpan(2),
TextInput::make('price_monthly')
->numeric()
->label(__('Monthly price'))
->helperText(__('Fill this in if you want it to be monthly payments'))
->required(),
TextInput::make('price_yearly')
->numeric()
->label(__('Yearly price'))
->helperText(__('Fill this in if you want it to be yearly payments'))
->required(),
Select::make('currency')
->label(__('Currency'))
->options([
'usd' => 'USD $',
'eur' => 'Euro €',
'gbp' => 'GBP £',
'nok' => 'NOK (Norwegian Krone)',
'aud' => 'AUD (Australian dollar)',
'cad' => 'CAD (Canadian dollar)',
'inr' => 'INR (Indian ₹ rupee)',
'thb' => 'THB (Thai Baht)',
'brl' => 'BRL R$ (Brazilian Real)',
])
->required(),
Grid::make()
->schema([
Section::make(__('Server permissions'))
->schema([
Checkbox::make('server_permissions.create')
->reactive()
->label('Allow server creation')
->helperText('This will allow users to create servers'),
Checkbox::make('server_permissions.update')
->label('Allow server updates')
->helperText('This will allow users to update servers'),
Checkbox::make('server_permissions.delete')
->label('Allow server deletion')
->helperText('This will allow users to delete servers'),
])
->columnSpan(1),
Section::make(__('Site permissions'))
->schema([
Checkbox::make('site_permissions.create')
->label('Allow site creation')
->helperText('This will allow users to create sites'),
Checkbox::make('site_permissions.update')
->label('Allow site updates')
->helperText('This will allow users to update sites'),
Checkbox::make('site_permissions.delete')
->label('Allow site deletion')
->helperText('This will allow users to delete sites'),
])
->columnSpan(1),
]),
Grid::make()
->schema([
Section::make(__('Available server providers'))
->description(__('These server providers will be available for users that are attached to this package.'))
->schema([
CheckboxList::make('providers')
->relationship('providers', 'name')
])
->columnSpan(1)
])
->visible(function ($get) {
return $get('server_permissions')['create'];
})
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('id')->label('ID')->searchable(),
Tables\Columns\TextColumn::make('name')
->label(__('Name'))
->description(function (Package $record) {
if (!$record->stripe_plan_id) {
return __('Not attached to Stripe.');
}
return "Attached to stripe - {$record->price_monthly} {$record->currency}";
}),
Tables\Columns\TextColumn::make('maximum_sites')
->formatStateUsing(fn (int $state) => $state === 0 ? __('Unlimited') : $state)
->label(__('Maximum sites')),
Tables\Columns\TextColumn::make('maximum_servers')
->formatStateUsing(fn (int $state) => $state === 0 ? __('Unlimited') : $state)
->label(__('Maximum servers')),
Tables\Columns\TextColumn::make('users_count')
->counts('users'),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
public static function getRelations(): array
{
return [
RelationManagers\UsersRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListPackages::route('/'),
'create' => Pages\CreatePackage::route('/create'),
'edit' => Pages\EditPackage::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\PackageResource\Pages;
use Filament\Resources\Pages\CreateRecord;
use App\Filament\Resources\PackageResource;
class CreatePackage extends CreateRecord
{
protected static string $resource = PackageResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\PackageResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\PackageResource;
class EditPackage extends EditRecord
{
protected static string $resource = PackageResource::class;
protected function getActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\PackageResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\PackageResource;
class ListPackages extends ListRecords
{
protected static string $resource = PackageResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Filament\Resources\PackageResource\RelationManagers;
use App\Models\User;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Tables\Actions\Action;
use Filament\Forms\Components\Select;
use App\Filament\Resources\UserResource;
use Filament\Resources\RelationManagers\RelationManager;
class UsersRelationManager extends RelationManager
{
protected static string $relationship = 'users';
protected static ?string $recordTitleAttribute = 'name';
public static function form(Form $form): Form
{
return UserResource::form($form);
}
public static function table(Table $table): Table
{
return UserResource::table($table)
->appendHeaderActions([
Action::make('add_user')
->label(__('Add user'))
->form(fn (self $livewire) => [
Select::make('user_id')
->label('User')
->options(User::orderBy('name')->get()->mapWithKeys(fn (User $user) => [$user->id => $user->name]))
->preload()
->searchable()
->required(),
])
->action(function (array $data, self $livewire) {
$user = User::find($data['user_id']);
$user->update([
'package_id' => $livewire->ownerRecord->id,
]);
})
->button(),
]);
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use App\Models\Provider;
use App\Models\ProviderPlan;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Filament\Forms\Components\TextInput;
use App\Filament\Resources\ProviderPlanResource\Pages;
class ProviderPlanResource extends Resource
{
protected static ?string $model = ProviderPlan::class;
protected static ?string $navigationIcon = 'heroicon-o-cube';
protected static ?string $navigationGroup = 'Providers';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('label'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('id')->label('ID')->searchable(),
Tables\Columns\TextColumn::make('provider.name')
->label(__('Provider'))
->searchable(),
Tables\Columns\TextColumn::make('plan_id')
->label(__('Plan ID'))
->searchable(),
Tables\Columns\TextColumn::make('label')
->label(__('Label'))
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->sortable()
->dateTime(),
])
->filters([
Tables\Filters\SelectFilter::make('provider_id')
->label(__('Provider'))
->options(fn () => Provider::orderBy('name')->get()->mapWithKeys(fn (Provider $provider) => [$provider->id => $provider->name])),
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListProviderPlans::route('/'),
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Filament\Resources\ProviderPlanResource\Pages;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\ProviderPlanResource;
class ListProviderPlans extends ListRecords
{
protected static string $resource = ProviderPlanResource::class;
protected function getActions(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use App\Models\Provider;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Models\ProviderRegion;
use Filament\Resources\Resource;
use App\Filament\Resources\ProviderRegionResource\Pages;
class ProviderRegionResource extends Resource
{
protected static ?string $model = ProviderRegion::class;
protected static ?string $navigationIcon = 'heroicon-o-globe';
protected static ?string $navigationGroup = 'Providers';
protected static ?int $navigationSort = 3;
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('id')->label('ID')->searchable(),
Tables\Columns\TextColumn::make('provider.name')
->label(__('Provider'))
->searchable(),
Tables\Columns\TextColumn::make('region_id')
->searchable()
->label(__('Region')),
Tables\Columns\TextColumn::make('label')
->searchable()
->label(__('Label')),
])
->filters([
Tables\Filters\SelectFilter::make('provider_id')
->label(__('Provider'))
->options(fn () => Provider::orderBy('name')->get()->mapWithKeys(fn (Provider $provider) => [$provider->id => $provider->name])),
])
->actions([
//
])
->bulkActions([
//
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListProviderRegions::route('/'),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\ProviderRegionResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\ProviderRegionResource;
class ListProviderRegions extends ListRecords
{
protected static string $resource = ProviderRegionResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Filament\Resources;
use Filament\Forms;
use Filament\Tables;
use App\Models\Provider;
use App\Models\ProviderPlan;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Filament\Notifications\Notification;
use Illuminate\Database\Eloquent\Builder;
use App\Filament\Resources\ProviderResource\Pages;
use App\Actions\Provider\SynchronizeProviderAction;
use App\Filament\Resources\ProviderResource\Widgets\AvailableProvidersOverview;
class ProviderResource extends Resource
{
protected static ?string $model = Provider::class;
protected static ?string $navigationIcon = 'heroicon-o-cloud-upload';
protected static ?string $navigationGroup = 'Providers';
protected static ?int $navigationSort = 1;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->label(__('Name'))
->required()
->columnSpan(2),
Forms\Components\CheckboxList::make('allowed_plans')
->options(function (Provider $record) {
return $record->plans->mapWithKeys(fn (ProviderPlan $plan) => [$plan->id => $plan->label ?? $plan->plan_id]);
})
->label(__('Allowed Plans')),
Forms\Components\CheckboxList::make('allowed_regions')
->options(fn (Provider $record) => $record->regions->pluck('label', 'id'))
->label(__('Allowed Regions')),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('id')->label('ID')->searchable(),
Tables\Columns\TextColumn::make('name')
->description(function (Provider $record) {
return "{$record->plans_count} plan(s) · {$record->regions_count} region(s)";
})
->label(__('Name'))
->searchable(),
Tables\Columns\TextColumn::make('label')
->label(__('Label'))
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->sortable()
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('synchronize_provider')
->label(__('Synchronize'))
->tooltip(__('This will synchronize the latest data from this provider to your Ploi Core installation'))
->icon('heroicon-o-refresh')
->action(function (Provider $record) {
$provider = app(SynchronizeProviderAction::class)->execute($record->ploi_id);
Notification::make()
->body(__('Provider :provider synchronized successfully.', ['provider' => $provider->name]))
->success()
->send();
}),
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withCount(['plans', 'regions']);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getWidgets(): array
{
return [
AvailableProvidersOverview::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListProviders::route('/'),
'synchronize' => Pages\SynchronizeProviders::route('/synchronize'),
'edit' => Pages\EditProvider::route('/{record}'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\ProviderResource\Pages;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\ProviderResource;
class EditProvider extends EditRecord
{
protected static string $resource = ProviderResource::class;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Filament\Resources\ProviderResource\Pages;
use Filament\Pages\Actions\Action;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\ProviderResource;
class ListProviders extends ListRecords
{
protected $listeners = [
'$refresh',
];
protected static string $resource = ProviderResource::class;
protected function getActions(): array
{
return [
Action::make('synchronize_providers')
->label(__('Synchronize providers'))
->icon('heroicon-o-refresh')
->color('secondary')
->url(route('filament.resources.providers.synchronize')),
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Filament\Resources\ProviderResource\Pages;
use Filament\Resources\Pages\Page;
use App\Filament\Resources\ProviderResource;
class SynchronizeProviders extends Page
{
protected static string $resource = ProviderResource::class;
protected static string $view = 'filament.resources.provider-resource.pages.synchronize-providers';
protected static ?string $title = 'Synchronize providers';
protected function getHeaderWidgets(): array
{
return [
ProviderResource\Widgets\AvailableProvidersOverview::class,
];
}
protected function getActions(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Filament\Resources\ProviderResource\Widgets;
use App\Models\AvailableProvider;
use Filament\Widgets\TableWidget;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\TextColumn;
use Filament\Notifications\Notification;
use Illuminate\Database\Eloquent\Builder;
use App\Actions\Provider\SynchronizeProviderAction;
use Illuminate\Database\Eloquent\Relations\Relation;
class AvailableProvidersOverview extends TableWidget
{
protected $listeners = [
'$refresh',
];
protected int|string|array $columnSpan = 'full';
protected static ?string $heading = 'Available Providers';
protected function getTableDescription(): ?string
{
return 'These are all the providers available from your ploi.io account which you can synchronize to your Ploi Core installation.';
}
protected function getTableQuery(): Builder|Relation
{
return AvailableProvider::query();
}
protected function getTableColumns(): array
{
return [
TextColumn::make('name')
->label(__('Name')),
TextColumn::make('label')
->label(__('Label')),
];
}
protected function getTableActions(): array
{
return [
Action::make('synchronize_provider')
->label(__('Synchronize'))
->icon('heroicon-o-refresh')
->action(function (AvailableProvider $record, self $livewire) {
$provider = app(SynchronizeProviderAction::class)->execute($record->id);
$livewire->emit('$refresh');
Notification::make()
->body(__('Provider :provider synchronized successfully.', ['provider' => $provider->name]))
->success()
->send();
}),
];
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Filament\Resources;
use Filament\Forms;
use Filament\Tables;
use App\Models\Redirect;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use App\Filament\Resources\RedirectResource\Pages;
class RedirectResource extends Resource
{
protected static ?string $model = Redirect::class;
protected static ?string $navigationIcon = 'heroicon-o-external-link';
protected static ?string $navigationGroup = 'Site management';
protected static ?int $navigationSort = 5;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('site_id'),
Forms\Components\TextInput::make('server_id'),
Forms\Components\TextInput::make('status')
->maxLength(255),
Forms\Components\TextInput::make('ploi_id'),
Forms\Components\TextInput::make('redirect_from')
->maxLength(255),
Forms\Components\TextInput::make('redirect_to')
->maxLength(255),
Forms\Components\TextInput::make('type')
->maxLength(255),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('redirect_from')
->wrap()
->label(__('Redirect from'))
->searchable(),
Tables\Columns\TextColumn::make('redirect_to')
->wrap()
->label(__('Redirect to'))
->searchable(),
Tables\Columns\TextColumn::make('server.name')
->label(__('Server'))
->searchable(),
Tables\Columns\TextColumn::make('site.domain')
->label(__('Site'))
->searchable(),
Tables\Columns\TextColumn::make('type')
->label(__('Type')),
Tables\Columns\BadgeColumn::make('status')
->enum([
Redirect::STATUS_BUSY => __('Busy'),
Redirect::STATUS_ACTIVE => __('Active'),
])
->colors([
'warning' => Redirect::STATUS_BUSY,
'success' => Redirect::STATUS_ACTIVE,
])
->label(__('Status')),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->sortable()
->dateTime(),
])
->filters([
//
])
->actions([
//
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRedirects::route('/'),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\RedirectResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\RedirectResource;
class ListRedirects extends ListRecords
{
protected static string $resource = RedirectResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace App\Filament\Resources;
use Filament\Forms;
use App\Models\User;
use Filament\Tables;
use App\Models\Server;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Illuminate\Support\HtmlString;
use Illuminate\Database\Eloquent\Builder;
use App\Actions\Server\SynchronizeServerAction;
use App\Filament\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource\RelationManagers;
class ServerResource extends Resource
{
protected static ?string $model = Server::class;
protected static ?string $navigationIcon = 'heroicon-o-server';
protected static ?string $navigationGroup = 'Server management';
protected static ?string $recordTitleAttribute = 'name';
public static function getGloballySearchableAttributes(): array
{
return ['name', 'ip', 'internal_ip', 'id'];
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->label(__('Name'))
->columnSpan(2),
Forms\Components\TextInput::make('ip')
->required()
->label('IP address')
->columnSpan(2),
Forms\Components\TextInput::make('internal_ip')
->required()
->label('Internal IP address')
->columnSpan(2),
Forms\Components\TextInput::make('maximum_sites')
->label(__('Maximum sites'))
->integer()
->required()
->columnSpan(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->label(__('Name'))
->searchable(),
Tables\Columns\BadgeColumn::make('status')
->label(__('Status'))
->enum([
Server::STATUS_BUSY => __('Busy'),
Server::STATUS_ACTIVE => __('Active'),
])
->colors([
'warning' => Server::STATUS_BUSY,
'success' => Server::STATUS_ACTIVE,
]),
Tables\Columns\TextColumn::make('users')
->label(__('Users'))
->wrap()
->getStateUsing(function (Server $record) {
$state = $record
->users
->map(function (User $user) {
return '<a href="' . route('filament.resources.users.edit', ['record' => $user]) . '" class="text-primary-600">' . $user->name . '</a>';
})
->implode(', ') ?: '-';
return new HtmlString($state);
}),
Tables\Columns\TextColumn::make('maximum_sites')
->label(__('Max sites'))
->formatStateUsing(fn (Server $record) => $record->maximum_sites . " (Current: {$record->sites_count})"),
Tables\Columns\TextColumn::make('ip')
->label(__('IP')),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->dateTime(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('synchronize_server')
->label(__('Synchronize'))
->tooltip(__('This will synchronize the latest data from this provider to your Ploi Core installation'))
->icon('heroicon-o-refresh')
->action(fn (Server $record) => app(SynchronizeServerAction::class)->execute($record->ploi_id))
->visible(fn (Server $record) => $record->status === Server::STATUS_ACTIVE),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->with(['users:id,name'])
->withCount(['sites']);
}
public static function getRelations(): array
{
return [
RelationManagers\UsersRelationManager::class,
RelationManagers\SitesRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListServers::route('/'),
'edit' => Pages\EditServer::route('/{record}/edit'),
'synchronize' => Pages\SynchronizeServers::route('/synchronize'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource;
use Filament\Resources\Pages\CreateRecord;
class CreateServer extends CreateRecord
{
protected static string $resource = ServerResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\EditRecord;
use App\Filament\Resources\ServerResource;
class EditServer extends EditRecord
{
protected static string $resource = ServerResource::class;
protected function getActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
use Filament\Pages\Actions\Action;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
use App\Filament\Resources\ServerResource;
class ListServers extends ListRecords
{
protected static string $resource = ServerResource::class;
protected function getActions(): array
{
return [
Action::make('synchronize_servers')
->label(__('Synchronize servers'))
->icon('heroicon-o-refresh')
->color('secondary')
->url(route('filament.resources.servers.synchronize')),
...parent::getActions(),
];
}
protected function applySearchToTableQuery(Builder $query): Builder
{
if (filled($searchTerm = $this->getTableSearchQuery())) {
$query
->where('domain', 'LIKE', "%{$searchTerm}%")
->orWhereHas('users', fn (Builder $query) => $query->where('name', 'LIKE', "%{$searchTerm}%"))
->orWhereHas('users', fn (Builder $query) => $query->where('email', 'LIKE', "%{$searchTerm}%"));
}
return $query;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Filament\Resources\ServerResource\Pages;
use App\Models\Server;
use App\Services\Ploi\Ploi;
use Filament\Pages\Actions\Action;
use Filament\Resources\Pages\Page;
use Filament\Notifications\Notification;
use App\Filament\Resources\ServerResource;
class SynchronizeServers extends Page
{
protected static string $resource = ServerResource::class;
protected static string $view = 'filament.resources.server-resource.pages.synchronize-servers';
protected static ?string $title = 'Synchronize servers';
protected function getHeaderWidgets(): array
{
return [
ServerResource\Widgets\AvailableServersOverview::class,
];
}
protected function getActions(): array
{
return [
Action::make('synchronize_servers')
->label(__('Synchronize all servers'))
->icon('heroicon-o-refresh')
->requiresConfirmation()
->modalHeading('Synchronize servers')
->modalSubheading('This will synchronize all the servers that are listed in the table, to your Ploi Core installation.')
->action(function () {
$availableServers = Ploi::make()->synchronize()->servers()->getData();
foreach ($availableServers as $availableServer) {
Server::query()
->updateOrCreate([
'ploi_id' => $availableServer->id,
], [
'status' => $availableServer->status,
'name' => $availableServer->name,
'ip' => $availableServer->ip_address,
'ssh_port' => $availableServer->ssh_port,
'internal_ip' => $availableServer->internal_ip,
'available_php_versions' => $availableServer->installed_php_versions,
]);
}
Notification::make()
->body(__('Servers synchronized successfully.'))
->success()
->send();
}),
];
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Filament\Resources\ServerResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\SiteResource;
use Filament\Resources\RelationManagers\RelationManager;
class SitesRelationManager extends RelationManager
{
protected static string $relationship = 'sites';
protected static ?string $recordTitleAttribute = 'domain';
public static function getLabel(): ?string
{
return __('Site');
}
protected static function getPluralModelLabel(): string
{
return __('Sites');
}
public static function form(Form $form): Form
{
return SiteResource::form($form);
}
public static function table(Table $table): Table
{
return SiteResource::table($table);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Filament\Resources\ServerResource\RelationManagers;
use Filament\Tables;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\UserResource;
use Filament\Resources\RelationManagers\RelationManager;
class UsersRelationManager extends RelationManager
{
protected static string $relationship = 'users';
protected static ?string $recordTitleAttribute = 'name';
public static function getLabel(): ?string
{
return __('User');
}
protected static function getPluralModelLabel(): string
{
return __('Users');
}
public static function form(Form $form): Form
{
return UserResource::form($form);
}
public static function table(Table $table): Table
{
return UserResource::table($table)
->appendHeaderActions([
Tables\Actions\AttachAction::make()->preloadRecordSelect(),
])
->appendActions([
Tables\Actions\DetachAction::make(),
]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Filament\Resources\ServerResource\Widgets;
use App\Models\AvailableServer;
use Filament\Widgets\TableWidget;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
use App\Actions\Server\SynchronizeServerAction;
use Illuminate\Database\Eloquent\Relations\Relation;
class AvailableServersOverview extends TableWidget
{
protected $listeners = [
'$refresh',
];
protected int|string|array $columnSpan = 'full';
protected static ?string $heading = 'Available servers';
protected function getTableQuery(): Builder|Relation
{
return AvailableServer::query();
}
protected function getTableDescription(): ?string
{
return 'These are all the servers available from your ploi.io account which you can synchronize to your Ploi Core installation.';
}
protected function getTableColumns(): array
{
return [
TextColumn::make('name')->label(__('Name')),
TextColumn::make('ip_address')->label(__('IP address')),
TextColumn::make('sites_count')->label(__('Sites')),
];
}
protected function getTableActions(): array
{
return [
Action::make('synchronize_server')
->label(__('Synchronize'))
->icon('heroicon-o-refresh')
->action(function (AvailableServer $record) {
app(SynchronizeServerAction::class)->execute($record->id);
}),
];
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace App\Filament\Resources;
use Filament\Forms;
use App\Models\Site;
use App\Models\User;
use Filament\Tables;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Illuminate\Support\HtmlString;
use App\Actions\Site\SynchronizeSiteAction;
use App\Filament\Resources\SiteResource\Pages;
use App\Filament\Resources\SiteResource\RelationManagers;
class SiteResource extends Resource
{
protected static ?string $model = Site::class;
protected static ?string $navigationIcon = 'heroicon-o-code';
protected static ?string $navigationGroup = 'Site management';
protected static ?int $navigationSort = 0;
protected static ?string $recordTitleAttribute = 'domain';
/**
* @return string|null
*/
public static function getLabel(): ?string
{
return __('Site');
}
public static function getPluralLabel(): ?string
{
return __('Sites');
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('domain')
->label(__('Domain'))
->required()
->hostname()
->unique(Site::class, column: 'domain', ignoreRecord: true)
->columnSpan(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('domain')
->description(function (Site $record) {
return "PHP $record->php_version";
})
->label(__('Name'))
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('server.name')
->label(__('Server'))
->searchable(),
Tables\Columns\BadgeColumn::make('status')
->enum([
Site::STATUS_BUSY => __('Busy'),
Site::STATUS_ACTIVE => __('Active'),
])
->colors([
'warning' => Site::STATUS_BUSY,
'success' => Site::STATUS_ACTIVE,
])
->label(__('Status')),
Tables\Columns\TextColumn::make('users')
->label(__('Users'))
->getStateUsing(function (Site $record) {
$state = $record
->users
->map(function (User $user) {
return '<a href="' . route('filament.resources.users.edit', ['record' => $user]) . '" class="text-primary-600">' . $user->name . '</a>';
})
->implode(', ') ?: '-';
return new HtmlString($state);
}),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->dateTime()
->sortable(),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\Action::make('synchronize_site')
->label(__('Synchronize'))
->tooltip(__('This will synchronize the latest data from this provider to your Ploi Core installation'))
->icon('heroicon-o-refresh')
->action(function (Site $record) {
app(SynchronizeSiteAction::class)->execute($record->server->ploi_id, $record->ploi_id);
})
->visible(fn (Site $record) => $record->status === Site::STATUS_ACTIVE),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('sites.created_at', 'desc');
}
public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder
{
return parent::getEloquentQuery()
->with(['users', 'server']);
}
public static function getRelations(): array
{
return [
RelationManagers\UsersRelationManager::class,
RelationManagers\CertificatesRelationManager::class,
RelationManagers\CronjobsRelationManager::class,
RelationManagers\RedirectsRelationManager::class,
RelationManagers\SystemUsersRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSites::route('/'),
'edit' => Pages\EditSite::route('/{record}/edit'),
'synchronize' => Pages\SynchronizeSites::route('/synchronize'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\SiteResource\Pages;
use App\Filament\Resources\SiteResource;
use Filament\Resources\Pages\CreateRecord;
class CreateSite extends CreateRecord
{
protected static string $resource = SiteResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SiteResource\Pages;
use Filament\Pages\Actions;
use App\Filament\Resources\SiteResource;
use Filament\Resources\Pages\EditRecord;
class EditSite extends EditRecord
{
protected static string $resource = SiteResource::class;
protected function getActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Filament\Resources\SiteResource\Pages;
use App\Traits\HasPloi;
use Filament\Pages\Actions\Action;
use App\Filament\Resources\SiteResource;
use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
class ListSites extends ListRecords
{
use HasPloi;
protected static string $resource = SiteResource::class;
protected function getActions(): array
{
return [
Action::make('synchronize_sites')
->label(__('Synchronize sites'))
->icon('heroicon-o-refresh')
->color('secondary')
->url(route('filament.resources.sites.synchronize')),
...parent::getActions()
];
}
protected function applySearchToTableQuery(Builder $query): Builder
{
if (filled($searchTerm = $this->getTableSearchQuery())) {
$query
->where('domain', 'LIKE', "%{$searchTerm}%")
->orWhereHas('users', fn (Builder $query) => $query->where('name', 'LIKE', "%{$searchTerm}%"))
->orWhereHas('users', fn (Builder $query) => $query->where('email', 'LIKE', "%{$searchTerm}%"));
}
return $query;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Filament\Resources\SiteResource\Pages;
use App\Models\Site;
use App\Models\Server;
use App\Services\Ploi\Ploi;
use Filament\Pages\Actions\Action;
use Filament\Resources\Pages\Page;
use App\Filament\Resources\SiteResource;
use Filament\Notifications\Notification;
class SynchronizeSites extends Page
{
protected static string $resource = SiteResource::class;
protected static string $view = 'filament.resources.site-resource.pages.synchronize-sites';
public function getHeaderWidgets(): array
{
return [
SiteResource\Widgets\AvailableSitesOverview::class,
];
}
protected function getActions(): array
{
return [
Action::make('synchronize_sites')
->label(__('Synchronize all sites'))
->icon('heroicon-o-refresh')
->requiresConfirmation()
->modalHeading('Synchronize sites')
->modalSubheading('This will synchronize all the sites that are listed in the table, to your Ploi Core installation.')
->action(function () {
$availableSites = Ploi::make()->synchronize()->sites()->getData();
foreach ($availableSites as $availableSite) {
$server = Server::query()->where('ploi_id', $availableSite->server_id)->firstOrFail();
$site = Site::query()
->updateOrCreate([
'ploi_id' => $availableSite->id,
], [
'domain' => $availableSite->domain,
'php_version' => $availableSite->php_version,
]);
$site->status = $availableSite->status;
$site->server_id = $server->id;
$site->save();
}
Notification::make()
->body(__('Sites synchronized successfully.'))
->success()
->send();
}),
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\SiteResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\CertificateResource;
use Filament\Resources\RelationManagers\RelationManager;
class CertificatesRelationManager extends RelationManager
{
protected static string $relationship = 'certificates';
protected static ?string $recordTitleAttribute = 'domain';
public static function form(Form $form): Form
{
return CertificateResource::form($form);
}
public static function table(Table $table): Table
{
return CertificateResource::table($table);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\SiteResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\CronjobResource;
use Filament\Resources\RelationManagers\RelationManager;
class CronjobsRelationManager extends RelationManager
{
protected static string $relationship = 'cronjobs';
protected static ?string $recordTitleAttribute = 'command';
public static function form(Form $form): Form
{
return CronjobResource::form($form);
}
public static function table(Table $table): Table
{
return CronjobResource::table($table);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\SiteResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\DatabaseResource;
use Filament\Resources\RelationManagers\RelationManager;
class DatabasesRelationManager extends RelationManager
{
protected static string $relationship = 'databases';
protected static ?string $recordTitleAttribute = 'name';
public static function form(Form $form): Form
{
return DatabaseResource::form($form);
}
public static function table(Table $table): Table
{
return DatabaseResource::table($table);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\SiteResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\RedirectResource;
use Filament\Resources\RelationManagers\RelationManager;
class RedirectsRelationManager extends RelationManager
{
protected static string $relationship = 'redirects';
protected static ?string $recordTitleAttribute = 'from';
public static function form(Form $form): Form
{
return RedirectResource::form($form);
}
public static function table(Table $table): Table
{
return RedirectResource::table($table);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\SiteResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\SiteSystemUserResource;
use Filament\Resources\RelationManagers\RelationManager;
class SystemUsersRelationManager extends RelationManager
{
protected static string $relationship = 'systemUsers';
protected static ?string $recordTitleAttribute = 'user_name';
public static function form(Form $form): Form
{
return SiteSystemUserResource::form($form);
}
public static function table(Table $table): Table
{
return SiteSystemUserResource::table($table);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Filament\Resources\SiteResource\RelationManagers;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Filament\Resources\UserResource;
use Filament\Tables\Actions\AttachAction;
use Filament\Tables\Actions\DetachAction;
use Filament\Resources\RelationManagers\RelationManager;
class UsersRelationManager extends RelationManager
{
protected static string $relationship = 'users';
protected static ?string $recordTitleAttribute = 'name';
public static function getLabel(): ?string
{
return __('User');
}
protected static function getPluralModelLabel(): string
{
return __('Users');
}
public static function form(Form $form): Form
{
return UserResource::form($form);
}
public static function table(Table $table): Table
{
return UserResource::table($table)
->headerActions([
AttachAction::make()
->preloadRecordSelect(),
])
->appendActions([
DetachAction::make(),
]);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Filament\Resources\SiteResource\Widgets;
use App\Models\AvailableSite;
use Filament\Widgets\TableWidget;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\TextColumn;
use Illuminate\Database\Eloquent\Builder;
use App\Actions\Site\SynchronizeSiteAction;
use Illuminate\Database\Eloquent\Relations\Relation;
class AvailableSitesOverview extends TableWidget
{
protected $listeners = [
'$refresh',
];
protected int|string|array $columnSpan = 'full';
protected static ?string $heading = 'Available sites';
protected function getTableDescription(): ?string
{
return 'These are all the sites available from your ploi.io account which you can synchronize to your Ploi Core installation.';
}
protected function getTableQuery(): Builder|Relation
{
return AvailableSite::query();
}
protected function getTableColumns(): array
{
return [
TextColumn::make('domain')->label(__('Site')),
TextColumn::make('system_user')->label(__('System user')),
];
}
protected function getTableActions(): array
{
return [
Action::make('synchronize_site')
->label(__('Synchronize'))
->icon('heroicon-o-refresh')
->action(function (AvailableSite $record) {
app(SynchronizeSiteAction::class)->execute(ploiServerId: $record->server_id, ploiSiteId: $record->id);
}),
];
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use Filament\Resources\Form;
use Filament\Resources\Table;
use App\Models\SiteSystemUser;
use Filament\Resources\Resource;
use App\Filament\Resources\SiteSystemUserResource\Pages;
class SiteSystemUserResource extends Resource
{
protected static ?string $model = SiteSystemUser::class;
protected static ?string $navigationIcon = 'heroicon-o-user-group';
protected static ?string $navigationGroup = 'Site management';
protected static ?int $navigationSort = 6;
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('user_name')
->label(__('Username'))
->searchable(),
Tables\Columns\TextColumn::make('site.domain')
->label(__('Site'))
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->label(__('Date'))
->dateTime()
->sortable(),
])
->filters([
//
])
->actions([
//
])
->bulkActions([
//
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSiteSystemUsers::route('/'),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SiteSystemUserResource\Pages;
use Filament\Pages\Actions;
use Filament\Resources\Pages\ListRecords;
use App\Filament\Resources\SiteSystemUserResource;
class ListSiteSystemUsers extends ListRecords
{
protected static string $resource = SiteSystemUserResource::class;
protected function getActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Filament\Resources;
use Filament\Tables;
use Filament\Resources\Form;
use Filament\Resources\Table;
use Filament\Resources\Resource;
use Laravel\Cashier\Subscription;
use App\Filament\Resources\SubscriptionResource\Pages;
class SubscriptionResource extends Resource
{
protected static ?string $model = Subscription::class;
protected static ?string $navigationIcon = 'heroicon-o-cash';
protected static ?int $navigationSort = 4;
public static function getSlug(): string
{
return 'subscriptions';
}
public static function form(Form $form): Form
{
return $form
->schema([
//
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('user.name')
->searchable()
->url(fn ($record) => UserResource::getUrl('edit', ['record' => $record])),
Tables\Columns\TextColumn::make('stripe_id')->searchable(),
Tables\Columns\TextColumn::make('stripe_plan')->searchable(),
Tables\Columns\BadgeColumn::make('stripe_status')
->label('Status')
->colors([
'success' => \Stripe\Subscription::STATUS_ACTIVE,
'warning' => \Stripe\Subscription::STATUS_PAST_DUE,
]),
Tables\Columns\TextColumn::make('created_at')
->sortable()
->dateTime()
->label(__('Date'))
])
->filters([
//
])
->actions([
// Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
])
->defaultSort('created_at', 'desc');
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSubscriptions::route('/'),
// 'create' => Pages\CreateSubscription::route('/create'),
// 'edit' => Pages\EditSubscription::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\SubscriptionResource\Pages;
use Filament\Resources\Pages\CreateRecord;
use App\Filament\Resources\SubscriptionResource;
class CreateSubscription extends CreateRecord
{
protected static string $resource = SubscriptionResource::class;
}

Some files were not shown because too many files have changed in this diff Show More