Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 902e05cd06 | |||
| 650c6724f0 | |||
| 5af0faa0ee | |||
| 9a385176cb | |||
| 90eca4e469 | |||
| d8f2997e59 | |||
| a69a7fcfeb | |||
| 62dc2e6499 |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
description = "A very basic flake";
|
description = "sops-tui — a TUI for managing SOPS-encrypted secrets";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
@@ -45,28 +45,23 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
src = craneLib.cleanCargoSource ./.;
|
src = craneLib.cleanCargoSource ./.;
|
||||||
|
|
||||||
commonArgs = {
|
commonArgs = {
|
||||||
inherit src;
|
inherit src;
|
||||||
strictDeps = true;
|
strictDeps = true;
|
||||||
};
|
};
|
||||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
|
||||||
individualCrateArgs = commonArgs // {
|
|
||||||
inherit cargoArtifacts;
|
|
||||||
inherit (craneLib.crateNameFromCargoToml { inherit src; }) version;
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSetForCrate =
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
crate:
|
|
||||||
lib.fileset.toSource {
|
sops-tui = craneLib.buildPackage (
|
||||||
root = ./.;
|
commonArgs
|
||||||
fileset = lib.fileset.unions [
|
|
||||||
./Cargo.toml
|
|
||||||
./Cargo.lock
|
|
||||||
];
|
|
||||||
};
|
|
||||||
server = craneLib.buildPackage (
|
|
||||||
individualCrateArgs
|
|
||||||
// {
|
// {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
meta = {
|
||||||
|
description = "TUI for managing SOPS-encrypted secrets in NixOS/sops-nix workflows";
|
||||||
|
mainProgram = "sops-tui";
|
||||||
|
platforms = lib.platforms.unix;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
@@ -75,15 +70,27 @@
|
|||||||
inherit system;
|
inherit system;
|
||||||
overlays = [ inputs.rust-overlay.overlays.default ];
|
overlays = [ inputs.rust-overlay.overlays.default ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
packages = {
|
||||||
|
default = sops-tui;
|
||||||
|
inherit sops-tui;
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = {
|
||||||
|
inherit sops-tui;
|
||||||
|
sops-tui-clippy = craneLib.cargoClippy (
|
||||||
|
commonArgs
|
||||||
|
// {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "--all-targets";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
sops-tui-fmt = craneLib.cargoFmt { inherit src; };
|
||||||
|
};
|
||||||
|
|
||||||
devShells.default = craneLib.devShell {
|
devShells.default = craneLib.devShell {
|
||||||
packages = with pkgs; [
|
inputsFrom = [ sops-tui ];
|
||||||
mold
|
packages = with pkgs; [ sops ];
|
||||||
llvmPackages.clang
|
|
||||||
llvmPackages.lld
|
|
||||||
sea-orm-cli
|
|
||||||
watchexec
|
|
||||||
pnpm
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+105
-27
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
flake-parts-lib,
|
|
||||||
inputs,
|
inputs,
|
||||||
den,
|
den,
|
||||||
...
|
...
|
||||||
@@ -39,51 +38,81 @@ in
|
|||||||
description = "A function that takes the NixOS configuration and returns a NixOS module to apply to the host based on its network configuration.";
|
description = "A function that takes the NixOS configuration and returns a NixOS module to apply to the host based on its network configuration.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
homeManagerModule = {
|
||||||
|
default = mkOption {
|
||||||
|
type = types.functionTo types.attrs;
|
||||||
|
default = identity: { };
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
config =
|
config =
|
||||||
let
|
let
|
||||||
secrets = builtins.fromJSON (builtins.readFile "${inputs.self}/${cfg.secretsDir}/secrets.json");
|
secrets = builtins.fromJSON (builtins.readFile "${inputs.self}/${cfg.secretsDir}/secrets.json");
|
||||||
all_keys = lib.flatten (lib.concatAttrValues per_host_keys);
|
|
||||||
per_host_keys = lib.mergeAttrsList (
|
toKeyList =
|
||||||
|
v:
|
||||||
|
if builtins.isString v then
|
||||||
|
[ v ]
|
||||||
|
else if builtins.isList v then
|
||||||
|
v
|
||||||
|
else
|
||||||
|
throw "Unexpected type ${builtins.typeOf v} for sopsPublic";
|
||||||
|
|
||||||
|
# Every host across every system: hostName -> [host pubkeys].
|
||||||
|
host_keys = lib.mergeAttrsList (
|
||||||
lib.flatten (
|
lib.flatten (
|
||||||
map (
|
map (
|
||||||
x:
|
perSystem:
|
||||||
builtins.mapAttrs (
|
lib.mapAttrsToList (_: host: { ${host.hostName} = toKeyList (host.sopsPublic or [ ]); }) perSystem
|
||||||
name: value:
|
|
||||||
let
|
|
||||||
v = value.sopsPublic or [ ];
|
|
||||||
type = builtins.typeOf v;
|
|
||||||
vv =
|
|
||||||
if type == "string" then
|
|
||||||
[ v ]
|
|
||||||
else if type == "list" then
|
|
||||||
v
|
|
||||||
else
|
|
||||||
throw "Unexpected type ${type} for sopsPublic in host ${name}";
|
|
||||||
in
|
|
||||||
vv
|
|
||||||
) x
|
|
||||||
) (builtins.attrValues den.hosts)
|
) (builtins.attrValues den.hosts)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Every user on every host is a home identity "<userName>@<hostName>".
|
||||||
|
home_keys = lib.mergeAttrsList (
|
||||||
|
lib.flatten (
|
||||||
|
map (
|
||||||
|
perSystem:
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
_: host:
|
||||||
|
lib.mapAttrsToList (_: user: {
|
||||||
|
"${user.userName}@${host.hostName}" = toKeyList (user.sopsPublic or [ ]);
|
||||||
|
}) (host.users or { })
|
||||||
|
) perSystem
|
||||||
|
) (builtins.attrValues den.hosts)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# Replaces the old hand-maintained `homeIdentities` option.
|
||||||
|
homeIdentities = builtins.attrNames home_keys;
|
||||||
|
|
||||||
|
# A secret's `hosts` may target either a host or a home identity.
|
||||||
|
identity_keys = host_keys // home_keys;
|
||||||
|
all_host_keys = lib.flatten (lib.attrValues host_keys);
|
||||||
|
all_home_keys = lib.flatten (lib.attrValues home_keys);
|
||||||
secret_map = lib.mapAttrs (
|
secret_map = lib.mapAttrs (
|
||||||
name: value:
|
name: value:
|
||||||
let
|
let
|
||||||
sopskeys = lib.unique (
|
sopskeys = lib.unique (
|
||||||
lib.flatten (map (k: per_host_keys.${k}) value.hosts)
|
lib.flatten (map (k: identity_keys.${k}) value.hosts)
|
||||||
++ (lib.optionals value.global all_keys)
|
++ (lib.optionals value.globalHosts all_host_keys)
|
||||||
|
++ (lib.optionals value.globalHomes all_home_keys)
|
||||||
++ cfg.masterKeys
|
++ cfg.masterKeys
|
||||||
);
|
);
|
||||||
|
# Descriptive flag: is this secret consumed by a home-manager user?
|
||||||
|
# True when it is global to homes or targets any home identity.
|
||||||
|
home = value.globalHomes || lib.any (h: lib.elem h homeIdentities) value.hosts;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit (value)
|
inherit (value)
|
||||||
format
|
format
|
||||||
neededForUsers
|
neededForUsers
|
||||||
hosts
|
hosts
|
||||||
global
|
globalHosts
|
||||||
|
globalHomes
|
||||||
;
|
;
|
||||||
keys = value.keys or [ ];
|
keys = value.keys or [ ];
|
||||||
inherit sopskeys;
|
inherit sopskeys home;
|
||||||
}
|
}
|
||||||
) secrets;
|
) secrets;
|
||||||
|
|
||||||
@@ -98,7 +127,7 @@ in
|
|||||||
lib.mapAttrsToList (
|
lib.mapAttrsToList (
|
||||||
name: value:
|
name: value:
|
||||||
let
|
let
|
||||||
hasHost = (lib.elem host value.hosts) || value.global;
|
hasHost = (lib.elem host value.hosts) || value.globalHosts;
|
||||||
isYamlOrJson = value.format == "yaml" || value.format == "json";
|
isYamlOrJson = value.format == "yaml" || value.format == "json";
|
||||||
in
|
in
|
||||||
(
|
(
|
||||||
@@ -110,7 +139,8 @@ in
|
|||||||
${name} = {
|
${name} = {
|
||||||
inherit (value) format neededForUsers;
|
inherit (value) format neededForUsers;
|
||||||
sopsFile = inputs.self + "/${cfg.secretsDir}/${name}";
|
sopsFile = inputs.self + "/${cfg.secretsDir}/${name}";
|
||||||
};
|
}
|
||||||
|
// lib.optionalAttrs (isYamlOrJson && value.keys == [ ]) { key = ""; };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ }
|
{ }
|
||||||
@@ -130,14 +160,57 @@ in
|
|||||||
) secret_map
|
) secret_map
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Home-manager analog of sops_secrets_map. Takes a key identity (e.g.
|
||||||
|
# `laptop-home`) and deliberately omits `neededForUsers`, which the
|
||||||
|
# sops-nix home-manager module does not support (it only matters for
|
||||||
|
# decrypting before system users exist, a NixOS-only concern).
|
||||||
|
home_secrets_map =
|
||||||
|
identity:
|
||||||
|
lib.mkMerge (
|
||||||
|
lib.concatLists (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: value:
|
||||||
|
let
|
||||||
|
hasHost = (lib.elem identity value.hosts) || value.globalHomes;
|
||||||
|
isYamlOrJson = value.format == "yaml" || value.format == "json";
|
||||||
|
in
|
||||||
|
(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
if hasHost && !(isYamlOrJson && value.keys != [ ]) then
|
||||||
|
{
|
||||||
|
${name} = {
|
||||||
|
inherit (value) format;
|
||||||
|
sopsFile = inputs.self + "/${cfg.secretsDir}/${name}";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (isYamlOrJson && value.keys == [ ]) { key = ""; };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
++ lib.optionals hasHost (
|
||||||
|
lib.map (v: {
|
||||||
|
"${name}-${v}" = {
|
||||||
|
inherit (value) format;
|
||||||
|
sopsFile = inputs.self + "/${cfg.secretsDir}/${name}";
|
||||||
|
key = v;
|
||||||
|
};
|
||||||
|
}) value.keys
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) secret_map
|
||||||
|
)
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
flake.secretsManifest = {
|
flake.secretsManifest = {
|
||||||
secretsDir = cfg.secretsDir;
|
secretsDir = cfg.secretsDir;
|
||||||
masterKeys = cfg.masterKeys;
|
masterKeys = cfg.masterKeys;
|
||||||
hosts = lib.mapAttrs (_: keys: { inherit keys; }) per_host_keys;
|
hosts = lib.mapAttrs (_: keys: { inherit keys; }) host_keys;
|
||||||
|
inherit homeIdentities;
|
||||||
secrets = secret_map;
|
secrets = secret_map;
|
||||||
secrets_map = sops_secrets_map;
|
|
||||||
};
|
};
|
||||||
secrets.nixosModule = {
|
secrets.nixosModule = {
|
||||||
default = host: {
|
default = host: {
|
||||||
@@ -151,6 +224,11 @@ in
|
|||||||
config.sops.secrets = sops_secrets_map config.networking.hostName;
|
config.sops.secrets = sops_secrets_map config.networking.hostName;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
secrets.homeManagerModule = {
|
||||||
|
default = identity: {
|
||||||
|
config.sops.secrets = home_secrets_map identity;
|
||||||
|
};
|
||||||
|
};
|
||||||
perSystem =
|
perSystem =
|
||||||
{ pkgs, self', ... }:
|
{ pkgs, self', ... }:
|
||||||
let
|
let
|
||||||
|
|||||||
+3
-4
@@ -1,12 +1,10 @@
|
|||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use ratatui::{prelude::CrosstermBackend, widgets::ListState, DefaultTerminal, Terminal};
|
use ratatui::{prelude::CrosstermBackend, widgets::ListState, Terminal};
|
||||||
use ratatui_form::{Email, Form};
|
|
||||||
use std::{io, path::Path};
|
use std::{io, path::Path};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
event::{AppEvent, Event, EventHandler},
|
event::{AppEvent, Event, EventHandler},
|
||||||
form::FormState,
|
|
||||||
manifest::{load_manifest, Manifest},
|
manifest::{load_manifest, Manifest},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ impl App {
|
|||||||
mut self,
|
mut self,
|
||||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||||
) -> color_eyre::Result<()> {
|
) -> color_eyre::Result<()> {
|
||||||
let project_root = Path::new("/home/nikkuss/dotfiles-new");
|
let project_root = std::env::current_dir()?;
|
||||||
self.events.send(AppEvent::LoadManifest);
|
self.events.send(AppEvent::LoadManifest);
|
||||||
|
|
||||||
while self.running {
|
while self.running {
|
||||||
@@ -53,6 +51,7 @@ impl App {
|
|||||||
AppEvent::LoadManifest => {
|
AppEvent::LoadManifest => {
|
||||||
if !self.isloadingmanifest {
|
if !self.isloadingmanifest {
|
||||||
let tx = self.events.clone_sender();
|
let tx = self.events.clone_sender();
|
||||||
|
let project_root = project_root.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = load_manifest(&project_root).await;
|
let result = load_manifest(&project_root).await;
|
||||||
let _ = tx.send(Event::App(AppEvent::ManifestLoaded(result)));
|
let _ = tx.send(Event::App(AppEvent::ManifestLoaded(result)));
|
||||||
|
|||||||
+9
-1
@@ -11,6 +11,8 @@ pub struct Manifest {
|
|||||||
#[serde(rename = "masterKeys")]
|
#[serde(rename = "masterKeys")]
|
||||||
pub master_keys: Vec<String>,
|
pub master_keys: Vec<String>,
|
||||||
pub hosts: BTreeMap<String, HostInfo>,
|
pub hosts: BTreeMap<String, HostInfo>,
|
||||||
|
#[serde(rename = "homeIdentities", default)]
|
||||||
|
pub home_identities: Vec<String>,
|
||||||
pub secrets: BTreeMap<String, SecretInfo>,
|
pub secrets: BTreeMap<String, SecretInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,11 +25,17 @@ pub struct HostInfo {
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct SecretInfo {
|
pub struct SecretInfo {
|
||||||
pub format: SecretFormat,
|
pub format: SecretFormat,
|
||||||
pub global: bool,
|
#[serde(rename = "globalHosts")]
|
||||||
|
pub global_hosts: bool,
|
||||||
|
#[serde(rename = "globalHomes")]
|
||||||
|
pub global_homes: bool,
|
||||||
pub hosts: Vec<String>,
|
pub hosts: Vec<String>,
|
||||||
#[serde(rename = "neededForUsers")]
|
#[serde(rename = "neededForUsers")]
|
||||||
pub needed_for_users: bool,
|
pub needed_for_users: bool,
|
||||||
|
pub sopskeys: Vec<String>,
|
||||||
pub keys: Vec<String>,
|
pub keys: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub home: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||||
|
|||||||
@@ -81,42 +81,47 @@ fn render_secret_detail(state: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
]));
|
]));
|
||||||
|
|
||||||
// Scope
|
// Scope
|
||||||
let scope = if secret.global {
|
let scope = match (secret.global_hosts, secret.global_homes) {
|
||||||
"global".to_string()
|
(true, true) => "global (hosts + homes)".to_string(),
|
||||||
} else {
|
(true, false) => "global (hosts)".to_string(),
|
||||||
"host-specific".to_string()
|
(false, true) => "global (homes)".to_string(),
|
||||||
|
(false, false) => "host-specific".to_string(),
|
||||||
};
|
};
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled(" Scope: ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" Scope: ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(scope, Style::default().fg(Color::White)),
|
Span::styled(scope, Style::default().fg(Color::White)),
|
||||||
]));
|
]));
|
||||||
if !secret.global {
|
if !(secret.global_hosts && secret.global_homes) {
|
||||||
// Hosts
|
// Hosts / identities. Home-manager identities are coloured distinctly
|
||||||
let hosts_str = if secret.hosts.is_empty() {
|
// so it is obvious which targets are user (home) rather than system.
|
||||||
if secret.global {
|
let mut host_spans: Vec<Span> = vec![Span::styled(
|
||||||
manifest
|
" Hosts: ",
|
||||||
.hosts
|
Style::default().fg(Color::DarkGray),
|
||||||
.keys()
|
)];
|
||||||
.cloned()
|
if secret.hosts.is_empty() {
|
||||||
.collect::<Vec<_>>()
|
host_spans.push(Span::styled("none", Style::default().fg(Color::White)));
|
||||||
.join(", ")
|
|
||||||
} else {
|
|
||||||
"none".to_string()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
secret.hosts.join(", ")
|
for (i, identity) in secret.hosts.iter().enumerate() {
|
||||||
};
|
if i > 0 {
|
||||||
lines.push(Line::from(vec![
|
host_spans.push(Span::styled(", ", Style::default().fg(Color::DarkGray)));
|
||||||
Span::styled(" Hosts: ", Style::default().fg(Color::DarkGray)),
|
}
|
||||||
Span::styled(hosts_str, Style::default().fg(Color::White)),
|
let is_home = manifest.home_identities.contains(identity);
|
||||||
]));
|
let style = if is_home {
|
||||||
|
Style::default().fg(Color::Magenta)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::White)
|
||||||
|
};
|
||||||
|
host_spans.push(Span::styled(identity.clone(), style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push(Line::from(host_spans));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recipients
|
// Recipients
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled(" Recipients: ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" Recipients: ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
format!("{} age keys", secret.keys.len()),
|
format!("{} age keys", secret.sopskeys.len()),
|
||||||
Style::default().fg(Color::White),
|
Style::default().fg(Color::White),
|
||||||
),
|
),
|
||||||
]));
|
]));
|
||||||
@@ -128,6 +133,14 @@ fn render_secret_detail(state: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
Span::styled("yes", Style::default().fg(Color::Yellow)),
|
Span::styled("yes", Style::default().fg(Color::Yellow)),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Home-manager: shown when this secret is consumed by a home-manager user.
|
||||||
|
if secret.home {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::styled(" Home-mgr: ", Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::styled("yes", Style::default().fg(Color::Magenta)),
|
||||||
|
]));
|
||||||
|
}
|
||||||
while lines.len() < 6 {
|
while lines.len() < 6 {
|
||||||
lines.push(Line::from(""));
|
lines.push(Line::from(""));
|
||||||
}
|
}
|
||||||
@@ -140,7 +153,7 @@ fn render_secret_detail(state: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
.fg(Color::DarkGray)
|
.fg(Color::DarkGray)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
)));
|
)));
|
||||||
for key in &secret.keys {
|
for key in &secret.sopskeys {
|
||||||
let truncated = if key.len() > 20 {
|
let truncated = if key.len() > 20 {
|
||||||
format!("{}...", &key[..20])
|
format!("{}...", &key[..20])
|
||||||
} else {
|
} else {
|
||||||
@@ -151,14 +164,22 @@ fn render_secret_detail(state: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
let label = if manifest.master_keys.contains(key) {
|
let label = if manifest.master_keys.contains(key) {
|
||||||
Span::styled(" (master)", Style::default().fg(Color::Magenta))
|
Span::styled(" (master)", Style::default().fg(Color::Magenta))
|
||||||
} else {
|
} else {
|
||||||
// Try to find which host owns this key
|
// Try to find which identity owns this key. Home identities are
|
||||||
let host_label = manifest
|
// tagged and coloured distinctly from system identities.
|
||||||
|
match manifest
|
||||||
.hosts
|
.hosts
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, info)| info.keys.contains(key))
|
.find(|(_, info)| info.keys.contains(key))
|
||||||
.map(|(name, _)| format!(" ({})", name))
|
{
|
||||||
.unwrap_or_default();
|
Some((name, _)) if manifest.home_identities.contains(name) => Span::styled(
|
||||||
Span::styled(host_label, Style::default().fg(Color::Cyan))
|
format!(" ({name}, home)"),
|
||||||
|
Style::default().fg(Color::Magenta),
|
||||||
|
),
|
||||||
|
Some((name, _)) => {
|
||||||
|
Span::styled(format!(" ({name})"), Style::default().fg(Color::Cyan))
|
||||||
|
}
|
||||||
|
None => Span::raw(""),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
@@ -168,7 +189,7 @@ fn render_secret_detail(state: &App, area: Rect, buf: &mut Buffer) {
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
let paragraph = Paragraph::new(lines).block(block).render(area, buf);
|
Paragraph::new(lines).block(block).render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_placeholder(area: Rect, buf: &mut Buffer, title: &str, message: &str) {
|
fn render_placeholder(area: Rect, buf: &mut Buffer, title: &str, message: &str) {
|
||||||
|
|||||||
Reference in New Issue
Block a user