There was no way to change an operator password anywhere, and no way at all to help somebody who had forgotten theirs. On a server with no mail to send a link through, that left the database as the only answer. An operator can now change their own, from the dashboard and from the account row on the console's home screen. It does not ask for the current one, and that is a decision rather than an oversight: the session already authorises emptying the library, deleting every account and every board, and minting invitations, so refusing it a password change protects nothing that somebody holding it would want, while asking would cost the only recovery this thing has. What replaces the check is that the change signs every other browser and device out and is logged, so a hostile one is visible the moment it happens. The session doing the changing is kept, or the screen would log you out for fixing your password. For everybody else there is a one-time link. An operator makes one from the person's row and hands it over however they already talk to them; opening it asks for a new password and nothing else. It is shaped exactly like an invitation, and for the same reasons: long, stored only as a digest, shown once in the response that makes it, spent once, and dead after a day. Making a second kills the first, because a replaced link left alive is a loose end nobody is tracking, and redeeming one signs every device out, since somebody resetting a password is very often doing it because somebody else has the account. The front door no longer talks about whoever runs the place. It says registrations are closed and to ask for an invitation link, which is the thing the reader can actually act on. And a pass over where things are configured. Two switches both meant "can new people sign up" for two completely different sets of people and neither said which, so they are named after their populations now and each points at the other. On the console, the buttons that start a scan moved out of the group that records what has already happened and in with the sources they act on, and the group that held both people and settings is two groups, because who may use this server and how it is set up are different questions and neither should be read past to reach the other.
130 lines
4.1 KiB
Swift
130 lines
4.1 KiB
Swift
//
|
|
// AdminMoreView.swift
|
|
// FTStore Admin
|
|
//
|
|
// Everything that is not an ordinary day's work.
|
|
//
|
|
// The main screen used to list all fourteen destinations in one column, which
|
|
// made the five that get opened constantly no easier to reach than the nine
|
|
// that get opened when something needs changing. A list stops being navigation
|
|
// at about that length and starts being an inventory.
|
|
//
|
|
// The split is by how often, not by how important: the home screen keeps what
|
|
// answers "what happened?", and this keeps what answers "how is it set up?"
|
|
// and "what has it been doing?".
|
|
//
|
|
// Grouped rather than flat, because nine in a row would only move the problem
|
|
// one screen along.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct AdminMoreView: View {
|
|
@EnvironmentObject private var session: AdminSession
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
NavigationLink {
|
|
AdminSourcesView()
|
|
} label: {
|
|
Label("Channels & Sources", systemImage: "antenna.radiowaves.left.and.right")
|
|
}
|
|
NavigationLink {
|
|
AdminTelegramView()
|
|
} label: {
|
|
Label { Text("Telegram") } icon: { TelegramMark(size: 17) }
|
|
}
|
|
NavigationLink {
|
|
AdminTweaksView()
|
|
} label: {
|
|
Label("Known Tweaks", systemImage: "wrench.and.screwdriver")
|
|
}
|
|
// Next to Known Tweaks, which is the list of what to recognise.
|
|
// This is the list of what to refuse, and the two are read
|
|
// together often enough that separating them would be a nuisance.
|
|
NavigationLink {
|
|
AdminBlocklistView()
|
|
} label: {
|
|
Label("Blocked Tweaks", systemImage: "hand.raised")
|
|
}
|
|
// With the sources it acts on rather than under "What it has been
|
|
// doing", which is a record of the past: a button that starts a
|
|
// scan is not a record of anything.
|
|
NavigationLink {
|
|
AdminActionsView()
|
|
} label: {
|
|
Label("Scraper Actions", systemImage: "bolt.horizontal")
|
|
}
|
|
} header: {
|
|
Text("Where builds come from")
|
|
} footer: {
|
|
Text("Where FTStore looks for builds, what it recognises when it finds one, what it refuses, and the buttons that make it look now.")
|
|
}
|
|
|
|
Section {
|
|
NavigationLink {
|
|
AdminUsageView()
|
|
} label: {
|
|
Label("Usage", systemImage: "chart.line.uptrend.xyaxis")
|
|
}
|
|
NavigationLink {
|
|
AdminLogsView()
|
|
} label: {
|
|
Label("Logs", systemImage: "scroll")
|
|
}
|
|
} header: {
|
|
Text("What it has been doing")
|
|
} footer: {
|
|
Text("The record: how much has been downloaded and stored, and every step the scraper took getting there.")
|
|
}
|
|
|
|
// Two groups rather than one called "People and settings", which was a
|
|
// bag with two different kinds of thing in it. Who may use this
|
|
// server is one question and how it is configured is another, and an
|
|
// operator looking for one of them should not have to read past the
|
|
// other.
|
|
Section {
|
|
// Access is a list of people too, and a third one: not operators
|
|
// and not the people who write in FTStore, but whoever is allowed
|
|
// to reach the server at all.
|
|
NavigationLink {
|
|
AdminAccessView()
|
|
} label: {
|
|
Label("Access", systemImage: "lock.shield")
|
|
}
|
|
if session.isAdmin {
|
|
// "Operators", not "Users": these are the accounts that sign
|
|
// in and run this server. The people who use FTStore are the
|
|
// Users entry on the main screen, and the two were named the
|
|
// same thing for long enough that somebody went looking for
|
|
// a poster here.
|
|
NavigationLink {
|
|
AdminUsersView()
|
|
} label: {
|
|
Label("Operators", systemImage: "person.2")
|
|
}
|
|
}
|
|
} header: {
|
|
Text("Who may use it")
|
|
} footer: {
|
|
Text("Access decides who may reach the store at all. Operators are the accounts that sign in here and run it. The people who post and review are Users, on the main screen.")
|
|
}
|
|
|
|
Section {
|
|
NavigationLink {
|
|
AdminServerSettingsView()
|
|
} label: {
|
|
Label("Server Settings", systemImage: "slider.horizontal.3")
|
|
}
|
|
} header: {
|
|
Text("How it is set up")
|
|
} footer: {
|
|
Text("Addresses, intervals, limits and where files are kept. Nothing here is about people.")
|
|
}
|
|
}
|
|
.navigationTitle("Setup & Diagnostics")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
}
|