Files
nixos-workstations/first-login-ui/src/Main.qml
T

865 lines
37 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}
}
}
}
}
}
}
}
}
}
}