mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
chore: improve team sync
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugins.cos
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -13,7 +14,7 @@ class COSProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt
|
||||
return COSProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return COSProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugins.obs
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -13,7 +14,7 @@ class OBSProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt
|
||||
return OBSProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return OBSProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugins.oss
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -13,7 +14,7 @@ class OSSProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt
|
||||
return OSSProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return OSSProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugins.s3
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -13,7 +14,7 @@ class S3ProtocolHostPanelExtension private constructor() : ProtocolHostPanelExte
|
||||
return S3ProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return S3ProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugins.smb
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -13,7 +14,7 @@ class SMBProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt
|
||||
return SMBProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return SMBProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugins.webdav
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -13,7 +14,7 @@ class WebDAVProtocolHostPanelExtension private constructor() : ProtocolHostPanel
|
||||
return WebDAVProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return WebDAVProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.actions.AnAction
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.protocol.*
|
||||
@@ -18,7 +19,11 @@ import java.awt.Dimension
|
||||
import java.awt.Window
|
||||
import javax.swing.*
|
||||
|
||||
class NewHostDialogV2(owner: Window, private val editHost: Host? = null) : DialogWrapper(owner) {
|
||||
class NewHostDialogV2(
|
||||
owner: Window,
|
||||
private val editHost: Host? = null,
|
||||
private val accountOwner: AccountOwner,
|
||||
) : DialogWrapper(owner) {
|
||||
|
||||
private object Current {
|
||||
var card: ProtocolHostPanel? = null
|
||||
@@ -65,11 +70,11 @@ class NewHostDialogV2(owner: Window, private val editHost: Host? = null) : Dialo
|
||||
toolbar.add(Box.createHorizontalGlue())
|
||||
|
||||
val extensions = ProtocolHostPanelExtension.extensions
|
||||
.filter { it.canCreateProtocolHostPanel() }
|
||||
.filter { it.canCreateProtocolHostPanel(accountOwner) }
|
||||
for ((index, extension) in extensions.withIndex()) {
|
||||
val protocol = extension.getProtocolProvider().getProtocol()
|
||||
val icon = ScaleIcon(extension.getProtocolProvider().getIcon(), 22)
|
||||
val hostPanel = extension.createProtocolHostPanel()
|
||||
val hostPanel = extension.createProtocolHostPanel(accountOwner)
|
||||
val button = JToggleButton(protocol, icon).apply { buttonGroup.add(this) }
|
||||
button.setVerticalTextPosition(SwingConstants.BOTTOM)
|
||||
button.setHorizontalTextPosition(SwingConstants.CENTER)
|
||||
|
||||
@@ -33,7 +33,7 @@ open class Scope(
|
||||
return get(clazz)
|
||||
}
|
||||
|
||||
synchronized(clazz) {
|
||||
synchronized(this) {
|
||||
if (beans.containsKey(clazz)) {
|
||||
return get(clazz)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora
|
||||
|
||||
|
||||
import app.termora.account.AccountManager
|
||||
import app.termora.actions.*
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
import app.termora.database.DatabaseManager
|
||||
@@ -244,10 +245,15 @@ class TerminalTabbed(
|
||||
val edit = popupMenu.add(I18n.getString("termora.keymgr.edit"))
|
||||
edit.addActionListener(object : AnAction() {
|
||||
private val hostManager get() = HostManager.getInstance()
|
||||
private val accountManager get() = AccountManager.getInstance()
|
||||
|
||||
override fun actionPerformed(evt: AnActionEvent) {
|
||||
if (tab is HostTerminalTab) {
|
||||
val host = hostManager.getHost(tab.host.id) ?: return
|
||||
val dialog = NewHostDialogV2(evt.window, host)
|
||||
val dialog = NewHostDialogV2(
|
||||
evt.window, host,
|
||||
accountManager.getOwners().first { it.id == host.ownerId },
|
||||
)
|
||||
dialog.setLocationRelativeTo(evt.window)
|
||||
dialog.isVisible = true
|
||||
|
||||
|
||||
@@ -183,8 +183,8 @@ object AccountHttp {
|
||||
|
||||
if (isRefreshing.compareAndSet(false, true)) {
|
||||
try {
|
||||
// 刷新 token
|
||||
accountManager.refreshToken()
|
||||
// 刷新 token 和用户
|
||||
accountManager.refresh()
|
||||
} finally {
|
||||
lock.withLock {
|
||||
isRefreshing.set(false)
|
||||
|
||||
@@ -14,12 +14,15 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class AccountManager private constructor() : ApplicationRunnerExtension {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(AccountManager::class.java)
|
||||
|
||||
fun getInstance(): AccountManager {
|
||||
return ApplicationScope.forApplicationScope()
|
||||
.getOrCreate(AccountManager::class) { AccountManager() }
|
||||
@@ -30,6 +33,7 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private val serverManager get() = ServerManager.getInstance()
|
||||
private var account = locally()
|
||||
private val accountProperties get() = AccountProperties.getInstance()
|
||||
|
||||
@@ -48,10 +52,14 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
|
||||
fun getAccessToken() = account.accessToken
|
||||
fun getRefreshToken() = account.refreshToken
|
||||
fun getOwnerIds() = account.teams.map { it.id }.toMutableList().apply { add(getAccountId()) }.toSet()
|
||||
fun getOwners() =
|
||||
account.teams.map { AccountOwner(it.id, it.name, OwnerType.Team) }
|
||||
.toMutableList().apply { AccountOwner(getAccountId(), getEmail(), OwnerType.User) }
|
||||
.toSet()
|
||||
fun getOwners(): Set<AccountOwner> {
|
||||
val owners = mutableSetOf<AccountOwner>()
|
||||
owners.add(AccountOwner(getAccountId(), getEmail(), OwnerType.User))
|
||||
for (team in getTeams()) {
|
||||
owners.add(AccountOwner(team.id, team.name, OwnerType.Team))
|
||||
}
|
||||
return owners
|
||||
}
|
||||
|
||||
fun isFreePlan(): Boolean {
|
||||
return isLocally() || getSubscription().plan == SubscriptionPlan.Free
|
||||
@@ -126,37 +134,39 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
|
||||
* 设置账户信息,可以多次调用,每次修改用户信息都要通过这个方法
|
||||
*/
|
||||
internal fun login(account: Account) {
|
||||
synchronized(this) {
|
||||
|
||||
val oldAccount = this.account
|
||||
val oldAccount = this.account
|
||||
|
||||
this.account = account
|
||||
this.account = account
|
||||
|
||||
// 立即保存到数据库
|
||||
val accountProperties = AccountProperties.getInstance()
|
||||
accountProperties.id = account.id
|
||||
accountProperties.server = account.server
|
||||
accountProperties.email = account.email
|
||||
accountProperties.teams = ohMyJson.encodeToString(account.teams)
|
||||
accountProperties.subscriptions = ohMyJson.encodeToString(account.subscriptions)
|
||||
accountProperties.accessToken = account.accessToken
|
||||
accountProperties.refreshToken = account.refreshToken
|
||||
accountProperties.secretKey = ohMyJson.encodeToString(account.secretKey)
|
||||
// 立即保存到数据库
|
||||
val accountProperties = AccountProperties.getInstance()
|
||||
accountProperties.id = account.id
|
||||
accountProperties.server = account.server
|
||||
accountProperties.email = account.email
|
||||
accountProperties.teams = ohMyJson.encodeToString(account.teams)
|
||||
accountProperties.subscriptions = ohMyJson.encodeToString(account.subscriptions)
|
||||
accountProperties.accessToken = account.accessToken
|
||||
accountProperties.refreshToken = account.refreshToken
|
||||
accountProperties.secretKey = ohMyJson.encodeToString(account.secretKey)
|
||||
|
||||
// 如果变更账户了,那么同步时间从0开始
|
||||
if (oldAccount.id != account.id) {
|
||||
accountProperties.nextSynchronizationSince = 0
|
||||
// 如果变更账户了,那么同步时间从0开始
|
||||
if (oldAccount.id != account.id) {
|
||||
accountProperties.nextSynchronizationSince = 0
|
||||
}
|
||||
|
||||
if (isLocally().not()) {
|
||||
accountProperties.publicKey = Base64.encodeBase64String(account.publicKey.encoded)
|
||||
accountProperties.privateKey = Base64.encodeBase64String(account.privateKey.encoded)
|
||||
} else {
|
||||
accountProperties.publicKey = StringUtils.EMPTY
|
||||
accountProperties.privateKey = StringUtils.EMPTY
|
||||
}
|
||||
|
||||
// 通知变化
|
||||
notifyAccountChanged(oldAccount, account)
|
||||
}
|
||||
|
||||
if (isLocally().not()) {
|
||||
accountProperties.publicKey = Base64.encodeBase64String(account.publicKey.encoded)
|
||||
accountProperties.privateKey = Base64.encodeBase64String(account.privateKey.encoded)
|
||||
} else {
|
||||
accountProperties.publicKey = StringUtils.EMPTY
|
||||
accountProperties.privateKey = StringUtils.EMPTY
|
||||
}
|
||||
|
||||
// 通知变化
|
||||
notifyAccountChanged(oldAccount, account)
|
||||
}
|
||||
|
||||
private fun notifyAccountChanged(oldAccount: Account, newAccount: Account) {
|
||||
@@ -220,7 +230,7 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
|
||||
|
||||
override fun ready() {
|
||||
if (isLocally().not()) {
|
||||
swingCoroutineScope.launch(Dispatchers.IO) { refreshToken() }
|
||||
swingCoroutineScope.launch(Dispatchers.IO) { refresh() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,8 +238,34 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
|
||||
/**
|
||||
* 刷新用户
|
||||
*/
|
||||
fun refresh(accessToken: String = getAccessToken()) {
|
||||
fun refresh() {
|
||||
runCatching { refreshToken() }.onSuccess {
|
||||
refreshAccount()
|
||||
}.onFailure {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error(it.message, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshAccount() {
|
||||
try {
|
||||
val me = serverManager.callMe(account.server, getAccessToken())
|
||||
val teams = me.teams.map {
|
||||
Team(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
secretKey = RSA.decrypt(getPrivateKey(), Base64.decodeBase64(it.secretKey)),
|
||||
role = it.role
|
||||
)
|
||||
}
|
||||
// 重新登录
|
||||
login(account.copy(teams = teams, subscriptions = me.subscriptions))
|
||||
} catch (e: Exception) {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AccountApplicationRunnerExtension private constructor() : ApplicationRunnerExtension {
|
||||
|
||||
@@ -75,8 +75,10 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
|
||||
val subscription = accountManager.getSubscription()
|
||||
val isFreePlan = accountManager.isFreePlan()
|
||||
val isLocally = accountManager.isLocally()
|
||||
val validTo = if (isFreePlan) "-" else if (subscription.endAt >= Long.MAX_VALUE)
|
||||
I18n.getString("termora.settings.account.lifetime") else
|
||||
val validTo = if (isFreePlan) "-"
|
||||
else if (subscription.endAt >= Long.MAX_VALUE)
|
||||
I18n.getString("termora.settings.account.lifetime")
|
||||
else
|
||||
DateFormatUtils.format(Date(subscription.endAt), I18n.getString("termora.date-format"))
|
||||
val lastSynchronizationOn = if (isFreePlan) "-" else
|
||||
DateFormatUtils.format(
|
||||
@@ -158,9 +160,19 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
|
||||
if (isFreePlan.not()) {
|
||||
actions.add(JXHyperlink(object : AnAction(I18n.getString("termora.settings.account.sync-now")) {
|
||||
override fun actionPerformed(evt: AnActionEvent) {
|
||||
// 全量同步
|
||||
accountProperties.nextSynchronizationSince = 0
|
||||
|
||||
// 拉取
|
||||
PullService.getInstance().trigger()
|
||||
|
||||
// 推送
|
||||
PushService.getInstance().trigger()
|
||||
|
||||
swingCoroutineScope.launch(Dispatchers.IO) {
|
||||
// 刷新账户
|
||||
accountManager.refreshAccount()
|
||||
|
||||
withContext(Dispatchers.Swing) {
|
||||
isEnabled = false
|
||||
lastSynchronizationOnLabel.text = DateFormatUtils.format(
|
||||
|
||||
@@ -2,5 +2,4 @@ package app.termora.account
|
||||
|
||||
import app.termora.database.OwnerType
|
||||
|
||||
data class AccountOwner(val id: String, val name: String, val type: OwnerType) {
|
||||
}
|
||||
data class AccountOwner(val id: String, val name: String, val type: OwnerType)
|
||||
@@ -48,6 +48,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
||||
Server(I18n.getString("termora.settings.account.server-singapore"), "https://account.termora.app")
|
||||
private val chinaServer =
|
||||
Server(I18n.getString("termora.settings.account.server-china"), "https://account.termora.cn")
|
||||
private val serverManager get() = ServerManager.getInstance()
|
||||
|
||||
init {
|
||||
isModal = true
|
||||
@@ -359,7 +360,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
||||
|
||||
val loginJob = swingCoroutineScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
ServerManager.getInstance().login(
|
||||
serverManager.login(
|
||||
server, usernameTextField.text,
|
||||
String(passwordField.password), mfaTextField.text.trim()
|
||||
)
|
||||
|
||||
@@ -45,6 +45,15 @@ class PullService private constructor() : SyncService(), Disposable, Application
|
||||
lastChangeHash = StringUtils.EMPTY
|
||||
}
|
||||
|
||||
// 团队变了,全量同步
|
||||
if (oldAccount.id == newAccount.id) {
|
||||
if (oldAccount.teams != newAccount.teams) {
|
||||
accountProperties.nextSynchronizationSince = 0
|
||||
trigger()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (oldAccount.isLocally && newAccount.isLocally.not()) {
|
||||
trigger()
|
||||
}
|
||||
@@ -213,7 +222,7 @@ class PullService private constructor() : SyncService(), Disposable, Application
|
||||
log.debug("拉取数据: {} 成功, 响应码: {}", id, response.code)
|
||||
}
|
||||
|
||||
if(response.isSuccessful.not()){
|
||||
if (response.isSuccessful.not()) {
|
||||
IOUtils.closeQuietly(response)
|
||||
}
|
||||
|
||||
@@ -281,7 +290,7 @@ class PullService private constructor() : SyncService(), Disposable, Application
|
||||
ownerId = ownerId,
|
||||
ownerType = ownerType,
|
||||
type = type,
|
||||
data = decryptData(id, data),
|
||||
data = decryptData(id, data, ownerId),
|
||||
version = version,
|
||||
// 因为已经是拉取最新版本了,所以这里无需再同步了
|
||||
synced = true,
|
||||
@@ -298,11 +307,8 @@ class PullService private constructor() : SyncService(), Disposable, Application
|
||||
if (log.isDebugEnabled) {
|
||||
log.debug("数据: {}, 类型: {} 云端已经删除,本地即将删除", id, type)
|
||||
}
|
||||
databaseManager.delete(
|
||||
id, type,
|
||||
DatabaseChangedExtension.Source.Sync
|
||||
)
|
||||
|
||||
databaseManager.delete(id, type, DatabaseChangedExtension.Source.Sync)
|
||||
if (log.isInfoEnabled) {
|
||||
log.info("数据: {}, 类型: {} 已从本地删除", id, type)
|
||||
|
||||
@@ -340,7 +346,7 @@ class PullService private constructor() : SyncService(), Disposable, Application
|
||||
ownerId = ownerId,
|
||||
ownerType = ownerType,
|
||||
type = type,
|
||||
data = decryptData(id, data),
|
||||
data = decryptData(id, data, ownerId),
|
||||
version = version,
|
||||
// 因为已经是拉取最新版本了,所以这里无需再同步了
|
||||
synced = true,
|
||||
@@ -377,7 +383,7 @@ class PullService private constructor() : SyncService(), Disposable, Application
|
||||
pullChanges()
|
||||
|
||||
// N 秒拉一次
|
||||
val result = withTimeoutOrNull(Random.nextInt(5, 15).seconds) {
|
||||
val result = withTimeoutOrNull(Random.nextInt(3, 10).seconds) {
|
||||
channel.receiveCatching()
|
||||
} ?: continue
|
||||
if (result.isFailure) break
|
||||
|
||||
@@ -4,6 +4,7 @@ import app.termora.*
|
||||
import app.termora.Application.ohMyJson
|
||||
import app.termora.database.Data
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
import app.termora.database.OwnerType
|
||||
import app.termora.plugin.DispatchThread
|
||||
import app.termora.plugin.internal.extension.DynamicExtensionHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -106,7 +107,20 @@ class PushService private constructor() : SyncService(), Disposable, Application
|
||||
.delete()
|
||||
.build()
|
||||
|
||||
AccountHttp.execute(request = request)
|
||||
try {
|
||||
AccountHttp.execute(request = request)
|
||||
} catch (e: Exception) {
|
||||
if (e is ResponseException) {
|
||||
if (e.code == 403) {
|
||||
// 如果是 Team 发现没有权限,那么很有可能是被提出团队
|
||||
if (data.ownerType == OwnerType.Team.name) {
|
||||
// 刷新用户
|
||||
accountManager.refreshAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
// 修改为已经同步
|
||||
updateData(data.id, synced = true)
|
||||
@@ -153,6 +167,12 @@ class PushService private constructor() : SyncService(), Disposable, Application
|
||||
}
|
||||
// 标记为已经同步
|
||||
updateData(data.id, synced = true, version = data.version)
|
||||
|
||||
// 如果是 Team 发现没有权限,那么很有可能是被提出团队
|
||||
if (data.ownerType == OwnerType.Team.name) {
|
||||
// 刷新用户
|
||||
accountManager.refreshAccount()
|
||||
}
|
||||
return
|
||||
} else if (response.code == 409) { // 版本冲突,一般来说是云端版本大于本地版本
|
||||
val json = ohMyJson.decodeFromString<JsonObject>(text)
|
||||
|
||||
@@ -54,7 +54,7 @@ class ServerManager private constructor() {
|
||||
val loginResponse = callLogin(serverInfo, server, username, password, mfa)
|
||||
|
||||
// call me
|
||||
val meResponse = callMe(server, loginResponse.accessToken)
|
||||
val meResponse = callMe(server.server, loginResponse.accessToken)
|
||||
|
||||
// 解密
|
||||
val salt = "${serverInfo.salt}:${username}".toByteArray()
|
||||
@@ -139,9 +139,9 @@ class ServerManager private constructor() {
|
||||
}
|
||||
|
||||
|
||||
private fun callMe(server: Server, accessToken: String): MeResponse {
|
||||
fun callMe(server: String, accessToken: String): MeResponse {
|
||||
val request = Request.Builder()
|
||||
.url("${server.server}/v1/users/me")
|
||||
.url("${server}/v1/users/me")
|
||||
.header("Authorization", "Bearer $accessToken")
|
||||
.build()
|
||||
val text = AccountHttp.execute(request = request)
|
||||
@@ -149,13 +149,13 @@ class ServerManager private constructor() {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class ServerInfo(val salt: String)
|
||||
data class ServerInfo(val salt: String)
|
||||
|
||||
@Serializable
|
||||
private data class LoginResponse(val accessToken: String, val refreshToken: String)
|
||||
data class LoginResponse(val accessToken: String, val refreshToken: String)
|
||||
|
||||
@Serializable
|
||||
private data class MeResponse(
|
||||
data class MeResponse(
|
||||
val id: String,
|
||||
val email: String,
|
||||
val publicKey: String,
|
||||
@@ -167,5 +167,5 @@ class ServerManager private constructor() {
|
||||
|
||||
|
||||
@Serializable
|
||||
private data class MeTeam(val id: String, val name: String, val role: TeamRole, val secretKey: String)
|
||||
data class MeTeam(val id: String, val name: String, val role: TeamRole, val secretKey: String)
|
||||
}
|
||||
@@ -78,28 +78,23 @@ abstract class SyncService {
|
||||
|
||||
protected fun encryptData(id: String, data: String, ownerId: String): String {
|
||||
val iv = DigestUtils.sha256(id).copyOf(12)
|
||||
var secretKey = EMPTY_BYTE_ARRAY
|
||||
if (ownerId != accountManager.getAccountId()) {
|
||||
val team = accountManager.getTeams().firstOrNull { it.id == ownerId }
|
||||
if (team == null) {
|
||||
return StringUtils.EMPTY
|
||||
} else {
|
||||
secretKey = team.secretKey
|
||||
}
|
||||
} else if (ownerId == accountManager.getAccountId()) {
|
||||
secretKey = accountManager.getSecretKey()
|
||||
}
|
||||
val secretKey = getSecretKey(ownerId)
|
||||
if (secretKey.isEmpty()) return StringUtils.EMPTY
|
||||
return Base64.encodeBase64String(AES.GCM.encrypt(secretKey, iv, data.toByteArray()))
|
||||
}
|
||||
|
||||
protected fun decryptData(id: String, data: String): String {
|
||||
protected fun getSecretKey(ownerId: String): ByteArray {
|
||||
if (ownerId == accountManager.getAccountId()) {
|
||||
return accountManager.getSecretKey()
|
||||
}
|
||||
val team = accountManager.getTeams().firstOrNull { it.id == ownerId }
|
||||
return team?.secretKey ?: EMPTY_BYTE_ARRAY
|
||||
}
|
||||
|
||||
protected fun decryptData(id: String, data: String, ownerId: String): String {
|
||||
val iv = DigestUtils.sha256(id).copyOf(12)
|
||||
return String(
|
||||
AES.GCM.decrypt(
|
||||
accountManager.getSecretKey(), iv,
|
||||
Base64.decodeBase64(data)
|
||||
)
|
||||
)
|
||||
val secretKey = getSecretKey(ownerId)
|
||||
if (secretKey.isEmpty()) throw IllegalStateException("根据 ownerId 无法获取对应密钥")
|
||||
return String(AES.GCM.decrypt(secretKey, iv, Base64.decodeBase64(data)))
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,22 @@ class Team(
|
||||
* 所属角色
|
||||
*/
|
||||
val role: TeamRole,
|
||||
)
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Team
|
||||
|
||||
if (id != other.id) return false
|
||||
if (name != other.name) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora.actions
|
||||
|
||||
import app.termora.NewHostDialogV2
|
||||
import app.termora.account.AccountManager
|
||||
import app.termora.tree.HostTreeNode
|
||||
import javax.swing.tree.TreePath
|
||||
|
||||
@@ -14,6 +15,8 @@ class NewHostAction : AnAction() {
|
||||
|
||||
}
|
||||
|
||||
private val accountManager get() = AccountManager.getInstance()
|
||||
|
||||
override fun actionPerformed(evt: AnActionEvent) {
|
||||
val tree = evt.getData(DataProviders.Welcome.HostTree) ?: return
|
||||
var lastNode = (tree.lastSelectedPathComponent ?: tree.model.root) as? HostTreeNode ?: return
|
||||
@@ -27,7 +30,7 @@ class NewHostAction : AnAction() {
|
||||
}
|
||||
|
||||
val lastHost = lastNode.host
|
||||
val dialog = NewHostDialogV2(evt.window)
|
||||
val dialog = NewHostDialogV2(evt.window, accountOwner = accountManager.getOwners().first { it.id == lastHost.ownerId })
|
||||
dialog.setLocationRelativeTo(evt.window)
|
||||
dialog.isVisible = true
|
||||
val host = (dialog.host ?: return).copy(
|
||||
|
||||
@@ -11,7 +11,6 @@ import app.termora.highlight.KeywordHighlightManager
|
||||
import app.termora.keymap.KeymapManager
|
||||
import app.termora.keymgr.KeyManager
|
||||
import app.termora.macro.MacroManager
|
||||
import app.termora.plugin.ExtensionManager
|
||||
import app.termora.plugin.internal.extension.DynamicExtensionHandler
|
||||
import app.termora.snippet.SnippetManager
|
||||
import app.termora.terminal.CursorStyle
|
||||
@@ -23,6 +22,7 @@ import org.jetbrains.exposed.v1.core.statements.StatementType
|
||||
import org.jetbrains.exposed.v1.jdbc.*
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@@ -32,11 +32,8 @@ import kotlin.reflect.KProperty
|
||||
|
||||
class DatabaseManager private constructor() : Disposable {
|
||||
companion object {
|
||||
val log: Logger = LoggerFactory.getLogger(DatabaseManager::class.java)
|
||||
|
||||
private const val DB_PASSWORD = "DB_PASSWORD"
|
||||
private const val DB_SALT = "DB_SALT"
|
||||
|
||||
val log = LoggerFactory.getLogger(DatabaseManager::class.java)!!
|
||||
fun getInstance(): DatabaseManager {
|
||||
return ApplicationScope.forApplicationScope()
|
||||
.getOrCreate(DatabaseManager::class) { DatabaseManager() }
|
||||
@@ -52,14 +49,6 @@ class DatabaseManager private constructor() : Disposable {
|
||||
val sftp by lazy { SFTP(this) }
|
||||
|
||||
|
||||
@Volatile
|
||||
internal var dbPassword = StringUtils.EMPTY
|
||||
private set
|
||||
|
||||
@Volatile
|
||||
internal var dbSalt = StringUtils.EMPTY
|
||||
private set
|
||||
|
||||
private val map = Collections.synchronizedMap<String, String?>(mutableMapOf())
|
||||
private val accountManager get() = AccountManager.getInstance()
|
||||
|
||||
@@ -101,11 +90,6 @@ class DatabaseManager private constructor() : Disposable {
|
||||
// 注册动态扩展
|
||||
registerDynamicExtensions()
|
||||
|
||||
for (extension in ExtensionManager.getInstance().getExtensions(DatabaseReadyExtension::class.java)) {
|
||||
extension.ready(this)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun registerDynamicExtensions() {
|
||||
@@ -308,6 +292,7 @@ class DatabaseManager private constructor() : Disposable {
|
||||
DataEntity.update({ DataEntity.id eq id }) {
|
||||
it[DataEntity.deleted] = true
|
||||
// 如果是本地用户,那么删除是不需要同步的,云端用户才需要同步
|
||||
// 云端用户也会判断,如果来源 Sync 那么默认同步了
|
||||
it[DataEntity.synced] = accountManager.isLocally()
|
||||
it[DataEntity.data] = StringUtils.EMPTY
|
||||
}
|
||||
@@ -367,7 +352,6 @@ class DatabaseManager private constructor() : Disposable {
|
||||
|
||||
|
||||
private inner class AccountDataTransferExtension : AccountExtension {
|
||||
private val hostManager get() = HostManager.getInstance()
|
||||
override fun onAccountChanged(oldAccount: Account, newAccount: Account) {
|
||||
if (oldAccount.isLocally && newAccount.isLocally) {
|
||||
return
|
||||
@@ -481,19 +465,17 @@ class DatabaseManager private constructor() : Disposable {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果团队变更,那么删除所有旧的团队数据,静默删除
|
||||
if (oldAccount.id == newAccount.id) {
|
||||
return
|
||||
}
|
||||
|
||||
for (team in oldAccount.teams) {
|
||||
// 如果被踢出团队,那么移除该团队的所有资产
|
||||
if (newAccount.teams.none { it.id == team.id }) {
|
||||
lock.withLock {
|
||||
transaction(database) {
|
||||
DataEntity.deleteWhere {
|
||||
DataEntity.ownerId.eq(team.id) and (DataEntity.ownerType.eq(
|
||||
OwnerType.Team.name
|
||||
))
|
||||
if (oldAccount.teams != newAccount.teams) {
|
||||
for (team in oldAccount.teams) {
|
||||
lock.withLock {
|
||||
transaction(database) {
|
||||
DataEntity.deleteWhere {
|
||||
DataEntity.ownerId.eq(team.id) and (DataEntity.ownerType.eq(
|
||||
OwnerType.Team.name
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package app.termora.database
|
||||
|
||||
import app.termora.database.DatabaseManager.Companion.log
|
||||
import app.termora.plugin.DispatchThread
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.ExtensionManager
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
interface DatabaseReadyExtension : Extension {
|
||||
|
||||
companion object {
|
||||
fun fireReady(databaseManager: DatabaseManager) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
for (extension in ExtensionManager.getInstance().getExtensions(DatabaseReadyExtension::class.java)) {
|
||||
try {
|
||||
extension.ready(databaseManager)
|
||||
} catch (e: Exception) {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SwingUtilities.invokeLater { fireReady(databaseManager) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库初始化完成
|
||||
*/
|
||||
fun ready(databaseManager: DatabaseManager) {}
|
||||
|
||||
override fun getDispatchThread(): DispatchThread {
|
||||
return DispatchThread.BGT
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class KeyManagerDialog(
|
||||
owner: Window,
|
||||
private val selectMode: Boolean = false,
|
||||
size: Dimension = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height")),
|
||||
private val accountOwner: AccountOwner? = null,
|
||||
) : DialogWrapper(owner) {
|
||||
var ok: Boolean = false
|
||||
|
||||
@@ -56,12 +57,40 @@ class KeyManagerDialog(
|
||||
tabbed.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
|
||||
tabbed.tabPlacement = JTabbedPane.TOP
|
||||
|
||||
if (accountOwner == null || accountOwner.type == OwnerType.User) {
|
||||
tabbed.addTab(
|
||||
I18n.getString("termora.keymgr.my-keys"),
|
||||
Icons.user,
|
||||
KeyManagerPanel(
|
||||
AccountOwner(
|
||||
accountManager.getAccountId(),
|
||||
accountManager.getEmail(),
|
||||
OwnerType.User
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (accountOwner != null && accountManager.hasTeamFeature()) {
|
||||
for (team in accountManager.getTeams()) {
|
||||
if (team.id == accountOwner.id) {
|
||||
tabbed.addTab(
|
||||
team.name,
|
||||
Icons.cwmUsers,
|
||||
KeyManagerPanel(
|
||||
AccountOwner(
|
||||
team.id,
|
||||
team.name,
|
||||
OwnerType.Team
|
||||
)
|
||||
)
|
||||
)
|
||||
return tabbed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tabbed.addTab(
|
||||
I18n.getString("termora.keymgr.my-keys"),
|
||||
Icons.user,
|
||||
KeyManagerPanel(AccountOwner(accountManager.getAccountId(), accountManager.getEmail(), OwnerType.User))
|
||||
)
|
||||
|
||||
if (accountManager.hasTeamFeature()) {
|
||||
for (team in accountManager.getTeams()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugin.internal.local
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -14,7 +15,7 @@ internal class LocalProtocolHostPanelExtension private constructor() : ProtocolH
|
||||
return LocalProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return LocalProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugin.internal.rdp
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -14,7 +15,7 @@ internal class RDPProtocolHostPanelExtension private constructor() : ProtocolHos
|
||||
return RDPProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return RDPProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugin.internal.serial
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -14,7 +15,7 @@ internal class SerialProtocolHostPanelExtension private constructor() : Protocol
|
||||
return SerialProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return SerialProtocolHostPanel()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora.plugin.internal.ssh
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.keymgr.KeyManager
|
||||
import app.termora.keymgr.KeyManagerDialog
|
||||
import app.termora.plugin.internal.BasicProxyOption
|
||||
@@ -29,7 +30,7 @@ import javax.swing.table.DefaultTableModel
|
||||
import kotlin.math.max
|
||||
|
||||
@Suppress("CascadeIf")
|
||||
open class SSHHostOptionsPane : OptionsPane() {
|
||||
open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
|
||||
protected val tunnelingOption = TunnelingOption()
|
||||
protected val generalOption = GeneralOption()
|
||||
protected val proxyOption = BasicProxyOption()
|
||||
@@ -375,6 +376,7 @@ open class SSHHostOptionsPane : OptionsPane() {
|
||||
val dialog = KeyManagerDialog(
|
||||
owner,
|
||||
selectMode = true,
|
||||
accountOwner = accountOwner,
|
||||
)
|
||||
dialog.pack()
|
||||
dialog.setLocationRelativeTo(owner)
|
||||
@@ -383,7 +385,7 @@ open class SSHHostOptionsPane : OptionsPane() {
|
||||
val selectedItem = publicKeyComboBox.selectedItem
|
||||
|
||||
publicKeyComboBox.removeAllItems()
|
||||
for (keyPair in KeyManager.getInstance().getOhKeyPairs()) {
|
||||
for (keyPair in KeyManager.getInstance().getOhKeyPairs(accountOwner.id)) {
|
||||
publicKeyComboBox.addItem(keyPair.id)
|
||||
}
|
||||
publicKeyComboBox.selectedItem = selectedItem
|
||||
@@ -465,7 +467,7 @@ open class SSHHostOptionsPane : OptionsPane() {
|
||||
if (authenticationTypeComboBox.selectedItem == AuthenticationType.PublicKey) {
|
||||
val selectedItem = publicKeyComboBox.selectedItem
|
||||
publicKeyComboBox.removeAllItems()
|
||||
for (pair in KeyManager.getInstance().getOhKeyPairs()) {
|
||||
for (pair in KeyManager.getInstance().getOhKeyPairs(accountOwner.id)) {
|
||||
publicKeyComboBox.addItem(pair.id)
|
||||
}
|
||||
publicKeyComboBox.selectedItem = selectedItem
|
||||
|
||||
@@ -2,12 +2,13 @@ package app.termora.plugin.internal.ssh
|
||||
|
||||
import app.termora.Disposer
|
||||
import app.termora.Host
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import java.awt.BorderLayout
|
||||
|
||||
class SSHProtocolHostPanel : ProtocolHostPanel() {
|
||||
class SSHProtocolHostPanel(accountOwner: AccountOwner) : ProtocolHostPanel() {
|
||||
|
||||
private val pane = SSHHostOptionsPane()
|
||||
private val pane = SSHHostOptionsPane(accountOwner)
|
||||
|
||||
init {
|
||||
initView()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugin.internal.ssh
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -14,8 +15,8 @@ internal class SSHProtocolHostPanelExtension private constructor() : ProtocolHos
|
||||
return SSHProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
return SSHProtocolHostPanel()
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return SSHProtocolHostPanel(accountOwner)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.termora.plugin.internal.wsl
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.protocol.ProtocolHostPanel
|
||||
import app.termora.protocol.ProtocolHostPanelExtension
|
||||
import app.termora.protocol.ProtocolProvider
|
||||
@@ -14,11 +15,11 @@ internal class WSLProtocolHostPanelExtension private constructor() : ProtocolHos
|
||||
return WSLProtocolProvider.instance
|
||||
}
|
||||
|
||||
override fun canCreateProtocolHostPanel(): Boolean {
|
||||
override fun canCreateProtocolHostPanel(accountOwner: AccountOwner): Boolean {
|
||||
return WSLSupport.isSupported
|
||||
}
|
||||
|
||||
override fun createProtocolHostPanel(): ProtocolHostPanel {
|
||||
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
|
||||
return WSLProtocolHostPanel()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package app.termora.protocol
|
||||
|
||||
import app.termora.account.AccountOwner
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.ExtensionManager
|
||||
|
||||
|
||||
interface ProtocolHostPanelExtension : Extension {
|
||||
companion object {
|
||||
val extensions
|
||||
@@ -19,11 +21,23 @@ interface ProtocolHostPanelExtension : Extension {
|
||||
/**
|
||||
* 是否可以创建协议主机面板
|
||||
*/
|
||||
@Deprecated("Old stuff")
|
||||
fun canCreateProtocolHostPanel(): Boolean = true
|
||||
|
||||
/**
|
||||
* 是否可以创建协议主机面板
|
||||
*/
|
||||
fun canCreateProtocolHostPanel(accountOwner: AccountOwner) = canCreateProtocolHostPanel()
|
||||
|
||||
/**
|
||||
* 创建协议主机面板
|
||||
*/
|
||||
fun createProtocolHostPanel(): ProtocolHostPanel
|
||||
@Deprecated("Old stuff")
|
||||
fun createProtocolHostPanel(): ProtocolHostPanel = throw UnsupportedOperationException()
|
||||
|
||||
/**
|
||||
* 创建协议主机面板
|
||||
*/
|
||||
fun createProtocolHostPanel(accountOwner: AccountOwner) = createProtocolHostPanel()
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora.transfer
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.account.AccountManager
|
||||
import app.termora.actions.AnAction
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
@@ -164,9 +165,13 @@ class TransportTabbed(
|
||||
// 编辑
|
||||
val edit = popupMenu.add(I18n.getString("termora.keymgr.edit"))
|
||||
edit.addActionListener(object : AnAction() {
|
||||
private val accountManager get() = AccountManager.getInstance()
|
||||
override fun actionPerformed(evt: AnActionEvent) {
|
||||
val window = evt.window
|
||||
val dialog = NewHostDialogV2(window, panel.host)
|
||||
val dialog = NewHostDialogV2(
|
||||
window,
|
||||
panel.host,
|
||||
accountOwner = accountManager.getOwners().first { it.id == panel.host.ownerId })
|
||||
dialog.setLocationRelativeTo(window)
|
||||
dialog.title = panel.host.name
|
||||
dialog.isVisible = true
|
||||
|
||||
@@ -2,6 +2,7 @@ package app.termora.tree
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.Application.ohMyJson
|
||||
import app.termora.account.AccountManager
|
||||
import app.termora.actions.OpenHostAction
|
||||
import app.termora.database.DatabaseChangedExtension
|
||||
import app.termora.database.DatabaseManager
|
||||
@@ -295,8 +296,11 @@ class NewHostTree : SimpleTree(), Disposable {
|
||||
}
|
||||
}
|
||||
newHost.addActionListener(object : ActionListener {
|
||||
private val accountManager get() = AccountManager.getInstance()
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
val dialog = NewHostDialogV2(owner)
|
||||
val dialog = NewHostDialogV2(
|
||||
owner,
|
||||
accountOwner = accountManager.getOwners().first { it.id == lastHost.ownerId })
|
||||
dialog.setLocationRelativeTo(owner)
|
||||
dialog.isVisible = true
|
||||
val host = (dialog.host ?: return).copy(
|
||||
@@ -311,8 +315,12 @@ class NewHostTree : SimpleTree(), Disposable {
|
||||
}
|
||||
})
|
||||
property.addActionListener(object : ActionListener {
|
||||
private val accountManager get() = AccountManager.getInstance()
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
val dialog = NewHostDialogV2(owner, lastHost)
|
||||
val dialog = NewHostDialogV2(
|
||||
owner,
|
||||
lastHost,
|
||||
accountOwner = accountManager.getOwners().first { it.id == lastHost.ownerId })
|
||||
dialog.setLocationRelativeTo(owner)
|
||||
dialog.title = lastHost.name
|
||||
dialog.isVisible = true
|
||||
@@ -639,12 +647,12 @@ class NewHostTree : SimpleTree(), Disposable {
|
||||
ownerType = folder.host.ownerType,
|
||||
ownerId = folder.host.ownerId,
|
||||
),
|
||||
DatabaseChangedExtension.Source.Sync
|
||||
DatabaseChangedExtension.Source.User
|
||||
)
|
||||
for (host in node.getAllChildren().map { it.host }) {
|
||||
hostManager.addHost(
|
||||
host.copy(ownerType = folder.host.ownerType, ownerId = folder.host.ownerId),
|
||||
DatabaseChangedExtension.Source.Sync
|
||||
DatabaseChangedExtension.Source.User
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@ class NewHostTreeModel private constructor() : SimpleTreeModel<Host>(
|
||||
}
|
||||
hostManager.removeHost(node.id)
|
||||
}
|
||||
removeNodeFromParent0(node)
|
||||
}
|
||||
|
||||
private fun removeNodeFromParent0(node: MutableTreeNode?) {
|
||||
super.removeNodeFromParent(node)
|
||||
}
|
||||
|
||||
@@ -232,7 +236,13 @@ class NewHostTreeModel private constructor() : SimpleTreeModel<Host>(
|
||||
|
||||
private inner class MyAccountAccountExtension : AccountExtension {
|
||||
override fun onAccountChanged(oldAccount: Account, newAccount: Account) {
|
||||
if (oldAccount.id != newAccount.id) reload(getRoot())
|
||||
if (oldAccount.id != newAccount.id) {
|
||||
reload(getRoot())
|
||||
} else if (oldAccount.teams != newAccount.teams) {
|
||||
val nodes = getRoot().children().toList().filterIsInstance<TeamTreeNode>()
|
||||
nodes.forEach { removeNodeFromParent0(it) }
|
||||
reload(getRoot())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user