Initial test
Some checks failed
tests / ci (push) Has been cancelled
linter / quality (push) Has been cancelled
Build & Push Docker Image to Registry / build (release) Failing after 3m7s

This commit is contained in:
Javier Feliz 2025-07-22 19:45:33 -04:00
parent 9b2b993418
commit 74f5dc5db1
13 changed files with 290 additions and 121 deletions

View File

@ -0,0 +1,33 @@
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Component;
class NewScript extends Component
{
#[Validate('required')]
public string $name = '';
#[Validate('required')]
public string $path = '';
#[Validate('required')]
public string $content = '';
public function create()
{
$this->validate();
auth()->user()->scripts()->create([
'name' => $this->name,
'path' => str($this->path)->remove('.sh')->lower()->kebab()->toString(),
'content' => $this->content
]);
$this->reset(['name', 'path', 'content']);
}
public function render()
{
return view('livewire.forms.new-script');
}
}

20
app/Models/Script.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Script extends Model
{
protected $guarded = ['id'];
public function viewUrl()
{
return route('script-view', ['path' => $this->path]);
}
public function contentUrl()
{
return route('script-content', ['path' => $this->path]);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
@ -58,4 +59,9 @@ class User extends Authenticatable
->map(fn($word) => Str::substr($word, 0, 1))
->implode('');
}
public function scripts(): HasMany
{
return $this->hasMany(Script::class);
}
}

View File

@ -0,0 +1,32 @@
<?php
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('scripts', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class);
$table->string('name');
$table->string('path');
$table->longText('content');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('scripts');
}
};

View File

@ -1,5 +1,5 @@
<x-layouts.app.sidebar :title="$title ?? null">
<x-layouts.app.header :title="$title ?? null">
<flux:main>
{{ $slot }}
</flux:main>
</x-layouts.app.sidebar>
</x-layouts.app.header>

View File

@ -1,18 +1,22 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
<head>
@include('partials.head')
</head>
<body class="min-h-screen bg-white dark:bg-zinc-800">
<flux:header container class="border-b border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
<flux:sidebar.toggle class="lg:hidden" icon="bars-2" inset="left" />
<a href="{{ route('dashboard') }}" class="ms-2 me-5 flex items-center space-x-2 rtl:space-x-reverse lg:ms-0" wire:navigate>
<a href="{{ route('dashboard') }}" class="ms-2 me-5 flex items-center space-x-2 rtl:space-x-reverse lg:ms-0"
wire:navigate>
<x-app-logo />
</a>
<flux:navbar class="-mb-px max-lg:hidden">
<flux:navbar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
<flux:navbar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')"
wire:navigate>
{{ __('Dashboard') }}
</flux:navbar.item>
</flux:navbar>
@ -21,34 +25,22 @@
<flux:navbar class="me-1.5 space-x-0.5 rtl:space-x-reverse py-0!">
<flux:tooltip :content="__('Search')" position="bottom">
<flux:navbar.item class="!h-10 [&>div>svg]:size-5" icon="magnifying-glass" href="#" :label="__('Search')" />
<flux:navbar.item class="!h-10 [&>div>svg]:size-5" icon="magnifying-glass" href="#"
:label="__('Search')" />
</flux:tooltip>
<flux:tooltip :content="__('Repository')" position="bottom">
<flux:navbar.item
class="h-10 max-lg:hidden [&>div>svg]:size-5"
icon="folder-git-2"
href="https://github.com/laravel/livewire-starter-kit"
target="_blank"
:label="__('Repository')"
/>
<flux:navbar.item class="h-10 max-lg:hidden [&>div>svg]:size-5" icon="folder-git-2"
href="https://github.com/laravel/livewire-starter-kit" target="_blank" :label="__('Repository')" />
</flux:tooltip>
<flux:tooltip :content="__('Documentation')" position="bottom">
<flux:navbar.item
class="h-10 max-lg:hidden [&>div>svg]:size-5"
icon="book-open-text"
href="https://laravel.com/docs/starter-kits#livewire"
target="_blank"
label="Documentation"
/>
<flux:navbar.item class="h-10 max-lg:hidden [&>div>svg]:size-5" icon="book-open-text"
href="https://laravel.com/docs/starter-kits#livewire" target="_blank" label="Documentation" />
</flux:tooltip>
</flux:navbar>
<!-- Desktop User Menu -->
<flux:dropdown position="top" align="end">
<flux:profile
class="cursor-pointer"
:initials="auth()->user()->initials()"
/>
<flux:profile class="cursor-pointer" :initials="auth()->user()->initials()" />
<flux:menu>
<flux:menu.radio.group>
@ -56,8 +48,7 @@
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<span class="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-lg">
<span
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white"
>
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white">
{{ auth()->user()->initials() }}
</span>
</span>
@ -73,7 +64,8 @@
<flux:menu.separator />
<flux:menu.radio.group>
<flux:menu.item :href="route('settings.profile')" icon="cog" wire:navigate>{{ __('Settings') }}</flux:menu.item>
<flux:menu.item :href="route('settings.profile')" icon="cog" wire:navigate>{{ __('Settings') }}
</flux:menu.item>
</flux:menu.radio.group>
<flux:menu.separator />
@ -89,7 +81,8 @@
</flux:header>
<!-- Mobile Menu -->
<flux:sidebar stashable sticky class="lg:hidden border-e border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
<flux:sidebar stashable sticky
class="lg:hidden border-e border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
<flux:sidebar.toggle class="lg:hidden" icon="x-mark" />
<a href="{{ route('dashboard') }}" class="ms-1 flex items-center space-x-2 rtl:space-x-reverse" wire:navigate>
@ -98,7 +91,8 @@
<flux:navlist variant="outline">
<flux:navlist.group :heading="__('Platform')">
<flux:navlist.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
<flux:navlist.item icon="layout-grid" :href="route('dashboard')"
:current="request()->routeIs('dashboard')" wire:navigate>
{{ __('Dashboard') }}
</flux:navlist.item>
</flux:navlist.group>
@ -107,11 +101,13 @@
<flux:spacer />
<flux:navlist variant="outline">
<flux:navlist.item icon="folder-git-2" href="https://github.com/laravel/livewire-starter-kit" target="_blank">
<flux:navlist.item icon="folder-git-2" href="https://github.com/laravel/livewire-starter-kit"
target="_blank">
{{ __('Repository') }}
</flux:navlist.item>
<flux:navlist.item icon="book-open-text" href="https://laravel.com/docs/starter-kits#livewire" target="_blank">
<flux:navlist.item icon="book-open-text" href="https://laravel.com/docs/starter-kits#livewire"
target="_blank">
{{ __('Documentation') }}
</flux:navlist.item>
</flux:navlist>
@ -121,4 +117,5 @@
@fluxScripts
</body>
</html>

View File

@ -1,18 +1,15 @@
<x-layouts.app :title="__('Dashboard')">
<div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
</div>
<div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
</div>
<div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
<div class="max-w-4xl mx-auto">
<livewire:forms.new-script />
<div class="mt-8">
@foreach (auth()->user()->scripts as $s)
<div class="bg-gray-300 dark:bg-zinc-700 p-4 mb-4 space-y-4">
<flux:text size="xl">{{ $s->name }}</flux:text>
<div class="flex gap-4">
<flux:button variant="primary" :href="$s->viewUrl()" target="_blank" icon="eye">View</flux:button>
</div>
</div>
<div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
<x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
@endforeach
</div>
</div>
</x-layouts.app>

View File

@ -0,0 +1,18 @@
<form wire:submit="create" class="flex flex-col gap-4">
<flux:heading size="xl">New Script</flux:heading>
<div class="flex gap-4">
<div class="flex-1">
<flux:input label="Name" placeholder="New install setup" wire:model="name" />
</div>
<flux:field class="flex-1">
<flux:label>Path</flux:label>
<flux:input.group>
<flux:input.group.prefix>{{ config('app.url') }}/</flux:input.group.prefix>
<flux:input placeholder="new-install" wire:model="path" />
</flux:input.group>
</flux:field>
</div>
<flux:textarea wire:model="content" label="Script content" />
<flux:button variant="primary" type="submit">Create</flux:button>
</form>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@include('partials.head')
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-okaidia.min.css"
crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>{{ $script->name }}</title>
</head>
<body>
@php
$copystring = "curl -fsSL ".route('script-content', ['path' => $script->path]).' | sh';
@endphp
<div class="max-w-2xl mx-auto py-24">
<flux:heading class="text-4xl text-center" size="xl">{{ $script->name }}</flux:heading>
<div class="my-8">
<div x-data="{ command: '{{ $copystring }}', copied: false }"
class="bg-zinc-800 flex justify-between items-center">
<pre class="dark:text-white p-4 rounded select-all" x-text="command"></pre>
<flux:button variant="ghost"
x-on:click="navigator.clipboard.writeText(command); copied = true; setTimeout(() => copied = false, 2000)"
class="absolute top-0 bottom-0 right-3">
<span x-show="!copied">
<flux:icon.clipboard />
</span>
<span x-cloak x-show="copied"
class="absolute mb-10 bg-black dark:bg-white dark:text-black p-0.5 rounded-lg">Copied!</span>
</flux:button>
</div>
<pre><code class="lang-shell">{{$script->content}}</code></pre>
</div>
</div>
@fluxScripts
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js"
integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/prism-bash.min.js"
integrity="sha512-whYhDwtTmlC/NpZlCr6PSsAaLOrfjVg/iXAnC4H/dtiHawpShhT2SlIMbpIhT/IL/NrpdMm+Hq2C13+VKpHTYw=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</body>
</html>

View File

@ -7,11 +7,14 @@ use App\Livewire\Auth\Login;
use App\Livewire\Auth\Register;
use App\Livewire\Auth\ResetPassword;
use App\Livewire\Auth\VerifyEmail;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::middleware('guest')->group(function () {
Route::get('login', Login::class)->name('login');
if (User::count() == 0) {
Route::get('register', Register::class)->name('register');
}
Route::get('forgot-password', ForgotPassword::class)->name('password.request');
Route::get('reset-password/{token}', ResetPassword::class)->name('password.reset');
});

View File

@ -3,6 +3,7 @@
use App\Livewire\Settings\Appearance;
use App\Livewire\Settings\Password;
use App\Livewire\Settings\Profile;
use App\Models\Script;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
@ -22,3 +23,15 @@ Route::middleware(['auth'])->group(function () {
});
require __DIR__ . '/auth.php';
Route::get("{path}.sh", function (string $path) {
$script = Script::where('path', $path)->firstOrFail();
return response($script->content, 200)->header('Content-Type', 'text/plain');
})->name('script-content');
Route::get("{path}", function (string $path) {
$script = Script::where('path', $path)->firstOrFail();
return view('script', compact('script'));
})->name('script-view');

View File

@ -14,5 +14,6 @@ export default defineConfig({
],
server: {
cors: true,
host: "scripthost.test"
},
});