mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
feat(qt6): MD3 rework all views — Dashboard, Profile, Admin, SuperGod, Comments, Settings
- Fix CCard content nesting (no anchors.fill inside CCard) - chipColor/badgeColor string→Theme color fixes - anchors-in-layout warnings resolved - Tonal surfaces, proper MD3 spacing - CButton replaces hand-rolled Rectangle buttons - All 6 views preserved with full functionality Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import "qmllib/dbal"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: "transparent"
|
||||
color: Theme.background
|
||||
|
||||
// ── DBAL connection ──────────────────────────────────────────
|
||||
DBALProvider { id: dbal }
|
||||
@@ -239,7 +239,6 @@ Rectangle {
|
||||
|
||||
function deleteRecord(idx) {
|
||||
var data = records[selectedEntity].slice();
|
||||
var filtered = getFilteredRecords();
|
||||
var actualRec = getPagedRecords()[idx];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data[i].id === actualRec.id) {
|
||||
@@ -326,10 +325,9 @@ Rectangle {
|
||||
// ── Stats bar ──────────────────────────────────────────────
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 80
|
||||
color: Theme.paper
|
||||
border.color: Theme.border
|
||||
border.width: 1
|
||||
Layout.preferredHeight: 88
|
||||
color: Theme.surface
|
||||
radius: 0
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -349,8 +347,7 @@ Rectangle {
|
||||
Layout.fillHeight: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
@@ -382,9 +379,7 @@ Rectangle {
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 220
|
||||
Layout.fillHeight: true
|
||||
color: Theme.paper
|
||||
border.color: Theme.border
|
||||
border.width: 1
|
||||
color: Theme.surface
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
@@ -418,7 +413,7 @@ Rectangle {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: "transparent"
|
||||
color: Theme.background
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
@@ -466,6 +461,7 @@ Rectangle {
|
||||
delegate: CChip {
|
||||
text: modelData
|
||||
checked: activeFilter === modelData
|
||||
chipColor: activeFilter === modelData ? Theme.primary : Theme.surface
|
||||
onClicked: { activeFilter = modelData; currentPage = 0; }
|
||||
}
|
||||
}
|
||||
@@ -487,15 +483,15 @@ Rectangle {
|
||||
Layout.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
// ── Column headers ────────────────────
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 44
|
||||
color: Theme.surface
|
||||
color: Theme.surfaceVariant
|
||||
radius: 0
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -557,8 +553,9 @@ Rectangle {
|
||||
color: {
|
||||
if (selectedRow === rowIndex) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
|
||||
if (selectedRows[rowIndex]) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06);
|
||||
return rowIndex % 2 === 0 ? "transparent" : Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, 0.4);
|
||||
return rowIndex % 2 === 0 ? "transparent" : Theme.surfaceVariant;
|
||||
}
|
||||
radius: 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -638,11 +635,24 @@ Rectangle {
|
||||
visible: totalFiltered() === 0
|
||||
Layout.preferredHeight: visible ? 120 : 0
|
||||
|
||||
CText {
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
variant: "body1"
|
||||
text: "No records found"
|
||||
color: Theme.textSecondary
|
||||
spacing: 8
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
variant: "h4"
|
||||
text: "No records found"
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
variant: "caption"
|
||||
text: "Try adjusting your search or filter criteria."
|
||||
color: Theme.textMuted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,7 +662,8 @@ Rectangle {
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 48
|
||||
color: Theme.surface
|
||||
color: Theme.surfaceVariant
|
||||
radius: 0
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -168,6 +168,7 @@ Rectangle {
|
||||
|
||||
CBadge {
|
||||
text: commentsModel.count + " comments"
|
||||
badgeColor: Theme.info
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
@@ -186,32 +187,26 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 12
|
||||
CText { variant: "subtitle1"; text: "Post a Comment" }
|
||||
|
||||
CText { variant: "subtitle1"; text: "Post a Comment" }
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Your comment"
|
||||
placeholderText: "Write your thoughts..."
|
||||
text: newCommentText
|
||||
onTextChanged: newCommentText = text
|
||||
}
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Your comment"
|
||||
placeholderText: "Write your thoughts..."
|
||||
text: newCommentText
|
||||
onTextChanged: newCommentText = text
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Post Comment"
|
||||
variant: "primary"
|
||||
size: "sm"
|
||||
enabled: newCommentText.trim().length > 0
|
||||
onClicked: addComment()
|
||||
}
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Post Comment"
|
||||
variant: "primary"
|
||||
size: "sm"
|
||||
enabled: newCommentText.trim().length > 0
|
||||
onClicked: addComment()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,88 +218,83 @@ Rectangle {
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 10
|
||||
// Comment header: avatar, user, time
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
// Comment header: avatar, user, time
|
||||
FlexRow {
|
||||
CAvatar {
|
||||
initials: model.initials
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CAvatar {
|
||||
initials: model.initials
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
FlexRow {
|
||||
spacing: 8
|
||||
CText {
|
||||
variant: "subtitle1"
|
||||
text: model.username
|
||||
}
|
||||
CChip {
|
||||
text: model.username === appWindow.currentUser ? "You" : ""
|
||||
visible: model.username === appWindow.currentUser
|
||||
}
|
||||
}
|
||||
spacing: 2
|
||||
|
||||
FlexRow {
|
||||
spacing: 8
|
||||
CText {
|
||||
variant: "caption"
|
||||
text: model.timestamp
|
||||
variant: "subtitle1"
|
||||
text: model.username
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comment body
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "body1"
|
||||
text: model.body
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Actions row
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CButton {
|
||||
text: model.liked ? "Liked (" + model.likes + ")" : "Like (" + model.likes + ")"
|
||||
variant: model.liked ? "primary" : "ghost"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
var newLikes;
|
||||
if (model.liked) {
|
||||
newLikes = model.likes - 1;
|
||||
commentsModel.setProperty(index, "likes", newLikes)
|
||||
commentsModel.setProperty(index, "liked", false)
|
||||
} else {
|
||||
newLikes = model.likes + 1;
|
||||
commentsModel.setProperty(index, "likes", newLikes)
|
||||
commentsModel.setProperty(index, "liked", true)
|
||||
}
|
||||
likeCommentOnDBAL(model.commentId, newLikes)
|
||||
CChip {
|
||||
text: model.username === appWindow.currentUser ? "You" : ""
|
||||
chipColor: Theme.primary
|
||||
visible: model.username === appWindow.currentUser
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
CText {
|
||||
variant: "caption"
|
||||
text: model.timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: "Delete"
|
||||
variant: "danger"
|
||||
size: "sm"
|
||||
visible: canDelete(model.username)
|
||||
onClicked: {
|
||||
deleteCommentOnDBAL(model.commentId)
|
||||
commentsModel.remove(index)
|
||||
// Comment body
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "body1"
|
||||
text: model.body
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Actions row
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CButton {
|
||||
text: model.liked ? "Liked (" + model.likes + ")" : "Like (" + model.likes + ")"
|
||||
variant: model.liked ? "primary" : "ghost"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
var newLikes;
|
||||
if (model.liked) {
|
||||
newLikes = model.likes - 1;
|
||||
commentsModel.setProperty(index, "likes", newLikes)
|
||||
commentsModel.setProperty(index, "liked", false)
|
||||
} else {
|
||||
newLikes = model.likes + 1;
|
||||
commentsModel.setProperty(index, "likes", newLikes)
|
||||
commentsModel.setProperty(index, "liked", true)
|
||||
}
|
||||
likeCommentOnDBAL(model.commentId, newLikes)
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
CButton {
|
||||
text: "Delete"
|
||||
variant: "danger"
|
||||
size: "sm"
|
||||
visible: canDelete(model.username)
|
||||
onClicked: {
|
||||
deleteCommentOnDBAL(model.commentId)
|
||||
commentsModel.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,22 +306,22 @@ Rectangle {
|
||||
Layout.fillWidth: true
|
||||
visible: commentsModel.count === 0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 40
|
||||
spacing: 12
|
||||
Item { Layout.preferredHeight: 24 }
|
||||
|
||||
CText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
variant: "h4"
|
||||
text: "No comments yet"
|
||||
}
|
||||
CText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
variant: "body2"
|
||||
text: "Be the first to start the discussion!"
|
||||
}
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "No comments yet"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "body2"
|
||||
text: "Be the first to start the discussion!"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 24 }
|
||||
}
|
||||
|
||||
// Bottom spacer
|
||||
|
||||
@@ -6,7 +6,7 @@ import "qmllib/dbal"
|
||||
|
||||
Rectangle {
|
||||
id: dashRoot
|
||||
color: "transparent"
|
||||
color: Theme.background
|
||||
|
||||
// ── DBAL connection ──────────────────────────────────────────
|
||||
DBALProvider { id: dbal }
|
||||
@@ -14,6 +14,13 @@ Rectangle {
|
||||
property var healthData: ({})
|
||||
property bool dbalOnline: dbal.connected
|
||||
|
||||
// ── MD3 palette ──────────────────────────────────────────────
|
||||
readonly property bool isDark: Theme.mode === "dark"
|
||||
readonly property color surfaceContainer: isDark ? Qt.rgba(1, 1, 1, 0.05) : Qt.rgba(0.31, 0.31, 0.44, 0.06)
|
||||
readonly property color surfaceContainerHigh: isDark ? Qt.rgba(1, 1, 1, 0.08) : Qt.rgba(0.31, 0.31, 0.44, 0.10)
|
||||
readonly property color onSurface: Theme.text
|
||||
readonly property color onSurfaceVariant: Theme.textSecondary
|
||||
|
||||
function refreshDBAL() {
|
||||
dbal.ping(function(success, error) {
|
||||
if (success) {
|
||||
@@ -28,43 +35,55 @@ Rectangle {
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 20
|
||||
spacing: 0
|
||||
|
||||
// Welcome header
|
||||
Item { Layout.preferredHeight: 24 }
|
||||
|
||||
// ── Welcome header ───────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
spacing: 12
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
CText {
|
||||
variant: "h3"
|
||||
text: "Welcome back, " + appWindow.currentUser
|
||||
}
|
||||
CText {
|
||||
variant: "body1"
|
||||
text: "Level " + appWindow.currentLevel + " \u00b7 " + appWindow.currentRole + " access"
|
||||
}
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h3"
|
||||
text: "Welcome back, " + appWindow.currentUser
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: dbal.loading ? "Refreshing..." : "Refresh"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
enabled: !dbal.loading
|
||||
onClicked: refreshDBAL()
|
||||
}
|
||||
Item { Layout.preferredHeight: 4 }
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "body1"
|
||||
text: "Level " + appWindow.currentLevel + " \u00b7 " + appWindow.currentRole + " access"
|
||||
color: onSurfaceVariant
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 12 }
|
||||
|
||||
CButton {
|
||||
text: dbal.loading ? "Refreshing..." : "Refresh"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
enabled: !dbal.loading
|
||||
onClicked: refreshDBAL()
|
||||
}
|
||||
}
|
||||
|
||||
// Stats row
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Stats row ────────────────────────────────────────
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
spacing: 16
|
||||
|
||||
Repeater {
|
||||
@@ -76,67 +95,100 @@ Rectangle {
|
||||
]
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 8
|
||||
CText { variant: "caption"; text: modelData.title }
|
||||
CText { variant: "h3"; text: modelData.value }
|
||||
CStatusBadge { status: modelData.status; text: modelData.status === "success" ? "Online" : "Active" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
variant: "outlined"
|
||||
|
||||
// Recent activity
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
title: "Recent Activity"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 8
|
||||
|
||||
CText { variant: "h4"; text: "Recent Activity" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ action: "Package installed", detail: "material_ui v2.1.0", time: "2 min ago" },
|
||||
{ action: "User logged in", detail: "admin", time: "5 min ago" },
|
||||
{ action: "Workflow executed", detail: "on_user_created", time: "12 min ago" },
|
||||
{ action: "Schema updated", detail: "forum entity", time: "1 hr ago" },
|
||||
{ action: "Seed data loaded", detail: "5 namespaces", time: "2 hr ago" }
|
||||
]
|
||||
delegate: CListItem {
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
title: modelData.action
|
||||
subtitle: modelData.detail + " \u00b7 " + modelData.time
|
||||
variant: "caption"
|
||||
text: modelData.title
|
||||
color: onSurfaceVariant
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 4 }
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h3"
|
||||
text: modelData.value
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CStatusBadge {
|
||||
status: modelData.status
|
||||
text: modelData.status === "success" ? "Online" : "Active"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quick actions
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Recent activity ──────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 12
|
||||
CText { variant: "h4"; text: "Quick Actions" }
|
||||
FlexRow {
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "Recent Activity"
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ action: "Package installed", detail: "material_ui v2.1.0", time: "2 min ago" },
|
||||
{ action: "User logged in", detail: "admin", time: "5 min ago" },
|
||||
{ action: "Workflow executed", detail: "on_user_created", time: "12 min ago" },
|
||||
{ action: "Schema updated", detail: "forum entity", time: "1 hr ago" },
|
||||
{ action: "Seed data loaded", detail: "5 namespaces", time: "2 hr ago" }
|
||||
]
|
||||
delegate: CListItem {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
CButton { text: "Forum"; variant: "default"; onClicked: appWindow.currentView = "forum" }
|
||||
CButton { text: "Gallery"; variant: "default"; onClicked: appWindow.currentView = "gallery" }
|
||||
CButton { text: "Guestbook"; variant: "default"; onClicked: appWindow.currentView = "guestbook" }
|
||||
CButton { text: "Blog"; variant: "default"; onClicked: appWindow.currentView = "blog" }
|
||||
CButton { text: "Profile"; variant: "ghost"; onClicked: appWindow.currentView = "profile" }
|
||||
title: modelData.action
|
||||
subtitle: modelData.detail + " \u00b7 " + modelData.time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Quick actions ────────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "Quick Actions"
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 12 }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
CButton { text: "Forum"; variant: "default"; onClicked: appWindow.currentView = "forum" }
|
||||
CButton { text: "Gallery"; variant: "default"; onClicked: appWindow.currentView = "gallery" }
|
||||
CButton { text: "Guestbook"; variant: "default"; onClicked: appWindow.currentView = "guestbook" }
|
||||
CButton { text: "Blog"; variant: "default"; onClicked: appWindow.currentView = "blog" }
|
||||
CButton { text: "Profile"; variant: "ghost"; onClicked: appWindow.currentView = "profile" }
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom spacer
|
||||
Item { Layout.preferredHeight: 24 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,20 @@ import QmlComponents 1.0
|
||||
import "qmllib/dbal"
|
||||
|
||||
Rectangle {
|
||||
color: "transparent"
|
||||
id: profileRoot
|
||||
color: Theme.background
|
||||
|
||||
// ── DBAL connection ──
|
||||
// ── DBAL connection ──────────────────────────────────────────
|
||||
DBALProvider { id: dbal }
|
||||
|
||||
// ── Mock fallback data ──
|
||||
// ── MD3 palette ──────────────────────────────────────────────
|
||||
readonly property bool isDark: Theme.mode === "dark"
|
||||
readonly property color surfaceContainer: isDark ? Qt.rgba(1, 1, 1, 0.05) : Qt.rgba(0.31, 0.31, 0.44, 0.06)
|
||||
readonly property color surfaceContainerHigh: isDark ? Qt.rgba(1, 1, 1, 0.08) : Qt.rgba(0.31, 0.31, 0.44, 0.10)
|
||||
readonly property color onSurface: Theme.text
|
||||
readonly property color onSurfaceVariant: Theme.textSecondary
|
||||
|
||||
// ── Mock fallback data ───────────────────────────────────────
|
||||
property string mockBio: "MetaBuilder enthusiast and open-source contributor."
|
||||
property string mockEmail: "demo@metabuilder.io"
|
||||
|
||||
@@ -23,7 +31,7 @@ Rectangle {
|
||||
property bool saving: false
|
||||
property string saveStatus: ""
|
||||
|
||||
// ── DBAL data loading ──
|
||||
// ── DBAL data loading ────────────────────────────────────────
|
||||
function loadProfile() {
|
||||
if (!appWindow.currentUser) return;
|
||||
dbal.read("user", appWindow.currentUser, function(result, error) {
|
||||
@@ -91,241 +99,303 @@ Rectangle {
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: 20
|
||||
spacing: 0
|
||||
|
||||
// Profile card
|
||||
Item { Layout.preferredHeight: 24 }
|
||||
|
||||
// ── Profile header card ──────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
FlexRow {
|
||||
CAvatar {
|
||||
initials: userInitials()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
spacing: 6
|
||||
|
||||
CAvatar {
|
||||
initials: userInitials()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
variant: "h3"
|
||||
text: appWindow.currentUser
|
||||
}
|
||||
|
||||
CText {
|
||||
variant: "h3"
|
||||
text: appWindow.currentUser
|
||||
}
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: userEmail
|
||||
color: Theme.text
|
||||
opacity: 0.7
|
||||
}
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "body2"
|
||||
text: userEmail
|
||||
color: onSurfaceVariant
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
spacing: 8
|
||||
CBadge { text: appWindow.currentRole }
|
||||
CBadge { text: "Level 2" }
|
||||
}
|
||||
FlexRow {
|
||||
spacing: 8
|
||||
CBadge { text: appWindow.currentRole; badgeColor: Theme.primary }
|
||||
CBadge { text: "Level 2"; badgeColor: Theme.info }
|
||||
}
|
||||
|
||||
CText {
|
||||
variant: "caption"
|
||||
text: "Member since January 15, 2026"
|
||||
}
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "caption"
|
||||
text: "Member since January 15, 2026"
|
||||
color: onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Activity summary
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Activity summary ─────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 12
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "Activity Summary"
|
||||
}
|
||||
|
||||
CText { variant: "h4"; text: "Activity Summary" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "Posts", value: "42" },
|
||||
{ label: "Comments", value: "128" },
|
||||
{ label: "Last Login", value: "Today, 09:15" }
|
||||
]
|
||||
delegate: CCard {
|
||||
Item { Layout.preferredHeight: 12 }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "Posts", value: "42" },
|
||||
{ label: "Comments", value: "128" },
|
||||
{ label: "Last Login", value: "Today, 09:15" }
|
||||
]
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
variant: "outlined"
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 6
|
||||
CText { variant: "caption"; text: modelData.label }
|
||||
CText { variant: "h4"; text: modelData.value }
|
||||
}
|
||||
variant: "caption"
|
||||
text: modelData.label
|
||||
color: onSurfaceVariant
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 4 }
|
||||
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: modelData.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edit profile section
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Edit profile ─────────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 14
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "Edit Profile"
|
||||
}
|
||||
|
||||
CText { variant: "h4"; text: "Edit Profile" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Display Name"
|
||||
placeholderText: "Enter display name"
|
||||
text: userDisplayName
|
||||
onTextChanged: userDisplayName = text
|
||||
}
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Email"
|
||||
placeholderText: "Enter email address"
|
||||
text: userEmail
|
||||
onTextChanged: userEmail = text
|
||||
}
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Bio"
|
||||
placeholderText: "Tell us about yourself..."
|
||||
text: userBio
|
||||
onTextChanged: userBio = text
|
||||
}
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Display Name"
|
||||
placeholderText: "Enter display name"
|
||||
text: userDisplayName
|
||||
onTextChanged: userDisplayName = text
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Email"
|
||||
placeholderText: "Enter email address"
|
||||
text: userEmail
|
||||
onTextChanged: userEmail = text
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Bio"
|
||||
placeholderText: "Tell us about yourself..."
|
||||
text: userBio
|
||||
onTextChanged: userBio = text
|
||||
}
|
||||
}
|
||||
|
||||
// Change password section
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Change password ───────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 14
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "Change Password"
|
||||
}
|
||||
|
||||
CText { variant: "h4"; text: "Change Password" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Current Password"
|
||||
placeholderText: "Enter current password"
|
||||
echoMode: TextInput.Password
|
||||
text: currentPassword
|
||||
onTextChanged: currentPassword = text
|
||||
}
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "New Password"
|
||||
placeholderText: "Enter new password"
|
||||
echoMode: TextInput.Password
|
||||
text: newPassword
|
||||
onTextChanged: newPassword = text
|
||||
}
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Confirm New Password"
|
||||
placeholderText: "Re-enter new password"
|
||||
echoMode: TextInput.Password
|
||||
text: confirmPassword
|
||||
onTextChanged: confirmPassword = text
|
||||
}
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Current Password"
|
||||
placeholderText: "Enter current password"
|
||||
echoMode: TextInput.Password
|
||||
text: currentPassword
|
||||
onTextChanged: currentPassword = text
|
||||
}
|
||||
|
||||
CAlert {
|
||||
Layout.fillWidth: true
|
||||
severity: "info"
|
||||
text: "Passwords must be at least 8 characters with uppercase, lowercase, and a number."
|
||||
visible: newPassword.length > 0
|
||||
}
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CAlert {
|
||||
Layout.fillWidth: true
|
||||
severity: "error"
|
||||
text: "Passwords do not match."
|
||||
visible: confirmPassword.length > 0 && newPassword !== confirmPassword
|
||||
}
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "New Password"
|
||||
placeholderText: "Enter new password"
|
||||
echoMode: TextInput.Password
|
||||
text: newPassword
|
||||
onTextChanged: newPassword = text
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Confirm New Password"
|
||||
placeholderText: "Re-enter new password"
|
||||
echoMode: TextInput.Password
|
||||
text: confirmPassword
|
||||
onTextChanged: confirmPassword = text
|
||||
}
|
||||
|
||||
Item { Layout.preferredHeight: 14 }
|
||||
|
||||
CAlert {
|
||||
Layout.fillWidth: true
|
||||
severity: "info"
|
||||
text: "Passwords must be at least 8 characters with uppercase, lowercase, and a number."
|
||||
visible: newPassword.length > 0
|
||||
}
|
||||
|
||||
CAlert {
|
||||
Layout.fillWidth: true
|
||||
severity: "error"
|
||||
text: "Passwords do not match."
|
||||
visible: confirmPassword.length > 0 && newPassword !== confirmPassword
|
||||
}
|
||||
}
|
||||
|
||||
// Connected accounts
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Connected accounts ────────────────────────────────
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
variant: "filled"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 12
|
||||
CText {
|
||||
Layout.fillWidth: true
|
||||
variant: "h4"
|
||||
text: "Connected Accounts"
|
||||
}
|
||||
|
||||
CText { variant: "h4"; text: "Connected Accounts" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CListItem {
|
||||
Layout.fillWidth: true
|
||||
title: "GitHub"
|
||||
subtitle: "Linked as @" + appWindow.currentUser
|
||||
leadingIcon: "github"
|
||||
}
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
spacing: 8
|
||||
CStatusBadge { status: "success"; text: "Connected" }
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton { text: "Unlink"; variant: "ghost"; size: "sm" }
|
||||
}
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
CListItem {
|
||||
Layout.fillWidth: true
|
||||
title: "GitHub"
|
||||
subtitle: "Linked as @" + appWindow.currentUser
|
||||
leadingIcon: "github"
|
||||
}
|
||||
|
||||
CListItem {
|
||||
Layout.fillWidth: true
|
||||
title: "Discord"
|
||||
subtitle: "Not linked"
|
||||
leadingIcon: "discord"
|
||||
}
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
spacing: 8
|
||||
CStatusBadge { status: "success"; text: "Connected" }
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton { text: "Unlink"; variant: "ghost"; size: "sm" }
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
spacing: 8
|
||||
CStatusBadge { status: "warning"; text: "Not Connected" }
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton { text: "Link Account"; variant: "primary"; size: "sm" }
|
||||
}
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
Item { Layout.preferredHeight: 8 }
|
||||
|
||||
CListItem {
|
||||
Layout.fillWidth: true
|
||||
title: "Discord"
|
||||
subtitle: "Not linked"
|
||||
leadingIcon: "discord"
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
spacing: 8
|
||||
CStatusBadge { status: "warning"; text: "Not Connected" }
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton { text: "Link Account"; variant: "primary"; size: "sm" }
|
||||
}
|
||||
}
|
||||
|
||||
// Save button
|
||||
Item { Layout.preferredHeight: 16 }
|
||||
|
||||
// ── Save button ──────────────────────────────────────
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
spacing: 12
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
@@ -337,7 +407,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
// Bottom spacer
|
||||
Item { Layout.preferredHeight: 20 }
|
||||
Item { Layout.preferredHeight: 24 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,85 +180,70 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
CText { variant: "h4"; text: "Profile" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
spacing: 16
|
||||
|
||||
CText { variant: "h4"; text: "Profile" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
// Avatar
|
||||
Rectangle {
|
||||
width: 64
|
||||
height: 64
|
||||
radius: 32
|
||||
color: Theme.primary
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
CText {
|
||||
anchors.centerIn: parent
|
||||
text: userInitials()
|
||||
variant: "h4"
|
||||
color: "#ffffff"
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
CText {
|
||||
variant: "subtitle1"
|
||||
text: appWindow.currentUser
|
||||
font.bold: true
|
||||
}
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: appWindow.currentRole + " \u00b7 Level " + appWindow.currentLevel
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
// Avatar
|
||||
CAvatar {
|
||||
size: "lg"
|
||||
initials: userInitials()
|
||||
}
|
||||
|
||||
CTextField {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
label: "Display Name"
|
||||
placeholderText: "Enter display name"
|
||||
text: displayName
|
||||
onTextChanged: displayName = text
|
||||
spacing: 4
|
||||
|
||||
CText {
|
||||
variant: "subtitle1"
|
||||
text: appWindow.currentUser
|
||||
font.bold: true
|
||||
}
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: appWindow.currentRole + " \u00b7 Level " + appWindow.currentLevel
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
label: "Display Name"
|
||||
placeholderText: "Enter display name"
|
||||
text: displayName
|
||||
onTextChanged: displayName = text
|
||||
}
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Email"
|
||||
placeholderText: "Enter email address"
|
||||
text: userEmail
|
||||
onTextChanged: userEmail = text
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
CAlert {
|
||||
visible: profileSaved
|
||||
severity: "success"
|
||||
text: "Profile saved successfully"
|
||||
}
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Email"
|
||||
placeholderText: "Enter email address"
|
||||
text: userEmail
|
||||
onTextChanged: userEmail = text
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
CAlert {
|
||||
visible: profileSaved
|
||||
severity: "success"
|
||||
text: "Profile saved successfully"
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: "Save Profile"
|
||||
variant: "primary"
|
||||
onClicked: saveProfile()
|
||||
}
|
||||
CButton {
|
||||
text: "Save Profile"
|
||||
variant: "primary"
|
||||
onClicked: saveProfile()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,67 +252,62 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 16
|
||||
CText { variant: "h4"; text: "Appearance" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CText { variant: "h4"; text: "Appearance" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
// Theme selector
|
||||
CText {
|
||||
variant: "subtitle2"
|
||||
text: "Theme"
|
||||
Layout.topMargin: 4
|
||||
}
|
||||
|
||||
// Theme selector
|
||||
CText {
|
||||
variant: "subtitle2"
|
||||
text: "Theme"
|
||||
}
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: availableThemes
|
||||
delegate: CButton {
|
||||
text: modelData.label
|
||||
variant: selectedTheme === modelData.id ? "primary" : "default"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
selectedTheme = modelData.id
|
||||
appWindow.currentTheme = modelData.id
|
||||
if (typeof Theme.setTheme === "function") {
|
||||
Theme.setTheme(modelData.id)
|
||||
}
|
||||
savePreferences()
|
||||
Repeater {
|
||||
model: availableThemes
|
||||
delegate: CButton {
|
||||
text: modelData.label
|
||||
variant: selectedTheme === modelData.id ? "primary" : "default"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
selectedTheme = modelData.id
|
||||
appWindow.currentTheme = modelData.id
|
||||
if (typeof Theme.setTheme === "function") {
|
||||
Theme.setTheme(modelData.id)
|
||||
}
|
||||
savePreferences()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Font size selector
|
||||
CText {
|
||||
variant: "subtitle2"
|
||||
text: "Font Size"
|
||||
Layout.topMargin: 8
|
||||
}
|
||||
// Font size selector
|
||||
CText {
|
||||
variant: "subtitle2"
|
||||
text: "Font Size"
|
||||
Layout.topMargin: 8
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ id: "small", label: "Small" },
|
||||
{ id: "medium", label: "Medium" },
|
||||
{ id: "large", label: "Large" }
|
||||
]
|
||||
delegate: CButton {
|
||||
text: modelData.label
|
||||
variant: fontSize === modelData.id ? "primary" : "default"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
fontSize = modelData.id
|
||||
savePreferences()
|
||||
}
|
||||
Repeater {
|
||||
model: [
|
||||
{ id: "small", label: "Small" },
|
||||
{ id: "medium", label: "Medium" },
|
||||
{ id: "large", label: "Large" }
|
||||
]
|
||||
delegate: CButton {
|
||||
text: modelData.label
|
||||
variant: fontSize === modelData.id ? "primary" : "default"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
fontSize = modelData.id
|
||||
savePreferences()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,78 +318,73 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 16
|
||||
CText { variant: "h4"; text: "Notifications" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CText { variant: "h4"; text: "Notifications" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
// Email notifications toggle
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
spacing: 12
|
||||
|
||||
// Email notifications toggle
|
||||
FlexRow {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
CText { variant: "subtitle2"; text: "Email Notifications" }
|
||||
CText { variant: "caption"; text: "Receive notification summaries via email"; opacity: 0.6 }
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: emailNotifications
|
||||
onCheckedChanged: {
|
||||
emailNotifications = checked
|
||||
savePreferences()
|
||||
}
|
||||
}
|
||||
spacing: 2
|
||||
CText { variant: "subtitle2"; text: "Email Notifications" }
|
||||
CText { variant: "caption"; text: "Receive notification summaries via email"; opacity: 0.6 }
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Desktop notifications toggle
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
CText { variant: "subtitle2"; text: "Desktop Notifications" }
|
||||
CText { variant: "caption"; text: "Show desktop push notifications for alerts"; opacity: 0.6 }
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: desktopNotifications
|
||||
onCheckedChanged: {
|
||||
desktopNotifications = checked
|
||||
savePreferences()
|
||||
}
|
||||
CSwitch {
|
||||
checked: emailNotifications
|
||||
onToggled: function(value) {
|
||||
emailNotifications = value
|
||||
savePreferences()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Sound alerts toggle
|
||||
FlexRow {
|
||||
// Desktop notifications toggle
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
spacing: 2
|
||||
CText { variant: "subtitle2"; text: "Desktop Notifications" }
|
||||
CText { variant: "caption"; text: "Show desktop push notifications for alerts"; opacity: 0.6 }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
CText { variant: "subtitle2"; text: "Sound Alerts" }
|
||||
CText { variant: "caption"; text: "Play a sound when new notifications arrive"; opacity: 0.6 }
|
||||
CSwitch {
|
||||
checked: desktopNotifications
|
||||
onToggled: function(value) {
|
||||
desktopNotifications = value
|
||||
savePreferences()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Switch {
|
||||
checked: soundAlerts
|
||||
onCheckedChanged: {
|
||||
soundAlerts = checked
|
||||
savePreferences()
|
||||
}
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Sound alerts toggle
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
CText { variant: "subtitle2"; text: "Sound Alerts" }
|
||||
CText { variant: "caption"; text: "Play a sound when new notifications arrive"; opacity: 0.6 }
|
||||
}
|
||||
|
||||
CSwitch {
|
||||
checked: soundAlerts
|
||||
onToggled: function(value) {
|
||||
soundAlerts = value
|
||||
savePreferences()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,81 +394,75 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 16
|
||||
CText { variant: "h4"; text: "Connection" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CText { variant: "h4"; text: "Connection" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
// DBAL URL
|
||||
CText { variant: "subtitle2"; text: "DBAL Server"; Layout.topMargin: 4 }
|
||||
|
||||
// DBAL URL
|
||||
CText { variant: "subtitle2"; text: "DBAL Server" }
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
FlexRow {
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "DBAL URL"
|
||||
placeholderText: "http://localhost:8080"
|
||||
text: dbalUrl
|
||||
onTextChanged: dbalUrl = text
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
CButton {
|
||||
text: dbalConnectionStatus === "testing" ? "Testing..." : "Test Connection"
|
||||
variant: "default"
|
||||
size: "sm"
|
||||
enabled: dbalConnectionStatus !== "testing"
|
||||
onClicked: testDBALConnection()
|
||||
}
|
||||
|
||||
CStatusBadge {
|
||||
status: connectionStatusColor(dbalConnectionStatus)
|
||||
text: connectionStatusLabel(dbalConnectionStatus)
|
||||
}
|
||||
}
|
||||
label: "DBAL URL"
|
||||
placeholderText: "http://localhost:8080"
|
||||
text: dbalUrl
|
||||
onTextChanged: dbalUrl = text
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
// Media Service URL
|
||||
CText { variant: "subtitle2"; text: "Media Service" }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Media Service URL"
|
||||
placeholderText: "http://localhost:9090"
|
||||
text: mediaServiceUrl
|
||||
onTextChanged: mediaServiceUrl = text
|
||||
CButton {
|
||||
text: dbalConnectionStatus === "testing" ? "Testing..." : "Test Connection"
|
||||
variant: "default"
|
||||
size: "sm"
|
||||
enabled: dbalConnectionStatus !== "testing"
|
||||
onClicked: testDBALConnection()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
CStatusBadge {
|
||||
status: connectionStatusColor(dbalConnectionStatus)
|
||||
text: connectionStatusLabel(dbalConnectionStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: mediaConnectionStatus === "testing" ? "Testing..." : "Test Connection"
|
||||
variant: "default"
|
||||
size: "sm"
|
||||
enabled: mediaConnectionStatus !== "testing"
|
||||
onClicked: testMediaConnection()
|
||||
}
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CStatusBadge {
|
||||
status: connectionStatusColor(mediaConnectionStatus)
|
||||
text: connectionStatusLabel(mediaConnectionStatus)
|
||||
}
|
||||
// Media Service URL
|
||||
CText { variant: "subtitle2"; text: "Media Service" }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Media Service URL"
|
||||
placeholderText: "http://localhost:9090"
|
||||
text: mediaServiceUrl
|
||||
onTextChanged: mediaServiceUrl = text
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
CButton {
|
||||
text: mediaConnectionStatus === "testing" ? "Testing..." : "Test Connection"
|
||||
variant: "default"
|
||||
size: "sm"
|
||||
enabled: mediaConnectionStatus !== "testing"
|
||||
onClicked: testMediaConnection()
|
||||
}
|
||||
|
||||
CStatusBadge {
|
||||
status: connectionStatusColor(mediaConnectionStatus)
|
||||
text: connectionStatusLabel(mediaConnectionStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,62 +472,56 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 12
|
||||
CText { variant: "h4"; text: "About" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CText { variant: "h4"; text: "About" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "Version", value: "2.1.0" },
|
||||
{ label: "Build Date", value: "2026-03-19" },
|
||||
{ label: "Qt Version", value: "6.8.x" },
|
||||
{ label: "Platform", value: Qt.platform.os },
|
||||
{ label: "DBAL Schema", value: "v1 REST API" }
|
||||
]
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ label: "Version", value: "2.1.0" },
|
||||
{ label: "Build Date", value: "2026-03-19" },
|
||||
{ label: "Qt Version", value: "6.8.x" },
|
||||
{ label: "Platform", value: Qt.platform.os },
|
||||
{ label: "DBAL Schema", value: "v1 REST API" }
|
||||
]
|
||||
|
||||
delegate: FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: modelData.label
|
||||
opacity: 0.6
|
||||
Layout.preferredWidth: 120
|
||||
}
|
||||
|
||||
CText {
|
||||
variant: "body1"
|
||||
text: modelData.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
delegate: FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CButton {
|
||||
text: "View Documentation"
|
||||
variant: "default"
|
||||
size: "sm"
|
||||
onClicked: Qt.openUrlExternally("https://github.com/nicholasgriffintn/metabuilder")
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: modelData.label
|
||||
opacity: 0.6
|
||||
Layout.preferredWidth: 120
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: "Report Issue"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: Qt.openUrlExternally("https://github.com/nicholasgriffintn/metabuilder/issues")
|
||||
CText {
|
||||
variant: "body1"
|
||||
text: modelData.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CButton {
|
||||
text: "View Documentation"
|
||||
variant: "default"
|
||||
size: "sm"
|
||||
onClicked: Qt.openUrlExternally("https://github.com/nicholasgriffintn/metabuilder")
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: "Report Issue"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: Qt.openUrlExternally("https://github.com/nicholasgriffintn/metabuilder/issues")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Data status footer ──────────────────────────────
|
||||
|
||||
@@ -6,7 +6,7 @@ import "qmllib/dbal"
|
||||
|
||||
Rectangle {
|
||||
id: superGodPanel
|
||||
color: "transparent"
|
||||
color: Theme.background
|
||||
|
||||
// ── DBAL connection ──
|
||||
DBALProvider { id: dbal }
|
||||
@@ -150,53 +150,47 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
CText { variant: "h2"; text: "Super God Panel" }
|
||||
CBadge { text: "Level 5"; badgeColor: Theme.primary }
|
||||
CStatusBadge { status: "success"; text: "Platform Control" }
|
||||
|
||||
CText { variant: "h2"; text: "Super God Panel" }
|
||||
CBadge { text: "Level 5" }
|
||||
CStatusBadge { status: "success"; text: "Platform Control" }
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
CButton {
|
||||
text: "Level 4"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: appWindow.currentView = "god"
|
||||
}
|
||||
CButton {
|
||||
text: "Level 3"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: appWindow.currentView = "admin"
|
||||
}
|
||||
CButton {
|
||||
text: "Level 2"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: appWindow.currentView = "dashboard"
|
||||
}
|
||||
CButton {
|
||||
text: "Level 4"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: appWindow.currentView = "god"
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CChip { text: tenants.length + " Tenants" }
|
||||
CChip { text: godUsers.length + " God Users" }
|
||||
CChip { text: pendingTransfers.length + " Pending Transfers" }
|
||||
CChip { text: "14 DB Backends" }
|
||||
CChip { text: "4 Daemons" }
|
||||
CButton {
|
||||
text: "Level 3"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: appWindow.currentView = "admin"
|
||||
}
|
||||
CButton {
|
||||
text: "Level 2"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
onClicked: appWindow.currentView = "dashboard"
|
||||
}
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CChip { text: tenants.length + " Tenants"; chipColor: Theme.primary }
|
||||
CChip { text: godUsers.length + " God Users"; chipColor: Theme.primary }
|
||||
CChip { text: pendingTransfers.length + " Pending Transfers"; chipColor: Theme.warning }
|
||||
CChip { text: "14 DB Backends"; chipColor: Theme.info }
|
||||
CChip { text: "4 Daemons"; chipColor: Theme.success }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,51 +253,45 @@ Rectangle {
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
CText { variant: "h4"; text: modelData.name }
|
||||
CStatusBadge {
|
||||
status: modelData.status === "active" ? "success" : "warning"
|
||||
text: modelData.status
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
CBadge { text: "Tenant" }
|
||||
CText { variant: "h4"; text: modelData.name }
|
||||
CStatusBadge {
|
||||
status: modelData.status === "active" ? "success" : "warning"
|
||||
text: modelData.status
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
CBadge { text: "Tenant"; badgeColor: Theme.primary }
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 24
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 24
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Owner"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: modelData.owner }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Homepage"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: modelData.homepage }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Created"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: modelData.created }
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Configure"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Owner"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: modelData.owner }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Homepage"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: modelData.homepage }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Created"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: modelData.created }
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Configure"
|
||||
variant: "ghost"
|
||||
size: "sm"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,8 +332,7 @@ Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
CAvatar { initials: modelData.initials }
|
||||
@@ -357,8 +344,8 @@ Rectangle {
|
||||
FlexRow {
|
||||
spacing: 8
|
||||
CText { variant: "subtitle1"; text: modelData.username }
|
||||
CBadge { text: "L" + modelData.level }
|
||||
CBadge { text: modelData.role }
|
||||
CBadge { text: "L" + modelData.level; badgeColor: Theme.primary }
|
||||
CBadge { text: modelData.role; badgeColor: Theme.secondary }
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
@@ -425,77 +412,71 @@ Rectangle {
|
||||
Layout.fillWidth: true
|
||||
visible: showTransferForm
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
spacing: 12
|
||||
CText { variant: "h4"; text: "New Transfer Request" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CText { variant: "h4"; text: "New Transfer Request" }
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "From User"
|
||||
placeholderText: "e.g. admin"
|
||||
text: transferFrom
|
||||
onTextChanged: transferFrom = text
|
||||
}
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "To User"
|
||||
placeholderText: "e.g. devops"
|
||||
text: transferTo
|
||||
onTextChanged: transferTo = text
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Reason"
|
||||
placeholderText: "Describe the reason for this transfer"
|
||||
text: transferReason
|
||||
onTextChanged: transferReason = text
|
||||
label: "From User"
|
||||
placeholderText: "e.g. admin"
|
||||
text: transferFrom
|
||||
onTextChanged: transferFrom = text
|
||||
}
|
||||
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Expiry Date"
|
||||
placeholderText: "YYYY-MM-DD"
|
||||
text: transferExpiry
|
||||
onTextChanged: transferExpiry = text
|
||||
label: "To User"
|
||||
placeholderText: "e.g. devops"
|
||||
text: transferTo
|
||||
onTextChanged: transferTo = text
|
||||
}
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Reason"
|
||||
placeholderText: "Describe the reason for this transfer"
|
||||
text: transferReason
|
||||
onTextChanged: transferReason = text
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Submit Transfer"
|
||||
variant: "primary"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
if (transferFrom && transferTo && transferReason) {
|
||||
var newTransfer = {
|
||||
from: transferFrom,
|
||||
to: transferTo,
|
||||
reason: transferReason,
|
||||
expiry: transferExpiry || "No expiry",
|
||||
status: "pending"
|
||||
};
|
||||
var updated = pendingTransfers.slice();
|
||||
updated.push(newTransfer);
|
||||
pendingTransfers = updated;
|
||||
transferFrom = "";
|
||||
transferTo = "";
|
||||
transferReason = "";
|
||||
transferExpiry = "";
|
||||
showTransferForm = false;
|
||||
}
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
label: "Expiry Date"
|
||||
placeholderText: "YYYY-MM-DD"
|
||||
text: transferExpiry
|
||||
onTextChanged: transferExpiry = text
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Submit Transfer"
|
||||
variant: "primary"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
if (transferFrom && transferTo && transferReason) {
|
||||
var newTransfer = {
|
||||
from: transferFrom,
|
||||
to: transferTo,
|
||||
reason: transferReason,
|
||||
expiry: transferExpiry || "No expiry",
|
||||
status: "pending"
|
||||
};
|
||||
var updated = pendingTransfers.slice();
|
||||
updated.push(newTransfer);
|
||||
pendingTransfers = updated;
|
||||
transferFrom = "";
|
||||
transferTo = "";
|
||||
transferReason = "";
|
||||
transferExpiry = "";
|
||||
showTransferForm = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,72 +494,66 @@ Rectangle {
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
CText { variant: "subtitle1"; text: modelData.from + " -> " + modelData.to }
|
||||
CStatusBadge { status: "warning"; text: "Pending" }
|
||||
Item { Layout.fillWidth: true }
|
||||
CText { variant: "caption"; text: "Expires: " + modelData.expiry; color: Theme.textSecondary }
|
||||
}
|
||||
|
||||
CText { variant: "subtitle1"; text: modelData.from + " → " + modelData.to }
|
||||
CStatusBadge { status: "warning"; text: "Pending" }
|
||||
Item { Layout.fillWidth: true }
|
||||
CText { variant: "caption"; text: "Expires: " + modelData.expiry; color: Theme.textSecondary }
|
||||
}
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: modelData.reason
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: modelData.reason
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Approve"
|
||||
variant: "primary"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
var entry = {
|
||||
from: pendingTransfers[index].from,
|
||||
to: pendingTransfers[index].to,
|
||||
reason: pendingTransfers[index].reason,
|
||||
date: "2026-03-18 " + Qt.formatTime(new Date(), "hh:mm"),
|
||||
status: "approved"
|
||||
};
|
||||
var hist = transferHistory.slice();
|
||||
hist.unshift(entry);
|
||||
transferHistory = hist;
|
||||
var pend = pendingTransfers.slice();
|
||||
pend.splice(index, 1);
|
||||
pendingTransfers = pend;
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
CButton {
|
||||
text: "Approve"
|
||||
variant: "primary"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
var entry = {
|
||||
from: pendingTransfers[index].from,
|
||||
to: pendingTransfers[index].to,
|
||||
reason: pendingTransfers[index].reason,
|
||||
date: "2026-03-18 " + Qt.formatTime(new Date(), "hh:mm"),
|
||||
status: "approved"
|
||||
};
|
||||
var hist = transferHistory.slice();
|
||||
hist.unshift(entry);
|
||||
transferHistory = hist;
|
||||
var pend = pendingTransfers.slice();
|
||||
pend.splice(index, 1);
|
||||
pendingTransfers = pend;
|
||||
}
|
||||
CButton {
|
||||
text: "Deny"
|
||||
variant: "danger"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
var entry = {
|
||||
from: pendingTransfers[index].from,
|
||||
to: pendingTransfers[index].to,
|
||||
reason: pendingTransfers[index].reason,
|
||||
date: "2026-03-18 " + Qt.formatTime(new Date(), "hh:mm"),
|
||||
status: "denied"
|
||||
};
|
||||
var hist = transferHistory.slice();
|
||||
hist.unshift(entry);
|
||||
transferHistory = hist;
|
||||
var pend = pendingTransfers.slice();
|
||||
pend.splice(index, 1);
|
||||
pendingTransfers = pend;
|
||||
}
|
||||
}
|
||||
CButton {
|
||||
text: "Deny"
|
||||
variant: "danger"
|
||||
size: "sm"
|
||||
onClicked: {
|
||||
var entry = {
|
||||
from: pendingTransfers[index].from,
|
||||
to: pendingTransfers[index].to,
|
||||
reason: pendingTransfers[index].reason,
|
||||
date: "2026-03-18 " + Qt.formatTime(new Date(), "hh:mm"),
|
||||
status: "denied"
|
||||
};
|
||||
var hist = transferHistory.slice();
|
||||
hist.unshift(entry);
|
||||
transferHistory = hist;
|
||||
var pend = pendingTransfers.slice();
|
||||
pend.splice(index, 1);
|
||||
pendingTransfers = pend;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,8 +579,7 @@ Rectangle {
|
||||
variant: "outlined"
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
Layout.fillWidth: true
|
||||
spacing: 16
|
||||
|
||||
ColumnLayout {
|
||||
@@ -614,7 +588,7 @@ Rectangle {
|
||||
|
||||
FlexRow {
|
||||
spacing: 8
|
||||
CText { variant: "subtitle1"; text: modelData.from + " → " + modelData.to }
|
||||
CText { variant: "subtitle1"; text: modelData.from + " -> " + modelData.to }
|
||||
CStatusBadge {
|
||||
status: modelData.status === "approved" ? "success" : "error"
|
||||
text: modelData.status
|
||||
@@ -662,26 +636,20 @@ Rectangle {
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
CText { variant: "subtitle1"; text: modelData.name }
|
||||
CStatusBadge {
|
||||
status: modelData.status === "healthy" ? "success" : "warning"
|
||||
text: modelData.status
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
CBadge { text: ":" + modelData.port }
|
||||
CText { variant: "subtitle1"; text: modelData.name }
|
||||
CStatusBadge {
|
||||
status: modelData.status === "healthy" ? "success" : "warning"
|
||||
text: modelData.status
|
||||
}
|
||||
|
||||
CText { variant: "caption"; text: "Uptime: " + modelData.uptime; color: Theme.textSecondary }
|
||||
Item { Layout.fillWidth: true }
|
||||
CBadge { text: ":" + modelData.port; badgeColor: Theme.info }
|
||||
}
|
||||
|
||||
CText { variant: "caption"; text: "Uptime: " + modelData.uptime; color: Theme.textSecondary }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -708,26 +676,30 @@ Rectangle {
|
||||
delegate: CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 8
|
||||
CText {
|
||||
variant: "caption"
|
||||
text: modelData.label
|
||||
color: Theme.textSecondary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
CText { variant: "caption"; text: modelData.label; color: Theme.textSecondary }
|
||||
CText { variant: "h3"; text: modelData.value + "%" }
|
||||
CText {
|
||||
variant: "h3"
|
||||
text: modelData.value + "%"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.surfaceVariant
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 6
|
||||
width: parent.width * (modelData.value / 100)
|
||||
height: parent.height
|
||||
radius: 3
|
||||
color: Theme.border
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * (modelData.value / 100)
|
||||
height: parent.height
|
||||
radius: 3
|
||||
color: modelData.value > 80 ? Theme.error : modelData.value > 60 ? Theme.warning : Theme.primary
|
||||
}
|
||||
color: modelData.value > 80 ? Theme.error : modelData.value > 60 ? Theme.warning : Theme.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -777,35 +749,29 @@ Rectangle {
|
||||
CCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 8
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 24
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 24
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Version"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "2.5.0-rc1" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Build Date"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "2026-03-15" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Node Count"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "4 nodes" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Platform"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "MetaBuilder Universal" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Version"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "2.5.0-rc1" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Build Date"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "2026-03-15" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Node Count"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "4 nodes" }
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 4
|
||||
CText { variant: "caption"; text: "Platform"; color: Theme.textSecondary }
|
||||
CText { variant: "body1"; text: "MetaBuilder Universal" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user