initial commit
This commit is contained in:
470
www/index.php
Normal file
470
www/index.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
ob_start(); // start buffering so we can set cookies before any output
|
||||
|
||||
// helper for settings
|
||||
function getSetting(
|
||||
string $cookieName,
|
||||
string $getName,
|
||||
int $default,
|
||||
?int $min = null,
|
||||
?int $max = null
|
||||
): int {
|
||||
if (isset($_GET[$getName])) {
|
||||
$value = intval($_GET[$getName]);
|
||||
|
||||
if (!is_null($min) && $value < $min) $value = $min;
|
||||
if (!is_null($max) && $value > $max) $value = $max;
|
||||
|
||||
$cookieOptions = [
|
||||
'expires' => time() + 86400 * 365,
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
];
|
||||
setcookie($cookieName, $value, $cookieOptions);
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (isset($_COOKIE[$cookieName])) {
|
||||
return intval($_COOKIE[$cookieName]);
|
||||
}
|
||||
|
||||
$cookieOptions = [
|
||||
'expires' => time() + 86400 * 365,
|
||||
'path' => '/',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
];
|
||||
setcookie($cookieName, $default, $cookieOptions);
|
||||
return $default;
|
||||
}
|
||||
|
||||
// ---- PASS TYPE COOKIE ----
|
||||
$defaultPassType = 0;
|
||||
if (isset($_GET['pt'])) {
|
||||
$passType = intval($_GET['pt']);
|
||||
setcookie('passtype', $passType, time() + 86400 * 365, '/');
|
||||
} elseif (isset($_COOKIE['passtype'])) {
|
||||
$passType = intval($_COOKIE['passtype']);
|
||||
} else {
|
||||
setcookie('passtype', $defaultPassType, time() + 86400 * 365, '/');
|
||||
$passType = $defaultPassType;
|
||||
}
|
||||
|
||||
// ---- CUSTOM PASSWORD SETTINGS ----
|
||||
$wMin = getSetting('wMin', 'wMinIn', 6, 3, 10);
|
||||
$wMax = getSetting('wMax', 'wMaxIn', 12, 3, 10);
|
||||
$wCount = getSetting('wCount', 'wCountIn', 2, 1, 5);
|
||||
$sChar = getSetting('sChar', 'sCharIn', 2, 0, 4);
|
||||
$numLen = getSetting('numLen', 'numLenIn', 3, 0, 8);
|
||||
|
||||
function curlHelper($url, $APIKey){
|
||||
// Initialise cURL
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // return the response as a string
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // follow redirects if any
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // timeout after 10 seconds
|
||||
// Execute the request
|
||||
$response = curl_exec($ch);
|
||||
// Handle cURL errors
|
||||
if ($response === false) {
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception("cURL error while calling API: {$error}");
|
||||
}
|
||||
// Check HTTP status code
|
||||
$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpStatus !== 200) {
|
||||
throw new Exception("API returned HTTP status {$httpStatus} (expected 200).");
|
||||
}
|
||||
$decoded = json_decode($response, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
if (isset($decoded[$APIKey])) {
|
||||
return $decoded[$APIKey];
|
||||
}
|
||||
}
|
||||
return trim($response);
|
||||
}
|
||||
|
||||
// Password Generator API Function
|
||||
function getStandardPasswordFromAPI($passType){
|
||||
$apiUrl = "http://172.17.0.1:8189/get_password";
|
||||
// Build the query string and full URL
|
||||
$query = http_build_query(['pwd_index' => $passType]);
|
||||
$url = rtrim($apiUrl, '?') . '?' . $query;
|
||||
return curlHelper($url, "password");
|
||||
}
|
||||
|
||||
// Password Generator API Function for Custom Password
|
||||
function getCustomPasswordFromAPI($passType, $payload){
|
||||
$url = 'http://172.17.0.1:8189/custom_password';
|
||||
|
||||
// Initialise a cURL handle
|
||||
$ch = curl_init($url);
|
||||
|
||||
// Tell cURL we want to send a POST request with a JSON body
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
|
||||
// Tell cURL what headers to send
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($payload),
|
||||
]);
|
||||
|
||||
// We want the response body back, not the HTTP headers
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
// Optional: if you need to trust self‑signed certs (rare for production)
|
||||
// curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
// curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
|
||||
// Execute the request
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// Basic error handling
|
||||
if ($response === false) {
|
||||
// Something went wrong with the cURL call
|
||||
error_log('cURL error: ' . curl_error($ch));
|
||||
curl_close($ch);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get HTTP status code to confirm the request succeeded
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
// Non‑200 responses are treated as errors
|
||||
error_log("Password API returned HTTP {$httpCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the JSON response
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if ($data === null || !isset($data['password'])) {
|
||||
error_log('Password API returned malformed JSON');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the password string
|
||||
return $data['password'];
|
||||
|
||||
}
|
||||
|
||||
// Password Count API Function
|
||||
function getPasswordCountFromAPI(){
|
||||
$apiUrl = "http://172.17.0.1:8189/get_count";
|
||||
// Build the query string and full URL
|
||||
$url = rtrim($apiUrl, '?') ;
|
||||
return curlHelper($url, "total_passwords");
|
||||
}
|
||||
|
||||
function passwordTest_strength($passwordTest) {
|
||||
$strength = 0;
|
||||
$possible_points = 12;
|
||||
$length = strlen($passwordTest);
|
||||
|
||||
if (detect_any_uppercase($passwordTest)) $strength += 1;
|
||||
if (detect_any_lowercase($passwordTest)) $strength += 1;
|
||||
$strength += min(count_numbers($passwordTest), 2);
|
||||
$strength += min(count_symbols($passwordTest), 2);
|
||||
|
||||
if ($length >= 8) {
|
||||
$strength += 2;
|
||||
$strength += min(($length - 8) * 0.5, 4);
|
||||
}
|
||||
|
||||
$strength_percent = $strength / (float)$possible_points;
|
||||
return floor($strength_percent * 10);
|
||||
}
|
||||
|
||||
|
||||
function detect_any_uppercase($string) {
|
||||
return strtolower($string) != $string;
|
||||
}
|
||||
|
||||
function detect_any_lowercase($string) {
|
||||
return strtoupper($string) != $string;
|
||||
}
|
||||
|
||||
function count_numbers($string) {
|
||||
return preg_match_all('/[0-9]/', $string);
|
||||
}
|
||||
|
||||
function count_symbols($string) {
|
||||
$regex = '/[' . preg_quote('!@£$%^&*-_+=?') . ']/';
|
||||
return preg_match_all($regex, $string);
|
||||
}
|
||||
|
||||
function returnActualPassword($passType){
|
||||
if ($passType == 3){
|
||||
|
||||
$payload = json_encode([
|
||||
'w_min' => getSetting('wMin', 'wMinIn', 6, 3, 10),
|
||||
'w_max' => getSetting('wMax', 'wMinIn', 6, 3, 10),
|
||||
'w_count' => getSetting('wCount', 'wCountIn', 2, 1, 5),
|
||||
's_char' => getSetting('sChar', 'sCharIn', 2, 0, 4),
|
||||
'num_len' => getSetting('numLen', 'numLenIn', 3, 0, 8),
|
||||
]);
|
||||
return getCustomPasswordFromAPI($passType, $payload);
|
||||
|
||||
}
|
||||
else{
|
||||
return getStandardPasswordFromAPI($passType);
|
||||
}
|
||||
}
|
||||
|
||||
$final = returnActualPassword($passType);
|
||||
#$final = getStandardPasswordFromAPI($passType);
|
||||
$total = getPasswordCountFromAPI();
|
||||
$rating = passwordTest_strength($final);
|
||||
|
||||
// ---- OUTPUT ----
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Matt-Cloud Password Generator</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h2>Matt-Cloud Password Generator</h2>
|
||||
<p>Hello folks.<br>
|
||||
This here is a nice little human-readable password generator. <br>
|
||||
You've got a few different modes and sometimes it is accidentally funny.<br>
|
||||
|
||||
<div class="help-link" id="helpToggle" >API</div>
|
||||
</div>
|
||||
|
||||
<div id="helpText" class="card">
|
||||
<strong>Matt-Cloud Password API</strong><p>
|
||||
To get passwords, you may:<p>
|
||||
<code>
|
||||
curl -s https://<?php echo $_SERVER['SERVER_NAME'] ?>/get_password?pwd_index=N<br>
|
||||
{<br>
|
||||
"password": "-`(UncoloredSwiftly2099"<br>
|
||||
}
|
||||
</code><p>
|
||||
Where N is an integer 0,1, or 2 for now.<p>
|
||||
To get verbose passwords, you may:<p>
|
||||
<code>
|
||||
curl -s https://<?php echo $_SERVER['SERVER_NAME'] ?>/verbose_password?pwd_index=N<br>
|
||||
{<br>
|
||||
"descriptor": {<br>
|
||||
"description": "This simple password is in the following format: !Password123 - this pulls from a list of 1291 simple words.",<br>
|
||||
"name": "Simple Password",<br>
|
||||
"type": "2"<br>
|
||||
},<br>
|
||||
"password": "&Keenness887",<br>
|
||||
"password count": 9<br>
|
||||
}
|
||||
</code><p>
|
||||
To get custom passwords, you may:<p>
|
||||
<code>
|
||||
curl -X POST https://<?php echo $_SERVER['SERVER_NAME'] ?>/custom_password \ <br>
|
||||
H "Content-Type: application/json" \ <br>
|
||||
d '{ <br>
|
||||
"w_min":5, <br>
|
||||
"w_max":8, <br>
|
||||
"w_count":3, <br>
|
||||
"s_char":2, <br>
|
||||
"num_len":3, <br>
|
||||
}' <br>
|
||||
{<br>
|
||||
"password": "Copier+ViolinBoned632*"<br>
|
||||
}
|
||||
</code><p>
|
||||
To get the API password count (but why tho?), you may:<p>
|
||||
<code>
|
||||
curl -s https://<?php echo $_SERVER['SERVER_NAME'] ?>/get_count<br>
|
||||
{<br>
|
||||
"total_passwords": 10<br>
|
||||
}
|
||||
</code><p>
|
||||
To view the password descriptor, you may <br>
|
||||
<code>
|
||||
curl -s https://<?php echo $_SERVER['SERVER_NAME'] ?>/get_info<br>
|
||||
</code>
|
||||
This will return the entire JSON descriptor variable <br>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<?php
|
||||
// only show the form if a password was generated
|
||||
if ($passType === 0 || $passType === 1 || $passType === 2 || $passType === 3) {
|
||||
echo '<form action="#" method="GET">';
|
||||
// Password output
|
||||
echo '<input type="text" onClick="this.select();" style="font-size:20pt;border:none;" value="' . htmlspecialchars($final) . '" id="myInput"><br><p>';
|
||||
echo 'Your password strength is: ' . $rating . '<br><p>';
|
||||
echo '<button onclick="myFunction()">Copy text</button>';
|
||||
echo '<input type="button" value="Generate Password" onClick="window.location.reload();">';
|
||||
|
||||
// Pass type selector
|
||||
echo '<select name="pt" onchange="this.form.submit()">';
|
||||
$types = ['Standard' => 0, 'Windows AD' => 1, 'Simple' => 2, 'Custom' => 3];
|
||||
foreach ($types as $label => $value) {
|
||||
echo '<option value="' . $value . '"' . ($passType == $value ? ' selected' : '') . '>' . $label . '</option>';
|
||||
}
|
||||
echo '</select>';
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Meter Code
|
||||
echo '<div id=meter>';
|
||||
for($i=0; $i < 10; $i++) {
|
||||
echo "<div";
|
||||
if($rating > $i) {
|
||||
echo " class=\"rating-{$rating}\"";
|
||||
}
|
||||
echo "></div>";
|
||||
}
|
||||
echo '</div>';
|
||||
////////////////////////////////////////////
|
||||
|
||||
// Total count
|
||||
echo '<br><p>There have been ' . $total . ' total passwords generated thus far.</p>';
|
||||
if($passType === 0 || $passType === 1 || $passType === 2) {
|
||||
|
||||
echo '<button id="info_panel" class="collapsible" type="button">';
|
||||
echo ' Click here for Password Rules';
|
||||
echo '</button>';
|
||||
|
||||
}
|
||||
// Custom slider UI (only for type 3)
|
||||
if ($passType == 3) {
|
||||
echo '</div><div class="card">';
|
||||
echo 'Hey folks, this thing finally works.<br><p>';
|
||||
|
||||
echo '<table width="500"><tr><td width="200">Minimum Word Length:</td>';
|
||||
echo '<td width="100"><input type="range" name="wMinIn" id="wMinIn" min="3" max="10" value="' . $wMin . '" oninput="updateTextInput1(this.value);"></td>';
|
||||
echo '<td width="50"><input type="number" name="wMin" id="wMin" min="3" max="10" value="' . $wMin . '" readonly></td></tr>';
|
||||
|
||||
echo '<tr><td width="200">Maximum Word Length:</td>';
|
||||
echo '<td width="100"><input type="range" name="wMaxIn" id="wMaxIn" min="3" max="10" value="' . $wMax . '" oninput="updateTextInput2(this.value);"></td>';
|
||||
echo '<td width="50"><input type="number" name="wMax" id="wMax" min="3" max="10" value="' . $wMax . '" readonly></td></tr>';
|
||||
|
||||
echo '<tr><td width="200">Number of Words:</td>';
|
||||
echo '<td width="100"><input type="range" name="wCountIn" id="wCountIn" min="1" max="5" value="' . $wCount . '" oninput="updateTextInput3(this.value);"></td>';
|
||||
echo '<td width="50"><input type="number" name="wCount" id="wCount" min="1" max="5" value="' . $wCount . '" readonly></td></tr>';
|
||||
|
||||
echo '<tr><td width="200">Special Characters:</td>';
|
||||
echo '<td width="100"><input type="range" name="sCharIn" id="sCharIn" min="0" max="4" value="' . $sChar . '" oninput="updateTextInput4(this.value);"></td>';
|
||||
echo '<td width="50"><input type="number" name="sChar" id="sChar" min="0" max="4" value="' . $sChar . '" readonly></td></tr>';
|
||||
|
||||
echo '<tr><td width="200">Number Length:</td>';
|
||||
echo '<td width="100"><input type="range" name="numLenIn" id="numLenIn" min="0" max="8" value="' . $numLen . '" oninput="updateTextInput5(this.value);"></td>';
|
||||
echo '<td width="50"><input type="number" name="numLen" id="numLen" min="0" max="8" value="' . $numLen . '" readonly></td></tr>';
|
||||
|
||||
echo '</table><br>';
|
||||
echo '<button type="submit" class="btn-gen">Generate Password</button>';
|
||||
//echo '<input type="button" value="Generate Password" onClick="window.location.reload()">';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
if ($passType == 0) {
|
||||
|
||||
echo '</div><div id="hidden_info" class="content">';
|
||||
echo '<p>I have a list of about 20k english words. When you go to this page, I select 60 of these words that are less than 13 characters, ';
|
||||
echo 'and use the one whose index matches the current second. Then, depending on the length of this word, ';
|
||||
echo 'this may or may not grab a couple more words from the list, and then it will generate a random number.';
|
||||
echo 'Then, it shuffles a list of special characters and sprinkles a few of those in with the words and numbers. ';
|
||||
echo 'Then, once all that is generated, it will shuffle all these things it generated and spit them back. ';
|
||||
echo 'It takes a hash of the generated password and compares it to a list of all hashes generated in the past. ';
|
||||
echo 'If the newly generated password is unique, it will print it out under here, otherwise it will try again. ';
|
||||
echo 'If it generates a bad password, just refresh until you get one you like. This site can never generate the same password twice. ';
|
||||
echo 'Also, this hash is a one way encryption, so the passwords cannot be re-created from the hashes.</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
if ($passType == 1) {
|
||||
echo '</div><div id="hidden_info" class="content"><p>';
|
||||
echo 'This password is always in the following format: <br>';
|
||||
echo '$Word1Word2Number$<br>';
|
||||
echo 'Where each word is less than 7 characters, the number is <br>';
|
||||
echo '3 digits, and the $ represents a Special Charater.</p></div><p>';
|
||||
}
|
||||
|
||||
if ($passType == 2) {
|
||||
echo '</div><div id="hidden_info" class="content"><br>';
|
||||
echo 'This simple password is in the following format:<br>';
|
||||
echo '!Password123<p>';
|
||||
echo 'Also, this draws from a simpler list of about 3k words.<p></div>';
|
||||
}
|
||||
|
||||
echo '</form>';
|
||||
} else {
|
||||
// duplicate hash case
|
||||
echo '<p>OH FUCK DUPLICATE PASSWORD!!!!1!!<br>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const panel = document.getElementById('info_panel');
|
||||
if (panel) {
|
||||
panel.addEventListener('click', function () {
|
||||
const help = document.getElementById('hidden_info');
|
||||
if (help.style.display === 'none' || help.style.display === '') {
|
||||
help.style.display = 'block';
|
||||
} else {
|
||||
help.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function myFunction() {
|
||||
const copyText = document.getElementById("myInput");
|
||||
copyText.select();
|
||||
document.execCommand("copy");
|
||||
alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
|
||||
// limit min/max slider relationship
|
||||
const elX = document.getElementById("wMaxIn");
|
||||
const elY = document.getElementById("wMinIn");
|
||||
function limit() {
|
||||
if (elX && elY) {
|
||||
if (elY.value > elX.value) {
|
||||
elY.value = elX.value;
|
||||
}
|
||||
document.getElementById("wMin").value = elY.value;
|
||||
}
|
||||
}
|
||||
if (elX && elY) {
|
||||
elX.onchange = limit;
|
||||
elY.onchange = limit;
|
||||
}
|
||||
|
||||
// helper to sync range to number input (already done in form via oninput)
|
||||
// but keep for safety
|
||||
function updateTextInput1(val) { document.getElementById('wMin').value = val; }
|
||||
function updateTextInput2(val) { document.getElementById('wMax').value = val; }
|
||||
function updateTextInput3(val) { document.getElementById('wCount').value = val; }
|
||||
function updateTextInput4(val) { document.getElementById('sChar').value = val; }
|
||||
function updateTextInput5(val) { document.getElementById('numLen').value = val; }
|
||||
|
||||
//Toggle the help text when the link is clicked
|
||||
document.getElementById('helpToggle').addEventListener('click', function () {
|
||||
const help = document.getElementById('helpText');
|
||||
if (help.style.display === 'none' || help.style.display === '') {
|
||||
help.style.display = 'block';
|
||||
} else {
|
||||
help.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
ob_end_flush();
|
||||
?>
|
||||
217
www/style.css
Normal file
217
www/style.css
Normal file
@ -0,0 +1,217 @@
|
||||
|
||||
|
||||
.active, .collapsible:hover {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 550px;
|
||||
padding: 0 px;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
.password_field {
|
||||
font-size:20pt;
|
||||
border:none;
|
||||
background-color: #ecf0f1;
|
||||
|
||||
}
|
||||
.button {
|
||||
|
||||
color: var(--clr-text);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Meter - dark‑mode look
|
||||
------------------------------------------------------------------ */
|
||||
#meter {
|
||||
display: flex;
|
||||
gap: 4px; /* doubled spacing between bars */
|
||||
padding: 8px; /* doubled padding inside container */
|
||||
align-items: center;
|
||||
|
||||
/* Card‑style background + border - same as before */
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--clr-border);
|
||||
border-radius: 4px;
|
||||
max-width: 420px; /* doubled max‑width */
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Bar - light‑dark contrast
|
||||
------------------------------------------------------------------ */
|
||||
#meter div {
|
||||
width: 36px; /* doubled width */
|
||||
height: 36px; /* doubled height */
|
||||
flex-shrink: 0;
|
||||
|
||||
/* Base colour (dark grey) - can be overridden via --bg */
|
||||
background: var(--bg, #4a5b6c);
|
||||
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
transition: background .25s ease, transform .2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Hover lift (still subtle on dark background) */
|
||||
#meter div:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Rating colour tiers - same vibrant colours
|
||||
------------------------------------------------------------------ */
|
||||
#meter div.rating-1,
|
||||
#meter div.rating-2 { --bg: #e74c3c; } /* red - poor */
|
||||
#meter div.rating-3,
|
||||
#meter div.rating-4 { --bg: #e67e22; } /* orange - fair */
|
||||
#meter div.rating-5,
|
||||
#meter div.rating-6 { --bg: #f1c40f; } /* yellow - average */
|
||||
#meter div.rating-7,
|
||||
#meter div.rating-8 { --bg: #2ecc71; } /* greenyellow - good */
|
||||
#meter div.rating-9,
|
||||
#meter div.rating-10{ --bg: #27ae60; } /* green - excellent */
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Optional - if you want to add a “selected” state
|
||||
------------------------------------------------------------------ */
|
||||
#meter div.selected {
|
||||
box-shadow: 0 0 0 2px var(--clr-accent);
|
||||
}
|
||||
|
||||
/* Standard Matt-Cloud CSS */
|
||||
|
||||
/* -------------------------------------------------
|
||||
1. Global settings & color palette
|
||||
------------------------------------------------- */
|
||||
:root {
|
||||
/* Dark theme - body & card backgrounds */
|
||||
--bg-body: #2c3e50; /* main page background */
|
||||
--bg-card: #34495e; /* card / panel background */
|
||||
--bg-sidebar: #3d566e; /* sidebar background (slightly lighter) */
|
||||
/* Accent / link colour */
|
||||
--clr-accent: #3498db; /* blue accent for links */
|
||||
/* Text colour */
|
||||
--clr-text: #ecf0f1; /* light whiteish text */
|
||||
/* Borders / accents */
|
||||
--clr-border: #1f2b38; /* dark border colour */
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
/* Body */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg-body);
|
||||
color: var(--clr-text);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a { color: var(--clr-accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* -------------------------------------------------
|
||||
2. Layout - wrapper, sidebar, main
|
||||
------------------------------------------------- */
|
||||
.wrapper { display: flex; min-height: 100vh; }
|
||||
.main { flex: 1; padding: 1rem; }
|
||||
|
||||
/* -------------------------------------------------
|
||||
3. Card components
|
||||
------------------------------------------------- */
|
||||
.card {
|
||||
max-width: 550px;
|
||||
margin: 20px auto 1rem auto;
|
||||
padding: 20px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--clr-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.collapsible {
|
||||
max-width: 550px;
|
||||
margin: 20px auto 1rem auto;
|
||||
padding: 20px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--clr-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.3);
|
||||
text-align: left;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.active, .collapsible:hover {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 550px;
|
||||
margin: 20px auto 1rem auto;
|
||||
padding: 20px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--clr-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.3);
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* -------------------------------------------------
|
||||
4. Tables
|
||||
------------------------------------------------- */
|
||||
table, th, td {
|
||||
border: 2px solid var(--clr-border);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td { padding: 10px; }
|
||||
|
||||
/* Alternate row colour for metrics table */
|
||||
#host_metrics_table tbody tr td:nth-of-type(even) {
|
||||
background: #3e5c78; /* slight contrast */
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
5. Lists & headings
|
||||
------------------------------------------------- */
|
||||
h1, h2, h3, h4 { color: var(--clr-text); margin: 0 0 .4rem 0; }
|
||||
ul { list-style: none; padding: 0; }
|
||||
ol { list-style: none; padding: 0; }
|
||||
li { margin-bottom: 10px; color: var(--clr-text); }
|
||||
|
||||
/* -------------------------------------------------
|
||||
6. Components grid
|
||||
------------------------------------------------- */
|
||||
.components {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.component {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--clr-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.component h3 { margin: 0 0 5px; }
|
||||
|
||||
/* -------------------------------------------------
|
||||
7. Help toggle / modal
|
||||
------------------------------------------------- */
|
||||
.help-link {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--clr-accent);
|
||||
text-align: right;
|
||||
}
|
||||
.help-link:hover { text-decoration: underline; }
|
||||
#helpText { display: none; }
|
||||
|
||||
/* -------------------------------------------------
|
||||
8. Misc helpers
|
||||
------------------------------------------------- */
|
||||
/* Hide numeric markers in metric columns (if any) */
|
||||
#host_metrics_column td { list-style: none; padding-left: 0; margin-left: 0; }
|
||||
Reference in New Issue
Block a user