mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
Add update lookup and telemetry, Add version and build to app config
This commit is contained in:
committed by
Gregor Vostrak
parent
f147fb9725
commit
2372ee0622
@@ -1,4 +1,6 @@
|
|||||||
APP_NAME=solidtime
|
APP_NAME=solidtime
|
||||||
|
APP_VERSION=0.0.0
|
||||||
|
APP_BUILD=0
|
||||||
VITE_APP_NAME=solidtime
|
VITE_APP_NAME=solidtime
|
||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
|
|||||||
46
.github/workflows/build-private.yml
vendored
46
.github/workflows/build-private.yml
vendored
@@ -20,15 +20,55 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: "Check out code"
|
- name: "Check out code"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||||
|
|
||||||
|
- name: "Get build"
|
||||||
|
id: build
|
||||||
|
run: echo "build=$(git rev-parse --short=8 HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: "Get Previous tag (normal push)"
|
||||||
|
id: previoustag
|
||||||
|
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
|
with:
|
||||||
|
prefix: "v"
|
||||||
|
|
||||||
|
- name: "Get version"
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if ${{ !startsWith(github.ref, 'refs/tags/v') }}; then
|
||||||
|
if ${{ startsWith(steps.previoustag.outputs.tag, 'v') }}; then
|
||||||
|
version=$(echo "${{ steps.previoustag.outputs.tag }}" | cut -c 2-)
|
||||||
|
echo "app_version=${version}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "ERROR: No previous tag found";
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
version=$(echo "${{ github.ref }}" | cut -c 12-)
|
||||||
|
echo "app_version=${version}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: "Copy .env template for production"
|
||||||
|
run: |
|
||||||
|
cp .env.production .env
|
||||||
|
rm .env.production .env.ci .env.example
|
||||||
|
|
||||||
|
- name: "Add version to .env"
|
||||||
|
run: sed -i 's/APP_VERSION=0.0.0/APP_VERSION=${{ steps.version.outputs.app_version }}/g' .env
|
||||||
|
|
||||||
|
- name: "Add build to .env"
|
||||||
|
run: sed -i 's/APP_BUILD=0/APP_BUILD=${{ steps.build.outputs.build }}/g' .env
|
||||||
|
|
||||||
|
- name: "Output .env"
|
||||||
|
run: cat .env
|
||||||
|
|
||||||
- name: "Use Node.js"
|
- name: "Use Node.js"
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
- name: "Copy .env template for production"
|
|
||||||
run: cp .env.production .env && cat .env
|
|
||||||
|
|
||||||
- name: "Checkout billing extension"
|
- name: "Checkout billing extension"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
42
.github/workflows/build-public.yml
vendored
42
.github/workflows/build-public.yml
vendored
@@ -25,9 +25,49 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: "Check out code"
|
- name: "Check out code"
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||||
|
|
||||||
|
- name: "Get build"
|
||||||
|
id: build
|
||||||
|
run: echo "build=$(git rev-parse --short=8 HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: "Get Previous tag (normal push)"
|
||||||
|
id: previoustag
|
||||||
|
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
|
with:
|
||||||
|
prefix: "v"
|
||||||
|
|
||||||
|
- name: "Get version"
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if ${{ !startsWith(github.ref, 'refs/tags/v') }}; then
|
||||||
|
if ${{ startsWith(steps.previoustag.outputs.tag, 'v') }}; then
|
||||||
|
version=$(echo "${{ steps.previoustag.outputs.tag }}" | cut -c 2-)
|
||||||
|
echo "app_version=${version}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "ERROR: No previous tag found";
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
version=$(echo "${{ github.ref }}" | cut -c 12-)
|
||||||
|
echo "app_version=${version}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: "Copy .env template for production"
|
- name: "Copy .env template for production"
|
||||||
run: cp .env.production .env
|
run: |
|
||||||
|
cp .env.production .env
|
||||||
|
rm .env.production .env.ci .env.example
|
||||||
|
|
||||||
|
- name: "Add version to .env"
|
||||||
|
run: sed -i 's/APP_VERSION=0.0.0/APP_VERSION=${{ steps.version.outputs.app_version }}/g' .env
|
||||||
|
|
||||||
|
- name: "Add build to .env"
|
||||||
|
run: sed -i 's/APP_BUILD=0/APP_BUILD=${{ steps.build.outputs.build }}/g' .env
|
||||||
|
|
||||||
|
- name: "Output .env"
|
||||||
|
run: cat .env
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
uses: php-actions/composer@v6
|
uses: php-actions/composer@v6
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands\SelfHost;
|
||||||
|
|
||||||
|
use App\Service\ApiService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class SelfHostCheckForUpdateCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'self-host:check-for-update';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$apiService = app(ApiService::class);
|
||||||
|
|
||||||
|
$latestVersion = $apiService->checkForUpdate();
|
||||||
|
if ($latestVersion === null) {
|
||||||
|
$this->error('Failed to check for update, check the logs for more information.');
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Cache for 13 hours, because the command runs twice daily (every 12 hours).
|
||||||
|
Cache::put('latest_version', $latestVersion, 60 * 60 * 12);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Console/Commands/SelfHost/SelfHostTelemetryCommand.php
Normal file
44
app/Console/Commands/SelfHost/SelfHostTelemetryCommand.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands\SelfHost;
|
||||||
|
|
||||||
|
use App\Service\ApiService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class SelfHostTelemetryCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'self-host:telemetry';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$apiService = app(ApiService::class);
|
||||||
|
|
||||||
|
$success = $apiService->telemetry();
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
$this->error('Failed to send telemetry data, check the logs for more information.');
|
||||||
|
|
||||||
|
return self::FAILURE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,14 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->command('time-entry:send-still-running-mails')
|
$schedule->command('time-entry:send-still-running-mails')
|
||||||
->when(fn (): bool => config('scheduling.tasks.time_entry_send_still_running_mails'))
|
->when(fn (): bool => config('scheduling.tasks.time_entry_send_still_running_mails'))
|
||||||
->everyTenMinutes();
|
->everyTenMinutes();
|
||||||
|
|
||||||
|
$schedule->command('self-hosting:check-for-update')
|
||||||
|
->when(fn (): bool => config('scheduling.tasks.self_hosting_check_for_update'))
|
||||||
|
->twiceDaily();
|
||||||
|
|
||||||
|
$schedule->command('self-hosting:telemetry')
|
||||||
|
->when(fn (): bool => config('scheduling.tasks.self_hosting_telemetry'))
|
||||||
|
->twiceDaily();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
34
app/Filament/Widgets/ServerOverview.php
Normal file
34
app/Filament/Widgets/ServerOverview.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Widgets;
|
||||||
|
|
||||||
|
use Filament\Widgets\Widget;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class ServerOverview extends Widget
|
||||||
|
{
|
||||||
|
protected static string $view = 'filament.widgets.server-overview';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected function getViewData(): array
|
||||||
|
{
|
||||||
|
$currentVersion = Cache::get('latest_version', null);
|
||||||
|
|
||||||
|
$needsUpdate = false;
|
||||||
|
if ($currentVersion !== null && version_compare($currentVersion, config('app.version')) > 0) {
|
||||||
|
$needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'version' => config('app.version'),
|
||||||
|
'build' => config('app.build'),
|
||||||
|
'environment' => config('app.env'),
|
||||||
|
'currentVersion' => $currentVersion,
|
||||||
|
'needsUpdate' => $needsUpdate,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Providers\Filament;
|
namespace App\Providers\Filament;
|
||||||
|
|
||||||
use App\Filament\Widgets\ActiveUserOverview;
|
use App\Filament\Widgets\ActiveUserOverview;
|
||||||
|
use App\Filament\Widgets\ServerOverview;
|
||||||
use App\Filament\Widgets\TimeEntriesCreated;
|
use App\Filament\Widgets\TimeEntriesCreated;
|
||||||
use App\Filament\Widgets\TimeEntriesImported;
|
use App\Filament\Widgets\TimeEntriesImported;
|
||||||
use App\Filament\Widgets\UserRegistrations;
|
use App\Filament\Widgets\UserRegistrations;
|
||||||
@@ -44,11 +45,13 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
])
|
])
|
||||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||||
->widgets([
|
->widgets([
|
||||||
|
ServerOverview::class,
|
||||||
ActiveUserOverview::class,
|
ActiveUserOverview::class,
|
||||||
UserRegistrations::class,
|
UserRegistrations::class,
|
||||||
TimeEntriesCreated::class,
|
TimeEntriesCreated::class,
|
||||||
TimeEntriesImported::class,
|
TimeEntriesImported::class,
|
||||||
])
|
])
|
||||||
|
->viteTheme('resources/css/filament/admin/theme.css')
|
||||||
->plugins([
|
->plugins([
|
||||||
EnvironmentIndicatorPlugin::make()
|
EnvironmentIndicatorPlugin::make()
|
||||||
->color(fn () => match (App::environment()) {
|
->color(fn () => match (App::environment()) {
|
||||||
|
|||||||
93
app/Service/ApiService.php
Normal file
93
app/Service/ApiService.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Models\Audit;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\Organization;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\ProjectMember;
|
||||||
|
use App\Models\Task;
|
||||||
|
use App\Models\TimeEntry;
|
||||||
|
use App\Models\User;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
class ApiService
|
||||||
|
{
|
||||||
|
private const string API_URL = 'https://app.solidtime.io/api';
|
||||||
|
|
||||||
|
public function checkForUpdate(): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = Http::asJson()
|
||||||
|
->timeout(3)
|
||||||
|
->connectTimeout(2)
|
||||||
|
->post(self::API_URL.'/check-for-update', [
|
||||||
|
'version' => config('app.version'),
|
||||||
|
'build' => config('app.build'),
|
||||||
|
'url' => config('app.url'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->status() === 200 && isset($response->json()['version']) && is_string($response->json()['version'])) {
|
||||||
|
return $response->json()['version'];
|
||||||
|
} else {
|
||||||
|
Log::warning('Failed to check for update', [
|
||||||
|
'status' => $response->status(),
|
||||||
|
'body' => $response->body(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('Failed to check for update', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function telemetry(): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = Http::asJson()
|
||||||
|
->timeout(3)
|
||||||
|
->connectTimeout(2)
|
||||||
|
->post(self::API_URL.'/telemetry', [
|
||||||
|
'version' => config('app.version'),
|
||||||
|
'build' => config('app.build'),
|
||||||
|
'url' => config('app.url'),
|
||||||
|
// telemetry data
|
||||||
|
'user_count' => User::count(),
|
||||||
|
'organization_count' => Organization::count(),
|
||||||
|
'audit_count' => Audit::count(),
|
||||||
|
'project_count' => Project::count(),
|
||||||
|
'project_member_count' => ProjectMember::count(),
|
||||||
|
'client_count' => Client::count(),
|
||||||
|
'task_count' => Task::count(),
|
||||||
|
'time_entry_count' => TimeEntry::count(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->status() === 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Log::warning('Failed send telemetry data', [
|
||||||
|
'status' => $response->status(),
|
||||||
|
'body' => $response->body(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::warning('Failed send telemetry data', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
"name": "solidtime-io/solidtime",
|
"name": "solidtime-io/solidtime",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "An open-source time-tracking app",
|
"description": "An open-source time-tracking app",
|
||||||
"version": "0.0.1",
|
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"require": {
|
"require": {
|
||||||
@@ -29,7 +28,7 @@
|
|||||||
"spatie/temporary-directory": "^2.2",
|
"spatie/temporary-directory": "^2.2",
|
||||||
"stechstudio/filament-impersonate": "^3.8",
|
"stechstudio/filament-impersonate": "^3.8",
|
||||||
"tightenco/ziggy": "^2.1.0",
|
"tightenco/ziggy": "^2.1.0",
|
||||||
"tpetry/laravel-postgresql-enhanced": "^1.0.0",
|
"tpetry/laravel-postgresql-enhanced": "^2.0.0",
|
||||||
"wikimedia/composer-merge-plugin": "^2.1.0"
|
"wikimedia/composer-merge-plugin": "^2.1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|||||||
1172
composer.lock
generated
1172
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,11 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'solidtime'),
|
'name' => 'solidtime',
|
||||||
|
|
||||||
|
'version' => env('APP_VERSION'),
|
||||||
|
|
||||||
|
'build' => env('APP_BUILD'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ return [
|
|||||||
|
|
||||||
'tasks' => [
|
'tasks' => [
|
||||||
'time_entry_send_still_running_mails' => (bool) env('SCHEDULING_TASK_TIME_ENTRY_SEND_STILL_RUNNING_MAILS', true),
|
'time_entry_send_still_running_mails' => (bool) env('SCHEDULING_TASK_TIME_ENTRY_SEND_STILL_RUNNING_MAILS', true),
|
||||||
|
'self_hosting_check_for_update' => (bool) env('SCHEDULING_TASK_SELF_HOSTING_CHECK_FOR_UPDATE', true),
|
||||||
|
'self_hosting_telemetry' => (bool) env('SCHEDULING_TASK_SELF_HOSTING_TELEMETRY', true),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
66
package-lock.json
generated
66
package-lock.json
generated
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "html",
|
"name": "solidtime",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -28,19 +28,19 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "^1.0.0",
|
"@inertiajs/vue3": "^1.0.0",
|
||||||
"@playwright/test": "^1.41.1",
|
"@playwright/test": "^1.41.1",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.11.5",
|
||||||
"@vitejs/plugin-vue": "^4.5.0",
|
"@vitejs/plugin-vue": "^4.5.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.6.4",
|
"axios": "^1.6.4",
|
||||||
"eslint-plugin-unused-imports": "^3.1.0",
|
"eslint-plugin-unused-imports": "^3.1.0",
|
||||||
"laravel-vite-plugin": "^1.0.0",
|
"laravel-vite-plugin": "^1.0.0",
|
||||||
"openapi-zod-client": "^1.16.2",
|
"openapi-zod-client": "^1.16.2",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.47",
|
||||||
"postcss-nesting": "^12.1.0",
|
"postcss-nesting": "^12.1.5",
|
||||||
"tailwindcss": "^3.1.0",
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-plugin-checker": "^0.7.2",
|
"vite-plugin-checker": "^0.7.2",
|
||||||
@@ -1639,24 +1639,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/forms": {
|
"node_modules/@tailwindcss/forms": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz",
|
||||||
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
|
"integrity": "sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mini-svg-data-uri": "^1.2.3"
|
"mini-svg-data-uri": "^1.2.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
|
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/typography": {
|
"node_modules/@tailwindcss/typography": {
|
||||||
"version": "0.5.14",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.14.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz",
|
||||||
"integrity": "sha512-ZvOCjUbsJBjL9CxQBn+VEnFpouzuKhxh2dH8xMIWHILL+HfOYtlAkWcyoon8LlzE53d2Yo6YO6pahKKNW3q1YQ==",
|
"integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash.castarray": "^4.4.0",
|
"lodash.castarray": "^4.4.0",
|
||||||
"lodash.isplainobject": "^4.0.6",
|
"lodash.isplainobject": "^4.0.6",
|
||||||
@@ -1664,7 +1662,7 @@
|
|||||||
"postcss-selector-parser": "6.0.10"
|
"postcss-selector-parser": "6.0.10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"tailwindcss": ">=3.0.0 || insiders"
|
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/match-sorter-utils": {
|
"node_modules/@tanstack/match-sorter-utils": {
|
||||||
@@ -2649,7 +2647,6 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserslist": "^4.23.3",
|
"browserslist": "^4.23.3",
|
||||||
"caniuse-lite": "^1.0.30001646",
|
"caniuse-lite": "^1.0.30001646",
|
||||||
@@ -4892,10 +4889,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
|
||||||
"license": "ISC"
|
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
@@ -5012,9 +5008,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.41",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
|
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -5029,11 +5025,10 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.1",
|
"picocolors": "^1.1.0",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
@@ -5175,7 +5170,6 @@
|
|||||||
"url": "https://opencollective.com/csstools"
|
"url": "https://opencollective.com/csstools"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT-0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/selector-resolve-nested": "^1.1.0",
|
"@csstools/selector-resolve-nested": "^1.1.0",
|
||||||
"@csstools/selector-specificity": "^3.1.1",
|
"@csstools/selector-specificity": "^3.1.1",
|
||||||
@@ -5657,10 +5651,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -5880,10 +5873,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
||||||
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
|
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -13,19 +13,19 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "^1.0.0",
|
"@inertiajs/vue3": "^1.0.0",
|
||||||
"@playwright/test": "^1.41.1",
|
"@playwright/test": "^1.41.1",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.11.5",
|
||||||
"@vitejs/plugin-vue": "^4.5.0",
|
"@vitejs/plugin-vue": "^4.5.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.6.4",
|
"axios": "^1.6.4",
|
||||||
"eslint-plugin-unused-imports": "^3.1.0",
|
"eslint-plugin-unused-imports": "^3.1.0",
|
||||||
"laravel-vite-plugin": "^1.0.0",
|
"laravel-vite-plugin": "^1.0.0",
|
||||||
"openapi-zod-client": "^1.16.2",
|
"openapi-zod-client": "^1.16.2",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.47",
|
||||||
"postcss-nesting": "^12.1.0",
|
"postcss-nesting": "^12.1.5",
|
||||||
"tailwindcss": "^3.1.0",
|
"tailwindcss": "^3.4.13",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vite-plugin-checker": "^0.7.2",
|
"vite-plugin-checker": "^0.7.2",
|
||||||
|
|||||||
10
resources/css/filament/admin/tailwind.config.js
Normal file
10
resources/css/filament/admin/tailwind.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import preset from '../../../../vendor/filament/filament/tailwind.config.preset'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
presets: [preset],
|
||||||
|
content: [
|
||||||
|
'./app/Filament/**/*.php',
|
||||||
|
'./resources/views/filament/**/*.blade.php',
|
||||||
|
'./vendor/filament/**/*.blade.php',
|
||||||
|
],
|
||||||
|
}
|
||||||
3
resources/css/filament/admin/theme.css
Normal file
3
resources/css/filament/admin/theme.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@import '/vendor/filament/filament/resources/css/theme.css';
|
||||||
|
|
||||||
|
@config 'tailwind.config.js';
|
||||||
28
resources/views/filament/widgets/server-overview.blade.php
Normal file
28
resources/views/filament/widgets/server-overview.blade.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<x-filament-widgets::widget>
|
||||||
|
<x-filament::section>
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-950 font-bold">Version</span> <span>v{{ $version }}</span><br>
|
||||||
|
<span class="text-gray-950 font-bold">Build</span> {{ $build }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($currentVersion !== null)
|
||||||
|
<div class="mt-4 inline-flex items-center justify-center gap-1">
|
||||||
|
@if ($needsUpdate)
|
||||||
|
<span>
|
||||||
|
<x-filament::icon
|
||||||
|
icon="heroicon-o-exclamation-triangle"
|
||||||
|
class="h-5 w-5 text-orange-500 dark:text-orange-400"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span>Update available (v{{ $currentVersion }})</span>
|
||||||
|
@else
|
||||||
|
<x-filament::icon
|
||||||
|
icon="heroicon-o-check-circle"
|
||||||
|
class="h-5 w-5 text-green-500 dark:text-green-400"
|
||||||
|
/>
|
||||||
|
<span>Current version</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</x-filament::section>
|
||||||
|
</x-filament-widgets::widget>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit\Console\Commands\SelfHost;
|
||||||
|
|
||||||
|
use App\Console\Commands\SelfHost\SelfHostCheckForUpdateCommand;
|
||||||
|
use App\Service\ApiService;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(SelfHostCheckForUpdateCommand::class)]
|
||||||
|
#[CoversClass(ApiService::class)]
|
||||||
|
#[UsesClass(SelfHostCheckForUpdateCommand::class)]
|
||||||
|
class SelfHostCheckForUpdateCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_checks_for_update_and_saves_version_in_cache(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Http::fake([
|
||||||
|
'https://app.solidtime.io/api/check-for-update' => Http::response(['version' => '1.2.3'], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:check-for-update');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertSame(Command::SUCCESS, $exitCode);
|
||||||
|
$output = Artisan::output();
|
||||||
|
$this->assertSame('1.2.3', Cache::get('latest_version'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_checks_for_update_fails_gracefully_if_response_has_error_status_code(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Http::fake([
|
||||||
|
'https://app.solidtime.io/api/check-for-update' => Http::response(null, 500),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:check-for-update');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertSame(Command::FAILURE, $exitCode);
|
||||||
|
$output = Artisan::output();
|
||||||
|
$this->assertStringContainsString('Failed to check for update, check the logs for more information.', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_checks_for_update_fails_gracefully_if_timeout_happens(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Http::fake([
|
||||||
|
'https://app.solidtime.io/api/check-for-update' => function (): void {
|
||||||
|
throw new ConnectionException('Connection timed out');
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:check-for-update');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertSame(Command::FAILURE, $exitCode);
|
||||||
|
$output = Artisan::output();
|
||||||
|
$this->assertStringContainsString('Failed to check for update, check the logs for more information.', $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit\Console\Commands\SelfHost;
|
||||||
|
|
||||||
|
use App\Console\Commands\SelfHost\SelfHostTelemetryCommand;
|
||||||
|
use App\Service\ApiService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(SelfHostTelemetryCommand::class)]
|
||||||
|
#[CoversClass(ApiService::class)]
|
||||||
|
#[UsesClass(SelfHostTelemetryCommand::class)]
|
||||||
|
class SelfHostTelemetryCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_telemetry_sends_data_to_telemetry_endpoint_of_solidtime_cloud(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Http::fake([
|
||||||
|
'https://app.solidtime.io/api/telemetry' => Http::response(['success' => true], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:telemetry');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertSame(Command::SUCCESS, $exitCode);
|
||||||
|
$output = Artisan::output();
|
||||||
|
$this->assertSame('', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_telemetry_sends_fails_gracefully_if_response_has_error_status_code(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Http::fake([
|
||||||
|
'https://app.solidtime.io/api/telemetry' => Http::response(null, 500),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:telemetry');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertSame(Command::FAILURE, $exitCode);
|
||||||
|
$output = Artisan::output();
|
||||||
|
$this->assertStringContainsString('Failed to send telemetry data, check the logs for more information.', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_telemetry_sends_fails_gracefully_if_timeout_happens(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Http::fake([
|
||||||
|
'https://app.solidtime.io/api/telemetry' => function (): void {
|
||||||
|
throw new ConnectionException('Connection timed out');
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:telemetry');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertSame(Command::FAILURE, $exitCode);
|
||||||
|
$output = Artisan::output();
|
||||||
|
$this->assertStringContainsString('Failed to send telemetry data, check the logs for more information.', $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\AuditResource;
|
use App\Filament\Resources\AuditResource;
|
||||||
use App\Models\Audit;
|
use App\Models\Audit;
|
||||||
@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(AuditResource::class)]
|
#[UsesClass(AuditResource::class)]
|
||||||
class AuditResourceTest extends FilamentTestCase
|
class AuditResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ClientResource;
|
use App\Filament\Resources\ClientResource;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
@@ -10,6 +10,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(ClientResource::class)]
|
#[UsesClass(ClientResource::class)]
|
||||||
class ClientResourceTest extends FilamentTestCase
|
class ClientResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\FailedJobResource;
|
use App\Filament\Resources\FailedJobResource;
|
||||||
use App\Filament\Resources\FailedJobResource\Pages\ViewFailedJobs;
|
use App\Filament\Resources\FailedJobResource\Pages\ViewFailedJobs;
|
||||||
@@ -11,6 +11,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(FailedJobResource::class)]
|
#[UsesClass(FailedJobResource::class)]
|
||||||
class FailedJobResourceTest extends FilamentTestCase
|
class FailedJobResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\OrganizationResource;
|
use App\Filament\Resources\OrganizationResource;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
@@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use Mockery\MockInterface;
|
use Mockery\MockInterface;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(OrganizationResource::class)]
|
#[UsesClass(OrganizationResource::class)]
|
||||||
class OrganizationResourceTest extends FilamentTestCase
|
class OrganizationResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\ProjectResource;
|
use App\Filament\Resources\ProjectResource;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
@@ -10,6 +10,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(ProjectResource::class)]
|
#[UsesClass(ProjectResource::class)]
|
||||||
class ProjectResourceTest extends FilamentTestCase
|
class ProjectResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TagResource;
|
use App\Filament\Resources\TagResource;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
@@ -10,6 +10,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(TagResource::class)]
|
#[UsesClass(TagResource::class)]
|
||||||
class TagResourceTest extends FilamentTestCase
|
class TagResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TaskResource;
|
use App\Filament\Resources\TaskResource;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
@@ -10,6 +10,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(TaskResource::class)]
|
#[UsesClass(TaskResource::class)]
|
||||||
class TaskResourceTest extends FilamentTestCase
|
class TaskResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
use App\Models\TimeEntry;
|
use App\Models\TimeEntry;
|
||||||
@@ -10,6 +10,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(TimeEntryResource::class)]
|
#[UsesClass(TimeEntryResource::class)]
|
||||||
class TimeEntryResourceTest extends FilamentTestCase
|
class TimeEntryResourceTest extends FilamentTestCase
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests\Unit\Filament;
|
namespace Tests\Unit\Filament\Resources;
|
||||||
|
|
||||||
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
||||||
use App\Filament\Resources\TimeEntryResource;
|
use App\Filament\Resources\TimeEntryResource;
|
||||||
@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use Mockery\MockInterface;
|
use Mockery\MockInterface;
|
||||||
use PHPUnit\Framework\Attributes\UsesClass;
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
#[UsesClass(TimeEntryResource::class)]
|
#[UsesClass(TimeEntryResource::class)]
|
||||||
class UserResourceTest extends FilamentTestCase
|
class UserResourceTest extends FilamentTestCase
|
||||||
83
tests/Unit/Filament/Widgets/ServerOverviewWidgetTest.php
Normal file
83
tests/Unit/Filament/Widgets/ServerOverviewWidgetTest.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit\Filament\Widgets;
|
||||||
|
|
||||||
|
use App\Filament\Widgets\ServerOverview;
|
||||||
|
use App\Models\User;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use PHPUnit\Framework\Attributes\UsesClass;
|
||||||
|
use Tests\Unit\Filament\FilamentTestCase;
|
||||||
|
|
||||||
|
#[UsesClass(ServerOverview::class)]
|
||||||
|
class ServerOverviewWidgetTest extends FilamentTestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
Config::set('auth.super_admins', ['admin@example.com']);
|
||||||
|
$user = User::factory()->withPersonalOrganization()->create([
|
||||||
|
'email' => 'admin@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_shows_version_and_build_it_no_information_about_the_current_version_exists(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Config::set('app.version', '1.0.0');
|
||||||
|
Config::set('app.build', 'ABC123');
|
||||||
|
Cache::forget('latest_version');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(ServerOverview::class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('1.0.0');
|
||||||
|
$response->assertSee('ABC123');
|
||||||
|
$response->assertDontSee('Update available');
|
||||||
|
$response->assertDontSee('Current version');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_show_version_is_current_when_the_latest_version_is_the_same_as_the_current_version(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Config::set('app.version', '1.0.0');
|
||||||
|
Config::set('app.build', 'ABC123');
|
||||||
|
Cache::put('latest_version', '1.0.0');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(ServerOverview::class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('1.0.0');
|
||||||
|
$response->assertSee('ABC123');
|
||||||
|
$response->assertDontSee('Update available');
|
||||||
|
$response->assertSee('Current version');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_shows_update_available(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Config::set('app.version', '1.0.0');
|
||||||
|
Config::set('app.build', 'ABC123');
|
||||||
|
Cache::put('latest_version', '1.0.1');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = Livewire::test(ServerOverview::class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$response->assertSee('1.0.0');
|
||||||
|
$response->assertSee('ABC123');
|
||||||
|
$response->assertSee('Update available');
|
||||||
|
$response->assertDontSee('Current version');
|
||||||
|
$response->assertSee('1.0.1');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from './vite-module-loader.js';
|
} from './vite-module-loader.js';
|
||||||
|
|
||||||
async function getConfig() {
|
async function getConfig() {
|
||||||
const paths = ['resources/js/app.ts', 'resources/css/app.css'];
|
const paths = ['resources/js/app.ts', 'resources/css/app.css', 'resources/css/filament/admin/theme.css'];
|
||||||
const modulePaths = await collectModuleAssetsPaths('extensions');
|
const modulePaths = await collectModuleAssetsPaths('extensions');
|
||||||
const additionalPlugins = await collectModulePlugins('extensions');
|
const additionalPlugins = await collectModulePlugins('extensions');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user