394 lines
16 KiB
C#
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;
|
|
}
|