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)
}
}
}
}
}
}
}
neiam
/grafana-plasmoid
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
10.6 KiB · text
History
35c3541