Show startup as one step checklist and re-check tools on the Server tab
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import SwiftUI
|
||||
#if os(iOS)
|
||||
import SafariServices
|
||||
#endif
|
||||
|
||||
struct SetupStep: Identifiable {
|
||||
enum Status {
|
||||
case done, active, todo
|
||||
}
|
||||
|
||||
let name: String
|
||||
var status: Status
|
||||
var detail: String?
|
||||
var signIn = false
|
||||
var signInURL: URL?
|
||||
|
||||
var id: String {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
func signInStep(_ name: String, hostname: String, state: String, authURL: URL?) -> SetupStep {
|
||||
if state == "Running" {
|
||||
return SetupStep(name: name, status: .done)
|
||||
}
|
||||
if state == "NeedsMachineAuth" {
|
||||
return SetupStep(name: name, status: .active, detail: "\(hostname) is waiting for a tailnet admin to approve it")
|
||||
}
|
||||
if state == "NeedsLogin" {
|
||||
return SetupStep(name: name, status: .active, detail: authURL == nil ? "Waiting for Tailscale's sign-in link…" : "\(hostname) is waiting for you", signIn: true, signInURL: authURL)
|
||||
}
|
||||
return SetupStep(name: name, status: .active, detail: "Waiting for Tailscale (\(state))…")
|
||||
}
|
||||
|
||||
struct StepsCard: View {
|
||||
let title: String
|
||||
let detail: String
|
||||
let steps: [SetupStep]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(title)
|
||||
.font(Fonts.font(17, weight: 700))
|
||||
Text(detail)
|
||||
.foregroundStyle(Palette.muted)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, 10)
|
||||
ForEach(Array(steps.enumerated()), id: \.element.id) { i, step in
|
||||
if i > 0 {
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
}
|
||||
StepRow(step: step)
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Palette.raised, in: RoundedRectangle(cornerRadius: 10))
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Palette.border))
|
||||
.frame(maxWidth: 460)
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
#if os(macOS)
|
||||
.onAppear {
|
||||
Tailnet.shared.serviceWatchers += 1
|
||||
}
|
||||
.onDisappear {
|
||||
Tailnet.shared.serviceWatchers -= 1
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private struct StepRow: View {
|
||||
let step: SetupStep
|
||||
@State private var signingIn = false
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
icon
|
||||
.frame(width: 18, height: 18)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(step.name)
|
||||
.foregroundStyle(step.status == .todo ? Palette.muted : Palette.text)
|
||||
if let detail = step.detail {
|
||||
Text(detail)
|
||||
.font(Fonts.font(12))
|
||||
.foregroundStyle(Palette.muted)
|
||||
.textSelection(.enabled)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
if step.signIn {
|
||||
Button("Sign in") {
|
||||
#if os(macOS)
|
||||
if let url = step.signInURL {
|
||||
openExternal(url)
|
||||
}
|
||||
#else
|
||||
signingIn = true
|
||||
#endif
|
||||
}
|
||||
.buttonStyle(WebButtonStyle(primary: true))
|
||||
.disabled(step.signInURL == nil)
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
#if os(iOS)
|
||||
.sheet(isPresented: $signingIn) {
|
||||
if let url = step.signInURL {
|
||||
SafariView(url: url)
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
.onChange(of: step.signIn) {
|
||||
if !step.signIn {
|
||||
signingIn = false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var icon: some View {
|
||||
switch step.status {
|
||||
case .done:
|
||||
Image(systemName: "checkmark")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.foregroundStyle(Palette.ok)
|
||||
case .active:
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
case .todo:
|
||||
Image(systemName: "circle")
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Palette.muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private struct SafariView: UIViewControllerRepresentable {
|
||||
let url: URL
|
||||
|
||||
func makeUIViewController(context: Context) -> SFSafariViewController {
|
||||
SFSafariViewController(url: url)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ controller: SFSafariViewController, context: Context) {}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user