Finish 3.6
This commit is contained in:
parent
cbe894293c
commit
05ecf57822
@ -129,7 +129,6 @@
|
||||
"symfony/stopwatch": "*",
|
||||
"symfony/web-profiler-bundle": "*",
|
||||
"symplify/config-transformer": "^11.1",
|
||||
"tomasvotruba/symfony-config-generator": "^0.1.5",
|
||||
"vincentlanglet/twig-cs-fixer": "^3.5"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@ -17,6 +17,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use PSC\Shop\ContactBundle\Repository\ContactRepository;
|
||||
use PSC\Shop\OrderBundle\Service\Order;
|
||||
use PSC\Shop\QueueBundle\Service\Queue\Manager;
|
||||
use PSC\System\SettingsBundle\Service\DiskUsage;
|
||||
use PSC\System\SettingsBundle\Service\Instance;
|
||||
use PSC\System\SettingsBundle\Service\Shop;
|
||||
use PSC\System\UpdateBundle\Service\Migration;
|
||||
@ -59,6 +60,7 @@ class DashboardController extends AbstractController
|
||||
ChartBuilderInterface $chartBuilder,
|
||||
ContactRepository $contactRepository,
|
||||
Order $orderService,
|
||||
DiskUsage $diskUsage,
|
||||
) {
|
||||
// Muss vor dem ersten Laden des Shops geprüft werden: ausstehende
|
||||
// Migrationen können Spalten ergänzen, die das Shop-Entity bereits mappt
|
||||
@ -170,6 +172,7 @@ class DashboardController extends AbstractController
|
||||
'queueErrorCount' => $queueService->getErrorJobCount(),
|
||||
'instance' => $instanceService->getInstance(),
|
||||
'chart' => $chart,
|
||||
'diskUsage' => $this->isGranted('ROLE_ADMIN') ? $diskUsage->getUsage() : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
{% block body %}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 filament-widgets-container gap-4 lg:gap-8 mb-6">
|
||||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<div class="space-y-4 lg:space-y-8">
|
||||
<div class="rounded-sm border border bg-white px-7.5 py-6 shadow-lg dark:border-strokedark dark:bg-boxdark">
|
||||
<div class="p-2 space-y-2">
|
||||
<div class="flex items-center justify-between gap-8 px-4 py-2 mb-2">
|
||||
@ -28,23 +29,124 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="rounded-sm border border bg-white px-7.5 py-6 shadow-lg dark:border-strokedark dark:bg-boxdark">
|
||||
<div class="p-2 space-y-2">
|
||||
<div class="flex items-center justify-between gap-8 px-4 py-2 mb-2">
|
||||
<h2 class="text-xl font-medium tracking-tight filament-card-heading">
|
||||
<div class="rounded-sm border border bg-white px-5 py-4 shadow-lg dark:border-strokedark dark:bg-boxdark">
|
||||
<div class="px-2 pt-1 pb-2">
|
||||
<h2 class="text-lg font-medium tracking-tight filament-card-heading mb-2">
|
||||
Warnhinweise
|
||||
</h2>
|
||||
<div aria-hidden="true" class="filament-hr border-t dark:border-gray-700"></div>
|
||||
</div>
|
||||
<div aria-hidden="true" class="filament-hr border-t dark:border-gray-700"></div>
|
||||
<table class="w-full text-start divide-y table-auto text-sm">
|
||||
<tbody>
|
||||
<tr><td class="px-4 py-2">Aktionen:</td><td class="px-4 py-2"><p>{% if queueErrorCount > 0 %}<span class="bg-red-100 text-red-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">{{ queueErrorCount }}</span>{% else %}<span class="bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Alles Ok</span>{% endif %}</p></td></tr>
|
||||
<tr><td class="px-4 py-2">Mailserver:</td><td class="px-4 py-2"><p>{% if not instance.isSmtpOwn %}<span class="bg-red-100 text-red-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Kein eigener definiert</span>{% else %}{% if instance.smtpHost == '' or instance.smtpPort == '' or instance.smtpUsername == '' or instance.smtpPassword == '' %}<span class="bg-yellow-500 text-yellow-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Smtp Einstellungen überprüfen</span>{% else %}<span class="bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Alles Ok</span>{% endif %}{% endif %}</p></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<table class="w-full text-start divide-y table-auto">
|
||||
<tbody>
|
||||
<tr><td class="px-4 py-3">Aktionen:</td><td class="px-4 py-3"><p>{% if queueErrorCount > 0 %}<span class="bg-red-100 text-red-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">{{ queueErrorCount }}</span>{% else %}<span class="bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Alles Ok</span>{% endif %}</p></td></tr>
|
||||
<tr><td class="px-4 py-3">Mailserver:</td><td class="px-4 py-3"><p>{% if not instance.isSmtpOwn %}<span class="bg-red-100 text-red-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Kein eigener definiert</span>{% else %}{% if instance.smtpHost == '' or instance.smtpPort == '' or instance.smtpUsername == '' or instance.smtpPassword == '' %}<span class="bg-yellow-500 text-yellow-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Smtp Einstellungen überprüfen</span>{% else %}<span class="bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded">Alles Ok</span>{% endif %}{% endif %}</p></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if diskUsage %}
|
||||
<div class="rounded-sm border border bg-white px-5 py-4 shadow-lg dark:border-strokedark dark:bg-boxdark">
|
||||
<div class="px-2 pt-1 pb-2">
|
||||
<h2 class="text-lg font-medium tracking-tight filament-card-heading mb-2">
|
||||
Speicherverbrauch
|
||||
</h2>
|
||||
<div aria-hidden="true" class="filament-hr border-t dark:border-gray-700"></div>
|
||||
</div>
|
||||
|
||||
{# Obere Liste wird gegen das Daten-Volume (/dev/sdb) abgeglichen.
|
||||
Fallback, falls der (evtl. ältere) Cache diese Keys nicht enthält. #}
|
||||
{% set dataDisk = diskUsage.dataDisk|default({ 'total': null, 'free': null, 'used': null }) %}
|
||||
{% set items = [{ label: 'Datenbank', size: diskUsage.database }] %}
|
||||
{% for label, size in diskUsage.directories %}
|
||||
{% set items = items|merge([{ label: label, size: size }]) %}
|
||||
{% endfor %}
|
||||
{% set colors = ['bg-psc-500', 'bg-sky-500', 'bg-emerald-500', 'bg-amber-500', 'bg-violet-500', 'bg-rose-500'] %}
|
||||
{# Übrige Belegung = belegter Speicher des Daten-Volumes außerhalb der überwachten Verzeichnisse #}
|
||||
{% if dataDisk.total %}
|
||||
{% set trackedSize = 0 %}
|
||||
{% for item in items %}{% if item.size is not null %}{% set trackedSize = trackedSize + item.size %}{% endif %}{% endfor %}
|
||||
{% set otherUsed = dataDisk.used - trackedSize %}
|
||||
{% if otherUsed < 0 %}{% set otherUsed = 0 %}{% endif %}
|
||||
{% set items = items|merge([{ label: 'Übrige Belegung', size: otherUsed, color: 'bg-gray-400 dark:bg-gray-500' }]) %}
|
||||
{% endif %}
|
||||
{% set totalSize = 0 %}
|
||||
{% for item in items %}
|
||||
{% if item.size is not null %}{% set totalSize = totalSize + item.size %}{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<div class="px-2 py-1 space-y-3">
|
||||
{% for item in items %}
|
||||
{% set percent = (totalSize > 0 and item.size is not null) ? (item.size / totalSize * 100) : 0 %}
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1 text-sm">
|
||||
<span class="font-medium text-gray-700 dark:text-gray-200">{{ item.label }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">
|
||||
{% if item.size is not null %}{{ item.size|readable_filesize }}<span class="ml-1 text-xs font-normal text-gray-400">({{ percent < 1 ? percent|round(2) : percent|round(1) }}%)</span>{% else %}<span class="text-gray-400">n/a</span>{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full h-2 rounded-full bg-gray-100 dark:bg-gray-700 overflow-hidden">
|
||||
<div class="h-2 rounded-full {{ item.color|default(colors[loop.index0 % colors|length]) }} transition-all duration-500"
|
||||
style="width: {{ percent > 0 and percent < 2 ? 2 : percent|round(1) }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# Dateisysteme: System (Overlay) und Data (Volume) jeweils mit Icon, Auslastungsbalken und Belegt/Frei/Gesamt #}
|
||||
{% set systemDisk = diskUsage.systemDisk|default({ 'total': null, 'free': null, 'used': null }) %}
|
||||
{% if systemDisk.total %}
|
||||
<div class="pt-3 mt-1 border-t dark:border-gray-700">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full bg-psc-50 text-psc-500 dark:bg-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-1 text-sm">
|
||||
<span class="font-medium text-gray-700 dark:text-gray-200">System <span class="text-xs font-normal text-gray-400">(Overlay)</span></span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ systemDisk.used|readable_filesize }} / {{ systemDisk.total|readable_filesize }}</span>
|
||||
</div>
|
||||
<div class="w-full h-2 rounded-full bg-gray-100 dark:bg-gray-700 overflow-hidden">
|
||||
<div class="h-2 rounded-full bg-psc-500 transition-all duration-500" style="width: {{ (systemDisk.used / systemDisk.total * 100)|round(1) }}%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-1 text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">Belegt <span class="font-semibold text-gray-900 dark:text-white">{{ systemDisk.used|readable_filesize }}</span> ({{ (systemDisk.used / systemDisk.total * 100)|round(1) }}%)</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">Frei <span class="font-semibold text-emerald-600 dark:text-emerald-400">{{ systemDisk.free|readable_filesize }}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set dataDisk = diskUsage.dataDisk|default({ 'total': null, 'free': null, 'used': null }) %}
|
||||
{% if dataDisk.total %}
|
||||
<div class="pt-3 mt-1 border-t dark:border-gray-700">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full bg-psc-50 text-psc-500 dark:bg-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-1 text-sm">
|
||||
<span class="font-medium text-gray-700 dark:text-gray-200">Data <span class="text-xs font-normal text-gray-400">(Volume)</span></span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ dataDisk.used|readable_filesize }} / {{ dataDisk.total|readable_filesize }}</span>
|
||||
</div>
|
||||
<div class="w-full h-2 rounded-full bg-gray-100 dark:bg-gray-700 overflow-hidden">
|
||||
<div class="h-2 rounded-full bg-psc-500 transition-all duration-500" style="width: {{ (dataDisk.used / dataDisk.total * 100)|round(1) }}%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-1 text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">Belegt <span class="font-semibold text-gray-900 dark:text-white">{{ dataDisk.used|readable_filesize }}</span> ({{ (dataDisk.used / dataDisk.total * 100)|round(1) }}%)</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">Frei <span class="font-semibold text-emerald-600 dark:text-emerald-400">{{ dataDisk.free|readable_filesize }}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-span-full">
|
||||
<div class="rounded-sm border border bg-white px-7.5 py-6 shadow-lg dark:border-strokedark dark:bg-boxdark">
|
||||
<div class="p-2 space-y-2">
|
||||
|
||||
@ -8,6 +8,19 @@ services:
|
||||
PSC\System\SettingsBundle\:
|
||||
resource: '../../*/*'
|
||||
|
||||
PSC\System\SettingsBundle\Service\DiskUsage:
|
||||
arguments:
|
||||
$directories:
|
||||
'Uploads (Data Volume)': '/data/www/old/public/uploads'
|
||||
'Pakete (Data Volume)': '/data/www/old/data/packages'
|
||||
'Templateprint Layouter (Data Volume)': '/data/www/old/market/templateprint'
|
||||
'Form Based Layouter (Data Volume)': '/data/www/old/market/collectlayouter'
|
||||
'Creative Layouter (Data Volume)': '/data/www/old/market/steplayouter'
|
||||
# Daten-Volume (/dev/sdb): Bezug für die obere Liste inkl. "Übrige Belegung"
|
||||
$dataPath: '/data/www/new/watch'
|
||||
# System-/Overlay-Dateisystem: Bezug für Belegt/Frei/Gesamt unten
|
||||
$systemPath: '/'
|
||||
|
||||
PSC\System\SettingsBundle\Form\Backend\CopyType:
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
144
src/new/src/PSC/System/SettingsBundle/Service/DiskUsage.php
Normal file
144
src/new/src/PSC/System/SettingsBundle/Service/DiskUsage.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\System\SettingsBundle\Service;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
|
||||
/**
|
||||
* Ermittelt den Speicherverbrauch der Datenbank und der konfigurierten
|
||||
* Verzeichnisse für die Anzeige im Dashboard.
|
||||
*
|
||||
* Da das Durchlaufen großer Verzeichnisse teuer ist, werden die Ergebnisse
|
||||
* zwischengespeichert, damit das Dashboard schnell bleibt.
|
||||
*/
|
||||
class DiskUsage
|
||||
{
|
||||
/**
|
||||
* @param array<string,string> $directories Label => absoluter Pfad
|
||||
* @param string $dataPath Pfad auf dem Daten-Volume (z.B. /dev/sdb), gegen das
|
||||
* die überwachten Verzeichnisse abgeglichen werden
|
||||
* @param string $systemPath Pfad auf dem System-/Overlay-Dateisystem
|
||||
*/
|
||||
public function __construct(
|
||||
private Connection $connection,
|
||||
private CacheInterface $cache,
|
||||
private array $directories,
|
||||
private string $dataPath = '/data/www/old',
|
||||
private string $systemPath = '/',
|
||||
private int $cacheTtl = 3600,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* database: ?int,
|
||||
* directories: array<string,?int>,
|
||||
* dataDisk: array{total: ?int, free: ?int, used: ?int},
|
||||
* systemDisk: array{total: ?int, free: ?int, used: ?int}
|
||||
* }
|
||||
*/
|
||||
public function getUsage(): array
|
||||
{
|
||||
// Versionssuffix: ändert sich die Struktur, werden alte Cache-Einträge ignoriert
|
||||
return $this->cache->get('psc_dashboard_disk_usage_v2', function (ItemInterface $item): array {
|
||||
$item->expiresAfter($this->cacheTtl);
|
||||
|
||||
return [
|
||||
'database' => $this->getDatabaseSize(),
|
||||
'directories' => $this->getDirectorySizes(),
|
||||
'dataDisk' => $this->getDiskSpace($this->dataPath),
|
||||
'systemDisk' => $this->getDiskSpace($this->systemPath),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gesamtkapazität, freier und belegter Speicher des Dateisystems am
|
||||
* angegebenen Pfad in Bytes.
|
||||
*
|
||||
* @return array{total: ?int, free: ?int, used: ?int}
|
||||
*/
|
||||
private function getDiskSpace(string $path): array
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
return ['total' => null, 'free' => null, 'used' => null];
|
||||
}
|
||||
|
||||
$total = @disk_total_space($path);
|
||||
$free = @disk_free_space($path);
|
||||
|
||||
if ($total === false || $free === false) {
|
||||
return ['total' => null, 'free' => null, 'used' => null];
|
||||
}
|
||||
|
||||
return [
|
||||
'total' => (int) $total,
|
||||
'free' => (int) $free,
|
||||
'used' => (int) ($total - $free),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Größe der aktuellen Datenbank in Bytes (Daten + Indizes).
|
||||
*/
|
||||
private function getDatabaseSize(): ?int
|
||||
{
|
||||
try {
|
||||
$database = $this->connection->getDatabase();
|
||||
|
||||
if ($database === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$size = $this->connection->fetchOne(
|
||||
'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = :db',
|
||||
['db' => $database],
|
||||
);
|
||||
|
||||
return $size !== false && $size !== null ? (int) $size : null;
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,?int> Label => Größe in Bytes (oder null)
|
||||
*/
|
||||
private function getDirectorySizes(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->directories as $label => $path) {
|
||||
$result[$label] = $this->getDirectorySize($path);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Größe eines Verzeichnisses in Bytes via "du" (schnell). Liefert null,
|
||||
* wenn das Verzeichnis fehlt oder nicht ermittelt werden kann.
|
||||
*/
|
||||
private function getDirectorySize(string $path): ?int
|
||||
{
|
||||
if (!is_dir($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$output = [];
|
||||
$exitCode = 0;
|
||||
@exec('du -sb ' . escapeshellarg($path) . ' 2>/dev/null', $output, $exitCode);
|
||||
|
||||
if ($exitCode === 0 && isset($output[0])) {
|
||||
$parts = preg_split('/\s+/', trim($output[0]));
|
||||
|
||||
if (isset($parts[0]) && is_numeric($parts[0])) {
|
||||
return (int) $parts[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -197,7 +197,7 @@ class StartController extends AbstractController
|
||||
$docData = [];
|
||||
if ($contactEntity->getAbteilung() != '') {
|
||||
$docData[] = [
|
||||
'name' => 'data[grad][enable]',
|
||||
'name' => 'data[betrieb][enable]',
|
||||
'value' => '1',
|
||||
];
|
||||
}
|
||||
|
||||
@ -1587,6 +1587,10 @@ html {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-cols-3{
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.grid-cols-4{
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
@ -1974,6 +1978,11 @@ html {
|
||||
border-color: rgb(250 204 21 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-amber-500{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(245 158 11 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-black\/70{
|
||||
background-color: rgb(0 0 0 / 0.7);
|
||||
}
|
||||
@ -2003,6 +2012,11 @@ html {
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-emerald-500{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(16 185 129 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-100{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||
@ -2110,6 +2124,16 @@ html {
|
||||
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-rose-500{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(244 63 94 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-sky-500{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(14 165 233 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-slate-100{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(241 245 249 / var(--tw-bg-opacity));
|
||||
@ -2125,6 +2149,11 @@ html {
|
||||
background-color: rgb(245 245 244 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-violet-500{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(139 92 246 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white{
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
@ -2312,10 +2341,18 @@ html {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.pt-1{
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
|
||||
.pt-2{
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-3{
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.text-left{
|
||||
text-align: left;
|
||||
}
|
||||
@ -2453,6 +2490,11 @@ html {
|
||||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-emerald-600{
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(5 150 105 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-400{
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||
@ -2777,6 +2819,10 @@ html {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.duration-500{
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
.duration-75{
|
||||
transition-duration: 75ms;
|
||||
}
|
||||
@ -4511,6 +4557,11 @@ html {
|
||||
background-color: rgb(30 58 138 / 0.2);
|
||||
}
|
||||
|
||||
:is(.dark .dark\:bg-gray-500){
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
:is(.dark .dark\:bg-gray-600){
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(75 85 99 / var(--tw-bg-opacity));
|
||||
@ -4581,6 +4632,11 @@ html {
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
:is(.dark .dark\:text-emerald-400){
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(52 211 153 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
:is(.dark .dark\:text-gray-100){
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(243 244 246 / var(--tw-text-opacity));
|
||||
@ -4968,6 +5024,12 @@ html {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.lg\:space-y-8 > :not([hidden]) ~ :not([hidden]){
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.lg\:border-r{
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
info:
|
||||
datum: 02.06.2026
|
||||
datum: 10.06.2026
|
||||
release: 2.3.6
|
||||
|
||||
changelog:
|
||||
- version: 2.3.6
|
||||
datum: 02.06.2026
|
||||
datum: 10.06.2026
|
||||
changes:
|
||||
- "Verbrauchsanzeige im Dashboard"
|
||||
- "Shop kann eigene SMTP Einstellungen haben"
|
||||
- "Passwort Start und Finish Aktion Absender und Empfänger Bug behoben"
|
||||
- "Form Based Layouter speichert jetzt die Firma vom angemeldeten Benutzer"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user