Eerste commit

This commit is contained in:
jf
2026-09-27 14:21:31 +00:00
commit de4ff128cb
295 changed files with 47374 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
<Application x:Class="MijnBesluit.Agentvenster.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Hoofdvenster.xaml">
</Application>
+7
View File
@@ -0,0 +1,7 @@
using System.Windows;
namespace MijnBesluit.Agentvenster;
public partial class App : Application
{
}
@@ -0,0 +1,48 @@
<Window x:Class="MijnBesluit.Agentvenster.Hoofdvenster"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MijnBesluit uitrolagent" Height="620" Width="860"
Icon="agentvenster.ico"
WindowStartupLocation="CenterScreen" Background="#FAF7F2"
Closing="Venster_Closing">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="0,0,0,12">
<TextBlock Text="MijnBesluit uitrolagent" FontSize="22" FontWeight="Bold" Foreground="#2A2724" />
<TextBlock x:Name="Doelregel" Foreground="#6B645C" Margin="0,4,0,0" TextWrapping="Wrap" />
</StackPanel>
<!-- Eén regel die van een afstand leesbaar is. Dit venster staat op een server waar je
niet naar zit te kijken; als je er langsloopt wil je in één oogopslag weten of hij
staat te wachten of bezig is. -->
<Border Grid.Row="1" x:Name="Standvak" Background="#EEF2DA" BorderBrush="#C1D13F"
BorderThickness="1" CornerRadius="6" Padding="12,10" Margin="0,0,0,12">
<TextBlock x:Name="Standregel" FontSize="15" FontWeight="Bold" Foreground="#3A3E1E"
Text="Bezig met opstarten…" TextWrapping="Wrap" />
</Border>
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,0,0,12">
<Button x:Name="KnopWacht" Content="Stoppen met wachten" Padding="14,7"
Click="KnopWacht_Click" />
<Button x:Name="KnopLeeg" Content="Logboek leegmaken" Padding="14,7" Margin="10,0,0,0"
Click="KnopLeeg_Click" />
</StackPanel>
<Border Grid.Row="3" BorderBrush="#E6E0D7" BorderThickness="1" Background="White" CornerRadius="6">
<TextBox x:Name="Logvenster" IsReadOnly="True" BorderThickness="0" Padding="10"
FontFamily="Consolas" FontSize="12"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
TextWrapping="Wrap" AcceptsReturn="True" />
</Border>
<ProgressBar Grid.Row="4" x:Name="Balk" Height="10" Margin="0,12,0,0" />
</Grid>
</Window>
@@ -0,0 +1,230 @@
using System.IO;
using System.Windows;
using System.Windows.Media;
using MijnBesluit.Uitrolagent;
namespace MijnBesluit.Agentvenster;
/// <summary>
/// Het venster op de SERVER. Eén ding: staan wachten tot er vanaf de ontwikkelmachine een
/// opdracht binnenkomt, en laten zien wat er dan gebeurt.
///
/// Waarom dit er is: het wachten kon al met MijnBesluit.Uitrolagent.exe --wacht, maar dan zit je
/// met een consolevenster dat je eerst moet openen en dat blijft staan tot je het wegklikt. Op
/// een server die je via een externe-bureaubladverbinding bedient is dat net het soort venster
/// dat je per ongeluk sluit of niet terugvindt. Dit is hetzelfde, maar zichtbaar: je
/// dubbelklikt, hij staat te wachten, en je ziet de uitrol regel voor regel voorbijkomen -
/// precies zoals het uitrolvenster dat op je eigen pc doet.
///
/// Er wordt hier niets nagebouwd. De lus is dezelfde <see cref="Wachtstand"/> als in de console;
/// dit venster geeft hem alleen een andere plek om zijn regels naartoe te schrijven.
/// </summary>
public partial class Hoofdvenster : Window
{
private readonly Agentinstellingen? _instellingen;
private CancellationTokenSource? _stopper;
private Task? _lus;
private bool _bezig;
/// <summary>
/// Zoveel regels houden we vast. Dit venster kan dagen open blijven staan, en een tekstvak
/// dat almaar aangroeit wordt op een gegeven moment traag - juist op het moment dat je erin
/// wilt kijken.
/// </summary>
private const int MaximaalRegels = 4000;
public Hoofdvenster()
{
InitializeComponent();
var pad = Path.Combine(AppContext.BaseDirectory, "uitrolagent.json");
Agentinstellingen gelezen;
try
{
gelezen = Agentinstellingen.Lees(pad, Meld);
}
catch (Exception ex)
{
Doelregel.Text = pad;
Stand("Het instellingenbestand is niet te lezen.", true);
Meld($"{pad} kon niet worden gelezen: {ex.Message}", true);
Meld("Herstel het bestand en start dit venster opnieuw.", false);
KnopWacht.IsEnabled = false;
return;
}
if (!File.Exists(pad))
{
// Niet blokkeren: zonder bestand gelden de standaardwaarden, en die kloppen op een
// gewone installatie vaak gewoon. Maar wél zeggen, want als de paden er straks naast
// zitten is dít de reden.
Meld($"Let op: {pad} staat er niet. Ik gebruik de standaardinstellingen.", true);
}
_instellingen = gelezen;
// Hardop zeggen waar hij zijn instellingen vandaan haalt. Publiceren zet uitrolagent.json
// terug op de versie uit de projectmap; wat je niet kwijt wilt, hoort in het geheimenbestand
// dat daar per sleutel overheen gaat. Die twee regels hieronder zijn het verschil tussen
// "hij pakt mijn wachtwoord niet" en zien waaróm.
Meld($"Instellingen: {pad}", false);
if (!string.IsNullOrWhiteSpace(gelezen.ExtraConfigFile))
Meld(File.Exists(gelezen.ExtraConfigFile)
? $"Aangevuld uit {gelezen.ExtraConfigFile}."
: $"Er is geen {gelezen.ExtraConfigFile}; alles komt uit het bestand hierboven.",
false);
Doelregel.Text = $"Kijkt in: {gelezen.Opdrachtmap} • Scripts: {gelezen.Scriptmap}";
if (!Directory.Exists(gelezen.Scriptmap))
Meld($"Let op: de scriptmap {gelezen.Scriptmap} bestaat niet. " +
"Een uitrol zal daarop stuklopen.", true);
Loaded += (_, _) => Beginnen();
}
// ------------------------------------------------------------------ de lus
/// <summary>
/// Begint met wachten. Gebeurt vanzelf bij het openen: je start dit venster omdat je gaat
/// uitrollen, dus nog een knop indrukken voegt niets toe.
/// </summary>
private void Beginnen()
{
if (_instellingen is null || _lus is not null) return;
_stopper = new CancellationTokenSource();
var teken = _stopper.Token;
KnopWacht.Content = "Stoppen met wachten";
Stand("Wacht op een opdracht van je pc.", false);
var wachtstand = new Wachtstand(_instellingen, Meld, Bezig);
// Op een eigen draad, zodat het venster blijft reageren terwijl de lus draait en een
// uitrol een half uur kan duren.
_lus = Task.Run(async () =>
{
try { await wachtstand.LoopAsync(teken); }
catch (OperationCanceledException) { }
catch (Exception ex) { Meld($"De agent is gestopt: {ex.Message}", true); }
}, teken);
_ = _lus.ContinueWith(_ => Dispatcher.Invoke(() =>
{
_lus = null;
Bezig(false);
KnopWacht.Content = "Wachten";
Stand("Staat stil. Er wordt niets opgepakt.", true);
}), TaskScheduler.Default);
}
private void Stoppen()
{
if (_lus is null) return;
Meld("Stoppen gevraagd.", false);
_stopper?.Cancel();
}
// ------------------------------------------------------------------ knoppen
private void KnopWacht_Click(object sender, RoutedEventArgs e)
{
if (_lus is null) Beginnen();
else Stoppen();
}
private void KnopLeeg_Click(object sender, RoutedEventArgs e)
{
Logvenster.Clear();
}
/// <summary>
/// Sluiten terwijl er een uitrol loopt is het ene geval waarin dit venster tegenspreekt. Een
/// halve uitrol laat een site achter die niet meer bij zijn database past, en dat merk je
/// pas als er iemand op de website zit.
/// </summary>
private void Venster_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (_bezig)
{
var antwoord = MessageBox.Show(
"Er loopt op dit moment een uitrol.\n\n" +
"Dit venster sluiten breekt hem af, en dan staat er straks misschien een halve " +
"versie op deze server.\n\nToch sluiten?",
"Er is nog iets bezig", MessageBoxButton.YesNo, MessageBoxImage.Warning,
MessageBoxResult.No);
if (antwoord != MessageBoxResult.Yes)
{
e.Cancel = true;
return;
}
}
_stopper?.Cancel();
}
// ------------------------------------------------------------------ meekijken
/// <summary>
/// Waar elke regel van de wachtstand binnenkomt. Komt van een andere draad, dus eerst terug
/// naar die van het venster.
/// </summary>
private void Meld(string tekst, bool isFout)
{
if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => Meld(tekst, isFout)); return; }
// Een lege regel krijgt geen tijdstempel. De PowerShell-scripts gebruiken witregels om hun
// uitvoer leesbaar te houden; met een klokje ervoor worden dat juist losse regels ruis.
Logvenster.AppendText(string.IsNullOrWhiteSpace(tekst)
? Environment.NewLine
: $"{DateTime.Now:HH:mm:ss} {(isFout ? "! " : " ")}{tekst}{Environment.NewLine}");
Inkorten();
Logvenster.ScrollToEnd();
}
/// <summary>Gooit de oudste regels weg als het er te veel worden. Zie MaximaalRegels.</summary>
private void Inkorten()
{
if (Logvenster.LineCount <= MaximaalRegels) return;
var vanaf = Logvenster.GetCharacterIndexFromLineIndex(Logvenster.LineCount - MaximaalRegels / 2);
if (vanaf > 0) Logvenster.Text = Logvenster.Text[vanaf..];
}
/// <summary>
/// Aan zodra er een opdracht wordt uitgevoerd, uit als hij klaar is. De balk loopt zolang er
/// iets gebeurt: uitrollen duurt minuten, en zonder teken van leven ga je je afvragen of hij
/// nog wel iets doet.
/// </summary>
private void Bezig(bool bezig)
{
if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => Bezig(bezig)); return; }
_bezig = bezig;
Balk.IsIndeterminate = bezig;
if (bezig) Stand("Bezig met uitrollen. Laat dit venster staan.", false);
else if (_lus is not null) Stand($"Wacht op een opdracht van je pc. " +
$"Laatste uitrol: {DateTime.Now:HH:mm:ss}.", false);
}
private void Stand(string tekst, bool isWaarschuwing)
{
if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(() => Stand(tekst, isWaarschuwing)); return; }
Standregel.Text = tekst;
Standvak.Background = isWaarschuwing ? new SolidColorBrush(Color.FromRgb(0xFA, 0xEF, 0xE2))
: new SolidColorBrush(Color.FromRgb(0xEE, 0xF2, 0xDA));
Standvak.BorderBrush = isWaarschuwing ? new SolidColorBrush(Color.FromRgb(0xD9, 0xA3, 0x4A))
: new SolidColorBrush(Color.FromRgb(0xC1, 0xD1, 0x3F));
Standregel.Foreground = isWaarschuwing ? new SolidColorBrush(Color.FromRgb(0x6B, 0x45, 0x12))
: new SolidColorBrush(Color.FromRgb(0x3A, 0x3E, 0x1E));
}
}
@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Het agentvenster: hetzelfde wachten als de wachtstand van MijnBesluit.Uitrolagent.exe,
maar met een gezicht. Bedoeld voor de SERVER; het uitrolvenster staat op de
ontwikkelmachine.
Waarom een apart project en niet een venster in de agent zelf: die agent is een
consoleprogramma en moet dat blijven. Zet je daar WPF in, dan wordt het een WinExe zonder
console, en dan werken zijn schakelaars voor zip, luisteren en een nieuwe sleutel alleen
nog met kunstgrepen (AttachConsole en aanverwante ellende - zie de SocketServer). Zo staat
het los: de agent doet het werk, dit project kijkt mee.
Let op bij het aanpassen van deze uitleg: twee streepjes achter elkaar mogen niet in een
XML-commentaar. Schrijf schakelaars dus uit in woorden. Zelfcontrole "Geen projectbestand
met een onmogelijk commentaar" bewaakt dat. -->
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>MijnBesluit.Agentvenster</AssemblyName>
<RootNamespace>MijnBesluit.Agentvenster</RootNamespace>
<PlatformTarget>x64</PlatformTarget>
<ApplicationIcon>agentvenster.ico</ApplicationIcon>
<!-- Directory.Build.props zet InvariantGlobalization voor de hele solution aan. WPF kan daar
niet tegen: zonder ICU valt het venster om bij de eerste letter die het wil tekenen, en
omdat het een WinExe is zie je daar niets van - je dubbelklikt en er gebeurt niets.
Zie de gelijkluidende regel in MijnBesluit.Uitroller.csproj. -->
<InvariantGlobalization>false</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<!-- De agent zelf. Een verwijzing naar een Exe-project is gewoon toegestaan: .NET levert
daarvan ook een dll op. Zo draait hier dezelfde Wachtstand als in de console - niet een
tweede uitvoering die na een half jaar net iets anders doet. -->
<ProjectReference Include="..\MijnBesluit.Uitrolagent\MijnBesluit.Uitrolagent.csproj" />
</ItemGroup>
<ItemGroup>
<!-- Ook als Resource, zodat het venster hem in zijn eigen titelbalk kan zetten. -->
<Resource Include="agentvenster.ico" />
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB