Files
LMEMappeCatastaliApp/tools/LmeLicenseManager/MainForm.cs
T

394 lines
16 KiB
C#

using System.Diagnostics;
namespace LmeLicenseManager;
internal sealed class MainForm : Form
{
private readonly LicenseRepository _repository;
private readonly TextBox _firstName = new();
private readonly TextBox _lastName = new();
private readonly TextBox _email = new();
private readonly DateTimePicker _expiresAt = new();
private readonly TextBox _notes = new();
private readonly TextBox _licenseCode = new();
private readonly DataGridView _grid = new();
private readonly Label _status = new();
private IReadOnlyList<IssuedLicense> _licenses = [];
public MainForm(LicenseRepository repository)
{
_repository = repository;
Text = "LME Pro - Gestore licenze manuali";
MinimumSize = new Size(980, 720);
StartPosition = FormStartPosition.CenterScreen;
Font = new Font("Segoe UI", 9F);
var root = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 5,
Padding = new Padding(18)
};
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
Controls.Add(root);
root.Controls.Add(BuildHeader(), 0, 0);
root.Controls.Add(BuildIssueForm(), 0, 1);
root.Controls.Add(BuildCodePanel(), 0, 2);
root.Controls.Add(BuildGrid(), 0, 3);
_status.AutoSize = true;
_status.Padding = new Padding(0, 8, 0, 0);
_status.ForeColor = Color.FromArgb(60, 77, 88);
root.Controls.Add(_status, 0, 4);
RefreshLicenses();
}
private Control BuildHeader()
{
var panel = new TableLayoutPanel { Dock = DockStyle.Top, AutoSize = true, ColumnCount = 2 };
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
var text = new Label
{
AutoSize = true,
Text = "Licenze manuali LME Pro\r\nEmetti codici cifrati e conserva titolari e scadenze nel registro locale.",
Font = new Font("Segoe UI", 12F, FontStyle.Bold),
Padding = new Padding(0, 0, 0, 14)
};
panel.Controls.Add(text, 0, 0);
var commands = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight, Margin = new Padding(8, 0, 0, 12) };
var openFolder = new Button { Text = "Apri archivio", AutoSize = true };
openFolder.Click += (_, _) => Process.Start(new ProcessStartInfo("explorer.exe", _repository.StoragePath) { UseShellExecute = true });
var exportKey = new Button { Text = "Backup chiave", AutoSize = true };
exportKey.Click += (_, _) => ExportSigningKey();
var importKey = new Button { Text = "Ripristina chiave", AutoSize = true };
importKey.Click += (_, _) => ImportSigningKey();
commands.Controls.Add(openFolder);
commands.Controls.Add(exportKey);
commands.Controls.Add(importKey);
panel.Controls.Add(commands, 1, 0);
return panel;
}
private Control BuildIssueForm()
{
var group = new GroupBox { Text = "Nuova licenza", Dock = DockStyle.Top, AutoSize = true, Padding = new Padding(12) };
var form = new TableLayoutPanel { Dock = DockStyle.Top, AutoSize = true, ColumnCount = 4, RowCount = 3 };
form.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25));
form.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25));
form.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 30));
form.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20));
AddLabeledControl(form, "Nome", _firstName, 0, 0);
AddLabeledControl(form, "Cognome", _lastName, 1, 0);
AddLabeledControl(form, "Email", _email, 2, 0);
_expiresAt.Format = DateTimePickerFormat.Custom;
_expiresAt.CustomFormat = "dd/MM/yyyy HH:mm";
_expiresAt.Value = DateTime.Now.AddYears(1);
AddLabeledControl(form, "Scadenza", _expiresAt, 3, 0);
_notes.PlaceholderText = "Riferimento fattura, cliente, note interne...";
AddLabeledControl(form, "Note", _notes, 0, 1, 3);
var issue = new Button
{
Text = "Emetti licenza",
Dock = DockStyle.Fill,
Height = 36,
Margin = new Padding(6, 23, 3, 4),
BackColor = Color.FromArgb(7, 94, 84),
ForeColor = Color.White,
FlatStyle = FlatStyle.Flat
};
issue.Click += (_, _) => IssueLicense();
form.Controls.Add(issue, 3, 1);
var note = new Label
{
AutoSize = true,
Text = "La revoca registrata qui non disattiva un codice già consegnato e offline: la disattivazione effettiva avviene alla scadenza.",
ForeColor = Color.FromArgb(90, 103, 111),
Padding = new Padding(3, 8, 3, 2)
};
form.Controls.Add(note, 0, 2);
form.SetColumnSpan(note, 4);
group.Controls.Add(form);
return group;
}
private Control BuildCodePanel()
{
var group = new GroupBox { Text = "Codice da consegnare", Dock = DockStyle.Top, Height = 122, Padding = new Padding(12) };
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2 };
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
_licenseCode.Multiline = true;
_licenseCode.ReadOnly = true;
_licenseCode.ScrollBars = ScrollBars.Vertical;
_licenseCode.Dock = DockStyle.Fill;
_licenseCode.Font = new Font("Consolas", 8.5F);
layout.Controls.Add(_licenseCode, 0, 0);
var copy = new Button { Text = "Copia codice", AutoSize = true, Dock = DockStyle.Top, Margin = new Padding(8, 0, 0, 0) };
copy.Click += (_, _) => CopyCurrentCode();
layout.Controls.Add(copy, 1, 0);
group.Controls.Add(layout);
return group;
}
private Control BuildGrid()
{
var panel = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 2, Padding = new Padding(0, 12, 0, 0) };
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
var commands = new FlowLayoutPanel { AutoSize = true, Dock = DockStyle.Top, FlowDirection = FlowDirection.LeftToRight };
var refresh = new Button { Text = "Aggiorna registro", AutoSize = true };
refresh.Click += (_, _) => RefreshLicenses();
var revoke = new Button { Text = "Segna revocata", AutoSize = true };
revoke.Click += (_, _) => RevokeSelected();
commands.Controls.Add(refresh);
commands.Controls.Add(revoke);
panel.Controls.Add(commands, 0, 0);
_grid.Dock = DockStyle.Fill;
_grid.ReadOnly = true;
_grid.AllowUserToAddRows = false;
_grid.AllowUserToDeleteRows = false;
_grid.AutoGenerateColumns = true;
_grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
_grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
_grid.MultiSelect = false;
_grid.SelectionChanged += (_, _) => ShowSelectedCode();
panel.Controls.Add(_grid, 0, 1);
return panel;
}
private void IssueLicense()
{
try
{
var expiry = new DateTimeOffset(_expiresAt.Value);
var license = _repository.Issue(_firstName.Text, _lastName.Text, _email.Text, expiry, _notes.Text);
_licenseCode.Text = license.Token;
Clipboard.SetText(license.Token);
_status.Text = $"Licenza {ShortId(license.LicenseId)} emessa e copiata negli appunti. Scadenza: {license.ExpiresAtUtc.ToLocalTime():dd/MM/yyyy HH:mm}.";
RefreshLicenses(license.LicenseId);
}
catch (Exception error)
{
MessageBox.Show(this, error.GetBaseException().Message, "Licenza non emessa", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void RefreshLicenses(string? selectLicenseId = null)
{
_licenses = _repository.GetAll();
_grid.DataSource = _licenses.Select(item => new
{
Id = ShortId(item.LicenseId),
item.Name,
item.Email,
Emessa = item.IssuedAtUtc.ToLocalTime().ToString("dd/MM/yyyy HH:mm"),
Scadenza = item.ExpiresAtUtc.ToLocalTime().ToString("dd/MM/yyyy HH:mm"),
item.Status,
item.Notes,
LicenseId = item.LicenseId
}).ToList();
if (selectLicenseId is not null)
{
foreach (DataGridViewRow row in _grid.Rows)
{
if (Equals(row.Cells["LicenseId"].Value, selectLicenseId))
{
row.Selected = true;
_grid.CurrentCell = row.Cells[0];
break;
}
}
}
if (_grid.Columns.Contains("LicenseId"))
{
_grid.Columns["LicenseId"]!.Visible = false;
}
_status.Text = $"{_licenses.Count} licenze registrate. Archivio: {_repository.StoragePath}";
}
private void ShowSelectedCode()
{
var selected = GetSelectedLicense();
if (selected is not null)
{
_licenseCode.Text = selected.Token;
}
}
private void CopyCurrentCode()
{
if (!string.IsNullOrWhiteSpace(_licenseCode.Text))
{
Clipboard.SetText(_licenseCode.Text);
_status.Text = "Codice licenza copiato negli appunti.";
}
}
private void RevokeSelected()
{
var selected = GetSelectedLicense();
if (selected is null || selected.RevokedAtUtc is not null)
{
return;
}
var answer = MessageBox.Show(
this,
"La revoca verrà annotata nel registro, ma un'app offline continuerà a riconoscere il codice fino alla scadenza. Continuare?",
"Segna licenza revocata",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (answer != DialogResult.Yes)
{
return;
}
_repository.MarkRevoked(selected.LicenseId);
RefreshLicenses(selected.LicenseId);
}
private void ExportSigningKey()
{
var password = PromptPassword("Password backup", "Scegli una password di almeno 12 caratteri per cifrare la chiave.", true);
if (password is null) return;
using var dialog = new SaveFileDialog
{
Title = "Salva backup della chiave licenze",
Filter = "Backup chiave LME (*.lmekey)|*.lmekey",
FileName = $"LME-Pro-licenze-{DateTime.Now:yyyyMMdd}.lmekey",
AddExtension = true
};
if (dialog.ShowDialog(this) != DialogResult.OK) return;
try
{
_repository.ExportSigningKeyBackup(dialog.FileName, password);
_status.Text = "Backup cifrato della chiave creato. Conserva file e password separatamente.";
}
catch (Exception error)
{
MessageBox.Show(this, error.GetBaseException().Message, "Backup non creato", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
private void ImportSigningKey()
{
using var dialog = new OpenFileDialog
{
Title = "Apri backup della chiave licenze",
Filter = "Backup chiave LME (*.lmekey)|*.lmekey"
};
if (dialog.ShowDialog(this) != DialogResult.OK) return;
var password = PromptPassword("Password backup", "Inserisci la password usata per cifrare questa chiave.", false);
if (password is null) return;
var answer = MessageBox.Show(
this,
"La chiave di emissione corrente verrà sostituita. Importa soltanto il backup associato a questa versione dell'app.",
"Ripristina chiave licenze",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (answer != DialogResult.Yes) return;
try
{
_repository.ImportSigningKeyBackup(dialog.FileName, password);
_status.Text = "Chiave di emissione ripristinata dal backup.";
}
catch (Exception error)
{
MessageBox.Show(this, error.GetBaseException().Message, "Chiave non importata", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private string? PromptPassword(string title, string message, bool confirm)
{
using var dialog = new Form
{
Text = title,
Width = 470,
Height = confirm ? 245 : 205,
StartPosition = FormStartPosition.CenterParent,
FormBorderStyle = FormBorderStyle.FixedDialog,
MaximizeBox = false,
MinimizeBox = false
};
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(14), RowCount = confirm ? 5 : 4, ColumnCount = 1 };
var explanation = new Label { Text = message, AutoSize = true, MaximumSize = new Size(420, 0) };
var password = new TextBox { UseSystemPasswordChar = true, Dock = DockStyle.Top };
var confirmation = new TextBox { UseSystemPasswordChar = true, Dock = DockStyle.Top, Visible = confirm };
var buttons = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Fill, AutoSize = true };
var ok = new Button { Text = "OK", DialogResult = DialogResult.OK, AutoSize = true };
var cancel = new Button { Text = "Annulla", DialogResult = DialogResult.Cancel, AutoSize = true };
buttons.Controls.Add(ok);
buttons.Controls.Add(cancel);
layout.Controls.Add(explanation);
layout.Controls.Add(new Label { Text = "Password", AutoSize = true });
layout.Controls.Add(password);
if (confirm)
{
var confirmationPanel = new TableLayoutPanel { Dock = DockStyle.Top, AutoSize = true, RowCount = 2 };
confirmationPanel.Controls.Add(new Label { Text = "Conferma password", AutoSize = true });
confirmationPanel.Controls.Add(confirmation);
layout.Controls.Add(confirmationPanel);
}
layout.Controls.Add(buttons);
dialog.Controls.Add(layout);
dialog.AcceptButton = ok;
dialog.CancelButton = cancel;
if (dialog.ShowDialog(this) != DialogResult.OK) return null;
if (confirm && password.Text != confirmation.Text)
{
MessageBox.Show(this, "Le password non coincidono.", title, MessageBoxButtons.OK, MessageBoxIcon.Warning);
return null;
}
return password.Text;
}
private IssuedLicense? GetSelectedLicense()
{
if (_grid.CurrentRow?.Cells["LicenseId"].Value is not string id)
{
return null;
}
return _licenses.FirstOrDefault(item => item.LicenseId == id);
}
private static void AddLabeledControl(TableLayoutPanel form, string labelText, Control control, int column, int row, int columnSpan = 1)
{
var panel = new TableLayoutPanel { Dock = DockStyle.Fill, AutoSize = true, RowCount = 2, Margin = new Padding(3) };
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.Controls.Add(new Label { Text = labelText, AutoSize = true }, 0, 0);
control.Dock = DockStyle.Top;
control.Margin = new Padding(0, 4, 0, 0);
panel.Controls.Add(control, 0, 1);
form.Controls.Add(panel, column, row);
if (columnSpan > 1)
{
form.SetColumnSpan(panel, columnSpan);
}
}
private static string ShortId(string id) => id.Length > 8 ? id[..8].ToUpperInvariant() : id;
}