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" } ] function goNext() { if (currentStep < steps.length - 1) { currentStep += 1 } } function goBack() { if (currentStep > 0) { currentStep -= 1 } } Shortcut { sequence: "Ctrl+Q" onActivated: Qt.quit() } 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 Layout.fillWidth: true implicitHeight: 50 echoMode: TextInput.Password passwordCharacter: "●" font.pixelSize: 15 leftPadding: 16 rightPadding: 16 selectByMouse: true background: Rectangle { radius: 10 color: field.activeFocus ? "#ffffff" : "#f8fafc" border.width: field.activeFocus ? 2 : 1 border.color: field.activeFocus ? "#2563eb" : "#cbd5e1" } } 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 ColumnLayout { Layout.alignment: Qt.AlignTop spacing: 0 StepDot { stepIndex: index } Rectangle { visible: index < root.steps.length - 1 Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 2 Layout.preferredHeight: 36 color: index < root.currentStep ? "#22c55e" : "#28517e" } } ColumnLayout { Layout.fillWidth: true Layout.alignment: Qt.AlignTop spacing: 3 Controls.Label { text: modelData.title color: index === root.currentStep ? "white" : "#c1d3e8" font.pixelSize: 15 font.weight: index === root.currentStep ? Font.DemiBold : Font.Medium } Controls.Label { text: modelData.subtitle color: "#7699c2" font.pixelSize: 12 } } } } } 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 } Controls.Label { text: "Ctrl+Q : quitter l'aperçu" color: "#6289b6" font.pixelSize: 11 Layout.alignment: Qt.AlignHCenter } } } // ----------------------------------------------------- // 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 } SecondaryButton { text: "Quitter l'aperçu" onClicked: Qt.quit() } } 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 Controls.Label { text: "Mot de passe local" color: "#0f172a" font.pixelSize: 29 font.weight: Font.Bold } Controls.Label { Layout.maximumWidth: 720 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 Controls.Label { text: "ℹ" color: "#2563eb" font.pixelSize: 22 font.weight: Font.Bold } 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 } Controls.Label { text: "Mot de passe temporaire actuel" color: "#334155" font.pixelSize: 13 font.weight: Font.DemiBold } SecretField { id: currentPassword placeholderText: "Saisissez le mot de passe temporaire" } Controls.Label { text: "Nouveau mot de passe" color: "#334155" font.pixelSize: 13 font.weight: Font.DemiBold } SecretField { id: newPassword placeholderText: "Choisissez votre mot de passe personnel" } Controls.Label { text: "Confirmation" color: "#334155" font.pixelSize: 13 font.weight: Font.DemiBold } 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: newPassword.text.length > 0 && newPassword.text === confirmPassword.text 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 } } } Controls.Label { text: "PIN temporaire actuel" color: "#334155" font.pixelSize: 13 font.weight: Font.DemiBold } SecretField { id: currentPin placeholderText: "PIN temporaire" inputMethodHints: Qt.ImhDigitsOnly } Controls.Label { text: "Nouveau PIN" color: "#334155" font.pixelSize: 13 font.weight: Font.DemiBold } SecretField { id: newPin placeholderText: "Nouveau PIN personnel" inputMethodHints: Qt.ImhDigitsOnly } Controls.Label { text: "Confirmation du nouveau PIN" color: "#334155" font.pixelSize: 13 font.weight: Font.DemiBold } 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: newPin.text.length >= 4 && newPin.text === confirmPin.text 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" Controls.Label { anchors.centerIn: parent text: "✓" color: "#16a34a" font.pixelSize: 50 font.weight: Font.Bold } } 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 } Rectangle { Layout.preferredWidth: 520 Layout.preferredHeight: 112 Layout.alignment: Qt.AlignHCenter radius: 16 color: "#f8fafc" border.width: 1 border.color: "#e2e8f0" ColumnLayout { anchors.fill: parent anchors.margins: 18 spacing: 10 RowLayout { Controls.Label { text: "✓"; color: "#16a34a"; font.pixelSize: 17; font.weight: Font.Bold } Controls.Label { text: "Mot de passe personnel configuré"; color: "#334155"; font.pixelSize: 14 } } RowLayout { Controls.Label { text: "✓"; color: "#16a34a"; font.pixelSize: 17; font.weight: Font.Bold } Controls.Label { text: "PIN personnel YubiKey configuré"; color: "#334155"; font.pixelSize: 14 } } } } Item { Layout.fillHeight: true } RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 12 SecondaryButton { text: "Revoir" onClicked: root.currentStep = 0 } PrimaryButton { text: "Fermer l'aperçu" onClicked: Qt.quit() } } } } } } } } } } }