Files
DorkNet/launcher/Backend/StartupStep.cs
T
Alexa f5ad4b59ef Localtunnel hosting + admin panel + AppData db
- Default hosting mode is now Localtunnel — public *.loca.lt URL,
  no account, auto-fetched on first run. Replaces the prototyped
  Tailscale mode.
- Tailscale code purged from launcher + CLI; docs swapped to
  Localtunnel throughout (easy-setup, joining-a-server, server-cli,
  troubleshooting, faq, READMEs).
- Launcher sidebar gains an Open admin panel button — opens
  https://admin.<apex> in the browser once the server is live.
- SQLite db moves to %APPDATA%\DorkNet\dorknet.db (server reads
  Database__SqlitePath env var, launcher passes it). Existing dbs
  under <serverDir>\data\ are migrated on first run.
- CLI Program.cs: --mode default flips from tailscale to localtunnel;
  csproj links in LocaltunnelTunnel + LocaltunnelInstaller.
- New Backend files: LauncherNameServer, LocaltunnelInstaller,
  LocaltunnelTunnel, SingleOriginServiceMap.
- .gitignore: exclude installer/out/.
2026-05-28 01:39:38 +03:00

101 lines
3.8 KiB
C#

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DorkNet.Launcher.Backend;
/// <summary>A single visible step in the host/join startup flow. The
/// XAML <c>ItemsControl</c> binds to a collection of these and the
/// DataTemplate switches presentation off <see cref="State"/>.
///
/// <para>This is a tiny INotifyPropertyChanged DTO on purpose — no MVVM
/// framework, no commands, no view-model layer. Code-behind owns the
/// collection and mutates fields as the flow progresses.</para></summary>
public sealed class StartupStep : INotifyPropertyChanged
{
public StartupStep(string title) { Title = title; }
public string Title { get; }
private StepState _state = StepState.Pending;
public StepState State
{
get => _state;
set { if (_state == value) return; _state = value; OnChanged(); OnChanged(nameof(IsActive)); }
}
private string _detail = "";
public string Detail
{
get => _detail;
set { if (_detail == value) return; _detail = value; OnChanged(); OnChanged(nameof(HasDetail)); }
}
/// <summary>0..1 progress fraction, or null for "no progress bar"
/// (e.g. an indeterminate phase like "starting server"). The
/// DataTemplate hides the bar when null.</summary>
private double? _progress;
public double? Progress
{
get => _progress;
set { if (_progress == value) return; _progress = value; OnChanged(); OnChanged(nameof(HasProgress)); OnChanged(nameof(ProgressPercent)); }
}
public bool HasDetail => !string.IsNullOrEmpty(_detail);
public bool HasProgress => _progress.HasValue;
public bool IsActive => _state == StepState.Active;
/// <summary>0..100 for the ProgressBar Value binding.</summary>
public double ProgressPercent => (_progress ?? 0) * 100;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name!));
}
public enum StepState
{
/// <summary>Hasn't started yet. Dim icon, no detail.</summary>
Pending,
/// <summary>In progress right now. Brand-coloured icon, detail +
/// optional progress bar visible.</summary>
Active,
/// <summary>Finished successfully. Green checkmark.</summary>
Done,
/// <summary>Failed — error in <see cref="StartupStep.Detail"/>. Red
/// X, the rest of the flow won't proceed.</summary>
Failed,
}
/// <summary>Helpers for the two known flows. Centralising the step
/// names here keeps the XAML free of hardcoded strings and makes it
/// obvious what stages a user will see.</summary>
public static class StartupFlow
{
public static ObservableCollection<StartupStep> NewHostFlow(HostingMode mode) => new()
{
new StartupStep("Download server binary"),
new StartupStep(mode switch
{
HostingMode.SingleOriginTunnel => "Start localtunnel",
HostingMode.LocalNetwork => "Set up local sslip.io address",
HostingMode.RemoteWildcard => "Install / start Tunnelto tunnel",
// Internet maps onto SingleOriginTunnel via AppState.Load
// migration; keep the arm in case a stale value sneaks in.
HostingMode.Internet => "Start localtunnel",
_ => "Start localtunnel",
}),
new StartupStep("Start the server"),
new StartupStep("Download client patcher"),
new StartupStep("Unlock Rec Room from Steam (if needed)"),
new StartupStep("Patch your Rec Room install"),
};
public static ObservableCollection<StartupStep> NewJoinFlow() => new()
{
new StartupStep("Download client patcher"),
new StartupStep("Unlock Rec Room from Steam (if needed)"),
new StartupStep("Patch your Rec Room install"),
};
}