Eerste commit
This commit is contained in:
@@ -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>
|
||||
@@ -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 |
Reference in New Issue
Block a user