source: advance the checked projection

Source-Sha: 194a59adfd6c901635da7a9c18253bb2210e2426
Policy-Sha: b9b0be0ee74e55f561a803b8aef6be3c2134f5a83b46816a069a372f2eb04f4d
Tree-Digest: 4697f2e281c1c6ecafff17cf6eeb391a0a1ac0fa6e5fc429f5b473f4579c252c
This commit is contained in:
Source publisher 2026-09-21 14:39:08 +00:00
commit ffdccba10f
42 changed files with 2516 additions and 554 deletions

View file

@ -49,6 +49,8 @@ ApplicationWindow {
property string pendingProcessQuery: ""
property var approval: parse(machine.approvalJson, {
})
property var fleetJoin: parse(machine.joinJson, {
})
// The capabilities that turn "a process" into "a process that can rewrite the
// machine". Tinted so a wall of chips still reads at a glance.
readonly property var sharpCapabilities: ["CAP_SYS_ADMIN", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_PTRACE", "CAP_SYS_BOOT", "CAP_BPF", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_DAC_READ_SEARCH", "CAP_SETUID", "CAP_SETGID"]
@ -1781,7 +1783,6 @@ ApplicationWindow {
font.pixelSize: Theme.fontMeta
Layout.preferredWidth: 100
}
}
}
@ -3004,6 +3005,243 @@ ApplicationWindow {
}
Rectangle {
id: fleetJoinCard
visible: machine.enrollment === "standalone" || machine.enrollment === "joining fleet"
Layout.fillWidth: true
Layout.preferredHeight: visible ? fleetJoinColumn.implicitHeight + 30 : 0
color: Theme.surface
radius: Theme.radius
border.width: 1
border.color: machine.enrollment === "joining fleet" ? Theme.warn : Theme.divider
ColumnLayout {
id: fleetJoinColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 15
spacing: 10
RowLayout {
Layout.fillWidth: true
ColumnLayout {
spacing: 2
Text {
text: "JOIN THIS MACHINE TO A FLEET"
color: Theme.text
font.pixelSize: 11
font.weight: 750
font.letterSpacing: 1
}
Text {
text: "Keep this machine's identity. Replace its local signing authority with the fleet Server key."
color: Theme.dim
font.pixelSize: 10
}
}
Item {
Layout.fillWidth: true
}
Text {
text: machine.enrollment === "joining fleet" ? "CONVERGING" : "ONE WAY"
color: machine.enrollment === "joining fleet" ? Theme.warn : Theme.red
font.pixelSize: 9
font.weight: 750
font.letterSpacing: 1
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Button {
text: machine.joinSeed.length ? "New host seed" : "Create host seed"
enabled: !machine.joinRunning && machine.enrollment === "standalone"
onClicked: machine.prepareFleetJoin()
background: Rectangle {
color: parent.pressed ? Theme.hover : Theme.raised
border.width: 1
border.color: Theme.divider
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
color: parent.enabled ? Theme.text : Theme.faint
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 11
font.weight: 650
}
}
TextField {
id: fleetJoinSeed
Layout.fillWidth: true
text: machine.joinSeed
readOnly: true
selectByMouse: true
placeholderText: "Create the host proof seed first"
color: Theme.cyan
placeholderTextColor: Theme.faint
font.family: Theme.mono
background: Rectangle {
color: Theme.raised
border.width: 1
border.color: fleetJoinSeed.activeFocus ? Theme.cyan : Theme.divider
radius: Theme.radiusSm
}
}
}
Text {
Layout.fillWidth: true
text: "In RedFlag Web, create a one-seat fleet join key using this host seed. Then return here with the key."
color: Theme.faint
font.pixelSize: 10
wrapMode: Text.Wrap
}
GridLayout {
Layout.fillWidth: true
columns: 2
columnSpacing: 8
rowSpacing: 4
Text {
text: "SERVER URL"
color: Theme.faint
font.pixelSize: 9
font.weight: 700
}
Text {
text: "ONE-SEAT JOIN KEY"
color: Theme.faint
font.pixelSize: 9
font.weight: 700
}
TextField {
id: fleetJoinServer
Layout.fillWidth: true
placeholderText: "https://redflag.example"
color: Theme.text
placeholderTextColor: Theme.faint
background: Rectangle {
color: Theme.raised
border.width: 1
border.color: fleetJoinServer.activeFocus ? Theme.red : Theme.divider
radius: Theme.radiusSm
}
}
TextField {
id: fleetJoinToken
Layout.fillWidth: true
placeholderText: "Paste the join key"
echoMode: TextInput.Password
color: Theme.text
placeholderTextColor: Theme.faint
font.family: Theme.mono
background: Rectangle {
color: Theme.raised
border.width: 1
border.color: fleetJoinToken.activeFocus ? Theme.red : Theme.divider
radius: Theme.radiusSm
}
}
}
CheckBox {
id: fleetJoinConfirm
enabled: machine.enrollment === "standalone" && !machine.joinRunning
text: "Retire this machine's standalone signing key and local mint permission."
contentItem: Text {
text: parent.text
color: parent.checked ? Theme.warn : Theme.dim
font.pixelSize: 11
leftPadding: parent.indicator.width + parent.spacing
}
}
RowLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: app.fleetJoin.error || (app.fleetJoin.restart_required ? "Fleet authority installed. The Agent is restarting into fleet mode." : "")
color: app.fleetJoin.error ? Theme.bad : Theme.good
font.pixelSize: 10
font.family: Theme.mono
wrapMode: Text.Wrap
}
Button {
text: machine.joinRunning ? "Joining…" : machine.enrollment === "joining fleet" ? "Restarting Agent…" : "Join fleet"
enabled: machine.enrollment === "standalone" && !machine.joinRunning && fleetJoinConfirm.checked && fleetJoinSeed.text.length > 0 && fleetJoinServer.text.trim().length > 0 && fleetJoinToken.text.trim().length > 0
onClicked: machine.joinFleet(fleetJoinServer.text.trim(), fleetJoinToken.text.trim(), fleetJoinSeed.text.trim())
background: Rectangle {
color: !parent.enabled ? Theme.raised : parent.pressed ? Qt.darker(Theme.red, 1.15) : Theme.red
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
color: parent.enabled ? "white" : Theme.faint
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 11
font.weight: 650
}
}
}
Connections {
function onJoinFinished(success) {
if (success) {
fleetJoinToken.text = "";
fleetJoinConfirm.checked = false;
}
}
target: machine
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 100

View file

@ -69,11 +69,14 @@ pub mod ffi {
#[qproperty(bool, scan_status_loading)]
#[qproperty(QString, operation_message)]
#[qproperty(QString, approval_json)]
#[qproperty(QString, join_seed)]
#[qproperty(QString, join_json)]
#[qproperty(i32, software_count)]
#[qproperty(i32, software_explicit_count)]
#[qproperty(i32, software_dependency_count)]
#[qproperty(i32, software_foreign_count)]
#[qproperty(bool, approval_running)]
#[qproperty(bool, join_running)]
#[qproperty(bool, telemetry_loading)]
#[qproperty(bool, overview_loading)]
#[qproperty(bool, processes_loading)]
@ -106,6 +109,15 @@ pub mod ffi {
available_version: &QString,
override_reason: &QString,
);
#[qinvokable]
fn prepare_fleet_join(self: Pin<&mut Machine>);
#[qinvokable]
fn join_fleet(
self: Pin<&mut Machine>,
server_url: &QString,
registration_token: &QString,
seed: &QString,
);
#[qsignal]
fn telemetry_updated(self: Pin<&mut Machine>);
@ -125,6 +137,10 @@ pub mod ffi {
fn operation_finished(self: Pin<&mut Machine>, success: bool);
#[qsignal]
fn approval_finished(self: Pin<&mut Machine>, success: bool);
#[qsignal]
fn join_prepared(self: Pin<&mut Machine>, success: bool);
#[qsignal]
fn join_finished(self: Pin<&mut Machine>, success: bool);
}
impl cxx_qt::Threading for Machine {}
@ -181,11 +197,14 @@ pub struct MachineRust {
scan_status_loading: bool,
operation_message: QString,
approval_json: QString,
join_seed: QString,
join_json: QString,
software_count: i32,
software_explicit_count: i32,
software_dependency_count: i32,
software_foreign_count: i32,
approval_running: bool,
join_running: bool,
telemetry_loading: bool,
overview_loading: bool,
processes_loading: bool,
@ -245,11 +264,14 @@ impl Default for MachineRust {
scan_status_loading: false,
operation_message: QString::default(),
approval_json: QString::from("{}"),
join_seed: QString::default(),
join_json: QString::from("{}"),
software_count: 0,
software_explicit_count: 0,
software_dependency_count: 0,
software_foreign_count: 0,
approval_running: false,
join_running: false,
telemetry_loading: false,
overview_loading: false,
processes_loading: false,
@ -427,6 +449,74 @@ impl ffi::Machine {
self.approval_finished(success);
}
pub fn prepare_fleet_join(mut self: Pin<&mut Self>) {
if self.join_running {
return;
}
self.as_mut().set_join_running(true);
self.as_mut().set_join_json(QString::from("{}"));
let thread = self.qt_thread();
std::thread::spawn(move || {
let result = local_api::post(
"/v1/actions/prepare-fleet-join",
&Value::Object(Default::default()),
);
let _ = thread.queue(move |machine| machine.apply_fleet_join_preparation(result));
});
}
pub fn join_fleet(
mut self: Pin<&mut Self>,
server_url: &QString,
registration_token: &QString,
seed: &QString,
) {
if self.join_running {
return;
}
let request = serde_json::json!({
"server_url": server_url.to_string(),
"registration_token": registration_token.to_string(),
"totp_seed": seed.to_string(),
});
self.as_mut().set_join_running(true);
self.as_mut().set_join_json(QString::from("{}"));
let thread = self.qt_thread();
std::thread::spawn(move || {
let result = local_api::post("/v1/actions/join-fleet", &request);
let _ = thread.queue(move |machine| machine.apply_fleet_join(result));
});
}
fn apply_fleet_join_preparation(mut self: Pin<&mut Self>, result: Result<Value, String>) {
self.as_mut().set_join_running(false);
let success = result.is_ok();
let payload = match result {
Ok(value) => {
self.as_mut().set_join_seed(qtext(&value, "totp_seed"));
value
}
Err(error) => serde_json::json!({ "error": error }),
};
self.as_mut().set_join_json(json_string(&payload));
self.join_prepared(success);
}
fn apply_fleet_join(mut self: Pin<&mut Self>, result: Result<Value, String>) {
self.as_mut().set_join_running(false);
let success = result.is_ok();
let payload = match result {
Ok(value) => {
self.as_mut().set_enrollment(QString::from("joining fleet"));
self.as_mut().set_join_seed(QString::default());
value
}
Err(error) => serde_json::json!({ "error": error }),
};
self.as_mut().set_join_json(json_string(&payload));
self.join_finished(success);
}
fn apply_telemetry(mut self: Pin<&mut Self>, result: Result<Value, String>) {
self.as_mut().set_telemetry_loading(false);
match result {
@ -501,6 +591,14 @@ impl ffi::Machine {
.or_else(|| text(info, "device_type"))
.unwrap_or_else(|| "Computer".into());
let registered = data.identity["registered"].as_bool().unwrap_or(false);
let enrollment = match data.identity["mode"].as_str() {
Some("fleet") => "fleet enrolled",
Some("standalone") => "standalone",
Some("joining") => "joining fleet",
Some("incomplete") => "incomplete",
_ if registered => "fleet enrolled",
_ => "standalone",
};
let updates = data.updates["updates"]
.as_array()
.cloned()
@ -536,11 +634,7 @@ impl ffi::Machine {
.set_agent_version(qtext(&data.identity, "agent_version"));
self.as_mut()
.set_agent_status(qtext(&data.status, "agent_status"));
self.as_mut().set_enrollment(QString::from(if registered {
"fleet enrolled"
} else {
"standalone"
}));
self.as_mut().set_enrollment(QString::from(enrollment));
self.as_mut().set_uptime(qtext(info, "uptime"));
self.as_mut()
.set_process_count(info["running_processes"].as_i64().unwrap_or(0) as i32);