commit f2a6525224070e7b7950d65ac2df0323ecda1e33 Author: Frederico Falcao Date: Fri May 30 10:46:17 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37922ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +vendor/ + +business/_databaseCredentials.php + +tech/backlog.json +tech/tabs/03-third-parties/passwords.json.enc diff --git a/_authenticate.php b/_authenticate.php new file mode 100644 index 0000000..d7f4b36 --- /dev/null +++ b/_authenticate.php @@ -0,0 +1,7 @@ + PDO::ERRMODE_EXCEPTION + ]); + +} catch (PDOException $e) { + echo 'Database error: ' . $e->getMessage(); +} +function db_select($tblName, $cols = [], $filter = []) { + global $pdo; + // Defaults for fitler + if (is_array($filter) && empty($filter)) $filter = ["true"]; + if (is_string($filter)) $filter = [$filter]; + + // Execute query + try { + $sql_query = 'SELECT '.implode(",",$cols).' FROM "'.$tblName.'" WHERE '.implode(" AND ",$filter).';'; + $stmt = $pdo->query($sql_query); + } catch (PDOException $e) { + + echo "DB QUERY ERROR: ".$e->getMessage(); die("\n\nwhile trying: $sql_query"); + } + + // Fetch and display results + $response = $stmt->fetchAll(PDO::FETCH_ASSOC); + + return $response; + +} diff --git a/business/_navbar.php b/business/_navbar.php new file mode 100644 index 0000000..ef5e003 --- /dev/null +++ b/business/_navbar.php @@ -0,0 +1,30 @@ + + + + + + diff --git a/business/dashboard/customers.php b/business/dashboard/customers.php new file mode 100644 index 0000000..5ef6fd8 --- /dev/null +++ b/business/dashboard/customers.php @@ -0,0 +1,55 @@ + 'John Doe', 'email' => 'john@example.com', 'joined' => '2024-12-01', 'status' => 'Active'], + ['name' => 'Jane Smith', 'email' => 'jane@example.com', 'joined' => '2025-01-15', 'status' => 'Active'], + ['name' => 'Carlos Perez', 'email' => 'carlos@example.com', 'joined' => '2025-03-10', 'status' => 'Inactive'], +]; +?> + + + + + Customers Dashboard + + + + + + +
+

πŸ‘₯ Customers

+ + + + + + + + + + + + + + + + + + + + +
NameEmailJoined DateStatus
+ + Active + + Inactive + +
+
+ + + + diff --git a/business/dashboard/sales.php b/business/dashboard/sales.php new file mode 100644 index 0000000..4b891c5 --- /dev/null +++ b/business/dashboard/sales.php @@ -0,0 +1,82 @@ + + + + + + Sales Dashboard + + + + + + + +
+

πŸ“ˆ Sales Overview

+
+
+
+
+
Today's Sales
+
+
+
+
+
+
This Month
+
+
+
+
+
+
New Orders
+
+
+
+
+
+
Top Product
+
+
+
+ +

Recent Orders

+ + + + + + + + + +
Order IDCustomerAmountStatus
#1001John Doe$125.00Completed
#1002Jane Smith$79.00Pending
#1003Bob Johnson$59.99Cancelled
+
+ + + + diff --git a/business/index.php b/business/index.php new file mode 100644 index 0000000..1d33ab1 --- /dev/null +++ b/business/index.php @@ -0,0 +1,106 @@ + '$25,300', + 'New Customers' => '120', + 'Active Subscriptions' => '350', + 'Churn Rate' => '2.5%', + 'Monthly Revenue' => '$7,400', + 'Site Visitors Today' => '1,540', +]; +?> + + + + + Business Metrics Dashboard + + + + + + + +
+

Key Metrics

+
+ $value): ?> +
+
+
+
+
+
+ +
+

+

Cohort Analysis

+
+ + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + +
+ + + + + + + diff --git a/index.php b/index.php new file mode 100644 index 0000000..8ae70fd --- /dev/null +++ b/index.php @@ -0,0 +1,29 @@ + + + + + + Login + + + + + +
+
+
+ +

Logged in

+ + + +
+
+
+ + + diff --git a/login.php b/login.php new file mode 100644 index 0000000..77223e6 --- /dev/null +++ b/login.php @@ -0,0 +1,51 @@ + + + + + + Login + + + + +
+
+
+ +

πŸ” Please Login

+ + +
+ + +
+
+ + +
+ +
+ +
+
+
+ + + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..e5ab4b6 --- /dev/null +++ b/logout.php @@ -0,0 +1,6 @@ + diff --git a/tech/api/_authenticateApiRequest.php b/tech/api/_authenticateApiRequest.php new file mode 100644 index 0000000..c739fc5 --- /dev/null +++ b/tech/api/_authenticateApiRequest.php @@ -0,0 +1,16 @@ + 'Unauthorized']); + exit; + +} diff --git a/tech/api/_dbCredentials.php b/tech/api/_dbCredentials.php new file mode 100644 index 0000000..460b480 --- /dev/null +++ b/tech/api/_dbCredentials.php @@ -0,0 +1,7 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (PDOException $e) { + die("❌ Connection failed: " . $e->getMessage()); + } + } + + return $db; +} + +function insertInto($table, $data) { + $db = connectToDb(); + + $columns = '"' . implode('","', array_keys($data)) . '"'; + $placeholders = ':' . implode(', :', array_keys($data)); + + $sql = "INSERT INTO \"$table\" ($columns) VALUES ($placeholders)"; + + $stmt = $db->prepare($sql); + $stmt->execute($data); + + return $db->lastInsertId(); +} + +function selectFrom($table, $conditions = []) { + $db = connectToDb(); + + $whereClause = ''; + if (!empty($conditions)) { + $clauses = []; + foreach ($conditions as $key => $value) { + $clauses[] = "\"$key\" = :$key"; + } + $whereClause = 'WHERE ' . implode(' AND ', $clauses); + } + + $sql = "SELECT * FROM \"$table\" $whereClause"; + + $stmt = $db->prepare($sql); + $stmt->execute($conditions); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function update($table, $data, $conditions) { + $db = connectToDb(); + + $setParts = []; + foreach ($data as $key => $value) { + $setParts[] = "\"$key\" = :set_$key"; + } + $setClause = implode(', ', $setParts); + + $whereParts = []; + foreach ($conditions as $key => $value) { + $whereParts[] = "\"$key\" = :where_$key"; + } + $whereClause = implode(' AND ', $whereParts); + + $sql = "UPDATE \"$table\" SET $setClause WHERE $whereClause"; + + $params = []; + foreach ($data as $key => $value) { + $params['set_' . $key] = $value; + } + foreach ($conditions as $key => $value) { + $params['where_' . $key] = $value; + } + + $stmt = $db->prepare($sql); + return $stmt->execute($params); +} +function upsert($table, $data, $conflictColumns) { + $db = connectToDb(); + + $columns = '"' . implode('","', array_keys($data)) . '"'; + $placeholders = ':' . implode(', :', array_keys($data)); + + $updateParts = []; + foreach ($data as $key => $value) { + $updateParts[] = "\"$key\" = EXCLUDED.\"$key\""; + } + $updateClause = implode(', ', $updateParts); + + $conflicts = '"' . implode('","', (array)$conflictColumns) . '"'; + + $sql = "INSERT INTO \"$table\" ($columns) VALUES ($placeholders) + ON CONFLICT ($conflicts) DO UPDATE SET $updateClause"; + + $stmt = $db->prepare($sql); + return $stmt->execute($data); +} diff --git a/tech/api/_telegramCredentials.php b/tech/api/_telegramCredentials.php new file mode 100644 index 0000000..0b687d7 --- /dev/null +++ b/tech/api/_telegramCredentials.php @@ -0,0 +1,3 @@ + TELEGRAM_CHAT_ID, + 'text' => $message + ]; + + // Initialize cURL + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + + if (curl_errno($ch)) { + error_log("Telegram cURL error: " . curl_error($ch)); + curl_close($ch); + return false; + } + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + return $httpCode === 200; +} diff --git a/tech/api/rebuildWebsite.php b/tech/api/rebuildWebsite.php new file mode 100644 index 0000000..aff5aaa --- /dev/null +++ b/tech/api/rebuildWebsite.php @@ -0,0 +1,16 @@ + 'βœ… Authenticated request']); + +/* +* +* HERE SHOULD BE THE CODE TO RUN: NPM BUILD, etc... +* (it's associated to deploy button in (upload.php) +* +*/ +exit; diff --git a/tech/api/updateProductList.php b/tech/api/updateProductList.php new file mode 100644 index 0000000..3183ad2 --- /dev/null +++ b/tech/api/updateProductList.php @@ -0,0 +1,96 @@ + + [ + "sqlTableName" => "MealPrep", + "columns" => [ + "name" + ], + "keyColumn" => "strapiDocumentId" + ], + "api::meal-plan.meal-plan" => + [ + "sqlTableName" => "MealPlan", + "columns" => [ + "name", + "numberOfItems", + "price", + ], + "keyColumn" => "strapiDocumentId" + ], + "api::single-meal.single-meal" => + [ + "sqlTableName" => "SingleMeal", + "columns" => [ + "name" + ], + "keyColumn" => "strapiDocumentId" + ], +]; + + +// Get raw POST body once +$rawInput = file_get_contents("php://input"); +$event = json_decode($rawInput, true); + +// Handle malformed JSON +if (!is_array($event) || !isset($event["uid"])) { + http_response_code(400); + echo json_encode(['error' => 'Invalid JSON or missing uid']); + exit; +} + +telegramSendMessage("✏️ New content updated at STRAPI (".$event["uid"].")"); +try { + // πŸ”Ž Check if the CONTENT-TYPE (uid) is in the list we care about + if (in_array($event["uid"], array_keys($reactToContentTypes))) { + + // 🎯 Get content type settings (SQL table & columns mapping) + $contentType = $reactToContentTypes[$event["uid"]]; + $sqlTblName = $contentType["sqlTableName"]; + + // πŸ— Build the data array for upsert + $cols = []; + foreach ($contentType["columns"] as $c) { + if (isset($event["entry"][$c])) { + $cols[$c] = $event["entry"][$c]; + } else { + // You might want to handle missing fields here + $cols[$c] = null; + } + } + + // πŸ”‘ Get the unique key column for upsert + $keyColumn = $contentType["keyColumn"]; + + // πŸš€ Run the UPSERT (insert or update) + $success = upsert($sqlTblName, $cols, $keyColumn); + + if ($success) { + telegramSendMessage("πŸ”„ Data successfully propagated to SUPABASE"); + } else { + telegramSendMessage("❌ Upsert failed"); + } + + } else { + // ⚠️ uid not in the list of content types we're watching + telegramSendMessage('ℹ️ No action taken for uid: ' . $event["uid"]); + } + +} catch (Exception $e) { + // 🚨 Catch and report any exceptions + http_response_code(500); + echo json_encode(['error' => 'Server Error','details' => $e->getMessage()]); + telegramSendMessage("500 Server Error: (".__FILE__.") ".$e->getMessage()); +} + +exit; diff --git a/tech/index.php b/tech/index.php new file mode 100644 index 0000000..d83f2f2 --- /dev/null +++ b/tech/index.php @@ -0,0 +1,66 @@ + + + + + + + GROWFIT ADMIN + + + + +
+ ← back to main +
+
+
+

Tech Admin Portal

+
+ + +
+
+ +
+
+ + + + + + + + + diff --git a/tech/tabs/01-deploy/00_README.md b/tech/tabs/01-deploy/00_README.md new file mode 100644 index 0000000..e609b71 --- /dev/null +++ b/tech/tabs/01-deploy/00_README.md @@ -0,0 +1,56 @@ +# 01-deploy β€” Deploy Tab Documentation + +This tab provides a web interface for source code deployment and management via Git. + +## Features + +- **Deploy a specific commit**: Checkout a chosen commit and run build scripts with one click. +- **Pull and merge ZIP from GitHub/GitLab**: Download a repo archive at a specific commit and merge it into the current working tree. +- **Direct ZIP upload**: Upload a ZIP file from your computer to merge and commit to the repository. +- **Browse commit history**: View recent commits, examine their details in a modal, and deploy any commit to a chosen target. +- **Multiple deploy targets**: Supports easy deployment to `www` (production), `beta`, or `beta-2` via dropdown actions. + +## File Overview + +- `01_constants.php` β€” Defines `$repoDir`, the local path to your code repository. Used everywhere the repo is referenced. +- `02_supportFuncs.php` β€” Helper functions for: + - Running Git commands + - Listing commits (`listGitCommits()`) + - Storing and loading last deploy info (`getLastDeployData()`, `setLastDeployData()`) + - Executing shell command sequences with formatted output (`runShellSteps()`) + - Sanitizing inputs for hashes/user paths (`sanitizeRepoInput()`) +- `03_handler.php` β€” Main backend logic: + - Handles POST/GET for deploys, ZIP pulls, uploads, and commit info retrieval. + - Echoes info/error/success blocks for each operation. + - All sensitive input is sanitized before use in shell commands. +- `04_nav-item.html.php` β€” Contains the navigation tab button HTML for activating the Deploy tab. +- `05_content.html.php` β€” Main UI HTML: + - Forms for ZIP pull/upload. + - Table of recent commits, each with deploy dropdown for all targets. + - Commit history linked to the modal for details. +- `06_modals.html.php` β€” Bootstrap modal for commit details, loaded on demand by JavaScript. +- `07_javascript.js` β€” + - Handles dynamic loading of commit info into the modal via AJAX/fetch. + - Live URL preview while typing GitHub repo/hash for ZIP pulls. + +## Usage + +1. **Deploy a commit**: Click the dropdown next to a commit, select your target, and the backend will checkout, install, build, and deploy that revision. +2. **Merge from GitHub**: Fill in username/repo/hash and click to pull & merge that remote ZIP into your codebase. +3. **Upload ZIP**: Upload a ZIP file (from external export or other CI pipeline) and have its content merged and committed to the repo. +4. **View commit details**: Click any commit in the table to view the full diff, author, date, and message in a modal. + +## Security & Safety + +- Inputs are sanitized before being passed to shell or git commands. +- Only users with web access to the admin panel can trigger deploy steps. +- All output from shell commands is displayed to the user for transparency. + +## Customization + +- To update what happens on deployment, edit the `$steps` arrays in `03_handler.php`. +- For new deploy targets, adjust forms and handler logic as needed. +- The build sequence (npm, asto, rsync, etc.) can be tailored for your tech stack. + +--- +This tab is a minimal self-service deployment panel fitting teams who need to pull, merge, and deploy new code quickly, review git history, and provide a safe admin pathway for ops and releases. \ No newline at end of file diff --git a/tech/tabs/01-deploy/01_constants.php b/tech/tabs/01-deploy/01_constants.php new file mode 100644 index 0000000..df9ddcb --- /dev/null +++ b/tech/tabs/01-deploy/01_constants.php @@ -0,0 +1,10 @@ + diff --git a/tech/tabs/01-deploy/02_supportFuncs.php b/tech/tabs/01-deploy/02_supportFuncs.php new file mode 100644 index 0000000..1422ef0 --- /dev/null +++ b/tech/tabs/01-deploy/02_supportFuncs.php @@ -0,0 +1,57 @@ +'', 'repo'=>'', 'hash'=>'']; +} + +function setLastDeployData($repoDir, $data) { + $jsonFile = $repoDir . '/deploy_last.json'; + file_put_contents($jsonFile, json_encode($data)); +} + + +function runShellSteps($steps, $cwd = null) { + // Runs an array of ['desc'=>..., 'cmd'=>...] steps, prints output and returns overall success + $allOk = true; + echo "
Shell Output:\n";
+    foreach ($steps as $step) {
+        $desc = $step['desc'] ?? '';
+        $cmd  = $step['cmd'];
+        echo "\n# $desc\n$ $cmd\n";
+        passthru(($cwd ? "cd ".escapeshellarg($cwd)." && " : "") . "$cmd 2>&1", $ret);
+        if ($ret !== 0 && stripos($desc, 'commit') === false) {
+            echo "\n❌ Error in step: $desc\n";
+            $allOk = false;
+            break;
+        }
+    }
+    echo "
"; + return $allOk; +} + +function sanitizeRepoInput($input, $type = 'alphanum') { + // Usage: sanitizeRepoInput($_POST['repo']), etc + if ($type === 'hash') return preg_replace('/[^a-fA-F0-9]/', '', $input); + if ($type === 'repo') return preg_replace('/[^a-zA-Z0-9_-]/', '', $input); + return trim($input); +} +?> diff --git a/tech/tabs/01-deploy/03_handler.php b/tech/tabs/01-deploy/03_handler.php new file mode 100644 index 0000000..250e5a8 --- /dev/null +++ b/tech/tabs/01-deploy/03_handler.php @@ -0,0 +1,126 @@ + "/ExtraSpace/www.growfit.fi", + "beta" => "/ExtraSpace/beta.growfit.fi", + "alpha" => "/ExtraSpace/alpha.growfit.fi", + ]; + if (!isset($folders[$target])) { + echo "
❌ INTERNAL ERROR: Deployment folder doesn't exist.
"; + return; + } + $gitWorkTree = $folders[$target]; + + if ($commit) { + echo "
πŸš€ Deploying commit: $commit to $target
"; + $steps = [ + ['desc'=>"Clear the target folder", 'cmd'=>"rm -rf $gitWorkTree; mkdir $gitWorkTree"], + ['desc'=>"Checkout commit", 'cmd'=>"GIT_DIR=$gitDir git archive $commit | tar -x -C $gitWorkTree"], + ['desc'=>"Install NPM packages", 'cmd'=>"cd $gitWorkTree && npm install"], + ['desc'=>"Set Astro prerender", 'cmd'=>"cd $gitWorkTree && find src/ -type f -name \"*.astro\" -exec sed -i 's/prerender = false/prerender = true/g' {} +"], + ['desc'=>"Build site", 'cmd'=>"cd $gitWorkTree && npm run build"] + ]; + $allOk = runShellSteps($steps); + + if ($allOk) { + echo "
βœ… Commit $commit deployed to $target.
"; + } else { + echo "
❌ One or more commands failed.
"; + } + } +} + +// 3. GET COMMIT INFO +if (isset($_GET['get_commit_info'])) { + $hash = sanitizeRepoInput($_GET['get_commit_info'], 'hash'); + if (!$hash) { + echo '
Invalid commit hash.
'; exit; + } + $cmd = gitCmd($repoDir, 'show --stat --pretty=format:"Commit: %H%nAuthor: %an <%ae>%nDate: %ad%n%n%s%n%b" --date=iso ' . escapeshellarg($hash) . ' -m'); + exec($cmd . ' 2>&1', $output, $retval); + if ($retval !== 0) { + echo '
Error fetching commit info.
'; exit; + } + echo '
'.htmlspecialchars(implode("\n", $output)).'
'; exit; +} + +// 4. PULL MERGE ZIP +if (isset($_POST['pull_merge_zip'])) { + $username = sanitizeRepoInput($_POST['username'], 'repo'); + $repo = sanitizeRepoInput($_POST['repo'], 'repo'); + $hash = sanitizeRepoInput($_POST['hash'], 'hash'); + if ($username && $repo && $hash) { + setLastDeployData($repoDir, ['username'=>$username,'repo'=>$repo,'hash'=>$hash]); + $zipUrl = "https://github.com/$username/$repo/archive/$hash.zip"; + $tmpZip = "/tmp/{$repo}_$hash.zip"; + $tmpExtract = "/tmp/{$repo}_$hash"; + $extractedSubdir = "$tmpExtract/{$repo}-$hash"; + $commitMsg = "Pulled ZIP from $username/$repo@$hash"; + + echo "
πŸ“₯ Downloading ZIP from: $zipUrl
"; + + $steps = [ + ['desc'=>"Download ZIP", 'cmd'=>"wget -q -O ".escapeshellarg($tmpZip)." ".escapeshellarg($zipUrl)], + ['desc'=>"Clean old extraction", 'cmd'=>"rm -rf ".escapeshellarg($tmpExtract)], + ['desc'=>"Extract ZIP", 'cmd'=>"unzip -q ".escapeshellarg($tmpZip)." -d ".escapeshellarg($tmpExtract)], + ['desc'=>"Rsync to repo", 'cmd'=>"rsync -a --delete --exclude='.git' $extractedSubdir/ $repoDir/"], + ['desc'=>"Git add", 'cmd'=>gitCmd($repoDir, "add -A")], + ['desc'=>"Git commit", 'cmd'=>gitCmd($repoDir, "commit -m " . escapeshellarg($commitMsg))] + ]; + + $allOk = runShellSteps($steps); + + if ($allOk) { + echo "
βœ… ZIP merged and committed as: $commitMsg
"; + } else { + echo "
❌ One or more commands failed.
"; + } + } else { + echo "
❌ Invalid input for username/repo/hash.
"; + } +} + +// 5. Handle direct ZIP upload & merge +if (isset($_POST['upload_zip_btn']) && isset($_FILES['upload_zip']) && $_FILES['upload_zip']['error'] === UPLOAD_ERR_OK) { + $uploadedFile = $_FILES['upload_zip']; + $commitMsg = trim($_POST['commit_msg'] ?? ''); + if (!$commitMsg) $commitMsg = "Uploaded ZIP: " . $uploadedFile['name'] . " on " . date('Y-m-d H:i'); + $tmpZip = $uploadedFile['tmp_name']; + $zipName = basename($uploadedFile['name']); + $tmpExtract = "/tmp/upload_zip_" . uniqid(); + mkdir($tmpExtract, 0777, true); + + echo "
πŸ“€ Uploading & extracting: $zipName
"; + + $extractDir = $tmpExtract . ((strpos($uploadedFile['name'], "project-bolt-") === 0) ? "/project" : ""); + + + + $steps = [ + ['desc'=>"Extract ZIP", 'cmd'=>"unzip -q " . escapeshellarg($tmpZip) . " -d " . escapeshellarg($tmpExtract)], + ['desc'=>"Git add", 'cmd'=>"GIT_DIR=$gitDir GIT_WORK_TREE=".escapeshellarg($extractDir)." git add -A"], + ['desc'=>"Git commit", 'cmd'=>"GIT_DIR=$gitDir GIT_WORK_TREE=".escapeshellarg($extractDir)." git commit -m ".escapeshellarg($commitMsg)], + ['desc'=>"Pushing to GITHUB", 'cmd'=>"GIT_DIR=$gitDir GIT_WORK_TREE=".escapeshellarg($extractDir)." git push"], + ['desc'=>"Cleaning Up", 'cmd'=>"rm -rf " . escapeshellarg($tmpExtract)], + ]; + + $allOk = runShellSteps($steps); + + if ($allOk) { + echo "
βœ… ZIP merged and committed: " . htmlspecialchars($commitMsg) . "
"; + } else { + echo "
ℹ️ No changes to commit, or an error occurred.
"; + } +} +?> diff --git a/tech/tabs/01-deploy/04_nav-item.html.php b/tech/tabs/01-deploy/04_nav-item.html.php new file mode 100644 index 0000000..cf0e333 --- /dev/null +++ b/tech/tabs/01-deploy/04_nav-item.html.php @@ -0,0 +1,10 @@ + + diff --git a/tech/tabs/01-deploy/05_content.html.php b/tech/tabs/01-deploy/05_content.html.php new file mode 100644 index 0000000..0931bfd --- /dev/null +++ b/tech/tabs/01-deploy/05_content.html.php @@ -0,0 +1,95 @@ + + +
+ +
+ +

https://github.com/USERNAME/REPO/archive/HASH.zip

+
+ + + + + +
+
+ +
+ + +
+ +
+ + + +
+
The ZIP will be extracted and committed to the repository. Bolt.new /project auto-unwrapped.
+
+ + + + + + + + + + + + + + + + + + + + +
CommitDateDescriptionActions
+ + + + +
+ + +
+
+
diff --git a/tech/tabs/01-deploy/06_modals.html.php b/tech/tabs/01-deploy/06_modals.html.php new file mode 100644 index 0000000..76226ad --- /dev/null +++ b/tech/tabs/01-deploy/06_modals.html.php @@ -0,0 +1,23 @@ + + + + + diff --git a/tech/tabs/01-deploy/07_javascript.js b/tech/tabs/01-deploy/07_javascript.js new file mode 100644 index 0000000..0742140 --- /dev/null +++ b/tech/tabs/01-deploy/07_javascript.js @@ -0,0 +1,46 @@ +// +// FILE: 07_javascript.js +// Description: the javascript code that will be added to the website + +document.addEventListener('DOMContentLoaded', function() { + var commitInfoModal = document.getElementById('commitInfoModal'); + if (commitInfoModal) { + commitInfoModal.addEventListener('show.bs.modal', function (event) { + var button = event.relatedTarget; + var hash = button.getAttribute('data-commit-hash'); + var modalBody = document.getElementById('commitModalBody'); + modalBody.innerHTML = `
+
+
Loading…
+
`; + fetch('get_commit_info.php?hash=' + encodeURIComponent(hash)) + .then(resp => resp.text()) + .then(html => { modalBody.innerHTML = html; }) + .catch(() => { + modalBody.innerHTML = '
Failed to load commit info.
'; + }); + }); + } +}); + +document.addEventListener('DOMContentLoaded', function () { + const usernameInput = document.querySelector('input[name="username"]'); + const repoInput = document.querySelector('input[name="repo"]'); + const hashInput = document.querySelector('input[name="hash"]'); + const urlPreview = document.getElementById('github_url_zip'); + + function updateUrl() { + const username = usernameInput.value.trim() || 'USERNAME'; + const repo = repoInput.value.trim() || 'REPO'; + const hash = hashInput.value.trim() || 'HASH'; + + const url = `https://github.com/${username}/${repo}/archive/${hash}.zip`; + urlPreview.innerHTML = `${url}`; + } + + [usernameInput, repoInput, hashInput].forEach(input => { + input.addEventListener('input', updateUrl); + }); + + updateUrl(); // Run once on load +}); diff --git a/tech/tabs/02-backlog/01_constants.php b/tech/tabs/02-backlog/01_constants.php new file mode 100644 index 0000000..bd51b0f --- /dev/null +++ b/tech/tabs/02-backlog/01_constants.php @@ -0,0 +1,5 @@ + uniqid('b_'), + 'title' => trim($_POST['backlog_title'] ?? ''), + 'desc' => trim($_POST['backlog_desc'] ?? ''), + 'status' => $status, + 'type' => $type, + 'created' => date('Y-m-d H:i:s'), + ]; + saveBacklog($backlogFile, $items); + echo "
βœ… Backlog item added.
"; +} + +// Edit +if ( + isset( + $_POST['edit_backlog_id'], + $_POST['edit_backlog_title'], + $_POST['edit_backlog_desc'], + $_POST['edit_backlog_status'], + $_POST['edit_backlog_type'] + ) +) { + $status = in_array($_POST['edit_backlog_status'], $valid_statuses) ? $_POST['edit_backlog_status'] : 'in progress'; + $type = in_array($_POST['edit_backlog_type'], $valid_types) ? $_POST['edit_backlog_type'] : 'bug'; + $items = loadBacklog($backlogFile); + foreach ($items as &$item) { + if ($item['id'] === $_POST['edit_backlog_id']) { + $item['title'] = trim($_POST['edit_backlog_title']); + $item['desc'] = trim($_POST['edit_backlog_desc']); + $item['status'] = $status; + $item['type'] = $type; + } + } + saveBacklog($backlogFile, $items); + echo "
✏️ Backlog item updated.
"; +} + +// Delete +if (isset($_POST['delete_backlog_id'])) { + $items = loadBacklog($backlogFile); + $items = array_filter($items, fn($item) => $item['id'] !== $_POST['delete_backlog_id']); + saveBacklog($backlogFile, $items); + echo "
πŸ—‘ Backlog item deleted.
"; +} diff --git a/tech/tabs/02-backlog/04_nav-item.html.php b/tech/tabs/02-backlog/04_nav-item.html.php new file mode 100644 index 0000000..a2b6b14 --- /dev/null +++ b/tech/tabs/02-backlog/04_nav-item.html.php @@ -0,0 +1,6 @@ + diff --git a/tech/tabs/02-backlog/05_content.html.php b/tech/tabs/02-backlog/05_content.html.php new file mode 100644 index 0000000..ca65f96 --- /dev/null +++ b/tech/tabs/02-backlog/05_content.html.php @@ -0,0 +1,119 @@ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ + + + + + + + + + + + + 1, 'done' => 2, 'cancelled' => 3]; + $type_priority = ['bug' => 1, 'improvement' => 2, 'nice-to-have' => 3]; + // Compare by status first + if ($status_priority[$a['status']] != $status_priority[$b['status']]) { + return $status_priority[$a['status']] - $status_priority[$b['status']]; + } + + // Status is the same, compare by type next + return $type_priority[$a['type']] - $type_priority[$b['type']]; + }); + foreach ($backlog as $item): ?> + + + + + + + + + + +
TTitleDescriptionStatusCreatedActions
+ 'bi-bug-fill text-danger', + 'improvement' => 'bi-tools text-success', + 'nice-to-have' => 'bi-star text-secondary' + ][$type] ?? ''; + ?> + + + + + 'warning', + 'done' => 'success', + 'cancelled' => 'secondary' + ][$status] ?? 'light'; + ?> + + + + + + +
+ + +
+      +
+
diff --git a/tech/tabs/02-backlog/06_modals.html.php b/tech/tabs/02-backlog/06_modals.html.php new file mode 100644 index 0000000..159c969 --- /dev/null +++ b/tech/tabs/02-backlog/06_modals.html.php @@ -0,0 +1,71 @@ + + + diff --git a/tech/tabs/02-backlog/07_javascript.js b/tech/tabs/02-backlog/07_javascript.js new file mode 100644 index 0000000..6a9edb0 --- /dev/null +++ b/tech/tabs/02-backlog/07_javascript.js @@ -0,0 +1,56 @@ +function editBacklog(id, title, desc, status, type) { + document.getElementById('editBacklogId').value = id; + document.getElementById('editBacklogTitle').value = title; + document.getElementById('editBacklogDesc').value = desc; + document.getElementById('editBacklogStatus').value = status; + document.getElementById('editBacklogType').value = type; + + const modalEl = document.getElementById('editBacklogModal'); + const modal = new bootstrap.Modal(modalEl); + modal.show(); + +} + +/** + * Escape HTML special characters in a string. + */ +function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +/** + * Show modal with backlog item details. + */ +function viewBacklog(id, title, desc, status, type, created) { + document.getElementById('viewBacklogTitle').textContent = title; + const descEl = document.getElementById('viewBacklogDesc'); + const escapedDesc = escapeHtml(desc); + descEl.innerHTML = escapedDesc.replace(/\n/g, '
'); + const statusBadge = document.getElementById('viewBacklogStatusBadge'); + const statusMap = { 'in progress': 'warning', 'done': 'success', 'cancelled': 'secondary' }; + statusBadge.textContent = status.charAt(0).toUpperCase() + status.slice(1); + statusBadge.className = 'badge bg-' + (statusMap[status] || 'light'); + document.getElementById('viewBacklogCreated').textContent = created; + + const typeMap = { + 'bug': 'bi-bug-fill text-danger', + 'improvement': 'bi-tools text-success', + 'nice-to-have': 'bi-star text-secondary' + }; + const typeIcon = typeMap[type] || ''; + const typeEl = document.getElementById('viewBacklogType'); + if (typeIcon) { + typeEl.innerHTML = ` ${type.charAt(0).toUpperCase() + type.slice(1)}`; + } else { + typeEl.textContent = ''; + } + + const modalEl = document.getElementById('viewBacklogModal'); + const modal = new bootstrap.Modal(modalEl); + modal.show(); +} diff --git a/tech/tabs/03-third-parties/00_README.md b/tech/tabs/03-third-parties/00_README.md new file mode 100644 index 0000000..1886a28 --- /dev/null +++ b/tech/tabs/03-third-parties/00_README.md @@ -0,0 +1,46 @@ +# 03-third-parties β€” Password Manager Tab + +This tab implements a web-based lightweight password manager for teams. +It allows you to save, view, and manage passwords and related info for third-party accounts/services in a secure file. + +## Features + +- **Store**: Save site/service, username, password, OTP secret, and card details for each entry. +- **Password Generation**: Generate strong random passwords. +- **Copy to Clipboard**: Instantly copy any password to clipboard with one click (no password is exposed/revealed by default). +- **OTP Code Preview**: If an OTP secret is stored, the current code is shown next to the entry. +- **Edit Protections**: All form submissions use POST and require confirmation before deleting an entry. +- **Encrypted Storage**: All credentials are saved in an encrypted JSON file (`passwords.json.enc`). + +## File Overview + +- `01_constants.php` β€” Defines file paths and any constants for this tab. +- `02_supportFuncs.php` β€” Helper functions for managing and encrypting credentials, generating OTPs, etc. +- `03_handler.php` β€” Logic for processing form submissions and updating the password list. +- `04_nav-item.html.php` β€” Generates the navigation item for this password manager tab. +- `05_content.html.php` β€” Main UI and form rendering for password management, entry listing, and actions. +- `06_modals.html.php` β€” (Not used / empty or for modal dialogs). +- `07_javascript.js` β€” Handles client-side UX, e.g. copying passwords to clipboard. +- `passwords.json.enc` β€” The encrypted vault file storing all team credentials. + +## Usage + +1. Navigate to the "Password Manager" tab in the interface. +2. Add new credentials with their site, username, password, OTP, card details, etc. +3. Use the πŸ“‹ button next to any password to copy it to your clipboard. +4. To delete an entry, click the red βœ– button and confirm. + +## Security Notes + +- All credentials are stored only on the server, encrypted at rest. +- Passwords are shown as dots by default; they can only be copied (not revealed) for safety. +- Use secure team practices with this tool and limit exposure of your admin interface. + +## Customizing + +- To change storage or encryption, update `02_supportFuncs.php` and references to `passwords.json.enc`. +- UI layout can be modified in `05_content.html.php`. +- Client logic (like clipboard copying) is in `07_javascript.js`. + +--- +This tab is intended for lightweight, shared team secrets/password management. For high-security requirements, use a dedicated enterprise password vault solution. \ No newline at end of file diff --git a/tech/tabs/03-third-parties/01_constants.php b/tech/tabs/03-third-parties/01_constants.php new file mode 100644 index 0000000..5205431 --- /dev/null +++ b/tech/tabs/03-third-parties/01_constants.php @@ -0,0 +1,16 @@ + diff --git a/tech/tabs/03-third-parties/02_supportFuncs.php b/tech/tabs/03-third-parties/02_supportFuncs.php new file mode 100644 index 0000000..91dddb0 --- /dev/null +++ b/tech/tabs/03-third-parties/02_supportFuncs.php @@ -0,0 +1,62 @@ +now(); // 6-digit string + } catch (\Throwable $e) { + return ''; + } +} +?> diff --git a/tech/tabs/03-third-parties/03_handler.php b/tech/tabs/03-third-parties/03_handler.php new file mode 100644 index 0000000..54f131a --- /dev/null +++ b/tech/tabs/03-third-parties/03_handler.php @@ -0,0 +1,45 @@ + trim($_POST['site']), + 'username' => trim($_POST['username']), + 'password' => $_POST['password'], + 'otp_secret' => trim($_POST['otp_secret'] ?? ''), + 'cc_number' => trim($_POST['cc_number'] ?? ''), + 'cc_exp' => trim($_POST['cc_exp'] ?? ''), + 'ccv' => trim($_POST['ccv'] ?? ''), + 'monthly' => trim($_POST['monthly'] ?? ''), + ]; + + savePasswordList($pwFile, $pwList, $key); + header("Location: ".$_SERVER['REQUEST_URI']); exit; +} +?> diff --git a/tech/tabs/03-third-parties/04_nav-item.html.php b/tech/tabs/03-third-parties/04_nav-item.html.php new file mode 100644 index 0000000..f6faf97 --- /dev/null +++ b/tech/tabs/03-third-parties/04_nav-item.html.php @@ -0,0 +1,10 @@ + + diff --git a/tech/tabs/03-third-parties/05_content.html.php b/tech/tabs/03-third-parties/05_content.html.php new file mode 100644 index 0000000..f82dd51 --- /dev/null +++ b/tech/tabs/03-third-parties/05_content.html.php @@ -0,0 +1,113 @@ + + +
+
Password Manager
+ + +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + $row): + ?> + + + + + + + + + + + + + + + + + + + + + + + + +
SiteUserPasswordOTPCard #ExpCCV€/mo
+
+ + +
+
+ + + + + + + + +
+ + +
+
+ 1): ?> + + +
diff --git a/tech/tabs/03-third-parties/06_modals.html.php b/tech/tabs/03-third-parties/06_modals.html.php new file mode 100644 index 0000000..b5d7e9a --- /dev/null +++ b/tech/tabs/03-third-parties/06_modals.html.php @@ -0,0 +1,7 @@ + + + + diff --git a/tech/tabs/03-third-parties/07_javascript.js b/tech/tabs/03-third-parties/07_javascript.js new file mode 100644 index 0000000..cd40663 --- /dev/null +++ b/tech/tabs/03-third-parties/07_javascript.js @@ -0,0 +1,17 @@ +document.addEventListener('DOMContentLoaded', function () { + document.querySelectorAll('.copy-pw-btn').forEach(function(btn) { + btn.addEventListener('click', function () { + var input = btn.closest('.input-group').querySelector('.pw-field'); + if (input) { + navigator.clipboard.writeText(input.value).then(function() { + btn.innerHTML = 'βœ…'; + }); + } + }); + }); +}); +// +// FILE: 07_javascript.js +// Description: the javascript code that will be added to the website + + diff --git a/tech/tabs/04-schema/01_constants.php b/tech/tabs/04-schema/01_constants.php new file mode 100644 index 0000000..64f426e --- /dev/null +++ b/tech/tabs/04-schema/01_constants.php @@ -0,0 +1,2 @@ +βœ… SQL schema saved."; + } diff --git a/tech/tabs/04-schema/04_nav-item.html.php b/tech/tabs/04-schema/04_nav-item.html.php new file mode 100644 index 0000000..60c09f5 --- /dev/null +++ b/tech/tabs/04-schema/04_nav-item.html.php @@ -0,0 +1,6 @@ + diff --git a/tech/tabs/04-schema/05_content.html.php b/tech/tabs/04-schema/05_content.html.php new file mode 100644 index 0000000..565fe66 --- /dev/null +++ b/tech/tabs/04-schema/05_content.html.php @@ -0,0 +1,10 @@ + +
+
+
+ + +
+ +
+
\ No newline at end of file diff --git a/tech/tabs/04-schema/07_javascript.js b/tech/tabs/04-schema/07_javascript.js new file mode 100644 index 0000000..88d38e8 --- /dev/null +++ b/tech/tabs/04-schema/07_javascript.js @@ -0,0 +1,14 @@ +document.addEventListener('DOMContentLoaded', () => { + const editor = CodeMirror.fromTextArea(document.getElementById('sqlSchemaEditor'), { + mode: 'text/x-sql', + theme: 'material', + lineNumbers: false, + indentUnit: 2, + tabSize: 2, + }); + // Force CodeMirror to refresh when the tab is shown + const schemaTab = document.querySelector('button[data-bs-target="#schema"]'); + schemaTab.addEventListener('shown.bs.tab', function () { + editor.refresh(); + }); +}); diff --git a/tech/tabs/05-actions/03_handler.php b/tech/tabs/05-actions/03_handler.php new file mode 100644 index 0000000..522aea5 --- /dev/null +++ b/tech/tabs/05-actions/03_handler.php @@ -0,0 +1,5 @@ +πŸ”„ ReSync executed."; + } diff --git a/tech/tabs/05-actions/04_nav-item.html.php b/tech/tabs/05-actions/04_nav-item.html.php new file mode 100644 index 0000000..7e6411d --- /dev/null +++ b/tech/tabs/05-actions/04_nav-item.html.php @@ -0,0 +1,6 @@ + diff --git a/tech/tabs/05-actions/05_content.html.php b/tech/tabs/05-actions/05_content.html.php new file mode 100644 index 0000000..0948dc3 --- /dev/null +++ b/tech/tabs/05-actions/05_content.html.php @@ -0,0 +1,6 @@ + +
+
+ +
+
\ No newline at end of file diff --git a/tech/tabs/06-backup/04_nav-item.html.php b/tech/tabs/06-backup/04_nav-item.html.php new file mode 100644 index 0000000..15d15b7 --- /dev/null +++ b/tech/tabs/06-backup/04_nav-item.html.php @@ -0,0 +1,6 @@ + diff --git a/tech/tabs/06-backup/05_content.html.php b/tech/tabs/06-backup/05_content.html.php new file mode 100644 index 0000000..0365148 --- /dev/null +++ b/tech/tabs/06-backup/05_content.html.php @@ -0,0 +1,4 @@ + +
+

βš™οΈ Coming soon...

+
\ No newline at end of file