10.6 KiB · text History 35c3541
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM

KCM.SimpleKCM {
    id: configGeneral

    property alias cfg_instanceUrl: instanceUrlField.text
    property alias cfg_accessToken: accessTokenField.text
    property string cfg_dashboards: "[]"

    property bool isTesting: false
    property string testResult: ""
    property bool testSuccess: false

    property bool isLoading: false
    property string loadError: ""
    property var available: []

    readonly property var chosen: {
        try {
            var parsed = JSON.parse(cfg_dashboards || "[]")
            return Array.isArray(parsed) ? parsed : []
        } catch (e) {
            return []
        }
    }

    readonly property bool canConnect: instanceUrlField.text.trim() !== "" && accessTokenField.text.trim() !== ""

    GrafanaAPI {
        id: testAPI
        baseUrl: instanceUrlField.text
        accessToken: accessTokenField.text
    }

    function testConnection() {
        if (!canConnect) return
        isTesting = true
        testResult = ""
        testAPI.testConnection(function(ok, message) {
            isTesting = false
            testResult = message
            testSuccess = ok
            if (ok) loadDashboards()
        })
    }

    function loadDashboards() {
        if (!canConnect) return
        isLoading = true
        loadError = ""
        testAPI.search("", function(ok, data) {
            isLoading = false
            if (!ok) { loadError = data; return }
            available = (data || []).filter(function(d) { return d.type === "dash-db" })
                .sort(function(a, b) {
                    return ((a.folderTitle || "") + "/" + a.title).localeCompare((b.folderTitle || "") + "/" + b.title)
                })
        })
    }

    function save(list) { cfg_dashboards = JSON.stringify(list) }
    function isChosen(uid) { return chosen.some(function(d) { return d.uid === uid }) }
    function add(d) { if (!isChosen(d.uid)) save(chosen.concat([{ uid: d.uid, title: d.title }])) }
    function removeAt(i) { var l = chosen.slice(); l.splice(i, 1); save(l) }
    function move(i, delta) {
        var t = i + delta
        if (t < 0 || t >= chosen.length) return
        var l = chosen.slice()
        var item = l.splice(i, 1)[0]
        l.splice(t, 0, item)
        save(l)
    }

    readonly property var filtered: {
        var q = searchField.text.trim().toLowerCase()
        if (!q) return available
        return available.filter(function(d) {
            return (d.title || "").toLowerCase().indexOf(q) !== -1 || (d.folderTitle || "").toLowerCase().indexOf(q) !== -1
                   || (d.tags || []).join(" ").toLowerCase().indexOf(q) !== -1
        })
    }

    Component.onCompleted: if (canConnect) Qt.callLater(loadDashboards)

    ColumnLayout {
        spacing: Kirigami.Units.largeSpacing

        Kirigami.FormLayout {
            Layout.fillWidth: true

            QQC2.TextField {
                id: instanceUrlField
                Kirigami.FormData.label: i18n("Grafana URL:")
                placeholderText: i18n("https://grafana.example.com")
                Layout.fillWidth: true
                onTextChanged: testResult = ""
            }

            RowLayout {
                Kirigami.FormData.label: i18n("Service account token:")
                Layout.fillWidth: true

                QQC2.TextField {
                    id: accessTokenField
                    Layout.fillWidth: true
                    placeholderText: i18n("glsa_…")
                    echoMode: showToken.checked ? TextInput.Normal : TextInput.Password
                    onTextChanged: testResult = ""
                }

                QQC2.ToolButton {
                    id: showToken
                    checkable: true
                    icon.name: checked ? "password-show-off" : "password-show-on"
                    QQC2.ToolTip.text: checked ? i18n("Hide token") : i18n("Show token")
                    QQC2.ToolTip.visible: hovered
                }
            }

            RowLayout {
                Kirigami.FormData.label: i18n("Connection:")

                QQC2.Button {
                    text: i18n("Test Connection")
                    icon.name: "network-connect"
                    enabled: !isTesting && canConnect
                    onClicked: testConnection()
                }

                QQC2.BusyIndicator {
                    visible: isTesting
                    Layout.preferredWidth: Kirigami.Units.iconSizes.small
                    Layout.preferredHeight: Kirigami.Units.iconSizes.small
                }
            }

            QQC2.Label {
                visible: testResult !== ""
                text: testResult
                color: testSuccess ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.negativeTextColor
                wrapMode: Text.WordWrap
                Layout.fillWidth: true
                font: Kirigami.Theme.smallFont
            }

            QQC2.Label {
                Layout.fillWidth: true
                wrapMode: Text.WordWrap
                font: Kirigami.Theme.smallFont
                color: Kirigami.Theme.disabledTextColor
                textFormat: Text.StyledText
                onLinkActivated: function(link) { Qt.openUrlExternally(link) }
                text: {
                    var base = testAPI.normalizedBase()
                    var where = base ? "<a href=\"" + base + "/org/serviceaccounts\">" + i18n("Service accounts") + "</a>" : i18n("Administration → Users and access → Service accounts")
                    return i18n("Create a service account under %1 with the <b>Viewer</b> role, then add a token to it. The widget only reads.", where)
                }
            }
        }

        // ── chosen ──────────────────────────────────────────────────────
        Kirigami.Heading {
            level: 3
            text: i18n("Dashboards")
        }

        QQC2.Label {
            visible: chosen.length === 0
            Layout.fillWidth: true
            wrapMode: Text.WordWrap
            opacity: 0.7
            text: i18n("None yet. Add dashboards from the list below; the first is the one shown by default.")
        }

        Repeater {
            model: chosen

            delegate: RowLayout {
                required property var modelData
                required property int index
                Layout.fillWidth: true
                spacing: Kirigami.Units.smallSpacing

                Kirigami.Icon {
                    source: "view-grid"
                    Layout.preferredWidth: Kirigami.Units.iconSizes.small
                    Layout.preferredHeight: Kirigami.Units.iconSizes.small
                }

                QQC2.Label {
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                    text: modelData.title + "  (" + modelData.uid + ")"
                }

                QQC2.ToolButton { icon.name: "go-up"; enabled: index > 0; onClicked: move(index, -1) }
                QQC2.ToolButton { icon.name: "go-down"; enabled: index < chosen.length - 1; onClicked: move(index, 1) }
                QQC2.ToolButton {
                    icon.name: "list-remove"
                    onClicked: removeAt(index)
                    QQC2.ToolTip.text: i18n("Remove")
                    QQC2.ToolTip.visible: hovered
                }
            }
        }

        // ── available ───────────────────────────────────────────────────
        RowLayout {
            Layout.fillWidth: true

            Kirigami.Heading {
                Layout.fillWidth: true
                level: 3
                text: i18n("On the server")
            }

            QQC2.BusyIndicator {
                visible: isLoading
                Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
                Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
            }

            QQC2.Button {
                text: available.length > 0 ? i18n("Reload") : i18n("List Dashboards")
                icon.name: "view-refresh"
                enabled: canConnect && !isLoading
                onClicked: loadDashboards()
            }
        }

        QQC2.Label {
            visible: loadError !== ""
            Layout.fillWidth: true
            wrapMode: Text.WordWrap
            color: Kirigami.Theme.negativeTextColor
            text: loadError
        }

        Kirigami.SearchField {
            id: searchField
            visible: available.length > 0
            Layout.fillWidth: true
            placeholderText: i18n("Filter by title, folder or tag…")
        }

        QQC2.ScrollView {
            visible: available.length > 0
            Layout.fillWidth: true
            Layout.preferredHeight: Kirigami.Units.gridUnit * 16

            ListView {
                id: list
                clip: true
                model: filtered

                delegate: QQC2.ItemDelegate {
                    id: row
                    required property var modelData
                    width: list.width

                    contentItem: RowLayout {
                        spacing: Kirigami.Units.smallSpacing

                        ColumnLayout {
                            Layout.fillWidth: true
                            spacing: 0

                            QQC2.Label {
                                Layout.fillWidth: true
                                elide: Text.ElideRight
                                text: row.modelData.title
                            }

                            QQC2.Label {
                                Layout.fillWidth: true
                                elide: Text.ElideRight
                                font: Kirigami.Theme.smallFont
                                opacity: 0.7
                                text: [row.modelData.folderTitle || i18n("General"), (row.modelData.tags || []).join(", ")]
                                      .filter(function(s) { return s !== "" }).join(" · ")
                            }
                        }

                        QQC2.Button {
                            text: isChosen(row.modelData.uid) ? i18n("Added") : i18n("Add")
                            icon.name: isChosen(row.modelData.uid) ? "checkmark" : "list-add"
                            enabled: !isChosen(row.modelData.uid)
                            onClicked: add(row.modelData)
                        }
                    }
                }
            }
        }
    }
}