Skip to content

Commit 1bad628

Browse files
Added support for viewing DynamoDB table information
1 parent 0995d15 commit 1bad628

File tree

8 files changed

+267
-13
lines changed

8 files changed

+267
-13
lines changed

GuiStack/Extensions/AWSExtensions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,24 @@
1111
using System.Collections.Generic;
1212
using System.Net;
1313
using System.Runtime.CompilerServices;
14+
using Amazon.DynamoDBv2;
1415
using Amazon.Runtime;
1516
using Amazon.SQS.Model;
1617

1718
namespace GuiStack.Extensions
1819
{
1920
public static class AWSExtensions
2021
{
22+
private static readonly Dictionary<string, string> DynamoDBBillingModeMap = new Dictionary<string, string>() {
23+
{ BillingMode.PAY_PER_REQUEST.Value, "On-demand" },
24+
{ BillingMode.PROVISIONED.Value, "Provisioned" }
25+
};
26+
27+
private static readonly Dictionary<string, string> DynamoDBTableClassMap = new Dictionary<string, string>() {
28+
{ TableClass.STANDARD.Value, "Standard" },
29+
{ TableClass.STANDARD_INFREQUENT_ACCESS.Value, "Standard-IA" }
30+
};
31+
2132
/// <summary>
2233
/// Throws a <see cref="WebException"/> if the response returns a non-successful status code (includes 3xx redirects, since they are unusable by the caller).
2334
/// </summary>
@@ -37,6 +48,30 @@ public static void ThrowIfUnsuccessful(this AmazonWebServiceResponse response, s
3748
}
3849
}
3950

51+
/// <summary>
52+
/// Converts the <see cref="BillingMode"/> into a human-readable string.
53+
/// </summary>
54+
/// <param name="entity">The <see cref="BillingMode"/> to convert.</param>
55+
public static string ToHumanReadableString(this BillingMode entity)
56+
{
57+
if(entity == null)
58+
return "(Unknown)";
59+
60+
return DynamoDBBillingModeMap.GetValueOrDefault(entity?.Value) ?? "(Unknown)";
61+
}
62+
63+
/// <summary>
64+
/// Converts the <see cref="TableClass"/> into a human-readable string.
65+
/// </summary>
66+
/// <param name="entity">The <see cref="TableClass"/> to convert.</param>
67+
public static string ToHumanReadableString(this TableClass entity)
68+
{
69+
if(entity == null)
70+
return "(Unknown)";
71+
72+
return DynamoDBTableClassMap.GetValueOrDefault(entity?.Value) ?? "(Unknown)";
73+
}
74+
4075
public static Dictionary<string, string> ToStringAttributes(this Dictionary<string, MessageAttributeValue> attributes)
4176
{
4277
Dictionary<string, string> strAttributes = new Dictionary<string, string>();

GuiStack/Extensions/ModelExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public static class ModelExtensions
2424
{ DynamoDBAttributeType.Number, ScalarAttributeType.N },
2525
{ DynamoDBAttributeType.Binary, ScalarAttributeType.B }
2626
};
27+
28+
private static readonly Dictionary<string, DynamoDBAttributeType> ScalarDynamoDBAttributeMap = new Dictionary<string, DynamoDBAttributeType>() {
29+
{ ScalarAttributeType.S.Value, DynamoDBAttributeType.String },
30+
{ ScalarAttributeType.N.Value, DynamoDBAttributeType.Number },
31+
{ ScalarAttributeType.B.Value, DynamoDBAttributeType.Binary }
32+
};
2733

2834
/// <summary>
2935
/// Converts the <see cref="IEnumerable"/>&lt;<see cref="S3Object"/>&gt; to <see cref="IEnumerable"/>&lt;<see cref="S3ObjectModel"/>&gt;.
@@ -38,6 +44,15 @@ public static IEnumerable<S3ObjectModel> ToObjectModel(this IEnumerable<S3Object
3844
});
3945
}
4046

47+
/// <summary>
48+
/// Converts the <see cref="ScalarAttributeType"/> to <see cref="DynamoDBAttributeType"/>.
49+
/// </summary>
50+
/// <param name="attributeType">The <see cref="ScalarAttributeType"/> to convert.</param>
51+
public static DynamoDBAttributeType ToDynamoDBAttributeType(this ScalarAttributeType attributeType)
52+
{
53+
return ScalarDynamoDBAttributeMap.GetValueOrDefault(attributeType.Value);
54+
}
55+
4156
/// <summary>
4257
/// Converts the <see cref="DynamoDBAttributeType"/> to <see cref="ScalarAttributeType"/>.
4358
/// </summary>

GuiStack/Models/DynamoDBAttribute.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,22 @@ public class DynamoDBAttribute
1515
{
1616
public string Name { get; set; }
1717
public DynamoDBAttributeType Type { get; set; }
18+
19+
public DynamoDBAttribute()
20+
{
21+
}
22+
23+
public DynamoDBAttribute(string name, DynamoDBAttributeType type)
24+
{
25+
Name = name;
26+
Type = type;
27+
}
1828
}
1929

2030
public enum DynamoDBAttributeType
2131
{
22-
String = 0,
32+
Unknown = 0,
33+
String,
2334
Number,
2435
Binary
2536
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*/
9+
10+
using System;
11+
using System.Collections.Generic;
12+
using Amazon;
13+
14+
namespace GuiStack.Models
15+
{
16+
public class DynamoDBTableModel
17+
{
18+
public string Name { get; set; }
19+
public Arn Arn { get; set; }
20+
public long ItemCount { get; set; }
21+
public long TableSizeBytes { get; set; }
22+
public string TableClass { get; set; }
23+
public string BillingMode { get; set; }
24+
public long ReadCapacityUnits { get; set; }
25+
public long WriteCapacityUnits { get; set; }
26+
public bool DeletionProtectionEnabled { get; set; }
27+
public string Status { get; set; }
28+
29+
public DynamoDBAttribute PartitionKey { get; set; }
30+
public DynamoDBAttribute SortKey { get; set; }
31+
public IEnumerable<DynamoDBAttribute> Attributes { get; set; }
32+
}
33+
}

GuiStack/Pages/DynamoDB/Index.cshtml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ else
4545
<select class="partitionkey-type-select">
4646
@foreach(var type in Enum.GetValues<DynamoDBAttributeType>())
4747
{
48+
if(type == DynamoDBAttributeType.Unknown)
49+
continue;
50+
4851
<option value="@type.ToString()">@type.ToString()</option>
4952
}
5053
</select>
@@ -57,6 +60,9 @@ else
5760
<select class="sortkey-type-select">
5861
@foreach(var type in Enum.GetValues<DynamoDBAttributeType>())
5962
{
63+
if(type == DynamoDBAttributeType.Unknown)
64+
continue;
65+
6066
<option value="@type.ToString()">@type.ToString()</option>
6167
}
6268
</select>
@@ -136,7 +142,7 @@ else
136142
}
137143
else
138144
{
139-
await Html.RenderPartialAsync("~/Pages/DynamoDB/_TableInfo.cshtml", null);
145+
await Html.RenderPartialAsync("~/Pages/DynamoDB/_TableInfo.cshtml", await Model.DynamoDBRepository.GetTableInfoAsync(Model.Table));
140146
}
141147
}
142148
catch(AmazonDynamoDBException ex)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
@*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright © Vincent Bengtsson & Contributors 2022-2024
7+
* https://github.com/Visual-Vincent/GuiStack
8+
*@
9+
10+
@using GuiStack.Extensions;
11+
@using GuiStack.Models;
12+
@model DynamoDBTableModel
13+
14+
@{
15+
await Html.RenderPartialAsync("~/Pages/Shared/_DeleteModal.cshtml", new DeleteModalModel("delete-dynamodb-row-modal"));
16+
}
17+
18+
<table class="gs-info-table colored">
19+
<tbody>
20+
<tr>
21+
<td>Status</td>
22+
<td>@Model.Status</td>
23+
</tr>
24+
<tr>
25+
<td>ARN</td>
26+
<td>@Model.Arn</td>
27+
</tr>
28+
<tr>
29+
<td>Partition key</td>
30+
<td>@Model.PartitionKey.Name (@Model.PartitionKey.Type)</td>
31+
</tr>
32+
<tr>
33+
<td>Sort key</td>
34+
<td>
35+
@if(Model.SortKey != null)
36+
{
37+
<text>@Model.SortKey.Name (@Model.SortKey.Type)</text>
38+
}
39+
else
40+
{
41+
<text>(none)</text>
42+
}
43+
</td>
44+
</tr>
45+
<tr>
46+
<td>Item count</td>
47+
<td>@Model.ItemCount <span class="gs-object-type">(updated every ~6 hours)</span></td>
48+
</tr>
49+
<tr>
50+
<td>Table size</td>
51+
<td>@Model.TableSizeBytes.ToFormattedFileSize() <span class="gs-object-type">(updated every ~6 hours)</span></td>
52+
</tr>
53+
</tbody>
54+
<tbody class="additional-info">
55+
<tr>
56+
<td>Table class</td>
57+
<td>@Model.TableClass</td>
58+
</tr>
59+
<tr>
60+
<td>Capacity mode</td>
61+
<td>@Model.BillingMode</td>
62+
</tr>
63+
<tr>
64+
<td>Provisioned read capacity</td>
65+
<td>@(Model.ReadCapacityUnits > 0 ? $"{Model.ReadCapacityUnits} RCU" : "N/A")</td>
66+
</tr>
67+
<tr>
68+
<td>Provisioned write capacity</td>
69+
<td>@(Model.WriteCapacityUnits > 0 ? $"{Model.WriteCapacityUnits} WCU" : "N/A")</td>
70+
</tr>
71+
<tr>
72+
<td>Deletion protection</td>
73+
<td>@(Model.DeletionProtectionEnabled ? "Yes" : "No")</td>
74+
</tr>
75+
</tbody>
76+
<tbody>
77+
<tr class="expand-button text-center collapsed">
78+
<td colspan="2">
79+
<a no-href onclick="gsevent_InfoTable_ToggleButton_Click(event)">
80+
View more <i class="fa-solid fa-angles-down" style="font-size: 12px"></i>
81+
</a>
82+
</td>
83+
</tr>
84+
<tr class="expand-button text-center expanded">
85+
<td colspan="2">
86+
<a no-href onclick="gsevent_InfoTable_ToggleButton_Click(event)">
87+
View less <i class="fa-solid fa-angles-up" style="font-size: 12px"></i>
88+
</a>
89+
</td>
90+
</tr>
91+
</tbody>
92+
</table>
93+
94+
<hr/>
95+
96+
<table class="gs-list padded autosize-all-cols-but-2nd-last">
97+
<thead>
98+
<tr>
99+
@foreach(var column in Model.Attributes)
100+
{
101+
<th>@column.Name</th>
102+
}
103+
<th>(Actions)</th>
104+
</tr>
105+
</thead>
106+
<tbody>
107+
@* TODO *@
108+
</tbody>
109+
</table>

GuiStack/Repositories/IDynamoDBRepository.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
using System;
1111
using System.Collections.Generic;
12+
using System.Linq;
1213
using System.Threading.Tasks;
14+
using Amazon;
1315
using Amazon.DynamoDBv2;
1416
using Amazon.DynamoDBv2.Model;
1517
using GuiStack.Authentication.AWS;
@@ -23,6 +25,7 @@ public interface IDynamoDBRepository
2325
Task CreateTableAsync(DynamoDBCreateTableModel model);
2426
Task DeleteTableAsync(string tableName);
2527
Task<string[]> GetTablesAsync();
28+
Task<DynamoDBTableModel> GetTableInfoAsync(string tableName);
2629
}
2730

2831
public class DynamoDBRepository : IDynamoDBRepository
@@ -74,6 +77,47 @@ public async Task<string[]> GetTablesAsync()
7477
return response.TableNames.ToArray();
7578
}
7679

80+
public async Task<DynamoDBTableModel> GetTableInfoAsync(string tableName)
81+
{
82+
if(string.IsNullOrWhiteSpace(tableName))
83+
throw new ArgumentNullException(nameof(tableName));
84+
85+
using var dynamodb = authenticator.Authenticate();
86+
var response = await dynamodb.DescribeTableAsync(tableName);
87+
88+
response.ThrowIfUnsuccessful("DynamoDB");
89+
90+
var table = response.Table;
91+
var partitionKey = table.KeySchema.Find(k => k.KeyType == KeyType.HASH);
92+
var sortKey = table.KeySchema.Find(k => k.KeyType == KeyType.RANGE);
93+
94+
var partitionKeyAttribute = table.AttributeDefinitions.Find(k => k.AttributeName == partitionKey.AttributeName);
95+
var sortKeyAttribute = sortKey != null ? table.AttributeDefinitions.Find(k => k.AttributeName == sortKey.AttributeName) : null;
96+
97+
var status = table.TableStatus?.Value;
98+
99+
status = !string.IsNullOrEmpty(status)
100+
? char.ToUpper(status[0]) + status?.ToLower()[1..]
101+
: "(Unknown)";
102+
103+
return new DynamoDBTableModel() {
104+
Name = table.TableName,
105+
Arn = Arn.Parse(table.TableArn),
106+
ItemCount = table.ItemCount,
107+
TableSizeBytes = table.TableSizeBytes,
108+
TableClass = AWSExtensions.ToHumanReadableString(table.TableClassSummary?.TableClass),
109+
BillingMode = AWSExtensions.ToHumanReadableString(table.BillingModeSummary?.BillingMode),
110+
ReadCapacityUnits = table.ProvisionedThroughput?.ReadCapacityUnits ?? 0,
111+
WriteCapacityUnits = table.ProvisionedThroughput?.WriteCapacityUnits ?? 0,
112+
DeletionProtectionEnabled = table.DeletionProtectionEnabled,
113+
Status = status,
114+
115+
PartitionKey = new DynamoDBAttribute(partitionKeyAttribute.AttributeName, partitionKeyAttribute.AttributeType.ToDynamoDBAttributeType()),
116+
SortKey = sortKeyAttribute != null ? new DynamoDBAttribute(sortKeyAttribute.AttributeName, sortKeyAttribute.AttributeType.ToDynamoDBAttributeType()) : null,
117+
Attributes = table.AttributeDefinitions.Select(a => new DynamoDBAttribute(a.AttributeName, a.AttributeType.ToDynamoDBAttributeType())).ToList()
118+
};
119+
}
120+
77121
public async Task DeleteTableAsync(string tableName)
78122
{
79123
if(string.IsNullOrWhiteSpace(tableName))

GuiStack/wwwroot/css/site.css

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -382,17 +382,18 @@ table.layout-fixed
382382
table-layout: fixed;
383383
}
384384

385-
table.autosize-col-1 tr > td:nth-child(1),
386-
table.autosize-col-2 tr > td:nth-child(2),
387-
table.autosize-col-3 tr > td:nth-child(3),
388-
table.autosize-col-4 tr > td:nth-child(4),
389-
table.autosize-col-5 tr > td:nth-child(5),
390-
table.autosize-col-6 tr > td:nth-child(6),
391-
table.autosize-col-7 tr > td:nth-child(7),
392-
table.autosize-col-8 tr > td:nth-child(8),
393-
table.autosize-col-9 tr > td:nth-child(9),
394-
table.autosize-last-col tr > td:last-child,
395-
table.autosize-all-cols-but-first tr > td:not(:first-child)
385+
table.autosize-col-1 tr > td:nth-child(1),
386+
table.autosize-col-2 tr > td:nth-child(2),
387+
table.autosize-col-3 tr > td:nth-child(3),
388+
table.autosize-col-4 tr > td:nth-child(4),
389+
table.autosize-col-5 tr > td:nth-child(5),
390+
table.autosize-col-6 tr > td:nth-child(6),
391+
table.autosize-col-7 tr > td:nth-child(7),
392+
table.autosize-col-8 tr > td:nth-child(8),
393+
table.autosize-col-9 tr > td:nth-child(9),
394+
table.autosize-last-col tr > td:last-child,
395+
table.autosize-all-cols-but-first tr > td:not(:first-child),
396+
table.autosize-all-cols-but-2nd-last tr > td:not(:nth-last-child(2))
396397
{
397398
width: 1px;
398399
white-space: nowrap;

0 commit comments

Comments
 (0)