Dateien nach "src/templates" hochladen

This commit is contained in:
2025-06-04 06:54:19 +00:00
parent bde6033f58
commit 8feb0de362
5 changed files with 568 additions and 0 deletions

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Passwort ändern</title>
<style>
body {
font-family: sans-serif;
color: white;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: black;
}
.login-box {
background: rgba(44, 44, 44, 0.85);
padding: 30px;
border-radius: 12px;
width: 320px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
.login-box h2 {
text-align: center;
margin-bottom: 20px;
}
.login-box input[type="password"] {
width: 100%;
padding: 12px;
margin: 10px 0;
border: none;
border-radius: 6px;
background-color: #444;
color: white;
}
.login-box button {
width: 100%;
padding: 12px;
background: #007bff;
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
margin-top: 10px;
font-weight: bold;
}
.login-box button:hover {
background-color: #0056b3;
}
.flash, .validation-message {
text-align: center;
margin-top: 10px;
font-size: 0.9em;
}
.flash {
color: #ff4d4d;
}
.validation-message {
color: #ffaa00;
}
</style>
<script>
function validateForm(event) {
const pw = document.getElementById('new_password').value;
const pwConfirm = document.getElementById('confirm_password').value;
const msg = document.getElementById('validation-msg');
const minLength = pw.length >= 8;
const hasUpper = /[A-Z]/.test(pw);
const hasLower = /[a-z]/.test(pw);
const hasSpecial = /[!@#\$%\^&\*\(\)_\+\-=\[\]\{\};:'",.<>\/?\\|]/.test(pw);
const match = pw === pwConfirm;
if (!minLength || !hasUpper || !hasLower || !hasSpecial) {
msg.textContent = "Das Passwort muss mind. 8 Zeichen lang sein, Groß-/Kleinbuchstaben und ein Sonderzeichen enthalten.";
event.preventDefault();
return false;
}
if (!match) {
msg.textContent = "Die Passwörter stimmen nicht überein.";
event.preventDefault();
return false;
}
return true;
}
</script>
</head>
<body>
<div class="login-box">
<h2>Neues Passwort setzen</h2>
<form method="POST" action="{{ url_for('change_password') }}" onsubmit="return validateForm(event)">
<input type="password" id="new_password" name="new_password" placeholder="Neues Passwort" required>
<input type="password" id="confirm_password" placeholder="Passwort bestätigen" required>
<button type="submit">Speichern</button>
<p id="validation-msg" class="validation-message"></p>
</form>
{% if get_flashed_messages() %}
<p class="flash">{{ get_flashed_messages()[0] }}</p>
{% endif %}
</div>
</body>
</html>

View File

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='dashboard.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
.vaults-scroll-wrapper {
max-height: 600px;
overflow-y: auto;
border: 1px solid #333;
border-radius: 12px;
padding-right: 10px;
}
</style>
<script>
function toggleModal(id) {
const modal = document.getElementById('modal-' + id);
modal.style.display = modal.style.display === 'flex' ? 'none' : 'flex';
}
function toggleAuthFields(selectEl) {
const form = selectEl.closest("form");
const keyField = form.querySelector(".ssh-key-field");
const passwordField = form.querySelector("input[name='password']");
if (selectEl.value === "key") {
keyField.style.display = "block";
if (passwordField) passwordField.style.display = "none";
} else {
keyField.style.display = "none";
if (passwordField) passwordField.style.display = "block";
}
}
</script>
</head>
<body>
<div class="sidebar">
<div class="logo-container">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo">
</div>
<ul class="nav">
<li><a href="{{ url_for('view_vaults') }}"><i class="fas fa-key"></i> Vaults</a></li>
<li><a href="{{ url_for('sftp') }}"><i class="fas fa-folder-open"></i> SFTP</a></li>
<li><a href="{{ url_for('users') }}"><i class="fas fa-users"></i> Profil</a></li>
</ul>
<div class="logout">
<a href="{{ url_for('logout') }}"><i class="fas fa-sign-out-alt"></i> Logout</a>
</div>
</div>
<div class="main-content">
<h1>{{ title }}</h1>
{% if current_user %}
<p style="margin-top: -10px;">
Eingeloggt als <strong>{{ current_user.username }}</strong>
</p>
{% endif %}
{% if page == 'vaults' %}
<form method="POST" action="{{ url_for('add_vault') }}" style="margin-bottom: 20px;">
<input type="text" name="vault_name" placeholder="Name" autocomplete="off" required>
<input type="text" name="host_or_name" placeholder="IP oder Hostname" autocomplete="off" required>
<input type="number" name="port" placeholder="Port (z.B. 22)" value="22" autocomplete="off">
<input type="text" name="user" placeholder="SSH Benutzername" autocomplete="off" required>
<input type="password" name="password" placeholder="Passwort (nur bei Passwortauth)" autocomplete="off">
<input type="text" name="ssh_key" placeholder="SSH-Key (nur bei Keyauth)" autocomplete="off" class="ssh-key-field" style="display: none;">
<select name="auth_type" onchange="toggleAuthFields(this)">
<option value="password">User + Passwort</option>
<option value="key">SSH-Key</option>
</select>
<button class="add-vault-btn"><i class="fas fa-plus"></i> Vault hinzufügen</button>
</form>
<div class="vaults-scroll-wrapper">
<div class="vaults-grid">
{% for vault in vaults %}
<div class="vault-card">
<div class="vault-header">
<span class="vault-name">{{ vault.name }}</span>
<button class="vault-settings" onclick="toggleModal('{{ vault.id }}')">
<i class="fas fa-cog"></i>
</button>
</div>
<div class="vault-actions">
<form action="{{ url_for('terminal', vault_id=vault.id) }}" method="get" target="_blank" style="display:inline;">
<button type="submit" class="connect-btn">
<i class="fas fa-terminal"></i> Verbinden
</button>
</form>
</div>
</div>
<div class="modal-overlay" id="modal-{{ vault.id }}">
<div class="modal">
<span class="modal-close" onclick="toggleModal('{{ vault.id }}')">&times;</span>
<h2>{{ vault.name }} bearbeiten</h2>
<form method="POST" action="{{ url_for('edit_vault', vault_id=vault.id) }}">
<input type="text" name="vault_name" value="{{ vault.name }}" autocomplete="off" required>
<input type="text" name="host_or_name" value="{{ vault.host_ip or vault.hostname }}" autocomplete="off" required>
<input type="number" name="port" value="{{ vault.port }}" autocomplete="off">
<input type="text" name="user" value="{{ vault.user }}">
<input type="password" name="password" placeholder="Neues Passwort" autocomplete="off">
<input type="text" name="ssh_key" value="{{ vault.ssh_key }}" class="ssh-key-field" autocomplete="off">
<select name="auth_type" onchange="toggleAuthFields(this)">
<option value="password" {% if vault.auth_type == 'password' %}selected{% endif %}>Passwort</option>
<option value="key" {% if vault.auth_type == 'key' %}selected{% endif %}>SSH-Key</option>
</select>
<button type="submit" class="edit-btn"><i class="fas fa-save"></i> Speichern</button>
</form>
<form method="POST" action="{{ url_for('delete_vault', vault_id=vault.id) }}" style="margin-top: 10px;">
<button class="delete-btn"><i class="fas fa-trash"></i> Löschen</button>
</form>
</div>
</div>
{% endfor %}
</div>
</div>
{% elif page == 'users' %}
<form method="POST" action="{{ url_for('users') }}">
<input type="password" name="password" placeholder="Neues Passwort" autocomplete="off" required>
<button type="submit"><i class="fas fa-key"></i> Passwort ändern</button>
</form>
{% if get_flashed_messages() %}
<p class="flash">{{ get_flashed_messages()[0] }}</p>
{% endif %}
{% endif %}
</div>
</body>
</html>

43
src/templates/login.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Login TeaShell</title>
<link rel="stylesheet" href="{{ url_for('static', filename='login.css') }}">
</head>
<body>
<div class="background-layer"></div>
<div class="login-box">
<h2>Login</h2>
<form method="POST">
<input type="text" name="username" placeholder="Benutzername" required>
<input type="password" name="password" placeholder="Passwort" required>
<div class="remember-me">
<input type="checkbox" id="remember" name="remember">
<label for="remember">Angemeldet bleiben</label>
</div>
<button type="submit">Anmelden</button>
</form>
<div class="login-hint">
<p>🔐 Login <strong>Lokal</strong></p>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash">
{% for category, message in messages %}
<p class="{{ category }}">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="footer-hint">
<p><strong>TeaShell</strong> 🐚</p>
</div>
</div>
</body>
</html>

85
src/templates/profil.html Normal file
View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Profil</title>
<link rel="stylesheet" href="{{ url_for('static', filename='dashboard.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
.validation-message {
text-align: center;
color: #ffaa00;
margin-top: 10px;
font-size: 0.9em;
}
.flash {
text-align: center;
color: #ff4d4d;
margin-top: 10px;
}
</style>
<script>
function validatePasswordForm(event) {
const pw = document.getElementById('new_password').value;
const pwConfirm = document.getElementById('confirm_password').value;
const msg = document.getElementById('pw-validation-msg');
const minLength = pw.length >= 8;
const hasUpper = /[A-Z]/.test(pw);
const hasLower = /[a-z]/.test(pw);
const hasSpecial = /[!@#\$%\^&\*\(\)_\+\-=\[\]\{\};:'",.<>\/?\\|]/.test(pw);
const match = pw === pwConfirm;
if (!minLength || !hasUpper || !hasLower || !hasSpecial) {
msg.textContent = "Mindestens 8 Zeichen, Groß-/Kleinschreibung und Sonderzeichen erforderlich.";
event.preventDefault();
return false;
}
if (!match) {
msg.textContent = "Die Passwörter stimmen nicht überein.";
event.preventDefault();
return false;
}
msg.textContent = "";
return true;
}
</script>
</head>
<body>
<div class="sidebar">
<div class="logo-container">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo">
</div>
<ul class="nav">
<li><a href="{{ url_for('view_vaults') }}"><i class="fas fa-key"></i> Vaults</a></li>
<li><a href="{{ url_for('sftp') }}"><i class="fas fa-folder-open"></i> SFTP</a></li>
<li><a href="{{ url_for('users') }}"><i class="fas fa-user-cog"></i> Profil</a></li>
</ul>
<div class="logout">
<a href="{{ url_for('logout') }}"><i class="fas fa-sign-out-alt"></i> Logout</a>
</div>
</div>
<div class="main-content">
<h1>Benutzerprofil</h1>
<form method="POST" action="{{ url_for('users') }}" onsubmit="return validatePasswordForm(event)">
<input type="password" id="new_password" name="password" placeholder="Neues Passwort" required>
<input type="password" id="confirm_password" placeholder="Passwort bestätigen" required>
<button type="submit"><i class="fas fa-key"></i> Passwort ändern</button>
<p id="pw-validation-msg" class="validation-message"></p>
</form>
<form method="POST" action="{{ url_for('users') }}">
<input type="text" name="new_username" placeholder="Neuer Benutzername" required value="{{ current_user.username }}">
<button type="submit"><i class="fas fa-user-edit"></i> Benutzername ändern</button>
</form>
{% if get_flashed_messages() %}
<p class="flash">{{ get_flashed_messages()[0] }}</p>
{% endif %}
</div>
</body>
</html>

View File

@ -0,0 +1,195 @@
{% extends 'base.html' %}
{% block content %}
<div class="sftp-wrapper">
<div class="sftp-panel">
<h3>Datei Upload & Verwaltung</h3>
<div class="upload-box" id="drop-zone">
<p>Datei hierher ziehen oder klicken</p>
<input type="file" id="file-input" hidden>
<ul class="file-list" id="file-list"></ul>
</div>
</div>
<div class="sftp-panel">
<h3>Ziel auswählen</h3>
<div class="target-scroll">
<table class="target-table">
<thead>
<tr>
<th>Host</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
{% for vault in vaults %}
<tr>
<td>{{ vault.name }} ({{ vault.user }}@{{ vault.host_ip or vault.hostname }})</td>
<td><button class="send-btn" data-id="{{ vault.id }}">Kopieren</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<style>
.sftp-wrapper {
display: flex;
flex-direction: row;
gap: 40px;
align-items: flex-start;
}
.sftp-panel {
flex: 1;
background: #1f1f1f;
padding: 20px;
border-radius: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
max-height: 600px;
overflow: hidden;
}
.target-scroll {
overflow-y: auto;
max-height: 450px;
border: 1px solid #333;
border-radius: 8px;
}
.target-table {
width: 100%;
border-collapse: collapse;
}
.target-table th, .target-table td {
padding: 10px;
border-bottom: 1px solid #333;
text-align: left;
}
.target-table th {
background-color: #2a2a2a;
}
.send-btn {
padding: 6px 10px;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.upload-box {
border: 2px dashed #00aaff;
padding: 30px;
text-align: center;
border-radius: 12px;
cursor: pointer;
min-height: 180px;
}
.upload-box.dragover {
background-color: rgba(0, 170, 255, 0.1);
}
.file-list {
margin-top: 20px;
list-style: none;
padding: 0;
}
.file-item {
background: #2a2a2a;
margin-bottom: 10px;
padding: 10px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-item button {
margin-left: 10px;
padding: 6px 10px;
background: #007bff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.file-item button.delete-btn {
background-color: #dc3545;
}
</style>
<script>
const dropZone = document.getElementById("drop-zone");
const fileInput = document.getElementById("file-input");
const fileList = document.getElementById("file-list");
dropZone.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", () => {
uploadFile(fileInput.files[0]);
});
dropZone.addEventListener("dragover", e => {
e.preventDefault();
dropZone.classList.add("dragover");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("dragover");
});
dropZone.addEventListener("drop", e => {
e.preventDefault();
dropZone.classList.remove("dragover");
uploadFile(e.dataTransfer.files[0]);
});
function uploadFile(file) {
const formData = new FormData();
formData.append("file", file);
fetch("/sftp/upload", {
method: "POST",
body: formData
}).then(res => res.json()).then(data => {
if (data.success) {
refreshFileList();
}
});
}
function refreshFileList() {
fetch("/sftp/files")
.then(res => res.json())
.then(files => {
fileList.innerHTML = "";
files.forEach(file => {
const li = document.createElement("li");
li.className = "file-item";
li.innerHTML = `
<span>${file}</span>
<button class="delete-btn" onclick="deleteFile('${file}')">Löschen</button>
`;
fileList.appendChild(li);
});
});
}
function deleteFile(filename) {
fetch(`/sftp/delete/${filename}`, { method: "POST" })
.then(() => refreshFileList());
}
refreshFileList();
</script>
{% endblock %}