mirror of
https://github.com/linuxserver/Heimdall.git
synced 2026-08-07 07:16:13 +00:00
Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 238452c104 | |||
| 91f7a2ec8d | |||
| 62823aef63 | |||
| 2ceec61e17 | |||
| 8fc5f3ccbf | |||
| 6a45d44f69 | |||
| 327af60d1c | |||
| a38ecd4864 | |||
| 1f7432fa26 | |||
| 55bda9daf0 | |||
| 667dd4c129 | |||
| d16b7f60f3 | |||
| 57fa9f26fb | |||
| bdf9160fdc | |||
| 93a321ff5b | |||
| 3f483c9ca7 | |||
| 1c7ae3e335 | |||
| 2555ab1b3c | |||
| 49c29c8681 | |||
| 6f932918aa | |||
| 13642d59d9 | |||
| 25d1dc3c6a | |||
| f1eec81591 | |||
| 46e09d172a | |||
| 9cfa2548fa | |||
| 8a622545ff | |||
| 39191885e5 | |||
| cb59689ead | |||
| 6d0242dead | |||
| caa4f39edd | |||
| a1f0d8f75d | |||
| 076348478e | |||
| ad9baffa62 | |||
| 9a9877a0dc | |||
| c0c202c5ff | |||
| f69cbba6cd | |||
| ec229ea0af | |||
| 5dcf462542 | |||
| d1c52b886b | |||
| e2215fe42b | |||
| 5247349d33 | |||
| f547ae42bb | |||
| 881533baa5 | |||
| fb9af1b216 | |||
| 5907a1f231 | |||
| df0eba046b | |||
| 6a776e30f8 | |||
| cbf099be2c | |||
| 56c53ab9c5 | |||
| 98b6d96cd1 | |||
| 5be7a65677 | |||
| 44be3cb319 | |||
| 243ad00810 | |||
| 7861ae1512 | |||
| 66dfe95c9f | |||
| 130661bd34 | |||
| 900fc83e79 | |||
| 4f30332854 | |||
| 852c231724 | |||
| 045bdf0deb | |||
| 755c3e59e1 | |||
| 32bf1d034f | |||
| 6d12c547e7 | |||
| ae4ce92dab | |||
| 966279b252 | |||
| 54cf2b88ca | |||
| 31f1ba8192 | |||
| eadd9d1dd8 | |||
| c9ea2cdeb3 | |||
| 517f51ba90 | |||
| 825f67a4a4 | |||
| 05a552ffcf | |||
| ad4584e548 | |||
| 7d93099f2c | |||
| 31ca05f74f | |||
| cd95fc3b92 | |||
| 63e777b338 | |||
| fd926e983d | |||
| dce37c1412 | |||
| 6b9f61b0e6 | |||
| d1a96dd752 | |||
| 1ccc0da2a7 | |||
| 41aa255b88 | |||
| a8e4ab448b | |||
| 31db31d0f7 | |||
| e42f78bd49 | |||
| 08b8ab6d4f | |||
| 57e0a335b7 | |||
| 0d90170385 | |||
| abe08a7b04 | |||
| 6075dcca2d | |||
| 4fa41d8114 | |||
| 1e6b1f6de5 | |||
| 53fd6242db | |||
| 69bc8cb34e | |||
| 0388ee939a | |||
| 2df58472a1 | |||
| abbc78ee8d | |||
| d1801d1088 | |||
| 22f66d35e5 | |||
| f197aeb013 | |||
| 8fb6438254 | |||
| d972cbcd0a | |||
| 638d2fe4a4 | |||
| e251f4f1bc | |||
| ecc54b6fbe | |||
| f0ce9d633a | |||
| 968a5823a1 | |||
| 53f28b5056 | |||
| 7bb0a7ceb4 | |||
| f5ddd93141 | |||
| 8554861d0a | |||
| 9091d1d707 | |||
| 42d29f0fdb | |||
| 474059eee8 | |||
| 1fb6f75555 | |||
| 48e16ebfc9 | |||
| 7153a41e03 | |||
| b9e75b9284 | |||
| b3cdc5779c | |||
| c1c3888673 | |||
| 11453e1018 | |||
| 749442ec6e | |||
| 5950ca9076 | |||
| 55d3766b39 | |||
| c71479f266 | |||
| 2e5d7453cf | |||
| 3cbbf807ab | |||
| 65a6ad53bf | |||
| dff3f90d00 | |||
| 041ec4236b | |||
| 808c41acd6 | |||
| c08b0bfe39 | |||
| f7de56b7a7 | |||
| 921631bdcd | |||
| d146fed320 | |||
| c56ffe1d1e | |||
| e9d561466b | |||
| b90b38eae9 | |||
| be59ac794e | |||
| f820c0b4f6 | |||
| 2b43760dc8 | |||
| 8f360f97ef | |||
| 014f054862 | |||
| 5ccb87cd7f | |||
| 8ba8f0c867 | |||
| c2a3368c7b | |||
| 395c775d4e | |||
| 1063d2cd09 | |||
| 18609fb3c2 | |||
| b880333856 | |||
| 026bcf9e43 | |||
| a4973869d4 | |||
| 07ea22dd10 | |||
| 6188b1d669 | |||
| 184e19abbc | |||
| f405cf2ddf | |||
| c16a4847f7 | |||
| 9d0209c9df |
+31
-2
@@ -4,7 +4,25 @@ APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
|
||||
# Security: Host Header Injection / Open Redirect hardening (CVE-2025-50578).
|
||||
# TRUSTED_PROXIES: comma-separated CIDRs/IPs of reverse proxies allowed to set
|
||||
# X-Forwarded-* headers. Defaults to the private ranges below when unset. Use
|
||||
# "*" to trust all proxies (only behind a trusted network boundary).
|
||||
#TRUSTED_PROXIES=192.168.0.0/16,172.16.0.0/12,10.0.0.0/8,127.0.0.1
|
||||
# TRUSTED_HOSTS: comma-separated hostnames Heimdall is allowed to serve. Unset
|
||||
# means no restriction (default, backward compatible). Set this to your own
|
||||
# domain to fully prevent host-header injection / open redirects.
|
||||
#TRUSTED_HOSTS=heimdall.example.com
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
APP_MAINTENANCE_STORE=database
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
LOG_STACK=single
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
DB_DATABASE=app.sqlite
|
||||
@@ -16,11 +34,14 @@ DB_DATABASE=app.sqlite
|
||||
#DB_USERNAME=<user>
|
||||
#DB_PASSWORD=<password>
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
BROADCAST_CONNECTION=log
|
||||
CACHE_STORE=file
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
@@ -48,3 +69,11 @@ PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
AUTH_ROLES_ENABLE=false
|
||||
AUTH_ROLES_HEADER="remote-groups"
|
||||
AUTH_ROLES_HTTP_HEADER="HTTP_REMOTE_GROUPS"
|
||||
AUTH_ROLES_ADMIN="admin"
|
||||
AUTH_ROLES_DELIMITER=","
|
||||
|
||||
ALLOW_INTERNAL_REQUESTS=false
|
||||
@@ -12,11 +12,11 @@ jobs:
|
||||
- name: Setup PHP, with composer and extensions
|
||||
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
|
||||
with:
|
||||
php-version: '8.3'
|
||||
php-version: '8.4'
|
||||
extensions: mbstring, dom, fileinfo, mysql, libxml, xml, xmlwriter, dom, tokenizer, filter, json, phar, pcre, openssl, pdo, intl, curl
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor
|
||||
key: composer-${{ hashFiles('composer.lock') }}
|
||||
@@ -31,17 +31,17 @@ jobs:
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
|
||||
- name: Cache yarn dependencies
|
||||
uses: actions/cache@v1
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
path: node_modules
|
||||
key: yarn-${{ hashFiles('yarn.lock') }}
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Run yarn
|
||||
run: yarn && yarn dev
|
||||
- name: Install node modules and build assets
|
||||
run: npm ci && npm run dev
|
||||
|
||||
- name: Run ESLint
|
||||
run: yarn lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: php artisan test
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Release
|
||||
|
||||
# Stage 1 of the release flow: opens a version-bump PR against 2.x.
|
||||
# When that PR is merged, tag-release.yml (stage 2) creates the tag and
|
||||
# the GitHub release automatically.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bump:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
version:
|
||||
description: 'Explicit version (e.g. 2.9.0) — overrides bump type'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release-pr:
|
||||
name: Open version bump PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 2.x
|
||||
fetch-depth: 0
|
||||
# Optional: set a RELEASE_TOKEN repo secret (fine-grained PAT with
|
||||
# contents + pull-requests write) so the bump PR triggers CI checks.
|
||||
# PRs created with the default github.token do not trigger workflows.
|
||||
token: ${{ secrets.RELEASE_TOKEN || github.token }}
|
||||
|
||||
- name: Determine new version
|
||||
id: version
|
||||
env:
|
||||
EXPLICIT_VERSION: ${{ inputs.version }}
|
||||
BUMP: ${{ inputs.bump }}
|
||||
run: |
|
||||
current=$(sed -nE "s/^[[:space:]]*'version' => '([0-9]+\.[0-9]+\.[0-9]+)',/\1/p" config/app.php)
|
||||
if [ -z "$current" ]; then
|
||||
echo "::error::Could not read current version from config/app.php"
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$EXPLICIT_VERSION" ]; then
|
||||
new="$EXPLICIT_VERSION"
|
||||
if ! echo "$new" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "::error::Invalid version '$new' — expected X.Y.Z"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
IFS=. read -r major minor patch <<< "$current"
|
||||
case "$BUMP" in
|
||||
major) new="$((major + 1)).0.0" ;;
|
||||
minor) new="$major.$((minor + 1)).0" ;;
|
||||
patch) new="$major.$minor.$((patch + 1))" ;;
|
||||
esac
|
||||
fi
|
||||
if git rev-parse -q --verify "refs/tags/v$new" > /dev/null; then
|
||||
echo "::error::Tag v$new already exists"
|
||||
exit 1
|
||||
fi
|
||||
echo "Bumping $current -> $new"
|
||||
echo "new=$new" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Push bump commit to release branch
|
||||
env:
|
||||
NEW_VERSION: ${{ steps.version.outputs.new }}
|
||||
run: |
|
||||
sed -i -E "s/^([[:space:]]*'version' => ')[0-9.]+(',)/\1$NEW_VERSION\2/" config/app.php
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -am "Bump version to $NEW_VERSION"
|
||||
git push --force origin "HEAD:refs/heads/release/v$NEW_VERSION"
|
||||
|
||||
- name: Open pull request
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
|
||||
NEW_VERSION: ${{ steps.version.outputs.new }}
|
||||
run: |
|
||||
existing=$(gh pr list --head "release/v$NEW_VERSION" --base 2.x --state open --json number -q '.[0].number')
|
||||
if [ -n "$existing" ]; then
|
||||
echo "PR #$existing is already open for release/v$NEW_VERSION"
|
||||
else
|
||||
gh pr create \
|
||||
--base 2.x \
|
||||
--head "release/v$NEW_VERSION" \
|
||||
--title "Bump version to $NEW_VERSION" \
|
||||
--body "Automated version bump. Merging this PR will tag v$NEW_VERSION and publish the GitHub release. Merge it last, once everything for the release is on 2.x."
|
||||
fi
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Tag and release
|
||||
|
||||
# Stage 2 of the release flow: whenever the version in config/app.php changes
|
||||
# on 2.x (normally by merging the PR opened by release.yml, but a hand-made
|
||||
# bump PR works too), create the matching tag and GitHub release.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 2.x
|
||||
paths:
|
||||
- config/app.php
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
tag-release:
|
||||
name: Tag and publish release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create release if version is untagged
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
version=$(sed -nE "s/^[[:space:]]*'version' => '([0-9]+\.[0-9]+\.[0-9]+)',/\1/p" config/app.php)
|
||||
if [ -z "$version" ]; then
|
||||
echo "::error::Could not read version from config/app.php"
|
||||
exit 1
|
||||
fi
|
||||
if git rev-parse -q --verify "refs/tags/v$version" > /dev/null; then
|
||||
echo "Tag v$version already exists — nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
gh release create "v$version" \
|
||||
--target "$GITHUB_SHA" \
|
||||
--title "v$version" \
|
||||
--generate-notes
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Tag version check
|
||||
|
||||
# Safety net for manually pushed tags: fails if the tag doesn't match the
|
||||
# version in config/app.php. Tags created by tag-release.yml use GITHUB_TOKEN
|
||||
# and therefore don't trigger this (they always match anyway).
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Tag matches config/app.php
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Compare tag with app version
|
||||
run: |
|
||||
tag="${GITHUB_REF_NAME#v}"
|
||||
version=$(sed -nE "s/^[[:space:]]*'version' => '([0-9]+\.[0-9]+\.[0-9]+)',/\1/p" config/app.php)
|
||||
if [ "$tag" != "$version" ]; then
|
||||
echo "::error::Tag v$tag does not match config/app.php version $version — bump the version (or use the Release workflow, which does it for you)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Tag v$tag matches config/app.php"
|
||||
@@ -29,3 +29,4 @@ yarn-error.log
|
||||
storage/app/public/avatars/*
|
||||
.env
|
||||
.phpunit.result.cache
|
||||
/.phpunit.cache
|
||||
|
||||
+1705
-1496
File diff suppressed because it is too large
Load Diff
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9003,
|
||||
"pathMappings": {
|
||||
"/var/www/html": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Start Docker Compose",
|
||||
"type": "shell",
|
||||
"command": "docker-compose up --build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.3.x | :white_check_mark: |
|
||||
| < 2.3 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
You can report any vulnerabilities on our discord server by DM-ing a team member, or asking a team member to DM you.
|
||||
|
||||
https://discord.com/invite/YWrKVTn
|
||||
+17346
-11130
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// $schedule->command('inspire')
|
||||
// ->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*/
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* The list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class Form extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return 'custom-form';
|
||||
}
|
||||
}
|
||||
+18
-2
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
/**
|
||||
* @param $bytes
|
||||
@@ -26,6 +27,17 @@ function format_bytes($bytes, bool $is_drive_size = true, string $beforeunit = '
|
||||
}
|
||||
}
|
||||
|
||||
function parse_size($size) {
|
||||
$unit = strtolower(substr($size, -1));
|
||||
$bytes = (int)$size;
|
||||
switch($unit) {
|
||||
case 'g': $bytes *= 1024 * 1024 * 1024; break;
|
||||
case 'm': $bytes *= 1024 * 1024; break;
|
||||
case 'k': $bytes *= 1024; break;
|
||||
}
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $title
|
||||
* @param string $separator
|
||||
@@ -117,7 +129,7 @@ function className($name)
|
||||
*/
|
||||
function isImage(string $file, string $extension): bool
|
||||
{
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg', 'webp'];
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg', 'webp', 'ico'];
|
||||
|
||||
if (!in_array($extension, $allowedExtensions)) {
|
||||
return false;
|
||||
@@ -129,7 +141,11 @@ function isImage(string $file, string $extension): bool
|
||||
fwrite($handle, $file);
|
||||
fclose($handle);
|
||||
|
||||
if ($extension == 'svg') {
|
||||
if ($extension === 'svg') {
|
||||
$sanitizer = new Sanitizer();
|
||||
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($tempFileName));
|
||||
file_put_contents($tempFileName, $sanitizedSvg);
|
||||
|
||||
return 'image/svg+xml' === mime_content_type($tempFileName);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Phrity\Net\Context;
|
||||
use WebSocket\Client;
|
||||
use WebSocket\Exception\Exception as WebSocketException;
|
||||
|
||||
/**
|
||||
* TrueNAS JSON-RPC 2.0 WebSocket Client
|
||||
*
|
||||
* Handles WebSocket communication with TrueNAS using the JSON-RPC 2.0 protocol.
|
||||
* Required for TrueNAS 25.04+ as the REST API is deprecated.
|
||||
*
|
||||
* @see https://api.truenas.com/v25.10/jsonrpc.html
|
||||
*/
|
||||
class TrueNASWebSocketClient
|
||||
{
|
||||
private ?Client $client = null;
|
||||
private string $url;
|
||||
private string $apiKey;
|
||||
private bool $ignoreTls;
|
||||
private bool $authenticated = false;
|
||||
private int $requestId = 1;
|
||||
|
||||
/**
|
||||
* Create a new TrueNAS WebSocket client instance.
|
||||
*
|
||||
* @param string $baseUrl The base URL of the TrueNAS instance (e.g., https://truenas.local)
|
||||
* @param string $apiKey The API key for authentication
|
||||
* @param bool $ignoreTls Whether to skip TLS certificate verification
|
||||
*/
|
||||
public function __construct(string $baseUrl, string $apiKey, bool $ignoreTls = false)
|
||||
{
|
||||
$baseUrl = rtrim($baseUrl, '/');
|
||||
$scheme = parse_url($baseUrl, PHP_URL_SCHEME);
|
||||
$host = parse_url($baseUrl, PHP_URL_HOST);
|
||||
$port = parse_url($baseUrl, PHP_URL_PORT);
|
||||
|
||||
$wsScheme = ($scheme === 'https') ? 'wss' : 'ws';
|
||||
$portPart = $port ? ':' . $port : '';
|
||||
|
||||
$this->url = "{$wsScheme}://{$host}{$portPart}/api/current";
|
||||
$this->apiKey = $apiKey;
|
||||
$this->ignoreTls = $ignoreTls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the TrueNAS WebSocket API and authenticate.
|
||||
*
|
||||
* @return bool True if connection and authentication succeeded
|
||||
* @throws \Exception If connection or authentication fails
|
||||
*/
|
||||
public function connect(): bool
|
||||
{
|
||||
if ($this->client !== null && $this->authenticated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build SSL options - always force HTTP/1.1 via ALPN for WebSocket compatibility
|
||||
// TrueNAS nginx defaults to HTTP/2 which doesn't support WebSocket upgrade
|
||||
$sslOptions = [
|
||||
'alpn_protocols' => 'http/1.1',
|
||||
];
|
||||
|
||||
if ($this->ignoreTls) {
|
||||
$sslOptions['verify_peer'] = false;
|
||||
$sslOptions['verify_peer_name'] = false;
|
||||
$sslOptions['allow_self_signed'] = true;
|
||||
}
|
||||
|
||||
// Create context using phrity/net-stream Context class (required by phrity/websocket v3.x)
|
||||
$streamContext = stream_context_create(['ssl' => $sslOptions]);
|
||||
$context = new Context($streamContext);
|
||||
|
||||
try {
|
||||
$this->client = new Client($this->url);
|
||||
$this->client->setTimeout(15);
|
||||
$this->client->setContext($context);
|
||||
|
||||
$authResult = $this->call('auth.login_with_api_key', [$this->apiKey]);
|
||||
|
||||
if ($authResult === true) {
|
||||
$this->authenticated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new \Exception('Authentication failed: Invalid API key');
|
||||
} catch (WebSocketException $e) {
|
||||
Log::error('TrueNAS WebSocket connection failed: ' . $e->getMessage());
|
||||
$this->disconnect();
|
||||
throw new \Exception('WebSocket connection failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a JSON-RPC 2.0 call to the TrueNAS API.
|
||||
*
|
||||
* @param string $method The JSON-RPC method name (e.g., 'system.info')
|
||||
* @param array $params Optional parameters for the method
|
||||
* @return mixed The result from the API call
|
||||
* @throws \Exception If the call fails or returns an error
|
||||
*/
|
||||
public function call(string $method, array $params = [])
|
||||
{
|
||||
if ($this->client === null) {
|
||||
throw new \Exception('WebSocket client not connected');
|
||||
}
|
||||
|
||||
$request = [
|
||||
'jsonrpc' => '2.0',
|
||||
'method' => $method,
|
||||
'id' => $this->requestId++,
|
||||
];
|
||||
|
||||
if (!empty($params)) {
|
||||
$request['params'] = $params;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->client->text(json_encode($request));
|
||||
$response = $this->client->receive();
|
||||
$decoded = json_decode($response->getContent(), true);
|
||||
|
||||
if (isset($decoded['error'])) {
|
||||
$errorMsg = $decoded['error']['message'] ?? 'Unknown error';
|
||||
$errorCode = $decoded['error']['code'] ?? 0;
|
||||
throw new \Exception("API error ({$errorCode}): {$errorMsg}");
|
||||
}
|
||||
|
||||
return $decoded['result'] ?? null;
|
||||
} catch (WebSocketException $e) {
|
||||
Log::error('TrueNAS WebSocket call failed: ' . $e->getMessage());
|
||||
throw new \Exception('WebSocket call failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the WebSocket connection.
|
||||
*/
|
||||
public function disconnect(): void
|
||||
{
|
||||
if ($this->client !== null) {
|
||||
try {
|
||||
$this->client->close();
|
||||
} catch (\Exception $e) {
|
||||
Log::debug('Error closing WebSocket: ' . $e->getMessage());
|
||||
}
|
||||
$this->client = null;
|
||||
$this->authenticated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the client is connected and authenticated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected(): bool
|
||||
{
|
||||
return $this->client !== null && $this->authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the connection by calling core.ping.
|
||||
*
|
||||
* @return bool True if the ping succeeds
|
||||
*/
|
||||
public function ping(): bool
|
||||
{
|
||||
try {
|
||||
$result = $this->call('core.ping');
|
||||
return $result === 'pong';
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up on destruction.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Illuminate\Http\Response;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
class ItemController extends Controller
|
||||
{
|
||||
@@ -33,34 +35,55 @@ class ItemController extends Controller
|
||||
/**
|
||||
* Display a listing of the resource on the dashboard.
|
||||
*/
|
||||
public function dash(): View
|
||||
public function dash(Request $request): View
|
||||
{
|
||||
$treat_tags_as = \App\Setting::fetch('treat_tags_as');
|
||||
|
||||
$data["treat_tags_as"] = $treat_tags_as;
|
||||
|
||||
if ($treat_tags_as == 'categories') {
|
||||
$data['categories'] = Item::whereHas('children')->with('children', function ($query) {
|
||||
$query->pinned()->orderBy('order', 'asc');
|
||||
})->pinned()->orderBy('order', 'asc')->get();
|
||||
|
||||
} elseif ($treat_tags_as == 'tags') {
|
||||
$data['apps'] = Item::with('parents')->where('type', 0)->pinned()->orderBy('order', 'asc')->get();
|
||||
$data['all_apps'] = Item::where('type', 0)->orderBy('order', 'asc')->get();
|
||||
$data['taglist'] = Item::where('id', 0)->orWhere(function($query) {
|
||||
$query->where('type', 1)->pinned();
|
||||
})->orderBy('order', 'asc')->get();
|
||||
if (config('app.auth_roles_enable')) {
|
||||
$roles = explode(config('app.auth_roles_delimiter'), $request->header(config('app.auth_roles_header')));
|
||||
if ($treat_tags_as == 'categories') {
|
||||
$data['categories'] = Item::whereHas('children')->with('children', function ($query) {
|
||||
$query->pinned()->orderBy('order', 'asc');
|
||||
})->pinned()->orderBy('order', 'asc')->get();
|
||||
} elseif ($treat_tags_as == 'tags') {
|
||||
$data['apps'] = Item::with('parents')->where('type', 0)->pinned()->orderBy('order', 'asc')->get();
|
||||
$data['all_apps'] = Item::where('type', 0)->orderBy('order', 'asc')->get();
|
||||
$data['taglist'] = Item::where('id', 0)->orWhere(function ($query) {
|
||||
$query->where('type', 1)->pinned();
|
||||
})->orderBy('order', 'asc')->get();
|
||||
} else {
|
||||
$data['apps'] = Item::whereHas('parents', function ($query) {
|
||||
$query->where('id', 0);
|
||||
})->whereIn('role', $roles)->orWhere('type', 1)->pinned()->orderBy('order', 'asc')->get();
|
||||
|
||||
$data['all_apps'] = Item::whereHas('parents', function ($query) {
|
||||
$query->where('id', 0);
|
||||
})->orWhere('type', 1)->orderBy('order', 'asc')->get();
|
||||
}
|
||||
} else {
|
||||
if ($treat_tags_as == 'categories') {
|
||||
$data['categories'] = Item::whereHas('children')->with('children', function ($query) {
|
||||
$query->pinned()->orderBy('order', 'asc');
|
||||
})->pinned()->orderBy('order', 'asc')->get();
|
||||
} elseif ($treat_tags_as == 'tags') {
|
||||
$data['apps'] = Item::with('parents')->where('type', 0)->pinned()->orderBy('order', 'asc')->get();
|
||||
$data['all_apps'] = Item::where('type', 0)->orderBy('order', 'asc')->get();
|
||||
$data['taglist'] = Item::where('id', 0)->orWhere(function ($query) {
|
||||
$query->where('type', 1)->pinned();
|
||||
})->orderBy('order', 'asc')->get();
|
||||
} else {
|
||||
$data['apps'] = Item::whereHas('parents', function ($query) {
|
||||
$query->where('id', 0);
|
||||
})->orWhere('type', 1)->pinned()->orderBy('order', 'asc')->get();
|
||||
|
||||
$data['apps'] = Item::whereHas('parents', function ($query) {
|
||||
$query->where('id', 0);
|
||||
})->orWhere('type', 1)->pinned()->orderBy('order', 'asc')->get();
|
||||
|
||||
$data['all_apps'] = Item::whereHas('parents', function ($query) {
|
||||
$query->where('id', 0);
|
||||
})->orWhere(function ($query) {
|
||||
$query->where('type', 1)->whereNot('id', 0);
|
||||
})->orderBy('order', 'asc')->get();
|
||||
$data['all_apps'] = Item::whereHas('parents', function ($query) {
|
||||
$query->where('id', 0);
|
||||
})->orWhere(function ($query) {
|
||||
$query->where('type', 1)->whereNot('id', 0);
|
||||
})->orderBy('order', 'asc')->get();
|
||||
}
|
||||
}
|
||||
|
||||
//$data['all_apps'] = Item::doesntHave('parents')->get();
|
||||
@@ -171,6 +194,7 @@ class ItemController extends Controller
|
||||
public function create(): View
|
||||
{
|
||||
//
|
||||
$data['item'] = new \App\Item();
|
||||
$data['tags'] = Item::ofType('tag')->orderBy('title', 'asc')->pluck('title', 'id');
|
||||
$data['tags']->prepend(__('app.dashboard'), 0);
|
||||
$data['current_tags'] = '0';
|
||||
@@ -185,7 +209,7 @@ class ItemController extends Controller
|
||||
{
|
||||
// Get the item
|
||||
$item = Item::find($id);
|
||||
if ($item->appid === null && $item->class !== null) { // old apps wont have an app id so set it
|
||||
if ($item->appid === null && $item->class !== null) { // old apps won't have an app id so set it
|
||||
$app = Application::where('class', $item->class)->first();
|
||||
if ($app) {
|
||||
$item->appid = $app->appid;
|
||||
@@ -215,7 +239,23 @@ class ItemController extends Controller
|
||||
]);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
$path = $request->file('file')->store('icons', 'public');
|
||||
$image = $request->file('file');
|
||||
$extension = $image->getClientOriginalExtension();
|
||||
|
||||
if ($extension === 'svg') {
|
||||
$sanitizer = new Sanitizer();
|
||||
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($image->getRealPath()));
|
||||
|
||||
// Verify that the sanitization removed malicious content
|
||||
if (strpos($sanitizedSvg, '<script>') !== false) {
|
||||
throw ValidationException::withMessages(['file' => 'SVG contains malicious content and cannot be uploaded.']);
|
||||
}
|
||||
|
||||
// Save the sanitized SVG back to the file
|
||||
file_put_contents($image->getRealPath(), $sanitizedSvg);
|
||||
}
|
||||
|
||||
$path = $image->store('icons', 'public');
|
||||
$request->merge([
|
||||
'icon' => $path,
|
||||
]);
|
||||
@@ -227,12 +267,32 @@ class ItemController extends Controller
|
||||
],
|
||||
];
|
||||
|
||||
// Proxy management
|
||||
$httpsProxy = getenv('HTTPS_PROXY');
|
||||
$httpsProxyLower = getenv('https_proxy');
|
||||
if ($httpsProxy !== false || $httpsProxyLower !== false) {
|
||||
$options['http']['proxy'] = $httpsProxy ?: $httpsProxyLower;
|
||||
}
|
||||
|
||||
$file = $request->input('icon');
|
||||
$path_parts = pathinfo($file);
|
||||
if (!array_key_exists('extension', $path_parts)) {
|
||||
throw ValidationException::withMessages(['file' => 'Icon URL must have a valid file extension.']);
|
||||
}
|
||||
$extension = $path_parts['extension'];
|
||||
|
||||
$contents = file_get_contents($request->input('icon'), false, stream_context_create($options));
|
||||
|
||||
if ($extension === 'svg') {
|
||||
$sanitizer = new Sanitizer();
|
||||
$contents = $sanitizer->sanitize($contents);
|
||||
|
||||
// Verify that the sanitization removed malicious content
|
||||
if (strpos($contents, '<script>') !== false) {
|
||||
throw ValidationException::withMessages(['file' => 'SVG contains malicious content and cannot be uploaded.']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isImage($contents, $extension)) {
|
||||
throw ValidationException::withMessages(['file' => 'Icon must be an image.']);
|
||||
}
|
||||
@@ -259,18 +319,28 @@ class ItemController extends Controller
|
||||
$storedConfigObject = json_decode($storedItem->getAttribute('description'));
|
||||
|
||||
$configObject = json_decode($config);
|
||||
$configObject->password = $storedConfigObject->password;
|
||||
if ($storedConfigObject && property_exists($storedConfigObject, 'password')) {
|
||||
$configObject->password = $storedConfigObject->password;
|
||||
} else {
|
||||
$configObject->password = null;
|
||||
}
|
||||
|
||||
$config = json_encode($configObject);
|
||||
}
|
||||
|
||||
$current_user = User::currentUser();
|
||||
$request->merge([
|
||||
'description' => $config,
|
||||
'user_id' => $current_user->getId(),
|
||||
]);
|
||||
|
||||
if ($request->input('appid') === 'null') {
|
||||
// Only assign ownership when creating; updates must keep the existing owner.
|
||||
if ($id === null) {
|
||||
$current_user = User::currentUser();
|
||||
$request->merge([
|
||||
'user_id' => $current_user->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->input('appid') === 'null' || $request->input('appid') === null) {
|
||||
$request->merge([
|
||||
'class' => null,
|
||||
]);
|
||||
@@ -284,7 +354,8 @@ class ItemController extends Controller
|
||||
$item = Item::create($request->all());
|
||||
} else {
|
||||
$item = Item::find($id);
|
||||
$item->update($request->all());
|
||||
// Exclude user_id so an update can never reassign ownership
|
||||
$item->update($request->except(['user_id']));
|
||||
}
|
||||
|
||||
$item->parents()->sync($request->tags);
|
||||
@@ -366,27 +437,41 @@ class ItemController extends Controller
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function appload(Request $request): ?string
|
||||
public function appload(Request $request): \Illuminate\Http\JsonResponse|string|null
|
||||
{
|
||||
$output = [];
|
||||
$appid = $request->input('app');
|
||||
$itemId = $request->input('item_id');
|
||||
|
||||
if ($appid === 'null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$output['config'] = null;
|
||||
$output['custom'] = null;
|
||||
|
||||
$app = Application::single($appid);
|
||||
|
||||
if (!$app) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
$output = (array)$app;
|
||||
|
||||
$appdetails = Application::getApp($appid);
|
||||
|
||||
if (!$appdetails) {
|
||||
return response()->json(['error' => 'Application details not found.'], 404);
|
||||
}
|
||||
|
||||
if ((bool)$app->enhanced === true) {
|
||||
// if(!isset($app->config)) { // class based config
|
||||
$output['custom'] = className($appdetails->name) . '.config';
|
||||
// }
|
||||
$item = $itemId ? Item::find($itemId) : Item::where('appid', $appid)->first();
|
||||
|
||||
if ($item) {
|
||||
$output['custom'] = className($appdetails->name) . '.config';
|
||||
$output['appvalue'] = $item->description;
|
||||
} else {
|
||||
// Ensure the app is installed if not found
|
||||
$output['custom'] = className($appdetails->name) . '.config';
|
||||
$output['appvalue'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$output['colour'] = ($app->tile_background == 'light') ? '#fafbfc' : '#161b1f';
|
||||
@@ -394,14 +479,12 @@ class ItemController extends Controller
|
||||
if (strpos($app->icon, '://') !== false) {
|
||||
$output['iconview'] = $app->icon;
|
||||
} elseif (strpos($app->icon, 'icons/') !== false) {
|
||||
// Private apps have the icon locally
|
||||
$output['iconview'] = URL::to('/') . '/storage/' . $app->icon;
|
||||
$output['icon'] = str_replace('icons/', '', $output['icon']);
|
||||
} else {
|
||||
$output['iconview'] = config('app.appsource') . 'icons/' . $app->icon;
|
||||
}
|
||||
|
||||
|
||||
return json_encode($output);
|
||||
}
|
||||
|
||||
@@ -439,25 +522,48 @@ class ItemController extends Controller
|
||||
*/
|
||||
public function execute($url, array $attrs = [], $overridevars = false): ?ResponseInterface
|
||||
{
|
||||
$vars = ($overridevars !== false) ?
|
||||
$overridevars : [
|
||||
'http_errors' => false,
|
||||
'timeout' => 15,
|
||||
'connect_timeout' => 15,
|
||||
'verify' => false,
|
||||
];
|
||||
// Default Guzzle client configuration
|
||||
$clientOptions = [
|
||||
'http_errors' => false,
|
||||
'timeout' => 15,
|
||||
'connect_timeout' => 15,
|
||||
'verify' => false, // In production, set this to `true` and manage certs.
|
||||
];
|
||||
|
||||
$client = new Client($vars);
|
||||
// If the user provided overrides, use them.
|
||||
if ($overridevars !== false) {
|
||||
$clientOptions = $overridevars;
|
||||
}
|
||||
|
||||
// Resolve the hostname to an IP address
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$ip = gethostbyname($host);
|
||||
|
||||
// Check if the IP is private or reserved
|
||||
$allowInternalIps = env('ALLOW_INTERNAL_REQUESTS', false);
|
||||
if (!$allowInternalIps && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
Log::warning('Blocked access to private or reserved IPs.', ['ip' => $ip, 'host' => $host]);
|
||||
abort(Response::HTTP_FORBIDDEN, 'Access to private or reserved IPs is not allowed.');
|
||||
}
|
||||
|
||||
// Force Guzzle to use the resolved IP address
|
||||
$clientOptions['curl'][CURLOPT_RESOLVE] = ["{$host}:80:{$ip}", "{$host}:443:{$ip}"];
|
||||
|
||||
$client = new Client($clientOptions);
|
||||
$method = 'GET';
|
||||
|
||||
try {
|
||||
return $client->request($method, $url, $attrs);
|
||||
} catch (ConnectException $e) {
|
||||
Log::error('Connection refused');
|
||||
Log::debug($e->getMessage());
|
||||
Log::warning('SSRF Attempt Blocked: Connection to a private IP was prevented.', [
|
||||
'url' => $url,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return null;
|
||||
} catch (ServerException $e) {
|
||||
Log::debug($e->getMessage());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('General error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -469,25 +575,65 @@ class ItemController extends Controller
|
||||
*/
|
||||
public function websitelookup($url): StreamInterface
|
||||
{
|
||||
$url = base64_decode($url);
|
||||
$data = $this->execute($url);
|
||||
$decodedUrl = base64_decode($url);
|
||||
|
||||
return $data->getBody();
|
||||
// Validate the URL format.
|
||||
if (filter_var($decodedUrl, FILTER_VALIDATE_URL) === false) {
|
||||
abort(Response::HTTP_BAD_REQUEST, 'Invalid URL format provided.');
|
||||
}
|
||||
|
||||
$response = $this->execute($decodedUrl);
|
||||
|
||||
// If execute() returns null, it means the connection failed.
|
||||
// This can happen for many reasons, including our SSRF protection kicking in.
|
||||
if ($response === null) {
|
||||
abort(Response::HTTP_FORBIDDEN, 'Access to the requested resource is not allowed or the resource is unavailable.');
|
||||
}
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @return void
|
||||
* Return live stats for an enhanced application tile.
|
||||
*
|
||||
* Always responds with HTTP 200 and valid JSON so the frontend refresh
|
||||
* loop (liveStatRefresh.js) keeps re-queueing the tile. On any failure we
|
||||
* degrade gracefully to an inactive, empty tile instead of a 500.
|
||||
*
|
||||
* @param int|string $id
|
||||
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
|
||||
*/
|
||||
public function getStats($id)
|
||||
{
|
||||
$item = Item::find($id);
|
||||
$graceful = response()->json(['status' => 'inactive', 'html' => '']);
|
||||
|
||||
$item = Item::find($id);
|
||||
if ($item === null) {
|
||||
return $graceful;
|
||||
}
|
||||
|
||||
// Non-enhanced items (or stale records) have no live-stats class.
|
||||
if (empty($item->class)) {
|
||||
return $graceful;
|
||||
}
|
||||
|
||||
try {
|
||||
$config = $item->getconfig();
|
||||
|
||||
// Guard against a stale/renamed class string from the remote apps repo.
|
||||
if (! class_exists($item->class)) {
|
||||
return $graceful;
|
||||
}
|
||||
|
||||
$config = $item->getconfig();
|
||||
if (isset($item->class)) {
|
||||
$application = new $item->class;
|
||||
$application->config = $config;
|
||||
echo $application->livestats();
|
||||
|
||||
// livestats() returns a JSON string; return it verbatim (no re-encoding).
|
||||
return response($application->livestats());
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('getStats failed for item '.$id.' ('.$item->class.'): '.$e->getMessage());
|
||||
|
||||
return $graceful;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Item;
|
||||
use App\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -21,6 +22,7 @@ class ItemRestController extends Controller
|
||||
public function index(): Collection
|
||||
{
|
||||
$columns = [
|
||||
'id',
|
||||
'title',
|
||||
'colour',
|
||||
'url',
|
||||
@@ -29,11 +31,27 @@ class ItemRestController extends Controller
|
||||
'appdescription',
|
||||
];
|
||||
|
||||
return Item::select($columns)
|
||||
return Item::with('parents')
|
||||
->select($columns)
|
||||
->where('deleted_at', null)
|
||||
->where('type', '0')
|
||||
->orderBy('order', 'asc')
|
||||
->get();
|
||||
->get()
|
||||
->map(function (Item $item) {
|
||||
return [
|
||||
'title' => $item->title,
|
||||
'colour' => $item->colour,
|
||||
'url' => $item->url,
|
||||
'description' => $item->description,
|
||||
'appid' => $item->appid,
|
||||
'appdescription' => $item->appdescription,
|
||||
'tags' => $item->parents
|
||||
->where('id', '!=', 0)
|
||||
->pluck('title')
|
||||
->values()
|
||||
->all(),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,6 +68,16 @@ class ItemRestController extends Controller
|
||||
*/
|
||||
public function store(Request $request): object
|
||||
{
|
||||
// Imports pass tags as an array of tag titles so they can round-trip
|
||||
// across instances. Resolve those titles into local tag ids (creating
|
||||
// any that don't yet exist) before handing off to the shared store
|
||||
// logic. When no tags are supplied we keep the previous behaviour.
|
||||
if ($request->has('tags')) {
|
||||
$request->merge([
|
||||
'tags' => $this->resolveTags($request->input('tags')),
|
||||
]);
|
||||
}
|
||||
|
||||
$item = ItemController::storelogic($request);
|
||||
|
||||
if ($item) {
|
||||
@@ -59,6 +87,61 @@ class ItemRestController extends Controller
|
||||
return (object) ['status' => 'FAILED'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an incoming list of tags into tag ids.
|
||||
*
|
||||
* Numeric 0 (or "0") maps to the root/default dashboard. Every other entry
|
||||
* is treated as a tag title: an existing tag with that title is reused, and
|
||||
* a missing one is created. The lookup keeps the operation idempotent so
|
||||
* importing many items that share a tag title only ever creates one tag.
|
||||
*
|
||||
* @param mixed $tags
|
||||
* @return array<int, int>
|
||||
*/
|
||||
private function resolveTags($tags): array
|
||||
{
|
||||
if (! is_array($tags)) {
|
||||
return [0];
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag === 0 || $tag === '0') {
|
||||
$ids[] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = is_string($tag) ? trim($tag) : $tag;
|
||||
|
||||
if ($title === '' || $title === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = Item::where('type', '1')
|
||||
->where('title', $title)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
$ids[] = (int) $existing->id;
|
||||
continue;
|
||||
}
|
||||
|
||||
$created = Item::create([
|
||||
'title' => $title,
|
||||
'type' => '1',
|
||||
'url' => str_slug($title, '-', 'en_US'),
|
||||
'user_id' => User::currentUser()->getId(),
|
||||
]);
|
||||
|
||||
$ids[] = (int) $created->id;
|
||||
}
|
||||
|
||||
$ids = array_values(array_unique($ids));
|
||||
|
||||
return empty($ids) ? [0] : $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
|
||||
@@ -4,9 +4,11 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Search;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
@@ -18,16 +20,120 @@ class SearchController extends Controller
|
||||
$requestprovider = $request->input('provider');
|
||||
$query = $request->input('q');
|
||||
|
||||
// Sanitize the query to prevent XSS
|
||||
$query = htmlspecialchars($query, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$provider = Search::providerDetails($requestprovider);
|
||||
|
||||
if (!$provider || !isset($provider->type)) {
|
||||
abort(404, 'Invalid provider');
|
||||
}
|
||||
|
||||
// If the query is empty, redirect to the provider's base URL
|
||||
if (!$query || trim($query) === '') {
|
||||
return redirect($provider->url);
|
||||
}
|
||||
|
||||
if ($provider->type == 'standard') {
|
||||
return redirect($provider->url.'?'.$provider->query.'='.urlencode($query));
|
||||
} elseif ($provider->type == 'external') {
|
||||
$class = new $provider->class;
|
||||
//print_r($provider);
|
||||
return $class->getResults($query, $provider);
|
||||
}
|
||||
|
||||
//print_r($provider);
|
||||
abort(404, 'Provider type not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get autocomplete suggestions for a search query
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function autocomplete(Request $request)
|
||||
{
|
||||
$requestprovider = $request->input('provider');
|
||||
$query = $request->input('q');
|
||||
|
||||
if (!$query || trim($query) === '') {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
$provider = Search::providerDetails($requestprovider);
|
||||
|
||||
if (!$provider || !isset($provider->autocomplete)) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
// Replace {query} placeholder with actual query
|
||||
$autocompleteUrl = str_replace('{query}', urlencode($query), $provider->autocomplete);
|
||||
|
||||
try {
|
||||
$response = Http::timeout(5)->get($autocompleteUrl);
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->body();
|
||||
|
||||
// Parse the response based on provider
|
||||
$suggestions = $this->parseAutocompleteResponse($data, $provider->id);
|
||||
|
||||
return response()->json($suggestions);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Return empty array on error
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse autocomplete response based on provider format
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $providerId
|
||||
* @return array
|
||||
*/
|
||||
private function parseAutocompleteResponse($data, $providerId)
|
||||
{
|
||||
$suggestions = [];
|
||||
|
||||
switch ($providerId) {
|
||||
case 'google':
|
||||
// Google returns XML format
|
||||
if (strpos($data, '<?xml') === 0) {
|
||||
$xml = simplexml_load_string($data);
|
||||
if ($xml && isset($xml->CompleteSuggestion)) {
|
||||
foreach ($xml->CompleteSuggestion as $suggestion) {
|
||||
if (isset($suggestion->suggestion['data'])) {
|
||||
$suggestions[] = (string) $suggestion->suggestion['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'bing':
|
||||
case 'ddg':
|
||||
// Bing and DuckDuckGo return JSON array format
|
||||
$json = json_decode($data, true);
|
||||
if (is_array($json) && isset($json[1]) && is_array($json[1])) {
|
||||
$suggestions = $json[1];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Try to parse as JSON array
|
||||
$json = json_decode($data, true);
|
||||
if (is_array($json)) {
|
||||
if (isset($json[1]) && is_array($json[1])) {
|
||||
$suggestions = $json[1];
|
||||
} else {
|
||||
$suggestions = $json;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Setting;
|
||||
use App\SettingGroup;
|
||||
use Exception;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -44,6 +45,7 @@ class SettingsController extends Controller
|
||||
if (! is_null($setting)) {
|
||||
return view('settings.edit')->with([
|
||||
'setting' => $setting,
|
||||
'value' => $setting->value,
|
||||
]);
|
||||
} else {
|
||||
$route = route('settings.list', []);
|
||||
@@ -68,16 +70,30 @@ class SettingsController extends Controller
|
||||
|
||||
if ($setting->type === 'image') {
|
||||
$validatedData = $request->validate([
|
||||
'value' => 'image'
|
||||
'value' => 'image',
|
||||
]);
|
||||
|
||||
if (!$request->hasFile('value')) {
|
||||
throw new \Exception(
|
||||
'file_too_big'
|
||||
);
|
||||
throw new \Exception('file_too_big');
|
||||
}
|
||||
|
||||
$path = $request->file('value')->store('backgrounds', 'public');
|
||||
$image = $request->file('value');
|
||||
$extension = $image->getClientOriginalExtension();
|
||||
|
||||
if ($extension === 'svg') {
|
||||
$sanitizer = new Sanitizer();
|
||||
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($image->getRealPath()));
|
||||
|
||||
// Verify that the sanitization removed malicious content
|
||||
if (strpos($sanitizedSvg, '<script>') !== false) {
|
||||
throw new \Exception('SVG contains malicious content and cannot be uploaded.');
|
||||
}
|
||||
|
||||
// Save the sanitized SVG back to the file
|
||||
file_put_contents($image->getRealPath(), $sanitizedSvg);
|
||||
}
|
||||
|
||||
$path = $image->store('backgrounds', 'public');
|
||||
|
||||
if ($path === null) {
|
||||
throw new \Exception('file_not_stored');
|
||||
@@ -99,7 +115,7 @@ class SettingsController extends Controller
|
||||
} catch (Exception $e) {
|
||||
return redirect($route)
|
||||
->with([
|
||||
'errors' => collect([__('app.alert.error.'.$e->getMessage())]),
|
||||
'errors' => collect([__('app.alert.error.' . $e->getMessage())]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,14 +88,21 @@ class TagController extends Controller
|
||||
*
|
||||
* @param $slug
|
||||
*/
|
||||
public function show($slug): View
|
||||
public function show($slug, Request $request): View
|
||||
{
|
||||
$item = Item::whereUrl($slug)->first();
|
||||
//print_r($item);
|
||||
$data['apps'] = $item->children()->pinned()->orderBy('order', 'asc')->get();
|
||||
if (config('app.auth_roles_enable')) {
|
||||
$roles = explode(config('app.auth_roles_delimiter'), $request->header(config('app.auth_roles_header')));
|
||||
$data['apps'] = $item->children()->whereIn('role', $roles)->pinned()->orderBy('order', 'asc')->get();
|
||||
} else {
|
||||
$data['apps'] = $item->children()->pinned()->orderBy('order', 'asc')->get();
|
||||
}
|
||||
$data['tag'] = $item->id;
|
||||
$data['all_apps'] = $item->children;
|
||||
|
||||
$data['taglist'] = Item::ofType('tag')->where('id', '>', 0)->orderBy('title', 'asc')->get();
|
||||
|
||||
return view('welcome', $data);
|
||||
}
|
||||
|
||||
@@ -135,7 +142,8 @@ class TagController extends Controller
|
||||
'url' => $slug,
|
||||
]);
|
||||
|
||||
Item::find($id)->update($request->all());
|
||||
// Exclude user_id so an update can never reassign ownership
|
||||
Item::find($id)->update($request->except(['user_id']));
|
||||
|
||||
$route = route('dash', []);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Item;
|
||||
use App\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -154,6 +155,11 @@ class UserController extends Controller
|
||||
public function destroy(User $user): RedirectResponse
|
||||
{
|
||||
if ($user->id !== 1) {
|
||||
// Hard-delete this user's items (tiles and tags) so they don't become
|
||||
// orphaned; item_tag pivot rows cascade via the existing FK. Shared
|
||||
// items (user_id = 0) are left untouched.
|
||||
Item::withoutGlobalScopes()->where('user_id', $user->id)->forceDelete();
|
||||
|
||||
$user->delete();
|
||||
$route = route('dash', []);
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':60,1',
|
||||
'bindings',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* Aliases may be used to conveniently assign middleware to routes and groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'allowed' => \App\Http\Middleware\CheckAllowed::class,
|
||||
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* The allow-list is read from the TRUSTED_HOSTS env var (comma-separated
|
||||
* hostnames). When it is unset/empty an empty array is returned so that NO
|
||||
* host restriction is applied, preserving Heimdall's historic behaviour of
|
||||
* running on arbitrary hosts. When set, only the listed hosts (and their
|
||||
* subdomains) are accepted; any other Host header is rejected by Symfony
|
||||
* with a SuspiciousOperationException (HTTP 400).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function hosts()
|
||||
{
|
||||
$trustedHosts = env('TRUSTED_HOSTS');
|
||||
|
||||
if ($trustedHosts === null || trim((string) $trustedHosts) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$hosts = [];
|
||||
|
||||
foreach (explode(',', (string) $trustedHosts) as $host) {
|
||||
$host = trim($host);
|
||||
|
||||
if ($host !== '') {
|
||||
$hosts[] = '^(.+\.)?'.preg_quote($host).'$';
|
||||
}
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the application should specify trusted hosts.
|
||||
*
|
||||
* The parent implementation skips enforcement whenever the app runs in the
|
||||
* "local" environment (Heimdall's shipped default, see .env.example) or
|
||||
* under the test runner, which would leave the TRUSTED_HOSTS allow-list
|
||||
* silently unenforced for almost every real deployment. Instead we tie
|
||||
* enforcement directly to configuration: apply the allow-list whenever one
|
||||
* has actually been provided, in any environment. When TRUSTED_HOSTS is
|
||||
* unset hosts() is empty and this returns false, preserving the historic
|
||||
* no-restriction behaviour.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldSpecifyTrustedHosts()
|
||||
{
|
||||
return ! empty($this->hosts());
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,52 @@ use Illuminate\Http\Request;
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
* The default trusted proxies used when the TRUSTED_PROXIES env var is unset.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $proxies = ['192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8', '127.0.0.1'];
|
||||
protected $defaultProxies = ['192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8', '127.0.0.1'];
|
||||
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The current proxy header mappings.
|
||||
*
|
||||
* @var array
|
||||
* Note: Request::HEADER_X_FORWARDED_HOST is intentionally NOT trusted to
|
||||
* prevent Host header injection / open redirects (CVE-2025-50578). A spoofed
|
||||
* X-Forwarded-Host header must never influence getHost()/url()/asset().
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
|
||||
/**
|
||||
* Create a new middleware instance.
|
||||
*
|
||||
* The set of trusted proxies is read from the TRUSTED_PROXIES env var
|
||||
* (comma-separated CIDRs/IPs). When unset it falls back to the historic
|
||||
* default list. The special value "*" trusts all proxies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$trustedProxies = env('TRUSTED_PROXIES');
|
||||
|
||||
if ($trustedProxies === null || trim((string) $trustedProxies) === '') {
|
||||
$this->proxies = $this->defaultProxies;
|
||||
} elseif (trim((string) $trustedProxies) === '*') {
|
||||
$this->proxies = '*';
|
||||
} else {
|
||||
$this->proxies = array_values(array_filter(
|
||||
array_map('trim', explode(',', (string) $trustedProxies)),
|
||||
fn ($proxy) => $proxy !== ''
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
'order',
|
||||
'appload',
|
||||
'test_config',
|
||||
//'get_stats'
|
||||
];
|
||||
}
|
||||
+6
-1
@@ -33,6 +33,7 @@ use Symfony\Component\ClassLoader\ClassMapGenerator;
|
||||
* @property string|null $class
|
||||
* @property string|null $appid
|
||||
* @property string|null $appdescription
|
||||
* @property string|null $role
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|Item[] $children
|
||||
* @property-read int|null $children_count
|
||||
* @property-read string $droppable
|
||||
@@ -51,6 +52,7 @@ use Symfony\Component\ClassLoader\ClassMapGenerator;
|
||||
* @method static Builder|Item pinned()
|
||||
* @method static Builder|Item query()
|
||||
* @method static Builder|Item whereAppdescription($value)
|
||||
* @method static Builder|Item whereRole($value)
|
||||
* @method static Builder|Item whereAppid($value)
|
||||
* @method static Builder|Item whereClass($value)
|
||||
* @method static Builder|Item whereColour($value)
|
||||
@@ -84,7 +86,9 @@ class Item extends Model
|
||||
static::addGlobalScope('user_id', function (Builder $builder) {
|
||||
$current_user = User::currentUser();
|
||||
if ($current_user) {
|
||||
$builder->where('user_id', $current_user->getId())->orWhere('user_id', 0);
|
||||
$builder->where(function ($query) use ($current_user) {
|
||||
$query->where('user_id', $current_user->getId())->orWhere('user_id', 0);
|
||||
});
|
||||
} else {
|
||||
$builder->where('user_id', 0);
|
||||
}
|
||||
@@ -105,6 +109,7 @@ class Item extends Model
|
||||
'user_id',
|
||||
'tag_id',
|
||||
'appid',
|
||||
'role',
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -14,11 +14,24 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Throwable;
|
||||
|
||||
class ProcessApps implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Most failures here are GitHub rate-limit responses; retries inside the
|
||||
* same window do not help, so a single attempt is enough.
|
||||
*/
|
||||
public int $tries = 1;
|
||||
|
||||
/**
|
||||
* Expire the ShouldBeUnique lock after 10 minutes so a crashed worker
|
||||
* does not permanently block future ProcessApps dispatches.
|
||||
*/
|
||||
public int $uniqueFor = 600;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@@ -57,4 +70,13 @@ class ProcessApps implements ShouldQueue, ShouldBeUnique
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
Log::error(static::class . ' permanently failed', [
|
||||
'exception_class' => $exception::class,
|
||||
'exception_message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile() . ':' . $exception->getLine(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -12,11 +12,26 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class UpdateApps implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Most failures here are GitHub rate-limit responses; retries inside the
|
||||
* same window do not help, so a single attempt is enough. The throttle
|
||||
* loop in handle() means the job is intentionally long-running, so we
|
||||
* leave $timeout unset and let the operator's worker config govern.
|
||||
*/
|
||||
public int $tries = 1;
|
||||
|
||||
/**
|
||||
* Expire the ShouldBeUnique lock after 10 minutes so a crashed worker
|
||||
* does not permanently block future UpdateApps dispatches.
|
||||
*/
|
||||
public int $uniqueFor = 600;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@@ -49,8 +64,14 @@ class UpdateApps implements ShouldQueue, ShouldBeUnique
|
||||
Cache::lock('updateApps')->forceRelease();
|
||||
}
|
||||
|
||||
public function failed($exception): void
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
Cache::lock('updateApps')->forceRelease();
|
||||
|
||||
Log::error(static::class . ' permanently failed', [
|
||||
'exception_class' => $exception::class,
|
||||
'exception_message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile() . ':' . $exception->getLine(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,13 @@ use App\User;
|
||||
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use App\Services\CustomFormBuilder;
|
||||
use Spatie\Html\Html;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -88,6 +91,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
$view->with('trianglify_seed', $trianglify_seed);
|
||||
$view->with('allusers', $allusers);
|
||||
$view->with('current_user', $current_user);
|
||||
if (config('app.auth_roles_enable')) {
|
||||
$view->with('enable_auth_admin_controls', in_array(config('app.auth_roles_admin'), explode(config('app.auth_roles_delimiter'), $_SERVER[config('app.auth_roles_http_header')])));
|
||||
} else {
|
||||
$view->with('enable_auth_admin_controls', true);
|
||||
}
|
||||
});
|
||||
|
||||
$this->app['view']->addNamespace('SupportedApps', app_path('SupportedApps'));
|
||||
@@ -122,6 +130,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
$this->app->register(IdeHelperServiceProvider::class);
|
||||
}
|
||||
|
||||
$this->app->singleton('custom-form', function ($app) {
|
||||
return new CustomFormBuilder($app->make(Html::class));
|
||||
});
|
||||
|
||||
$this->app->singleton('settings', function () {
|
||||
return new Setting();
|
||||
});
|
||||
@@ -138,8 +150,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
$db_type = config()->get('database.default');
|
||||
|
||||
if ($db_type == 'sqlite') {
|
||||
$db_file = database_path(env('DB_DATABASE', 'app.sqlite'));
|
||||
if (! is_file($db_file)) {
|
||||
$db_file = config()->get('database.connections.sqlite.database');
|
||||
Log::debug('SQLite Database Path: ' . $db_file);
|
||||
// Do not create a file for the in-memory database identifier.
|
||||
if ($db_file !== ':memory:' && ! is_file($db_file)) {
|
||||
touch($db_file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The policy mappings for the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $policies = [
|
||||
'App\Model' => 'App\Policies\ModelPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Broadcast::routes();
|
||||
|
||||
require base_path('routes/channels.php');
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event listener mappings for the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
'App\Events\Event' => [
|
||||
'App\Listeners\EventListener',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if events and listeners should be automatically discovered.
|
||||
*/
|
||||
public function shouldDiscoverEvents(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+23
-17
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use Cache;
|
||||
use Form;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Request as Input;
|
||||
use Yaml;
|
||||
|
||||
abstract class Search
|
||||
@@ -106,31 +105,38 @@ abstract class Search
|
||||
if ((bool) $homepage_search !== true) {
|
||||
return $output;
|
||||
}
|
||||
$user_search_provider = $user_search_provider ?? 'none';
|
||||
$user_search_provider = Input::get('p') ?? $user_search_provider ?? 'none';
|
||||
|
||||
if ((bool) $search_provider) {
|
||||
if ((bool) $user_search_provider) {
|
||||
$name = 'app.options.'.$user_search_provider;
|
||||
$provider = self::providerDetails($user_search_provider);
|
||||
$providers = self::providers();
|
||||
$providerCount = count($providers);
|
||||
|
||||
// If there's only one provider, use its key instead of the user's setting
|
||||
if ($providerCount === 1) {
|
||||
$user_search_provider = $providers->keys()->first();
|
||||
}
|
||||
|
||||
$output .= '<div class="searchform">';
|
||||
$output .= '<form action="'.url('search').'"'.getLinkTargetAttribute().' method="get">';
|
||||
$output .= '<div id="search-container" class="input-container">';
|
||||
$output .= '<select name="provider">';
|
||||
foreach (self::providers() as $key => $searchprovider) {
|
||||
$selected = ((string) $key === (string) $user_search_provider) ? ' selected="selected"' : '';
|
||||
$output .= '<option value="'.$key.'"'.$selected.'>'.$searchprovider['name'].'</option>';
|
||||
|
||||
// Only show dropdown if there's more than one provider
|
||||
if ($providerCount > 1) {
|
||||
$output .= '<select name="provider">';
|
||||
foreach ($providers as $key => $searchprovider) {
|
||||
$selected = ((string) $key === (string) $user_search_provider) ? ' selected="selected"' : '';
|
||||
$output .= '<option value="'.$key.'"'.$selected.'>'.$searchprovider['name'].'</option>';
|
||||
}
|
||||
$output .= '</select>';
|
||||
} else {
|
||||
// Hidden input for single provider
|
||||
$output .= '<input type="hidden" name="provider" value="'.$user_search_provider.'" />';
|
||||
}
|
||||
$output .= '</select>';
|
||||
$output .= Form::text(
|
||||
'q',
|
||||
null,
|
||||
[
|
||||
'class' => 'homesearch',
|
||||
'autofocus' => 'autofocus',
|
||||
'placeholder' => __('app.settings.search').'...'
|
||||
]
|
||||
);
|
||||
|
||||
$output .= '<input type="text" name="q" value="'.e(Input::get('q') ?? '').'" class="homesearch" autofocus placeholder="'.__('app.settings.search').'..." />';
|
||||
$output .= '<button type="submit">'.ucwords(__('app.settings.search')).'</button>';
|
||||
$output .= '</div>';
|
||||
$output .= '</form>';
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Spatie\Html\Html;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class CustomFormBuilder
|
||||
{
|
||||
protected Html $html;
|
||||
|
||||
public function __construct(Html $html)
|
||||
{
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
public function text($name, $value = null, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->input('text', $name, $value)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
public function password($name, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->input('password', $name)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
public function hidden($name, $value = null, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->input('hidden', $name, $value)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
public function checkbox($name, $value = null, $checked = false, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->checkbox($name, $value, $checked)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
public function select($name, $list = [], $selected = null, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->select($name)->options($list, $selected)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
public function textarea($name, $value = null, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->textarea($name, $value)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
public function input($type, $name, $value = null, $options = [])
|
||||
{
|
||||
return new HtmlString(
|
||||
$this->html->input($type, $name, $value)->attributes($options)
|
||||
);
|
||||
}
|
||||
|
||||
// Add other methods as needed
|
||||
}
|
||||
+55
-29
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use Form;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -10,7 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Session\SessionManager;
|
||||
use Illuminate\Session\Store;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
|
||||
/**
|
||||
* App\Setting
|
||||
@@ -72,9 +71,23 @@ class Setting extends Model
|
||||
|
||||
public static function getInput(Request $request): object
|
||||
{
|
||||
$image = $request->file('value');
|
||||
if ($image && $image->getClientOriginalExtension() === 'svg') {
|
||||
$sanitizer = new Sanitizer();
|
||||
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($image->getRealPath()));
|
||||
|
||||
// Verify that the sanitization removed malicious content
|
||||
if (strpos($sanitizedSvg, '<script>') !== false) {
|
||||
throw new \Exception('SVG contains malicious content and cannot be uploaded.');
|
||||
}
|
||||
|
||||
// Save the sanitized SVG back to the file
|
||||
file_put_contents($image->getRealPath(), $sanitizedSvg);
|
||||
}
|
||||
|
||||
return (object) [
|
||||
'value' => $request->input('value'),
|
||||
'image' => $request->file('value'),
|
||||
'image' => $image,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -110,6 +123,12 @@ class Setting extends Model
|
||||
$options = (array) json_decode($this->options);
|
||||
if ($this->key === 'search_provider') {
|
||||
$options = Search::providers()->pluck('name', 'id')->toArray();
|
||||
} elseif ($this->key === 'default_tag') {
|
||||
$options = [];
|
||||
$tags = Item::where('type', 1)->where('id', '>', 0)->pinned()->orderBy('title', 'asc')->get();
|
||||
foreach ($tags as $tag) {
|
||||
$options[$tag->tag_url] = $tag->title;
|
||||
}
|
||||
}
|
||||
$value = (array_key_exists($this->value, $options))
|
||||
? __($options[$this->value])
|
||||
@@ -137,63 +156,70 @@ class Setting extends Model
|
||||
switch ($this->type) {
|
||||
case 'image':
|
||||
$value = '';
|
||||
if (isset($this->value) && ! empty($this->value)) {
|
||||
$value .= '<a class="setting-view-image" href="'.
|
||||
asset('storage/'.$this->value).
|
||||
'" title="'.
|
||||
__('app.settings.view').
|
||||
'" target="_blank"><img src="'.
|
||||
asset('storage/'.
|
||||
$this->value).
|
||||
if (isset($this->value) && !empty($this->value)) {
|
||||
$value .= '<a class="setting-view-image" href="' .
|
||||
asset('storage/' . $this->value) .
|
||||
'" title="' .
|
||||
__('app.settings.view') .
|
||||
'" target="_blank"><img src="' .
|
||||
asset('storage/' .
|
||||
$this->value) .
|
||||
'" /></a>';
|
||||
}
|
||||
$value .= Form::file('value', ['class' => 'form-control']);
|
||||
if (isset($this->value) && ! empty($this->value)) {
|
||||
$value .= '<a class="settinglink" href="'.
|
||||
route('settings.clear', $this->id).
|
||||
'" title="'.
|
||||
__('app.settings.remove').
|
||||
'">'.
|
||||
__('app.settings.reset').
|
||||
$value .= '<input type="file" name="value" class="form-control" />';
|
||||
if (isset($this->value) && !empty($this->value)) {
|
||||
$value .= '<a class="settinglink" href="' .
|
||||
route('settings.clear', $this->id) .
|
||||
'" title="' .
|
||||
__('app.settings.remove') .
|
||||
'">' .
|
||||
__('app.settings.reset') .
|
||||
'</a>';
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case 'boolean':
|
||||
$checked = false;
|
||||
if (isset($this->value) && (bool) $this->value === true) {
|
||||
if (isset($this->value) && (bool)$this->value === true) {
|
||||
$checked = true;
|
||||
}
|
||||
$set_checked = ($checked) ? ' checked="checked"' : '';
|
||||
$value = '
|
||||
<input type="hidden" name="value" value="0" />
|
||||
<label class="switch">
|
||||
<input type="checkbox" name="value" value="1"'.$set_checked.' />
|
||||
<input type="checkbox" name="value" value="1"' . $set_checked . ' />
|
||||
<span class="slider round"></span>
|
||||
</label>';
|
||||
|
||||
|
||||
break;
|
||||
case 'select':
|
||||
$options = json_decode($this->options);
|
||||
if ($this->key === 'search_provider') {
|
||||
$options = Search::providers()->pluck('name', 'id');
|
||||
} elseif ($this->key === 'default_tag') {
|
||||
$options = ['' => 'app.options.none'];
|
||||
$tags = Item::where('type', 1)->where('id', '>', 0)->pinned()->orderBy('title', 'asc')->get();
|
||||
foreach ($tags as $tag) {
|
||||
$options[$tag->tag_url] = $tag->title;
|
||||
}
|
||||
}
|
||||
$value = '<select name="value" class="form-control">';
|
||||
foreach ($options as $key => $opt) {
|
||||
$options->$key = __($opt);
|
||||
$value .= '<option value="' . $key . '" ' . (($this->value == $key) ? 'selected' : '') . '>' . __($opt) . '</option>';
|
||||
}
|
||||
$value = Form::select('value', $options, null, ['class' => 'form-control']);
|
||||
$value .= '</select>';
|
||||
break;
|
||||
case 'textarea':
|
||||
$value = Form::textarea('value', null, ['class' => 'form-control', 'cols' => '44', 'rows' => '15']);
|
||||
$value = '<textarea name="value" class="form-control" cols="44" rows="15">' . htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8') . '</textarea>';
|
||||
break;
|
||||
default:
|
||||
$value = Form::text('value', null, ['class' => 'form-control']);
|
||||
$value = '<input type="text" name="value" class="form-control" value="' . htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8') . '" />';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\SettingGroup::class, 'group_id');
|
||||
|
||||
@@ -85,6 +85,11 @@ abstract class SupportedApps
|
||||
'connect_timeout' => 15,
|
||||
] : $overridevars;
|
||||
|
||||
// Check global setting to skip TLS verification (useful for self-signed certificates)
|
||||
if (Setting::fetch('skip_tls_verification')) {
|
||||
$vars['verify'] = false;
|
||||
}
|
||||
|
||||
$client = new Client($vars);
|
||||
|
||||
$method = ($overridemethod === null || $overridemethod === false) ? $this->method : $overridemethod;
|
||||
|
||||
@@ -1,53 +1,15 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register The Auto Loader
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Composer provides a convenient, automatically generated class loader
|
||||
| for our application. We just need to utilize it! We'll require it
|
||||
| into the script here so that we do not have to worry about the
|
||||
| loading of any our classes "manually". Feels great to relax.
|
||||
|
|
||||
*/
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Run The Artisan Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When we run the console application, the current CLI command will be
|
||||
| executed in this console and the response sent back to a terminal
|
||||
| or another output device for the developers. Here goes nothing!
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
|
||||
$status = $kernel->handle(
|
||||
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||
new Symfony\Component\Console\Output\ConsoleOutput
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Shutdown The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Once Artisan has finished running, we will fire off the shutdown events
|
||||
| so that any final work may be done by the application before we shut
|
||||
| down the process. This is the last thing to happen to the request.
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel->terminate($input, $status);
|
||||
// Bootstrap Laravel and handle the command...
|
||||
$status = (require_once __DIR__.'/bootstrap/app.php')
|
||||
->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
|
||||
+37
-46
@@ -1,55 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The first thing we will do is create a new Laravel application instance
|
||||
| which serves as the "glue" for all the components of Laravel, and is
|
||||
| the IoC container for the system binding all of the various parts.
|
||||
|
|
||||
*/
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
$app = new Illuminate\Foundation\Application(
|
||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||
);
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withProviders([
|
||||
\Spatie\Html\HtmlServiceProvider::class,
|
||||
])
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
channels: __DIR__.'/../routes/channels.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->redirectGuestsTo(fn () => route('login'));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bind Important Interfaces
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, we need to bind some important interfaces into the container so
|
||||
| we will be able to resolve them when needed. The kernels serve the
|
||||
| incoming requests to this application from both the web and CLI.
|
||||
|
|
||||
*/
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
//
|
||||
'order',
|
||||
'appload',
|
||||
'test_config',
|
||||
//'get_stats'
|
||||
]);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Http\Kernel::class,
|
||||
App\Http\Kernel::class
|
||||
);
|
||||
$middleware->append(\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Console\Kernel::class,
|
||||
App\Console\Kernel::class
|
||||
);
|
||||
$middleware->throttleApi('60,1');
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||
App\Exceptions\Handler::class
|
||||
);
|
||||
$middleware->replace(\Illuminate\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TrustProxies::class);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Return The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This script returns the application instance. The instance is given to
|
||||
| the calling script so we can separate the building of the instances
|
||||
| from the actual running of the application and sending responses.
|
||||
|
|
||||
*/
|
||||
$middleware->trustHosts();
|
||||
$middleware->replace(\Illuminate\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustHosts::class);
|
||||
|
||||
return $app;
|
||||
$middleware->alias([
|
||||
'allowed' => \App\Http\Middleware\CheckAllowed::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
//
|
||||
})->create();
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\FormMacroServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
];
|
||||
+20
-15
@@ -8,28 +8,30 @@
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"graham-campbell/github": "^12.0",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"laravel/framework": "^10.44",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^4.2",
|
||||
"laravelcollective/html": "^6.4",
|
||||
"nunomaduro/collision": "^6.3",
|
||||
"symfony/yaml": "^6.2",
|
||||
"ext-json": "*",
|
||||
"php": "^8.4",
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"enshrined/svg-sanitize": "^0.22.0",
|
||||
"graham-campbell/github": "^13.0",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/tinker": "^3.0",
|
||||
"laravel/ui": "^4.4",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
"nunomaduro/collision": "^8.0",
|
||||
"phrity/websocket": "^3.6",
|
||||
"spatie/laravel-html": "^3.11",
|
||||
"spatie/laravel-ignition": "^2.4",
|
||||
"symfony/yaml": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^2.13",
|
||||
"barryvdh/laravel-ide-helper": "^3.0",
|
||||
"filp/whoops": "^2.8",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"phpunit/phpunit": "^9.5.10",
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"squizlabs/php_codesniffer": "3.*",
|
||||
"symfony/thanks": "^1.2",
|
||||
"fakerphp/faker": "^1.9.1"
|
||||
"fakerphp/faker": "^1.23"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
@@ -83,6 +85,9 @@
|
||||
"kylekatarnls/update-helper": true,
|
||||
"symfony/thanks": true,
|
||||
"php-http/discovery": true
|
||||
},
|
||||
"platform": {
|
||||
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
|
||||
Generated
+3267
-2584
File diff suppressed because it is too large
Load Diff
+13
-176
@@ -5,191 +5,28 @@ use Illuminate\Support\Facades\Facade;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application. This value is used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| any other location as required by the application or its packages.
|
||||
|
|
||||
*/
|
||||
'version' => '2.8.1',
|
||||
|
||||
'name' => env('APP_NAME', 'Heimdall'),
|
||||
'version' => '2.6.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services your application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| your application so that it is used when running Artisan tasks.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
'appsource' => env('APP_SOURCE', 'https://appslist.heimdall.site/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. We have gone
|
||||
| ahead and set this to a sensible default for you out of the box.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by the translation service provider. You are free to set this value
|
||||
| to any of the locales which will be supported by the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => 'en',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Fallback Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The fallback locale determines the locale to use when the current one
|
||||
| is not available. You may change the value to correspond to any of
|
||||
| the language folders that are provided through your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'fallback_locale' => 'en',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Faker Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This locale will be used by the Faker PHP library when generating fake
|
||||
| data for your database seeds. For example, this will be used to get
|
||||
| localized telephone numbers, street address information and more.
|
||||
|
|
||||
*/
|
||||
|
||||
'faker_locale' => 'en_US',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is used by the Illuminate encrypter service and should be set
|
||||
| to a random, 32 character string, otherwise these encrypted strings
|
||||
| will not be safe. Please do this before deploying an application!
|
||||
|
|
||||
*/
|
||||
|
||||
'key' => env('APP_KEY', 'base64:I206O8ibx+GQyRE7BeOxDobn04Mfmyyc5Ptzns/C0mY='),
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => 'file',
|
||||
// 'store' => 'redis',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Autoloaded Service Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The service providers listed here will be automatically loaded on the
|
||||
| request to your application. Feel free to add your own services to
|
||||
| this array to grant expanded functionality to your applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => ServiceProvider::defaultProviders()->merge([
|
||||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
Collective\Html\HtmlServiceProvider::class,
|
||||
/*
|
||||
* Application Service Providers...
|
||||
*/
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
])->toArray(),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Aliases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array of class aliases will be registered when this application
|
||||
| is started. However, feel free to register as many as you wish as
|
||||
| the aliases are "lazy" loaded so they don't hinder performance.
|
||||
|
|
||||
*/
|
||||
'allow_internal_requests' => env('ALLOW_INTERNAL_REQUESTS', false),
|
||||
|
||||
'aliases' => Facade::defaultAliases()->merge([
|
||||
'EnhancedApps' => App\EnhancedApps::class,
|
||||
'Form' => Collective\Html\FormFacade::class,
|
||||
'Html' => Collective\Html\HtmlFacade::class,
|
||||
'Form' => App\Facades\Form::class,
|
||||
'Redis' => Illuminate\Support\Facades\Redis::class,
|
||||
'SupportedApps' => App\SupportedApps::class,
|
||||
'Yaml' => Symfony\Component\Yaml\Yaml::class,
|
||||
])->toArray(),
|
||||
|
||||
'auth_roles_enable' => (bool) env('AUTH_ROLES_ENABLE', false),
|
||||
|
||||
'auth_roles_header' => env('AUTH_ROLES_HEADER', 'remote-groups'),
|
||||
|
||||
'auth_roles_http_header' => env('AUTH_ROLES_HTTP_HEADER', 'HTTP_REMOTE_GROUPS'),
|
||||
|
||||
'auth_roles_admin' => env('AUTH_ROLES_ADMIN', 'admin'),
|
||||
|
||||
'auth_roles_delimiter' => env('AUTH_ROLES_DELIMITER', ','),
|
||||
|
||||
];
|
||||
|
||||
+1
-103
@@ -2,120 +2,18 @@
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| here which uses session storage and the Eloquent user provider.
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'token',
|
||||
'provider' => 'users',
|
||||
'hash' => false,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| sources which represent each model / table. These sources may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may specify multiple password reset configurations if you have more
|
||||
| than one user table or model in the application and you want to have
|
||||
| separate password reset settings based on the specific user types.
|
||||
|
|
||||
| The expiry time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => 'password_reset_tokens',
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
'model' => App\User::class, // Update this to the correct namespace
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| times out and the user is prompted to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
];
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Broadcaster
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default broadcaster that will be used by the
|
||||
| framework when an event needs to be broadcast. You may set this to
|
||||
| any of the connections defined in the "connections" array below.
|
||||
|
|
||||
| Supported: "pusher", "ably", "redis", "log", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('BROADCAST_DRIVER', 'null'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Broadcast Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the broadcast connections that will be used
|
||||
| to broadcast events to other systems or over websockets. Samples of
|
||||
| each available type of connection are provided inside this array.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'pusher' => [
|
||||
'driver' => 'pusher',
|
||||
'key' => env('PUSHER_APP_KEY'),
|
||||
'secret' => env('PUSHER_APP_SECRET'),
|
||||
'app_id' => env('PUSHER_APP_ID'),
|
||||
'options' => [
|
||||
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'encrypted' => true,
|
||||
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||
],
|
||||
],
|
||||
|
||||
'ably' => [
|
||||
'driver' => 'ably',
|
||||
'key' => env('ABLY_KEY'),
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'driver' => 'log',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache connection that gets used while
|
||||
| using this caching library. This connection is used when another is
|
||||
| not explicitly specified when executing a given caching function.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_DRIVER', 'file'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "apc", "array", "database", "file",
|
||||
| "memcached", "redis", "dynamodb", "octane", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'apc' => [
|
||||
'driver' => 'apc',
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'cache',
|
||||
'connection' => null,
|
||||
'lock_connection' => null,
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
'lock_path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'lock_connection' => 'default',
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
|
||||
| stores there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||
|
||||
];
|
||||
+10
-135
@@ -1,151 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for all database work. Of course
|
||||
| you may use many connections at once using the Database library.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'mysql'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here are each of the database connections setup for your application.
|
||||
| Of course, examples of configuring each database platform that is
|
||||
| supported by Laravel is shown below to make development simple.
|
||||
|
|
||||
|
|
||||
| All database work in Laravel is done through the PHP PDO facilities
|
||||
| so make sure you have the driver for your particular database of
|
||||
| choice installed on your machine before you begin development.
|
||||
|
|
||||
*/
|
||||
'default' => env('DB_CONNECTION', 'sqlite'), // Make sure the default connection is set
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'database' => database_path(env('DB_DATABASE', 'database.sqlite')),
|
||||
// Use the correct path, but let the special in-memory identifier
|
||||
// pass through untouched so tests can run against ':memory:'.
|
||||
'database' => env('DB_DATABASE', 'app.sqlite') === ':memory:'
|
||||
? ':memory:'
|
||||
: database_path(env('DB_DATABASE', 'app.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), // Enable foreign key constraints
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DATABASE_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run in the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => 'migrations',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
],
|
||||
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => false, // disable to preserve original behavior for existing applications
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -2,77 +2,14 @@
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application. Just store away!
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure as many filesystem "disks" as you wish, and you
|
||||
| may even configure multiple disks of the same driver. Defaults have
|
||||
| been set up for each driver as an example of the required values.
|
||||
|
|
||||
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app'),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Hash Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default hash driver that will be used to hash
|
||||
| passwords for your application. By default, the bcrypt algorithm is
|
||||
| used; however, you remain free to modify this option if you wish.
|
||||
|
|
||||
| Supported: "bcrypt", "argon", "argon2id"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'bcrypt',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bcrypt Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Bcrypt algorithm. This will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'bcrypt' => [
|
||||
'rounds' => env('BCRYPT_ROUNDS', 12),
|
||||
'verify' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Argon Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Argon algorithm. These will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'argon' => [
|
||||
'memory' => 65536,
|
||||
'threads' => 1,
|
||||
'time' => 4,
|
||||
'verify' => true,
|
||||
],
|
||||
|
||||
];
|
||||
-112
@@ -2,127 +2,15 @@
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send any email
|
||||
| messages sent by your application. Alternative mailers may be setup
|
||||
| and used as needed; however, this mailer will be used by default.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'smtp'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers to be used while
|
||||
| sending an e-mail. You will specify which one you are using for your
|
||||
| mailers below. You are free to add additional mailers as required.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "log", "array", "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => null,
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'mailgun' => [
|
||||
'transport' => 'mailgun',
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
|
||||
'roundrobin' => [
|
||||
'transport' => 'roundrobin',
|
||||
'mailers' => [
|
||||
'ses',
|
||||
'postmark',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all e-mails sent by your application to be sent from
|
||||
| the same address. Here, you may specify a name and address that is
|
||||
| used globally for all e-mails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Markdown Mail Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you are using Markdown based email rendering, you may configure your
|
||||
| theme and component paths here, allowing you to customize the design
|
||||
| of the emails. Or, you may simply stick with the Laravel defaults!
|
||||
|
|
||||
*/
|
||||
|
||||
'markdown' => [
|
||||
'theme' => 'default',
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue API supports an assortment of back-ends via a single
|
||||
| API, giving you convenient access to each back-end using the same
|
||||
| syntax for every one. Here you may define a default connection.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'sync'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection information for each server that
|
||||
| is used by your application. A default configuration has been added
|
||||
| for each back-end shipped with Laravel. You are free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'jobs',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => 'localhost',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => 90,
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control which database and table are used to store the jobs that
|
||||
| have failed. You may change them to any database / table you wish.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'mysql'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -2,18 +2,6 @@
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'mailgun' => [
|
||||
'domain' => env('MAILGUN_DOMAIN'),
|
||||
'secret' => env('MAILGUN_SECRET'),
|
||||
@@ -21,14 +9,4 @@ return [
|
||||
'scheme' => 'https',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_TOKEN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default session "driver" that will be used on
|
||||
| requests. By default, we will use the lightweight native driver but
|
||||
| you may specify any of the other wonderful drivers provided here.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "apc",
|
||||
| "memcached", "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'file'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to immediately expire on the browser closing, set that option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it is stored. All encryption will be run
|
||||
| automatically by Laravel and you can use the Session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the native session driver, we need a location where session
|
||||
| files may be stored. A default has been set for you but a different
|
||||
| location may be specified. This is only needed for file sessions.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table we
|
||||
| should use to manage the sessions. Of course, a sensible default is
|
||||
| provided for you; however, you are free to change this as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => 'sessions',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While using one of the framework's cache driven session backends you may
|
||||
| list a cache store that should be used for these sessions. This value
|
||||
| must match with one of the application's configured cache "stores".
|
||||
|
|
||||
| Affects: "apc", "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the cookie used to identify a session
|
||||
| instance by ID. The name specified here will get used every time a
|
||||
| new session cookie is created by the framework for every driver.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application but you are free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => '/',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the domain of the cookie used to identify a session
|
||||
| in your application. This will determine which domains the cookie is
|
||||
| available to in your application. A sensible default has been set.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. You are free to modify this option if needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" since this is a secure default value.
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Partitioned Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will tie the cookie to the top-level site for
|
||||
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||
|
|
||||
*/
|
||||
|
||||
'partitioned' => false,
|
||||
|
||||
];
|
||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('items', function (Blueprint $table) {
|
||||
$table->text('role')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('items', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
Regular → Executable
@@ -0,0 +1,35 @@
|
||||
<?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('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* Reassign items whose user_id references a user that no longer exists to a
|
||||
* surviving user, so previously orphaned tiles and tags become visible again.
|
||||
* user_id = 0 means "shared with all users" and is never treated as orphaned.
|
||||
*
|
||||
* The DB query builder is used directly (not the Item model) so the global
|
||||
* scope and soft-delete constraints don't interfere.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Prefer user id 1 if it still exists, otherwise the lowest existing id.
|
||||
$target = DB::table('users')->where('id', 1)->value('id')
|
||||
?? DB::table('users')->min('id');
|
||||
|
||||
// No users left: nothing to reassign to.
|
||||
if ($target === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('items')
|
||||
->where('user_id', '!=', 0)
|
||||
->whereNotIn('user_id', function ($query) {
|
||||
$query->select('id')->from('users');
|
||||
})
|
||||
->update(['user_id' => $target]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* This is a data migration; the original ownership cannot be recovered.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
Regular → Executable
Regular → Executable
+28
@@ -350,5 +350,33 @@ class SettingsSeeder extends Seeder
|
||||
$setting->save();
|
||||
}
|
||||
|
||||
if (! $setting = Setting::find(15)) {
|
||||
$setting = new Setting;
|
||||
$setting->id = 15;
|
||||
$setting->group_id = 4;
|
||||
$setting->key = 'default_tag';
|
||||
$setting->type = 'select';
|
||||
$setting->label = 'app.settings.default_tag';
|
||||
$setting->value = '';
|
||||
$setting->save();
|
||||
} else {
|
||||
$setting->group_id = 4;
|
||||
$setting->label = 'app.settings.default_tag';
|
||||
$setting->save();
|
||||
}
|
||||
|
||||
if (! $setting = Setting::find(16)) {
|
||||
$setting = new Setting;
|
||||
$setting->id = 16;
|
||||
$setting->group_id = 4;
|
||||
$setting->key = 'skip_tls_verification';
|
||||
$setting->type = 'boolean';
|
||||
$setting->label = 'app.settings.skip_tls_verification';
|
||||
$setting->value = '0';
|
||||
$setting->save();
|
||||
} else {
|
||||
$setting->label = 'app.settings.skip_tls_verification';
|
||||
$setting->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+1
@@ -18,6 +18,7 @@ class UsersSeeder extends Seeder
|
||||
$user->username = 'admin';
|
||||
$user->email = 'admin@test.com';
|
||||
$user->password = null;
|
||||
$user->public_front = 0; // Default value for public_front
|
||||
$user->save();
|
||||
|
||||
$user_id = $user->id;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
version: "3"
|
||||
services:
|
||||
nginx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: nginx/Dockerfile
|
||||
ports:
|
||||
- "8080:80"
|
||||
networks:
|
||||
- internal
|
||||
volumes:
|
||||
- ../:/var/www/html
|
||||
php:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: php/Dockerfile
|
||||
networks:
|
||||
- internal
|
||||
environment:
|
||||
XDEBUG_MODE: debug
|
||||
XDEBUG_CONFIG: client_host=host.docker.internal client_port=9003
|
||||
volumes:
|
||||
- ../:/var/www/html
|
||||
networks:
|
||||
internal:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM nginx:alpine
|
||||
COPY ./default.conf /etc/nginx/conf.d
|
||||
@@ -0,0 +1,19 @@
|
||||
server {
|
||||
listen 0.0.0.0:80;
|
||||
|
||||
root /var/www/html;
|
||||
|
||||
location / {
|
||||
index index.php index.html;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass php:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM php:8.4-fpm
|
||||
|
||||
RUN pecl install xdebug \
|
||||
&& docker-php-ext-enable xdebug
|
||||
+19
-5
@@ -20,7 +20,7 @@ return array (
|
||||
'settings.language' => 'Sprache',
|
||||
'settings.reset' => 'Zurücksetzen auf Standard',
|
||||
'settings.remove' => 'Entfernen',
|
||||
'settings.search' => 'suche',
|
||||
'settings.search' => 'Suche',
|
||||
'settings.no_items' => 'Keine Elemente gefunden',
|
||||
'settings.label' => 'Bezeichnung',
|
||||
'settings.value' => 'Wert',
|
||||
@@ -28,12 +28,18 @@ return array (
|
||||
'settings.view' => 'Ansicht',
|
||||
'settings.custom_css' => 'Angepasstes CSS',
|
||||
'settings.custom_js' => 'Angepasstes JavaScript',
|
||||
'settings.treat_tags_as' => 'Tags behandeln als:',
|
||||
'settings.default_tag' => 'Standard Tag',
|
||||
'settings.folders' => 'Ordner',
|
||||
'settings.tags' => 'Tags',
|
||||
'settings.categories' => 'Kategorien',
|
||||
'settings.skip_tls_verification' => 'TLS-Überprüfung überspringen (selbstsignierte Zertifikate)',
|
||||
'options.none' => '- nicht festgelegt -',
|
||||
'options.google' => 'Google',
|
||||
'options.ddg' => 'DuckDuckGo',
|
||||
'options.bing' => 'Bing',
|
||||
'options.qwant' => 'Qwant',
|
||||
'options.startpage' => 'StartSeite',
|
||||
'options.startpage' => 'Startseite',
|
||||
'options.yes' => 'Ja',
|
||||
'options.no' => 'Nein',
|
||||
'options.nzbhydra' => 'NZBHydra',
|
||||
@@ -46,7 +52,7 @@ return array (
|
||||
'dash.pin_item' => 'Element auf dem Dashboard anheften',
|
||||
'dash.no_apps' => 'Derzeit gibt es keine angeheftete Anwendungen. :link1 oder :link2',
|
||||
'dash.link1' => 'Anwendung neu hinzufügen',
|
||||
'dash.link2' => 'anheften',
|
||||
'dash.link2' => 'Anheften',
|
||||
'dash.pinned_items' => 'Angeheftete Elemente',
|
||||
'apps.app_list' => 'Anwendungsliste',
|
||||
'apps.view_trash' => 'Ansicht Papierkorb',
|
||||
@@ -66,7 +72,7 @@ return array (
|
||||
'apps.add_tag' => 'Tag hinzufügen',
|
||||
'apps.tag_name' => 'Tag Name',
|
||||
'apps.tags' => 'Tags',
|
||||
'apps.override' => 'Fals anders zur Haupt-URL',
|
||||
'apps.override' => 'Falls anders zur Haupt-URL',
|
||||
'apps.preview' => 'Vorschau',
|
||||
'apps.apptype' => 'Anwendungstyp',
|
||||
'apps.website' => 'Webseite',
|
||||
@@ -74,6 +80,7 @@ return array (
|
||||
'apps.only_admin_account' => 'Nur mit Admin-Konto!',
|
||||
'apps.autologin_url' => 'Auto Login URL',
|
||||
'apps.show_deleted' => 'Gelöschte Anwendung anzeigen',
|
||||
'app.import' => 'Importieren',
|
||||
'dashboard' => 'Home Dashboard',
|
||||
'user.user_list' => 'Nutzer',
|
||||
'user.add_user' => 'Nutzer hinzufügen',
|
||||
@@ -81,13 +88,15 @@ return array (
|
||||
'user.avatar' => 'Avatar',
|
||||
'user.email' => 'Email',
|
||||
'user.password_confirm' => 'Passwort bestätigen',
|
||||
'user.secure_front' => 'Öffentlichen Zugang erlauben - Tritt nur bei gesetztem Passwort in kraft.',
|
||||
'user.secure_front' => 'Öffentlichen Zugang erlauben - Tritt nur bei gesetztem Passwort in Kraft.',
|
||||
'user.autologin' => 'Anmelden von spezieller URL erlauben. Jeder mit diesem Link kann sich anmelden.',
|
||||
'url' => 'URL',
|
||||
'title' => 'Titel',
|
||||
'delete' => 'Löschen',
|
||||
'optional' => 'Optional',
|
||||
'restore' => 'Wiederherstellen',
|
||||
'export' => 'Exportieren',
|
||||
'import' => 'Importieren',
|
||||
'alert.success.item_created' => 'Element erfolgreich erstellt',
|
||||
'alert.success.item_updated' => 'Element erfolgreich aktualisiert',
|
||||
'alert.success.item_deleted' => 'Element erfolgreich gelöscht',
|
||||
@@ -99,10 +108,15 @@ return array (
|
||||
'alert.success.tag_restored' => 'Tag erfolgreich wiederhergestellt',
|
||||
'alert.success.setting_updated' => 'Die Einstellungen wurden übernommen',
|
||||
'alert.error.not_exist' => 'Diese Einstellung existiert nicht.',
|
||||
'alert.error.file_too_big' => 'Datei zu groß',
|
||||
'alert.error.file_not_stored' => 'Datei konnte nicht gespeichert werden',
|
||||
'alert.success.user_created' => 'Nutzer erfolgreich erstellt',
|
||||
'alert.success.user_updated' => 'Nutzer erfolgreich aktualisiert',
|
||||
'alert.success.user_deleted' => 'Nutzer erfolgreich gelöscht',
|
||||
'alert.success.user_restored' => 'Nutzer erfolgreich wiederhergestellt',
|
||||
'dashboard.reorder' => 'Elemente neu anordnen und anheften',
|
||||
'dashboard.settings' => 'Einstellungen',
|
||||
'role' => 'Authentifizierungsrolle',
|
||||
'unauthorized_for_form' => 'Sie haben keinen Zugriff auf diese Seite.',
|
||||
'disabled_feature' => 'Diese Funktion ist deaktiviert.',
|
||||
);
|
||||
@@ -29,9 +29,11 @@ return array (
|
||||
'settings.custom_css' => 'Custom CSS',
|
||||
'settings.custom_js' => 'Custom JavaScript',
|
||||
'settings.treat_tags_as' => 'Treat Tags As:',
|
||||
'settings.default_tag' => 'Default tag',
|
||||
'settings.folders' => 'Folders',
|
||||
'settings.tags' => 'Tags',
|
||||
'settings.categories' => 'Categories',
|
||||
'settings.skip_tls_verification' => 'Skip TLS Verification (for self-signed certificates)',
|
||||
'options.none' => '- not set -',
|
||||
'options.google' => 'Google',
|
||||
'options.ddg' => 'DuckDuckGo',
|
||||
@@ -114,4 +116,7 @@ return array (
|
||||
'alert.success.user_restored' => 'User restored successfully',
|
||||
'dashboard.reorder' => 'Reorder and pin items',
|
||||
'dashboard.settings' => 'Settings',
|
||||
'role' => 'Authentication role',
|
||||
'unauthorized_for_form' => 'You are not authorized to view this form.',
|
||||
'disabled_feature' => 'This feature is disabled.',
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
|
||||
return array (
|
||||
'failed' => 'These credentials do not match our records.',
|
||||
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
|
||||
return array (
|
||||
'previous' => '« Previous',
|
||||
'next' => 'Next »',
|
||||
);
|
||||
@@ -4,6 +4,6 @@ return array (
|
||||
'password' => 'Passwords must be at least six characters and match the confirmation.',
|
||||
'reset' => 'Your password has been reset!',
|
||||
'sent' => 'We have e-mailed your password reset link!',
|
||||
'token' => 'This password reset token is invalid.',
|
||||
|
||||
'user' => 'We can\'t find a user with that e-mail address.',
|
||||
);
|
||||
@@ -1,191 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| as the size rules. Feel free to tweak each of these messages here.
|
||||
|
|
||||
*/
|
||||
|
||||
'accepted' => 'The :attribute field must be accepted.',
|
||||
'accepted_if' => 'The :attribute field must be accepted when :other is :value.',
|
||||
'active_url' => 'The :attribute field must be a valid URL.',
|
||||
'after' => 'The :attribute field must be a date after :date.',
|
||||
'after_or_equal' => 'The :attribute field must be a date after or equal to :date.',
|
||||
'alpha' => 'The :attribute field must only contain letters.',
|
||||
'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.',
|
||||
'alpha_num' => 'The :attribute field must only contain letters and numbers.',
|
||||
'array' => 'The :attribute field must be an array.',
|
||||
'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.',
|
||||
'before' => 'The :attribute field must be a date before :date.',
|
||||
'before_or_equal' => 'The :attribute field must be a date before or equal to :date.',
|
||||
'between' => [
|
||||
'array' => 'The :attribute field must have between :min and :max items.',
|
||||
'file' => 'The :attribute field must be between :min and :max kilobytes.',
|
||||
'numeric' => 'The :attribute field must be between :min and :max.',
|
||||
'string' => 'The :attribute field must be between :min and :max characters.',
|
||||
],
|
||||
'boolean' => 'The :attribute field must be true or false.',
|
||||
'can' => 'The :attribute field contains an unauthorized value.',
|
||||
'confirmed' => 'The :attribute field confirmation does not match.',
|
||||
'current_password' => 'The password is incorrect.',
|
||||
'date' => 'The :attribute field must be a valid date.',
|
||||
'date_equals' => 'The :attribute field must be a date equal to :date.',
|
||||
'date_format' => 'The :attribute field must match the format :format.',
|
||||
'decimal' => 'The :attribute field must have :decimal decimal places.',
|
||||
'declined' => 'The :attribute field must be declined.',
|
||||
'declined_if' => 'The :attribute field must be declined when :other is :value.',
|
||||
'different' => 'The :attribute field and :other must be different.',
|
||||
'digits' => 'The :attribute field must be :digits digits.',
|
||||
'digits_between' => 'The :attribute field must be between :min and :max digits.',
|
||||
'dimensions' => 'The :attribute field has invalid image dimensions.',
|
||||
'distinct' => 'The :attribute field has a duplicate value.',
|
||||
'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.',
|
||||
'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.',
|
||||
'email' => 'The :attribute field must be a valid email address.',
|
||||
'ends_with' => 'The :attribute field must end with one of the following: :values.',
|
||||
'enum' => 'The selected :attribute is invalid.',
|
||||
'exists' => 'The selected :attribute is invalid.',
|
||||
'extensions' => 'The :attribute field must have one of the following extensions: :values.',
|
||||
'file' => 'The :attribute field must be a file.',
|
||||
'filled' => 'The :attribute field must have a value.',
|
||||
'gt' => [
|
||||
'array' => 'The :attribute field must have more than :value items.',
|
||||
'file' => 'The :attribute field must be greater than :value kilobytes.',
|
||||
'numeric' => 'The :attribute field must be greater than :value.',
|
||||
'string' => 'The :attribute field must be greater than :value characters.',
|
||||
],
|
||||
'gte' => [
|
||||
'array' => 'The :attribute field must have :value items or more.',
|
||||
'file' => 'The :attribute field must be greater than or equal to :value kilobytes.',
|
||||
'numeric' => 'The :attribute field must be greater than or equal to :value.',
|
||||
'string' => 'The :attribute field must be greater than or equal to :value characters.',
|
||||
],
|
||||
'hex_color' => 'The :attribute field must be a valid hexadecimal color.',
|
||||
'image' => 'The :attribute field must be an image.',
|
||||
'in' => 'The selected :attribute is invalid.',
|
||||
'in_array' => 'The :attribute field must exist in :other.',
|
||||
'integer' => 'The :attribute field must be an integer.',
|
||||
'ip' => 'The :attribute field must be a valid IP address.',
|
||||
'ipv4' => 'The :attribute field must be a valid IPv4 address.',
|
||||
'ipv6' => 'The :attribute field must be a valid IPv6 address.',
|
||||
'json' => 'The :attribute field must be a valid JSON string.',
|
||||
'lowercase' => 'The :attribute field must be lowercase.',
|
||||
'lt' => [
|
||||
'array' => 'The :attribute field must have less than :value items.',
|
||||
'file' => 'The :attribute field must be less than :value kilobytes.',
|
||||
'numeric' => 'The :attribute field must be less than :value.',
|
||||
'string' => 'The :attribute field must be less than :value characters.',
|
||||
],
|
||||
'lte' => [
|
||||
'array' => 'The :attribute field must not have more than :value items.',
|
||||
'file' => 'The :attribute field must be less than or equal to :value kilobytes.',
|
||||
'numeric' => 'The :attribute field must be less than or equal to :value.',
|
||||
'string' => 'The :attribute field must be less than or equal to :value characters.',
|
||||
],
|
||||
'mac_address' => 'The :attribute field must be a valid MAC address.',
|
||||
'max' => [
|
||||
'array' => 'The :attribute field must not have more than :max items.',
|
||||
'file' => 'The :attribute field must not be greater than :max kilobytes.',
|
||||
'numeric' => 'The :attribute field must not be greater than :max.',
|
||||
'string' => 'The :attribute field must not be greater than :max characters.',
|
||||
],
|
||||
'max_digits' => 'The :attribute field must not have more than :max digits.',
|
||||
'mimes' => 'The :attribute field must be a file of type: :values.',
|
||||
'mimetypes' => 'The :attribute field must be a file of type: :values.',
|
||||
'min' => [
|
||||
'array' => 'The :attribute field must have at least :min items.',
|
||||
'file' => 'The :attribute field must be at least :min kilobytes.',
|
||||
'numeric' => 'The :attribute field must be at least :min.',
|
||||
'string' => 'The :attribute field must be at least :min characters.',
|
||||
],
|
||||
'min_digits' => 'The :attribute field must have at least :min digits.',
|
||||
'missing' => 'The :attribute field must be missing.',
|
||||
'missing_if' => 'The :attribute field must be missing when :other is :value.',
|
||||
'missing_unless' => 'The :attribute field must be missing unless :other is :value.',
|
||||
'missing_with' => 'The :attribute field must be missing when :values is present.',
|
||||
'missing_with_all' => 'The :attribute field must be missing when :values are present.',
|
||||
'multiple_of' => 'The :attribute field must be a multiple of :value.',
|
||||
'not_in' => 'The selected :attribute is invalid.',
|
||||
'not_regex' => 'The :attribute field format is invalid.',
|
||||
'numeric' => 'The :attribute field must be a number.',
|
||||
'password' => [
|
||||
'letters' => 'The :attribute field must contain at least one letter.',
|
||||
'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.',
|
||||
'numbers' => 'The :attribute field must contain at least one number.',
|
||||
'symbols' => 'The :attribute field must contain at least one symbol.',
|
||||
'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.',
|
||||
],
|
||||
'present' => 'The :attribute field must be present.',
|
||||
'present_if' => 'The :attribute field must be present when :other is :value.',
|
||||
'present_unless' => 'The :attribute field must be present unless :other is :value.',
|
||||
'present_with' => 'The :attribute field must be present when :values is present.',
|
||||
'present_with_all' => 'The :attribute field must be present when :values are present.',
|
||||
'prohibited' => 'The :attribute field is prohibited.',
|
||||
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.',
|
||||
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.',
|
||||
'prohibits' => 'The :attribute field prohibits :other from being present.',
|
||||
'regex' => 'The :attribute field format is invalid.',
|
||||
'required' => 'The :attribute field is required.',
|
||||
'required_array_keys' => 'The :attribute field must contain entries for: :values.',
|
||||
'required_if' => 'The :attribute field is required when :other is :value.',
|
||||
'required_if_accepted' => 'The :attribute field is required when :other is accepted.',
|
||||
'required_unless' => 'The :attribute field is required unless :other is in :values.',
|
||||
'required_with' => 'The :attribute field is required when :values is present.',
|
||||
'required_with_all' => 'The :attribute field is required when :values are present.',
|
||||
'required_without' => 'The :attribute field is required when :values is not present.',
|
||||
'required_without_all' => 'The :attribute field is required when none of :values are present.',
|
||||
'same' => 'The :attribute field must match :other.',
|
||||
'size' => [
|
||||
'array' => 'The :attribute field must contain :size items.',
|
||||
'file' => 'The :attribute field must be :size kilobytes.',
|
||||
'numeric' => 'The :attribute field must be :size.',
|
||||
'string' => 'The :attribute field must be :size characters.',
|
||||
],
|
||||
'starts_with' => 'The :attribute field must start with one of the following: :values.',
|
||||
'string' => 'The :attribute field must be a string.',
|
||||
'timezone' => 'The :attribute field must be a valid timezone.',
|
||||
'unique' => 'The :attribute has already been taken.',
|
||||
'uploaded' => 'The :attribute failed to upload.',
|
||||
'uppercase' => 'The :attribute field must be uppercase.',
|
||||
'url' => 'The :attribute field must be a valid URL.',
|
||||
'ulid' => 'The :attribute field must be a valid ULID.',
|
||||
'uuid' => 'The :attribute field must be a valid UUID.',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => [
|
||||
'attribute-name' => [
|
||||
'rule-name' => 'custom-message',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap our attribute placeholder
|
||||
| with something more reader friendly such as "E-Mail Address" instead
|
||||
| of "email". This simply helps us make our message more expressive.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => [],
|
||||
|
||||
];
|
||||
+35
-26
@@ -4,23 +4,23 @@ return array (
|
||||
'settings.system' => 'Sistema',
|
||||
'settings.appearance' => 'Apariencia',
|
||||
'settings.miscellaneous' => 'Miscelánea',
|
||||
'settings.advanced' => 'Avanzada',
|
||||
'settings.advanced' => 'Avanzado',
|
||||
'settings.support' => 'Soporte',
|
||||
'settings.donate' => 'Donar',
|
||||
'settings.version' => 'Versión',
|
||||
'settings.background_image' => 'Imagen de Fondo',
|
||||
'settings.trianglify' => 'Trianglify',
|
||||
'settings.trianglify_seed' => 'Trianglify Random Seed',
|
||||
'settings.window_target' => 'Vínculo abre en',
|
||||
'settings.window_target.current' => 'Abrir en ésta pestaña',
|
||||
'settings.trianglify' => 'Patrón de triángulos',
|
||||
'settings.trianglify_seed' => 'Semilla Aleatoria del Patrón de Triángulos',
|
||||
'settings.window_target' => 'Abrir enlace en',
|
||||
'settings.window_target.current' => 'Abrir en esta pestaña',
|
||||
'settings.window_target.one' => 'Abrir en la misma pestaña',
|
||||
'settings.window_target.new' => 'Abrir en una nueva pestaña',
|
||||
'settings.homepage_search' => 'Página de Inicio de Búsqueda',
|
||||
'settings.homepage_search' => 'Búsqueda en Página de Inicio',
|
||||
'settings.search_provider' => 'Proveedor de búsqueda',
|
||||
'settings.language' => 'Idioma',
|
||||
'settings.reset' => 'Restablecer a predeterminado',
|
||||
'settings.remove' => 'Quitar',
|
||||
'settings.search' => 'búsqueda',
|
||||
'settings.search' => 'buscar',
|
||||
'settings.no_items' => 'No se encontraron elementos',
|
||||
'settings.label' => 'Etiqueta',
|
||||
'settings.value' => 'Valor',
|
||||
@@ -28,6 +28,10 @@ return array (
|
||||
'settings.view' => 'Ver',
|
||||
'settings.custom_css' => 'CSS Personalizado',
|
||||
'settings.custom_js' => 'JavaScript Personalizado',
|
||||
'settings.treat_tags_as' => 'Tratar etiquetas como:',
|
||||
'settings.folders' => 'Carpetas',
|
||||
'settings.tags' => 'Etiquetas',
|
||||
'settings.categories' => 'Categorías',
|
||||
'options.none' => '- no establecido -',
|
||||
'options.google' => 'Google',
|
||||
'options.ddg' => 'DuckDuckGo',
|
||||
@@ -42,38 +46,39 @@ return array (
|
||||
'buttons.cancel' => 'Cancelar',
|
||||
'buttons.add' => 'Añadir',
|
||||
'buttons.upload' => 'Cargar un archivo',
|
||||
'buttons.downloadapps' => 'Actualizar lista de Applicaciones',
|
||||
'dash.pin_item' => 'Pin elemento al tablero',
|
||||
'buttons.downloadapps' => 'Actualizar Lista de Aplicaciones',
|
||||
'dash.pin_item' => 'Anclar elemento al tablero',
|
||||
'dash.no_apps' => 'Actualmente no hay aplicaciones ancladas, :link1 o :link2',
|
||||
'dash.link1' => 'Agregue una aplicación aquí',
|
||||
'dash.link2' => 'Pin de un elemento en el tablero',
|
||||
'dash.link2' => 'Anclar un elemento en el tablero',
|
||||
'dash.pinned_items' => 'Elementos Anclados',
|
||||
'apps.app_list' => 'Lista de aplicaciones',
|
||||
'apps.app_list' => 'Listado de aplicaciones',
|
||||
'apps.view_trash' => 'Vista de la papelera de reciclaje',
|
||||
'apps.add_application' => 'Agregar aplicación',
|
||||
'apps.application_name' => 'Nombre de la aplicación',
|
||||
'apps.colour' => 'Color',
|
||||
'apps.icon' => 'Icono',
|
||||
'apps.pinned' => 'Fijado',
|
||||
'apps.pinned' => 'Anclado',
|
||||
'apps.title' => 'Título',
|
||||
'apps.hex' => 'Código de color hexadecimal',
|
||||
'apps.username' => 'Nombre de usuario',
|
||||
'apps.password' => 'Contraseña',
|
||||
'apps.config' => 'Config',
|
||||
'apps.config' => 'Configuración',
|
||||
'apps.apikey' => 'Clave de API',
|
||||
'apps.enable' => 'Habilitar',
|
||||
'apps.tag_list' => 'Lista de Etiquetas',
|
||||
'apps.tag_list' => 'Listado de etiquetas',
|
||||
'apps.add_tag' => 'Añadir Etiqueta',
|
||||
'apps.tag_name' => 'Nombre de Etiqueta',
|
||||
'apps.tags' => 'Etiquetas',
|
||||
'apps.override' => 'Si es differente de la URL principal',
|
||||
'apps.override' => 'Si es diferente de la URL principal',
|
||||
'apps.preview' => 'Previsualizar',
|
||||
'apps.apptype' => 'Tipo de Aplicación',
|
||||
'apps.website' => 'Sitio Web',
|
||||
'apps.description' => 'Descripción',
|
||||
'apps.only_admin_account' => '¡Solo si tienes cuenta de administrador!',
|
||||
'apps.autologin_url' => 'URL de auto inicio de sessión',
|
||||
'apps.autologin_url' => 'URL de inicio de sesión automático',
|
||||
'apps.show_deleted' => 'Mostrando Aplicaciones Eliminadas',
|
||||
'apps.import' => 'Importar',
|
||||
'dashboard' => 'Tablero Inicial',
|
||||
'user.user_list' => 'Usuarios',
|
||||
'user.add_user' => 'Añadir Usuario',
|
||||
@@ -82,27 +87,31 @@ return array (
|
||||
'user.email' => 'Correo Electrónico',
|
||||
'user.password_confirm' => 'Confirmar Contraseña',
|
||||
'user.secure_front' => 'Permitir acceso público a la interfaz - Forzado solo si una contraseña está definida.',
|
||||
'user.autologin' => 'Permitir inicio de sesión desde una URL específica. Cualquiera con el vínculo puede iniciar sesión.',
|
||||
'user.autologin' => 'Permitir inicio de sesión desde una URL específica. Cualquiera con el enlace puede iniciar sesión.',
|
||||
'url' => 'Url',
|
||||
'title' => 'Título',
|
||||
'delete' => 'Borrar',
|
||||
'optional' => 'Opcional',
|
||||
'restore' => 'Restaurar',
|
||||
'export' => 'Exportar',
|
||||
'import' => 'Importar',
|
||||
'alert.success.item_created' => 'Elemento creado con éxito',
|
||||
'alert.success.item_updated' => 'Artículo actualizado con éxito',
|
||||
'alert.success.item_updated' => 'Elemento actualizado con éxito',
|
||||
'alert.success.item_deleted' => 'Elemento eliminado correctamente',
|
||||
'alert.success.item_restored' => 'Elemento restaurado con éxito',
|
||||
'alert.success.updating' => 'Actualizando lista de aplicaciones',
|
||||
'alert.success.tag_created' => 'Etiqueta creada exitósamente',
|
||||
'alert.success.tag_updated' => 'Etiqueta actualizada exitósamente',
|
||||
'alert.success.tag_deleted' => 'Etiqueta eliminada exitósamente',
|
||||
'alert.success.tag_restored' => 'Etiqueta restaurada exitósamente',
|
||||
'alert.success.setting_updated' => 'Ha editado con éxito esta configuración',
|
||||
'alert.success.tag_updated' => 'Etiqueta actualizada exitosamente',
|
||||
'alert.success.tag_deleted' => 'Etiqueta eliminada exitosamente',
|
||||
'alert.success.tag_restored' => 'Etiqueta restaurada exitosamente',
|
||||
'alert.success.setting_updated' => 'Configuración editada con éxito',
|
||||
'alert.error.not_exist' => 'Esta configuración no existe.',
|
||||
'alert.success.user_created' => 'Usuario creado exitósamente',
|
||||
'alert.success.user_updated' => 'Usuario actualizado exitósamente',
|
||||
'alert.success.user_deleted' => 'Usuario eliminado exitósamente',
|
||||
'alert.success.user_restored' => 'Usuario restaurado exitósamente',
|
||||
'alert.error.file_too_big' => 'El fichero es demasiado grande.',
|
||||
'alert.error.file_not_stored' => 'El fichero no se ha podido almacenar.',
|
||||
'alert.success.user_created' => 'Usuario creado exitosamente',
|
||||
'alert.success.user_updated' => 'Usuario actualizado exitosamente',
|
||||
'alert.success.user_deleted' => 'Usuario eliminado exitosamente',
|
||||
'alert.success.user_restored' => 'Usuario restaurado exitosamente',
|
||||
'dashboard.reorder' => 'Reordenar y anclar elementos',
|
||||
'dashboard.settings' => 'Ajustes',
|
||||
);
|
||||
@@ -93,7 +93,7 @@ return [
|
||||
'timezone' => ':attribute は有効なゾーンでなければなりません。',
|
||||
'unique' => ':attribute は既に取得されています。',
|
||||
'uploaded' => ':attribute のアップロードに失敗しました。',
|
||||
'url' => ':attribute 形式が無効です。'
|
||||
'url' => ':attribute 形式が無効です。',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
return array (
|
||||
'settings.system' => 'Sistem',
|
||||
'settings.appearance' => 'Izgled',
|
||||
'settings.miscellaneous' => 'Razno',
|
||||
'settings.advanced' => 'Napredno',
|
||||
'settings.support' => 'Podrška',
|
||||
'settings.donate' => 'Doniraj',
|
||||
'settings.version' => 'Verzija',
|
||||
'settings.background_image' => 'Slika u pozadini',
|
||||
'settings.trianglify' => 'Trianglify pozadine',
|
||||
'settings.trianglify_seed' => 'Trianglify Random Seed',
|
||||
'settings.window_target' => 'Linkovi se otvaraju',
|
||||
'settings.window_target.current' => 'U ovom tab-u',
|
||||
'settings.window_target.one' => 'U istom tab-u',
|
||||
'settings.window_target.new' => 'U novom tab-u',
|
||||
'settings.homepage_search' => 'Pretraga na početnoj strani',
|
||||
'settings.search_provider' => 'Podrazumevani pretraživač',
|
||||
'settings.language' => 'Jezik',
|
||||
'settings.reset' => 'Vraćanje na podrazumevane vrednosti',
|
||||
'settings.remove' => 'Ukloni',
|
||||
'settings.search' => 'pretraga',
|
||||
'settings.no_items' => 'Nije pronađen ni jedan rezultat',
|
||||
'settings.label' => 'Labela',
|
||||
'settings.value' => 'Vrednost',
|
||||
'settings.edit' => 'Izmeni',
|
||||
'settings.view' => 'Pregled',
|
||||
'settings.custom_css' => 'Ručni CSS',
|
||||
'settings.custom_js' => 'Ručni JavaScript',
|
||||
'settings.treat_tags_as' => 'Tretiraj Oznake Kao:',
|
||||
'settings.folders' => 'Fascikle',
|
||||
'settings.tags' => 'Oznake',
|
||||
'settings.categories' => 'Kategorije',
|
||||
'options.none' => '- nepodešeno -',
|
||||
'options.google' => 'Google',
|
||||
'options.ddg' => 'DuckDuckGo',
|
||||
'options.bing' => 'Bing',
|
||||
'options.qwant' => 'Qwant',
|
||||
'options.startpage' => 'StartPage',
|
||||
'options.yes' => 'Da',
|
||||
'options.no' => 'Ne',
|
||||
'options.nzbhydra' => 'NZBHydra',
|
||||
'options.jackett' => 'Jackett',
|
||||
'buttons.save' => 'Sačuvaj',
|
||||
'buttons.cancel' => 'Otkaži',
|
||||
'buttons.add' => 'Dodaj',
|
||||
'buttons.upload' => 'Učitaj ikonu',
|
||||
'buttons.downloadapps' => 'Ažuriraj Listu Aplikacija',
|
||||
'dash.pin_item' => 'Zakači na Dashboard',
|
||||
'dash.no_apps' => 'Trenutno nema zakačenih aplikacija, :link1 or :link2',
|
||||
'dash.link1' => 'Dodaj aplikaciju ovde',
|
||||
'dash.link2' => 'Zakači stavku na Dashboard',
|
||||
'dash.pinned_items' => 'Zakačene Stavke',
|
||||
'apps.app_list' => 'Lista aplikacija',
|
||||
'apps.view_trash' => 'Pogledaj smeće',
|
||||
'apps.add_application' => 'Dodaj aplikaciju',
|
||||
'apps.application_name' => 'Naziv aplikacije',
|
||||
'apps.colour' => 'Boja',
|
||||
'apps.icon' => 'Ikona',
|
||||
'apps.pinned' => 'Zakačeno',
|
||||
'apps.title' => 'Naziv',
|
||||
'apps.hex' => 'Hexadecimalna boja',
|
||||
'apps.username' => 'Korisnik',
|
||||
'apps.password' => 'Šifra',
|
||||
'apps.config' => 'Konfiguracija',
|
||||
'apps.apikey' => 'API Ključ',
|
||||
'apps.enable' => 'Omogući',
|
||||
'apps.tag_list' => 'Lista oznaka',
|
||||
'apps.add_tag' => 'Dodaj oznaku',
|
||||
'apps.tag_name' => 'Naziv oznake',
|
||||
'apps.tags' => 'Oznake',
|
||||
'apps.override' => 'Ukoliko je različito od glavnom url-a',
|
||||
'apps.preview' => 'Prikaz',
|
||||
'apps.apptype' => 'Tip Aplikacije',
|
||||
'apps.website' => 'Web sajt',
|
||||
'apps.description' => 'Opis',
|
||||
'apps.only_admin_account' => 'Samo ako imate administratorski nalog!',
|
||||
'apps.autologin_url' => 'Url za automatsko logovanje',
|
||||
'apps.show_deleted' => 'Prikazivanje obrisanih aplikacija',
|
||||
'app.import' => 'Uvoz',
|
||||
'dashboard' => 'Početni Dashboard',
|
||||
'user.user_list' => 'Korisnici',
|
||||
'user.add_user' => 'Dodaj korisnika',
|
||||
'user.username' => 'Korisnik',
|
||||
'user.avatar' => 'Slika',
|
||||
'user.email' => 'E-mail',
|
||||
'user.password_confirm' => 'Potvrdi šifu',
|
||||
'user.secure_front' => 'Dozvoli javni pristup početnoj stranici - Forsira se samo ako je podešena šifra.',
|
||||
'user.autologin' => 'Dozvoli logovanje sa specifičnog URL-a. Bilo ko sa linkom se može ulogovati.',
|
||||
'url' => 'URL',
|
||||
'title' => 'Naziv',
|
||||
'delete' => 'Obriši',
|
||||
'optional' => 'Opcionalno',
|
||||
'restore' => 'Povrati',
|
||||
'export' => 'Izvezi',
|
||||
'import' => 'Uvezi',
|
||||
'alert.success.item_created' => 'Stavka uspešno kreirana',
|
||||
'alert.success.item_updated' => 'Stavka uspešno ažurirana',
|
||||
'alert.success.item_deleted' => 'Stavka uspešno obrisana',
|
||||
'alert.success.item_restored' => 'Stavka uspešno povraćena',
|
||||
'alert.success.updating' => 'Ažuriranje liste aplikacija',
|
||||
'alert.success.tag_created' => 'Oznaka uspešno kreirana',
|
||||
'alert.success.tag_updated' => 'Oznaka uspešno ažurirana',
|
||||
'alert.success.tag_deleted' => 'Oznaka uspešno obrisana',
|
||||
'alert.success.tag_restored' => 'Oznaka uspešno povraćena',
|
||||
'alert.success.setting_updated' => 'Uspešno ste izmenili ovo podešavanje',
|
||||
'alert.error.not_exist' => 'Ovo podešavanje ne postoji.',
|
||||
'alert.error.file_too_big' => 'Prevelik fajl.',
|
||||
'alert.error.file_not_stored' => 'Nije moguće učitati fajl.',
|
||||
'alert.success.user_created' => 'Korisnik uspešno kreiran',
|
||||
'alert.success.user_updated' => 'Korisnik uspešnop ažuriran',
|
||||
'alert.success.user_deleted' => 'Korisnik uspešno obrisan',
|
||||
'alert.success.user_restored' => 'Korisnik uspešno povraćen',
|
||||
'dashboard.reorder' => 'Preuredi i zakači stavke',
|
||||
'dashboard.settings' => 'Podešavanja',
|
||||
);
|
||||
Generated
+4231
-2716
File diff suppressed because it is too large
Load Diff
+36
-7
@@ -2,12 +2,12 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "npm run development",
|
||||
"development": "mix",
|
||||
"watch": "mix watch",
|
||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"development": "npx mix",
|
||||
"watch": "npx mix watch",
|
||||
"watch-poll": "npx mix watch -- --watch-options-poll=1000",
|
||||
"hot": "npx mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production",
|
||||
"production": "npx mix --production",
|
||||
"lint": "eslint 'resources/assets/js/*'"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -21,10 +21,39 @@
|
||||
"laravel-mix": "^6.0.49",
|
||||
"prettier": "^2.8.1",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "13.*"
|
||||
"sass-loader": "13.*",
|
||||
"webpack": "^5.100.1",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"select2": "^4.0.13",
|
||||
"select2": "~4.0.13",
|
||||
"sortablejs": "^1.15.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@babel/core": "^7.29.7",
|
||||
"@babel/plugin-transform-modules-systemjs": "^7.29.7",
|
||||
"bn.js": "^5.2.4",
|
||||
"brace-expansion": "^1.1.16",
|
||||
"fast-uri": "^3.1.3",
|
||||
"flatted": "^3.4.2",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"http-proxy-middleware": "^2.0.10",
|
||||
"immutable": "^5.1.9",
|
||||
"js-yaml": "^4.3.0",
|
||||
"launch-editor": "^2.14.1",
|
||||
"lodash": "^4.18.1",
|
||||
"minimatch": "^3.1.5",
|
||||
"node-forge": "^1.4.0",
|
||||
"on-headers": "^1.1.0",
|
||||
"path-to-regexp": "^0.1.13",
|
||||
"picomatch": "^2.3.2",
|
||||
"postcss": "^8.5.16",
|
||||
"qs": "^6.15.3",
|
||||
"serialize-javascript": "^7.0.7",
|
||||
"shell-quote": "^1.9.0",
|
||||
"svgo": "^2.8.2",
|
||||
"uuid": "^11.1.1",
|
||||
"ws": "^8.21.0",
|
||||
"yaml": "^1.10.3"
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,10 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true">
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true" cacheDirectory=".phpunit.cache">
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory suffix="Test.php">./tests/Unit</directory>
|
||||
@@ -16,13 +11,18 @@
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
|
||||
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
</php>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">./app</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user