mirror of
https://github.com/hoshikawa2/rfp_response_automation.git
synced 2026-03-06 18:21:02 +00:00
first commit
This commit is contained in:
67
files/templates/admin_menu.html
Normal file
67
files/templates/admin_menu.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<h1>⚙️ Admin Panel</h1>
|
||||
|
||||
<!-- USERS -->
|
||||
<div class="card">
|
||||
|
||||
<h2>👤 Users</h2>
|
||||
|
||||
<p class="small">
|
||||
Create, edit and manage system users and permissions.
|
||||
</p>
|
||||
|
||||
<a href="{{ url_for('users.list_users') }}" class="btn">
|
||||
Open User Management
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- KNOWLEDGE -->
|
||||
<div class="card">
|
||||
|
||||
<h2>🔐 Knowledge Governance</h2>
|
||||
|
||||
<p class="small">
|
||||
Invalidate outdated knowledge or manually add validated information to the RAG base.
|
||||
</p>
|
||||
|
||||
<a href="{{ url_for('admin.invalidate_page') }}" class="btn">
|
||||
Open Governance Tools
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
||||
<h2>♻️ Maintenance</h2>
|
||||
|
||||
<p class="small">
|
||||
Reload all knowledge indexes, embeddings and caches without restarting the server.
|
||||
</p>
|
||||
|
||||
<button class="btn" onclick="rebootSystem()">
|
||||
Reload Knowledge
|
||||
</button>
|
||||
|
||||
<pre id="rebootResult" style="margin-top:10px;"></pre>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
async function rebootSystem() {
|
||||
|
||||
const box = document.getElementById("rebootResult");
|
||||
box.textContent = "⏳ Reloading...";
|
||||
|
||||
const res = await fetch("/admin/reboot", {
|
||||
method: "POST"
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
box.textContent = "✅ " + data.message;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
321
files/templates/base.html
Normal file
321
files/templates/base.html
Normal file
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>ORACLE RFP AI Platform</title>
|
||||
|
||||
<style>
|
||||
/* =========================
|
||||
GLOBAL THEME (Oracle light)
|
||||
========================= */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
||||
|
||||
/* antes: gradiente dark */
|
||||
background: #f5f6f7;
|
||||
|
||||
/* antes: texto claro */
|
||||
color: #1f2937;
|
||||
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
LINKS
|
||||
========================= */
|
||||
|
||||
a {
|
||||
color: #C74634; /* Oracle red */
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
NAVBAR
|
||||
========================= */
|
||||
|
||||
.navbar {
|
||||
position: fixed; /* 🔥 cola no topo real */
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background: #E30613;
|
||||
|
||||
z-index: 9999;
|
||||
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.navbar-inner {
|
||||
width: 100%;
|
||||
|
||||
/* 🔥 padding mínimo só vertical */
|
||||
padding: 14px 18px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* texto branco */
|
||||
.navbar a,
|
||||
.nav-left {
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
|
||||
/* destaque Oracle */
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
LAYOUT
|
||||
========================= */
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 90px auto 40px auto; /* 🔥 espaço para navbar */
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
CARD
|
||||
========================= */
|
||||
|
||||
.card {
|
||||
|
||||
/* antes: #1e293b */
|
||||
background: #ffffff;
|
||||
|
||||
border-radius: 14px;
|
||||
|
||||
padding: 24px;
|
||||
|
||||
/* antes sombra pesada */
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
margin-bottom: 24px;
|
||||
|
||||
}
|
||||
|
||||
/* =========================
|
||||
TABLE
|
||||
========================= */
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
INPUTS
|
||||
========================= */
|
||||
|
||||
input, select, textarea {
|
||||
|
||||
/* antes fundo escuro */
|
||||
background: #ffffff;
|
||||
|
||||
border: 1px solid #e5e7eb;
|
||||
|
||||
color: #111;
|
||||
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
BUTTONS
|
||||
========================= */
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: #F80000;
|
||||
color: white;
|
||||
|
||||
padding: 10px 18px;
|
||||
border-radius: 8px;
|
||||
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
FLASH MESSAGES
|
||||
========================= */
|
||||
|
||||
.flash {
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.flash-success {
|
||||
background: #e7f7ed;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.flash-error {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
MOBILE
|
||||
========================= */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
margin: 20px 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: white; /* texto branco também */
|
||||
}
|
||||
|
||||
.oracle-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- =========================
|
||||
NAVBAR
|
||||
========================= -->
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
|
||||
<div class="nav-left logo">
|
||||
<img
|
||||
src="{{ url_for('static', filename='oracle.webp') }}"
|
||||
class="oracle-icon"
|
||||
alt="Oracle"
|
||||
/>
|
||||
|
||||
<span>ORACLE RFP AI Platform</span>
|
||||
</div>
|
||||
|
||||
{% if request.endpoint != 'auth.login_page' %}
|
||||
<div class="nav-links">
|
||||
<a href="/">Chat</a>
|
||||
|
||||
{% if current_user and current_user.role == "admin" %}
|
||||
<a href="/admin">Admin</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user %}
|
||||
<a href="/logout">Logout</a>
|
||||
{% else %}
|
||||
<a href="/login">Login</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- =========================
|
||||
PAGE CONTENT
|
||||
========================= -->
|
||||
<div class="container">
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
82
files/templates/excel/job_status.html
Normal file
82
files/templates/excel/job_status.html
Normal file
@@ -0,0 +1,82 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
.job-card {
|
||||
max-width: 520px;
|
||||
margin: 80px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #eee;
|
||||
border-top: 4px solid #E30613;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="card job-card">
|
||||
|
||||
<h2>Excel Processing</h2>
|
||||
<p>Job ID: <b>{{ job_id }}</b></p>
|
||||
|
||||
<div id="status-area">
|
||||
<div class="spinner"></div>
|
||||
<p>Processing...</p>
|
||||
</div>
|
||||
|
||||
<div id="download-area" style="display:none">
|
||||
<a id="download-btn" class="btn btn-success">Download Result</a>
|
||||
</div>
|
||||
|
||||
<div id="error-area" style="display:none; color:#dc2626">
|
||||
<p><b>Error occurred</b></p>
|
||||
<pre id="error-detail"></pre>
|
||||
<a href="/job/{{ job_id }}/logs" target="_blank">View logs</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const jobId = "{{ job_id }}";
|
||||
|
||||
async function checkStatus() {
|
||||
const r = await fetch(`/job/${jobId}/status`);
|
||||
const s = await r.json();
|
||||
|
||||
if (s.status === "DONE") {
|
||||
document.getElementById("status-area").style.display = "none";
|
||||
document.getElementById("download-area").style.display = "block";
|
||||
|
||||
document.getElementById("download-btn").href =
|
||||
`/download/${jobId}`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.status === "ERROR") {
|
||||
document.getElementById("status-area").style.display = "none";
|
||||
document.getElementById("error-area").style.display = "block";
|
||||
|
||||
document.getElementById("error-detail").innerText =
|
||||
s.detail || "Unknown error";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(checkStatus, 2000);
|
||||
}
|
||||
|
||||
checkStatus();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
1023
files/templates/index.html
Normal file
1023
files/templates/index.html
Normal file
File diff suppressed because it is too large
Load Diff
203
files/templates/invalidate.html
Normal file
203
files/templates/invalidate.html
Normal file
@@ -0,0 +1,203 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<h1>🔐 RAG Knowledge Governance</h1>
|
||||
|
||||
<!-- ===================== -->
|
||||
<!-- 🔎 INVALIDATE SEARCH -->
|
||||
<!-- ===================== -->
|
||||
<section style="margin-bottom: 40px;">
|
||||
<h2>❌ Invalidate Knowledge</h2>
|
||||
|
||||
<form method="post" action="/admin/search">
|
||||
<textarea name="statement" rows="4" style="width:100%;"
|
||||
placeholder="Paste the invalid or outdated statement here">{{ statement }}</textarea>
|
||||
<br><br>
|
||||
<button type="submit">Search occurrences</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- ===================== -->
|
||||
<!-- ➕ MANUAL KNOWLEDGE -->
|
||||
<!-- ===================== -->
|
||||
<section style="margin-bottom: 40px;">
|
||||
<h2>➕ Add Manual Knowledge</h2>
|
||||
|
||||
<form id="manualForm">
|
||||
<textarea id="manualText" rows="8" style="width:100%;"
|
||||
placeholder="Paste validated Oracle technical information here..."></textarea>
|
||||
|
||||
<br><br>
|
||||
|
||||
<input type="text" id="reason" style="width:100%;"
|
||||
placeholder="Reason / justification (e.g. official Oracle clarification)"/>
|
||||
|
||||
<br><br>
|
||||
|
||||
<button type="button" onclick="submitManual()">Add Knowledge</button>
|
||||
</form>
|
||||
|
||||
<pre id="manualResult" style="margin-top:15px;"></pre>
|
||||
</section>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- ===================== -->
|
||||
<!-- 📚 SEARCH RESULTS -->
|
||||
<!-- ===================== -->
|
||||
<section>
|
||||
<h2>📚 Knowledge Matches</h2>
|
||||
|
||||
{% if results|length == 0 %}
|
||||
<p><i>No matching knowledge found.</i></p>
|
||||
{% endif %}
|
||||
|
||||
{% for r in results %}
|
||||
<div style="border:1px solid #ccc; padding:15px; margin:15px 0; border-radius:6px;">
|
||||
|
||||
<strong>Chunk Hash:</strong>
|
||||
<span data-hash-label>{{ r.chunk_hash or "—" }}</span><br>
|
||||
<strong>Origin:</strong> {{ r.origin or "UNKNOWN" }}<br>
|
||||
<strong>Created at:</strong> {{ r.created_at or "—" }}<br>
|
||||
<strong>Status:</strong> {{ r.status }}<br>
|
||||
<strong>Source:</strong> {{ r.source }}<br>
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<strong>Content:</strong>
|
||||
<pre style="
|
||||
white-space: pre-wrap;
|
||||
background:#f8f8f8;
|
||||
padding:10px;
|
||||
border-radius:4px;
|
||||
border:1px solid #ddd;
|
||||
max-height:400px;
|
||||
overflow:auto;
|
||||
">{{ r.text }}</pre>
|
||||
<div style="margin-top:10px;">
|
||||
<strong>Change to:</strong>
|
||||
|
||||
<textarea class="edit-box" data-hash="{{ r.chunk_hash }}"
|
||||
style="
|
||||
width:100%;
|
||||
min-height:140px;
|
||||
background:#f8f8f8;
|
||||
padding:10px;
|
||||
border-radius:4px;
|
||||
border:1px solid #ddd;
|
||||
font-family: monospace;
|
||||
"
|
||||
data-original="{{ r.text | e }}"
|
||||
>{{ r.text }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if r.chunk_hash %}
|
||||
<button
|
||||
style="color:red;"
|
||||
data-hash="{{ (r.chunk_hash or '') | string | e }}"
|
||||
onclick="invalidateChunk(this)">
|
||||
Invalidate
|
||||
</button>
|
||||
<button
|
||||
style="color:blue; margin-left:10px;"
|
||||
data-hash="{{ (r.chunk_hash or '') | string | e }}"
|
||||
onclick="updateChunk(this)">
|
||||
Change content
|
||||
</button>
|
||||
{% else %}
|
||||
<p><i>Derived from Knowledge Graph (non-revocable)</i></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<script>
|
||||
async function submitManual() {
|
||||
const text = document.getElementById("manualText").value;
|
||||
const reason = document.getElementById("reason").value;
|
||||
|
||||
const res = await fetch("/admin/add-knowledge", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ text, reason })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
document.getElementById("manualResult").textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
async function invalidateChunk(btn) {
|
||||
const chunkHash = btn.dataset.hash;
|
||||
console.log("chunkHash:", chunkHash, "type:", typeof chunkHash);
|
||||
|
||||
const res = await fetch("/admin/revoke", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({
|
||||
chunk_hash: chunkHash,
|
||||
reason: "Invalidated via admin interface"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
console.log("server:", res.status, data);
|
||||
|
||||
if (res.ok) btn.closest("div").remove();
|
||||
}
|
||||
async function updateChunk(btn) {
|
||||
|
||||
const card = btn.closest("div");
|
||||
|
||||
const chunkHash = btn.dataset.hash;
|
||||
|
||||
const textarea = card.querySelector(`.edit-box[data-hash="${chunkHash}"]`);
|
||||
const newText = textarea.value;
|
||||
|
||||
if (!chunkHash) return;
|
||||
|
||||
if (!confirm("Update this chunk content?")) return;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Saving...";
|
||||
|
||||
const res = await fetch("/admin/update-chunk", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({
|
||||
chunk_hash: chunkHash,
|
||||
text: newText
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
btn.disabled = false;
|
||||
|
||||
if (res.ok) {
|
||||
const newHash = data.chunk_hash;
|
||||
|
||||
const hashLabel = card.querySelector("[data-hash-label]");
|
||||
if (hashLabel && newHash) {
|
||||
hashLabel.textContent = newHash;
|
||||
}
|
||||
|
||||
btn.dataset.hash = newHash;
|
||||
|
||||
textarea.style.borderColor = "green";
|
||||
|
||||
setTimeout(() => {
|
||||
btn.textContent = "Change content";
|
||||
textarea.style.borderColor = "#ddd";
|
||||
}, 1200);
|
||||
|
||||
} else {
|
||||
alert("Failed to update");
|
||||
btn.textContent = "Change content";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
25
files/templates/users/form.html
Normal file
25
files/templates/users/form.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="card">
|
||||
<h2>{{ "Edit User" if user else "New User" }}</h2>
|
||||
|
||||
<form method="post">
|
||||
<input name="name" placeholder="Name" value="{{ user.name if user else '' }}" required>
|
||||
<input name="email" placeholder="Email" value="{{ user.email if user else '' }}" required>
|
||||
|
||||
<select name="role">
|
||||
<option value="user">User</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="active" {% if user and user.active %}checked{% endif %}>
|
||||
Active
|
||||
</label>
|
||||
|
||||
<button class="btn btn-success">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
33
files/templates/users/list.html
Normal file
33
files/templates/users/list.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="card">
|
||||
<h2>Users</h2>
|
||||
|
||||
<a class="btn btn-primary" href="{{ url_for('users.new_user') }}">+ New User</a>
|
||||
|
||||
<table class="table table-dark">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Active</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td>{{ u.name }}</td>
|
||||
<td>{{ u.email }}</td>
|
||||
<td>{{ u.role }}</td>
|
||||
<td>{{ "Yes" if u.active else "No" }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('users.edit_user', user_id=u.id) }}">Edit</a> |
|
||||
<a href="{{ url_for('users.delete_user', user_id=u.id) }}">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
114
files/templates/users/login.html
Normal file
114
files/templates/users/login.html
Normal file
@@ -0,0 +1,114 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
.login-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 380px;
|
||||
background: #ffffff;
|
||||
border-radius: 14px;
|
||||
padding: 32px;
|
||||
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.08);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.login-sub {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.login-card input {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-top: 18px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.oracle-accent {
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
background: #E30613;
|
||||
border-radius: 8px 8px 0 0;
|
||||
margin: -32px -32px 24px -32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="login-wrapper">
|
||||
|
||||
<div class="login-card">
|
||||
|
||||
<!-- barra vermelha Oracle -->
|
||||
<div class="oracle-accent"></div>
|
||||
|
||||
<div class="login-title">
|
||||
Sign in
|
||||
</div>
|
||||
|
||||
<div class="login-sub">
|
||||
Oracle RFP AI Platform
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for msg in messages %}
|
||||
<div class="flash flash-error">{{ msg }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" action="/login">
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Email"
|
||||
required
|
||||
/>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
/>
|
||||
|
||||
<button class="btn login-btn">
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-footer">
|
||||
© Oracle RFP AI Platform
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
20
files/templates/users/set_password.html
Normal file
20
files/templates/users/set_password.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="card">
|
||||
|
||||
<h2>Set Password</h2>
|
||||
|
||||
{% if expired %}
|
||||
<p>Link expired or invalid.</p>
|
||||
{% else %}
|
||||
<form method="post">
|
||||
<input type="password" name="password" placeholder="New password" required>
|
||||
<input type="password" name="password2" placeholder="Confirm password" required>
|
||||
<button class="btn btn-primary">Save</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
51
files/templates/users/signup.html
Normal file
51
files/templates/users/signup.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="card">
|
||||
|
||||
<h2>Create Access</h2>
|
||||
|
||||
<p class="small">
|
||||
Enter your email to receive a secure link and set your password.
|
||||
</p>
|
||||
|
||||
<form method="post">
|
||||
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
/>
|
||||
|
||||
<br><br>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="name (optional)"
|
||||
/>
|
||||
|
||||
<br><br>
|
||||
|
||||
<button type="submit">
|
||||
Send password link
|
||||
</button>
|
||||
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for cat, msg in messages %}
|
||||
<div class="info-box">
|
||||
{{ msg }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user