396 lines
15 KiB
Plaintext
396 lines
15 KiB
Plaintext
@model Aberwyn.Models.SetupSettings
|
||
@{
|
||
ViewData["Title"] = "Installera Aberwyn";
|
||
}
|
||
|
||
<h1>Installera Aberwyn</h1>
|
||
|
||
@if (ViewBag.Error != null)
|
||
{
|
||
<div class="alert alert-danger">@ViewBag.Error</div>
|
||
}
|
||
|
||
<style>
|
||
.setup-step {
|
||
margin-bottom: 2rem;
|
||
padding: 1.5rem;
|
||
background-color: #f8f9fa;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||
}
|
||
.form-group {
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
label {
|
||
font-weight: bold;
|
||
margin-bottom: 0.2rem;
|
||
}
|
||
input.form-control {
|
||
padding: 0.5rem;
|
||
font-size: 1rem;
|
||
border-radius: 6px;
|
||
}
|
||
.is-invalid {
|
||
border-color: #dc3545;
|
||
}
|
||
.invalid-feedback {
|
||
color: #dc3545;
|
||
font-size: 0.875rem;
|
||
display: none;
|
||
}
|
||
.is-invalid + .invalid-feedback {
|
||
display: block;
|
||
}
|
||
|
||
button {
|
||
padding: 10px 20px;
|
||
font-size: 1rem;
|
||
border-radius: 6px;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
margin-top: 0.5rem;
|
||
margin-right: 0.5rem;
|
||
}
|
||
.btn-primary { background-color: #6a0dad; color: white; }
|
||
.btn-primary:hover { background-color: #5a0cab; }
|
||
|
||
.btn-secondary { background-color: #adb5bd; color: black; }
|
||
.btn-secondary:hover { background-color: #9ca3af; }
|
||
|
||
.btn-success { background-color: #198754; color: white; }
|
||
.btn-success:hover { background-color: #157347; }
|
||
|
||
.btn-danger { background-color: #dc3545; color: white; }
|
||
.btn-danger:hover { background-color: #bb2d3b; }
|
||
|
||
.btn-outline-info {
|
||
border: 1px solid #0dcaf0;
|
||
color: #0dcaf0;
|
||
background: none;
|
||
}
|
||
.btn-outline-info:hover {
|
||
background-color: #0dcaf0;
|
||
color: white;
|
||
}
|
||
|
||
#setup-summary ul {
|
||
list-style: none;
|
||
padding-left: 0;
|
||
}
|
||
#setup-summary li {
|
||
margin-bottom: 0.4rem;
|
||
}
|
||
.spinner {
|
||
display: inline-block;
|
||
width: 1.2rem;
|
||
height: 1.2rem;
|
||
border: 3px solid rgba(0,0,0,0.2);
|
||
border-top-color: #6a0dad;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-left: 0.5rem;
|
||
}
|
||
@@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
|
||
<form method="post" id="setup-form">
|
||
<div id="step-1" class="setup-step">
|
||
<h2>Steg 1: Databasinställningar</h2>
|
||
<div class="form-group">
|
||
<label for="DbHost">Server</label>
|
||
<input asp-for="DbHost" name="DbHost" class="form-control" id="DbHost" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="DbPort">Port</label>
|
||
<input asp-for="DbPort" name="DbPort" class="form-control" id="DbPort" value="3306" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="DbName">Databasnamn</label>
|
||
<input asp-for="DbName" name="DbName" class="form-control" id="DbName" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="DbUser">Användarnamn</label>
|
||
<input asp-for="DbUser" name="DbUser" class="form-control" id="DbUser" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="DbPassword">Lösenord</label>
|
||
<input asp-for="DbPassword" name="DbPassword" type="password" class="form-control" id="DbPassword" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<button type="button" class="btn btn-outline-info" onclick="testDbConnection()">Testa anslutning</button>
|
||
<div id="db-test-result" class="mt-2"></div>
|
||
<button type="button" class="btn btn-primary" onclick="validateStep(1) && goToStep(2)">Nästa</button>
|
||
</div>
|
||
|
||
<div id="step-2" class="setup-step" style="display: none;">
|
||
<h2>Steg 2: Administratör</h2>
|
||
<div class="form-group">
|
||
<label for="AdminUsername">Admin Användarnamn</label>
|
||
<input asp-for="AdminUsername" name="AdminUsername" class="form-control" id="AdminUsername" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="AdminEmail">E-postadress</label>
|
||
<input asp-for="AdminEmail" name="AdminEmail" type="email" class="form-control" id="AdminEmail" required />
|
||
<div class="invalid-feedback">Fältet är obligatoriskt</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="AdminPassword">Lösenord</label>
|
||
<input asp-for="AdminPassword" name="AdminPassword" type="password" class="form-control"
|
||
id="AdminPassword"
|
||
pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d]).{6,}"
|
||
title="Minst 6 tecken, inklusive versal, gemen, siffra och specialtecken"
|
||
required />
|
||
<div class="invalid-feedback">Ange ett giltigt lösenord enligt kraven</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="ConfirmPassword">Bekräfta lösenord</label>
|
||
<input type="password" id="ConfirmPassword" class="form-control" required />
|
||
<span id="password-match-status" style="margin-top: 4px; font-size: 1.2rem;"></span>
|
||
<div class="invalid-feedback" id="confirmPasswordError">Lösenorden matchar inte</div>
|
||
</div>
|
||
|
||
|
||
<ul id="password-criteria" style="list-style: none; padding-left: 0; font-size: 0.9rem; margin-top: 0.5rem;">
|
||
<li id="length-criteria">❌ Minst 6 tecken</li>
|
||
<li id="uppercase-criteria">❌ Minst en stor bokstav (A–Z)</li>
|
||
<li id="lowercase-criteria">❌ Minst en liten bokstav (a–z)</li>
|
||
<li id="digit-criteria">❌ Minst en siffra (0–9)</li>
|
||
<li id="special-criteria">❌ Minst ett specialtecken (!#&...)</li>
|
||
</ul>
|
||
|
||
|
||
<button type="button" class="btn btn-secondary" onclick="goToStep(1)">Tillbaka</button>
|
||
<button type="button" class="btn btn-primary" onclick="validateAdminStep() && goToStep(3)">Nästa</button>
|
||
</div>
|
||
|
||
|
||
<div id="step-3" class="setup-step" style="display: none;">
|
||
<h2>Steg 3: Konfiguration pågår...</h2>
|
||
<div id="setup-summary">
|
||
<ul>
|
||
<li><strong>Server:</strong> <span id="summary-DbHost"></span></li>
|
||
<li><strong>Port:</strong> <span id="summary-DbPort"></span></li>
|
||
<li><strong>Databas:</strong> <span id="summary-DbName"></span></li>
|
||
<li><strong>Användare:</strong> <span id="summary-DbUser"></span></li>
|
||
<li><strong>Adminnamn:</strong> <span id="summary-AdminUsername"></span></li>
|
||
<li><strong>Admin-e-post:</strong> <span id="summary-AdminEmail"></span></li>
|
||
</ul>
|
||
</div>
|
||
<div id="setup-progress" class="mt-3">
|
||
<p>
|
||
Skapar databastabeller och konfigurerar administratörskonto...
|
||
<span class="spinner"></span>
|
||
</p>
|
||
</div>
|
||
<button type="button" class="btn btn-secondary" onclick="goToStep(2)">Tillbaka</button>
|
||
<button type="button" class="btn btn-success" onclick="submitSetup()">Slutför installation</button>
|
||
</div>
|
||
</form>
|
||
|
||
|
||
@if (User.IsInRole("Admin"))
|
||
{
|
||
<form method="post" asp-action="Reset" class="mt-3" onsubmit="return confirm('Är du säker på att du vill återställa konfigurationen?')">
|
||
<button type="submit" class="btn btn-danger">Återställ inställningar</button>
|
||
</form>
|
||
}
|
||
|
||
@section Scripts {
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.5/jquery.validate.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate-unobtrusive/3.2.12/jquery.validate.unobtrusive.min.js"></script>
|
||
|
||
<script>
|
||
function goToStep(step) {
|
||
document.querySelectorAll('.setup-step').forEach(s => s.style.display = 'none');
|
||
document.getElementById('step-' + step).style.display = 'block';
|
||
|
||
if (step === 3) {
|
||
document.getElementById('summary-DbHost').textContent = document.getElementById('DbHost').value;
|
||
document.getElementById('summary-DbPort').textContent = document.getElementById('DbPort').value;
|
||
document.getElementById('summary-DbName').textContent = document.getElementById('DbName').value;
|
||
document.getElementById('summary-DbUser').textContent = document.getElementById('DbUser').value;
|
||
document.getElementById('summary-AdminUsername').textContent = document.getElementById('AdminUsername').value;
|
||
document.getElementById('summary-AdminEmail').textContent = document.getElementById('AdminEmail').value;
|
||
|
||
document.getElementById('setup-progress').innerHTML = `<p>Redo att påbörja installationen.</p>`;
|
||
}
|
||
}
|
||
document.getElementById('AdminPassword').addEventListener('input', function () {
|
||
const val = this.value;
|
||
|
||
const hasLength = val.length >= 6;
|
||
const hasUpper = /[A-Z]/.test(val);
|
||
const hasLower = /[a-z]/.test(val);
|
||
const hasDigit = /\d/.test(val);
|
||
const hasSpecial = /[!@@#$%^&*(),.?":{}|<>_\-\\[\]\/+=~`]/.test(val); // lägg till specialtecken
|
||
|
||
updateCriteria('length-criteria', hasLength);
|
||
updateCriteria('uppercase-criteria', hasUpper);
|
||
updateCriteria('lowercase-criteria', hasLower);
|
||
updateCriteria('digit-criteria', hasDigit);
|
||
updateCriteria('special-criteria', hasSpecial);
|
||
});
|
||
|
||
function updateCriteria(id, isValid) {
|
||
const el = document.getElementById(id);
|
||
if (isValid) {
|
||
el.textContent = '✔ ' + el.textContent.slice(2);
|
||
el.style.color = 'green';
|
||
} else {
|
||
el.textContent = '❌ ' + el.textContent.slice(2);
|
||
el.style.color = 'red';
|
||
}
|
||
}
|
||
|
||
function validateStep(step) {
|
||
let isValid = true;
|
||
document.querySelectorAll('#step-' + step + ' [required]').forEach(field => {
|
||
if (!field.value.trim()) {
|
||
field.classList.add('is-invalid');
|
||
isValid = false;
|
||
} else {
|
||
field.classList.remove('is-invalid');
|
||
}
|
||
});
|
||
return isValid;
|
||
}
|
||
|
||
function testDbConnection() {
|
||
const host = document.getElementById('DbHost').value;
|
||
const port = document.getElementById('DbPort').value;
|
||
const db = document.getElementById('DbName').value;
|
||
const user = document.getElementById('DbUser').value;
|
||
const pass = document.getElementById('DbPassword').value;
|
||
|
||
const output = document.getElementById('db-test-result');
|
||
output.innerHTML = '<span class="spinner"></span> Testar...';
|
||
|
||
fetch('/api/setup/testdb', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ host, port, db, user, pass })
|
||
})
|
||
.then(async res => {
|
||
const text = await res.text();
|
||
console.log("Raw:", text);
|
||
try {
|
||
const result = JSON.parse(text);
|
||
if (result.success) {
|
||
output.innerHTML = '✅ <span class="text-success">Anslutning lyckades!</span>';
|
||
} else {
|
||
output.innerHTML = '❌ <span class="text-danger">' + result.message + '</span>';
|
||
}
|
||
} catch (e) {
|
||
output.innerHTML = '❌ <span class="text-danger">Felaktigt svar från servern: ' + text + '</span>';
|
||
}
|
||
})
|
||
.catch(err => {
|
||
output.innerHTML = '❌ <span class="text-danger">Nätverksfel: ' + err.message + '</span>';
|
||
});
|
||
}
|
||
function validateAdminStep() {
|
||
let valid = validateStep(2);
|
||
const pass = document.getElementById('AdminPassword');
|
||
const confirm = document.getElementById('ConfirmPassword');
|
||
const confirmError = document.getElementById('confirmPasswordError');
|
||
|
||
if (pass.value !== confirm.value) {
|
||
confirm.classList.add('is-invalid');
|
||
confirmError.style.display = 'block';
|
||
valid = false;
|
||
} else {
|
||
confirm.classList.remove('is-invalid');
|
||
confirmError.style.display = 'none';
|
||
}
|
||
|
||
return valid;
|
||
}
|
||
|
||
document.getElementById('ConfirmPassword').addEventListener('input', function () {
|
||
const pass = document.getElementById('AdminPassword').value;
|
||
const confirm = this.value;
|
||
const confirmField = document.getElementById('ConfirmPassword');
|
||
const matchStatus = document.getElementById('password-match-status');
|
||
|
||
if (confirm.length === 0) {
|
||
matchStatus.innerHTML = '';
|
||
confirmField.classList.remove('is-invalid');
|
||
return;
|
||
}
|
||
|
||
if (pass === confirm) {
|
||
matchStatus.innerHTML = '✔';
|
||
matchStatus.style.color = 'green';
|
||
confirmField.classList.remove('is-invalid');
|
||
} else {
|
||
matchStatus.innerHTML = '❌';
|
||
matchStatus.style.color = 'red';
|
||
confirmField.classList.add('is-invalid');
|
||
}
|
||
});
|
||
|
||
async function submitSetup() {
|
||
goToStep(3);
|
||
const form = document.getElementById('setup-form');
|
||
const formData = new FormData(form);
|
||
const data = {};
|
||
formData.forEach((value, key) => { data[key] = value; });
|
||
|
||
const resultEl = document.getElementById('setup-progress');
|
||
resultEl.innerHTML = `<p>
|
||
Skapar databastabeller och konfigurerar administratörskonto...
|
||
<span class="spinner"></span>
|
||
</p>`;
|
||
|
||
try {
|
||
const response = await fetch('/setup', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
const text = await response.text();
|
||
let result;
|
||
try {
|
||
result = JSON.parse(text);
|
||
} catch (err) {
|
||
throw new Error("Kunde inte tolka JSON-svar: " + text);
|
||
}
|
||
|
||
if (!response.ok) {
|
||
resultEl.innerHTML = `
|
||
<div class="alert alert-danger mt-3">
|
||
❌ Fel vid installation: ${result.error || 'Okänt fel'}
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
resultEl.innerHTML = `
|
||
<div class="alert alert-success mt-3">
|
||
✅ ${result.message || 'Installation slutförd!'}
|
||
<br />
|
||
<button class="btn btn-success mt-3" onclick="window.location.href='/'">Gå vidare</button>
|
||
</div>`;
|
||
} catch (err) {
|
||
console.error("Något gick fel:", err);
|
||
resultEl.innerHTML = `<div class="alert alert-danger mt-3">
|
||
❌ Fel vid installation: ${err.message}
|
||
</div>`;
|
||
}
|
||
}
|
||
|
||
|
||
</script>
|
||
}
|