mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-02 14:36:17 +00:00
User management (#185)
This commit is contained in:
@ -1,4 +1,11 @@
|
||||
@props(["open" => false, "align" => "right", "width" => "48", "contentClasses" => "list-none divide-y divide-gray-100 rounded-md border border-gray-200 bg-white py-1 text-base dark:divide-gray-600 dark:border-gray-600 dark:bg-gray-700"])
|
||||
@props([
|
||||
"open" => false,
|
||||
"align" => "right",
|
||||
"width" => "48",
|
||||
"contentClasses" => "list-none divide-y divide-gray-100 rounded-md border border-gray-200 bg-white py-1 text-base dark:divide-gray-600 dark:border-gray-600 dark:bg-gray-700",
|
||||
"search" => false,
|
||||
"searchUrl" => "",
|
||||
])
|
||||
|
||||
@php
|
||||
switch ($align) {
|
||||
@ -42,6 +49,28 @@ class="{{ $width }} {{ $alignmentClasses }} absolute z-50 mt-2 rounded-md"
|
||||
@click="open = false"
|
||||
>
|
||||
<div class="{{ $contentClasses }} rounded-md">
|
||||
@if ($search)
|
||||
<div class="p-2">
|
||||
<input
|
||||
type="text"
|
||||
x-ref="search"
|
||||
x-model="search"
|
||||
x-on:keydown.window.prevent.enter="open = false"
|
||||
x-on:keydown.window.prevent.escape="open = false"
|
||||
x-on:keydown.window.prevent.arrow-up="
|
||||
open = true
|
||||
$refs.search.focus()
|
||||
"
|
||||
x-on:keydown.window.prevent.arrow-down="
|
||||
open = true
|
||||
$refs.search.focus()
|
||||
"
|
||||
class="w-full rounded-md border border-gray-200 p-2"
|
||||
placeholder="Search..."
|
||||
/>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ $content }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 9-3 3m0 0 3 3m-3-3h7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 323 B |
14
resources/views/components/heroicons/o-user-group.blade.php
Normal file
14
resources/views/components/heroicons/o-user-group.blade.php
Normal file
@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 767 B |
10
resources/views/components/heroicons/o-x-mark.blade.php
Normal file
10
resources/views/components/heroicons/o-x-mark.blade.php
Normal file
@ -0,0 +1,10 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
After Width: | Height: | Size: 249 B |
0
resources/views/components/select2.blade.php
Normal file
0
resources/views/components/select2.blade.php
Normal file
@ -4,11 +4,11 @@
|
||||
|
||||
@php
|
||||
$class = [
|
||||
"success" => "rounded-full bg-green-50 px-2 py-1 text-xs uppercase text-green-500 dark:bg-green-500 dark:bg-opacity-10",
|
||||
"danger" => "rounded-full bg-red-50 px-2 py-1 text-xs uppercase text-red-500 dark:bg-red-500 dark:bg-opacity-10",
|
||||
"warning" => "rounded-full bg-yellow-50 px-2 py-1 text-xs uppercase text-yellow-500 dark:bg-yellow-500 dark:bg-opacity-10",
|
||||
"disabled" => "rounded-full bg-gray-50 px-2 py-1 text-xs uppercase text-gray-500 dark:bg-gray-500 dark:bg-opacity-30 dark:text-gray-400",
|
||||
"info" => "rounded-full bg-primary-50 px-2 py-1 text-xs uppercase text-primary-500 dark:bg-primary-500 dark:bg-opacity-10",
|
||||
"success" => "rounded-md border border-green-300 bg-green-50 px-2 py-1 text-xs uppercase text-green-500 dark:border-green-600 dark:bg-green-500 dark:bg-opacity-10",
|
||||
"danger" => "rounded-md border border-red-300 bg-red-50 px-2 py-1 text-xs uppercase text-red-500 dark:border-red-600 dark:bg-red-500 dark:bg-opacity-10",
|
||||
"warning" => "rounded-md border border-yellow-300 bg-yellow-50 px-2 py-1 text-xs uppercase text-yellow-500 dark:border-yellow-600 dark:bg-yellow-500 dark:bg-opacity-10",
|
||||
"disabled" => "rounded-md border border-gray-300 bg-gray-50 px-2 py-1 text-xs uppercase text-gray-500 dark:border-gray-600 dark:bg-gray-500 dark:bg-opacity-30 dark:text-gray-400",
|
||||
"info" => "rounded-md border border-primary-300 bg-primary-50 px-2 py-1 text-xs uppercase text-primary-500 dark:border-primary-600 dark:bg-primary-500 dark:bg-opacity-10",
|
||||
];
|
||||
@endphp
|
||||
|
||||
|
@ -9,22 +9,22 @@
|
||||
<x-dropdown-link :href="route('profile')">
|
||||
{{ __("Profile") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('projects')">
|
||||
<x-dropdown-link :href="route('settings.projects')">
|
||||
{{ __("Projects") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('server-providers')">
|
||||
<x-dropdown-link :href="route('settings.server-providers')">
|
||||
{{ __("Server Providers") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('source-controls')">
|
||||
<x-dropdown-link :href="route('settings.source-controls')">
|
||||
{{ __("Source Controls") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('storage-providers')">
|
||||
<x-dropdown-link :href="route('settings.storage-providers')">
|
||||
{{ __("Storage Providers") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('notification-channels')">
|
||||
<x-dropdown-link :href="route('settings.notification-channels')">
|
||||
{{ __("Notification Channels") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link :href="route('ssh-keys')">
|
||||
<x-dropdown-link :href="route('settings.ssh-keys')">
|
||||
{{ __("SSH Keys") }}
|
||||
</x-dropdown-link>
|
||||
<!-- Authentication -->
|
||||
|
@ -41,7 +41,7 @@ class="p-6"
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
<x-secondary-button :href="route('storage-providers')" class="ml-2 flex-none">
|
||||
<x-secondary-button :href="route('settings.storage-providers')" class="ml-2 flex-none">
|
||||
Connect
|
||||
</x-secondary-button>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ class="fixed top-0 z-50 flex h-[64px] w-full items-center border-b border-gray-2
|
||||
>
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-start">
|
||||
<div class="flex flex-none items-center justify-start">
|
||||
<div
|
||||
class="flex items-center justify-start border-r border-gray-200 px-3 py-3 dark:border-gray-700 md:w-64"
|
||||
>
|
||||
@ -17,7 +17,7 @@ class="inline-flex items-center rounded-md p-2 text-sm text-gray-500 hover:bg-gr
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<x-heroicon name="o-bars-3-center-left" class="h-6 w-6" />
|
||||
</button>
|
||||
<a href="/" class="ms-2 flex md:me-24">
|
||||
<a href="/" class="ms-2 flex flex-none md:me-24">
|
||||
<div class="relative flex items-center justify-start text-3xl font-extrabold">
|
||||
<x-application-logo class="h-9 w-9 rounded-md" />
|
||||
<span class="ml-1 hidden md:block">Deploy</span>
|
||||
@ -70,9 +70,11 @@ class="flex rounded-full p-1 text-sm focus:ring-2 focus:ring-gray-300 dark:focus
|
||||
{{ __("Profile") }}
|
||||
</x-dropdown-link>
|
||||
|
||||
<x-dropdown-link :href="route('projects')">
|
||||
{{ __("Projects") }}
|
||||
</x-dropdown-link>
|
||||
@if (auth()->user()->isAdmin())
|
||||
<x-dropdown-link :href="route('settings.projects')">
|
||||
{{ __("Projects") }}
|
||||
</x-dropdown-link>
|
||||
@endif
|
||||
|
||||
<!-- Authentication -->
|
||||
<form method="POST" action="{{ route("logout") }}">
|
||||
|
@ -5,7 +5,14 @@
|
||||
<div
|
||||
class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-100 px-4 py-2 pr-7 text-sm text-gray-900 focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:focus:ring-gray-600"
|
||||
>
|
||||
{{ auth()->user()->currentProject?->name ?? __("Select Project") }}
|
||||
<x-heroicon name="o-inbox-stack" class="mr-2 h-4 w-4 lg:hidden" />
|
||||
<span class="hidden lg:block">
|
||||
@if (auth()->user()->currentProject &&auth()->user()->can("view", auth()->user()->currentProject))
|
||||
{{ auth()->user()->currentProject->name }}
|
||||
@else
|
||||
{{ __("Select Project") }}
|
||||
@endif
|
||||
</span>
|
||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
||||
</button>
|
||||
@ -14,8 +21,8 @@ class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-10
|
||||
</x-slot>
|
||||
<x-slot:content>
|
||||
@foreach (auth()->user()->projects as $project)
|
||||
<x-dropdown-link class="relative" :href="route('projects.switch', ['project' => $project])">
|
||||
<span class="block truncate">{{ ucfirst($project->name) }}</span>
|
||||
<x-dropdown-link class="relative" :href="route('settings.projects.switch', ['project' => $project])">
|
||||
<span class="block truncate">{{ $project->name }}</span>
|
||||
@if ($project->id == auth()->user()->current_project_id)
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||
@ -24,12 +31,14 @@ class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-10
|
||||
</x-dropdown-link>
|
||||
@endforeach
|
||||
|
||||
<x-dropdown-link href="{{ route('projects') }}">
|
||||
{{ __("Projects List") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link href="{{ route('projects', ['create' => 'open']) }}">
|
||||
{{ __("Create a Project") }}
|
||||
</x-dropdown-link>
|
||||
@if (auth()->user()->isAdmin())
|
||||
<x-dropdown-link href="{{ route('settings.projects') }}">
|
||||
{{ __("Projects List") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link href="{{ route('settings.projects', ['create' => 'open']) }}">
|
||||
{{ __("Create a Project") }}
|
||||
</x-dropdown-link>
|
||||
@endif
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
@ -1,35 +1,37 @@
|
||||
<div data-tooltip="Servers" class="cursor-pointer">
|
||||
<x-dropdown width="full">
|
||||
<x-slot:trigger>
|
||||
<div>
|
||||
<div
|
||||
class="block w-full rounded-md border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500"
|
||||
>
|
||||
{{ isset($server) ? $server->name : "Select Server" }}
|
||||
@if (auth()->user()->currentProject &&auth()->user()->can("view", auth()->user()->currentProject))
|
||||
<div data-tooltip="Servers" class="cursor-pointer">
|
||||
<x-dropdown width="full">
|
||||
<x-slot:trigger>
|
||||
<div>
|
||||
<div
|
||||
class="block w-full rounded-md border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500"
|
||||
>
|
||||
{{ isset($server) ? $server->name : "Select Server" }}
|
||||
</div>
|
||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-slot:content>
|
||||
@foreach (auth()->user()->currentProject->servers as $s)
|
||||
<x-dropdown-link class="relative" :href="route('servers.show', ['server' => $s])">
|
||||
<span class="block truncate">{{ ucfirst($s->name) }}</span>
|
||||
@if (isset($server) && $server->id == $s->id)
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||
</span>
|
||||
@endif
|
||||
</x-dropdown-link>
|
||||
@endforeach
|
||||
</x-slot>
|
||||
<x-slot:content>
|
||||
@foreach (auth()->user()->currentProject->servers as $s)
|
||||
<x-dropdown-link class="relative" :href="route('servers.show', ['server' => $s])">
|
||||
<span class="block truncate">{{ ucfirst($s->name) }}</span>
|
||||
@if (isset($server) && $server->id == $s->id)
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||
</span>
|
||||
@endif
|
||||
</x-dropdown-link>
|
||||
@endforeach
|
||||
|
||||
<x-dropdown-link href="{{ route('servers') }}">
|
||||
{{ __("Servers List") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link href="{{ route('servers.create') }}">
|
||||
{{ __("Create a Server") }}
|
||||
</x-dropdown-link>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
<x-dropdown-link href="{{ route('servers') }}">
|
||||
{{ __("Servers List") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link href="{{ route('servers.create') }}">
|
||||
{{ __("Create a Server") }}
|
||||
</x-dropdown-link>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
@endif
|
||||
|
@ -1,124 +0,0 @@
|
||||
<div x-data="siteCombobox()">
|
||||
<div class="relative">
|
||||
<div
|
||||
@click="open = !open"
|
||||
class="text-md flex h-10 w-full cursor-pointer items-center rounded-md bg-gray-200 px-4 py-3 pr-10 leading-5 focus:ring-1 focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-100"
|
||||
x-text="selected.domain ?? 'Select Site'"
|
||||
></div>
|
||||
<button type="button" @click="open = !open" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5 text-gray-400"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
x-show="open"
|
||||
@click.away="open = false"
|
||||
class="absolute mt-1 w-full overflow-auto rounded-md bg-white pb-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-700 sm:text-sm"
|
||||
>
|
||||
<div class="relative p-2">
|
||||
<input
|
||||
x-model="query"
|
||||
@input="filterSitesAndOpen"
|
||||
placeholder="Filter"
|
||||
class="dark:focus:ring-800 w-full rounded-md bg-gray-200 py-2 pl-3 pr-10 text-sm leading-5 focus:ring-1 focus:ring-gray-400 dark:bg-gray-800 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
<div class="relative max-h-[350px] overflow-y-auto">
|
||||
<template x-for="(site, index) in filteredSites" :key="index">
|
||||
<div
|
||||
@click="selectSite(site); open = false"
|
||||
:class="site.id === selected.id ? 'cursor-default bg-primary-600 text-white' : 'cursor-pointer'"
|
||||
class="relative select-none px-4 py-2 text-gray-700 hover:bg-primary-600 hover:text-white dark:text-white"
|
||||
>
|
||||
<span class="block truncate" x-text="site.domain"></span>
|
||||
<template x-if="site.id === selected.id">
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
class="h-5 w-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
x-show="filteredSites.length === 0"
|
||||
class="relative block cursor-default select-none truncate px-4 py-2 text-gray-700 dark:text-white"
|
||||
>
|
||||
No sites found!
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<hr class="border-gray-300 dark:border-gray-600" />
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="{{ route("servers.sites", ["server" => $server]) }}"
|
||||
class="@if(request()->routeIs('sites')) cursor-default bg-primary-600 text-white @else cursor-pointer @endif relative block select-none px-4 py-2 text-gray-700 hover:bg-primary-600 hover:text-white dark:text-white"
|
||||
>
|
||||
<span class="block truncate">Sites List</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="{{ route("servers.sites.create", ["server" => $server]) }}"
|
||||
class="@if(request()->routeIs('sites.create')) cursor-default bg-primary-600 text-white @else cursor-pointer @endif relative block select-none px-4 py-2 text-gray-700 hover:bg-primary-600 hover:text-white dark:text-white"
|
||||
>
|
||||
<span class="block truncate">Create a Site</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function siteCombobox() {
|
||||
const sites = @json(\App\Models\Site::query()->where('server_id', $server->id)->select('id', 'domain')->get());
|
||||
return {
|
||||
open: false,
|
||||
query: '',
|
||||
sites: sites,
|
||||
selected: @if(isset($site)) @json($site->only('id', 'domain')) @else {} @endif,
|
||||
filteredSites: sites,
|
||||
selectSite(site) {
|
||||
if (this.selected.id !== site.id) {
|
||||
this.selected = site;
|
||||
window.location.href = '{{ url('/servers') }}/' + '{{ $server->id }}/sites/' + site.id
|
||||
}
|
||||
},
|
||||
filterSitesAndOpen() {
|
||||
if (this.query === '') {
|
||||
this.filteredSites = this.sites;
|
||||
this.open = false;
|
||||
} else {
|
||||
this.filteredSites = this.sites.filter((site) =>
|
||||
site.domain
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '')
|
||||
.includes(this.query.toLowerCase().replace(/\s+/g, ''))
|
||||
);
|
||||
this.open = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
@ -143,7 +143,7 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
>
|
||||
<x-heroicon name="o-wrench-screwdriver" class="h-6 w-6" />
|
||||
<span class="ml-2">
|
||||
{{ __("Settings") }}
|
||||
{{ __("Server Settings") }}
|
||||
</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
@ -168,45 +168,72 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
<span class="ml-2">Profile</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link :href="route('projects')" :active="request()->routeIs('projects')">
|
||||
<x-heroicon name="o-inbox-stack" class="h-6 w-6" />
|
||||
<span class="ml-2">Projects</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link :href="route('server-providers')" :active="request()->routeIs('server-providers')">
|
||||
<x-heroicon name="o-server-stack" class="h-6 w-6" />
|
||||
<span class="ml-2">Server Providers</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link :href="route('source-controls')" :active="request()->routeIs('source-controls')">
|
||||
<x-heroicon name="o-code-bracket" class="h-6 w-6" />
|
||||
<span class="ml-2">Source Controls</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link :href="route('storage-providers')" :active="request()->routeIs('storage-providers')">
|
||||
<x-heroicon name="o-circle-stack" class="h-6 w-6" />
|
||||
<span class="ml-2">Storage Providers</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('notification-channels')"
|
||||
:active="request()->routeIs('notification-channels')"
|
||||
>
|
||||
<x-heroicon name="o-bell" class="h-6 w-6" />
|
||||
<span class="ml-2">Notification Channels</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link :href="route('ssh-keys')" :active="request()->routeIs('ssh-keys')">
|
||||
<x-heroicon name="o-key" class="h-6 w-6" />
|
||||
<span class="ml-2">SSH Keys</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
|
||||
@if (auth()->user()->isAdmin())
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.users.index')"
|
||||
:active="request()->routeIs('settings.users*')"
|
||||
>
|
||||
<x-heroicon name="o-user-group" class="h-6 w-6" />
|
||||
<span class="ml-2">Users</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.projects')"
|
||||
:active="request()->routeIs('settings.projects')"
|
||||
>
|
||||
<x-heroicon name="o-inbox-stack" class="h-6 w-6" />
|
||||
<span class="ml-2">Projects</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.server-providers')"
|
||||
:active="request()->routeIs('settings.server-providers')"
|
||||
>
|
||||
<x-heroicon name="o-server-stack" class="h-6 w-6" />
|
||||
<span class="ml-2">Server Providers</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.source-controls')"
|
||||
:active="request()->routeIs('settings.source-controls')"
|
||||
>
|
||||
<x-heroicon name="o-code-bracket" class="h-6 w-6" />
|
||||
<span class="ml-2">Source Controls</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.storage-providers')"
|
||||
:active="request()->routeIs('settings.storage-providers')"
|
||||
>
|
||||
<x-heroicon name="o-circle-stack" class="h-6 w-6" />
|
||||
<span class="ml-2">Storage Providers</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.notification-channels')"
|
||||
:active="request()->routeIs('settings.notification-channels')"
|
||||
>
|
||||
<x-heroicon name="o-bell" class="h-6 w-6" />
|
||||
<span class="ml-2">Notification Channels</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
<li>
|
||||
<x-sidebar-link
|
||||
:href="route('settings.ssh-keys')"
|
||||
:active="request()->routeIs('settings.ssh-keys')"
|
||||
>
|
||||
<x-heroicon name="o-key" class="h-6 w-6" />
|
||||
<span class="ml-2">SSH Keys</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
9
resources/views/profile/index.blade.php
Normal file
9
resources/views/profile/index.blade.php
Normal file
@ -0,0 +1,9 @@
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("Profile") }}</x-slot>
|
||||
|
||||
@include("profile.partials.update-profile-information")
|
||||
|
||||
@include("profile.partials.update-password")
|
||||
|
||||
@include("profile.partials.two-factor-authentication")
|
||||
</x-settings-layout>
|
@ -0,0 +1,78 @@
|
||||
<x-card>
|
||||
<x-slot name="title">
|
||||
{{ __("Two Factor Authentication") }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="description">
|
||||
{{ __("Here you can activate 2FA to secure your account") }}
|
||||
</x-slot>
|
||||
|
||||
<div id="two-factor">
|
||||
@if (! auth()->user()->two_factor_secret)
|
||||
{{-- Enable 2FA --}}
|
||||
<form
|
||||
hx-post="{{ route("two-factor.enable") }}"
|
||||
hx-target="#two-factor"
|
||||
hx-select="#two-factor"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<x-primary-button type="submit">
|
||||
{{ __("Enable Two-Factor") }}
|
||||
</x-primary-button>
|
||||
</form>
|
||||
@else
|
||||
{{-- Disable 2FA --}}
|
||||
<form
|
||||
hx-post="{{ route("two-factor.disable") }}"
|
||||
hx-target="#two-factor"
|
||||
hx-select="#two-factor"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
|
||||
<x-danger-button type="submit">
|
||||
{{ __("Disable Two-Factor") }}
|
||||
</x-danger-button>
|
||||
</form>
|
||||
|
||||
@if (session("status") == "two-factor-authentication-enabled")
|
||||
<div class="mt-5">
|
||||
{{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.') }}
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
{!! auth()->user()->twoFactorQrCodeSvg() !!}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Show 2FA Recovery Codes --}}
|
||||
<div class="mt-5">
|
||||
{{ __("Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-5 rounded-md border border-gray-100 p-2 dark:border-gray-700">
|
||||
@foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code)
|
||||
<div class="mt-2">{{ $code }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Regenerate 2FA Recovery Codes --}}
|
||||
<form
|
||||
class="mt-5"
|
||||
hx-post="{{ route("two-factor.recovery-codes") }}"
|
||||
hx-target="#two-factor"
|
||||
hx-select="#two-factor"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<x-primary-button type="submit">
|
||||
{{ __("Regenerate Recovery Codes") }}
|
||||
</x-primary-button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</x-card>
|
@ -70,7 +70,7 @@ class="mt-6 space-y-6"
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
<x-secondary-button
|
||||
:href="route('server-providers', ['provider' => $provider])"
|
||||
:href="route('settings.server-providers', ['provider' => $provider])"
|
||||
class="ml-2 flex-none"
|
||||
>
|
||||
{{ __("Connect") }}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<x-profile-layout>
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("Notification Channels") }}</x-slot>
|
||||
|
||||
@include("settings.notification-channels.partials.channels-list")
|
||||
</x-profile-layout>
|
||||
</x-settings-layout>
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<form
|
||||
id="add-channel-form"
|
||||
hx-post="{{ route("notification-channels.add") }}"
|
||||
hx-post="{{ route("settings.notification-channels.add") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#add-channel-form"
|
||||
hx-ext="disable-element"
|
||||
|
@ -24,7 +24,7 @@
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('notification-channels.delete', $channel->id) }}'; $dispatch('open-modal', 'delete-channel')"
|
||||
x-on:click="deleteAction = '{{ route('settings.notification-channels.delete', $channel->id) }}'; $dispatch('open-modal', 'delete-channel')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
@ -1,9 +0,0 @@
|
||||
<x-profile-layout>
|
||||
<x-slot name="pageTitle">{{ __("Profile") }}</x-slot>
|
||||
|
||||
@include("settings.profile.partials.update-profile-information")
|
||||
|
||||
@include("settings.profile.partials.update-password")
|
||||
|
||||
@include("settings.profile.partials.two-factor-authentication")
|
||||
</x-profile-layout>
|
@ -1,60 +0,0 @@
|
||||
<x-card>
|
||||
<x-slot name="title">
|
||||
{{ __("Two Factor Authentication") }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="description">
|
||||
{{ __("Here you can activate 2FA to secure your account") }}
|
||||
</x-slot>
|
||||
|
||||
@if (! auth()->user()->two_factor_secret)
|
||||
{{-- Enable 2FA --}}
|
||||
<form method="POST" action="{{ route("two-factor.enable") }}">
|
||||
@csrf
|
||||
|
||||
<x-primary-button type="submit">
|
||||
{{ __("Enable Two-Factor") }}
|
||||
</x-primary-button>
|
||||
</form>
|
||||
@else
|
||||
{{-- Disable 2FA --}}
|
||||
<form method="POST" action="{{ route("two-factor.disable") }}">
|
||||
@csrf
|
||||
@method("DELETE")
|
||||
|
||||
<x-danger-button type="submit">
|
||||
{{ __("Disable Two-Factor") }}
|
||||
</x-danger-button>
|
||||
</form>
|
||||
|
||||
@if (session("status") == "two-factor-authentication-enabled")
|
||||
<div class="mt-5">
|
||||
{{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.') }}
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
{!! auth()->user()->twoFactorQrCodeSvg() !!}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Show 2FA Recovery Codes --}}
|
||||
<div class="mt-5">
|
||||
{{ __("Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-5 rounded-md border border-gray-100 p-2 dark:border-gray-700">
|
||||
@foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code)
|
||||
<div class="mt-2">{{ $code }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Regenerate 2FA Recovery Codes --}}
|
||||
<form class="mt-5" method="POST" action="{{ route("two-factor.recovery-codes") }}">
|
||||
@csrf
|
||||
|
||||
<x-primary-button type="submit">
|
||||
{{ __("Regenerate Recovery Codes") }}
|
||||
</x-primary-button>
|
||||
</form>
|
||||
@endif
|
||||
</x-card>
|
@ -1,5 +1,5 @@
|
||||
<x-profile-layout>
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("Projects") }}</x-slot>
|
||||
|
||||
@include("settings.projects.partials.projects-list")
|
||||
</x-profile-layout>
|
||||
</x-settings-layout>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<x-modal name="create-project" :show="request()->has('create')">
|
||||
<form
|
||||
id="create-project-form"
|
||||
hx-post="{{ route("projects.create") }}"
|
||||
hx-post="{{ route("settings.projects.create") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#create-project-form"
|
||||
hx-ext="disable-element"
|
||||
|
@ -6,7 +6,7 @@
|
||||
<x-modal name="edit-project-{{ $project->id }}">
|
||||
<form
|
||||
id="edit-project-form-{{ $project->id }}"
|
||||
hx-post="{{ route("projects.update", $project) }}"
|
||||
hx-post="{{ route("settings.projects.update", $project) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#edit-project-form-{{ $project->id }}"
|
||||
hx-ext="disable-element"
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="flex items-center">
|
||||
@include("settings.projects.partials.edit-project", ["project" => $project])
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('projects.delete', $project) }}'; $dispatch('open-modal', 'delete-project')"
|
||||
x-on:click="deleteAction = '{{ route('settings.projects.delete', $project) }}'; $dispatch('open-modal', 'delete-project')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<x-profile-layout>
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("Storage Providers") }}</x-slot>
|
||||
|
||||
@include("settings.server-providers.partials.providers-list")
|
||||
</x-profile-layout>
|
||||
</x-settings-layout>
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<form
|
||||
id="connect-provider-form"
|
||||
hx-post="{{ route("server-providers.connect") }}"
|
||||
hx-post="{{ route("settings.server-providers.connect") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#connect-provider-form"
|
||||
hx-ext="disable-element"
|
||||
|
@ -26,7 +26,7 @@ class="h-10 w-10"
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('server-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||
x-on:click="deleteAction = '{{ route('settings.server-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<x-profile-layout>
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("Source Controls") }}</x-slot>
|
||||
|
||||
@include("settings.source-controls.partials.source-controls-list")
|
||||
</x-profile-layout>
|
||||
</x-settings-layout>
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<form
|
||||
id="connect-source-control-form"
|
||||
hx-post="{{ route("source-controls.connect") }}"
|
||||
hx-post="{{ route("settings.source-controls.connect") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#connect-source-control-form"
|
||||
hx-ext="disable-element"
|
||||
|
@ -22,7 +22,7 @@
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('source-controls.delete', $sourceControl->id) }}'; $dispatch('open-modal', 'delete-source-control')"
|
||||
x-on:click="deleteAction = '{{ route('settings.source-controls.delete', $sourceControl->id) }}'; $dispatch('open-modal', 'delete-source-control')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<x-profile-layout>
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("SSH Keys") }}</x-slot>
|
||||
|
||||
@include("settings.ssh-keys.partials.keys-list")
|
||||
</x-profile-layout>
|
||||
</x-settings-layout>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<x-modal name="add-key">
|
||||
<form
|
||||
id="add-ssh-key-form"
|
||||
hx-post="{{ route("ssh-keys.add") }}"
|
||||
hx-post="{{ route("settings.ssh-keys.add") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#add-ssh-key-form"
|
||||
hx-ext="disable-element"
|
||||
|
@ -21,7 +21,7 @@
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('ssh-keys.delete', $key->id) }}'; $dispatch('open-modal', 'delete-ssh-key')"
|
||||
x-on:click="deleteAction = '{{ route('settings.ssh-keys.delete', $key->id) }}'; $dispatch('open-modal', 'delete-ssh-key')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<x-profile-layout>
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">{{ __("Storage Providers") }}</x-slot>
|
||||
|
||||
@include("settings.storage-providers.partials.providers-list")
|
||||
</x-profile-layout>
|
||||
</x-settings-layout>
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<form
|
||||
id="connect-storage-provider-form"
|
||||
hx-post="{{ route("storage-providers.connect") }}"
|
||||
hx-post="{{ route("settings.storage-providers.connect") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#connect-storage-provider-form"
|
||||
hx-ext="disable-element"
|
||||
|
@ -43,7 +43,7 @@ class="h-10 w-10"
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('storage-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||
x-on:click="deleteAction = '{{ route('settings.storage-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
|
61
resources/views/settings/users/index.blade.php
Normal file
61
resources/views/settings/users/index.blade.php
Normal file
@ -0,0 +1,61 @@
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">Users</x-slot>
|
||||
|
||||
<x-container>
|
||||
<x-card-header>
|
||||
<x-slot name="title">Users</x-slot>
|
||||
<x-slot name="description">Here you can manage users</x-slot>
|
||||
<x-slot name="aside">
|
||||
@include("settings.users.partials.create-user")
|
||||
</x-slot>
|
||||
</x-card-header>
|
||||
<div class="space-y-3" x-data="{ deleteAction: '' }">
|
||||
<x-table>
|
||||
<x-thead>
|
||||
<x-tr>
|
||||
<x-th>ID</x-th>
|
||||
<x-th>Name</x-th>
|
||||
<x-th>Email</x-th>
|
||||
<x-th>Role</x-th>
|
||||
<x-th></x-th>
|
||||
</x-tr>
|
||||
</x-thead>
|
||||
<x-tbody>
|
||||
@foreach ($users as $user)
|
||||
<x-tr>
|
||||
<x-td>{{ $user->id }}</x-td>
|
||||
<x-td>{{ $user->name }}</x-td>
|
||||
<x-td>{{ $user->email }}</x-td>
|
||||
<x-td>
|
||||
<div class="inline-flex">
|
||||
@if ($user->role === \App\Enums\UserRole::ADMIN)
|
||||
<x-status status="success">ADMIN</x-status>
|
||||
@else
|
||||
<x-status status="info">USER</x-status>
|
||||
@endif
|
||||
</div>
|
||||
</x-td>
|
||||
<x-td class="text-right">
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('settings.users.delete', ['user' => $user]) }}'; $dispatch('open-modal', 'delete-user')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
<x-icon-button :href="route('settings.users.show', ['user' => $user])">
|
||||
<x-heroicon name="o-cog-6-tooth" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
</x-td>
|
||||
</x-tr>
|
||||
@endforeach
|
||||
</x-tbody>
|
||||
</x-table>
|
||||
<x-confirmation-modal
|
||||
name="delete-user"
|
||||
:title="__('Confirm')"
|
||||
:description="__('Are you sure that you want to delete this user?')"
|
||||
method="delete"
|
||||
x-bind:action="deleteAction"
|
||||
/>
|
||||
</div>
|
||||
</x-container>
|
||||
</x-settings-layout>
|
@ -0,0 +1,69 @@
|
||||
<div>
|
||||
<x-primary-button x-data="" x-on:click.prevent="$dispatch('open-modal', 'create-user')">New User</x-primary-button>
|
||||
|
||||
<x-modal name="create-user">
|
||||
<form
|
||||
id="create-user-form"
|
||||
hx-post="{{ route("settings.users.store") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#create-user-form"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-create-user"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Create New User</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="name" value="Name" />
|
||||
<x-text-input value="{{ old('name') }}" id="name" name="name" type="text" class="mt-1 w-full" />
|
||||
@error("name")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="email" value="Email" />
|
||||
<x-text-input value="{{ old('email') }}" id="email" name="email" type="text" class="mt-1 w-full" />
|
||||
@error("email")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="password" value="Password" />
|
||||
<x-text-input id="password" name="password" type="password" class="mt-1 w-full" />
|
||||
@error("password")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="role" value="Role" />
|
||||
<x-select-input id="role" name="role" class="mt-1 w-full">
|
||||
<option
|
||||
value="{{ \App\Enums\UserRole::USER }}"
|
||||
@if(old('role') === \App\Enums\UserRole::USER) selected @endif
|
||||
>
|
||||
User
|
||||
</option>
|
||||
<option
|
||||
value="{{ \App\Enums\UserRole::ADMIN }}"
|
||||
@if(old('role') === \App\Enums\UserRole::ADMIN) selected @endif
|
||||
>
|
||||
Admin
|
||||
</option>
|
||||
</x-select-input>
|
||||
@error("role")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">Cancel</x-secondary-button>
|
||||
|
||||
<x-primary-button id="btn-create-project" class="ml-3">Create</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
</div>
|
@ -0,0 +1,120 @@
|
||||
<x-card>
|
||||
<x-slot name="title">Projects</x-slot>
|
||||
|
||||
<x-slot name="description">Manage the projects that the user is in</x-slot>
|
||||
|
||||
<x-slot name="aside">
|
||||
<x-secondary-button :href="route('settings.users.index')">Back to Users</x-secondary-button>
|
||||
</x-slot>
|
||||
|
||||
<form
|
||||
id="update-projects"
|
||||
hx-post="{{ route("settings.users.update-projects", ["user" => $user]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#update-projects"
|
||||
hx-trigger="submit"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-save-projects"
|
||||
class="mt-6"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<script>
|
||||
let projects = @json($user->projects);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="space-y-6"
|
||||
x-data="{
|
||||
q: '',
|
||||
projects: projects,
|
||||
search() {
|
||||
htmx.ajax('GET', '{{ request()->getUri() }}?q=' + this.q, {
|
||||
target: '#projects-list',
|
||||
swap: 'outerHTML',
|
||||
select: '#projects-list',
|
||||
}).then(() => {
|
||||
document.getElementById('q').focus()
|
||||
})
|
||||
},
|
||||
addProject(project) {
|
||||
if (this.projects.find((p) => p.id === project.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.projects.push(project)
|
||||
this.q = ''
|
||||
},
|
||||
removeProject(id) {
|
||||
this.projects = this.projects.filter((project) => project.id !== id)
|
||||
},
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<x-input-label value="Projects" />
|
||||
|
||||
<div class="mt-1">
|
||||
<template x-for="project in projects">
|
||||
<div class="mr-1 inline-flex">
|
||||
<x-status status="info" class="flex items-center">
|
||||
<span x-text="project.name"></span>
|
||||
<x-heroicon
|
||||
name="o-x-mark"
|
||||
class="ml-1 h-4 w-4 cursor-pointer"
|
||||
x-on:click="removeProject(project.id)"
|
||||
/>
|
||||
<input type="hidden" name="projects[]" x-bind:value="project.id" />
|
||||
</x-status>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label value="Add new Project" />
|
||||
|
||||
@php
|
||||
$projects = \App\Models\Project::query()
|
||||
->where(function ($query) {
|
||||
if (request()->has("q")) {
|
||||
$query->where("name", "like", "%" . request("q") . "%");
|
||||
}
|
||||
})
|
||||
->take(5)
|
||||
->get();
|
||||
@endphp
|
||||
|
||||
<x-dropdown width="full">
|
||||
<x-slot name="trigger">
|
||||
<x-text-input
|
||||
id="q"
|
||||
name="q"
|
||||
x-model="q"
|
||||
type="text"
|
||||
class="mt-1 w-full"
|
||||
placeholder="Search for projects..."
|
||||
autocomplete="off"
|
||||
x-on:input.debounce.500ms="search"
|
||||
/>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
<div id="projects-list">
|
||||
@foreach ($projects as $project)
|
||||
<x-dropdown-link
|
||||
class="cursor-pointer"
|
||||
x-on:click="addProject({ id: {{ $project->id }}, name: '{{ $project->name }}' })"
|
||||
>
|
||||
{{ $project->name }}
|
||||
</x-dropdown-link>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<x-slot name="actions">
|
||||
<x-primary-button id="btn-save-projects" form="update-projects">Save</x-primary-button>
|
||||
</x-slot>
|
||||
</x-card>
|
@ -0,0 +1,99 @@
|
||||
<x-card>
|
||||
<x-slot name="title">User Info</x-slot>
|
||||
|
||||
<x-slot name="description">You can update user's info here</x-slot>
|
||||
|
||||
<form
|
||||
id="update-user-info"
|
||||
hx-post="{{ route("settings.users.update", ["user" => $user]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#update-user-info"
|
||||
hx-trigger="submit"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-save-info"
|
||||
class="mt-6 space-y-6"
|
||||
>
|
||||
@csrf
|
||||
<div>
|
||||
<x-input-label for="name" value="Name" />
|
||||
<x-text-input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
value="{{ old('name', $user->name) }}"
|
||||
class="mt-1 block w-full"
|
||||
required
|
||||
autocomplete="name"
|
||||
/>
|
||||
@error("name")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="email" value="Email" />
|
||||
<x-text-input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
value="{{ old('email', $user->email) }}"
|
||||
class="mt-1 block w-full"
|
||||
required
|
||||
autocomplete="email"
|
||||
/>
|
||||
@error("email")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="timezone" value="Timezone" />
|
||||
<x-select-input id="timezone" name="timezone" class="mt-1 block w-full" required>
|
||||
@foreach (timezone_identifiers_list() as $timezone)
|
||||
<option
|
||||
value="{{ $timezone }}"
|
||||
@if(old('timezone', $user->timezone) == $timezone) selected @endif
|
||||
>
|
||||
{{ $timezone }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("timezone")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="role" value="Role" />
|
||||
<x-select-input id="role" name="role" class="mt-1 w-full">
|
||||
<option
|
||||
value="{{ \App\Enums\UserRole::USER }}"
|
||||
@if(old('role', $user->role) === \App\Enums\UserRole::USER) selected @endif
|
||||
>
|
||||
User
|
||||
</option>
|
||||
<option
|
||||
value="{{ \App\Enums\UserRole::ADMIN }}"
|
||||
@if(old('role', $user->role) === \App\Enums\UserRole::ADMIN) selected @endif
|
||||
>
|
||||
Admin
|
||||
</option>
|
||||
</x-select-input>
|
||||
@error("role")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="password" value="New Password" />
|
||||
<x-text-input id="password" name="password" type="password" class="mt-1 w-full" />
|
||||
@error("password")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<x-slot name="actions">
|
||||
<x-primary-button id="btn-save-info" form="update-user-info">Save</x-primary-button>
|
||||
</x-slot>
|
||||
</x-card>
|
@ -0,0 +1,69 @@
|
||||
<x-card>
|
||||
<x-slot name="title">
|
||||
{{ __("Update Password") }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="description">
|
||||
{{ __("Ensure your account is using a long, random password to stay secure.") }}
|
||||
</x-slot>
|
||||
|
||||
<form
|
||||
id="update-password"
|
||||
class="mt-6 space-y-6"
|
||||
hx-post="{{ route("profile.password") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#update-password"
|
||||
hx-trigger="submit"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-save-password"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<x-input-label for="current_password" :value="__('Current Password')" />
|
||||
<x-text-input
|
||||
id="current_password"
|
||||
name="current_password"
|
||||
type="password"
|
||||
class="mt-1 block w-full"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
@error("current_password")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="password" :value="__('New Password')" />
|
||||
<x-text-input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
class="mt-1 block w-full"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
@error("password")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
<x-text-input
|
||||
id="password_confirmation"
|
||||
name="password_confirmation"
|
||||
type="password"
|
||||
class="mt-1 block w-full"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
@error("password_confirmation")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
</form>
|
||||
<x-slot name="actions">
|
||||
<x-primary-button id="btn-save-password" form="update-password">
|
||||
{{ __("Save") }}
|
||||
</x-primary-button>
|
||||
</x-slot>
|
||||
</x-card>
|
9
resources/views/settings/users/show.blade.php
Normal file
9
resources/views/settings/users/show.blade.php
Normal file
@ -0,0 +1,9 @@
|
||||
<x-settings-layout>
|
||||
<x-slot name="pageTitle">Users</x-slot>
|
||||
|
||||
<x-container>
|
||||
@include("settings.users.partials.update-projects")
|
||||
|
||||
@include("settings.users.partials.update-user-info")
|
||||
</x-container>
|
||||
</x-settings-layout>
|
@ -13,7 +13,10 @@
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
<x-secondary-button :href="route('source-controls', ['redirect' => request()->url()])" class="ml-2 flex-none">
|
||||
<x-secondary-button
|
||||
:href="route('settings.source-controls', ['redirect' => request()->url()])"
|
||||
class="ml-2 flex-none"
|
||||
>
|
||||
{{ __("Connect") }}
|
||||
</x-secondary-button>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user