1- //! `qn endpoint tag {add,remove} ` — per- endpoint tag management.
1+ //! `qn endpoint tag … ` — endpoint tag management.
22//!
3- //! For account-wide tag list/rename/delete, see `qn tag`.
3+ //! Covers both account-wide tag CRUD (`list`, `rename`, `delete`) and
4+ //! per-endpoint tag operations (`add`, `remove`). Tags only exist to label
5+ //! endpoints, which is why the account-wide CRUD lives here too.
46
57use clap:: Subcommand ;
6- use quicknode_sdk:: admin:: CreateTagRequest ;
8+ use comfy_table:: Cell ;
9+ use quicknode_sdk:: admin:: { CreateTagRequest , RenameTagRequest } ;
10+ use serde:: Serialize ;
711
12+ use crate :: confirm:: { decide_without_prompt, prompt_yes_no, ConfirmCfg , Severity } ;
813use crate :: context:: Ctx ;
914use crate :: errors:: CliError ;
15+ use crate :: output:: { new_table, set_header_bold, write_table, Render } ;
1016
1117#[ derive( Debug , Subcommand ) ]
1218pub enum TagCmd {
19+ /// List every tag on the account with usage counts.
20+ #[ command( visible_alias = "ls" ) ]
21+ List ,
22+ /// Rename a tag.
23+ Rename {
24+ /// Tag id (numeric).
25+ tag_id : i32 ,
26+ /// New label.
27+ label : String ,
28+ } ,
29+ /// Delete a tag. The tag must not be applied to any endpoint.
30+ Delete {
31+ /// Tag id (numeric).
32+ tag_id : i32 ,
33+ } ,
1334 /// Tag an endpoint. Creates the tag on the account if missing.
1435 Add {
1536 /// Endpoint id.
1637 id : String ,
1738 /// Tag label.
1839 label : String ,
1940 } ,
20- /// Remove a tag from an endpoint. `tag_id` is the numeric tag id from `qn tag list`.
41+ /// Remove a tag from an endpoint. `tag_id` is the numeric tag id from `qn endpoint tag list`.
2142 Remove {
2243 /// Endpoint id.
2344 id : String ,
@@ -28,17 +49,86 @@ pub enum TagCmd {
2849
2950pub async fn run ( cmd : TagCmd , ctx : Ctx ) -> Result < ( ) , CliError > {
3051 match cmd {
31- TagCmd :: Add { id, label } => {
32- let req = CreateTagRequest {
33- label : Some ( label. clone ( ) ) ,
34- } ;
35- ctx. sdk . admin . create_tag ( & id, & req) . await ?;
36- ctx. out . note ( & format ! ( "✓ Tagged {id} with {label:?}" ) ) ;
37- }
38- TagCmd :: Remove { id, tag_id } => {
39- ctx. sdk . admin . delete_tag ( & id, & tag_id) . await ?;
40- ctx. out . note ( & format ! ( "✓ Removed tag {tag_id} from {id}" ) ) ;
41- }
52+ TagCmd :: List => list ( ctx) . await ,
53+ TagCmd :: Rename { tag_id, label } => rename ( tag_id, label, ctx) . await ,
54+ TagCmd :: Delete { tag_id } => delete ( tag_id, ctx) . await ,
55+ TagCmd :: Add { id, label } => add ( id, label, ctx) . await ,
56+ TagCmd :: Remove { id, tag_id } => remove ( id, tag_id, ctx) . await ,
57+ }
58+ }
59+
60+ async fn list ( ctx : Ctx ) -> Result < ( ) , CliError > {
61+ let resp = ctx. sdk . admin . list_tags ( ) . await ?;
62+ crate :: output:: emit ( & ctx. out , & TagsView ( resp) )
63+ }
64+
65+ async fn rename ( tag_id : i32 , label : String , ctx : Ctx ) -> Result < ( ) , CliError > {
66+ let req = RenameTagRequest {
67+ label : label. clone ( ) ,
68+ } ;
69+ ctx. sdk . admin . rename_tag ( tag_id, & req) . await ?;
70+ ctx. out . note ( & format ! ( "✓ Renamed tag {tag_id} → {label:?}" ) ) ;
71+ Ok ( ( ) )
72+ }
73+
74+ async fn delete ( tag_id : i32 , ctx : Ctx ) -> Result < ( ) , CliError > {
75+ let cfg = ConfirmCfg :: new (
76+ ctx. global . yes_count ,
77+ ctx. global . no_input ,
78+ ctx. out . stdout_is_tty ,
79+ ) ;
80+ let proceed = match decide_without_prompt ( Severity :: Mild , cfg) ? {
81+ true => true ,
82+ false => prompt_yes_no ( & format ! ( "Delete tag {tag_id}?" ) ) ?,
83+ } ;
84+ if !proceed {
85+ return Err ( CliError :: Cancelled ) ;
4286 }
87+ ctx. sdk . admin . delete_account_tag ( tag_id) . await ?;
88+ ctx. out . note ( & format ! ( "✓ Deleted tag {tag_id}" ) ) ;
89+ Ok ( ( ) )
90+ }
91+
92+ async fn add ( id : String , label : String , ctx : Ctx ) -> Result < ( ) , CliError > {
93+ let req = CreateTagRequest {
94+ label : Some ( label. clone ( ) ) ,
95+ } ;
96+ ctx. sdk . admin . create_tag ( & id, & req) . await ?;
97+ ctx. out . note ( & format ! ( "✓ Tagged {id} with {label:?}" ) ) ;
98+ Ok ( ( ) )
99+ }
100+
101+ async fn remove ( id : String , tag_id : String , ctx : Ctx ) -> Result < ( ) , CliError > {
102+ ctx. sdk . admin . delete_tag ( & id, & tag_id) . await ?;
103+ ctx. out . note ( & format ! ( "✓ Removed tag {tag_id} from {id}" ) ) ;
43104 Ok ( ( ) )
44105}
106+
107+ #[ derive( Serialize ) ]
108+ struct TagsView ( quicknode_sdk:: admin:: ListTagsResponse ) ;
109+
110+ impl Render for TagsView {
111+ fn render_table (
112+ & self ,
113+ w : & mut dyn std:: io:: Write ,
114+ ctx : & crate :: output:: OutputCtx ,
115+ ) -> std:: io:: Result < ( ) > {
116+ let data = match & self . 0 . data {
117+ Some ( d) => d,
118+ None => {
119+ writeln ! ( w, "(no tag data)" ) ?;
120+ return Ok ( ( ) ) ;
121+ }
122+ } ;
123+ let mut t = new_table ( ctx) ;
124+ set_header_bold ( & mut t, ctx, vec ! [ "ID" , "LABEL" , "USAGE" ] ) ;
125+ for tg in & data. tags {
126+ t. add_row ( vec ! [
127+ Cell :: new( tg. id) ,
128+ Cell :: new( & tg. label) ,
129+ Cell :: new( tg. usage_count) ,
130+ ] ) ;
131+ }
132+ write_table ( w, & t)
133+ }
134+ }
0 commit comments