Merge branch 'main' of https://github.com/Eskimue/skyview.astronomiemuseum.de
This commit is contained in:
+107
-88
@@ -119,6 +119,68 @@ if (!function_exists('planetVisibilityClass')) {
|
|||||||
return ['class' => 'planet-visibility-bad', 'label' => 'am Tag'];
|
return ['class' => 'planet-visibility-bad', 'label' => 'am Tag'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!function_exists('indexPythonCandidates')) {
|
||||||
|
function indexPythonCandidates(): array
|
||||||
|
{
|
||||||
|
if (PHP_OS_FAMILY === 'Windows') {
|
||||||
|
$candidates = [];
|
||||||
|
$localAppData = getenv('LOCALAPPDATA');
|
||||||
|
if (is_string($localAppData) && $localAppData !== '') {
|
||||||
|
$localPython = $localAppData . '\\Python\\bin\\python.exe';
|
||||||
|
if (is_file($localPython)) {
|
||||||
|
$candidates[] = escapeshellarg($localPython);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_merge(
|
||||||
|
$candidates,
|
||||||
|
['py -3', 'py', 'python']
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['/usr/bin/python3', '/usr/bin/python', 'python3', 'python'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!function_exists('runIndexPythonCommand')) {
|
||||||
|
function runIndexPythonCommand(string $scriptPath, array $args): array
|
||||||
|
{
|
||||||
|
putenv('PYTHONUTF8=1');
|
||||||
|
putenv('PYTHONIOENCODING=utf-8');
|
||||||
|
|
||||||
|
$escapedArguments = array_map('escapeshellarg', array_merge([$scriptPath], $args));
|
||||||
|
$lastError = 'Python konnte nicht gestartet werden.';
|
||||||
|
|
||||||
|
foreach (indexPythonCandidates() as $pythonBinary) {
|
||||||
|
$command = $pythonBinary . ' ' . implode(' ', $escapedArguments) . ' 2>&1';
|
||||||
|
$output = [];
|
||||||
|
$resultCode = 0;
|
||||||
|
exec($command, $output, $resultCode);
|
||||||
|
|
||||||
|
$joined = trim(implode("\n", $output));
|
||||||
|
$decoded = json_decode($joined, true);
|
||||||
|
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
return [
|
||||||
|
'ok' => (bool) ($decoded['ok'] ?? false),
|
||||||
|
'data' => $decoded,
|
||||||
|
'raw' => $joined,
|
||||||
|
'command' => $command,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($joined !== '') {
|
||||||
|
$lastError = $joined;
|
||||||
|
} elseif ($resultCode !== 0) {
|
||||||
|
$lastError = 'Fehlercode ' . $resultCode . ' bei ' . $pythonBinary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => false,
|
||||||
|
'error' => $lastError,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function idx_tleChecksum(string $line): int
|
function idx_tleChecksum(string $line): int
|
||||||
{
|
{
|
||||||
@@ -227,82 +289,47 @@ if ($loggedIn) {
|
|||||||
$date = (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('Y-m-d');
|
$date = (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('Y-m-d');
|
||||||
|
|
||||||
$scriptPath = realpath(__DIR__ . '/py/api.py');
|
$scriptPath = realpath(__DIR__ . '/py/api.py');
|
||||||
if (PHP_OS_FAMILY === 'Windows') {
|
|
||||||
$pythonBin = 'python';
|
|
||||||
} elseif (file_exists('/usr/bin/python3')) {
|
|
||||||
$pythonBin = '/usr/bin/python3';
|
|
||||||
} elseif (file_exists('/usr/bin/python')) {
|
|
||||||
$pythonBin = '/usr/bin/python';
|
|
||||||
} else {
|
|
||||||
$pythonBin = 'python';
|
|
||||||
}
|
|
||||||
putenv('PYTHONUTF8=1');
|
|
||||||
putenv('PYTHONIOENCODING=utf-8');
|
|
||||||
|
|
||||||
// Sonne + Mond
|
// Sonne + Mond
|
||||||
$args = [
|
$pythonResult = runIndexPythonCommand($scriptPath, [
|
||||||
$pythonBin,
|
'sun_moon_rise_set',
|
||||||
escapeshellarg($scriptPath),
|
(string)$lat,
|
||||||
escapeshellarg('sun_moon_rise_set'),
|
(string)$lon,
|
||||||
escapeshellarg((string)$lat),
|
(string)$elev,
|
||||||
escapeshellarg((string)$lon),
|
$date,
|
||||||
escapeshellarg((string)$elev),
|
$tz,
|
||||||
escapeshellarg($date),
|
]);
|
||||||
escapeshellarg($tz),
|
if ($pythonResult['ok'] ?? false) {
|
||||||
];
|
$skyData = $pythonResult['data'];
|
||||||
$cmd = implode(' ', $args) . ' 2>&1';
|
|
||||||
$out = [];
|
|
||||||
$resultCode = 0;
|
|
||||||
exec($cmd, $out, $resultCode);
|
|
||||||
$rawText = trim(implode("\n", $out));
|
|
||||||
$raw = json_decode($rawText, true);
|
|
||||||
if (is_array($raw) && ($raw['ok'] ?? false)) {
|
|
||||||
$skyData = $raw;
|
|
||||||
$nextDate = (new DateTimeImmutable($date, new DateTimeZone($tz)))->modify('+1 day')->format('Y-m-d');
|
$nextDate = (new DateTimeImmutable($date, new DateTimeZone($tz)))->modify('+1 day')->format('Y-m-d');
|
||||||
$argsNext = [
|
$pythonResultNext = runIndexPythonCommand($scriptPath, [
|
||||||
$pythonBin,
|
'sun_moon_rise_set',
|
||||||
escapeshellarg($scriptPath),
|
(string)$lat,
|
||||||
escapeshellarg('sun_moon_rise_set'),
|
(string)$lon,
|
||||||
escapeshellarg((string)$lat),
|
(string)$elev,
|
||||||
escapeshellarg((string)$lon),
|
$nextDate,
|
||||||
escapeshellarg((string)$elev),
|
$tz,
|
||||||
escapeshellarg($nextDate),
|
]);
|
||||||
escapeshellarg($tz),
|
if ($pythonResultNext['ok'] ?? false) {
|
||||||
];
|
$skyDataNext = $pythonResultNext['data'];
|
||||||
$cmdNext = implode(' ', $argsNext) . ' 2>&1';
|
|
||||||
$outNext = [];
|
|
||||||
$resultCodeNext = 0;
|
|
||||||
exec($cmdNext, $outNext, $resultCodeNext);
|
|
||||||
$rawTextNext = trim(implode("\n", $outNext));
|
|
||||||
$rawNext = json_decode($rawTextNext, true);
|
|
||||||
if (is_array($rawNext) && ($rawNext['ok'] ?? false)) {
|
|
||||||
$skyDataNext = $rawNext;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$pyError = $raw['error'] ?? ($rawText !== '' ? $rawText : 'Fehler beim Berechnen der Aufgangszeiten.');
|
$pyError = $pythonResult['data']['error'] ?? $pythonResult['error'] ?? 'Fehler beim Berechnen der Aufgangszeiten.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planeten
|
// Planeten
|
||||||
$args2 = [
|
$pythonResult2 = runIndexPythonCommand($scriptPath, [
|
||||||
$pythonBin,
|
'planet_rise_set',
|
||||||
escapeshellarg($scriptPath),
|
(string)$lat,
|
||||||
escapeshellarg('planet_rise_set'),
|
(string)$lon,
|
||||||
escapeshellarg((string)$lat),
|
(string)$elev,
|
||||||
escapeshellarg((string)$lon),
|
$date,
|
||||||
escapeshellarg((string)$elev),
|
$tz,
|
||||||
escapeshellarg($date),
|
]);
|
||||||
escapeshellarg($tz),
|
if ($pythonResult2['ok'] ?? false) {
|
||||||
];
|
$planetData = $pythonResult2['data'];
|
||||||
$cmd2 = implode(' ', $args2) . ' 2>&1';
|
|
||||||
$out2 = [];
|
|
||||||
$resultCode2 = 0;
|
|
||||||
exec($cmd2, $out2, $resultCode2);
|
|
||||||
$rawText2 = trim(implode("\n", $out2));
|
|
||||||
$raw2 = json_decode($rawText2, true);
|
|
||||||
if (is_array($raw2) && ($raw2['ok'] ?? false)) {
|
|
||||||
$planetData = $raw2;
|
|
||||||
} elseif ($pyError === null) {
|
} elseif ($pyError === null) {
|
||||||
$pyError = $raw2['error'] ?? ($rawText2 !== '' ? $rawText2 : 'Fehler beim Berechnen der Planetenzeiten.');
|
$pyError = $pythonResult2['data']['error'] ?? $pythonResult2['error'] ?? 'Fehler beim Berechnen der Planetenzeiten.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Satelliten-Überflüge heute — Favorit zuerst, dann alle gemerkten
|
// Satelliten-Überflüge heute — Favorit zuerst, dann alle gemerkten
|
||||||
@@ -341,28 +368,20 @@ if ($loggedIn) {
|
|||||||
], $userSats), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
], $userSats), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
$satsPayload = base64_encode((string) $satsJson);
|
$satsPayload = base64_encode((string) $satsJson);
|
||||||
|
|
||||||
$args3 = [
|
$pythonResult3 = runIndexPythonCommand($scriptPath, [
|
||||||
$pythonBin,
|
'satellite_passes',
|
||||||
escapeshellarg($scriptPath),
|
(string)$lat,
|
||||||
escapeshellarg('satellite_passes'),
|
(string)$lon,
|
||||||
escapeshellarg((string)$lat),
|
(string)$elev,
|
||||||
escapeshellarg((string)$lon),
|
$tz,
|
||||||
escapeshellarg((string)$elev),
|
$dayStart->format('Y-m-d\TH:i:s\Z'),
|
||||||
escapeshellarg($tz),
|
$dayEnd->format('Y-m-d\TH:i:s\Z'),
|
||||||
escapeshellarg($dayStart->format('Y-m-d\TH:i:s\Z')),
|
$satsPayload,
|
||||||
escapeshellarg($dayEnd->format('Y-m-d\TH:i:s\Z')),
|
]);
|
||||||
escapeshellarg($satsPayload),
|
if ($pythonResult3['ok'] ?? false) {
|
||||||
];
|
$satData = $pythonResult3['data'];
|
||||||
$cmd3 = implode(' ', $args3) . ' 2>&1';
|
|
||||||
$out3 = [];
|
|
||||||
$resultCode3 = 0;
|
|
||||||
exec($cmd3, $out3, $resultCode3);
|
|
||||||
$rawText3 = trim(implode("\n", $out3));
|
|
||||||
$raw3 = json_decode($rawText3, true);
|
|
||||||
if (is_array($raw3) && ($raw3['ok'] ?? false)) {
|
|
||||||
$satData = $raw3;
|
|
||||||
} else {
|
} else {
|
||||||
$satError = $raw3['error'] ?? ($rawText3 !== '' ? $rawText3 : 'Fehler beim Berechnen der Satellitenüberflüge.');
|
$satError = $pythonResult3['data']['error'] ?? $pythonResult3['error'] ?? 'Fehler beim Berechnen der Satellitenüberflüge.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-10
@@ -429,7 +429,7 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card satpass-card">
|
<section class="card satpass-card">
|
||||||
<div id="satpassStatus" class="hint">Lade Ueberfluege...</div>
|
<div id="satpassStatus" class="hint">Lade Überflüge...</div>
|
||||||
|
|
||||||
<div class="satpass-main-layout">
|
<div class="satpass-main-layout">
|
||||||
<div class="satpass-sidebar">
|
<div class="satpass-sidebar">
|
||||||
@@ -620,9 +620,14 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function azToDirectionName(az) {
|
function azToDirectionName(az) {
|
||||||
|
<<<<<<< HEAD
|
||||||
const displayAz = getDisplayedAzimuthDeg(az);
|
const displayAz = getDisplayedAzimuthDeg(az);
|
||||||
const dirs = ['Norden','Nordwesten','Westen','Suedwesten','Sueden','Suedosten','Osten','Nordosten'];
|
const dirs = ['Norden','Nordwesten','Westen','Suedwesten','Sueden','Suedosten','Osten','Nordosten'];
|
||||||
return dirs[Math.round(displayAz/45) % 8];
|
return dirs[Math.round(displayAz/45) % 8];
|
||||||
|
=======
|
||||||
|
const dirs = ['Norden','Nord Osten','Osten','Süd Osten','Süden','Süd Westen','Westen','Nord Westen'];
|
||||||
|
return dirs[Math.round(az/45) % 8];
|
||||||
|
>>>>>>> 65be4bdf9b70b6b4f405d70e52ef7c99c1bcb498
|
||||||
}
|
}
|
||||||
|
|
||||||
function altitudeToPhrase(el) {
|
function altitudeToPhrase(el) {
|
||||||
@@ -647,7 +652,7 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
|
|
||||||
function classifySkyBrightness(sunAltDeg) {
|
function classifySkyBrightness(sunAltDeg) {
|
||||||
if (sunAltDeg > -4) return {key:'day', label:'Tag'};
|
if (sunAltDeg > -4) return {key:'day', label:'Tag'};
|
||||||
if (sunAltDeg >= -12) return {key:'twilight', label:'Daemmerung'};
|
if (sunAltDeg >= -12) return {key:'twilight', label:'Dämmerung'};
|
||||||
return {key:'night', label:'Nacht'};
|
return {key:'night', label:'Nacht'};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +697,7 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
|
|
||||||
function getDarknessCondition(sunAltDeg, visiblePeak) {
|
function getDarknessCondition(sunAltDeg, visiblePeak) {
|
||||||
if (sunAltDeg > -4) return { key: 'daylight', score: 0.05, label: 'Tageshimmel' };
|
if (sunAltDeg > -4) return { key: 'daylight', score: 0.05, label: 'Tageshimmel' };
|
||||||
if (sunAltDeg >= -12) return { key: 'twilight', score: 0.65, label: 'Daemmerung' };
|
if (sunAltDeg >= -12) return { key: 'twilight', score: 0.65, label: 'Dämmerung' };
|
||||||
if ((visiblePeak?.elevationDeg ?? -1) < 0) return { key: 'deep_night', score: 0.20, label: 'Nicht sichtbar / Erdschatten' };
|
if ((visiblePeak?.elevationDeg ?? -1) < 0) return { key: 'deep_night', score: 0.20, label: 'Nicht sichtbar / Erdschatten' };
|
||||||
return { key: 'ideal', score: 1.00, label: 'Dunkler Himmel, Satellit sonnenbeschienen' };
|
return { key: 'ideal', score: 1.00, label: 'Dunkler Himmel, Satellit sonnenbeschienen' };
|
||||||
}
|
}
|
||||||
@@ -807,8 +812,43 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
}).filter((v,i,a) => i===0 || v!==a[i-1])
|
}).filter((v,i,a) => i===0 || v!==a[i-1])
|
||||||
: descriptors;
|
: descriptors;
|
||||||
if (limited.length === 1) return `Sie ist dabei ${limited[0]} zu beobachten.`;
|
if (limited.length === 1) return `Sie ist dabei ${limited[0]} zu beobachten.`;
|
||||||
if (limited.length === 2) return `Sie ist zunaechst ${limited[0]} zu beobachten und zuletzt ${limited[1]}.`;
|
if (limited.length === 2) return `Sie ist zunächst ${limited[0]} zu beobachten und zuletzt ${limited[1]}.`;
|
||||||
return `Sie ist zunaechst ${limited[0]} zu beobachten, spaeter ${limited.slice(1,-1).join(', ')} und zuletzt ${limited[limited.length-1]}.`;
|
return `Sie ist zunächst ${limited[0]} zu beobachten, später ${limited.slice(1,-1).join(', ')} und zuletzt ${limited[limited.length-1]}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFlightNarrative(satName, visiblePhase, visiblePeak) {
|
||||||
|
if (!visiblePhase.visiblePath.length) return '';
|
||||||
|
const startPoint = visiblePhase.visiblePath[0];
|
||||||
|
const endPoint = visiblePhase.visiblePath[visiblePhase.visiblePath.length - 1];
|
||||||
|
const startDir = azToDirectionName(startPoint.azimuthDeg);
|
||||||
|
const endDir = azToDirectionName(endPoint.azimuthDeg);
|
||||||
|
|
||||||
|
let peakText = `steigt bis auf etwa ${visiblePeak.elevationDeg.toFixed(0)}° Höhe im ${azToDirectionName(visiblePeak.azimuthDeg)}`;
|
||||||
|
if (visiblePeak.elevationDeg >= 75) {
|
||||||
|
peakText = 'steigt bis nahe an den Zenit';
|
||||||
|
} else if (visiblePeak.elevationDeg >= 55) {
|
||||||
|
peakText = `steigt hoch über den ${azToDirectionName(visiblePeak.azimuthDeg)} auf`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${satName} taucht gegen ${formatTime(visiblePhase.visibleStartTime)} Uhr am ${startDir}horizont auf, ${peakText} und zieht dann weiter Richtung ${endDir}, wo sie um ${formatTime(visiblePhase.visibleEndTime)} Uhr wieder verschwindet.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildObservationSummaryClean(satName, pass, visibility) {
|
||||||
|
if (visibility.darkness.key === 'daylight') return `${satName} ist bei diesem Überflug am Tageshimmel praktisch nicht zu sehen.`;
|
||||||
|
const visiblePhase = getVisiblePhase(pass);
|
||||||
|
if (!visiblePhase.visiblePath.length) {
|
||||||
|
return `${satName} liegt bei diesem Überflug während der gesamten Passage im Erdschatten und ist deshalb nicht sichtbar.`;
|
||||||
|
}
|
||||||
|
const visiblePeak = visibility.visiblePeak ?? getVisiblePeak(pass, visiblePhase);
|
||||||
|
const flightText = buildFlightNarrative(satName, visiblePhase, visiblePeak);
|
||||||
|
const shadowText = pass.shadowEntry
|
||||||
|
? ` Sie tritt um ${formatTime(pass.shadowEntry.time)} Uhr in den Erdschatten ein.` : '';
|
||||||
|
const magText = pass.peakMag !== null
|
||||||
|
? ` Ihre maximale Helligkeit beträgt ${formatMag(pass.peakMag)} mag.` : '';
|
||||||
|
return `Der Überflug der ${satName} am ${formatDateLocal(pass.aosTime)} (${formatWeekdayShort(pass.aosTime)}) um ${formatTime(pass.aosTime)} Uhr ist ${visibility.label} (${visibility.scorePercent} von 100 Punkten). `
|
||||||
|
+ `Der Überflug ist ${formatDurationLong(visiblePhase.visibleDurationMs)} lang und bis ${formatTime(visiblePhase.visibleEndTime)} Uhr zu sehen. `
|
||||||
|
+ `${flightText} Die maximale Horizonthöhe der Raumstation ${satName} beträgt ${visiblePeak.elevationDeg.toFixed(0)}°.`
|
||||||
|
+ `${shadowText}${magText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVisiblePhase(pass) {
|
function getVisiblePhase(pass) {
|
||||||
@@ -864,14 +904,14 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
return `${satName} liegt bei diesem Überflug waehrend der gesamten Passage im Erdschatten und ist deshalb nicht sichtbar.`;
|
return `${satName} liegt bei diesem Überflug waehrend der gesamten Passage im Erdschatten und ist deshalb nicht sichtbar.`;
|
||||||
}
|
}
|
||||||
const visiblePeak = visibility.visiblePeak ?? getVisiblePeak(pass, visiblePhase);
|
const visiblePeak = visibility.visiblePeak ?? getVisiblePeak(pass, visiblePhase);
|
||||||
const pathText = buildPathDescription(visiblePhase.visiblePath);
|
const flightText = buildFlightNarrative(satName, visiblePhase, visiblePeak);
|
||||||
const shadowText = pass.shadowEntry
|
const shadowText = pass.shadowEntry
|
||||||
? ` Sie tritt um ${formatTime(pass.shadowEntry.time)} Uhr in den Erdschatten ein.` : '';
|
? ` Sie tritt um ${formatTime(pass.shadowEntry.time)} Uhr in den Erdschatten ein.` : '';
|
||||||
const magText = pass.peakMag !== null
|
const magText = pass.peakMag !== null
|
||||||
? ` Ihre maximale Helligkeit betraegt ${formatMag(pass.peakMag)} mag.` : '';
|
? ` Ihre maximale Helligkeit beträgt ${formatMag(pass.peakMag)} mag.` : '';
|
||||||
return `Die Beobachtungsqualitaet von ${satName} am ${formatDateLocal(pass.aosTime)} (${formatWeekdayShort(pass.aosTime)}) um ${formatTime(pass.aosTime)} Uhr ist ${visibility.label} (${visibility.scorePercent} von 100 Punkten). `
|
return `Der Überflug der ${satName} am ${formatDateLocal(pass.aosTime)} (${formatWeekdayShort(pass.aosTime)}) um ${formatTime(pass.aosTime)} Uhr ist ${visibility.label} (${visibility.scorePercent} von 100 Punkten). `
|
||||||
+ `Der Überflug ist ${formatDurationLong(visiblePhase.visibleDurationMs)} lang und bis ${formatTime(visiblePhase.visibleEndTime)} Uhr zu sehen. `
|
+ `Der Überflug ist ${formatDurationLong(visiblePhase.visibleDurationMs)} lang und bis ${formatTime(visiblePhase.visibleEndTime)} Uhr zu sehen. `
|
||||||
+ `${pathText} Die fuer die Bewertung relevante maximale sichtbare Horizonthoehe betraegt ${visiblePeak.elevationDeg.toFixed(0)}°.`
|
+ `${flightText} Die maximale Horizonthöhe der Raumstation ${satName} beträgt ${visiblePeak.elevationDeg.toFixed(0)}°.`
|
||||||
+ `${shadowText}${magText}`;
|
+ `${shadowText}${magText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1162,7 +1202,7 @@ if (!empty($currentLocation) && is_array($currentLocation)) {
|
|||||||
lastSelectedSatelliteName = satResult.name;
|
lastSelectedSatelliteName = satResult.name;
|
||||||
lastSelectedPass = pass;
|
lastSelectedPass = pass;
|
||||||
const visibility = classifyVisibility(pass);
|
const visibility = classifyVisibility(pass);
|
||||||
const observationSummary = buildObservationSummary(satResult.name, pass, visibility);
|
const observationSummary = buildObservationSummaryClean(satResult.name, pass, visibility);
|
||||||
|
|
||||||
passInfoEl.innerHTML = `
|
passInfoEl.innerHTML = `
|
||||||
<table class="data-table mb-sm">
|
<table class="data-table mb-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user