mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-08-06 19:40:08 +02:00
✨ Allow editing of limits
This commit is contained in:
@ -37,7 +37,7 @@ class PMWindowController: NSWindowController, NSWindowDelegate {
|
||||
|
||||
extension NSWindowController {
|
||||
|
||||
public func positionWindowInTopLeftCorner(offsetY: CGFloat = 0, offsetX: CGFloat = 0) {
|
||||
public func positionWindowInTopRightCorner(offsetY: CGFloat = 0, offsetX: CGFloat = 0) {
|
||||
guard let frame = NSScreen.main?.frame else { return }
|
||||
guard let window = self.window else { return }
|
||||
|
||||
|
@ -96,10 +96,16 @@ class PhpConfigurationFile: CreatedFromFile {
|
||||
// Replace the specific line
|
||||
self.lines[item.lineIndex] = components.joined(separator: "=")
|
||||
|
||||
// Ensure the watchers aren't tripped up by config changes
|
||||
PhpConfigWatcher.ignoresModificationsToConfigValues = true
|
||||
|
||||
// Finally, join the string and save the file atomatically again
|
||||
try self.lines.joined(separator: "\n")
|
||||
.write(toFile: self.filePath, atomically: true, encoding: .utf8)
|
||||
|
||||
// Ensure watcher behaviour is reverted
|
||||
PhpConfigWatcher.ignoresModificationsToConfigValues = false
|
||||
|
||||
// Reload the original file
|
||||
self.reload()
|
||||
}
|
||||
|
@ -152,9 +152,6 @@ extension StatusMenu {
|
||||
NSMenuItem(title: "mi_php_version_manager".localized,
|
||||
action: #selector(MainMenu.openPhpVersionManager),
|
||||
keyEquivalent: "m"),
|
||||
NSMenuItem(title: "mi_php_config_manager".localized,
|
||||
action: #selector(MainMenu.openConfigGUI),
|
||||
keyEquivalent: "g"),
|
||||
NSMenuItem(title: "mi_php_config".localized,
|
||||
action: #selector(MainMenu.openActiveConfigFolder),
|
||||
keyEquivalent: "c"),
|
||||
@ -203,6 +200,16 @@ extension StatusMenu {
|
||||
post: stats.post_max_size,
|
||||
upload: stats.upload_max_filesize)
|
||||
)
|
||||
|
||||
// TODO: As soon as this does more than just edit memory limits, move this
|
||||
/*
|
||||
addItem(NSMenuItem.separator())
|
||||
addItem(NSMenuItem(
|
||||
title: "mi_manage_limits".localized,
|
||||
action: #selector(MainMenu.openConfigGUI),
|
||||
keyEquivalent: "l")
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
// MARK: - Extensions
|
||||
|
@ -65,7 +65,7 @@ class PreferencesWindowController: PMWindowController {
|
||||
App.shared.preferencesWindowController?.showWindow(self)
|
||||
|
||||
if justCreated {
|
||||
App.shared.preferencesWindowController?.positionWindowInTopLeftCorner()
|
||||
App.shared.preferencesWindowController?.positionWindowInTopRightCorner()
|
||||
}
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
@ -18,5 +18,6 @@ struct SectionHeaderView: View {
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.appSecondary)
|
||||
.background(Color.debug)
|
||||
.minimumScaleFactor(0.8)
|
||||
}
|
||||
}
|
||||
|
@ -60,27 +60,39 @@ struct StatsView: View {
|
||||
.padding(.leading, 30)
|
||||
.padding(.trailing, 30)
|
||||
} else {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 30) {
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
VStack(alignment: .center, spacing: 3) {
|
||||
SectionHeaderView(text: "mi_memory_limit".localized.uppercased())
|
||||
Text(memoryLimit)
|
||||
.fontWeight(.medium)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
Divider()
|
||||
VStack(alignment: .center, spacing: 3) {
|
||||
SectionHeaderView(text: "mi_post_max_size".localized.uppercased())
|
||||
Text(maxPostSize)
|
||||
.fontWeight(.medium)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
Divider()
|
||||
VStack(alignment: .center, spacing: 3) {
|
||||
SectionHeaderView(text: "mi_upload_max_filesize".localized.uppercased())
|
||||
Text(maxUploadSize)
|
||||
.fontWeight(.medium)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
Divider().hidden()
|
||||
Button {
|
||||
Task { @MainActor in
|
||||
MainMenu.shared.openConfigGUI()
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "gearshape.fill")
|
||||
}
|
||||
.focusable(false)
|
||||
.frame(minWidth: 30, alignment: .center)
|
||||
}
|
||||
.padding(10)
|
||||
.padding(5)
|
||||
.background(Color.debug)
|
||||
}
|
||||
}
|
||||
@ -92,6 +104,6 @@ struct StatsView_Previews: PreviewProvider {
|
||||
memoryLimit: "1024 MB",
|
||||
maxPostSize: "1024 MB",
|
||||
maxUploadSize: "1024 MB"
|
||||
)
|
||||
).frame(height: 100)
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ struct ProgressWindowView: View {
|
||||
window.contentView = NSHostingView(rootView: view)
|
||||
let controller = NSWindowController(window: window)
|
||||
controller.showWindow(nil)
|
||||
controller.positionWindowInTopLeftCorner()
|
||||
controller.positionWindowInTopRightCorner()
|
||||
controller.window?.makeKeyAndOrderFront(self)
|
||||
// NSApp.activate(ignoringOtherApps: true)
|
||||
return controller
|
||||
|
@ -20,7 +20,7 @@ class TerminalProgressWindowController: NSWindowController, NSWindowDelegate {
|
||||
|
||||
windowController.showWindow(windowController)
|
||||
windowController.window?.makeKeyAndOrderFront(nil)
|
||||
windowController.positionWindowInTopLeftCorner()
|
||||
windowController.positionWindowInTopRightCorner()
|
||||
|
||||
windowController.progressView?.labelTitle.stringValue = title
|
||||
windowController.progressView?.labelDescription.stringValue = description
|
||||
|
@ -10,6 +10,8 @@ import Foundation
|
||||
|
||||
class PhpConfigWatcher {
|
||||
|
||||
static var ignoresModificationsToConfigValues: Bool = false
|
||||
|
||||
let folderMonitorQueue = DispatchQueue(label: "FolderMonitorQueue", attributes: .concurrent)
|
||||
|
||||
let url: URL
|
||||
@ -23,7 +25,7 @@ class PhpConfigWatcher {
|
||||
init(for url: URL) {
|
||||
if FileSystem is TestableFileSystem {
|
||||
fatalError("""
|
||||
PhpConfigWatcher is not compatible with testable FS! "
|
||||
PhpConfigWatcher is not compatible with testable FS!"
|
||||
You are not allowed to instantiate these while using a testable FS.
|
||||
""")
|
||||
}
|
||||
@ -124,15 +126,14 @@ class FSWatcher {
|
||||
|
||||
// Define the block to call when a file change is detected.
|
||||
folderMonitorSource?.setEventHandler { [weak self] in
|
||||
// The default behaviour is to reload the menu
|
||||
switch behaviour {
|
||||
case .reloadsMenu:
|
||||
// Default behaviour: reload the menu items
|
||||
self?.parent.didChange?(self!.url)
|
||||
case .reloadsWatchers:
|
||||
// Alternative behaviour: reload all watchers
|
||||
App.shared.handlePhpConfigWatcher(forceReload: true)
|
||||
if behaviour == .reloadsWatchers
|
||||
&& !PhpConfigWatcher.ignoresModificationsToConfigValues {
|
||||
// Reload all configuration watchers
|
||||
return App.shared.handlePhpConfigWatcher(forceReload: true)
|
||||
}
|
||||
|
||||
// The default behaviour is to reload the menu
|
||||
self?.parent.didChange?(self!.url)
|
||||
}
|
||||
|
||||
// Define a cancel handler to ensure the directory is closed when the source is cancelled.
|
||||
|
@ -52,8 +52,14 @@ class BytePhpPreference: PhpPreference {
|
||||
// MARK: Save Value
|
||||
|
||||
private func updatedFieldValue() {
|
||||
internalValue = "\(value)\(unit.rawValue)"
|
||||
|
||||
if value == -1 {
|
||||
// In case we're dealing with unlimited value, we don't need a unit
|
||||
internalValue = "-1"
|
||||
} else {
|
||||
// We need to append the unit otherwise
|
||||
internalValue = "\(value)\(unit.rawValue)"
|
||||
}
|
||||
|
||||
do {
|
||||
try PhpPreference.persistToIniFile(key: self.key, value: self.internalValue)
|
||||
Log.info("The preference \(key) was updated to: \(value)")
|
||||
|
@ -18,7 +18,7 @@ class PhpPreference {
|
||||
|
||||
internal static func persistToIniFile(key: String, value: String) throws {
|
||||
if let file = PhpEnvironments.shared.getConfigFile(forKey: key) {
|
||||
try file.replace(key: key, value: value)
|
||||
return try file.replace(key: key, value: value)
|
||||
}
|
||||
|
||||
throw PhpConfigurationFile.ReplacementErrors.missingFile
|
||||
|
@ -35,12 +35,12 @@ struct PreferenceContainer<ControlView: View>: View {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
controlView
|
||||
|
||||
Text(self.description.localizedForSwiftUI)
|
||||
.lineLimit(nil)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .topLeading)
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
@ -51,6 +51,7 @@ struct ByteLimitView: View {
|
||||
@State private var unit: BytePhpPreference.UnitOption
|
||||
@State private var numberText: String
|
||||
@State private var unlimited: Bool
|
||||
@State private var timer: Timer?
|
||||
|
||||
private var preference: BytePhpPreference
|
||||
|
||||
@ -65,9 +66,11 @@ struct ByteLimitView: View {
|
||||
if !unlimited {
|
||||
HStack {
|
||||
TextField("", text: $numberText)
|
||||
.onChange(of: numberText) { newText in
|
||||
self.preference.value = Int(newText) ?? 256
|
||||
print(self.preference.internalValue)
|
||||
.onChange(of: numberText) { [weak preference] newText in
|
||||
timer?.invalidate()
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { _ in
|
||||
preference?.value = Int(newText) ?? 256
|
||||
}
|
||||
}
|
||||
Picker("Limit Name", selection: $unit) {
|
||||
ForEach(BytePhpPreference.UnitOption.allCases, id: \.self) {
|
||||
@ -84,16 +87,27 @@ struct ByteLimitView: View {
|
||||
}
|
||||
|
||||
Toggle(isOn: $unlimited) {
|
||||
Label("Allow unlimited usage", systemImage: "heart").labelStyle(.titleOnly)
|
||||
}
|
||||
Text("confman.byte_limit.unlimited".localizedForSwiftUI)
|
||||
}.onChange(of: unlimited, perform: { [weak preference] unlimited in
|
||||
timer?.invalidate()
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.8, repeats: false) { _ in
|
||||
preference?.value = unlimited ? -1 : 512
|
||||
preference?.unit = .megabyte
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ByteLimitView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PreferenceContainer(name: "Max Size", description: "Some maximum size") {
|
||||
PreferenceContainer(
|
||||
name: "Max Size",
|
||||
description:
|
||||
"Here's an extensive description that is obviously way too long but it should wrap." +
|
||||
"The point of the wrapping text is that is allows us to see what's going on with the layout here."
|
||||
) {
|
||||
ByteLimitView(preference: BytePhpPreference(key: "max_memory"))
|
||||
}
|
||||
}.frame(width: 600, height: 200)
|
||||
|
||||
ConfigManagerView()
|
||||
.frame(width: 600, height: .infinity)
|
||||
|
@ -20,7 +20,7 @@ struct ConfigManagerView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(alignment: .center, spacing: 15) {
|
||||
Image(systemName: "square.and.pencil.circle.fill")
|
||||
Image(systemName: "gearshape.fill")
|
||||
.resizable()
|
||||
.frame(width: 40, height: 40)
|
||||
.foregroundColor(Color.blue)
|
||||
@ -69,7 +69,7 @@ struct ConfigManagerView: View {
|
||||
|
||||
VStack(alignment: .trailing) {
|
||||
Button("Close", action: {
|
||||
|
||||
App.shared.phpConfigManagerWindowController?.close()
|
||||
})
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
@ -80,7 +80,7 @@ struct ConfigManagerView: View {
|
||||
alignment: .topTrailing
|
||||
)
|
||||
}
|
||||
}
|
||||
}.frame(maxHeight: 485)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,9 @@ class PhpConfigManagerWindowController: PMWindowController {
|
||||
}
|
||||
|
||||
App.shared.phpConfigManagerWindowController?.showWindow(self)
|
||||
App.shared.phpConfigManagerWindowController?.window?.setCenterPosition(offsetY: 70)
|
||||
App.shared.phpConfigManagerWindowController?.positionWindowInTopRightCorner()
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
App.shared.phpConfigManagerWindowController?.window?.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class PhpVersionManagerWindowController: PMWindowController {
|
||||
}
|
||||
|
||||
App.shared.phpVersionManagerWindowController?.showWindow(self)
|
||||
App.shared.phpVersionManagerWindowController?.positionWindowInTopLeftCorner()
|
||||
App.shared.phpVersionManagerWindowController?.positionWindowInTopRightCorner()
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
"mi_no_php_linked_explain" = "What's This?";
|
||||
"mi_php_version_manager" = "PHP Version Manager...";
|
||||
"mi_php_config_manager" = "PHP Configuration Editor...";
|
||||
"mi_manage_limits" = "Manage Limits...";
|
||||
|
||||
"mi_diagnostics" = "Diagnostics";
|
||||
"mi_active_services" = "Active Services";
|
||||
@ -85,7 +86,8 @@
|
||||
// CONFMAN
|
||||
|
||||
"confman.title" = "PHP Configuration Editor";
|
||||
"confman.description" = "This feature lets you customize your PHP installation with ease. Changes are automatically applied.";
|
||||
"confman.description" = "This feature lets you customize your PHP installation's configuration with ease.\nPlease note that any changes you make are immediately and automatically applied.";
|
||||
"confman.byte_limit.unlimited" = "Allow unlimited usage";
|
||||
|
||||
"php_ini.memory_limit.title" = "Memory Limit";
|
||||
"php_ini.memory_limit.description" = "This sets the maximum amount of memory in bytes that a script is allowed to allocate. This helps prevent poorly written scripts for eating up all available memory on a server.";
|
||||
|
Reference in New Issue
Block a user