diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..4fa5aec --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,49 @@ +/* General Body Styles */ +body { + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: #F8F9FA; + color: #212529; +} + +/* Header Styles */ +.header-gradient { + background: linear-gradient(to right, #0B5ED7, #0D6EFD); + color: white; +} + +.header-gradient .navbar-brand { + font-weight: 600; +} + +/* Main container */ +.main-container { + padding-top: 2rem; + padding-bottom: 2rem; +} + +/* Card styles for dashboard elements */ +.card { + border: none; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1); +} + +/* Table styles */ +.table { + vertical-align: middle; +} + +.table thead th { + font-weight: 600; + border-bottom-width: 2px; +} + +/* Action buttons in table */ +.btn-action { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; +} + +/* Toast notifications */ +.toast-container { + z-index: 1090; +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..d585e26 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,24 @@ +document.addEventListener('DOMContentLoaded', function () { + const scanBtn = document.getElementById('scanRepoBtn'); + + if (scanBtn) { + scanBtn.addEventListener('click', function (e) { + e.preventDefault(); + + const spinner = scanBtn.querySelector('.spinner-border'); + const buttonText = scanBtn.querySelector('.button-text'); + const buttonIcon = scanBtn.querySelector('.bi-search'); + + // Show spinner, hide icon, and update text + if (spinner) spinner.style.display = 'inline-block'; + if (buttonIcon) buttonIcon.style.display = 'none'; + if (buttonText) buttonText.textContent = 'Scanning...'; + + // Disable button + scanBtn.disabled = true; + + // Redirect to trigger the scan and show success message + window.location.href = 'index.php?scan=success'; + }); + } +}); \ No newline at end of file diff --git a/db/migrations/001_create_users_table.sql b/db/migrations/001_create_users_table.sql new file mode 100644 index 0000000..7ad1648 --- /dev/null +++ b/db/migrations/001_create_users_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `users` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `username` VARCHAR(255) NOT NULL UNIQUE, + `password` VARCHAR(255) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/index.php b/index.php index 7205f3d..c1dff83 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,204 @@ getNamespaces(true); + $ectd_ns = isset($namespaces['ectd']) ? $namespaces['ectd'] : 'urn:ectd-org:ectd'; + $xml->registerXPathNamespace('ectd', $ectd_ns); + + // Example of fetching a value, adjust xpath as per actual XML structure + $result = $xml->xpath('//product-name'); // Simplified xpath + if (empty($result)) { + // Try another common path + $result = $xml->xpath('//admin/product-name'); + } + + if (!empty($result)) { + $productName = (string)$result[0]; + } + } + } else { + $status = 'Validation Failed'; + } + } else { + $status = 'Missing index.xml'; + } + + $sequences[] = [ + 'id' => $item, + 'product' => $productName, + 'status' => $status, + 'last_scanned' => date('Y-m-d H:i:s'), + ]; + } + } + return $sequences; +} + +// Define the repository path and scan it. +$repositoryPath = __DIR__ . '/repository'; +$sequences = scanRepository($repositoryPath); + +function getStatusBadgeClass($status) { + switch ($status) { + case 'Validated': + return 'bg-success'; + case 'Validation Failed': + return 'bg-danger'; + case 'Missing index.xml': + case 'Invalid XML': + return 'bg-danger'; + case 'Pending Validation': + return 'bg-warning text-dark'; + default: + return 'bg-secondary'; + } +} ?> - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + + + <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'eCTD Submission Manager'); ?> + + + + + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- + + + + + +
+
+

Submissions Dashboard

+ +
+ + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Sequence IDProductStatusLast ScannedActions
+ + + + + + View + + +
+
+
+
+
+ + + + + - + \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..0a52b12 --- /dev/null +++ b/login.php @@ -0,0 +1,68 @@ +prepare("SELECT * FROM users WHERE username = ?"); + $stmt->execute([$username]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + header('Location: index.php'); + exit; + } else { + $error = 'Invalid username or password.'; + } + } +} +?> + + + + + + Login + + + + +
+
+
+
+
+

Login

+
+
+ +
+ +
+
+ + +
+
+ + +
+ + Register +
+
+
+
+
+
+ + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..95db42c --- /dev/null +++ b/logout.php @@ -0,0 +1,6 @@ +prepare("SELECT * FROM users WHERE username = ?"); + $stmt->execute([$username]); + if ($stmt->fetch()) { + $error = 'Username already exists.'; + } else { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $stmt = db()->prepare("INSERT INTO users (username, password) VALUES (?, ?)"); + if ($stmt->execute([$username, $hashed_password])) { + $success = 'Registration successful! You can now login.'; + } else { + $error = 'An error occurred. Please try again.'; + } + } + } +} +?> + + + + + + Register + + + + +
+
+
+
+
+

Register

+
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+ + Login +
+ +
+
+
+
+
+ + diff --git a/repository/0001/index.xml b/repository/0001/index.xml new file mode 100644 index 0000000..4f43600 --- /dev/null +++ b/repository/0001/index.xml @@ -0,0 +1,6 @@ + + + + Product A + + \ No newline at end of file diff --git a/repository/0002/index.xml b/repository/0002/index.xml new file mode 100644 index 0000000..ff74212 --- /dev/null +++ b/repository/0002/index.xml @@ -0,0 +1,6 @@ + + + + Product B + + \ No newline at end of file diff --git a/sequence.php b/sequence.php new file mode 100644 index 0000000..57ca036 --- /dev/null +++ b/sequence.php @@ -0,0 +1,204 @@ + $sequenceId, + 'productName' => 'N/A', + 'status' => 'Unknown', + 'files' => [], + 'xml_content' => '', + 'validation_errors' => [] + ]; + + // List files in the directory + $files = scandir($sequencePath); + $details['files'] = array_diff($files, ['.', '..']); + + $xmlPath = $sequencePath . '/index.xml'; + if (file_exists($xmlPath)) { + $xmlContent = file_get_contents($xmlPath); + $details['xml_content'] = htmlspecialchars($xmlContent); + $errors = XmlValidator::validate($xmlContent); + + if (empty($errors)) { + $details['status'] = 'Validated'; + $xml = simplexml_load_string($xmlContent); + if ($xml) { + // Correctly parsing with namespace + $namespaces = $xml->getNamespaces(true); + $ectd_ns = isset($namespaces['ectd']) ? $namespaces['ectd'] : 'urn:ectd-org:ectd'; + $xml->registerXPathNamespace('ectd', $ectd_ns); + + // Example of fetching a value, adjust xpath as per actual XML structure + $result = $xml->xpath('//product-name'); // Simplified xpath + if (empty($result)) { + // Try another common path + $result = $xml->xpath('//admin/product-name'); + } + + if (!empty($result)) { + $details['productName'] = (string)$result[0]; + } else { + $details['productName'] = 'N/A'; + } + } + } else { + $details['status'] = 'Validation Failed'; + $details['validation_errors'] = $errors; + } + } else { + $details['status'] = 'Missing index.xml'; + } + + return $details; +} + +$sequenceId = isset($_GET['id']) ? basename($_GET['id']) : ''; +$sequenceDetails = null; +$uploadMessage = ''; + +// Handle file upload +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['sequenceFile'])) { + if (!empty($sequenceId) && is_numeric($sequenceId)) { + $targetDir = __DIR__ . '/repository/' . $sequenceId . '/'; + if (!is_dir($targetDir)) { + mkdir($targetDir, 0775, true); + } + $targetFile = $targetDir . basename($_FILES['sequenceFile']['name']); + + if (move_uploaded_file($_FILES['sequenceFile']['tmp_name'], $targetFile)) { + $uploadMessage = '
File uploaded successfully.
'; + } else { + $uploadMessage = '
Sorry, there was an error uploading your file.
'; + } + } else { + $uploadMessage = '
Invalid sequence ID for upload.
'; + } +} + + +if (!empty($sequenceId) && is_numeric($sequenceId)) { + $sequenceDetails = getSequenceDetails($sequenceId); +} + +// Determine badge class based on status +$statusClass = 'bg-secondary'; +if (isset($sequenceDetails['status'])) { + switch ($sequenceDetails['status']) { + case 'Validated': + $statusClass = 'bg-success'; + break; + case 'Validation Failed': + $statusClass = 'bg-danger'; + break; + case 'Missing index.xml': + $statusClass = 'bg-warning text-dark'; + break; + } +} + +?> + + + + + + Sequence Details - eCTD Submission Manager + + + + + + + + + +
+
+

eCTD Submission Manager

+ ← Back to Dashboard +
+
+ +
+ +
+
+

Sequence Details:

+
+
+

Product Name:

+

Status:

+ + +

Validation Report

+
+
    + +
  • + +
+
+ + +

Files in Sequence

+ +
    + +
  • + +
+ +

No files found in this sequence.

+ + +

index.xml Content

+ +
+ +

No index.xml content to display.

+ + +
+ +

Upload File to Sequence

+ +
+
+ + +
+ +
+ +
+
+ + + +
+ + + + + diff --git a/validators/xml_validator.php b/validators/xml_validator.php new file mode 100644 index 0000000..9c72555 --- /dev/null +++ b/validators/xml_validator.php @@ -0,0 +1,29 @@ +loadXML($xmlString)) { + $errors[] = "Failed to parse XML."; + return $errors; + } + + $rootElement = $dom->documentElement; + if ($rootElement->tagName !== 'ectd:ectd') { + $errors[] = "Root element is not 'ectd:ectd'."; + } + + $dtdVersion = $dom->getElementsByTagName('dtd-version')->item(0); + if (!$dtdVersion || empty($dtdVersion->nodeValue)) { + $errors[] = "Required tag 'dtd-version' is missing or empty."; + } + + $submissionType = $dom->getElementsByTagName('submission-type')->item(0); + if (!$submissionType || empty($submissionType->nodeValue)) { + $errors[] = "Required tag 'submission-type' is missing or empty."; + } + + return $errors; + } +} +?> \ No newline at end of file