Compare commits
30 Commits
bde907f7cd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db7b8751ec | ||
|
|
d00fea4ecb | ||
|
|
5289b0b215 | ||
|
|
6411bc66b7 | ||
|
|
42049b6bc7 | ||
|
|
4262fa83b1 | ||
|
|
653581a16e | ||
|
|
c90ab7c990 | ||
|
|
9c15de9f6a | ||
|
|
08281db28f | ||
|
|
abeb796fd0 | ||
|
|
16fd1c5ef3 | ||
|
|
cec4b79951 | ||
|
|
315798f7b4 | ||
|
|
c8237c68b4 | ||
|
|
f746e54399 | ||
|
|
348519fa2d | ||
|
|
2737cf04ed | ||
|
|
18f090ec71 | ||
|
|
666a8b8d38 | ||
|
|
9afda37d44 | ||
|
|
79826bed91 | ||
|
|
516dc0edb3 | ||
|
|
9ac8bd88ea | ||
|
|
c7fded1d8f | ||
|
|
66d384b04d | ||
|
|
79b0184d3a | ||
|
|
74f52a9e7d | ||
|
|
f290f941ed | ||
|
|
7a6982570e |
221
.gitea/workflows/ci.yaml
Normal file
221
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,221 @@
|
||||
name: CI Deploy Laravel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
extensions: mbstring, ctype, fileinfo, openssl, pdo, tokenizer, xml
|
||||
coverage: none
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: npm
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --no-interaction --no-progress --prefer-dist
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
cp .env.example .env
|
||||
php artisan key:generate --force
|
||||
|
||||
- name: Install NPM dependencies
|
||||
run: npm ci || npm install --no-audit --no-fund
|
||||
|
||||
- name: Build frontend assets for tests
|
||||
run: npm run build
|
||||
|
||||
- name: Run tests
|
||||
run: php artisan test --compact
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
extensions: mbstring, ctype, fileinfo, openssl, pdo, tokenizer, xml
|
||||
coverage: none
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: npm
|
||||
|
||||
- name: Install production Composer dependencies
|
||||
run: composer install --no-dev --no-interaction --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Install NPM dependencies
|
||||
run: npm ci || npm install --no-audit --no-fund
|
||||
|
||||
- name: Build frontend assets
|
||||
run: npm run build
|
||||
|
||||
- name: Upload deployment artifact
|
||||
uses: actions/upload-artifact@v3.2.2-node20
|
||||
with:
|
||||
name: laravel-build
|
||||
include-hidden-files: true
|
||||
path: |
|
||||
app
|
||||
bootstrap
|
||||
config
|
||||
database
|
||||
public
|
||||
public/.htaccess
|
||||
resources
|
||||
routes
|
||||
vendor
|
||||
artisan
|
||||
composer.json
|
||||
package.json
|
||||
vite.config.js
|
||||
if-no-files-found: error
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v3-node20
|
||||
with:
|
||||
name: laravel-build
|
||||
path: release
|
||||
|
||||
- name: Install SSH deployment tools
|
||||
run: sudo apt-get update && sudo apt-get install -y openssh-client sshpass rsync
|
||||
|
||||
- name: Configure SSH
|
||||
env:
|
||||
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
if [ -n "$DEPLOY_KNOWN_HOSTS" ]; then
|
||||
echo "$DEPLOY_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
||||
chmod 600 ~/.ssh/known_hosts
|
||||
fi
|
||||
|
||||
- name: Deploy files with rsync over SSH
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }}
|
||||
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
|
||||
SSHPASS: ${{ secrets.DEPLOY_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$DEPLOY_PASSWORD" ]; then
|
||||
echo "DEPLOY_PASSWORD secret is empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
sshpass -e rsync -az --delete \
|
||||
-e "ssh -p ${DEPLOY_PORT:-22} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PreferredAuthentications=password -o PubkeyAuthentication=no -o NumberOfPasswordPrompts=1" \
|
||||
--exclude='.env' \
|
||||
--exclude='storage/' \
|
||||
--exclude='storage/logs/*' \
|
||||
--exclude='storage/framework/cache/*' \
|
||||
--exclude='storage/framework/sessions/*' \
|
||||
--exclude='storage/framework/views/*' \
|
||||
release/ "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}"
|
||||
|
||||
sshpass -e ssh \
|
||||
-p "${DEPLOY_PORT:-22}" \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=password \
|
||||
-o PubkeyAuthentication=no \
|
||||
-o NumberOfPasswordPrompts=1 \
|
||||
"${DEPLOY_USER}@${DEPLOY_HOST}" \
|
||||
"test -f \"${DEPLOY_PATH}/public/.htaccess\" || { echo 'public/.htaccess missing after rsync'; exit 1; }"
|
||||
|
||||
- name: Create .env on server from secret
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }}
|
||||
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
|
||||
DEPLOY_ENV_FILE: ${{ secrets.DEPLOY_ENV_FILE }}
|
||||
SSHPASS: ${{ secrets.DEPLOY_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$DEPLOY_ENV_FILE" ]; then
|
||||
echo "DEPLOY_ENV_FILE secret is empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_PASSWORD" ]; then
|
||||
echo "DEPLOY_PASSWORD secret is empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s' "$DEPLOY_ENV_FILE" | sshpass -e ssh \
|
||||
-p "${DEPLOY_PORT:-22}" \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=password \
|
||||
-o PubkeyAuthentication=no \
|
||||
-o NumberOfPasswordPrompts=1 \
|
||||
"${DEPLOY_USER}@${DEPLOY_HOST}" \
|
||||
"mkdir -p \"${DEPLOY_PATH}\" && cat > \"${DEPLOY_PATH}/.env\" && chmod 600 \"${DEPLOY_PATH}/.env\""
|
||||
|
||||
- name: Run post-deploy Laravel commands
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }}
|
||||
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
|
||||
SSHPASS: ${{ secrets.DEPLOY_PASSWORD }}
|
||||
run: |
|
||||
if [ -z "$DEPLOY_PASSWORD" ]; then
|
||||
echo "DEPLOY_PASSWORD secret is empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sshpass -e ssh \
|
||||
-p "${DEPLOY_PORT:-22}" \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o UserKnownHostsFile=/dev/null \
|
||||
-o PreferredAuthentications=password \
|
||||
-o PubkeyAuthentication=no \
|
||||
-o NumberOfPasswordPrompts=1 \
|
||||
"${DEPLOY_USER}@${DEPLOY_HOST}" \
|
||||
"cd ${DEPLOY_PATH} && mkdir -p storage/framework/cache/data storage/framework/sessions storage/framework/views storage/logs bootstrap/cache && chmod -R ug+rw storage bootstrap/cache && php artisan optimize:clear && php artisan config:cache && php artisan route:cache && php artisan view:cache"
|
||||
|
||||
# Required repository secrets:
|
||||
# - DEPLOY_HOST: Server hostname or IP.
|
||||
# - DEPLOY_PORT: SSH port (optional, defaults to 22).
|
||||
# - DEPLOY_USER: SSH user for deployment.
|
||||
# - DEPLOY_PASSWORD: SSH password for deployment user.
|
||||
# - DEPLOY_PATH: Absolute path of the Laravel app on the server.
|
||||
# - DEPLOY_KNOWN_HOSTS: Optional pinned known_hosts line(s) for stricter host verification.
|
||||
# - DEPLOY_ENV_FILE: Full .env content as a multiline secret (contains APP_KEY, DB_*, etc).
|
||||
34
app/Http/Controllers/RoleController.php
Normal file
34
app/Http/Controllers/RoleController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Role;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class RoleController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$roles = Role::orderBy('name')->paginate(10);
|
||||
|
||||
return view('roles.index', compact('roles'));
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
return view('roles.create');
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255', 'unique:roles,name'],
|
||||
]);
|
||||
|
||||
Role::create($validated);
|
||||
|
||||
return redirect()->route('roles.index')->with('success', __('Role created successfully.'));
|
||||
}
|
||||
}
|
||||
41
app/Http/Controllers/UserController.php
Normal file
41
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$users = User::orderBy('name')->paginate(10);
|
||||
|
||||
return view('users.index', compact('users'));
|
||||
}
|
||||
|
||||
public function edit(User $user): View
|
||||
{
|
||||
$roles = Role::orderBy('name')->get();
|
||||
|
||||
return view('users.edit', compact('user', 'roles'));
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email,'.$user->id],
|
||||
'roles' => ['nullable', 'array'],
|
||||
'roles.*' => ['integer', 'exists:roles,id'],
|
||||
]);
|
||||
|
||||
$user->update(['name' => $validated['name'], 'email' => $validated['email']]);
|
||||
$user->roles()->sync($validated['roles'] ?? []);
|
||||
|
||||
return redirect()->route('users.index')->with('success', __('User updated successfully.'));
|
||||
}
|
||||
}
|
||||
21
app/Models/Role.php
Normal file
21
app/Models/Role.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Database\Factories\RoleFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
/** @use HasFactory<RoleFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name'];
|
||||
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
@@ -29,4 +30,9 @@ class User extends Authenticatable
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Role::class);
|
||||
}
|
||||
}
|
||||
|
||||
24
database/factories/RoleFactory.php
Normal file
24
database/factories/RoleFactory.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Role;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Role>
|
||||
*/
|
||||
class RoleFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->word(),
|
||||
];
|
||||
}
|
||||
}
|
||||
28
database/migrations/2026_05_11_032743_create_roles_table.php
Normal file
28
database/migrations/2026_05_11_032743_create_roles_table.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('roles', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('roles');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('role_user', function (Blueprint $table) {
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
|
||||
$table->primary(['user_id', 'role_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('role_user');
|
||||
}
|
||||
};
|
||||
81
package-lock.json
generated
81
package-lock.json
generated
@@ -32,6 +32,25 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
@@ -667,6 +686,68 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
|
||||
|
||||
BIN
public/images/mbip_logo.png
Normal file
BIN
public/images/mbip_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -1,3 +1,4 @@
|
||||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
{{-- <svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||
</svg>
|
||||
</svg> --}}
|
||||
<img src="/images/mbip_logo.png" {{ $attributes }} />
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
@@ -15,6 +15,15 @@
|
||||
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
||||
{{ __('Dashboard') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link :href="route('users.index')" :active="request()->routeIs('users.*')">
|
||||
{{ __('Users') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link :href="route('roles.index')" :active="request()->routeIs('roles.*')">
|
||||
{{ __('Roles') }}
|
||||
</x-nav-link>
|
||||
<x-nav-link :href="route('settings.index')" :active="request()->routeIs('settings.*')">
|
||||
{{ __('Settings') }}
|
||||
</x-nav-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,6 +79,12 @@
|
||||
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
|
||||
{{ __('Dashboard') }}
|
||||
</x-responsive-nav-link>
|
||||
<x-responsive-nav-link :href="route('users.index')" :active="request()->routeIs('users.*')">
|
||||
{{ __('Users') }}
|
||||
</x-responsive-nav-link>
|
||||
<x-responsive-nav-link :href="route('roles.index')" :active="request()->routeIs('roles.*')">
|
||||
{{ __('Roles') }}
|
||||
</x-responsive-nav-link>
|
||||
</div>
|
||||
|
||||
<!-- Responsive Settings Options -->
|
||||
|
||||
32
resources/views/roles/create.blade.php
Normal file
32
resources/views/roles/create.blade.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('New Role') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<form method="POST" action="{{ route('roles.store') }}" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name')" required autofocus />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Create Role') }}</x-primary-button>
|
||||
<a href="{{ route('roles.index') }}" class="text-sm text-gray-600 dark:text-gray-400 hover:underline">
|
||||
{{ __('Cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
68
resources/views/roles/index.blade.php
Normal file
68
resources/views/roles/index.blade.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Roles') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-end mb-4">
|
||||
<a href="{{ route('roles.create') }}">
|
||||
<x-primary-button>{{ __('New Role') }}</x-primary-button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="mb-4 text-sm text-green-600 dark:text-green-400">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
{{ __('ID') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
{{ __('Name') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
{{ __('Created') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach ($roles as $role)
|
||||
<tr class="{{ $loop->even ? 'bg-gray-50 dark:bg-gray-700' : 'bg-white dark:bg-gray-800' }}">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $role->id }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $role->name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $role->created_at->format('M j, Y') }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if ($roles->isEmpty())
|
||||
<p class="text-center text-gray-500 dark:text-gray-400 py-6">{{ __('No roles found.') }}</p>
|
||||
@endif
|
||||
|
||||
@if ($roles->hasPages())
|
||||
<div class="mt-4">
|
||||
{{ $roles->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
17
resources/views/settings/index.blade.php
Normal file
17
resources/views/settings/index.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Settings') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
{{ __('Settings') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
58
resources/views/users/edit.blade.php
Normal file
58
resources/views/users/edit.blade.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Edit User') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
<form method="POST" action="{{ route('users.update', $user) }}" class="space-y-6">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required autocomplete="email" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label :value="__('Roles')" />
|
||||
<div class="mt-2 space-y-2">
|
||||
@foreach ($roles as $role)
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="roles[]"
|
||||
value="{{ $role->id }}"
|
||||
class="rounded border-gray-300 dark:border-gray-700 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800"
|
||||
{{ in_array($role->id, old('roles', $user->roles->pluck('id')->toArray())) ? 'checked' : '' }}
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ $role->name }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
<x-input-error class="mt-2" :messages="$errors->get('roles')" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save Changes') }}</x-primary-button>
|
||||
<a href="{{ route('users.index') }}" class="text-sm text-gray-600 dark:text-gray-400 hover:underline">
|
||||
{{ __('Cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
68
resources/views/users/index.blade.php
Normal file
68
resources/views/users/index.blade.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Users') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6">
|
||||
@if (session('success'))
|
||||
<div class="mb-4 text-sm text-green-600 dark:text-green-400">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
{{ __('Name') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
{{ __('Email') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
{{ __('Joined') }}
|
||||
</th>
|
||||
<th class="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
@foreach ($users as $user)
|
||||
<tr class="{{ $loop->even ? 'bg-gray-50 dark:bg-gray-700' : 'bg-white dark:bg-gray-800' }}">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $user->name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $user->email }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $user->created_at->format('M j, Y') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-right">
|
||||
<a href="{{ route('users.edit', $user) }}" class="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
{{ __('Edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if ($users->isEmpty())
|
||||
<p class="text-center text-gray-500 dark:text-gray-400 py-6">{{ __('No users found.') }}</p>
|
||||
@endif
|
||||
|
||||
@if ($users->hasPages())
|
||||
<div class="mt-4">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -48,7 +48,7 @@
|
||||
@endif
|
||||
</header>
|
||||
|
||||
<h1 class="text-2xl font-bold text-gray-800 dark:text-white">Git Courses</h1>
|
||||
<h1 class="text-2xl font-bold text-gray-800 dark:text-white">Test WebHook 5 Git Courses First Day</h1>
|
||||
|
||||
<div class="flex items-center justify-center w-full transition-opacity opacity-100 duration-750 lg:grow starting:opacity-0">
|
||||
<main class="flex max-w-[335px] w-full flex-col-reverse lg:max-w-4xl lg:flex-row">
|
||||
@@ -90,7 +90,7 @@
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Watch video tutorials at
|
||||
Watch video tutorials at Namaste
|
||||
<a href="https://laracasts.com" target="_blank" class="inline-flex items-center space-x-1 font-medium underline underline-offset-4 text-[#f53003] dark:text-[#FF4433] ml-1">
|
||||
<span>Laracasts</span>
|
||||
<svg
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\RoleController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', function () {
|
||||
@@ -12,6 +14,13 @@ Route::get('/dashboard', function () {
|
||||
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/users', [UserController::class, 'index'])->name('users.index');
|
||||
Route::get('/users/{user}/edit', [UserController::class, 'edit'])->name('users.edit');
|
||||
Route::patch('/users/{user}', [UserController::class, 'update'])->name('users.update');
|
||||
Route::get('/roles', [RoleController::class, 'index'])->name('roles.index');
|
||||
Route::get('/roles/create', [RoleController::class, 'create'])->name('roles.create');
|
||||
Route::post('/roles', [RoleController::class, 'store'])->name('roles.store');
|
||||
Route::view('/settings', 'settings.index')->name('settings.index');
|
||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
//
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutVite();
|
||||
$this->withoutMiddleware(PreventRequestForgery::class);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user