Merge pull request #133 from passepartoutvpn/allow-any-host-character
Allow any host character
This commit is contained in:
commit
348153f400
|
@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## 1.11.0 Beta 2276 (2020-02-27)
|
## 1.11.0 Beta 2279 (2020-02-28)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Allow any character in host profile name. [#26](https://github.com/passepartoutvpn/passepartout-ios/issues/26)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -208,7 +208,7 @@ class ProductManager: NSObject {
|
||||||
|
|
||||||
log.debug("Checking 'Unlimited hosts'")
|
log.debug("Checking 'Unlimited hosts'")
|
||||||
if !isEligible(forFeature: .unlimitedHosts) {
|
if !isEligible(forFeature: .unlimitedHosts) {
|
||||||
let ids = service.currentHostNames()
|
let ids = service.hostIds()
|
||||||
if ids.count > AppConstants.InApp.limitedNumberOfHosts {
|
if ids.count > AppConstants.InApp.limitedNumberOfHosts {
|
||||||
for id in ids {
|
for id in ids {
|
||||||
service.removeProfile(ProfileKey(.host, id))
|
service.removeProfile(ProfileKey(.host, id))
|
||||||
|
@ -219,7 +219,7 @@ class ProductManager: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Checking providers")
|
log.debug("Checking providers")
|
||||||
for name in service.currentProviderNames() {
|
for name in service.providerNames() {
|
||||||
guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else {
|
guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,7 @@ class ProductManager: NSObject {
|
||||||
|
|
||||||
extension ConnectionService {
|
extension ConnectionService {
|
||||||
var hasReachedMaximumNumberOfHosts: Bool {
|
var hasReachedMaximumNumberOfHosts: Bool {
|
||||||
let numberOfHosts = currentHostNames().count
|
let numberOfHosts = hostIds().count
|
||||||
return numberOfHosts >= AppConstants.InApp.limitedNumberOfHosts
|
return numberOfHosts >= AppConstants.InApp.limitedNumberOfHosts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ class ConfigurationViewController: UIViewController, StrongTableHost {
|
||||||
// sections
|
// sections
|
||||||
if isEditable {
|
if isEditable {
|
||||||
model.add(.reset)
|
model.add(.reset)
|
||||||
|
model.setHeader("", forSection: .reset)
|
||||||
}
|
}
|
||||||
if !isServerPushed {
|
if !isServerPushed {
|
||||||
model.add(.tls)
|
model.add(.tls)
|
||||||
|
|
|
@ -85,8 +85,8 @@ class OrganizerViewController: UITableViewController, StrongTableHost {
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
providers = service.currentProviderNames().sorted()
|
providers = service.sortedProviderNames()
|
||||||
hosts = service.currentHostNames().sortedCaseInsensitive()
|
hosts = service.sortedHostIds()
|
||||||
|
|
||||||
var providerRows = [RowType](repeating: .profile, count: providers.count)
|
var providerRows = [RowType](repeating: .profile, count: providers.count)
|
||||||
var hostRows = [RowType](repeating: .profile, count: hosts.count)
|
var hostRows = [RowType](repeating: .profile, count: hosts.count)
|
||||||
|
@ -467,11 +467,10 @@ extension OrganizerViewController {
|
||||||
if rowProfile.context == .provider {
|
if rowProfile.context == .provider {
|
||||||
let metadata = InfrastructureFactory.shared.metadata(forName: rowProfile.id)
|
let metadata = InfrastructureFactory.shared.metadata(forName: rowProfile.id)
|
||||||
cell.imageView?.image = metadata?.logo
|
cell.imageView?.image = metadata?.logo
|
||||||
cell.leftText = metadata?.description ?? rowProfile.id
|
|
||||||
} else {
|
} else {
|
||||||
cell.imageView?.image = nil
|
cell.imageView?.image = nil
|
||||||
cell.leftText = rowProfile.id
|
|
||||||
}
|
}
|
||||||
|
cell.leftText = service.screenTitle(rowProfile)
|
||||||
cell.rightText = service.isActiveProfile(rowProfile) ? L10n.Core.Organizer.Cells.Profile.Value.current : nil
|
cell.rightText = service.isActiveProfile(rowProfile) ? L10n.Core.Organizer.Cells.Profile.Value.current : nil
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
|
@ -693,7 +692,7 @@ extension OrganizerViewController: ConnectionServiceDelegate {
|
||||||
perform(segue: StoryboardSegue.Organizer.selectProfileSegueIdentifier, sender: profile)
|
perform(segue: StoryboardSegue.Organizer.selectProfileSegueIdentifier, sender: profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectionService(didRename oldProfile: ConnectionProfile, to newProfile: ConnectionProfile) {
|
func connectionService(didRename profile: ConnectionProfile, to newTitle: String) {
|
||||||
TransientStore.shared.serialize(withProfiles: false) // rename
|
TransientStore.shared.serialize(withProfiles: false) // rename
|
||||||
|
|
||||||
reloadModel()
|
reloadModel()
|
||||||
|
|
|
@ -34,9 +34,7 @@ private let log = SwiftyBeaver.self
|
||||||
class WizardHostViewController: UITableViewController, StrongTableHost {
|
class WizardHostViewController: UITableViewController, StrongTableHost {
|
||||||
@IBOutlet private weak var itemNext: UIBarButtonItem!
|
@IBOutlet private weak var itemNext: UIBarButtonItem!
|
||||||
|
|
||||||
private let existingHosts: [String] = {
|
private let existingHostIds = TransientStore.shared.service.sortedHostIds()
|
||||||
return TransientStore.shared.service.currentHostNames().sortedCaseInsensitive()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var parsingResult: OpenVPN.ConfigurationParser.Result? {
|
var parsingResult: OpenVPN.ConfigurationParser.Result? {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -47,19 +45,23 @@ class WizardHostViewController: UITableViewController, StrongTableHost {
|
||||||
var removesConfigurationOnCancel = false
|
var removesConfigurationOnCancel = false
|
||||||
|
|
||||||
private var createdProfile: HostConnectionProfile?
|
private var createdProfile: HostConnectionProfile?
|
||||||
|
|
||||||
|
private var createdTitle: String?
|
||||||
|
|
||||||
|
private var replacedProfile: ConnectionProfile?
|
||||||
|
|
||||||
// MARK: StrongTableHost
|
// MARK: StrongTableHost
|
||||||
|
|
||||||
lazy var model: StrongTableModel<SectionType, RowType> = {
|
lazy var model: StrongTableModel<SectionType, RowType> = {
|
||||||
let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
|
let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
|
||||||
model.add(.meta)
|
model.add(.meta)
|
||||||
model.setFooter(L10n.Core.Global.Host.TitleInput.message, forSection: .meta)
|
// model.setFooter(L10n.Core.Global.Host.TitleInput.message, forSection: .meta)
|
||||||
if !existingHosts.isEmpty {
|
if !existingHostIds.isEmpty {
|
||||||
model.add(.existing)
|
model.add(.existing)
|
||||||
model.setHeader(L10n.App.Wizards.Host.Sections.Existing.header, forSection: .existing)
|
model.setHeader(L10n.App.Wizards.Host.Sections.Existing.header, forSection: .existing)
|
||||||
}
|
}
|
||||||
model.set([.titleInput], forSection: .meta)
|
model.set([.titleInput], forSection: .meta)
|
||||||
model.set(.existingHost, count: existingHosts.count, forSection: .existing)
|
model.set(.existingHost, count: existingHostIds.count, forSection: .existing)
|
||||||
return model
|
return model
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -104,26 +106,28 @@ class WizardHostViewController: UITableViewController, StrongTableHost {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile = HostConnectionProfile(title: enteredTitle, hostname: hostname)
|
let profile = HostConnectionProfile(hostname: hostname)
|
||||||
let builder = OpenVPNTunnelProvider.ConfigurationBuilder(sessionConfiguration: result.configuration)
|
let builder = OpenVPNTunnelProvider.ConfigurationBuilder(sessionConfiguration: result.configuration)
|
||||||
profile.parameters = builder.build()
|
profile.parameters = builder.build()
|
||||||
|
|
||||||
let service = TransientStore.shared.service
|
let service = TransientStore.shared.service
|
||||||
guard !service.containsProfile(profile) else {
|
replacedProfile = nil
|
||||||
let replacedProfile = service.profile(withContext: profile.context, id: profile.id)
|
if let existingHostId = service.existingHostId(withTitle: enteredTitle) {
|
||||||
|
replacedProfile = service.profile(withContext: profile.context, id: existingHostId)
|
||||||
let alert = UIAlertController.asAlert(title, L10n.Core.Wizards.Host.Alerts.Existing.message)
|
let alert = UIAlertController.asAlert(title, L10n.Core.Wizards.Host.Alerts.Existing.message)
|
||||||
alert.addPreferredAction(L10n.Core.Global.ok) {
|
alert.addPreferredAction(L10n.Core.Global.ok) {
|
||||||
self.next(withProfile: profile, replacedProfile: replacedProfile)
|
self.next(withProfile: profile, title: enteredTitle)
|
||||||
}
|
}
|
||||||
alert.addCancelAction(L10n.Core.Global.cancel)
|
alert.addCancelAction(L10n.Core.Global.cancel)
|
||||||
present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
next(withProfile: profile, replacedProfile: nil)
|
next(withProfile: profile, title: enteredTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func next(withProfile profile: HostConnectionProfile, replacedProfile: ConnectionProfile?) {
|
private func next(withProfile profile: HostConnectionProfile, title: String) {
|
||||||
createdProfile = profile
|
createdProfile = profile
|
||||||
|
createdTitle = title
|
||||||
|
|
||||||
let accountVC = StoryboardScene.Main.accountIdentifier.instantiate()
|
let accountVC = StoryboardScene.Main.accountIdentifier.instantiate()
|
||||||
if let replacedProfile = replacedProfile {
|
if let replacedProfile = replacedProfile {
|
||||||
|
@ -134,7 +138,7 @@ class WizardHostViewController: UITableViewController, StrongTableHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func finish(withCredentials credentials: Credentials) {
|
private func finish(withCredentials credentials: Credentials) {
|
||||||
guard let profile = createdProfile else {
|
guard let profile = createdProfile, let title = createdTitle else {
|
||||||
fatalError("No profile created?")
|
fatalError("No profile created?")
|
||||||
}
|
}
|
||||||
let service = TransientStore.shared.service
|
let service = TransientStore.shared.service
|
||||||
|
@ -150,7 +154,10 @@ class WizardHostViewController: UITableViewController, StrongTableHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dismiss(animated: true) {
|
dismiss(animated: true) {
|
||||||
service.addOrReplaceProfile(profile, credentials: credentials)
|
if let replacedProfile = self.replacedProfile {
|
||||||
|
service.removeProfile(ProfileKey(replacedProfile))
|
||||||
|
}
|
||||||
|
service.addOrReplaceProfile(profile, credentials: credentials, title: title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,15 +213,15 @@ extension WizardHostViewController {
|
||||||
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
|
let cell = Cells.field.dequeue(from: tableView, for: indexPath)
|
||||||
cell.caption = L10n.App.Wizards.Host.Cells.TitleInput.caption
|
cell.caption = L10n.App.Wizards.Host.Cells.TitleInput.caption
|
||||||
cell.captionWidth = 100.0
|
cell.captionWidth = 100.0
|
||||||
cell.allowedCharset = .filename
|
// cell.allowedCharset = .filename
|
||||||
cell.field.applyProfileId(.current)
|
cell.field.applyProfileId(.current)
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
case .existingHost:
|
case .existingHost:
|
||||||
let hostTitle = existingHosts[indexPath.row]
|
let existingTitle = hostTitle(at: indexPath.row)
|
||||||
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
||||||
cell.leftText = hostTitle
|
cell.leftText = existingTitle
|
||||||
cell.accessoryType = .none
|
cell.accessoryType = .none
|
||||||
cell.isTappable = true
|
cell.isTappable = true
|
||||||
return cell
|
return cell
|
||||||
|
@ -227,15 +234,19 @@ extension WizardHostViewController {
|
||||||
guard let titleIndexPath = model.indexPath(forRow: .titleInput, ofSection: .meta) else {
|
guard let titleIndexPath = model.indexPath(forRow: .titleInput, ofSection: .meta) else {
|
||||||
fatalError("Could not found title cell?")
|
fatalError("Could not found title cell?")
|
||||||
}
|
}
|
||||||
let hostTitle = existingHosts[indexPath.row]
|
let existingTitle = hostTitle(at: indexPath.row)
|
||||||
let cellTitle = tableView.cellForRow(at: titleIndexPath) as? FieldTableViewCell
|
let cellTitle = tableView.cellForRow(at: titleIndexPath) as? FieldTableViewCell
|
||||||
cellTitle?.field.text = hostTitle
|
cellTitle?.field.text = existingTitle
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func hostTitle(at row: Int) -> String {
|
||||||
|
return TransientStore.shared.service.screenTitle(forHostId: existingHostIds[row])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
|
@ -79,7 +79,11 @@ class ServiceViewController: UIViewController, StrongTableHost {
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
vpn.profile = profile
|
vpn.profile = profile
|
||||||
|
|
||||||
title = profile?.screenTitle
|
if let profile = profile {
|
||||||
|
title = service.screenTitle(ProfileKey(profile))
|
||||||
|
} else {
|
||||||
|
title = nil
|
||||||
|
}
|
||||||
navigationItem.rightBarButtonItem = (profile?.context == .host) ? itemEdit : nil
|
navigationItem.rightBarButtonItem = (profile?.context == .host) ? itemEdit : nil
|
||||||
if reloadingViews {
|
if reloadingViews {
|
||||||
reloadModel()
|
reloadModel()
|
||||||
|
@ -230,34 +234,36 @@ class ServiceViewController: UIViewController, StrongTableHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func renameProfile() {
|
@IBAction private func renameProfile() {
|
||||||
let alert = UIAlertController.asAlert(L10n.Core.Service.Alerts.Rename.title, L10n.Core.Global.Host.TitleInput.message)
|
let alert = UIAlertController.asAlert(L10n.Core.Service.Alerts.Rename.title, nil)
|
||||||
alert.addTextField { (field) in
|
alert.addTextField { (field) in
|
||||||
field.text = self.profile?.id
|
field.text = self.service.screenTitle(ProfileKey(self.uncheckedProfile))
|
||||||
field.applyProfileId(.current)
|
field.applyProfileId(.current)
|
||||||
field.delegate = self
|
field.delegate = self
|
||||||
}
|
}
|
||||||
pendingRenameAction = alert.addPreferredAction(L10n.Core.Global.ok) {
|
pendingRenameAction = alert.addPreferredAction(L10n.Core.Global.ok) {
|
||||||
guard let newId = alert.textFields?.first?.text else {
|
guard let newTitle = alert.textFields?.first?.text else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.doRenameCurrentProfile(to: newId)
|
self.doRenameCurrentProfile(to: newTitle)
|
||||||
}
|
}
|
||||||
alert.addCancelAction(L10n.Core.Global.cancel)
|
alert.addCancelAction(L10n.Core.Global.cancel)
|
||||||
pendingRenameAction?.isEnabled = false
|
pendingRenameAction?.isEnabled = false
|
||||||
present(alert, animated: true, completion: nil)
|
present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doRenameCurrentProfile(to newId: String) {
|
private func doRenameCurrentProfile(to newTitle: String) {
|
||||||
guard let renamedProfile = service.renameProfile(uncheckedHostProfile, to: newId) else {
|
guard let profile = profile else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setProfile(renamedProfile, reloadingViews: false)
|
service.renameProfile(profile, to: newTitle)
|
||||||
|
setProfile(profile, reloadingViews: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func toggleVpnService(cell: ToggleTableViewCell) {
|
private func toggleVpnService(cell: ToggleTableViewCell) {
|
||||||
if cell.isOn {
|
if cell.isOn {
|
||||||
if #available(iOS 12, *) {
|
if #available(iOS 12, *) {
|
||||||
IntentDispatcher.donateConnection(with: uncheckedProfile)
|
let title = service.screenTitle(ProfileKey(uncheckedProfile))
|
||||||
|
IntentDispatcher.donateConnection(with: uncheckedProfile, title: title)
|
||||||
}
|
}
|
||||||
guard !service.needsCredentials(for: uncheckedProfile) else {
|
guard !service.needsCredentials(for: uncheckedProfile) else {
|
||||||
let alert = UIAlertController.asAlert(
|
let alert = UIAlertController.asAlert(
|
||||||
|
@ -1424,7 +1430,8 @@ extension ServiceViewController: ProviderPoolViewControllerDelegate {
|
||||||
vpn.reinstallIfEnabled()
|
vpn.reinstallIfEnabled()
|
||||||
|
|
||||||
if #available(iOS 12, *) {
|
if #available(iOS 12, *) {
|
||||||
IntentDispatcher.donateConnection(with: uncheckedProviderProfile)
|
let title = service.screenTitle(forProviderName: uncheckedProviderProfile.name)
|
||||||
|
IntentDispatcher.donateConnection(with: uncheckedProviderProfile, title: title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ import Convenience
|
||||||
class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewControllerDelegate, StrongTableHost {
|
class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewControllerDelegate, StrongTableHost {
|
||||||
private let service = TransientStore.shared.service
|
private let service = TransientStore.shared.service
|
||||||
|
|
||||||
private var providers: [Infrastructure.Metadata] = []
|
private var providers: [Infrastructure.Name] = []
|
||||||
|
|
||||||
private var hosts: [String] = []
|
private var hosts: [String] = []
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func reloadModel() {
|
func reloadModel() {
|
||||||
providers = service.currentProviderMetadata().sorted()
|
providers = service.sortedProviderNames()
|
||||||
hosts = service.currentHostNames().sortedCaseInsensitive()
|
hosts = service.sortedHostIds()
|
||||||
|
|
||||||
if !providers.isEmpty {
|
if !providers.isEmpty {
|
||||||
model.add(.providers)
|
model.add(.providers)
|
||||||
|
@ -124,10 +124,16 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
|
||||||
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
|
||||||
switch model.row(at: indexPath) {
|
switch model.row(at: indexPath) {
|
||||||
case .providerShortcut:
|
case .providerShortcut:
|
||||||
cell.leftText = providers[indexPath.row].description
|
let name = providers[indexPath.row]
|
||||||
|
if let metadata = InfrastructureFactory.shared.metadata(forName: name) {
|
||||||
|
cell.leftText = metadata.description
|
||||||
|
} else {
|
||||||
|
cell.leftText = name
|
||||||
|
}
|
||||||
|
|
||||||
case .hostShortcut:
|
case .hostShortcut:
|
||||||
cell.leftText = hosts[indexPath.row]
|
let id = hosts[indexPath.row]
|
||||||
|
cell.leftText = service.screenTitle(forHostId: id)
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
@ -135,7 +141,7 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
switch model.row(at: indexPath) {
|
switch model.row(at: indexPath) {
|
||||||
case .providerShortcut:
|
case .providerShortcut:
|
||||||
selectedProfile = service.profile(withContext: .provider, id: providers[indexPath.row].name)
|
selectedProfile = service.profile(withContext: .provider, id: providers[indexPath.row])
|
||||||
pickProviderLocation()
|
pickProviderLocation()
|
||||||
|
|
||||||
case .hostShortcut:
|
case .hostShortcut:
|
||||||
|
@ -150,7 +156,8 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
|
||||||
guard let host = selectedProfile as? HostConnectionProfile else {
|
guard let host = selectedProfile as? HostConnectionProfile else {
|
||||||
fatalError("Not a HostConnectionProfile")
|
fatalError("Not a HostConnectionProfile")
|
||||||
}
|
}
|
||||||
addShortcut(with: IntentDispatcher.intentConnect(profile: host))
|
let title = service.screenTitle(forHostId: host.id)
|
||||||
|
addShortcut(with: IntentDispatcher.intentConnect(profile: host, title: title))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addMoveToLocation(pool: Pool) {
|
private func addMoveToLocation(pool: Pool) {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 872f827c80ab3a94565edb3db3ae8af5ec619d41
|
Subproject commit f3cf90e8276d51860c33b2faef49718ef1e5b269
|
|
@ -1,3 +1,7 @@
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Allow any character in host profile name.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Programming error in some SoftEther negotiation (Grivus).
|
- Programming error in some SoftEther negotiation (Grivus).
|
||||||
|
|
Loading…
Reference in New Issue