Dateien nach "src/templates" hochladen
This commit is contained in:
113
src/templates/change_password.html
Normal file
113
src/templates/change_password.html
Normal 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>
|
132
src/templates/dashboard.html
Normal file
132
src/templates/dashboard.html
Normal 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 }}')">×</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
43
src/templates/login.html
Normal 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
85
src/templates/profil.html
Normal 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>
|
195
src/templates/sftp-modul.html
Normal file
195
src/templates/sftp-modul.html
Normal 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 %}
|
Reference in New Issue
Block a user