diff --git a/first-login-ui/src/Main.qml b/first-login-ui/src/Main.qml deleted file mode 100644 index d73ba41..0000000 --- a/first-login-ui/src/Main.qml +++ /dev/null @@ -1,1219 +0,0 @@ -import QtQuick -import QtQuick.Window -import QtQuick.Layouts -import QtQuick.Controls as Controls -import org.kde.kirigami as Kirigami - -Kirigami.ApplicationWindow { - id: root - - title: "Configuration initiale du poste" - visibility: Window.FullScreen - color: "#eef2f7" - - property int currentStep: 0 - property var steps: [ - { "title": "Bienvenue", "subtitle": "Présentation" }, - { "title": "Mot de passe", "subtitle": "Accès de secours" }, - { "title": "YubiKey", "subtitle": "PIN personnel" }, - { "title": "Terminé", "subtitle": "Poste prêt" } - ] - - property bool passwordStepValid: - currentPassword.text.trim().length > 0 && - newPassword.text.length > 0 && - confirmPassword.text.length > 0 && - newPassword.text === confirmPassword.text - - property bool pinStepValid: - currentPin.text.length >= 4 && - newPin.text.length >= 4 && - confirmPin.text.length >= 4 && - newPin.text === confirmPin.text - - property bool workflowComplete: passwordStepValid && pinStepValid - property bool closeAttempted: false - - function goNext() { - // Défense en profondeur : même si un bouton était activé par erreur, - // on ne franchit jamais une étape dont les champs requis sont incomplets. - if (currentStep === 1 && !passwordStepValid) - return - if (currentStep === 2 && !pinStepValid) - return - - if (currentStep < steps.length - 1) - currentStep += 1 - } - - function goBack() { - if (currentStep > 0) - currentStep -= 1 - } - - onClosing: function(close) { - if (!workflowComplete) { - close.accepted = false - closeAttempted = true - closeWarningTimer.restart() - } - } - - Timer { - id: closeWarningTimer - interval: 3500 - repeat: false - onTriggered: root.closeAttempted = false - } - - // Indicateur volontairement informatif : il ne bloque jamais - // la validation du mot de passe. La longueur est privilégiée, - // la diversité des caractères apporte un bonus visuel. - function passwordStrengthLevel(password) { - if (password.length === 0) - return 0 - - var classes = 0 - if (/[a-z]/.test(password)) classes += 1 - if (/[A-Z]/.test(password)) classes += 1 - if (/[0-9]/.test(password)) classes += 1 - if (/[^A-Za-z0-9]/.test(password)) classes += 1 - - var level = 1 - - if (password.length >= 16) - level = classes >= 2 ? 4 : 3 - else if (password.length >= 12) - level = classes >= 2 ? 3 : 2 - else if (password.length >= 8) - level = classes >= 3 ? 2 : 1 - - // Quelques motifs manifestement faibles : on réduit - // l'indication sans empêcher l'utilisateur de continuer. - var lower = password.toLowerCase() - if (lower === "password" || lower === "motdepasse" || - lower === "azerty" || lower === "qwerty" || - lower === "12345678" || /^(.)\1+$/.test(password)) { - level = 1 - } - - return level - } - - function passwordStrengthLabel(level) { - if (level === 1) return "Faible" - if (level === 2) return "Moyen" - if (level === 3) return "Bon" - if (level === 4) return "Très bon" - return "Non évalué" - } - - function passwordStrengthColor(level) { - if (level === 1) return "#dc2626" - if (level === 2) return "#d97706" - if (level === 3) return "#2563eb" - if (level === 4) return "#16a34a" - return "#cbd5e1" - } - - function passwordStrengthHint(level) { - if (level === 1) return "Privilégiez surtout la longueur : une phrase de passe de 12 caractères ou plus est un bon point de départ." - if (level === 2) return "Correct pour un usage courant, mais quelques caractères supplémentaires amélioreraient sensiblement la robustesse." - if (level === 3) return "Bonne robustesse apparente : la longueur et la diversité sont satisfaisantes." - if (level === 4) return "Très bonne robustesse apparente : mot de passe long et suffisamment diversifié." - return "Saisissez votre nouveau mot de passe pour obtenir une indication de robustesse." - } - - component PrimaryButton: Controls.Button { - id: control - implicitHeight: 48 - implicitWidth: 170 - leftPadding: 24 - rightPadding: 24 - - contentItem: Controls.Label { - text: control.text - font.pixelSize: 15 - font.weight: Font.DemiBold - color: "white" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - background: Rectangle { - radius: 12 - color: control.down ? "#1e40af" : control.hovered ? "#2563eb" : "#1d4ed8" - Behavior on color { ColorAnimation { duration: 120 } } - } - } - - component SecondaryButton: Controls.Button { - id: control - implicitHeight: 48 - implicitWidth: 140 - leftPadding: 22 - rightPadding: 22 - - contentItem: Controls.Label { - text: control.text - font.pixelSize: 15 - font.weight: Font.Medium - color: "#334155" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - background: Rectangle { - radius: 12 - color: control.down ? "#dbe3ee" : control.hovered ? "#edf2f7" : "transparent" - border.width: 1 - border.color: "#cbd5e1" - } - } - - component SecretField: Controls.TextField { - id: field - - property bool secretVisible: false - - Layout.fillWidth: true - implicitHeight: 50 - echoMode: secretVisible ? TextInput.Normal : TextInput.Password - passwordCharacter: "●" - font.pixelSize: 15 - leftPadding: 16 - rightPadding: 56 - selectByMouse: true - - background: Rectangle { - radius: 10 - color: field.activeFocus ? "#ffffff" : "#f8fafc" - border.width: field.activeFocus ? 2 : 1 - border.color: field.activeFocus ? "#2563eb" : "#cbd5e1" - } - - Controls.ToolButton { - id: visibilityButton - anchors.right: parent.right - anchors.rightMargin: 8 - anchors.verticalCenter: parent.verticalCenter - width: 38 - height: 38 - - hoverEnabled: true - focusPolicy: Qt.NoFocus - Accessible.name: field.secretVisible ? "Masquer la valeur" : "Afficher la valeur" - - onClicked: field.secretVisible = !field.secretVisible - - contentItem: Kirigami.Icon { - source: field.secretVisible ? "view-hidden" : "view-visible" - implicitWidth: 20 - implicitHeight: 20 - color: visibilityButton.hovered ? "#2563eb" : "#64748b" - } - - background: Rectangle { - radius: 8 - color: visibilityButton.pressed ? "#dbeafe" - : visibilityButton.hovered ? "#eff6ff" - : "transparent" - - Behavior on color { - ColorAnimation { duration: 100 } - } - } - } - } - - component FieldLabel: RowLayout { - id: fieldLabel - - property string iconName: "dialog-password" - property string labelText: "" - - spacing: 8 - - Kirigami.Icon { - source: fieldLabel.iconName - implicitWidth: 17 - implicitHeight: 17 - color: "#64748b" - } - - Controls.Label { - text: fieldLabel.labelText - color: "#334155" - font.pixelSize: 13 - font.weight: Font.DemiBold - } - } - - component StepDot: Rectangle { - required property int stepIndex - - width: 34 - height: 34 - radius: 17 - color: stepIndex < root.currentStep ? "#22c55e" - : stepIndex === root.currentStep ? "#ffffff" - : "#17345f" - border.width: stepIndex === root.currentStep ? 2 : 0 - border.color: "#93c5fd" - - Controls.Label { - anchors.centerIn: parent - text: parent.stepIndex < root.currentStep ? "✓" : (parent.stepIndex + 1) - color: parent.stepIndex === root.currentStep ? "#0f2b50" : "white" - font.pixelSize: 14 - font.weight: Font.Bold - } - } - - RowLayout { - anchors.fill: parent - spacing: 0 - - // ----------------------------------------------------- - // Colonne gauche : identité + progression - // ----------------------------------------------------- - Rectangle { - Layout.preferredWidth: Math.max(330, root.width * 0.29) - Layout.fillHeight: true - - gradient: Gradient { - GradientStop { position: 0.0; color: "#081a33" } - GradientStop { position: 0.55; color: "#0b2a50" } - GradientStop { position: 1.0; color: "#123d70" } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: 44 - spacing: 0 - - RowLayout { - spacing: 14 - - Rectangle { - width: 54 - height: 54 - radius: 16 - color: "#1d4ed8" - - Controls.Label { - anchors.centerIn: parent - text: "N" - color: "white" - font.pixelSize: 25 - font.weight: Font.Bold - } - } - - ColumnLayout { - spacing: 2 - - Controls.Label { - text: "NixOS Workstations" - color: "white" - font.pixelSize: 19 - font.weight: Font.DemiBold - } - - Controls.Label { - text: "Configuration du poste" - color: "#a8c1e1" - font.pixelSize: 13 - } - } - } - - Item { Layout.preferredHeight: 70 } - - Controls.Label { - text: "VOTRE CONFIGURATION" - color: "#7fa4d1" - font.pixelSize: 11 - font.weight: Font.Bold - font.letterSpacing: 1.5 - } - - Item { Layout.preferredHeight: 26 } - - Repeater { - model: root.steps - - delegate: Item { - required property int index - required property var modelData - - Layout.fillWidth: true - Layout.preferredHeight: 78 - - RowLayout { - anchors.fill: parent - spacing: 16 - - // Colonne de largeur fixe : tous les marqueurs et tous - // les titres commencent exactement au même endroit. - Item { - Layout.preferredWidth: 42 - Layout.fillHeight: true - - StepDot { - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - stepIndex: index - } - - Rectangle { - visible: index < root.steps.length - 1 - anchors.top: parent.top - anchors.topMargin: 34 - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: 2 - color: index < root.currentStep ? "#22c55e" : "#28517e" - } - } - - ColumnLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: 3 - - Controls.Label { - Layout.fillWidth: true - text: modelData.title - color: index === root.currentStep ? "white" : "#c1d3e8" - font.pixelSize: 15 - font.weight: index === root.currentStep ? Font.DemiBold : Font.Medium - horizontalAlignment: Text.AlignLeft - } - - Controls.Label { - Layout.fillWidth: true - text: modelData.subtitle - color: "#7699c2" - font.pixelSize: 12 - horizontalAlignment: Text.AlignLeft - } - } - } - } - } - - Item { Layout.fillHeight: true } - - Rectangle { - Layout.fillWidth: true - implicitHeight: 86 - radius: 14 - color: "#102f55" - border.width: 1 - border.color: "#214b78" - - RowLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 12 - - Controls.Label { - text: "🛡" - font.pixelSize: 23 - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 3 - - Controls.Label { - text: "Configuration locale" - color: "white" - font.pixelSize: 13 - font.weight: Font.DemiBold - } - - Controls.Label { - Layout.fillWidth: true - text: "Les secrets définitifs ne seront pas enregistrés dans Git." - wrapMode: Text.WordWrap - color: "#91add0" - font.pixelSize: 11 - } - } - } - } - - Item { Layout.preferredHeight: 20 } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 6 - - Kirigami.Icon { - source: "lock" - implicitWidth: 14 - implicitHeight: 14 - color: "#6289b6" - } - - Controls.Label { - text: "Assistant obligatoire jusqu'à la fin" - color: "#6289b6" - font.pixelSize: 11 - } - } - } - } - - // ----------------------------------------------------- - // Partie droite - // ----------------------------------------------------- - Rectangle { - id: contentArea - Layout.fillWidth: true - Layout.fillHeight: true - color: "#eef2f7" - - ColumnLayout { - anchors.fill: parent - anchors.leftMargin: Math.max(48, contentArea.width * 0.07) - anchors.rightMargin: Math.max(48, contentArea.width * 0.07) - anchors.topMargin: 34 - anchors.bottomMargin: 34 - spacing: 20 - - RowLayout { - Layout.fillWidth: true - - Rectangle { - implicitWidth: previewLabel.implicitWidth + 28 - implicitHeight: 32 - radius: 16 - color: "#fff7ed" - border.width: 1 - border.color: "#fed7aa" - - Controls.Label { - id: previewLabel - anchors.centerIn: parent - text: "MODE APERÇU — aucune modification système" - color: "#c2410c" - font.pixelSize: 11 - font.weight: Font.Bold - } - } - - Item { Layout.fillWidth: true } - - Rectangle { - implicitWidth: lockStatusRow.implicitWidth + 24 - implicitHeight: 34 - radius: 17 - color: root.closeAttempted ? "#fef2f2" : "#f8fafc" - border.width: 1 - border.color: root.closeAttempted ? "#fecaca" : "#cbd5e1" - - RowLayout { - id: lockStatusRow - anchors.centerIn: parent - spacing: 7 - - Kirigami.Icon { - source: root.workflowComplete ? "lock-open" : "lock" - implicitWidth: 16 - implicitHeight: 16 - color: root.closeAttempted ? "#dc2626" : "#64748b" - } - - Controls.Label { - text: root.closeAttempted - ? "Terminez la configuration avant de quitter" - : root.workflowComplete - ? "Configuration complète" - : "Configuration en cours" - color: root.closeAttempted ? "#b91c1c" : "#475569" - font.pixelSize: 11 - font.weight: Font.DemiBold - } - } - } - } - - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - radius: 24 - color: "white" - border.width: 1 - border.color: "#dce3ec" - - ColumnLayout { - anchors.fill: parent - anchors.margins: Math.max(34, Math.min(64, parent.width * 0.055)) - spacing: 24 - - // Petit indicateur de progression supérieur - RowLayout { - Layout.fillWidth: true - spacing: 10 - - Repeater { - model: root.steps.length - - Rectangle { - required property int index - Layout.fillWidth: true - implicitHeight: 5 - radius: 3 - color: index <= root.currentStep ? "#2563eb" : "#e2e8f0" - - Behavior on color { ColorAnimation { duration: 180 } } - } - } - } - - StackLayout { - id: wizard - Layout.fillWidth: true - Layout.fillHeight: true - currentIndex: root.currentStep - - // --------------------------------- - // 0 - Bienvenue - // --------------------------------- - Item { - ColumnLayout { - anchors.fill: parent - spacing: 20 - - Item { Layout.fillHeight: true } - - Rectangle { - Layout.preferredWidth: 86 - Layout.preferredHeight: 86 - Layout.alignment: Qt.AlignHCenter - radius: 24 - color: "#eff6ff" - border.width: 1 - border.color: "#bfdbfe" - - Controls.Label { - anchors.centerIn: parent - text: "✦" - color: "#2563eb" - font.pixelSize: 42 - font.weight: Font.Bold - } - } - - Controls.Label { - Layout.fillWidth: true - text: "Bienvenue sur votre nouveau poste" - color: "#0f172a" - font.pixelSize: 32 - font.weight: Font.Bold - horizontalAlignment: Text.AlignHCenter - } - - Controls.Label { - Layout.maximumWidth: 720 - Layout.alignment: Qt.AlignHCenter - text: "Quelques étapes rapides vont personnaliser vos moyens d'authentification et finaliser la préparation de votre environnement de travail." - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 16 - lineHeight: 1.25 - horizontalAlignment: Text.AlignHCenter - } - - Item { Layout.preferredHeight: 12 } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 14 - - Repeater { - model: [ - { "icon": "🔑", "title": "Mot de passe", "text": "Définir votre accès local de secours" }, - { "icon": "🔐", "title": "YubiKey", "text": "Personnaliser votre PIN FIDO2" } - ] - - Rectangle { - required property var modelData - Layout.preferredWidth: 290 - Layout.preferredHeight: 128 - radius: 16 - color: "#f8fafc" - border.width: 1 - border.color: "#e2e8f0" - - RowLayout { - anchors.fill: parent - anchors.margins: 18 - spacing: 14 - - Controls.Label { - text: modelData.icon - font.pixelSize: 28 - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 5 - - Controls.Label { - text: modelData.title - color: "#0f172a" - font.pixelSize: 15 - font.weight: Font.DemiBold - } - - Controls.Label { - Layout.fillWidth: true - text: modelData.text - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 12 - } - } - } - } - } - } - - Item { Layout.fillHeight: true } - - PrimaryButton { - Layout.alignment: Qt.AlignHCenter - text: "Commencer" - onClicked: root.goNext() - } - } - } - - // --------------------------------- - // 1 - Mot de passe - // --------------------------------- - Item { - ColumnLayout { - anchors.fill: parent - spacing: 18 - - RowLayout { - Layout.fillWidth: true - spacing: 20 - - Rectangle { - Layout.preferredWidth: 80 - Layout.preferredHeight: 80 - radius: 22 - color: "#eff6ff" - border.width: 1 - border.color: "#bfdbfe" - - Kirigami.Icon { - anchors.centerIn: parent - source: "dialog-password" - implicitWidth: 38 - implicitHeight: 38 - color: "#2563eb" - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 5 - - Controls.Label { - text: "Mot de passe local" - color: "#0f172a" - font.pixelSize: 29 - font.weight: Font.Bold - } - - Controls.Label { - Layout.fillWidth: true - text: "Ce mot de passe reste disponible comme solution de secours si votre YubiKey est momentanément indisponible." - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 15 - lineHeight: 1.2 - } - } - } - - Rectangle { - Layout.fillWidth: true - implicitHeight: 70 - radius: 12 - color: "#eff6ff" - border.width: 1 - border.color: "#bfdbfe" - - RowLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 12 - - Kirigami.Icon { - source: "emblem-information" - implicitWidth: 22 - implicitHeight: 22 - color: "#2563eb" - } - - Controls.Label { - Layout.fillWidth: true - text: "Aperçu uniquement : les champs ci-dessous ne modifient actuellement aucun mot de passe." - wrapMode: Text.WordWrap - color: "#1e40af" - font.pixelSize: 13 - } - } - } - - Item { Layout.preferredHeight: 4 } - - FieldLabel { - iconName: "dialog-password" - labelText: "Mot de passe temporaire actuel" - } - - SecretField { - id: currentPassword - placeholderText: "Saisissez le mot de passe temporaire" - } - - FieldLabel { - iconName: "document-edit" - labelText: "Nouveau mot de passe" - } - - SecretField { - id: newPassword - placeholderText: "Choisissez votre mot de passe personnel" - } - - // Indicateur de robustesse purement informatif. - // Il ne participe pas à la condition d'activation - // du bouton Continuer. - Rectangle { - id: strengthCard - Layout.fillWidth: true - implicitHeight: 112 - radius: 12 - color: "#f8fafc" - border.width: 1 - border.color: "#e2e8f0" - - property int strengthLevel: root.passwordStrengthLevel(newPassword.text) - - ColumnLayout { - anchors.fill: parent - anchors.margins: 14 - spacing: 8 - - RowLayout { - Layout.fillWidth: true - spacing: 8 - - Kirigami.Icon { - source: "security-high" - implicitWidth: 17 - implicitHeight: 17 - color: root.passwordStrengthColor(strengthCard.strengthLevel) - } - - Controls.Label { - text: "Robustesse du mot de passe" - color: "#334155" - font.pixelSize: 12 - font.weight: Font.DemiBold - } - - Item { Layout.fillWidth: true } - - Controls.Label { - text: root.passwordStrengthLabel(strengthCard.strengthLevel) - color: root.passwordStrengthColor(strengthCard.strengthLevel) - font.pixelSize: 12 - font.weight: Font.Bold - } - } - - RowLayout { - Layout.fillWidth: true - spacing: 6 - - Repeater { - model: 4 - - Rectangle { - required property int index - Layout.fillWidth: true - implicitHeight: 7 - radius: 4 - color: index < parent.parent.parent.strengthLevel - ? root.passwordStrengthColor(strengthCard.strengthLevel) - : "#e2e8f0" - - Behavior on color { - ColorAnimation { duration: 140 } - } - } - } - } - - Controls.Label { - Layout.fillWidth: true - text: root.passwordStrengthHint(strengthCard.strengthLevel) - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 11 - } - } - } - - Controls.Label { - text: "Indication uniquement : un mot de passe faible n'est pas bloqué." - color: "#94a3b8" - font.pixelSize: 11 - } - - FieldLabel { - iconName: "dialog-ok-apply" - labelText: "Confirmation" - } - - SecretField { - id: confirmPassword - placeholderText: "Confirmez votre nouveau mot de passe" - } - - Controls.Label { - visible: confirmPassword.text.length > 0 && newPassword.text !== confirmPassword.text - text: "Les deux nouveaux mots de passe ne correspondent pas." - color: "#dc2626" - font.pixelSize: 12 - } - - Item { Layout.fillHeight: true } - - RowLayout { - Layout.fillWidth: true - - SecondaryButton { - text: "Retour" - onClicked: root.goBack() - } - - Item { Layout.fillWidth: true } - - PrimaryButton { - text: "Continuer" - enabled: root.passwordStepValid - opacity: enabled ? 1.0 : 0.45 - onClicked: root.goNext() - } - } - } - } - - // --------------------------------- - // 2 - YubiKey - // --------------------------------- - Item { - ColumnLayout { - anchors.fill: parent - spacing: 18 - - RowLayout { - Layout.fillWidth: true - spacing: 20 - - Rectangle { - Layout.preferredWidth: 80 - Layout.preferredHeight: 80 - radius: 22 - color: "#f0fdf4" - border.width: 1 - border.color: "#bbf7d0" - - Controls.Label { - anchors.centerIn: parent - text: "🔐" - font.pixelSize: 34 - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 5 - - Controls.Label { - text: "PIN de votre YubiKey" - color: "#0f172a" - font.pixelSize: 29 - font.weight: Font.Bold - } - - Controls.Label { - Layout.fillWidth: true - text: "Remplacez le PIN temporaire de préparation par un PIN personnel que vous serez seul à connaître." - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 15 - } - } - } - - Rectangle { - Layout.fillWidth: true - implicitHeight: 78 - radius: 12 - color: "#fffbeb" - border.width: 1 - border.color: "#fde68a" - - RowLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 12 - - Controls.Label { - text: "⌨" - color: "#b45309" - font.pixelSize: 23 - } - - Controls.Label { - Layout.fillWidth: true - text: "Vérifiez Verr Num avant d'utiliser le pavé numérique. En cas de doute, utilisez les chiffres situés au-dessus des lettres." - wrapMode: Text.WordWrap - color: "#92400e" - font.pixelSize: 13 - } - } - } - - FieldLabel { - iconName: "dialog-password" - labelText: "PIN temporaire actuel" - } - - SecretField { - id: currentPin - placeholderText: "PIN temporaire" - inputMethodHints: Qt.ImhDigitsOnly - } - - FieldLabel { - iconName: "security-high" - labelText: "Nouveau PIN" - } - - SecretField { - id: newPin - placeholderText: "Nouveau PIN personnel" - inputMethodHints: Qt.ImhDigitsOnly - } - - FieldLabel { - iconName: "dialog-ok-apply" - labelText: "Confirmation du nouveau PIN" - } - - SecretField { - id: confirmPin - placeholderText: "Confirmez le nouveau PIN" - inputMethodHints: Qt.ImhDigitsOnly - } - - Controls.Label { - visible: confirmPin.text.length > 0 && newPin.text !== confirmPin.text - text: "Les deux nouveaux PIN ne correspondent pas." - color: "#dc2626" - font.pixelSize: 12 - } - - Item { Layout.fillHeight: true } - - RowLayout { - Layout.fillWidth: true - - SecondaryButton { - text: "Retour" - onClicked: root.goBack() - } - - Item { Layout.fillWidth: true } - - PrimaryButton { - text: "Finaliser" - enabled: root.pinStepValid - opacity: enabled ? 1.0 : 0.45 - onClicked: root.goNext() - } - } - } - } - - // --------------------------------- - // 3 - Terminé - // --------------------------------- - Item { - ColumnLayout { - anchors.fill: parent - spacing: 18 - - Item { Layout.fillHeight: true } - - Rectangle { - Layout.preferredWidth: 96 - Layout.preferredHeight: 96 - Layout.alignment: Qt.AlignHCenter - radius: 48 - color: "#dcfce7" - border.width: 1 - border.color: "#86efac" - - Kirigami.Icon { - anchors.centerIn: parent - source: "dialog-ok-apply" - implicitWidth: 52 - implicitHeight: 52 - color: "#16a34a" - } - } - - Controls.Label { - Layout.fillWidth: true - text: "Votre poste est prêt" - color: "#0f172a" - font.pixelSize: 32 - font.weight: Font.Bold - horizontalAlignment: Text.AlignHCenter - } - - Controls.Label { - Layout.maximumWidth: 680 - Layout.alignment: Qt.AlignHCenter - text: "Dans la version finale, cet écran confirmera que votre mot de passe local et le PIN de votre YubiKey ont été personnalisés avec succès." - wrapMode: Text.WordWrap - color: "#64748b" - font.pixelSize: 15 - lineHeight: 1.25 - horizontalAlignment: Text.AlignHCenter - } - - Item { Layout.preferredHeight: 12 } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 12 - - Repeater { - model: [ - { "icon": "dialog-password", "title": "Mot de passe", "text": "Accès local renseigné" }, - { "icon": "security-high", "title": "YubiKey", "text": "PIN personnel renseigné" }, - { "icon": "computer-laptop", "title": "Poste", "text": "Configuration finalisée" } - ] - - Rectangle { - required property var modelData - Layout.preferredWidth: 205 - Layout.preferredHeight: 126 - radius: 16 - color: "#f8fafc" - border.width: 1 - border.color: "#e2e8f0" - - ColumnLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 7 - - RowLayout { - Layout.fillWidth: true - - Rectangle { - Layout.preferredWidth: 38 - Layout.preferredHeight: 38 - radius: 11 - color: "#eff6ff" - - Kirigami.Icon { - anchors.centerIn: parent - source: modelData.icon - implicitWidth: 21 - implicitHeight: 21 - color: "#2563eb" - } - } - - Item { Layout.fillWidth: true } - - Rectangle { - Layout.preferredWidth: 25 - Layout.preferredHeight: 25 - radius: 13 - color: "#dcfce7" - - Kirigami.Icon { - anchors.centerIn: parent - source: "dialog-ok-apply" - implicitWidth: 15 - implicitHeight: 15 - color: "#16a34a" - } - } - } - - Controls.Label { - text: modelData.title - color: "#0f172a" - font.pixelSize: 14 - font.weight: Font.DemiBold - } - - Controls.Label { - Layout.fillWidth: true - text: modelData.text - color: "#64748b" - font.pixelSize: 11 - wrapMode: Text.WordWrap - } - } - } - } - } - - Item { Layout.fillHeight: true } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 12 - - SecondaryButton { - text: "Revoir" - onClicked: root.currentStep = 0 - } - - PrimaryButton { - text: "Terminer" - enabled: root.workflowComplete - opacity: enabled ? 1.0 : 0.45 - onClicked: Qt.quit() - } - } - } - } - } - } - } - } - } - } -}