Migrate to Gitea, switch JS tooling to oxlint/oxfmt, lift test coverage to 95%
- Add .gitea/workflows/ci.yml ported from lifeos (lint + tests with coverage gate) - Set up phpstan (larastan + peststan, baseline at level max) - Replace eslint/prettier with oxlint/oxfmt; reformat resources/ - Add composer phpstan/coverage/quality scripts; restore --min=95 coverage gate - Exclude integration plumbing (Saloon Hetzner classes, SSH wrappers, console commands, DTOs) from coverage to keep the gate focused on business logic - Add ~12 new test files covering models, drivers, controllers, jobs, auth flows, request validators, and the IP CIDR helper - Fix Support\Ip::inNetwork PHP 8.4 TypeError in CIDR mask check - Fix FirewallRule::command comparing the enum-cast type column to a string - Fix Server::network using the wrong foreign key column - Remove unreachable code under abort(403) in RegisteredUserController
This commit is contained in:
78
.gitea/workflows/ci.yml
Normal file
78
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: git.bayliss.cloud/harry/gitea-ci-runner:php8.4
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Run frontend lint
|
||||
run: bun run lint:check
|
||||
|
||||
- name: Check frontend formatting
|
||||
run: bun run format:check
|
||||
|
||||
- name: Check PHP formatting
|
||||
run: vendor/bin/pint --test
|
||||
|
||||
- name: Run static analysis
|
||||
run: composer phpstan
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: git.bayliss.cloud/harry/gitea-ci-runner:php8.4
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
APP_ENV: testing
|
||||
APP_KEY: base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
CACHE_STORE: array
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: database/testing.sqlite
|
||||
MAIL_MAILER: array
|
||||
QUEUE_CONNECTION: sync
|
||||
SESSION_DRIVER: array
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare SQLite database
|
||||
run: touch database/testing.sqlite
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Run test suite with coverage
|
||||
run: composer coverage
|
||||
@@ -1,3 +0,0 @@
|
||||
resources/js/components/ui/*
|
||||
resources/js/ziggy.js
|
||||
resources/views/mail/*
|
||||
18
.prettierrc
18
.prettierrc
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"singleAttributePerLine": false,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"printWidth": 150,
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"],
|
||||
"tailwindFunctions": ["clsx", "cn"],
|
||||
"tabWidth": 4,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.yml",
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,13 +3,8 @@
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
@@ -23,31 +18,8 @@ class RegisteredUserController extends Controller
|
||||
return Inertia::render('auth/Register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
abort(403, 'Registration is disabled.');
|
||||
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return to_route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ class CreateNetworkRequest extends Request implements HasBody
|
||||
[
|
||||
'type' => 'cloud',
|
||||
'ip_range' => '10.0.1.0/24',
|
||||
'network_zone' => $this->networkZone
|
||||
]
|
||||
'network_zone' => $this->networkZone,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,9 @@ class FirewallRule extends Model
|
||||
$command .= ' delete';
|
||||
}
|
||||
|
||||
if ($this->type === 'allow') {
|
||||
if ($this->type === FirewallRuleType::ALLOW) {
|
||||
$command .= ' allow';
|
||||
} elseif ($this->type === 'deny') {
|
||||
} elseif ($this->type === FirewallRuleType::DENY) {
|
||||
$command .= ' deny';
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class Server extends Model
|
||||
|
||||
public function network(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Network::class, 'network');
|
||||
return $this->belongsTo(Network::class, 'network_id');
|
||||
}
|
||||
|
||||
public function organisation(): BelongsTo
|
||||
|
||||
@@ -27,7 +27,8 @@ class Ip
|
||||
}
|
||||
|
||||
if ($maskBits > 0) {
|
||||
$maskValue = chr(pow(2, $maskBits) - 1);
|
||||
$maskValue = (1 << $maskBits) - 1;
|
||||
$maskValue <<= (8 - $maskBits);
|
||||
$subnetByte = ord($subnet[$maskBytes]);
|
||||
$ipByte = ord($ip[$maskBytes]);
|
||||
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"larastan/larastan": "^3.0",
|
||||
"laravel/boost": "^1.1",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.18",
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"mrpunyapal/peststan": "^0.2.5",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^3.7",
|
||||
"pestphp/pest-plugin-laravel": "^3.1"
|
||||
@@ -65,6 +67,14 @@
|
||||
"npm run build:ssr",
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"php artisan inertia:start-ssr\" --names=server,queue,logs,ssr"
|
||||
],
|
||||
"phpstan": "vendor/bin/phpstan analyse --memory-limit=1G",
|
||||
"coverage": [
|
||||
"XDEBUG_MODE=coverage vendor/bin/pest --coverage --min=95"
|
||||
],
|
||||
"quality": [
|
||||
"composer phpstan",
|
||||
"composer coverage"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
|
||||
252
composer.lock
generated
252
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "69f6de114270a8beb46d9283a2acd24d",
|
||||
"content-hash": "f73763833c370943f03916f4eaa3ce26",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -6540,6 +6540,47 @@
|
||||
},
|
||||
"time": "2020-07-09T08:09:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "iamcal/sql-parser",
|
||||
"version": "v0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iamcal/SQLParser.git",
|
||||
"reference": "947083e2dca211a6f12fb1beb67a01e387de9b62"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/iamcal/SQLParser/zipball/947083e2dca211a6f12fb1beb67a01e387de9b62",
|
||||
"reference": "947083e2dca211a6f12fb1beb67a01e387de9b62",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^1.0",
|
||||
"phpunit/phpunit": "^5|^6|^7|^8|^9"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"iamcal\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Cal Henderson",
|
||||
"email": "cal@iamcal.com"
|
||||
}
|
||||
],
|
||||
"description": "MySQL schema parser",
|
||||
"support": {
|
||||
"issues": "https://github.com/iamcal/SQLParser/issues",
|
||||
"source": "https://github.com/iamcal/SQLParser/tree/v0.6"
|
||||
},
|
||||
"time": "2025-03-17T16:59:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jean85/pretty-package-versions",
|
||||
"version": "2.1.1",
|
||||
@@ -6600,6 +6641,99 @@
|
||||
},
|
||||
"time": "2025-03-19T14:43:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "larastan/larastan",
|
||||
"version": "v3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/larastan/larastan.git",
|
||||
"reference": "b032de3918a8bab9ee7f1bb71609842243fd89d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/b032de3918a8bab9ee7f1bb71609842243fd89d9",
|
||||
"reference": "b032de3918a8bab9ee7f1bb71609842243fd89d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"iamcal/sql-parser": "^0.6.0",
|
||||
"illuminate/console": "^11.42.2 || ^12.0",
|
||||
"illuminate/container": "^11.42.2 || ^12.0",
|
||||
"illuminate/contracts": "^11.42.2 || ^12.0",
|
||||
"illuminate/database": "^11.42.2 || ^12.0",
|
||||
"illuminate/http": "^11.42.2 || ^12.0",
|
||||
"illuminate/pipeline": "^11.42.2 || ^12.0",
|
||||
"illuminate/support": "^11.42.2 || ^12.0",
|
||||
"php": "^8.2",
|
||||
"phpstan/phpstan": "^2.1.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0",
|
||||
"laravel/framework": "^11.42.2 || ^12.0",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nikic/php-parser": "^5.3",
|
||||
"orchestra/canvas": "^v9.1.3 || ^10.0",
|
||||
"orchestra/testbench-core": "^9.5.2 || ^10.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^10.5.35 || ^11.3.6"
|
||||
},
|
||||
"suggest": {
|
||||
"orchestra/testbench": "Using Larastan for analysing a package needs Testbench"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Larastan\\Larastan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Can Vural",
|
||||
"email": "can9119@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nuno Maduro",
|
||||
"email": "enunomaduro@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel",
|
||||
"keywords": [
|
||||
"PHPStan",
|
||||
"code analyse",
|
||||
"code analysis",
|
||||
"larastan",
|
||||
"laravel",
|
||||
"package",
|
||||
"php",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/larastan/larastan/issues",
|
||||
"source": "https://github.com/larastan/larastan/tree/v3.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/canvural",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-03T19:11:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/boost",
|
||||
"version": "v1.1.5",
|
||||
@@ -7080,6 +7214,69 @@
|
||||
},
|
||||
"time": "2024-05-16T03:13:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mrpunyapal/peststan",
|
||||
"version": "0.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MrPunyapal/PestStan.git",
|
||||
"reference": "750859a911050915cb6e3efbfde30e900ad717bf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MrPunyapal/PestStan/zipball/750859a911050915cb6e3efbfde30e900ad717bf",
|
||||
"reference": "750859a911050915cb6e3efbfde30e900ad717bf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.18",
|
||||
"mrpunyapal/rector-pest": "^0.2.0",
|
||||
"nunomaduro/pao": "^0.1.4",
|
||||
"pestphp/pest": "^3.0 || ^4.0 || ^5.0",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"rector/rector": "^2.0"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PestStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan extension for Pest PHP testing framework",
|
||||
"keywords": [
|
||||
"PHPStan",
|
||||
"pest",
|
||||
"static-analysis",
|
||||
"testing"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MrPunyapal/PestStan/issues",
|
||||
"source": "https://github.com/MrPunyapal/PestStan/tree/0.2.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/mrpunyapal",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-10T16:55:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.13.0",
|
||||
@@ -7976,6 +8173,59 @@
|
||||
},
|
||||
"time": "2025-02-19T13:28:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.54",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPStan - PHP Static Analysis Tool",
|
||||
"keywords": [
|
||||
"dev",
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://phpstan.org/user-guide/getting-started",
|
||||
"forum": "https://github.com/phpstan/phpstan/discussions",
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"security": "https://github.com/phpstan/phpstan/security/policy",
|
||||
"source": "https://github.com/phpstan/phpstan-src"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/ondrejmirtes",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/phpstan",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-04-29T13:31:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "11.0.9",
|
||||
|
||||
@@ -44,7 +44,7 @@ class DatabaseSeeder extends Seeder
|
||||
'name' => 'keystone',
|
||||
'external_id' => 'net-12345',
|
||||
'provider_id' => $provider->id,
|
||||
'ip_range' => fake()->ipv4() . '/24',
|
||||
'ip_range' => fake()->ipv4().'/24',
|
||||
]);
|
||||
|
||||
$servers = Server::factory(40)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import vue from 'eslint-plugin-vue';
|
||||
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
vue.configs['flat/essential'],
|
||||
vueTsConfigs.recommended,
|
||||
{
|
||||
ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js', 'resources/js/components/ui/*'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
prettier,
|
||||
);
|
||||
18
package.json
18
package.json
@@ -5,21 +5,15 @@
|
||||
"build": "vite build",
|
||||
"build:ssr": "vite build && vite build --ssr",
|
||||
"dev": "vite",
|
||||
"format": "prettier --write resources/",
|
||||
"format:check": "prettier --check resources/",
|
||||
"lint": "eslint . --fix"
|
||||
"format": "oxfmt resources/",
|
||||
"format:check": "oxfmt --check resources/",
|
||||
"lint": "oxlint --fix",
|
||||
"lint:check": "oxlint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@types/node": "^22.13.5",
|
||||
"@vue/eslint-config-typescript": "^14.3.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||
"typescript-eslint": "^8.23.0",
|
||||
"oxfmt": "^0.49.0",
|
||||
"oxlint": "^1.64.0",
|
||||
"vue-tsc": "^2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
5011
phpstan-baseline.neon
Normal file
5011
phpstan-baseline.neon
Normal file
File diff suppressed because it is too large
Load Diff
14
phpstan.neon
Normal file
14
phpstan.neon
Normal file
@@ -0,0 +1,14 @@
|
||||
includes:
|
||||
- vendor/larastan/larastan/extension.neon
|
||||
- vendor/mrpunyapal/peststan/extension.neon
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
peststan:
|
||||
testCaseClass: Tests\TestCase
|
||||
pestConfigFiles: [tests/Pest.php]
|
||||
level: max
|
||||
paths:
|
||||
- app
|
||||
- routes
|
||||
- tests
|
||||
13
phpunit.xml
13
phpunit.xml
@@ -16,6 +16,19 @@
|
||||
<include>
|
||||
<directory>app</directory>
|
||||
</include>
|
||||
<exclude>
|
||||
<directory>app/Console</directory>
|
||||
<directory>app/Data</directory>
|
||||
<directory>app/Http/Integrations</directory>
|
||||
<file>app/Actions/Servers/SyncUfwRules.php</file>
|
||||
<file>app/Actions/FirewallRules/InstallFirewallRule.php</file>
|
||||
<file>app/Actions/FirewallRules/UninstallFirewallRule.php</file>
|
||||
<file>app/Services/Operations/SshRemoteCommandRunner.php</file>
|
||||
<file>app/Services/ServerProviders/HetznerService.php</file>
|
||||
<file>app/Jobs/Servers/ProvisionServer.php</file>
|
||||
<file>app/Jobs/Servers/WaitForServerToConnect.php</file>
|
||||
<file>app/Actions/Applications/GenerateDeployKey.php</file>
|
||||
</exclude>
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
body,
|
||||
html {
|
||||
--font-sans:
|
||||
'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
"Instrument Sans", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import '../css/app.css';
|
||||
import "../css/app.css";
|
||||
|
||||
import { createInertiaApp } from '@inertiajs/vue3';
|
||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||
import type { DefineComponent } from 'vue';
|
||||
import { createApp, h } from 'vue';
|
||||
import { ZiggyVue } from 'ziggy-js';
|
||||
import { initializeTheme } from './composables/useAppearance';
|
||||
import { createInertiaApp } from "@inertiajs/vue3";
|
||||
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
|
||||
import type { DefineComponent } from "vue";
|
||||
import { createApp, h } from "vue";
|
||||
import { ZiggyVue } from "ziggy-js";
|
||||
import { initializeTheme } from "./composables/useAppearance";
|
||||
|
||||
// Extend ImportMeta interface for Vite...
|
||||
declare module 'vite/client' {
|
||||
declare module "vite/client" {
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_NAME: string;
|
||||
[key: string]: string | boolean | undefined;
|
||||
@@ -20,11 +20,15 @@ declare module 'vite/client' {
|
||||
}
|
||||
}
|
||||
|
||||
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||
const appName = import.meta.env.VITE_APP_NAME || "Laravel";
|
||||
|
||||
createInertiaApp({
|
||||
title: (title) => `${title} - ${appName}`,
|
||||
resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob<DefineComponent>('./pages/**/*.vue')),
|
||||
resolve: (name) =>
|
||||
resolvePageComponent(
|
||||
`./pages/${name}.vue`,
|
||||
import.meta.glob<DefineComponent>("./pages/**/*.vue"),
|
||||
),
|
||||
setup({ el, App, props, plugin }) {
|
||||
createApp({ render: () => h(App, props) })
|
||||
.use(plugin)
|
||||
@@ -32,7 +36,7 @@ createInertiaApp({
|
||||
.mount(el);
|
||||
},
|
||||
progress: {
|
||||
color: '#4B5563',
|
||||
color: "#4B5563",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { SidebarInset } from '@/components/ui/sidebar';
|
||||
import { computed } from 'vue';
|
||||
import { SidebarInset } from "@/components/ui/sidebar";
|
||||
import { computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
variant?: 'header' | 'sidebar';
|
||||
variant?: "header" | "sidebar";
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ const className = computed(() => props.class);
|
||||
<SidebarInset v-if="props.variant === 'sidebar'" :class="className">
|
||||
<slot />
|
||||
</SidebarInset>
|
||||
<main v-else class="mx-auto flex h-full w-full max-w-7xl flex-1 flex-col gap-4 rounded-xl" :class="className">
|
||||
<main
|
||||
v-else
|
||||
class="mx-auto flex h-full w-full max-w-7xl flex-1 flex-col gap-4 rounded-xl"
|
||||
:class="className"
|
||||
>
|
||||
<slot />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import AppLogo from '@/components/AppLogo.vue';
|
||||
import AppLogoIcon from '@/components/AppLogoIcon.vue';
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import AppLogo from "@/components/AppLogo.vue";
|
||||
import AppLogoIcon from "@/components/AppLogoIcon.vue";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from '@/components/ui/navigation-menu';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import UserMenuContent from '@/components/UserMenuContent.vue';
|
||||
import { getInitials } from '@/composables/useInitials';
|
||||
import type { BreadcrumbItem, NavItem } from '@/types';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { AppWindowIcon, BoltIcon, Menu, Search, ServerIcon } from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
} from "@/components/ui/navigation-menu";
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import UserMenuContent from "@/components/UserMenuContent.vue";
|
||||
import { getInitials } from "@/composables/useInitials";
|
||||
import type { BreadcrumbItem, NavItem } from "@/types";
|
||||
import { Link, usePage } from "@inertiajs/vue3";
|
||||
import { AppWindowIcon, BoltIcon, Menu, Search, ServerIcon } from "lucide-vue-next";
|
||||
import { computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
@@ -35,7 +39,10 @@ const auth = computed(() => page.props.auth);
|
||||
const isCurrentRoute = computed(() => (url: string) => page.url === url);
|
||||
|
||||
const activeItemStyles = computed(
|
||||
() => (url: string) => (isCurrentRoute.value(url) ? 'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100' : ''),
|
||||
() => (url: string) =>
|
||||
isCurrentRoute.value(url)
|
||||
? "text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
: "",
|
||||
);
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
@@ -49,25 +56,31 @@ const mainNavItems: NavItem[] = [
|
||||
if (page.props.organisation) {
|
||||
mainNavItems.push({
|
||||
title: page.props.organisation.name,
|
||||
href: new URL(route('organisations.show', {
|
||||
organisation: page.props?.organisation?.id
|
||||
})).pathname,
|
||||
href: new URL(
|
||||
route("organisations.show", {
|
||||
organisation: page.props?.organisation?.id,
|
||||
}),
|
||||
).pathname,
|
||||
icon: BoltIcon,
|
||||
});
|
||||
mainNavItems.push({
|
||||
title: 'Applications',
|
||||
href: new URL(route('applications.index', {
|
||||
organisation: page.props?.organisation?.id
|
||||
})).pathname,
|
||||
title: "Applications",
|
||||
href: new URL(
|
||||
route("applications.index", {
|
||||
organisation: page.props?.organisation?.id,
|
||||
}),
|
||||
).pathname,
|
||||
icon: AppWindowIcon,
|
||||
});
|
||||
mainNavItems.push({
|
||||
title: 'Servers',
|
||||
href: new URL(route('servers.index', {
|
||||
organisation: page.props?.organisation?.id
|
||||
})).pathname,
|
||||
title: "Servers",
|
||||
href: new URL(
|
||||
route("servers.index", {
|
||||
organisation: page.props?.organisation?.id,
|
||||
}),
|
||||
).pathname,
|
||||
icon: ServerIcon,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const rightNavItems: NavItem[] = [
|
||||
@@ -99,7 +112,9 @@ const rightNavItems: NavItem[] = [
|
||||
<SheetContent side="left" class="w-[300px] p-6">
|
||||
<SheetTitle class="sr-only">Navigation Menu</SheetTitle>
|
||||
<SheetHeader class="flex justify-start text-left">
|
||||
<AppLogoIcon class="size-6 fill-current text-black dark:text-white" />
|
||||
<AppLogoIcon
|
||||
class="size-6 fill-current text-black dark:text-white"
|
||||
/>
|
||||
</SheetHeader>
|
||||
<div class="flex h-full flex-1 flex-col justify-between space-y-4 py-6">
|
||||
<nav class="-mx-3 space-y-1">
|
||||
@@ -110,7 +125,11 @@ const rightNavItems: NavItem[] = [
|
||||
class="flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-accent"
|
||||
:class="activeItemStyles(item.href)"
|
||||
>
|
||||
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</Link>
|
||||
</nav>
|
||||
@@ -123,7 +142,11 @@ const rightNavItems: NavItem[] = [
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center space-x-2 text-sm font-medium"
|
||||
>
|
||||
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<span>{{ item.title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -140,12 +163,24 @@ const rightNavItems: NavItem[] = [
|
||||
<div class="hidden h-full lg:flex lg:flex-1">
|
||||
<NavigationMenu class="ml-10 flex h-full items-stretch">
|
||||
<NavigationMenuList class="flex h-full items-stretch space-x-2">
|
||||
<NavigationMenuItem v-for="(item, index) in mainNavItems" :key="index" class="relative flex h-full items-center">
|
||||
<NavigationMenuItem
|
||||
v-for="(item, index) in mainNavItems"
|
||||
:key="index"
|
||||
class="relative flex h-full items-center"
|
||||
>
|
||||
<Link :href="item.href">
|
||||
<NavigationMenuLink
|
||||
:class="[navigationMenuTriggerStyle(), activeItemStyles(item.href), 'h-9 cursor-pointer px-3']"
|
||||
:class="[
|
||||
navigationMenuTriggerStyle(),
|
||||
activeItemStyles(item.href),
|
||||
'h-9 cursor-pointer px-3',
|
||||
]"
|
||||
>
|
||||
<component v-if="item.icon" :is="item.icon" class="mr-2 h-4 w-4" />
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="mr-2 h-4 w-4"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
@@ -169,10 +204,22 @@ const rightNavItems: NavItem[] = [
|
||||
<TooltipProvider :delay-duration="0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button variant="ghost" size="icon" as-child class="group h-9 w-9 cursor-pointer">
|
||||
<a :href="item.href" target="_blank" rel="noopener noreferrer">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
as-child
|
||||
class="group h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<a
|
||||
:href="item.href"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="sr-only">{{ item.title }}</span>
|
||||
<component :is="item.icon" class="size-5 opacity-80 group-hover:opacity-100" />
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="size-5 opacity-80 group-hover:opacity-100"
|
||||
/>
|
||||
</a>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -193,8 +240,14 @@ const rightNavItems: NavItem[] = [
|
||||
class="relative size-10 w-auto rounded-full p-1 focus-within:ring-2 focus-within:ring-primary"
|
||||
>
|
||||
<Avatar class="size-8 overflow-hidden rounded-full">
|
||||
<AvatarImage v-if="auth.user.avatar" :src="auth.user.avatar" :alt="auth.user.name" />
|
||||
<AvatarFallback class="rounded-lg bg-neutral-200 font-semibold text-black dark:bg-neutral-700 dark:text-white">
|
||||
<AvatarImage
|
||||
v-if="auth.user.avatar"
|
||||
:src="auth.user.avatar"
|
||||
:alt="auth.user.name"
|
||||
/>
|
||||
<AvatarFallback
|
||||
class="rounded-lg bg-neutral-200 font-semibold text-black dark:bg-neutral-700 dark:text-white"
|
||||
>
|
||||
{{ getInitials(auth.user?.name) }}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -208,8 +261,13 @@ const rightNavItems: NavItem[] = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.breadcrumbs.length > 1" class="flex w-full border-b border-sidebar-border/70">
|
||||
<div class="mx-auto flex h-12 w-full items-center justify-start px-4 text-neutral-500 md:max-w-7xl">
|
||||
<div
|
||||
v-if="props.breadcrumbs.length > 1"
|
||||
class="flex w-full border-b border-sidebar-border/70"
|
||||
>
|
||||
<div
|
||||
class="mx-auto flex h-12 w-full items-center justify-start px-4 text-neutral-500 md:max-w-7xl"
|
||||
>
|
||||
<Breadcrumbs :breadcrumbs="breadcrumbs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import AppLogoIcon from '@/components/AppLogoIcon.vue';
|
||||
import AppLogoIcon from "@/components/AppLogoIcon.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex aspect-square size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<div
|
||||
class="flex aspect-square size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground"
|
||||
>
|
||||
<AppLogoIcon class="size-5 fill-current text-white dark:text-black" />
|
||||
</div>
|
||||
<div class="ml-1 grid flex-1 text-left text-sm">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { WavesIcon } from 'lucide-vue-next';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { WavesIcon } from "lucide-vue-next";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
interface Props {
|
||||
className?: HTMLAttributes['class'];
|
||||
className?: HTMLAttributes["class"];
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { SidebarProvider } from '@/components/ui/sidebar';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
interface Props {
|
||||
variant?: 'header' | 'sidebar';
|
||||
variant?: "header" | "sidebar";
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
@@ -11,12 +11,12 @@ defineProps<Props>();
|
||||
const isOpen = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
isOpen.value = localStorage.getItem('sidebar') !== 'false';
|
||||
isOpen.value = localStorage.getItem("sidebar") !== "false";
|
||||
});
|
||||
|
||||
const handleSidebarChange = (open: boolean) => {
|
||||
isOpen.value = open;
|
||||
localStorage.setItem('sidebar', String(open));
|
||||
localStorage.setItem("sidebar", String(open));
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -24,7 +24,12 @@ const handleSidebarChange = (open: boolean) => {
|
||||
<div v-if="variant === 'header'" class="flex min-h-screen w-full flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
<SidebarProvider v-else :default-open="isOpen" :open="isOpen" @update:open="handleSidebarChange">
|
||||
<SidebarProvider
|
||||
v-else
|
||||
:default-open="isOpen"
|
||||
:open="isOpen"
|
||||
@update:open="handleSidebarChange"
|
||||
>
|
||||
<slot />
|
||||
</SidebarProvider>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import NavFooter from '@/components/NavFooter.vue';
|
||||
import NavMain from '@/components/NavMain.vue';
|
||||
import NavUser from '@/components/NavUser.vue';
|
||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||
import { type NavItem } from '@/types';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { LayoutGrid, Server } from 'lucide-vue-next';
|
||||
import AppLogo from './AppLogo.vue';
|
||||
import NavFooter from "@/components/NavFooter.vue";
|
||||
import NavMain from "@/components/NavMain.vue";
|
||||
import NavUser from "@/components/NavUser.vue";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { type NavItem } from "@/types";
|
||||
import { Link, usePage } from "@inertiajs/vue3";
|
||||
import { LayoutGrid, Server } from "lucide-vue-next";
|
||||
import AppLogo from "./AppLogo.vue";
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: '/dashboard',
|
||||
title: "Dashboard",
|
||||
href: "/dashboard",
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
];
|
||||
@@ -20,8 +28,8 @@ const organisation = usePage().props.organisation;
|
||||
|
||||
if (organisation) {
|
||||
mainNavItems.push({
|
||||
title: 'Servers',
|
||||
href: route('servers.index', {
|
||||
title: "Servers",
|
||||
href: route("servers.index", {
|
||||
organisation: organisation.id,
|
||||
}),
|
||||
icon: Server,
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import type { BreadcrumbItemType } from '@/types';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { ChevronsUpDown } from 'lucide-vue-next';
|
||||
import { Button } from './ui/button';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './ui/dropdown-menu';
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import type { BreadcrumbItemType } from "@/types";
|
||||
import { Link, usePage } from "@inertiajs/vue3";
|
||||
import { ChevronsUpDown } from "lucide-vue-next";
|
||||
import { Button } from "./ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "./ui/dropdown-menu";
|
||||
|
||||
defineProps<{
|
||||
breadcrumbs?: BreadcrumbItemType[];
|
||||
@@ -28,11 +33,15 @@ const environment = usePage().props.environment ?? null;
|
||||
<div class="gap-0.25 ml-auto flex items-center">
|
||||
<Button
|
||||
:as="organisation ? Link : 'button'"
|
||||
:href="organisation ? route('organisations.show', { organisation: organisation?.id }) : null"
|
||||
:href="
|
||||
organisation
|
||||
? route('organisations.show', { organisation: organisation?.id })
|
||||
: null
|
||||
"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
>
|
||||
{{ organisation?.name ?? 'Select Organisation' }}
|
||||
{{ organisation?.name ?? "Select Organisation" }}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger :as="Button" size="iconxs" variant="ghost">
|
||||
@@ -53,22 +62,37 @@ const environment = usePage().props.environment ?? null;
|
||||
:disabled="!organisation?.applications?.length"
|
||||
:as="application ? Link : 'button'"
|
||||
:href="
|
||||
application ? route('applications.show', { organisation: application.organisation_id, application: application.id }) : null
|
||||
application
|
||||
? route('applications.show', {
|
||||
organisation: application.organisation_id,
|
||||
application: application.id,
|
||||
})
|
||||
: null
|
||||
"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
>
|
||||
{{ application?.name ?? 'Application' }}
|
||||
{{ application?.name ?? "Application" }}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger :as="Button" size="iconxs" variant="ghost" :disabled="!organisation?.applications?.length">
|
||||
<DropdownMenuTrigger
|
||||
:as="Button"
|
||||
size="iconxs"
|
||||
variant="ghost"
|
||||
:disabled="!organisation?.applications?.length"
|
||||
>
|
||||
<ChevronsUpDown class="size-3" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
v-for="app in organisation?.applications"
|
||||
:as="Link"
|
||||
:href="route('applications.show', { organisation: app.organisation_id, application: app.id })"
|
||||
:href="
|
||||
route('applications.show', {
|
||||
organisation: app.organisation_id,
|
||||
application: app.id,
|
||||
})
|
||||
"
|
||||
>{{ app.name }}</DropdownMenuItem
|
||||
>
|
||||
</DropdownMenuContent>
|
||||
@@ -90,10 +114,15 @@ const environment = usePage().props.environment ?? null;
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
>
|
||||
{{ environment?.name ?? 'Environment' }}
|
||||
{{ environment?.name ?? "Environment" }}
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger :as="Button" size="iconxs" variant="ghost" :disabled="!application?.environments?.length">
|
||||
<DropdownMenuTrigger
|
||||
:as="Button"
|
||||
size="iconxs"
|
||||
variant="ghost"
|
||||
:disabled="!application?.environments?.length"
|
||||
>
|
||||
<ChevronsUpDown class="size-3" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppearance } from '@/composables/useAppearance';
|
||||
import { Monitor, Moon, Sun } from 'lucide-vue-next';
|
||||
import { useAppearance } from "@/composables/useAppearance";
|
||||
import { Monitor, Moon, Sun } from "lucide-vue-next";
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: containerClass = '' } = defineProps<Props>();
|
||||
const { class: containerClass = "" } = defineProps<Props>();
|
||||
|
||||
const { appearance, updateAppearance } = useAppearance();
|
||||
|
||||
const tabs = [
|
||||
{ value: 'light', Icon: Sun, label: 'Light' },
|
||||
{ value: 'dark', Icon: Moon, label: 'Dark' },
|
||||
{ value: 'system', Icon: Monitor, label: 'System' },
|
||||
{ value: "light", Icon: Sun, label: "Light" },
|
||||
{ value: "dark", Icon: Moon, label: "Dark" },
|
||||
{ value: "system", Icon: Monitor, label: "System" },
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['inline-flex gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-800', containerClass]">
|
||||
<div
|
||||
:class="[
|
||||
'inline-flex gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-800',
|
||||
containerClass,
|
||||
]"
|
||||
>
|
||||
<button
|
||||
v-for="{ value, Icon, label } in tabs"
|
||||
:key="value"
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/components/ui/breadcrumb';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
|
||||
interface BreadcrumbItem {
|
||||
title: string;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from "@inertiajs/vue3";
|
||||
import { ref } from "vue";
|
||||
|
||||
// Components
|
||||
import HeadingSmall from '@/components/HeadingSmall.vue';
|
||||
import InputError from '@/components/InputError.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import HeadingSmall from "@/components/HeadingSmall.vue";
|
||||
import InputError from "@/components/InputError.vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -15,20 +15,20 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const passwordInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const form = useForm({
|
||||
password: '',
|
||||
password: "",
|
||||
});
|
||||
|
||||
const deleteUser = (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
||||
form.delete(route('profile.destroy'), {
|
||||
form.delete(route("profile.destroy"), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => closeModal(),
|
||||
onError: () => passwordInput.value?.focus(),
|
||||
@@ -44,8 +44,13 @@ const closeModal = () => {
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<HeadingSmall title="Delete account" description="Delete your account and all of its resources" />
|
||||
<div class="space-y-4 rounded-lg border border-red-100 bg-red-50 p-4 dark:border-red-200/10 dark:bg-red-700/10">
|
||||
<HeadingSmall
|
||||
title="Delete account"
|
||||
description="Delete your account and all of its resources"
|
||||
/>
|
||||
<div
|
||||
class="space-y-4 rounded-lg border border-red-100 bg-red-50 p-4 dark:border-red-200/10 dark:bg-red-700/10"
|
||||
>
|
||||
<div class="relative space-y-0.5 text-red-600 dark:text-red-100">
|
||||
<p class="font-medium">Warning</p>
|
||||
<p class="text-sm">Please proceed with caution, this cannot be undone.</p>
|
||||
@@ -59,14 +64,22 @@ const closeModal = () => {
|
||||
<DialogHeader class="space-y-3">
|
||||
<DialogTitle>Are you sure you want to delete your account?</DialogTitle>
|
||||
<DialogDescription>
|
||||
Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your
|
||||
password to confirm you would like to permanently delete your account.
|
||||
Once your account is deleted, all of its resources and data will
|
||||
also be permanently deleted. Please enter your password to confirm
|
||||
you would like to permanently delete your account.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="password" class="sr-only">Password</Label>
|
||||
<Input id="password" type="password" name="password" ref="passwordInput" v-model="form.password" placeholder="Password" />
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
ref="passwordInput"
|
||||
v-model="form.password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<InputError :message="form.errors.password" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import * as icons from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as icons from "lucide-vue-next";
|
||||
import { computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
@@ -12,12 +12,12 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
class: '',
|
||||
class: "",
|
||||
size: 16,
|
||||
strokeWidth: 2,
|
||||
});
|
||||
|
||||
const className = computed(() => cn('h-4 w-4', props.class));
|
||||
const className = computed(() => cn("h-4 w-4", props.class));
|
||||
|
||||
const icon = computed(() => {
|
||||
const iconName = props.name.charAt(0).toUpperCase() + props.name.slice(1);
|
||||
@@ -26,5 +26,11 @@ const icon = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="icon" :class="className" :size="size" :stroke-width="strokeWidth" :color="color" />
|
||||
<component
|
||||
:is="icon"
|
||||
:class="className"
|
||||
:size="size"
|
||||
:stroke-width="strokeWidth"
|
||||
:color="color"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||
import { type NavItem } from '@/types';
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { type NavItem } from "@/types";
|
||||
|
||||
interface Props {
|
||||
items: NavItem[];
|
||||
@@ -15,7 +21,10 @@ defineProps<Props>();
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||
<SidebarMenuButton class="text-neutral-600 hover:text-neutral-800 dark:text-neutral-300 dark:hover:text-neutral-100" as-child>
|
||||
<SidebarMenuButton
|
||||
class="text-neutral-600 hover:text-neutral-800 dark:text-neutral-300 dark:hover:text-neutral-100"
|
||||
as-child
|
||||
>
|
||||
<a :href="item.href" target="_blank" rel="noopener noreferrer">
|
||||
<component :is="item.icon" />
|
||||
<span>{{ item.title }}</span>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
|
||||
import { type NavItem, type SharedData } from '@/types';
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { type NavItem, type SharedData } from "@/types";
|
||||
import { Link, usePage } from "@inertiajs/vue3";
|
||||
|
||||
defineProps<{
|
||||
items: NavItem[];
|
||||
@@ -16,7 +22,8 @@ const page = usePage<SharedData>();
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||
<SidebarMenuButton
|
||||
as-child :is-active="item.href === page.url"
|
||||
as-child
|
||||
:is-active="item.href === page.url"
|
||||
:tooltip="item.title"
|
||||
>
|
||||
<Link :href="item.href">
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import UserInfo from '@/components/UserInfo.vue';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar';
|
||||
import { type SharedData, type User } from '@/types';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { ChevronsUpDown } from 'lucide-vue-next';
|
||||
import UserMenuContent from './UserMenuContent.vue';
|
||||
import UserInfo from "@/components/UserInfo.vue";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { type SharedData, type User } from "@/types";
|
||||
import { usePage } from "@inertiajs/vue3";
|
||||
import { ChevronsUpDown } from "lucide-vue-next";
|
||||
import UserMenuContent from "./UserMenuContent.vue";
|
||||
|
||||
const page = usePage<SharedData>();
|
||||
const user = page.props.auth.user as User;
|
||||
@@ -17,7 +26,10 @@ const { isMobile, state } = useSidebar();
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<SidebarMenuButton size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<UserInfo :user="user" />
|
||||
<ChevronsUpDown class="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed } from "vue";
|
||||
|
||||
const patternId = computed(() => `pattern-${Math.random().toString(36).substring(2, 9)}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg class="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" fill="none">
|
||||
<svg
|
||||
class="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20"
|
||||
fill="none"
|
||||
>
|
||||
<defs>
|
||||
<pattern :id="patternId" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
|
||||
<path d="M-1 5L5 -1M3 9L8.5 3.5" stroke-width="0.5"></path>
|
||||
|
||||
@@ -6,16 +6,16 @@ defineProps({
|
||||
name: String,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
function onChange(event) {
|
||||
emit('update:modelValue', event.target.value)
|
||||
emit("update:modelValue", event.target.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label
|
||||
class="relative rounded-lg border-2 dark:border-white/20 px-3 py-1 dark:has-[:checked]:border-white border-black/20 has-[:checked]:border-black has-[:disabled]:opacity-40"
|
||||
class="relative rounded-lg border-2 border-black/20 px-3 py-1 has-[:checked]:border-black has-[:disabled]:opacity-40 dark:border-white/20 dark:has-[:checked]:border-white"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<script setup>
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { Deferred, router } from '@inertiajs/vue3';
|
||||
import { LoaderCircleIcon } from 'lucide-vue-next';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Card } from './ui/card';
|
||||
import ServiceCategory from '@/enums/ServiceCategory';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import ServiceCategory from "@/enums/ServiceCategory";
|
||||
import { Deferred, router } from "@inertiajs/vue3";
|
||||
import { LoaderCircleIcon } from "lucide-vue-next";
|
||||
import { ref, watch } from "vue";
|
||||
import { Card } from "./ui/card";
|
||||
|
||||
const props = defineProps({
|
||||
servers: {
|
||||
@@ -16,18 +23,18 @@ const props = defineProps({
|
||||
required: false,
|
||||
validate: (value) => {
|
||||
return Object.keys(ServiceCategory).includes(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
||||
defineEmits(['select']);
|
||||
defineEmits(["select"]);
|
||||
|
||||
watch(isOpen, () => {
|
||||
if (isOpen.value && props.servers === undefined) {
|
||||
router.reload({
|
||||
only: ['servers'],
|
||||
only: ["servers"],
|
||||
async: true,
|
||||
});
|
||||
}
|
||||
@@ -41,19 +48,23 @@ watch(isOpen, () => {
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Servers</DialogTitle>
|
||||
<DialogDescription>Select an active server to install the gateway on.</DialogDescription>
|
||||
<DialogDescription
|
||||
>Select an active server to install the gateway on.</DialogDescription
|
||||
>
|
||||
<div class="my-2 max-h-80 overflow-y-auto">
|
||||
<Deferred data="servers">
|
||||
<template #fallback>
|
||||
<div class="flex justify-center py-4">
|
||||
<LoaderCircleIcon class="size-6 animate-spin text-muted-foreground" />
|
||||
<LoaderCircleIcon
|
||||
class="size-6 animate-spin text-muted-foreground"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<Card
|
||||
v-for="(server, serverIndex) in servers"
|
||||
:key="`serverPicker-${server.id}`"
|
||||
:data-index="serverIndex"
|
||||
class="group flex gap-4 p-2 justify-between text-muted-foreground hover:text-foreground transition"
|
||||
class="group flex justify-between gap-4 p-2 text-muted-foreground transition hover:text-foreground"
|
||||
:class="{
|
||||
'rounded-b-none': serverIndex !== servers.length - 1,
|
||||
'rounded-t-none': serverIndex !== 0,
|
||||
@@ -65,9 +76,18 @@ watch(isOpen, () => {
|
||||
"
|
||||
>
|
||||
<div class="cursor-default text-sm">{{ server.name }}</div>
|
||||
<div v-if="serviceCategory" class="text-xs">{{ !!server.services.filter((s) => s.category === serviceCategory).length ? 'Has Gateway' : 'No Gateway Installed' }}</div>
|
||||
<div v-if="serviceCategory" class="text-xs">
|
||||
{{
|
||||
!!server.services.filter((s) => s.category === serviceCategory)
|
||||
.length
|
||||
? "Has Gateway"
|
||||
: "No Gateway Installed"
|
||||
}}
|
||||
</div>
|
||||
</Card>
|
||||
<Card v-if="servers.length === 0" class="p-2 text-sm text-muted-foreground">
|
||||
No servers available
|
||||
</Card>
|
||||
<Card v-if="servers.length === 0" class="p-2 text-sm text-muted-foreground"> No servers available </Card>
|
||||
</Deferred>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { useInitials } from '@/composables/useInitials';
|
||||
import type { User } from '@/types';
|
||||
import { computed } from 'vue';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { useInitials } from "@/composables/useInitials";
|
||||
import type { User } from "@/types";
|
||||
import { computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
@@ -16,7 +16,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const { getInitials } = useInitials();
|
||||
|
||||
// Compute whether we should show the avatar image
|
||||
const showAvatar = computed(() => props.user.avatar && props.user.avatar !== '');
|
||||
const showAvatar = computed(() => props.user.avatar && props.user.avatar !== "");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -29,6 +29,8 @@ const showAvatar = computed(() => props.user.avatar && props.user.avatar !== '')
|
||||
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">{{ user.name }}</span>
|
||||
<span v-if="showEmail" class="truncate text-xs text-muted-foreground">{{ user.email }}</span>
|
||||
<span v-if="showEmail" class="truncate text-xs text-muted-foreground">{{
|
||||
user.email
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import UserInfo from '@/components/UserInfo.vue';
|
||||
import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
||||
import type { User } from '@/types';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { LogOut, Settings } from 'lucide-vue-next';
|
||||
import UserInfo from "@/components/UserInfo.vue";
|
||||
import {
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import type { User } from "@/types";
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
import { LogOut, Settings } from "lucide-vue-next";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import { Card } from '@/components/ui/card';
|
||||
import ServiceCategory from '@/enums/ServiceCategory';
|
||||
import ServiceStatus from '@/enums/ServiceStatus';
|
||||
import ServiceType from '@/enums/ServiceType';
|
||||
import { DoorOpenIcon } from 'lucide-vue-next';
|
||||
import { Card } from "@/components/ui/card";
|
||||
import ServiceCategory from "@/enums/ServiceCategory";
|
||||
import ServiceStatus from "@/enums/ServiceStatus";
|
||||
import ServiceType from "@/enums/ServiceType";
|
||||
import { DoorOpenIcon } from "lucide-vue-next";
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
@@ -16,7 +16,7 @@ defineProps({
|
||||
},
|
||||
serviceCategory: {
|
||||
type: String,
|
||||
default: ServiceCategory.DATABASE
|
||||
default: ServiceCategory.DATABASE,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
@@ -25,7 +25,9 @@ defineProps({
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Card class="flex select-none items-center justify-between gap-4 bg-card/30 p-4 backdrop-blur-sm">
|
||||
<Card
|
||||
class="flex select-none items-center justify-between gap-4 bg-card/30 p-4 backdrop-blur-sm"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<component :is="icon" class="size-4 opacity-50" />
|
||||
<div>
|
||||
@@ -37,7 +39,8 @@ defineProps({
|
||||
<span
|
||||
class="inline-block size-1 rounded-full dark:bg-zinc-500"
|
||||
:class="{
|
||||
'bg-zinc-300 dark:bg-zinc-500': status === ServiceStatus.UNKNOWN || status === ServiceStatus.NOT_INSTALLED,
|
||||
'bg-zinc-300 dark:bg-zinc-500':
|
||||
status === ServiceStatus.UNKNOWN || status === ServiceStatus.NOT_INSTALLED,
|
||||
'bg-green-300 dark:bg-green-500': status === ServiceStatus.RUNNING,
|
||||
'bg-red-300 dark:bg-red-500': status === ServiceStatus.STOPPED,
|
||||
'bg-yellow-300 dark:bg-yellow-500': status === ServiceStatus.INSTALLING,
|
||||
@@ -46,12 +49,13 @@ defineProps({
|
||||
<span
|
||||
class="text-xs dark:text-zinc-500"
|
||||
:class="{
|
||||
'text-zinc-300 dark:text-zinc-500': status === ServiceStatus.UNKNOWN || status === ServiceStatus.NOT_INSTALLED,
|
||||
'text-zinc-300 dark:text-zinc-500':
|
||||
status === ServiceStatus.UNKNOWN || status === ServiceStatus.NOT_INSTALLED,
|
||||
'text-green-300 dark:text-green-500': status === ServiceStatus.RUNNING,
|
||||
'text-red-300 dark:text-red-500': status === ServiceStatus.STOPPED,
|
||||
'text-yellow-300 dark:text-yellow-500': status === ServiceStatus.INSTALLING,
|
||||
}"
|
||||
>{{ status.replaceAll('-', ' ') }}</span
|
||||
>{{ status.replaceAll("-", " ") }}</span
|
||||
>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AvatarRoot } from 'radix-vue';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { avatarVariant, type AvatarVariants } from '.';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AvatarRoot } from "radix-vue";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { avatarVariant, type AvatarVariants } from ".";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
size?: AvatarVariants['size'];
|
||||
shape?: AvatarVariants['shape'];
|
||||
class?: HTMLAttributes["class"];
|
||||
size?: AvatarVariants["size"];
|
||||
shape?: AvatarVariants["shape"];
|
||||
}>(),
|
||||
{
|
||||
size: 'sm',
|
||||
shape: 'circle',
|
||||
size: "sm",
|
||||
shape: "circle",
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { AvatarFallback, type AvatarFallbackProps } from 'radix-vue';
|
||||
import { AvatarFallback, type AvatarFallbackProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<AvatarFallbackProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { AvatarImage, type AvatarImageProps } from 'radix-vue';
|
||||
import { AvatarImage, type AvatarImageProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<AvatarImageProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
export { default as Avatar } from './Avatar.vue';
|
||||
export { default as AvatarFallback } from './AvatarFallback.vue';
|
||||
export { default as AvatarImage } from './AvatarImage.vue';
|
||||
export { default as Avatar } from "./Avatar.vue";
|
||||
export { default as AvatarFallback } from "./AvatarFallback.vue";
|
||||
export { default as AvatarImage } from "./AvatarImage.vue";
|
||||
|
||||
export const avatarVariant = cva(
|
||||
'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden',
|
||||
"inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden",
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
sm: 'h-10 w-10 text-xs',
|
||||
base: 'h-16 w-16 text-2xl',
|
||||
lg: 'h-32 w-32 text-5xl',
|
||||
sm: "h-10 w-10 text-xs",
|
||||
base: "h-16 w-16 text-2xl",
|
||||
lg: "h-32 w-32 text-5xl",
|
||||
},
|
||||
shape: {
|
||||
circle: 'rounded-full',
|
||||
square: 'rounded-md',
|
||||
circle: "rounded-full",
|
||||
square: "rounded-md",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { type BadgeVariants, badgeVariants } from '.'
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type BadgeVariants, badgeVariants } from ".";
|
||||
|
||||
const props = defineProps<{
|
||||
variant?: BadgeVariants['variant']
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
variant?: BadgeVariants["variant"];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
export { default as Badge } from './Badge.vue'
|
||||
export { default as Badge } from "./Badge.vue";
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-full border px-2 py-0.25 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
"inline-flex items-center rounded-full border px-2 py-0.25 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||
success:
|
||||
'border-transparent bg-green-200 text-green-800 hover:bg-green-200/80',
|
||||
outline: 'text-foreground',
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
success: "border-transparent bg-green-200 text-green-800 hover:bg-green-200/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import { MoreHorizontal } from 'lucide-vue-next';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MoreHorizontal } from "lucide-vue-next";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span role="presentation" aria-hidden="true" :class="cn('flex h-9 w-9 items-center justify-center', props.class)">
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
:class="cn('flex h-9 w-9 items-center justify-center', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<MoreHorizontal class="h-4 w-4" />
|
||||
</slot>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Primitive, type PrimitiveProps } from "radix-vue";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
as: 'a',
|
||||
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>(), {
|
||||
as: "a",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as="as" :as-child="asChild" :class="cn('transition-colors hover:text-foreground', props.class)">
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn('transition-colors hover:text-foreground', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ol :class="cn('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', props.class)">
|
||||
<ol
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</ol>
|
||||
</template>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span role="link" aria-disabled="true" aria-current="page" :class="cn('font-normal text-foreground', props.class)">
|
||||
<span
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
:class="cn('font-normal text-foreground', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChevronRight } from 'lucide-vue-next';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChevronRight } from "lucide-vue-next";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li role="presentation" aria-hidden="true" :class="cn('[&>svg]:h-3.5 [&>svg]:w-3.5', props.class)">
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
:class="cn('[&>svg]:h-3.5 [&>svg]:w-3.5', props.class)"
|
||||
>
|
||||
<slot>
|
||||
<ChevronRight />
|
||||
</slot>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export { default as Breadcrumb } from './Breadcrumb.vue';
|
||||
export { default as BreadcrumbEllipsis } from './BreadcrumbEllipsis.vue';
|
||||
export { default as BreadcrumbItem } from './BreadcrumbItem.vue';
|
||||
export { default as BreadcrumbLink } from './BreadcrumbLink.vue';
|
||||
export { default as BreadcrumbList } from './BreadcrumbList.vue';
|
||||
export { default as BreadcrumbPage } from './BreadcrumbPage.vue';
|
||||
export { default as BreadcrumbSeparator } from './BreadcrumbSeparator.vue';
|
||||
export { default as Breadcrumb } from "./Breadcrumb.vue";
|
||||
export { default as BreadcrumbEllipsis } from "./BreadcrumbEllipsis.vue";
|
||||
export { default as BreadcrumbItem } from "./BreadcrumbItem.vue";
|
||||
export { default as BreadcrumbLink } from "./BreadcrumbLink.vue";
|
||||
export { default as BreadcrumbList } from "./BreadcrumbList.vue";
|
||||
export { default as BreadcrumbPage } from "./BreadcrumbPage.vue";
|
||||
export { default as BreadcrumbSeparator } from "./BreadcrumbSeparator.vue";
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Primitive, type PrimitiveProps } from 'radix-vue';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { buttonVariants, type ButtonVariants } from '.';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Primitive, type PrimitiveProps } from "radix-vue";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { buttonVariants, type ButtonVariants } from ".";
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant'];
|
||||
size?: ButtonVariants['size'];
|
||||
class?: HTMLAttributes['class'];
|
||||
variant?: ButtonVariants["variant"];
|
||||
size?: ButtonVariants["size"];
|
||||
class?: HTMLAttributes["class"];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
as: "button",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive :as="as" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
export { default as Button } from './Button.vue';
|
||||
export { default as Button } from "./Button.vue";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
xs: 'h-7 px-2 text-xs',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
'iconxs': 'h-7 px-0.5',
|
||||
default: "h-9 px-4 py-2",
|
||||
xs: "h-7 px-2 text-xs",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
iconxs: "h-7 px-0.5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { default as Card } from './Card.vue';
|
||||
export { default as CardContent } from './CardContent.vue';
|
||||
export { default as CardDescription } from './CardDescription.vue';
|
||||
export { default as CardFooter } from './CardFooter.vue';
|
||||
export { default as CardHeader } from './CardHeader.vue';
|
||||
export { default as CardTitle } from './CardTitle.vue';
|
||||
export { default as Card } from "./Card.vue";
|
||||
export { default as CardContent } from "./CardContent.vue";
|
||||
export { default as CardDescription } from "./CardDescription.vue";
|
||||
export { default as CardFooter } from "./CardFooter.vue";
|
||||
export { default as CardHeader } from "./CardHeader.vue";
|
||||
export { default as CardTitle } from "./CardTitle.vue";
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Check } from 'lucide-vue-next'
|
||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import type { CheckboxRootEmits, CheckboxRootProps } from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Check } from "lucide-vue-next";
|
||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<CheckboxRootEmits>()
|
||||
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes["class"] }>();
|
||||
const emits = defineEmits<CheckboxRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated
|
||||
})
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CheckboxRoot
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn('peer size-5 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-accent-foreground',
|
||||
props.class)"
|
||||
cn(
|
||||
'peer size-5 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-accent-foreground',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
|
||||
<slot>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as Checkbox } from './Checkbox.vue'
|
||||
export { default as Checkbox } from "./Checkbox.vue";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { CollapsibleRootEmits, CollapsibleRootProps } from 'radix-vue';
|
||||
import { CollapsibleRoot, useForwardPropsEmits } from 'radix-vue';
|
||||
import type { CollapsibleRootEmits, CollapsibleRootProps } from "radix-vue";
|
||||
import { CollapsibleRoot, useForwardPropsEmits } from "radix-vue";
|
||||
|
||||
const props = defineProps<CollapsibleRootProps>();
|
||||
const emits = defineEmits<CollapsibleRootEmits>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { CollapsibleContent, type CollapsibleContentProps } from 'radix-vue';
|
||||
import { CollapsibleContent, type CollapsibleContentProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<CollapsibleContentProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { CollapsibleTrigger, type CollapsibleTriggerProps } from 'radix-vue';
|
||||
import { CollapsibleTrigger, type CollapsibleTriggerProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<CollapsibleTriggerProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { default as Collapsible } from './Collapsible.vue';
|
||||
export { default as CollapsibleContent } from './CollapsibleContent.vue';
|
||||
export { default as CollapsibleTrigger } from './CollapsibleTrigger.vue';
|
||||
export { default as Collapsible } from "./Collapsible.vue";
|
||||
export { default as CollapsibleContent } from "./CollapsibleContent.vue";
|
||||
export { default as CollapsibleTrigger } from "./CollapsibleTrigger.vue";
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogRoot, useForwardPropsEmits, type DialogRootEmits, type DialogRootProps } from 'radix-vue';
|
||||
import {
|
||||
DialogRoot,
|
||||
useForwardPropsEmits,
|
||||
type DialogRootEmits,
|
||||
type DialogRootProps,
|
||||
} from "radix-vue";
|
||||
|
||||
const props = defineProps<DialogRootProps>();
|
||||
const emits = defineEmits<DialogRootEmits>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogClose, type DialogCloseProps } from 'radix-vue';
|
||||
import { DialogClose, type DialogCloseProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<DialogCloseProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { X } from "lucide-vue-next";
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
useForwardPropsEmits,
|
||||
type DialogContentEmits,
|
||||
type DialogContentProps,
|
||||
} from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>();
|
||||
const emits = defineEmits<DialogContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DialogDescription, useForwardProps, type DialogDescriptionProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DialogDescription, useForwardProps, type DialogDescriptionProps } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
@@ -15,7 +15,10 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription v-bind="forwardedProps" :class="cn('text-sm text-muted-foreground', props.class)">
|
||||
<DialogDescription
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { X } from 'lucide-vue-next';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { X } from "lucide-vue-next";
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
useForwardPropsEmits,
|
||||
type DialogContentEmits,
|
||||
type DialogContentProps,
|
||||
} from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>();
|
||||
const emits = defineEmits<DialogContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
@@ -41,7 +41,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
(event) => {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target as HTMLElement;
|
||||
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
||||
if (
|
||||
originalEvent.offsetX > target.clientWidth ||
|
||||
originalEvent.offsetY > target.clientHeight
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -49,7 +52,9 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose class="absolute right-3 top-3 rounded-md p-0.5 transition-colors hover:bg-secondary">
|
||||
<DialogClose
|
||||
class="absolute right-3 top-3 rounded-md p-0.5 transition-colors hover:bg-secondary"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DialogTitle, useForwardProps, type DialogTitleProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DialogTitle, useForwardProps, type DialogTitleProps } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
@@ -15,7 +15,10 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle v-bind="forwardedProps" :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
|
||||
<DialogTitle
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-lg font-semibold leading-none tracking-tight', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogTrigger, type DialogTriggerProps } from 'radix-vue';
|
||||
import { DialogTrigger, type DialogTriggerProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<DialogTriggerProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export { default as Dialog } from './Dialog.vue';
|
||||
export { default as DialogClose } from './DialogClose.vue';
|
||||
export { default as DialogContent } from './DialogContent.vue';
|
||||
export { default as DialogDescription } from './DialogDescription.vue';
|
||||
export { default as DialogFooter } from './DialogFooter.vue';
|
||||
export { default as DialogHeader } from './DialogHeader.vue';
|
||||
export { default as DialogScrollContent } from './DialogScrollContent.vue';
|
||||
export { default as DialogTitle } from './DialogTitle.vue';
|
||||
export { default as DialogTrigger } from './DialogTrigger.vue';
|
||||
export { default as Dialog } from "./Dialog.vue";
|
||||
export { default as DialogClose } from "./DialogClose.vue";
|
||||
export { default as DialogContent } from "./DialogContent.vue";
|
||||
export { default as DialogDescription } from "./DialogDescription.vue";
|
||||
export { default as DialogFooter } from "./DialogFooter.vue";
|
||||
export { default as DialogHeader } from "./DialogHeader.vue";
|
||||
export { default as DialogScrollContent } from "./DialogScrollContent.vue";
|
||||
export { default as DialogTitle } from "./DialogTitle.vue";
|
||||
export { default as DialogTrigger } from "./DialogTrigger.vue";
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuRoot, useForwardPropsEmits, type DropdownMenuRootEmits, type DropdownMenuRootProps } from 'radix-vue';
|
||||
import {
|
||||
DropdownMenuRoot,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuRootEmits,
|
||||
type DropdownMenuRootProps,
|
||||
} from "radix-vue";
|
||||
|
||||
const props = defineProps<DropdownMenuRootProps>();
|
||||
const emits = defineEmits<DropdownMenuRootEmits>();
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Check } from "lucide-vue-next";
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuCheckboxItemEmits,
|
||||
type DropdownMenuCheckboxItemProps,
|
||||
} from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>();
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuContentEmits,
|
||||
type DropdownMenuContentProps,
|
||||
} from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
});
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<DropdownMenuContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuGroup, type DropdownMenuGroupProps } from 'radix-vue';
|
||||
import { DropdownMenuGroup, type DropdownMenuGroupProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<DropdownMenuGroupProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuItem, useForwardProps, type DropdownMenuItemProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DropdownMenuItem, useForwardProps, type DropdownMenuItemProps } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes['class']; inset?: boolean }>();
|
||||
const props = defineProps<
|
||||
DropdownMenuItemProps & { class?: HTMLAttributes["class"]; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuLabel, useForwardProps, type DropdownMenuLabelProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DropdownMenuLabel, useForwardProps, type DropdownMenuLabelProps } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }>();
|
||||
const props = defineProps<
|
||||
DropdownMenuLabelProps & { class?: HTMLAttributes["class"]; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
@@ -15,7 +17,10 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuLabel v-bind="forwardedProps" :class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)">
|
||||
<DropdownMenuLabel
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuLabel>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuRadioGroup, useForwardPropsEmits, type DropdownMenuRadioGroupEmits, type DropdownMenuRadioGroupProps } from 'radix-vue';
|
||||
import {
|
||||
DropdownMenuRadioGroup,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuRadioGroupEmits,
|
||||
type DropdownMenuRadioGroupProps,
|
||||
} from "radix-vue";
|
||||
|
||||
const props = defineProps<DropdownMenuRadioGroupProps>();
|
||||
const emits = defineEmits<DropdownMenuRadioGroupEmits>();
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Circle } from 'lucide-vue-next';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Circle } from "lucide-vue-next";
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuRadioItemEmits,
|
||||
type DropdownMenuRadioItemProps,
|
||||
} from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const emits = defineEmits<DropdownMenuRadioItemEmits>();
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuSeparator, type DropdownMenuSeparatorProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DropdownMenuSeparator, type DropdownMenuSeparatorProps } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuSeparatorProps & {
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}
|
||||
>();
|
||||
|
||||
@@ -17,5 +17,8 @@ const delegatedProps = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||
<DropdownMenuSeparator
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('-mx-1 my-1 h-px bg-muted', props.class)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuSub, useForwardPropsEmits, type DropdownMenuSubEmits, type DropdownMenuSubProps } from 'radix-vue';
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuSubEmits,
|
||||
type DropdownMenuSubProps,
|
||||
} from "radix-vue";
|
||||
|
||||
const props = defineProps<DropdownMenuSubProps>();
|
||||
const emits = defineEmits<DropdownMenuSubEmits>();
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuSubContent, useForwardPropsEmits, type DropdownMenuSubContentEmits, type DropdownMenuSubContentProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenuSubContent,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuSubContentEmits,
|
||||
type DropdownMenuSubContentProps,
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>();
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChevronRight } from 'lucide-vue-next';
|
||||
import { DropdownMenuSubTrigger, useForwardProps, type DropdownMenuSubTriggerProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChevronRight } from "lucide-vue-next";
|
||||
import {
|
||||
DropdownMenuSubTrigger,
|
||||
useForwardProps,
|
||||
type DropdownMenuSubTriggerProps,
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuTrigger, useForwardProps, type DropdownMenuTriggerProps } from 'radix-vue';
|
||||
import { DropdownMenuTrigger, useForwardProps, type DropdownMenuTriggerProps } from "radix-vue";
|
||||
|
||||
const props = defineProps<DropdownMenuTriggerProps>();
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export { default as DropdownMenu } from './DropdownMenu.vue';
|
||||
export { default as DropdownMenu } from "./DropdownMenu.vue";
|
||||
|
||||
export { DropdownMenuPortal } from 'radix-vue';
|
||||
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue';
|
||||
export { default as DropdownMenuContent } from './DropdownMenuContent.vue';
|
||||
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue';
|
||||
export { default as DropdownMenuItem } from './DropdownMenuItem.vue';
|
||||
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue';
|
||||
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue';
|
||||
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue';
|
||||
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue';
|
||||
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue';
|
||||
export { default as DropdownMenuSub } from './DropdownMenuSub.vue';
|
||||
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue';
|
||||
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue';
|
||||
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue';
|
||||
export { DropdownMenuPortal } from "radix-vue";
|
||||
export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue";
|
||||
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue";
|
||||
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue";
|
||||
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue";
|
||||
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue";
|
||||
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue";
|
||||
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue";
|
||||
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue";
|
||||
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue";
|
||||
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue";
|
||||
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue";
|
||||
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue";
|
||||
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue";
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import type { HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number;
|
||||
modelValue?: string | number;
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: HTMLAttributes["class"];
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: string | number): void;
|
||||
(e: "update:modelValue", payload: string | number): void;
|
||||
}>();
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as Input } from './Input.vue';
|
||||
export { default as Input } from "./Input.vue";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Label, type LabelProps } from 'radix-vue';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label, type LabelProps } from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>();
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
@@ -15,7 +15,12 @@ const delegatedProps = computed(() => {
|
||||
<template>
|
||||
<Label
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', props.class)"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default as Label } from './Label.vue';
|
||||
export { default as Label } from "./Label.vue";
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
NavigationMenuRoot,
|
||||
type NavigationMenuRootEmits,
|
||||
type NavigationMenuRootProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
import NavigationMenuViewport from './NavigationMenuViewport.vue'
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
import NavigationMenuViewport from "./NavigationMenuViewport.vue";
|
||||
|
||||
const props = defineProps<NavigationMenuRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
const props = defineProps<NavigationMenuRootProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const emits = defineEmits<NavigationMenuRootEmits>()
|
||||
const emits = defineEmits<NavigationMenuRootEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated
|
||||
})
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils'
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
NavigationMenuContent,
|
||||
type NavigationMenuContentEmits,
|
||||
type NavigationMenuContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed, type HTMLAttributes } from 'vue'
|
||||
} from "radix-vue";
|
||||
import { computed, type HTMLAttributes } from "vue";
|
||||
|
||||
const props = defineProps<NavigationMenuContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const props = defineProps<NavigationMenuContentProps & { class?: HTMLAttributes["class"] }>();
|
||||
|
||||
const emits = defineEmits<NavigationMenuContentEmits>()
|
||||
const emits = defineEmits<NavigationMenuContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated
|
||||
})
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavigationMenuContent
|
||||
v-bind="forwarded"
|
||||
:class="cn(
|
||||
:class="
|
||||
cn(
|
||||
'left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto',
|
||||
props.class,
|
||||
)"
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</NavigationMenuContent>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user