Skip to content

Commit a5074d1

Browse files
committed
fix(CLI): Added new commands
fix(Dashboard): Updated most views Signed-off-by: Charles d'Avernas <charles.davernas@neuroglia.io>
1 parent 59e1962 commit a5074d1

33 files changed

+855
-66
lines changed

src/api/Synapse.Api.Application/Commands/Resources/CreateResourceCommand.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public CreateResourceCommand(IResource resource, string group, string version, s
3434
if (string.IsNullOrWhiteSpace(version)) throw new ArgumentNullException(nameof(version));
3535
if (string.IsNullOrWhiteSpace(plural)) throw new ArgumentNullException(nameof(plural));
3636
this.Resource = resource ?? throw new ArgumentNullException(nameof(resource));
37+
3738
this.Group = group;
3839
this.Version = version;
3940
this.Plural = plural;
@@ -78,6 +79,7 @@ public class CreateResourceCommandHandler(IResourceRepository repository)
7879
/// <inheritdoc/>
7980
public virtual async Task<IOperationResult<IResource>> HandleAsync(CreateResourceCommand command, CancellationToken cancellationToken)
8081
{
82+
if (command.Resource.GetName().Trim().EndsWith('-')) command.Resource.Metadata.Name = $"{command.Resource.GetName().Trim()}{Guid.NewGuid().ToString("N")[..15]}";
8183
var resource = await repository.AddAsync(command.Resource, command.Group, command.Version, command.Plural, command.DryRun, cancellationToken);
8284
return new OperationResult<IResource>((int)HttpStatusCode.Created, resource);
8385
}

src/api/Synapse.Api.Application/Commands/Resources/Generic/CreateResourceCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class CreateResourceCommandHandler<TResource>(IResourceRepository reposit
4646
/// <inheritdoc/>
4747
public virtual async Task<IOperationResult<TResource>> HandleAsync(CreateResourceCommand<TResource> command, CancellationToken cancellationToken)
4848
{
49+
if (command.Resource.GetName().Trim().EndsWith('-')) command.Resource.Metadata.Name = $"{command.Resource.GetName().Trim()}{Guid.NewGuid().ToString("N")[..15]}";
4950
var resource = await repository.AddAsync(command.Resource, false, cancellationToken);
5051
return new OperationResult<TResource>((int)HttpStatusCode.Created, resource);
5152
}

src/api/Synapse.Api.Client.Http/Extensions/IServiceCollectionExtensions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@ public static class IServiceCollectionExtensions
2828
public static IServiceCollection AddSynapseHttpApiClient(this IServiceCollection services, Action<SynapseHttpApiClientOptions> setup)
2929
{
3030
services.Configure(setup);
31-
services.AddHttpClient(typeof(SynapseHttpApiClient).Name, (provider, http) =>
31+
services.AddHttpClient<ISynapseApiClient, SynapseHttpApiClient>((provider, http) =>
3232
{
3333
http.BaseAddress = provider.GetRequiredService<IOptions<SynapseHttpApiClientOptions>>().Value.BaseAddress;
3434
});
35-
services.TryAddScoped<ISynapseApiClient, SynapseHttpApiClient>();
3635
services.TryAddSingleton(provider =>
3736
{
3837
var options = provider.GetRequiredService<IOptions<SynapseHttpApiClientOptions>>().Value;

src/api/Synapse.Api.Client.Http/Services/SynapseHttpApiClient.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public class SynapseHttpApiClient
2626
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
2727
/// <param name="loggerFactory">The service used to create <see cref="ILogger"/>s</param>
2828
/// <param name="serializer">The service used to serialize/deserialize objects to/from JSON</param>
29-
/// <param name="httpClientFactory">The service used to create <see cref="System.Net.Http.HttpClient"/>s</param>
30-
public SynapseHttpApiClient(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IJsonSerializer serializer, IHttpClientFactory httpClientFactory)
29+
/// <param name="httpClient">The service used to perform http requests</param>
30+
public SynapseHttpApiClient(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IJsonSerializer serializer, HttpClient httpClient)
3131
{
3232
this.ServiceProvider = serviceProvider;
3333
this.Logger = loggerFactory.CreateLogger(GetType());
3434
this.Serializer = serializer;
35-
this.HttpClient = httpClientFactory.CreateClient(typeof(SynapseHttpApiClient).Name);
35+
this.HttpClient = httpClient;
3636
this.WorkflowData = ActivatorUtilities.CreateInstance<DocumentHttpApiClient>(this.ServiceProvider, this.HttpClient);
3737
foreach (var apiProperty in GetType().GetProperties().Where(p => p.CanRead && p.PropertyType.GetGenericType(typeof(IResourceApiClient<>)) != null))
3838
{

src/api/Synapse.Api.Http/Synapse.Api.Http.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.10.0" />
14-
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.10.0" />
13+
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.11.0" />
14+
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.11.0" />
1515
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
1616
</ItemGroup>
1717

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Neuroglia.Data.Infrastructure.ResourceOriented;
2+
using Synapse.Cli.Commands.Namespaces;
3+
4+
namespace Synapse.Cli.Commands;
5+
6+
/// <summary>
7+
/// Represents the <see cref="Command"/> used to manage <see cref="Namespace"/>s
8+
/// </summary>
9+
public class NamespaceCommand
10+
: Command
11+
{
12+
13+
/// <summary>
14+
/// Gets the <see cref="NamespaceCommand"/>'s name
15+
/// </summary>
16+
public const string CommandName = "namespace";
17+
/// <summary>
18+
/// Gets the <see cref="NamespaceCommand"/>'s description
19+
/// </summary>
20+
public const string CommandDescription = "Manages namespaces";
21+
22+
/// <inheritdoc/>
23+
public NamespaceCommand(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, ISynapseApiClient api)
24+
: base(serviceProvider, loggerFactory, api, CommandName, CommandDescription)
25+
{
26+
this.AddAlias("namespaces");
27+
this.AddAlias("ns");
28+
this.AddCommand(ActivatorUtilities.CreateInstance<CreateNamespaceCommand>(this.ServiceProvider));
29+
this.AddCommand(ActivatorUtilities.CreateInstance<ListNamespacesCommand>(this.ServiceProvider));
30+
this.AddCommand(ActivatorUtilities.CreateInstance<DeleteNamespaceCommand>(this.ServiceProvider));
31+
}
32+
33+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Neuroglia.Data.Infrastructure.ResourceOriented;
2+
3+
namespace Synapse.Cli.Commands.Namespaces;
4+
5+
/// <summary>
6+
/// Represents the <see cref="Command"/> used to create a new <see cref="Namespace"/>
7+
/// </summary>
8+
internal class CreateNamespaceCommand
9+
: Command
10+
{
11+
12+
/// <summary>
13+
/// Gets the <see cref="CreateNamespaceCommand"/>'s name
14+
/// </summary>
15+
public const string CommandName = "create";
16+
/// <summary>
17+
/// Gets the <see cref="CreateNamespaceCommand"/>'s description
18+
/// </summary>
19+
public const string CommandDescription = "Creates a new namespace.";
20+
21+
/// <inheritdoc/>
22+
public CreateNamespaceCommand(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, ISynapseApiClient api)
23+
: base(serviceProvider, loggerFactory, api, CommandName, CommandDescription)
24+
{
25+
this.Add(new Argument<string>("name") { Description = "The name of the namespace to create." });
26+
this.Handler = CommandHandler.Create<string>(this.HandleAsync);
27+
}
28+
29+
/// <summary>
30+
/// Handles the <see cref="CreateNamespaceCommand"/>
31+
/// </summary>
32+
/// <param name="name">The name of the namespace to create</param>
33+
/// <returns>A new awaitable <see cref="Task"/></returns>
34+
public async Task HandleAsync(string name)
35+
{
36+
ArgumentException.ThrowIfNullOrWhiteSpace(name);
37+
await this.Api.Namespaces.CreateAsync(new()
38+
{
39+
Metadata = new()
40+
{
41+
Name = name
42+
}
43+
});
44+
Console.WriteLine($"namespace/{name} created");
45+
}
46+
47+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Neuroglia.Data;
2+
using Neuroglia.Data.Infrastructure.ResourceOriented;
3+
4+
namespace Synapse.Cli.Commands.Namespaces;
5+
6+
/// <summary>
7+
/// Represents the <see cref="Command"/> used to delete a single <see cref="Namespace"/>
8+
/// </summary>
9+
internal class DeleteNamespaceCommand
10+
: Command
11+
{
12+
13+
/// <summary>
14+
/// Gets the <see cref="DeleteNamespaceCommand"/>'s name
15+
/// </summary>
16+
public const string CommandName = "delete";
17+
/// <summary>
18+
/// Gets the <see cref="DeleteNamespaceCommand"/>'s description
19+
/// </summary>
20+
public const string CommandDescription = "Deletes the specified namespace";
21+
22+
/// <inheritdoc/>
23+
public DeleteNamespaceCommand(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, ISynapseApiClient api)
24+
: base(serviceProvider, loggerFactory, api, CommandName, CommandDescription)
25+
{
26+
this.AddAlias("del");
27+
this.Add(new Argument<string>("name") { Description = "The name of the namespace to delete" });
28+
this.Add(CommandOptions.Confirm);
29+
this.Handler = CommandHandler.Create<string, bool>(this.HandleAsync);
30+
}
31+
32+
/// <summary>
33+
/// Handles the <see cref="DeleteNamespaceCommand"/>
34+
/// </summary>
35+
/// <param name="name">The name of the namespace to delete</param>
36+
/// <param name="y">A boolean indicating whether or not to ask for the user's confirmation</param>
37+
/// <returns>A new awaitable <see cref="Task"/></returns>
38+
public async Task HandleAsync(string name, bool y)
39+
{
40+
if (!y)
41+
{
42+
Console.Write($"Are you sure you wish to delete the namespace '{name}'? Press 'y' to confirm, or any other key to cancel: ");
43+
var inputKey = Console.ReadKey();
44+
Console.WriteLine();
45+
if (inputKey.Key != ConsoleKey.Y) return;
46+
}
47+
await this.Api.Namespaces.DeleteAsync(name);
48+
Console.WriteLine($"namespace/{name} deleted");
49+
}
50+
51+
static class CommandOptions
52+
{
53+
54+
public static Option<bool> Confirm => new(["-y", "--yes"], () => false, "Delete the @namespace(s) without prompting confirmation");
55+
56+
}
57+
58+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using Neuroglia.Data.Infrastructure.ResourceOriented;
2+
3+
namespace Synapse.Cli.Commands.Namespaces;
4+
5+
/// <summary>
6+
/// Represents the <see cref="Command"/> used to list<see cref="Namespace"/>s
7+
/// </summary>
8+
internal class ListNamespacesCommand
9+
: Command
10+
{
11+
12+
/// <summary>
13+
/// Gets the <see cref="ListNamespacesCommand"/>'s name
14+
/// </summary>
15+
public const string CommandName = "list";
16+
/// <summary>
17+
/// Gets the <see cref="ListNamespacesCommand"/>'s description
18+
/// </summary>
19+
public const string CommandDescription = "Lists namespaces";
20+
21+
/// <inheritdoc/>
22+
public ListNamespacesCommand(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, ISynapseApiClient api)
23+
: base(serviceProvider, loggerFactory, api, CommandName, CommandDescription)
24+
{
25+
this.AddAlias("ls");
26+
this.Handler = CommandHandler.Create(this.HandleAsync);
27+
}
28+
29+
/// <summary>
30+
/// Handles the <see cref="ListNamespacesCommand"/>
31+
/// </summary>
32+
/// <returns>A new awaitable <see cref="Task"/></returns>
33+
public async Task HandleAsync()
34+
{
35+
var table = new Table();
36+
var isEmpty = true;
37+
table.Border(TableBorder.None);
38+
table.AddColumn("NAME");
39+
table.AddColumn("CREATED AT", column =>
40+
{
41+
column.Alignment = Justify.Center;
42+
});
43+
await foreach (var @namespace in await this.Api.Namespaces.ListAsync())
44+
{
45+
isEmpty = false;
46+
table.AddRow
47+
(
48+
@namespace.GetName(),
49+
@namespace.Metadata.CreationTimestamp.ToString()!
50+
);
51+
}
52+
if (isEmpty)
53+
{
54+
AnsiConsole.WriteLine($"No resource found");
55+
return;
56+
}
57+
AnsiConsole.Write(table);
58+
}
59+
60+
}

src/cli/Synapse.Cli/Commands/WorkflowCommand.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ public WorkflowCommand(IServiceProvider serviceProvider, ILoggerFactory loggerFa
2525
this.AddAlias("workflows");
2626
this.AddAlias("wf");
2727
this.AddCommand(ActivatorUtilities.CreateInstance<CreateWorkflowCommand>(this.ServiceProvider));
28-
//this.AddCommand(ActivatorUtilities.CreateInstance<RunWorkflowCommand>(this.ServiceProvider));
29-
//this.AddCommand(ActivatorUtilities.CreateInstance<GetWorkflowCommand>(this.ServiceProvider));
30-
//this.AddCommand(ActivatorUtilities.CreateInstance<ListWorkflowsCommand>(this.ServiceProvider));
28+
this.AddCommand(ActivatorUtilities.CreateInstance<RunWorkflowCommand>(this.ServiceProvider));
29+
this.AddCommand(ActivatorUtilities.CreateInstance<GetWorkflowCommand>(this.ServiceProvider));
30+
this.AddCommand(ActivatorUtilities.CreateInstance<ListWorkflowsCommand>(this.ServiceProvider));
3131
this.AddCommand(ActivatorUtilities.CreateInstance<DeleteWorkflowCommand>(this.ServiceProvider));
3232
}
3333

src/cli/Synapse.Cli/Commands/WorkflowInstanceCommand.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Synapse.Cli.Commands;
1+
using Synapse.Cli.Commands.WorkflowInstances;
2+
3+
namespace Synapse.Cli.Commands;
24

35
/// <summary>
46
/// Represents the <see cref="Command"/> used to manage <see cref="WorkflowInstance"/>s
@@ -21,12 +23,11 @@ public WorkflowInstanceCommand(IServiceProvider serviceProvider, ILoggerFactory
2123
: base(serviceProvider, loggerFactory, api, CommandName, CommandDescription)
2224
{
2325
this.AddAlias("workflow-instances");
24-
this.AddAlias("instance");
25-
this.AddAlias("instances");
2626
this.AddAlias("wfi");
27-
//this.AddCommand(ActivatorUtilities.CreateInstance<GetWorkflowInstanceCommand>(this.ServiceProvider));
28-
//this.AddCommand(ActivatorUtilities.CreateInstance<ListWorkflowInstancesCommand>(this.ServiceProvider));
29-
//this.AddCommand(ActivatorUtilities.CreateInstance<DeleteWorkflowInstanceCommand>(this.ServiceProvider));
27+
this.AddCommand(ActivatorUtilities.CreateInstance<GetWorkflowInstanceCommand>(this.ServiceProvider));
28+
this.AddCommand(ActivatorUtilities.CreateInstance<GetWorkflowInstanceOutputCommand>(this.ServiceProvider));
29+
this.AddCommand(ActivatorUtilities.CreateInstance<ListWorkflowInstancesCommand>(this.ServiceProvider));
30+
this.AddCommand(ActivatorUtilities.CreateInstance<DeleteWorkflowInstanceCommand>(this.ServiceProvider));
3031
//this.AddCommand(ActivatorUtilities.CreateInstance<SuspendWorkflowInstanceCommand>(this.ServiceProvider));
3132
//this.AddCommand(ActivatorUtilities.CreateInstance<ResumeWorkflowInstanceCommand>(this.ServiceProvider));
3233
//this.AddCommand(ActivatorUtilities.CreateInstance<CancelWorkflowInstanceCommand>(this.ServiceProvider));
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
namespace Synapse.Cli.Commands.WorkflowInstances;
2+
3+
/// <summary>
4+
/// Represents the <see cref="Command"/> used to delete a single <see cref="WorkflowInstance"/>
5+
/// </summary>
6+
internal class DeleteWorkflowInstanceCommand
7+
: Command
8+
{
9+
10+
/// <summary>
11+
/// Gets the <see cref="DeleteWorkflowInstanceCommand"/>'s name
12+
/// </summary>
13+
public const string CommandName = "delete";
14+
/// <summary>
15+
/// Gets the <see cref="DeleteWorkflowInstanceCommand"/>'s description
16+
/// </summary>
17+
public const string CommandDescription = "Deletes the specified workflow instance";
18+
19+
/// <inheritdoc/>
20+
public DeleteWorkflowInstanceCommand(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, ISynapseApiClient api)
21+
: base(serviceProvider, loggerFactory, api, CommandName, CommandDescription)
22+
{
23+
this.AddAlias("del");
24+
this.Add(new Argument<string>("name") { Description = "The name of the workflow instance to delete" });
25+
this.Add(CommandOptions.Namespace);
26+
this.Add(CommandOptions.Confirm);
27+
this.Handler = CommandHandler.Create<string, string, bool>(this.HandleAsync);
28+
}
29+
30+
/// <summary>
31+
/// Handles the <see cref="DeleteWorkflowInstanceCommand"/>
32+
/// </summary>
33+
/// <param name="namespace">The namespace of the workflow to delete</param>
34+
/// <param name="name">The name of the workflow to delete</param>
35+
/// <param name="y">A boolean indicating whether or not to ask for the user's confirmation</param>
36+
/// <returns>A new awaitable <see cref="Task"/></returns>
37+
public async Task HandleAsync(string name, string @namespace, bool y)
38+
{
39+
if (!y)
40+
{
41+
Console.Write($"Are you sure you wish to delete the workflow instance '{name}.{@namespace}'? Press 'y' to confirm, or any other key to cancel: ");
42+
var inputKey = Console.ReadKey();
43+
Console.WriteLine();
44+
if (inputKey.Key != ConsoleKey.Y) return;
45+
}
46+
await this.Api.WorkflowInstances.DeleteAsync(name, @namespace);
47+
Console.WriteLine($"workflow-instance/{name} deleted");
48+
}
49+
50+
static class CommandOptions
51+
{
52+
53+
public static Option<string> Namespace => new([ "-n", "--namespace" ], () => "default", "The namespace the workflow instance to delete belongs to.");
54+
55+
public static Option<bool> Confirm => new(["-y", "--yes"], () => false, "Delete the workflow instance without prompting confirmation.");
56+
57+
}
58+
59+
}

0 commit comments

Comments
 (0)