Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/VaultPanel.qml

Raw
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window

Item {
    id: root

    required property Theme theme
    required property Vault vaultService

    readonly property bool locked: !vaultService.unlocked
    readonly property bool setupMode: vaultService.setupEditing || !vaultService.profile.serverUrl || !vaultService.profile.account
    readonly property var detail: vaultService.detail || ({})
    readonly property var fields: detail.fields || []

    function focusPrimary() {
        if (setupMode)
            serverUrlSetup.input.forceActiveFocus();
        else if (vaultService.status === "second-factor")
            secondFactorCode.input.forceActiveFocus();
        else if (locked)
            masterPassword.input.forceActiveFocus();
        else if (detail.requiresReprompt)
            repromptPassword.input.forceActiveFocus();
        else
            searchField.input.forceActiveFocus();
    }

    function clearPasswordInputs() {
        masterPassword.text = "";
        repromptPassword.text = "";
        showPassword.checked = false;
        showRepromptPassword.checked = false;
    }

    function fieldEnding(suffix) {
        for (let i = 0; i < fields.length; i++)
            if (String(fields[i].id || "").endsWith(suffix))
                return fields[i];
        return null;
    }

    function copyFieldEnding(suffix) {
        const field = fieldEnding(suffix);
        if (field && (field.kind === "text" || field.kind === "url"))
            vaultService.copy(field.id);
    }

    function fieldCopyable(suffix) {
        const field = fieldEnding(suffix);
        return !!field && (field.kind === "text" || field.kind === "url");
    }

    function reconcileVisibleSelection() {
        const visible = vaultService.visibleItems;
        if (visible.length === 0) {
            vaultService.selectedId = "";
            vaultService.detail = ({});
            vaultService.resultScrollPosition = 0;
            vaultService.detailScrollPosition = 0;
            return;
        }
        if (!visible.some(item => item.id === vaultService.selectedId))
            vaultService.select(visible[0].id);
    }

    onSetupModeChanged: if (visible)
        Qt.callLater(root.focusPrimary)
    onLockedChanged: if (visible)
        Qt.callLater(root.focusPrimary)
    Component.onCompleted: Qt.callLater(root.focusPrimary)
    onVisibleChanged: if (visible)
        Qt.callLater(root.focusPrimary)
    onDetailChanged: if (visible && detail.requiresReprompt)
        Qt.callLater(root.focusPrimary)

    Connections {
        target: root.vaultService

        function onStatusChanged() {
            if (root.visible && root.vaultService.status === "second-factor")
                Qt.callLater(root.focusPrimary);
        }

        function onSelectedIdChanged() {
            repromptPassword.text = "";
            showRepromptPassword.checked = false;
        }

        function onLockGenerationChanged() {
            root.clearPasswordInputs();
        }

        function onPanelOpenChanged() {
            if (!root.vaultService.panelOpen)
                root.clearPasswordInputs();
        }
    }

    Connections {
        target: root.Window.window
        enabled: results.restoringScroll || detailScroll.restoringScroll
        function onFrameSwapped() {
            if (results.restoringScroll)
                results.restoreScroll();
            if (detailScroll.restoringScroll)
                detailScroll.restoreScroll();
        }
    }

    Shortcut {
        sequence: "Ctrl+U"
        context: Qt.WindowShortcut
        enabled: !root.locked && root.fieldCopyable(".username")
        onActivated: root.copyFieldEnding(".username")
    }

    Shortcut {
        sequence: "Ctrl+P"
        context: Qt.WindowShortcut
        enabled: !root.locked && root.fieldCopyable(".password")
        onActivated: root.copyFieldEnding(".password")
    }

    Shortcut {
        sequence: "Ctrl+T"
        context: Qt.WindowShortcut
        enabled: !root.locked && !!root.detail.totp
        onActivated: root.vaultService.copy("totp-current", root.detail.totp.currentSlot)
    }

    Shortcut {
        sequence: "Ctrl+Shift+T"
        context: Qt.WindowShortcut
        enabled: !root.locked && !!root.detail.totp
        onActivated: root.vaultService.copy("totp-next", root.detail.totp.nextSlot)
    }

    Item {
        id: browserPane

        anchors.top: parent.top
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        width: Math.round(parent.width * 0.37)

        PopupTextField {
            id: searchField

            objectName: "vaultSearchField"
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.rightMargin: 14
            height: 42
            theme: root.theme
            icon: ""
            placeholderText: "search vault"
            text: root.vaultService.query
            enabled: !root.locked
            onEdited: text => {
                root.vaultService.query = text;
                root.reconcileVisibleSelection();
            }
            Keys.onUpPressed: event => {
                event.accepted = true;
                root.vaultService.moveSelection(-1);
            }
            Keys.onDownPressed: event => {
                event.accepted = true;
                root.vaultService.moveSelection(1);
            }
            Keys.onReturnPressed: event => {
                event.accepted = true;
                if (root.vaultService.selectedId.length > 0)
                    root.vaultService.refreshDetail();
            }
        }

        Flickable {
            id: results

            anchors.top: searchField.bottom
            anchors.topMargin: 8
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.rightMargin: 14
            anchors.bottom: statusRow.top
            anchors.bottomMargin: 8
            contentHeight: resultColumn.implicitHeight
            clip: true
            boundsBehavior: Flickable.StopAtBounds
            property bool restoringScroll: true

            function restoreScroll() {
                contentY = Math.min(root.vaultService.resultScrollPosition, Math.max(0, contentHeight - height));
                restoringScroll = false;
            }

            function revealItem(item) {
                const point = item.mapToItem(resultColumn, 0, 0);
                if (point.y < contentY)
                    contentY = point.y;
                else if (point.y + item.height > contentY + height)
                    contentY = Math.min(point.y + item.height - height, Math.max(0, contentHeight - height));
            }

            onContentYChanged: if (!restoringScroll)
                root.vaultService.resultScrollPosition = contentY

            Column {
                id: resultColumn

                width: results.width
                spacing: 5

                NuguText {
                    visible: root.locked || root.vaultService.visibleItems.length === 0
                    width: parent.width
                    height: 80
                    verticalAlignment: Text.AlignVCenter
                    horizontalAlignment: Text.AlignHCenter
                    theme: root.theme
                    variant: "meta"
                    text: root.locked ? "unlock to search vault" : (root.vaultService.status === "loading" ? "loading vault" : "no matching items")
                }

                Repeater {
                    model: root.locked ? [] : root.vaultService.groupedItems

                    delegate: Column {
                        id: group

                        required property var modelData
                        width: resultColumn.width
                        spacing: 4

                        NuguText {
                            width: parent.width
                            height: 24
                            verticalAlignment: Text.AlignBottom
                            theme: root.theme
                            variant: "meta"
                            text: group.modelData.label
                        }

                        Repeater {
                            model: group.modelData.items

                            delegate: PopupCard {
                                id: itemRow

                                required property var modelData
                                theme: root.theme
                                padding: 0
                                active: modelData.id === root.vaultService.selectedId
                                interactive: true
                                width: group.width
                                height: 56
                                activeFocusOnTab: true
                                Accessible.role: Accessible.ListItem
                                Accessible.name: modelData.title + ", " + modelData.itemType
                                onActiveChanged: if (active)
                                    Qt.callLater(results.revealItem, itemRow)
                                Component.onCompleted: if (active)
                                    Qt.callLater(results.revealItem, itemRow)
                                Keys.onReturnPressed: event => {
                                    event.accepted = true;
                                    root.vaultService.select(modelData.id);
                                }
                                onClicked: root.vaultService.select(modelData.id)

                                Item {
                                    id: iconSlot

                                    anchors.left: parent.left
                                    anchors.leftMargin: 8
                                    anchors.verticalCenter: parent.verticalCenter
                                    width: 36
                                    height: 36

                                    Image {
                                        id: favicon

                                        anchors.fill: parent
                                        visible: source.toString().length > 0 && status !== Image.Error
                                        source: itemRow.modelData.favicon ? "file://" + itemRow.modelData.favicon : ""
                                        sourceSize.width: 36
                                        sourceSize.height: 36
                                        fillMode: Image.PreserveAspectFit
                                        asynchronous: true
                                    }

                                    Rectangle {
                                        anchors.fill: parent
                                        visible: !favicon.visible
                                        radius: 9
                                        color: root.theme.withAlpha(root.theme.ui.accent, 0.1)

                                        Text {
                                            anchors.centerIn: parent
                                            color: root.theme.ui.accent
                                            font.family: "Symbols Nerd Font Mono"
                                            font.pixelSize: 16
                                            text: itemRow.modelData.itemType === "login" ? "" : (itemRow.modelData.itemType === "card" ? "" : (itemRow.modelData.itemType === "identity" ? "" : (itemRow.modelData.itemType === "ssh key" ? "" : "")))
                                        }
                                    }
                                }

                                Column {
                                    anchors.left: iconSlot.right
                                    anchors.leftMargin: 10
                                    anchors.right: favoriteMark.left
                                    anchors.rightMargin: 8
                                    anchors.verticalCenter: parent.verticalCenter
                                    spacing: 2

                                    NuguText {
                                        theme: root.theme
                                        width: parent.width
                                        text: itemRow.modelData.title
                                        elide: Text.ElideRight
                                    }

                                    NuguText {
                                        theme: root.theme
                                        variant: "meta"
                                        width: parent.width
                                        text: itemRow.modelData.itemType + (itemRow.modelData.subtitle ? " · " + itemRow.modelData.subtitle : "")
                                        elide: Text.ElideRight
                                    }
                                }

                                Text {
                                    id: favoriteMark

                                    anchors.right: parent.right
                                    anchors.rightMargin: 11
                                    anchors.verticalCenter: parent.verticalCenter
                                    width: 18
                                    visible: itemRow.modelData.favorite
                                    color: root.theme.ui.accent
                                    font.family: "Symbols Nerd Font Mono"
                                    font.pixelSize: 14
                                    text: ""
                                }
                            }
                        }
                    }
                }
            }
        }

        Row {
            id: statusRow

            anchors.left: parent.left
            anchors.right: parent.right
            anchors.rightMargin: 14
            anchors.bottom: parent.bottom
            height: 24
            spacing: 6

            Rectangle {
                anchors.verticalCenter: parent.verticalCenter
                width: 7
                height: 7
                radius: 4
                color: root.vaultService.status === "unlocked" ? root.theme.info : (root.vaultService.status === "loading" ? root.theme.warning : root.theme.ui.minor)
            }

            NuguText {
                anchors.verticalCenter: parent.verticalCenter
                width: parent.width - 13 - (settingsButton.visible ? settingsButton.width + statusRow.spacing : 0)
                theme: root.theme
                variant: "meta"
                text: root.vaultService.status === "unlocked" ? "vault ready" : root.vaultService.status.replace(/-/g, " ")
                elide: Text.ElideRight
            }

            PopupButton {
                id: settingsButton

                theme: root.theme
                text: "settings"
                accessibleName: "vault settings"
                height: 24
                visible: !root.setupMode
                enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending
                onClicked: root.vaultService.beginSetup()
            }
        }
    }

    Rectangle {
        anchors.top: parent.top
        anchors.bottom: parent.bottom
        anchors.left: browserPane.right
        width: 1
        color: root.theme.withAlpha(root.theme.ui.minor, 0.35)
    }

    Item {
        id: detailPane

        anchors.top: parent.top
        anchors.bottom: parent.bottom
        anchors.left: browserPane.right
        anchors.leftMargin: 18
        anchors.right: parent.right

        Item {
            anchors.fill: parent
            visible: root.locked

            Column {
                visible: root.setupMode
                anchors.centerIn: parent
                width: Math.min(390, parent.width - 32)
                spacing: 12

                NuguText {
                    theme: root.theme
                    variant: "title"
                    font.pixelSize: 24
                    text: "configure vault"
                }

                NuguText {
                    theme: root.theme
                    text: "server url"
                }

                SetupField {
                    id: serverUrlSetup

                    objectName: "vaultSetupServerUrl"
                    theme: root.theme
                    placeholder: "vault.example.com"
                    text: root.vaultService.setupUrlDraft
                    enabled: !root.vaultService.setupSaving && !root.vaultService.connectionBusy
                    onEdited: text => root.vaultService.setupUrlDraft = text
                    onAccepted: accountSetup.input.forceActiveFocus()
                }

                NuguText {
                    theme: root.theme
                    text: "account"
                }

                SetupField {
                    id: accountSetup

                    objectName: "vaultSetupAccount"
                    theme: root.theme
                    placeholder: "email address"
                    text: root.vaultService.setupAccountDraft
                    enabled: !root.vaultService.setupSaving && !root.vaultService.connectionBusy
                    onEdited: text => root.vaultService.setupAccountDraft = text
                    onAccepted: root.vaultService.saveSetup()
                }

                Row {
                    spacing: 8

                    PopupButton {
                        theme: root.theme
                        text: root.vaultService.connectionTesting ? "testing" : "test connection"
                        enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending && !root.vaultService.connectionBusy
                        onClicked: root.vaultService.testConnection()
                    }

                    PopupButton {
                        theme: root.theme
                        text: root.vaultService.setupSaving ? "saving" : "save"
                        role: "primary"
                        enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending && !root.vaultService.connectionBusy
                        onClicked: root.vaultService.saveSetup()
                    }

                    PopupButton {
                        theme: root.theme
                        text: "cancel"
                        visible: !!root.vaultService.profile.serverUrl && !!root.vaultService.profile.account
                        enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending
                        onClicked: root.vaultService.cancelSetup()
                    }
                }

                NuguText {
                    width: parent.width
                    theme: root.theme
                    variant: "meta"
                    text: root.vaultService.connectionResult
                    visible: text.length > 0
                    wrapMode: Text.WordWrap
                }

                NuguText {
                    width: parent.width
                    theme: root.theme
                    variant: "meta"
                    color: root.theme.error
                    text: root.vaultService.setupError || root.vaultService.message
                    visible: text.length > 0
                    wrapMode: Text.WordWrap
                }
            }

            Column {
                visible: !root.setupMode
                anchors.centerIn: parent
                width: Math.min(390, parent.width - 32)
                spacing: 12

                Rectangle {
                    anchors.horizontalCenter: parent.horizontalCenter
                    width: 66
                    height: 66
                    radius: 18
                    color: root.theme.withAlpha(root.theme.ui.accent, 0.1)
                    border.width: 1
                    border.color: root.theme.withAlpha(root.theme.ui.accent, 0.45)

                    Text {
                        anchors.centerIn: parent
                        color: root.theme.ui.accent
                        font.family: "Symbols Nerd Font Mono"
                        font.pixelSize: 28
                        text: ""
                    }
                }

                NuguText {
                    anchors.horizontalCenter: parent.horizontalCenter
                    theme: root.theme
                    variant: "title"
                    font.pixelSize: 24
                    text: root.vaultService.status === "second-factor" ? "verify account" : (root.vaultService.status === "authentication-failure" ? "unlock failed" : "unlock vault")
                }

                NuguText {
                    width: parent.width
                    horizontalAlignment: Text.AlignHCenter
                    theme: root.theme
                    variant: "meta"
                    text: String(root.vaultService.profile.serverUrl || "") + "\n" + String(root.vaultService.profile.account || "")
                }

                Row {
                    visible: root.vaultService.status !== "second-factor"
                    width: parent.width
                    height: visible ? 48 : 0
                    spacing: 6

                    PopupTextField {
                        id: masterPassword

                        objectName: "vaultMasterPassword"
                        theme: root.theme
                        width: parent.width - 46
                        height: parent.height
                        placeholderText: "master password"
                        echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
                        inputMethodHints: Qt.ImhSensitiveData
                        onAccepted: {
                            const secret = text;
                            text = "";
                            root.vaultService.unlock(secret);
                        }
                    }

                    PopupButton {
                        id: showPassword

                        theme: root.theme
                        width: 40
                        height: 40
                        anchors.verticalCenter: parent.verticalCenter
                        icon: checked ? "" : ""
                        text: "show master password"
                        accessibleName: text
                        tooltip: text
                        showText: false
                        onClicked: checked = !checked
                    }
                }

                PopupButton {
                    visible: root.vaultService.status !== "second-factor"
                    anchors.horizontalCenter: parent.horizontalCenter
                    height: visible ? implicitHeight : 0
                    theme: root.theme
                    role: "primary"
                    text: root.vaultService.status === "loading" ? "unlocking" : "unlock"
                    enabled: root.vaultService.status !== "loading"
                    onClicked: {
                        const secret = masterPassword.text;
                        masterPassword.text = "";
                        root.vaultService.unlock(secret);
                    }
                }

                Column {
                    visible: root.vaultService.status === "second-factor"
                    width: parent.width
                    height: visible ? implicitHeight : 0
                    spacing: 8

                    NuguText {
                        width: parent.width
                        horizontalAlignment: Text.AlignHCenter
                        theme: root.theme
                        variant: "meta"
                        text: root.vaultService.secondFactorHint()
                    }

                    Row {
                        anchors.horizontalCenter: parent.horizontalCenter
                        spacing: 6

                        Repeater {
                            model: root.vaultService.challenge.providers || []

                            delegate: PopupButton {
                                required property var modelData
                                theme: root.theme
                                text: String(modelData.label || "verification")
                                checked: Number(modelData.id) === root.vaultService.secondFactorProvider
                                enabled: !!modelData.codeSupported && !root.vaultService.verificationPending
                                onClicked: {
                                    root.vaultService.selectSecondFactor(modelData.id);
                                    secondFactorCode.input.forceActiveFocus();
                                }
                            }
                        }
                    }

                    PopupTextField {
                        id: secondFactorCode

                        width: parent.width
                        height: 44
                        theme: root.theme
                        placeholderText: "verification code"
                        inputMethodHints: Qt.ImhSensitiveData
                        text: root.vaultService.secondFactorDraft
                        enabled: !root.vaultService.verificationPending
                        onEdited: text => root.vaultService.secondFactorDraft = text
                        onAccepted: root.vaultService.submitSecondFactor(text)
                    }

                    Row {
                        anchors.horizontalCenter: parent.horizontalCenter
                        spacing: 8

                        PopupButton {
                            theme: root.theme
                            text: root.vaultService.verificationPending ? "verifying" : "verify"
                            role: "primary"
                            enabled: root.vaultService.secondFactorProvider >= 0 && !root.vaultService.verificationPending
                            onClicked: root.vaultService.submitSecondFactor(secondFactorCode.text)
                        }

                        PopupButton {
                            theme: root.theme
                            text: "cancel"
                            onClicked: root.vaultService.cancelUnlock()
                        }
                    }
                }

                NuguText {
                    visible: root.vaultService.message.length > 0
                    width: parent.width
                    horizontalAlignment: Text.AlignHCenter
                    theme: root.theme
                    variant: "meta"
                    color: root.vaultService.verificationPending || root.vaultService.message === "verification required" ? root.theme.ui.minor : root.theme.error
                    text: root.vaultService.message
                    wrapMode: Text.WordWrap
                }

                NuguText {
                    width: parent.width
                    horizontalAlignment: Text.AlignHCenter
                    theme: root.theme
                    variant: "meta"
                    text: root.vaultService.cache.available ? (root.vaultService.cache.stale ? "encrypted cache stale" : "encrypted cache ready") : "no encrypted cache"
                }
            }
        }

        Item {
            anchors.fill: parent
            visible: !root.locked

            Item {
                id: detailHeader

                anchors.top: parent.top
                anchors.left: parent.left
                anchors.right: parent.right
                height: 58

                NuguText {
                    id: detailTitle

                    width: parent.width - headerActions.width - 12
                    theme: root.theme
                    variant: "title"
                    font.pixelSize: 22
                    text: String(root.detail.title || "select an item")
                    elide: Text.ElideRight
                }

                NuguText {
                    anchors.top: detailTitle.bottom
                    anchors.topMargin: 2
                    width: parent.width - headerActions.width - 12
                    theme: root.theme
                    variant: "meta"
                    text: String(root.detail.itemType || "") + (root.detail.subtitle ? " · " + root.detail.subtitle : "")
                    elide: Text.ElideRight
                }

                Row {
                    id: headerActions

                    anchors.right: parent.right
                    anchors.top: parent.top
                    spacing: 6

                    PopupButton {
                        theme: root.theme
                        width: 36
                        height: 36
                        icon: root.detail.favorite ? "" : ""
                        text: root.detail.favorite ? "unfavorite" : "favorite"
                        accessibleName: text
                        tooltip: text
                        showText: false
                        checked: !!root.detail.favorite
                        enabled: !!root.detail.id
                        onClicked: root.vaultService.setFavorite(!root.detail.favorite)
                    }

                    PopupButton {
                        theme: root.theme
                        width: 36
                        height: 36
                        icon: ""
                        text: "sync vault"
                        accessibleName: text
                        tooltip: text
                        showText: false
                        onClicked: root.vaultService.sync()
                    }

                    PopupButton {
                        theme: root.theme
                        width: 36
                        height: 36
                        icon: ""
                        text: "lock vault"
                        accessibleName: text
                        tooltip: text
                        showText: false
                        onClicked: root.vaultService.lock()
                    }
                }
            }

            Flickable {
                id: detailScroll

                anchors.top: detailHeader.bottom
                anchors.topMargin: 8
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: feedbackLabel.top
                anchors.bottomMargin: 5
                contentHeight: detailColumn.implicitHeight
                clip: true
                boundsBehavior: Flickable.StopAtBounds
                property bool restoringScroll: true

                function restoreScroll() {
                    contentY = Math.min(root.vaultService.detailScrollPosition, Math.max(0, contentHeight - height));
                    restoringScroll = false;
                }

                onContentYChanged: if (!restoringScroll)
                    root.vaultService.detailScrollPosition = contentY

                Column {
                    id: detailColumn

                    width: detailScroll.width
                    spacing: 8

                    PopupCard {
                        visible: !!root.detail.requiresReprompt
                        theme: root.theme
                        active: true
                        padding: 10
                        width: parent.width
                        height: visible ? 108 : 0

                        Column {
                            anchors.fill: parent
                            spacing: 7

                            NuguText {
                                width: parent.width
                                theme: root.theme
                                variant: "meta"
                                text: root.vaultService.repromptError || "master password required for sensitive fields"
                                color: root.vaultService.repromptError ? root.theme.error : root.theme.ui.minor
                                elide: Text.ElideRight
                            }

                            Row {
                                width: parent.width
                                height: 44
                                spacing: 6

                                PopupTextField {
                                    id: repromptPassword

                                    theme: root.theme
                                    width: parent.width - 86
                                    height: parent.height
                                    placeholderText: "master password"
                                    echoMode: showRepromptPassword.checked ? TextInput.Normal : TextInput.Password
                                    inputMethodHints: Qt.ImhSensitiveData
                                    onAccepted: {
                                        const secret = text;
                                        text = "";
                                        root.vaultService.reprompt(secret);
                                    }
                                }

                                PopupButton {
                                    id: showRepromptPassword

                                    theme: root.theme
                                    width: 34
                                    height: 34
                                    anchors.verticalCenter: parent.verticalCenter
                                    icon: checked ? "" : ""
                                    text: "show master password"
                                    accessibleName: text
                                    tooltip: text
                                    showText: false
                                    onClicked: checked = !checked
                                }

                                PopupButton {
                                    theme: root.theme
                                    width: 34
                                    height: 34
                                    anchors.verticalCenter: parent.verticalCenter
                                    icon: ""
                                    text: "verify master password"
                                    accessibleName: text
                                    tooltip: text
                                    showText: false
                                    onClicked: {
                                        const secret = repromptPassword.text;
                                        repromptPassword.text = "";
                                        root.vaultService.reprompt(secret);
                                    }
                                }
                            }
                        }
                    }

                    PopupButton {
                        visible: !!root.detail.totp
                        theme: root.theme
                        width: parent.width
                        height: visible ? 34 : 0
                        text: "totp"
                        icon: root.vaultService.sectionExpanded("totp") ? "" : ""
                        accessibleName: (root.vaultService.sectionExpanded("totp") ? "collapse " : "expand ") + text
                        tooltip: accessibleName
                        onClicked: root.vaultService.toggleSection("totp")
                    }

                    PopupCard {
                        visible: !!root.detail.totp && root.vaultService.sectionExpanded("totp")
                        theme: root.theme
                        active: true
                        padding: 12
                        width: parent.width
                        height: visible ? 106 : 0

                        Row {
                            anchors.fill: parent
                            spacing: 16

                            Column {
                                width: parent.width * 0.62
                                spacing: 7

                                NuguText {
                                    theme: root.theme
                                    variant: "meta"
                                    text: "current code"
                                }

                                PopupButton {
                                    theme: root.theme
                                    height: 46
                                    text: String(root.detail.totp ? root.detail.totp.current : "")
                                    accessibleName: "copy current code"
                                    tooltip: accessibleName
                                    onClicked: root.vaultService.copy("totp-current", root.detail.totp.currentSlot)
                                }
                            }

                            Column {
                                width: parent.width * 0.32
                                spacing: 7

                                NuguText {
                                    theme: root.theme
                                    variant: "meta"
                                    text: "next"
                                }

                                PopupButton {
                                    theme: root.theme
                                    width: parent.width
                                    height: 34
                                    text: String(root.detail.totp ? root.detail.totp.next : "")
                                    accessibleName: "copy next code"
                                    tooltip: accessibleName
                                    onClicked: root.vaultService.copy("totp-next", root.detail.totp.nextSlot)
                                }

                                NuguText {
                                    theme: root.theme
                                    variant: "meta"
                                    text: String(root.detail.totp ? root.detail.totp.remaining : 0) + "s remaining"
                                }
                            }
                        }
                    }

                    PopupButton {
                        visible: root.fields.length > 0
                        theme: root.theme
                        width: parent.width
                        height: visible ? 34 : 0
                        text: "fields"
                        icon: root.vaultService.sectionExpanded("fields") ? "" : ""
                        accessibleName: (root.vaultService.sectionExpanded("fields") ? "collapse " : "expand ") + text
                        tooltip: accessibleName
                        onClicked: root.vaultService.toggleSection("fields")
                    }

                    Repeater {
                        model: root.vaultService.sectionExpanded("fields") ? root.fields : []

                        delegate: PopupCard {
                            id: fieldRow

                            required property var modelData
                            objectName: "vault-field-" + modelData.id
                            theme: root.theme
                            padding: 0
                            width: detailColumn.width
                            height: 58

                            Column {
                                anchors.left: parent.left
                                anchors.leftMargin: 12
                                anchors.right: actions.left
                                anchors.rightMargin: 8
                                anchors.verticalCenter: parent.verticalCenter
                                spacing: 3

                                NuguText {
                                    width: parent.width
                                    theme: root.theme
                                    variant: "meta"
                                    text: String(fieldRow.modelData.label || "field")
                                    elide: Text.ElideRight
                                }

                                NuguText {
                                    width: parent.width
                                    theme: root.theme
                                    font.family: "IBM Plex Mono"
                                    text: fieldRow.modelData.kind === "restricted" ? (root.detail.requiresReprompt ? "re-prompt required" : "access denied") : fieldRow.modelData.sensitive && !root.vaultService.isRevealed(fieldRow.modelData.id) ? "••••••••••••" : String(fieldRow.modelData.value || "")
                                    elide: Text.ElideRight
                                }
                            }

                            Row {
                                id: actions

                                anchors.right: parent.right
                                anchors.rightMargin: 8
                                anchors.verticalCenter: parent.verticalCenter
                                spacing: 5

                                PopupButton {
                                    visible: fieldRow.modelData.sensitive && fieldRow.modelData.kind !== "restricted"
                                    theme: root.theme
                                    width: 34
                                    height: 34
                                    icon: root.vaultService.isRevealed(fieldRow.modelData.id) ? "" : ""
                                    text: root.vaultService.isRevealed(fieldRow.modelData.id) ? "conceal " + fieldRow.modelData.label : "reveal " + fieldRow.modelData.label
                                    accessibleName: text
                                    tooltip: text
                                    showText: false
                                    onClicked: root.vaultService.toggleReveal(fieldRow.modelData.id)
                                }

                                PopupButton {
                                    visible: fieldRow.modelData.kind === "url"
                                    theme: root.theme
                                    width: 34
                                    height: 34
                                    icon: ""
                                    text: "open " + fieldRow.modelData.label
                                    accessibleName: text
                                    tooltip: text
                                    showText: false
                                    onClicked: root.vaultService.open(fieldRow.modelData.id)
                                }

                                PopupButton {
                                    visible: fieldRow.modelData.kind === "text" || fieldRow.modelData.kind === "url"
                                    theme: root.theme
                                    width: 34
                                    height: 34
                                    icon: ""
                                    text: "copy " + fieldRow.modelData.label
                                    accessibleName: text
                                    tooltip: text
                                    showText: false
                                    onClicked: root.vaultService.copy(fieldRow.modelData.id)
                                }

                                NuguText {
                                    visible: fieldRow.modelData.kind === "unsupported" || fieldRow.modelData.kind === "restricted"
                                    anchors.verticalCenter: parent.verticalCenter
                                    theme: root.theme
                                    variant: "meta"
                                    text: fieldRow.modelData.kind === "restricted" ? "restricted" : "unsupported"
                                }
                            }
                        }
                    }
                }
            }

            NuguText {
                id: feedbackLabel

                anchors.left: parent.left
                anchors.right: parent.right
                anchors.bottom: parent.bottom
                height: 20
                horizontalAlignment: Text.AlignRight
                theme: root.theme
                variant: "meta"
                color: root.theme.info
                text: root.vaultService.feedback
            }
        }
    }
}