new tests
This commit is contained in:
+151
-38
@@ -2,6 +2,7 @@ import SwiftUI
|
||||
import AppKit
|
||||
|
||||
extension Notification.Name {
|
||||
static let dbsynOpenConnectionManager = Notification.Name("DBSyn.OpenConnectionManager")
|
||||
static let workbenchRevealSQLEditor = Notification.Name("DBSyn.RevealSQLEditor")
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ struct DBSynApp: App {
|
||||
let icon = NSImage(contentsOfFile: Bundle.main.path(forResource: "DBSynBrand", ofType: "png") ?? "")
|
||||
NSApplication.shared.orderFrontStandardAboutPanel(options: [
|
||||
.applicationName: "DBSyn",
|
||||
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage,
|
||||
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage ?? NSImage(),
|
||||
.version: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0",
|
||||
.credits: NSAttributedString(string: "Native MySQL client for macOS")
|
||||
])
|
||||
@@ -125,48 +126,153 @@ struct WorkbenchSettingsView: View {
|
||||
@AppStorage("preferences.migrationFilenamePrefix")
|
||||
private var migrationFilenamePrefix = "create"
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
@State private var selectedSection: SettingsSection? = .general
|
||||
|
||||
editorSettings
|
||||
.tabItem {
|
||||
Label(
|
||||
"SQL Editor",
|
||||
systemImage: "chevron.left.forwardslash.chevron.right"
|
||||
)
|
||||
}
|
||||
|
||||
executionSettings
|
||||
.tabItem {
|
||||
Label(
|
||||
"Query Execution",
|
||||
systemImage: "bolt"
|
||||
)
|
||||
}
|
||||
|
||||
resultSettings
|
||||
.tabItem {
|
||||
Label(
|
||||
"Result Grid",
|
||||
systemImage: "tablecells"
|
||||
)
|
||||
}
|
||||
|
||||
migrationSettings
|
||||
.tabItem { Label("Laravel", systemImage: "curlybraces") }
|
||||
|
||||
generalSettings
|
||||
.tabItem {
|
||||
Label(
|
||||
"General",
|
||||
systemImage: "gearshape"
|
||||
)
|
||||
}
|
||||
private enum SettingsSection: String, CaseIterable, Identifiable {
|
||||
case general = "General"
|
||||
case connections = "Connections"
|
||||
case security = "Security"
|
||||
case editor = "SQL Editor"
|
||||
case execution = "Query Execution"
|
||||
case results = "Result Grid"
|
||||
case database = "Database"
|
||||
case diagrams = "Schema Designer"
|
||||
case laravel = "Laravel"
|
||||
case administration = "Administration"
|
||||
case exports = "Export & Backup"
|
||||
case advanced = "Advanced"
|
||||
var id: String { rawValue }
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .general: return "gearshape"
|
||||
case .connections: return "server.rack"
|
||||
case .security: return "lock.shield"
|
||||
case .editor: return "chevron.left.forwardslash.chevron.right"
|
||||
case .execution: return "bolt"
|
||||
case .results: return "tablecells"
|
||||
case .database: return "externaldrive"
|
||||
case .diagrams: return "point.3.connected.trianglepath.dotted"
|
||||
case .laravel: return "curlybraces"
|
||||
case .administration: return "gauge.with.dots.needle.67percent"
|
||||
case .exports: return "square.and.arrow.up"
|
||||
case .advanced: return "wrench.and.screwdriver"
|
||||
}
|
||||
}
|
||||
.frame(width: 760, height: 480)
|
||||
}
|
||||
|
||||
@AppStorage("preferences.diagramPalette") private var diagramPalette = "Ocean"
|
||||
@AppStorage("preferences.diagramShowAllColumns") private var diagramShowAllColumns = false
|
||||
@AppStorage("preferences.migrationOutputDirectory") private var migrationOutputDirectory = ""
|
||||
@AppStorage("preferences.migrationUseTimestamps") private var migrationUseTimestamps = true
|
||||
@AppStorage("preferences.confirmDestructiveSQL") private var confirmDestructiveSQL = true
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
List(SettingsSection.allCases, selection: $selectedSection) { section in
|
||||
Label(section.rawValue, systemImage: section.icon)
|
||||
.tag(section)
|
||||
.padding(.vertical, 3)
|
||||
}
|
||||
.listStyle(.sidebar)
|
||||
.navigationTitle("Settings")
|
||||
.frame(minWidth: 205)
|
||||
} detail: {
|
||||
Group {
|
||||
switch selectedSection ?? .general {
|
||||
case .general: generalSettings
|
||||
case .connections: connectionsSettings
|
||||
case .security: securitySettings
|
||||
case .editor: editorSettings
|
||||
case .execution: executionSettings
|
||||
case .results: resultSettings
|
||||
case .database: databaseSettings
|
||||
case .diagrams: diagramSettings
|
||||
case .laravel: migrationSettings
|
||||
case .administration: administrationSettings
|
||||
case .exports: exportSettings
|
||||
case .advanced: advancedSettings
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
.frame(minWidth: 940, idealWidth: 1050, minHeight: 610, idealHeight: 710)
|
||||
.preferredColorScheme(appearance == "dark" ? .dark : appearance == "light" ? .light : nil)
|
||||
}
|
||||
|
||||
private func information(_ message: String) -> some View {
|
||||
Text(message).font(.callout).foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
private var connectionsSettings: some View {
|
||||
settingsPage("Connections") {
|
||||
information("Manage and switch saved connections from the main window's Connections menu. Select a profile to connect, or edit its MySQL host, port and default schema.")
|
||||
information("Connection profiles and credentials are stored separately. Passwords are held in macOS Keychain.")
|
||||
Button("Open Connection Manager") {
|
||||
NotificationCenter.default.post(name: .dbsynOpenConnectionManager, object: nil)
|
||||
// Leave window management to the Settings scene.
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
|
||||
private var securitySettings: some View {
|
||||
settingsPage("Security & Transport") {
|
||||
information("Each connection has independent TLS and SSH transport options. Edit a connection to configure certificate-verified TLS, or an SSH tunnel using macOS OpenSSH.")
|
||||
information("Do not disable certificate verification to work around connection failures. Native TLS handshake compatibility must be validated against your server before relying on it.")
|
||||
information("Passwords use the DBSyn.MySQL Keychain service. Legacy WorkbenchReplacement.MySQL credentials are read for compatibility.")
|
||||
}
|
||||
}
|
||||
|
||||
private var databaseSettings: some View {
|
||||
settingsPage("Database Navigator") {
|
||||
settingsRow("Expand tables") {
|
||||
Toggle("Automatically expand table folders", isOn: $autoExpandTables)
|
||||
}
|
||||
information("Select the active database in the schema navigator. Refresh schema metadata from the main window after switching connections.")
|
||||
}
|
||||
}
|
||||
|
||||
private var diagramSettings: some View {
|
||||
settingsPage("Schema Designer") {
|
||||
settingsRow("Default palette") {
|
||||
Picker("Palette", selection: $diagramPalette) {
|
||||
ForEach(["Ocean", "Slate", "Forest", "Plum"], id: \.self) { Text($0) }
|
||||
}.labelsHidden().frame(width: 200)
|
||||
}
|
||||
settingsRow("Columns") {
|
||||
Toggle("Show all columns by default", isOn: $diagramShowAllColumns)
|
||||
}
|
||||
information("Diagram-specific layouts and table selections are saved from the Schema Designer. You can override the palette and column visibility there.")
|
||||
}
|
||||
}
|
||||
|
||||
private var administrationSettings: some View {
|
||||
settingsPage("Administration") {
|
||||
information("Server status, connections, users, variables, performance and logs are available in the Administration navigator.")
|
||||
information("Server start/stop and configuration-file editing require server-level operating-system privileges and are not available through ordinary MySQL credentials.")
|
||||
}
|
||||
}
|
||||
|
||||
private var exportSettings: some View {
|
||||
settingsPage("Export & Backup") {
|
||||
information("SQL and CSV export, backup and restore actions are available from the main window. Choose a destination when exporting.")
|
||||
information("Backups and restores can be destructive. Review the target server and schema before starting a restore.")
|
||||
}
|
||||
}
|
||||
|
||||
private var advancedSettings: some View {
|
||||
settingsPage("Advanced") {
|
||||
settingsRow("Safety") {
|
||||
Toggle("Confirm destructive SQL operations", isOn: $confirmDestructiveSQL)
|
||||
}
|
||||
information("The safety preference is reserved for a future SQL confirmation integration; it does not yet intercept SQL execution.")
|
||||
information("For troubleshooting, use the macOS Console and the DBSyn test runner. TLS negotiation must be tested on a macOS host.")
|
||||
}
|
||||
}
|
||||
|
||||
private var migrationSettings: some View {
|
||||
settingsPage("Laravel & Developer Tools") {
|
||||
Text("Generate Laravel migrations from a connected MySQL table or an imported CREATE TABLE SQL file.")
|
||||
@@ -175,6 +281,13 @@ struct WorkbenchSettingsView: View {
|
||||
TextField("create", text: $migrationFilenamePrefix)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
settingsRow("Output folder") {
|
||||
TextField("Choose folder when saving", text: $migrationOutputDirectory)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
settingsRow("Filename") {
|
||||
Toggle("Use timestamp prefix", isOn: $migrationUseTimestamps)
|
||||
}
|
||||
Text("Open Tools → Generate Laravel Migration to preview, edit and save the generated PHP file.")
|
||||
.font(.callout).foregroundStyle(.secondary)
|
||||
Text("Generated migrations must be reviewed before execution. DBSyn never executes generated PHP.")
|
||||
|
||||
@@ -276,7 +276,13 @@ struct LaravelMigrationView: View {
|
||||
private func save() {
|
||||
let panel = NSSavePanel(); panel.allowedContentTypes = [UTType(filenameExtension: "php") ?? .plainText]
|
||||
let name = tableName.isEmpty ? "table" : tableName.lowercased()
|
||||
panel.nameFieldStringValue = "\(prefix)_\(name)_table.php"
|
||||
let timestamp = DateFormatter()
|
||||
timestamp.dateFormat = "yyyy_MM_dd_HHmmss"
|
||||
let useTimestamps = UserDefaults.standard.object(forKey: "preferences.migrationUseTimestamps") as? Bool ?? true
|
||||
let datePrefix = useTimestamps ? timestamp.string(from: Date()) + "_" : ""
|
||||
panel.nameFieldStringValue = "\(datePrefix)\(prefix)_\(name)_table.php"
|
||||
let folder = UserDefaults.standard.string(forKey: "preferences.migrationOutputDirectory") ?? ""
|
||||
if !folder.isEmpty { panel.directoryURL = URL(fileURLWithPath: (folder as NSString).expandingTildeInPath, isDirectory: true) }
|
||||
if panel.runModal() == .OK, let url = panel.url {
|
||||
do { try generated.write(to: url, atomically: true, encoding: .utf8); errorText = ""; statusText = "Saved: \(url.lastPathComponent)" }
|
||||
catch { errorText = error.localizedDescription }
|
||||
|
||||
@@ -85,6 +85,9 @@ struct MainView: View {
|
||||
}
|
||||
.frame(minWidth: 800, minHeight: 520)
|
||||
.background(Color(nsColor: .windowBackgroundColor))
|
||||
.onReceive(NotificationCenter.default.publisher(for: .dbsynOpenConnectionManager)) { _ in
|
||||
showingConnectionManager = true
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .workbenchRevealSQLEditor)) { _ in
|
||||
administrationPage = nil
|
||||
navigatorMode = 1
|
||||
|
||||
@@ -98,7 +98,8 @@ struct DBSynSchemaDiagramView: View {
|
||||
if let stored = UserDefaults.standard.stringArray(forKey: selectionKey) {
|
||||
includedObjects = Set(stored)
|
||||
} else { includedObjects = Set(allObjects) }
|
||||
theme = UserDefaults.standard.string(forKey: themeKey) ?? "Ocean"
|
||||
theme = UserDefaults.standard.string(forKey: themeKey) ?? UserDefaults.standard.string(forKey: "preferences.diagramPalette") ?? "Ocean"
|
||||
showAllColumns = UserDefaults.standard.bool(forKey: "preferences.diagramShowAllColumns")
|
||||
}
|
||||
private func saveOptions() {
|
||||
UserDefaults.standard.set(Array(includedObjects).sorted(), forKey: selectionKey)
|
||||
|
||||
Reference in New Issue
Block a user