Merge pull request #4754 from coollabsio/improve-git-and-service-provider

Improves: GitHub handling, AppServiceProvider and 500 error message rendering
This commit is contained in:
Andras Bacsai
2025-01-16 21:09:08 +01:00
committed by GitHub
13 changed files with 393 additions and 78 deletions

View File

@@ -27,19 +27,28 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
public function handle() public function handle()
{ {
try { try {
$github_access_token = generate_github_jwt_token($this->github_app); $github_access_token = generateGithubJwt($this->github_app);
$response = Http::withHeaders([ $response = Http::withHeaders([
'Authorization' => "Bearer $github_access_token", 'Authorization' => "Bearer $github_access_token",
'Accept' => 'application/vnd.github+json', 'Accept' => 'application/vnd.github+json',
])->get("{$this->github_app->api_url}/app"); ])->get("{$this->github_app->api_url}/app");
if (! $response->successful()) {
throw new \RuntimeException('Failed to fetch GitHub app permissions: '.$response->body());
}
$response = $response->json(); $response = $response->json();
$permissions = data_get($response, 'permissions'); $permissions = data_get($response, 'permissions');
$this->github_app->contents = data_get($permissions, 'contents'); $this->github_app->contents = data_get($permissions, 'contents');
$this->github_app->metadata = data_get($permissions, 'metadata'); $this->github_app->metadata = data_get($permissions, 'metadata');
$this->github_app->pull_requests = data_get($permissions, 'pull_requests'); $this->github_app->pull_requests = data_get($permissions, 'pull_requests');
$this->github_app->administration = data_get($permissions, 'administration'); $this->github_app->administration = data_get($permissions, 'administration');
$this->github_app->save(); $this->github_app->save();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('GithubAppPermissionJob failed with: '.$e->getMessage()); send_internal_notification('GithubAppPermissionJob failed with: '.$e->getMessage());
throw $e; throw $e;

View File

@@ -105,7 +105,7 @@ class GithubPrivateRepository extends Component
$this->page = 1; $this->page = 1;
$this->selected_github_app_id = $github_app_id; $this->selected_github_app_id = $github_app_id;
$this->github_app = GithubApp::where('id', $github_app_id)->first(); $this->github_app = GithubApp::where('id', $github_app_id)->first();
$this->token = generate_github_installation_token($this->github_app); $this->token = generateGithubInstallationToken($this->github_app);
$this->loadRepositoryByPage(); $this->loadRepositoryByPage();
if ($this->repositories->count() < $this->total_repositories_count) { if ($this->repositories->count() < $this->total_repositories_count) {
while ($this->repositories->count() < $this->total_repositories_count) { while ($this->repositories->count() < $this->total_repositories_count) {

View File

@@ -76,7 +76,7 @@ class Change extends Component
// Need administration:read:write permission // Need administration:read:write permission
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#list-self-hosted-runners-for-a-repository // https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#list-self-hosted-runners-for-a-repository
// $github_access_token = generate_github_installation_token($this->github_app); // $github_access_token = generateGithubInstallationToken($this->github_app);
// $repositories = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100"); // $repositories = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100");
// $runners_by_repository = collect([]); // $runners_by_repository = collect([]);
// $repositories = $repositories->json()['repositories']; // $repositories = $repositories->json()['repositories'];

View File

@@ -999,7 +999,7 @@ class Application extends BaseModel
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; $fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
$base_command = "{$base_command} {$this->source->html_url}/{$customRepository}"; $base_command = "{$base_command} {$this->source->html_url}/{$customRepository}";
} else { } else {
$github_access_token = generate_github_installation_token($this->source); $github_access_token = generateGithubInstallationToken($this->source);
if ($exec_in_docker) { if ($exec_in_docker) {
$base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; $base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";
@@ -1111,7 +1111,7 @@ class Application extends BaseModel
$commands->push($git_clone_command); $commands->push($git_clone_command);
} }
} else { } else {
$github_access_token = generate_github_installation_token($this->source); $github_access_token = generateGithubInstallationToken($this->source);
if ($exec_in_docker) { if ($exec_in_docker) {
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}"; $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}";
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git";

View File

@@ -3,37 +3,68 @@
namespace App\Providers; namespace App\Providers;
use App\Models\PersonalAccessToken; use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\Event; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
use Laravel\Sanctum\Sanctum; use Laravel\Sanctum\Sanctum;
use Laravel\Telescope\TelescopeServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
public function register(): void public function register(): void
{ {
if ($this->app->environment('local')) { if (App::isLocal()) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class); $this->app->register(TelescopeServiceProvider::class);
} }
} }
public function boot(): void public function boot(): void
{ {
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { $this->configureCommands();
$event->extendSocialite('authentik', \SocialiteProviders\Authentik\Provider::class); $this->configureModels();
}); $this->configurePasswords();
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); $this->configureSanctumModel();
$this->configureGitHubHttp();
}
private function configureCommands(): void
{
if (App::isProduction()) {
DB::prohibitDestructiveCommands();
}
}
private function configureModels(): void
{
// Disabled because it's causing issues with the application
// Model::shouldBeStrict();
}
private function configurePasswords(): void
{
Password::defaults(function () { Password::defaults(function () {
$rule = Password::min(8); return App::isProduction()
? Password::min(8)
return $this->app->isProduction() ->mixedCase()
? $rule->mixedCase()->letters()->numbers()->symbols() ->letters()
: $rule; ->numbers()
->symbols()
->uncompromised()
: Password::min(8)->letters();
}); });
}
Http::macro('github', function (string $api_url, ?string $github_access_token = null) { private function configureSanctumModel(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
private function configureGitHubHttp(): void
{
Http::macro('GitHub', function (string $api_url, ?string $github_access_token = null) {
if ($github_access_token) { if ($github_access_token) {
return Http::withHeaders([ return Http::withHeaders([
'X-GitHub-Api-Version' => '2022-11-28', 'X-GitHub-Api-Version' => '2022-11-28',

View File

@@ -12,77 +12,108 @@ use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token\Builder; use Lcobucci\JWT\Token\Builder;
function generate_github_installation_token(GithubApp $source) function generateGithubToken(GithubApp $source, string $type)
{ {
$signingKey = InMemory::plainText($source->privateKey->private_key); $response = Http::get("{$source->api_url}/zen");
$algorithm = new Sha256; $serverTime = CarbonImmutable::now()->setTimezone('UTC');
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default())); $githubTime = Carbon::parse($response->header('date'));
$now = CarbonImmutable::now(); $timeDiff = abs($serverTime->diffInSeconds($githubTime));
$now = $now->setTime($now->format('H'), $now->format('i'));
$issuedToken = $tokenBuilder if ($timeDiff > 50) {
->issuedBy($source->app_id) throw new \Exception(
->issuedAt($now) 'System time is out of sync with GitHub API time:<br>'.
->expiresAt($now->modify('+10 minutes')) '- System time: '.$serverTime->format('Y-m-d H:i:s').' UTC<br>'.
->getToken($algorithm, $signingKey) '- GitHub time: '.$githubTime->format('Y-m-d H:i:s').' UTC<br>'.
->toString(); '- Difference: '.$timeDiff.' seconds<br>'.
$token = Http::withHeaders([ 'Please synchronize your system clock.'
'Authorization' => "Bearer $issuedToken", );
'Accept' => 'application/vnd.github.machine-man-preview+json',
])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
if ($token->failed()) {
throw new RuntimeException('Failed to get access token for '.$source->name.' with error: '.data_get($token->json(), 'message', 'no error message found'));
} }
return $token->json()['token'];
}
function generate_github_jwt_token(GithubApp $source)
{
$signingKey = InMemory::plainText($source->privateKey->private_key); $signingKey = InMemory::plainText($source->privateKey->private_key);
$algorithm = new Sha256; $algorithm = new Sha256;
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default())); $tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
$now = CarbonImmutable::now(); $now = CarbonImmutable::now()->setTimezone('UTC');
$now = $now->setTime($now->format('H'), $now->format('i')); $now = $now->setTime($now->format('H'), $now->format('i'), $now->format('s'));
return $tokenBuilder $jwt = $tokenBuilder
->issuedBy($source->app_id) ->issuedBy($source->app_id)
->issuedAt($now->modify('-1 minute')) ->issuedAt($now->modify('-1 minute'))
->expiresAt($now->modify('+10 minutes')) ->expiresAt($now->modify('+8 minutes'))
->getToken($algorithm, $signingKey) ->getToken($algorithm, $signingKey)
->toString(); ->toString();
return match ($type) {
'jwt' => $jwt,
'installation' => (function () use ($source, $jwt) {
$response = Http::withHeaders([
'Authorization' => "Bearer $jwt",
'Accept' => 'application/vnd.github.machine-man-preview+json',
])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
if (! $response->successful()) {
$error = data_get($response->json(), 'message', 'no error message found');
throw new RuntimeException("Failed to get installation token for {$source->name} with error: ".$error);
}
return $response->json()['token'];
})(),
default => throw new \InvalidArgumentException("Unsupported token type: {$type}")
};
}
function generateGithubInstallationToken(GithubApp $source)
{
return generateGithubToken($source, 'installation');
}
function generateGithubJwt(GithubApp $source)
{
return generateGithubToken($source, 'jwt');
} }
function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true) function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true)
{ {
if (is_null($source)) { if (is_null($source)) {
throw new \Exception('Not implemented yet.'); throw new \Exception('Source is required for API calls');
} }
if ($source->getMorphClass() === \App\Models\GithubApp::class) {
if ($source->is_public) { if ($source->getMorphClass() !== GithubApp::class) {
$response = Http::github($source->api_url)->$method($endpoint); throw new \InvalidArgumentException("Unsupported source type: {$source->getMorphClass()}");
}
if ($source->is_public) {
$response = Http::GitHub($source->api_url)->$method($endpoint);
} else {
$token = generateGithubInstallationToken($source);
if ($data && in_array(strtolower($method), ['post', 'patch', 'put'])) {
$response = Http::GitHub($source->api_url, $token)->$method($endpoint, $data);
} else { } else {
$github_access_token = generate_github_installation_token($source); $response = Http::GitHub($source->api_url, $token)->$method($endpoint);
if ($data && ($method === 'post' || $method === 'patch' || $method === 'put')) {
$response = Http::github($source->api_url, $github_access_token)->$method($endpoint, $data);
} else {
$response = Http::github($source->api_url, $github_access_token)->$method($endpoint);
}
} }
} }
$json = $response->json();
if ($response->failed() && $throwError) { if (! $response->successful() && $throwError) {
ray($json); $resetTime = Carbon::parse((int) $response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s');
throw new \Exception("Failed to get data from {$source->name} with error:<br><br>".$json['message'].'<br><br>Rate Limit resets at: '.Carbon::parse((int) $response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s').'UTC'); $errorMessage = data_get($response->json(), 'message', 'no error message found');
$remainingCalls = $response->header('X-RateLimit-Remaining', '0');
throw new \Exception(
'GitHub API call failed:<br>'.
"Error: {$errorMessage}<br>".
'Rate Limit Status:<br>'.
"- Remaining Calls: {$remainingCalls}<br>".
"- Reset Time: {$resetTime} UTC"
);
} }
return [ return [
'rate_limit_remaining' => $response->header('X-RateLimit-Remaining'), 'rate_limit_remaining' => $response->header('X-RateLimit-Remaining'),
'rate_limit_reset' => $response->header('X-RateLimit-Reset'), 'rate_limit_reset' => $response->header('X-RateLimit-Reset'),
'data' => collect($json), 'data' => collect($response->json()),
]; ];
} }
function get_installation_path(GithubApp $source) function getInstallationPath(GithubApp $source)
{ {
$github = GithubApp::where('uuid', $source->uuid)->first(); $github = GithubApp::where('uuid', $source->uuid)->first();
$name = str(Str::kebab($github->name)); $name = str(Str::kebab($github->name));
@@ -90,7 +121,8 @@ function get_installation_path(GithubApp $source)
return "$github->html_url/$installation_path/$name/installations/new"; return "$github->html_url/$installation_path/$name/installations/new";
} }
function get_permissions_path(GithubApp $source)
function getPermissionsPath(GithubApp $source)
{ {
$github = GithubApp::where('uuid', $source->uuid)->first(); $github = GithubApp::where('uuid', $source->uuid)->first();
$name = str(Str::kebab($github->name)); $name = str(Str::kebab($github->name));

View File

@@ -47,6 +47,7 @@
"spatie/laravel-ray": "^1.37", "spatie/laravel-ray": "^1.37",
"spatie/laravel-schemaless-attributes": "^2.4", "spatie/laravel-schemaless-attributes": "^2.4",
"spatie/url": "^2.2", "spatie/url": "^2.2",
"stevebauman/purify": "^6.2",
"stripe/stripe-php": "^16.2.0", "stripe/stripe-php": "^16.2.0",
"symfony/yaml": "^7.1.6", "symfony/yaml": "^7.1.6",
"visus/cuid2": "^4.1.0", "visus/cuid2": "^4.1.0",

127
composer.lock generated
View File

@@ -1883,6 +1883,67 @@
], ],
"time": "2024-12-27T00:36:43+00:00" "time": "2024-12-27T00:36:43+00:00"
}, },
{
"name": "ezyang/htmlpurifier",
"version": "v4.18.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "cb56001e54359df7ae76dc522d08845dc741621b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b",
"reference": "cb56001e54359df7ae76dc522d08845dc741621b",
"shasum": ""
},
"require": {
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"require-dev": {
"cerdic/css-tidy": "^1.7 || ^2.0",
"simpletest/simpletest": "dev-master"
},
"suggest": {
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
"ext-bcmath": "Used for unit conversion and imagecrash protection",
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
"ext-tidy": "Used for pretty-printing HTML"
},
"type": "library",
"autoload": {
"files": [
"library/HTMLPurifier.composer.php"
],
"psr-0": {
"HTMLPurifier": "library/"
},
"exclude-from-classmap": [
"/library/HTMLPurifier/Language/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "Edward Z. Yang",
"email": "admin@htmlpurifier.org",
"homepage": "http://ezyang.com"
}
],
"description": "Standards compliant HTML filter written in PHP",
"homepage": "http://htmlpurifier.org/",
"keywords": [
"html"
],
"support": {
"issues": "https://github.com/ezyang/htmlpurifier/issues",
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0"
},
"time": "2024-11-01T03:51:45+00:00"
},
{ {
"name": "firebase/php-jwt", "name": "firebase/php-jwt",
"version": "v6.10.2", "version": "v6.10.2",
@@ -8330,6 +8391,72 @@
], ],
"time": "2024-03-08T11:35:19+00:00" "time": "2024-03-08T11:35:19+00:00"
}, },
{
"name": "stevebauman/purify",
"version": "v6.2.2",
"source": {
"type": "git",
"url": "https://github.com/stevebauman/purify.git",
"reference": "a449299a3d5f5f8ef177e626721b3f69143890a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stevebauman/purify/zipball/a449299a3d5f5f8ef177e626721b3f69143890a4",
"reference": "a449299a3d5f5f8ef177e626721b3f69143890a4",
"shasum": ""
},
"require": {
"ezyang/htmlpurifier": "^4.17",
"illuminate/contracts": "^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0",
"php": ">=7.4"
},
"require-dev": {
"orchestra/testbench": "^5.0|^6.0|^7.0|^8.0|^9.0",
"phpunit/phpunit": "^8.0|^9.0|^10.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Purify": "Stevebauman\\Purify\\Facades\\Purify"
},
"providers": [
"Stevebauman\\Purify\\PurifyServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Stevebauman\\Purify\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Steve Bauman",
"email": "steven_bauman@outlook.com"
}
],
"description": "An HTML Purifier / Sanitizer for Laravel",
"keywords": [
"Purifier",
"clean",
"cleaner",
"html",
"laravel",
"purification",
"purify"
],
"support": {
"issues": "https://github.com/stevebauman/purify/issues",
"source": "https://github.com/stevebauman/purify/tree/v6.2.2"
},
"time": "2024-09-24T12:27:10+00:00"
},
{ {
"name": "stripe/stripe-php", "name": "stripe/stripe-php",
"version": "v16.4.0", "version": "v16.4.0",

115
config/purify.php Normal file
View File

@@ -0,0 +1,115 @@
<?php
use Stevebauman\Purify\Definitions\Html5Definition;
return [
/*
|--------------------------------------------------------------------------
| Default Config
|--------------------------------------------------------------------------
|
| This option defines the default config that is provided to HTMLPurifier.
|
*/
'default' => 'default',
/*
|--------------------------------------------------------------------------
| Config sets
|--------------------------------------------------------------------------
|
| Here you may configure various sets of configuration for differentiated use of HTMLPurifier.
| A specific set of configuration can be applied by calling the "config($name)" method on
| a Purify instance. Feel free to add/remove/customize these attributes as you wish.
|
| Documentation: http://htmlpurifier.org/live/configdoc/plain.html
|
| Core.Encoding The encoding to convert input to.
| HTML.Doctype Doctype to use during filtering.
| HTML.Allowed The allowed HTML Elements with their allowed attributes.
| HTML.ForbiddenElements The forbidden HTML elements. Elements that are listed in this
| string will be removed, however their content will remain.
| CSS.AllowedProperties The Allowed CSS properties.
| AutoFormat.AutoParagraph Newlines are converted in to paragraphs whenever possible.
| AutoFormat.RemoveEmpty Remove empty elements that contribute no semantic information to the document.
|
*/
'configs' => [
'default' => [
'Core.Encoding' => 'utf-8',
'HTML.Doctype' => 'HTML 4.01 Transitional',
'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,b,u,strong,i,em,s,del,a[href|title],ul,ol,li,p[style],br,span,img[width|height|alt|src],blockquote',
'HTML.ForbiddenElements' => '',
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align',
'AutoFormat.AutoParagraph' => false,
'AutoFormat.RemoveEmpty' => false,
],
],
/*
|--------------------------------------------------------------------------
| HTMLPurifier definitions
|--------------------------------------------------------------------------
|
| Here you may specify a class that augments the HTML definitions used by
| HTMLPurifier. Additional HTML5 definitions are provided out of the box.
| When specifying a custom class, make sure it implements the interface:
|
| \Stevebauman\Purify\Definitions\Definition
|
| Note that these definitions are applied to every Purifier instance.
|
| Documentation: http://htmlpurifier.org/docs/enduser-customize.html
|
*/
'definitions' => Html5Definition::class,
/*
|--------------------------------------------------------------------------
| HTMLPurifier CSS definitions
|--------------------------------------------------------------------------
|
| Here you may specify a class that augments the CSS definitions used by
| HTMLPurifier. When specifying a custom class, make sure it implements
| the interface:
|
| \Stevebauman\Purify\Definitions\CssDefinition
|
| Note that these definitions are applied to every Purifier instance.
|
| CSS should be extending $definition->info['css-attribute'] = values
| See HTMLPurifier_CSSDefinition for further explanation
|
*/
'css-definitions' => null,
/*
|--------------------------------------------------------------------------
| Serializer
|--------------------------------------------------------------------------
|
| The storage implementation where HTMLPurifier can store its serializer files.
| If the filesystem cache is in use, the path must be writable through the
| storage disk by the web server, otherwise an exception will be thrown.
|
*/
'serializer' => [
'driver' => env('CACHE_STORE', env('CACHE_DRIVER', 'file')),
'cache' => \Stevebauman\Purify\Cache\CacheDefinitionCache::class,
],
// 'serializer' => [
// 'disk' => env('FILESYSTEM_DISK', 'local'),
// 'path' => 'purify',
// 'cache' => \Stevebauman\Purify\Cache\FilesystemDefinitionCache::class,
// ],
];

View File

@@ -1,20 +1,20 @@
@extends('layouts.base') @extends('layouts.base')
<div class="flex flex-col items-center justify-center h-full"> <div class="flex items-center justify-center min-h-screen">
<div> <div class="w-full max-w-3xl px-8">
<p class="font-mono font-semibold text-red-500 text-7xl">500</p> <p class="font-mono font-semibold text-red-500 text-[200px] leading-none">500</p>
<h1 class="mt-4 font-bold tracking-tight dark:text-white">Wait, this is not cool...</h1> <h1 class="text-3xl font-bold tracking-tight dark:text-white">Wait, this is not cool...</h1>
<p class="text-base leading-7 text-neutral-300">There has been an error, we are working on it. <p class="mt-2 text-lg leading-7 text-neutral-300">There has been an error with the following error message:</p>
</p>
@if ($exception->getMessage() !== '') @if ($exception->getMessage() !== '')
<code class="mt-6 text-xs text-left text-red-500">Error: {{ $exception->getMessage() }} <div class="mt-6 text-sm text-red-500">
</code> {!! Purify::clean($exception->getMessage()) !!}
</div>
@endif @endif
<div class="flex items-center mt-10 gap-x-6"> <div class="flex items-center mt-10 gap-6">
<a href="/"> <a href="/">
<x-forms.button>Go back home</x-forms.button> <x-forms.button>Go back home</x-forms.button>
</a> </a>
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact <a target="_blank" class="text-sm hover:text-neutral-300 flex items-center gap-1" href="{{ config('constants.urls.contact') }}">
support Contact support
<x-external-link /> <x-external-link />
</a> </a>
</div> </div>

View File

@@ -10,7 +10,7 @@
</x-forms.button> </x-forms.button>
</a> </a>
@if (data_get($application, 'source.is_public') === false) @if (data_get($application, 'source.is_public') === false)
<a target="_blank" class="hover:no-underline" href="{{ get_installation_path($application->source) }}"> <a target="_blank" class="hover:no-underline" href="{{ getInstallationPath($application->source) }}">
<x-forms.button> <x-forms.button>
Open Git App Open Git App
<x-external-link /> <x-external-link />

View File

@@ -5,7 +5,7 @@
<livewire:source.github.create /> <livewire:source.github.create />
</x-modal-input> </x-modal-input>
@if ($repositories->count() > 0) @if ($repositories->count() > 0)
<a target="_blank" class="flex hover:no-underline" href="{{ get_installation_path($github_app) }}"> <a target="_blank" class="flex hover:no-underline" href="{{ getInstallationPath($github_app) }}">
<x-forms.button> <x-forms.button>
Change Repositories on GitHub Change Repositories on GitHub
<x-external-link /> <x-external-link />

View File

@@ -6,7 +6,7 @@
<div class="flex gap-2"> <div class="flex gap-2">
@if (data_get($github_app, 'installation_id')) @if (data_get($github_app, 'installation_id'))
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
<a href="{{ get_installation_path($github_app) }}"> <a href="{{ getInstallationPath($github_app) }}">
<x-forms.button> <x-forms.button>
Update Repositories Update Repositories
<x-external-link /> <x-external-link />
@@ -52,7 +52,7 @@
</svg> </svg>
<span>You must complete this step before you can use this source!</span> <span>You must complete this step before you can use this source!</span>
</div> </div>
<a class="items-center justify-center box" href="{{ get_installation_path($github_app) }}"> <a class="items-center justify-center box" href="{{ getInstallationPath($github_app) }}">
Install Repositories on GitHub Install Repositories on GitHub
</a> </a>
@else @else
@@ -106,7 +106,7 @@
<div class="flex items-end gap-2 "> <div class="flex items-end gap-2 ">
<h2 class="pt-4">Permissions</h2> <h2 class="pt-4">Permissions</h2>
<x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button> <x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button>
<a href="{{ get_permissions_path($github_app) }}"> <a href="{{ getPermissionsPath($github_app) }}">
<x-forms.button> <x-forms.button>
Update Update
<x-external-link /> <x-external-link />