Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Failing after 4s
Docker-Images bauen und veröffentlichen / build (VITE_OMSORG_CORE_URL=${{ vars.OMSORG_CORE_PUBLIC_URL }}, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
- omsorgapp: drop Electron, run as a plain Vite/React browser app; refresh token moves to an HttpOnly cookie (omsorgCore), CORS added for the new browser origin, document download/preview switched to Blob-based browser APIs. - Add Dockerfiles for omsorgCore, omsorgapp, and omsorgWeb, a docker-compose.yml wiring Postgres/MySQL/all three apps together, and a Gitea Actions workflow that builds and pushes images to the repo's container registry on push to main and on version tags. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
129 lines
4.1 KiB
React
129 lines
4.1 KiB
React
import { useState } from "react";
|
|
|
|
import { FormActions } from "../employees/EmployeeForm";
|
|
import { useAuth } from "../../app/AuthContext";
|
|
import ModalPortal from "../../components/ui/ModalPortal";
|
|
|
|
const PIN_VALIDITY_OPTIONS = [1, 3, 7, 14];
|
|
const DEFAULT_PIN_VALIDITY_DAYS = 7;
|
|
|
|
function errorMessage(result) {
|
|
if (result.status === 403) {
|
|
return "Keine Berechtigung, Passwörter zurückzusetzen.";
|
|
}
|
|
if (result.status === 404) {
|
|
return "Nutzer nicht gefunden.";
|
|
}
|
|
if (result.status === 400) {
|
|
return typeof result.data === "string" ? result.data : "Eingaben prüfen.";
|
|
}
|
|
return "Passwort konnte nicht zurückgesetzt werden.";
|
|
}
|
|
|
|
export default function ResetUserPasswordDialog({ user, onClose, onDone }) {
|
|
const { passwordMinLength } = useAuth();
|
|
const [mode, setMode] = useState("Invite");
|
|
const [pinValidityDays, setPinValidityDays] = useState(DEFAULT_PIN_VALIDITY_DAYS);
|
|
const [initialPassword, setInitialPassword] = useState("");
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
|
|
async function handleSubmit(event) {
|
|
event.preventDefault();
|
|
|
|
if (mode === "Direct" && initialPassword.trim().length < passwordMinLength) {
|
|
setError(`Initiales Passwort muss mindestens ${passwordMinLength} Zeichen lang sein.`);
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
setError(null);
|
|
|
|
const result = await window.omsorg.users.resetPassword(user.id, {
|
|
mode,
|
|
initialPassword: mode === "Direct" ? initialPassword : null,
|
|
pinValidityDays: mode === "Invite" ? pinValidityDays : null,
|
|
});
|
|
|
|
setIsSaving(false);
|
|
|
|
if (!result.ok) {
|
|
setError(errorMessage(result));
|
|
return;
|
|
}
|
|
|
|
onDone();
|
|
}
|
|
|
|
return (
|
|
<ModalPortal>
|
|
<div className="modal-overlay" role="dialog" aria-modal="true" aria-label="Passwort zurücksetzen">
|
|
<div className="modal-panel">
|
|
<h2>Passwort für {user.username} zurücksetzen</h2>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<fieldset className="form-field">
|
|
<legend>Zugang</legend>
|
|
|
|
<label>
|
|
<input
|
|
type="radio"
|
|
name="reset-mode"
|
|
value="Invite"
|
|
checked={mode === "Invite"}
|
|
onChange={() => setMode("Invite")}
|
|
/>
|
|
Einladung per Mail (Nutzer setzt eigenes Passwort)
|
|
</label>
|
|
|
|
<label>
|
|
<input
|
|
type="radio"
|
|
name="reset-mode"
|
|
value="Direct"
|
|
checked={mode === "Direct"}
|
|
onChange={() => setMode("Direct")}
|
|
/>
|
|
Passwort direkt vergeben (muss beim nächsten Login geändert werden)
|
|
</label>
|
|
</fieldset>
|
|
|
|
{mode === "Invite" && (
|
|
<label className="form-field">
|
|
<span>PIN gültig für</span>
|
|
<select
|
|
value={pinValidityDays}
|
|
onChange={(event) => setPinValidityDays(Number(event.target.value))}
|
|
>
|
|
{PIN_VALIDITY_OPTIONS.map((days) => (
|
|
<option key={days} value={days}>
|
|
{days} {days === 1 ? "Tag" : "Tage"}
|
|
{days === DEFAULT_PIN_VALIDITY_DAYS ? " (Standard)" : ""}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
)}
|
|
|
|
{mode === "Direct" && (
|
|
<label className="form-field">
|
|
<span>Neues initiales Passwort</span>
|
|
<input
|
|
type="text"
|
|
value={initialPassword}
|
|
onChange={(event) => setInitialPassword(event.target.value)}
|
|
placeholder={`Mindestens ${passwordMinLength} Zeichen`}
|
|
/>
|
|
</label>
|
|
)}
|
|
|
|
{error && <p className="login-error form-error">{error}</p>}
|
|
|
|
<FormActions onCancel={onClose} isSaving={isSaving} submitLabel="Zurücksetzen" />
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</ModalPortal>
|
|
);
|
|
}
|