Compare commits

...

25 Commits

Author SHA1 Message Date
dependabot[bot]
124acb2d7d chore(deps): bump com.h2database:h2 from 2.3.232 to 2.4.240
Bumps [com.h2database:h2](https://github.com/h2database/h2database) from 2.3.232 to 2.4.240.
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.3.232...version-2.4.240)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-version: 2.4.240
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 01:17:14 +00:00
dependabot[bot]
5110595404 chore(deps): bump com.fasterxml.uuid:java-uuid-generator
Bumps [com.fasterxml.uuid:java-uuid-generator](https://github.com/cowtowncoder/java-uuid-generator) from 5.1.0 to 5.1.1.
- [Commits](https://github.com/cowtowncoder/java-uuid-generator/compare/java-uuid-generator-5.1.0...java-uuid-generator-5.1.1)

---
updated-dependencies:
- dependency-name: com.fasterxml.uuid:java-uuid-generator
  dependency-version: 5.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 09:16:25 +08:00
dependabot[bot]
034e0939be chore(deps): bump exposed from 1.0.0-rc-1 to 1.0.0-rc-2
Bumps `exposed` from 1.0.0-rc-1 to 1.0.0-rc-2.

Updates `org.jetbrains.exposed:exposed-core` from 1.0.0-rc-1 to 1.0.0-rc-2
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/compare/1.0.0-rc-1...1.0.0-rc-2)

Updates `org.jetbrains.exposed:exposed-crypt` from 1.0.0-rc-1 to 1.0.0-rc-2
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/compare/1.0.0-rc-1...1.0.0-rc-2)

Updates `org.jetbrains.exposed:exposed-jdbc` from 1.0.0-rc-1 to 1.0.0-rc-2
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/compare/1.0.0-rc-1...1.0.0-rc-2)

Updates `org.jetbrains.exposed:exposed-migration-core` from 1.0.0-rc-1 to 1.0.0-rc-2
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/compare/1.0.0-rc-1...1.0.0-rc-2)

---
updated-dependencies:
- dependency-name: org.jetbrains.exposed:exposed-core
  dependency-version: 1.0.0-rc-2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.exposed:exposed-crypt
  dependency-version: 1.0.0-rc-2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.exposed:exposed-jdbc
  dependency-version: 1.0.0-rc-2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.exposed:exposed-migration-core
  dependency-version: 1.0.0-rc-2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 09:16:17 +08:00
dependabot[bot]
4ccfa82c8a chore(deps): bump okhttp from 5.2.0 to 5.2.1
Bumps `okhttp` from 5.2.0 to 5.2.1.

Updates `com.squareup.okhttp3:okhttp` from 5.2.0 to 5.2.1
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.0...parent-5.2.1)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.2.0 to 5.2.1
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.2.0...parent-5.2.1)

---
updated-dependencies:
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 09:15:59 +08:00
hstyi
d21ae5499a feat: HTTP server for authentication 2025-10-23 17:23:01 +08:00
hstyi
d80a9d48ab fix: data pull may not be possible 2025-10-23 16:51:04 +08:00
dependabot[bot]
b305d6fd34 chore(deps): bump com.github.hstyi:geolite2
Bumps [com.github.hstyi:geolite2](https://github.com/hstyi/GeoLite2) from v1.0-202510060050 to v1.0-202510200054.
- [Release notes](https://github.com/hstyi/GeoLite2/releases)
- [Commits](https://github.com/hstyi/GeoLite2/commits)

---
updated-dependencies:
- dependency-name: com.github.hstyi:geolite2
  dependency-version: v1.0-202510200054
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-22 13:48:30 +08:00
dependabot[bot]
756fd305d1 chore(deps): bump com.qcloud:cos_api from 5.6.255.1 to 5.6.257
Bumps [com.qcloud:cos_api](https://github.com/tencentyun/cos-java-sdk-v5) from 5.6.255.1 to 5.6.257.
- [Release notes](https://github.com/tencentyun/cos-java-sdk-v5/releases)
- [Changelog](https://github.com/tencentyun/cos-java-sdk-v5/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tencentyun/cos-java-sdk-v5/commits)

---
updated-dependencies:
- dependency-name: com.qcloud:cos_api
  dependency-version: 5.6.257
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 09:31:18 +08:00
dependabot[bot]
f9549fbb7d chore(deps): bump org.testcontainers:testcontainers-bom
Bumps [org.testcontainers:testcontainers-bom](https://github.com/testcontainers/testcontainers-java) from 1.21.3 to 2.0.0.
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.21.3...2.0.0)

---
updated-dependencies:
- dependency-name: org.testcontainers:testcontainers-bom
  dependency-version: 2.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-16 09:54:22 +08:00
hstyi
e18b454fcc fix: Nord Dark selected color 2025-10-14 11:06:59 +08:00
dependabot[bot]
4f4ccfa7d4 chore(deps): bump flatlaf from 3.6.1 to 3.6.2
Bumps `flatlaf` from 3.6.1 to 3.6.2.

Updates `com.formdev:flatlaf` from 3.6.1 to 3.6.2
- [Release notes](https://github.com/JFormDesigner/FlatLaf/releases)
- [Changelog](https://github.com/JFormDesigner/FlatLaf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JFormDesigner/FlatLaf/compare/3.6.1...3.6.2)

Updates `com.formdev:flatlaf-extras` from 3.6.1 to 3.6.2
- [Release notes](https://github.com/JFormDesigner/FlatLaf/releases)
- [Changelog](https://github.com/JFormDesigner/FlatLaf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JFormDesigner/FlatLaf/compare/3.6.1...3.6.2)

Updates `com.formdev:flatlaf-swingx` from 3.6.1 to 3.6.2
- [Release notes](https://github.com/JFormDesigner/FlatLaf/releases)
- [Changelog](https://github.com/JFormDesigner/FlatLaf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JFormDesigner/FlatLaf/compare/3.6.1...3.6.2)

---
updated-dependencies:
- dependency-name: com.formdev:flatlaf
  dependency-version: 3.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.formdev:flatlaf-extras
  dependency-version: 3.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.formdev:flatlaf-swingx
  dependency-version: 3.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 11:14:29 +08:00
dependabot[bot]
5dfd5fefb2 chore(deps): bump jgit from 7.2.0.202503040940-r to 7.4.0.202509020913-r
Bumps `jgit` from 7.2.0.202503040940-r to 7.4.0.202509020913-r.

Updates `org.eclipse.jgit:org.eclipse.jgit` from 7.2.0.202503040940-r to 7.4.0.202509020913-r
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.2.0.202503040940-r...v7.4.0.202509020913-r)

Updates `org.eclipse.jgit:org.eclipse.jgit.ssh.apache` from 7.2.0.202503040940-r to 7.4.0.202509020913-r
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.2.0.202503040940-r...v7.4.0.202509020913-r)

Updates `org.eclipse.jgit:org.eclipse.jgit.ssh.apache.agent` from 7.2.0.202503040940-r to 7.4.0.202509020913-r
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.2.0.202503040940-r...v7.4.0.202509020913-r)

---
updated-dependencies:
- dependency-name: org.eclipse.jgit:org.eclipse.jgit
  dependency-version: 7.4.0.202509020913-r
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.eclipse.jgit:org.eclipse.jgit.ssh.apache
  dependency-version: 7.4.0.202509020913-r
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.eclipse.jgit:org.eclipse.jgit.ssh.apache.agent
  dependency-version: 7.4.0.202509020913-r
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-10 10:21:56 +08:00
dependabot[bot]
a7ea4c70d2 chore(deps): bump com.qcloud:cos_api from 5.6.255 to 5.6.255.1
Bumps [com.qcloud:cos_api](https://github.com/tencentyun/cos-java-sdk-v5) from 5.6.255 to 5.6.255.1.
- [Release notes](https://github.com/tencentyun/cos-java-sdk-v5/releases)
- [Changelog](https://github.com/tencentyun/cos-java-sdk-v5/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tencentyun/cos-java-sdk-v5/commits)

---
updated-dependencies:
- dependency-name: com.qcloud:cos_api
  dependency-version: 5.6.255.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-10 10:21:31 +08:00
dependabot[bot]
b7796f58f0 chore(deps): bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0
Bumps org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-lang3
  dependency-version: 3.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-10 10:21:25 +08:00
dependabot[bot]
c7bedc57e0 chore(deps): bump io.minio:minio from 8.5.17 to 8.6.0
Bumps [io.minio:minio](https://github.com/minio/minio-java) from 8.5.17 to 8.6.0.
- [Release notes](https://github.com/minio/minio-java/releases)
- [Commits](https://github.com/minio/minio-java/compare/8.5.17...8.6.0)

---
updated-dependencies:
- dependency-name: io.minio:minio
  dependency-version: 8.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-10 10:21:14 +08:00
dependabot[bot]
935f305ada chore(deps): bump com.github.hstyi:geolite2
Bumps [com.github.hstyi:geolite2](https://github.com/hstyi/GeoLite2) from v1.0-202508180058 to v1.0-202510060050.
- [Release notes](https://github.com/hstyi/GeoLite2/releases)
- [Commits](https://github.com/hstyi/GeoLite2/commits)

---
updated-dependencies:
- dependency-name: com.github.hstyi:geolite2
  dependency-version: v1.0-202510060050
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-10 10:21:01 +08:00
dependabot[bot]
8cf47a7ca1 chore(deps): bump okhttp from 5.1.0 to 5.2.0
Bumps `okhttp` from 5.1.0 to 5.2.0.

Updates `com.squareup.okhttp3:okhttp` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

Updates `com.squareup.okhttp3:logging-interceptor` from 5.1.0 to 5.2.0
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-5.1.0...parent-5.2.0)

---
updated-dependencies:
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.squareup.okhttp3:logging-interceptor
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-09 14:56:29 +08:00
dependabot[bot]
c6c5ad711d chore(deps): bump jna from 5.17.0 to 5.18.1
Bumps `jna` from 5.17.0 to 5.18.1.

Updates `net.java.dev.jna:jna` from 5.17.0 to 5.18.1
- [Changelog](https://github.com/java-native-access/jna/blob/master/CHANGES.md)
- [Commits](https://github.com/java-native-access/jna/compare/5.17.0...5.18.1)

Updates `net.java.dev.jna:jna-platform` from 5.17.0 to 5.18.1
- [Changelog](https://github.com/java-native-access/jna/blob/master/CHANGES.md)
- [Commits](https://github.com/java-native-access/jna/compare/5.17.0...5.18.1)

---
updated-dependencies:
- dependency-name: net.java.dev.jna:jna
  dependency-version: 5.18.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: net.java.dev.jna:jna-platform
  dependency-version: 5.18.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-04 14:08:52 +08:00
hstyi
5fc76d955a feat: supports ECDSA keypair 2025-10-01 15:25:31 +08:00
hstyi
0aabe1b0dc chore: jbrsdk-21.0.8 2025-09-28 14:23:16 +08:00
hsurich
820c4274e7 chore: allow case-insensitive search including remarks 2025-09-28 07:02:19 +08:00
hstyi
fcec30d70a chore: editor shows tooltip 2025-09-24 09:30:36 +08:00
dependabot[bot]
f6dc0098f7 chore(deps): bump com.github.oshi:oshi-core from 6.8.1 to 6.9.0
Bumps [com.github.oshi:oshi-core](https://github.com/oshi/oshi) from 6.8.1 to 6.9.0.
- [Release notes](https://github.com/oshi/oshi/releases)
- [Changelog](https://github.com/oshi/oshi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/oshi/oshi/compare/oshi-parent-6.8.1...oshi-parent-6.9.0)

---
updated-dependencies:
- dependency-name: com.github.oshi:oshi-core
  dependency-version: 6.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-23 09:07:17 +08:00
hstyi
ca7b30bdb0 chore: improve code 2025-09-19 14:48:08 +08:00
imblowsnow
f73e7f4214 feat: show remark on node hover 2025-09-18 09:04:59 +08:00
32 changed files with 384 additions and 218 deletions

View File

@@ -3,8 +3,8 @@ name: Linux
on: [ push, pull_request ]
env:
JBR_MAJOR: 21.0.7
JBR_PATCH: b1038.58
JBR_MAJOR: 21.0.8
JBR_PATCH: b1138.52
jobs:
build:

View File

@@ -8,8 +8,8 @@ env:
# 只有发布版本时才需要公证
TERMORA_MAC_NOTARY: "${{ startsWith(github.event.head_commit.message, 'release: ') && github.repository == 'TermoraDev/termora' }}"
TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE: ${{ secrets.TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE }}
JBR_MAJOR: 21.0.7
JBR_PATCH: b1038.58
JBR_MAJOR: 21.0.8
JBR_PATCH: b1138.52
jobs:
build:

View File

@@ -3,8 +3,8 @@ name: Windows
on: [ push, pull_request ]
env:
JBR_MAJOR: 21.0.7
JBR_PATCH: b1038.58
JBR_MAJOR: 21.0.8
JBR_PATCH: b1138.52
jobs:
build:

View File

@@ -339,6 +339,7 @@ tasks.register<Exec>("jlink") {
"java.security.jgss",
"jdk.crypto.ec",
"jdk.unsupported",
"jdk.httpserver",
)
commandLine(

View File

@@ -4,10 +4,10 @@ slf4j = "2.0.17"
pty4j = "0.13.10"
tinylog = "2.7.0"
kotlinx-coroutines = "1.10.2"
flatlaf = "3.6.1"
flatlaf = "3.6.2"
kotlinx-serialization-json = "1.9.0"
commons-codec = "1.19.0"
commons-lang3 = "3.18.0"
commons-lang3 = "3.19.0"
commons-csv = "1.14.1"
commons-net = "3.12.0"
commons-text = "1.14.0"
@@ -16,18 +16,18 @@ commons-vfs2 = "2.10.0"
swingx = "1.6.5-1"
jgoodies-forms = "1.9.0"
jfa = "1.2.0"
oshi = "6.8.1"
oshi = "6.9.0"
versioncompare = "1.4.1"
jna = "5.17.0"
jna = "5.18.1"
jSystemThemeDetector = "3.9.1"
commons-io = "2.20.0"
jbr-api = "17.1.10.1"
hutool = "5.8.40"
jsch = "2.27.3"
okhttp = "5.1.0"
okhttp = "5.2.1"
sshj = "0.39.0"
sshd-core = "2.15.0"
jgit = "7.2.0.202503040940-r"
jgit = "7.4.0.202509020913-r"
commonmark = "0.26.0"
jnafilechooser = "1.1.2"
xodus = "2.0.1"
@@ -35,16 +35,16 @@ bip39 = "1.0.9"
colorpicker = "2.0.1"
rhino = "1.8.0"
delight-rhino-sandbox = "0.0.17"
testcontainers = "1.21.3"
testcontainers = "2.0.0"
mixpanel = "1.5.3"
jSerialComm = "2.11.2"
ini4j = "0.5.5-2"
restart4j = "0.0.1"
eddsa = "0.3.0"
exposed = "1.0.0-rc-1"
h2 = "2.3.232"
exposed = "1.0.0-rc-2"
h2 = "2.4.240"
sqlite = "3.50.3.0"
jug = "5.1.0"
jug = "5.1.1"
semver4j = "6.0.0"
jsvg = "2.0.0"
dom4j = "2.2.0"

View File

@@ -8,7 +8,7 @@ project.version = "0.0.4"
dependencies {
testImplementation(kotlin("test"))
implementation("com.qcloud:cos_api:5.6.255")
implementation("com.qcloud:cos_api:5.6.257")
compileOnly(project(":"))
}

View File

@@ -4,7 +4,7 @@ plugins {
project.version = "0.0.7"
project.version = "0.0.8"
dependencies {

View File

@@ -1,9 +1,6 @@
package app.termora.plugins.editor
import app.termora.DocumentAdaptor
import app.termora.DynamicColor
import app.termora.EnableManager
import app.termora.Icons
import app.termora.*
import app.termora.database.DatabaseManager
import com.formdev.flatlaf.FlatLaf
import com.formdev.flatlaf.extras.components.FlatTextField
@@ -39,6 +36,10 @@ class EditorPanel(private val window: JFrame, private val file: File) : JPanel(B
companion object {
private val log = LoggerFactory.getLogger(EditorPanel::class.java)
private val saveIcon = DynamicIcon(
"icons/save.svg", "icons/save_dark.svg",
loader = EditorPlugin::class.java.classLoader
)
}
private var text = file.readText(Charsets.UTF_8)
@@ -54,6 +55,7 @@ class EditorPanel(private val window: JFrame, private val file: File) : JPanel(B
private val prevBtn = JButton(Icons.up)
private val context = SearchContext()
private val softWrapBtn = JToggleButton(Icons.softWrap)
private val saveBtn = JButton(saveIcon)
private val scrollUpBtn = JButton(Icons.scrollUp)
private val scrollEndBtn = JButton(Icons.scrollDown)
private val prettyBtn = JButton(Icons.reformatCode)
@@ -141,11 +143,18 @@ class EditorPanel(private val window: JFrame, private val file: File) : JPanel(B
)
toolbar.orientation = VERTICAL
toolbar.add(saveBtn)
toolbar.add(scrollUpBtn)
toolbar.add(prettyBtn)
toolbar.add(softWrapBtn)
toolbar.add(scrollEndBtn)
saveBtn.toolTipText = EditorI18n.getString("termora.plugins.editor.save")
scrollUpBtn.toolTipText = EditorI18n.getString("termora.plugins.editor.first-line")
scrollEndBtn.toolTipText = EditorI18n.getString("termora.plugins.editor.last-line")
softWrapBtn.toolTipText = EditorI18n.getString("termora.plugins.editor.soft-wrap")
prettyBtn.toolTipText = EditorI18n.getString("termora.plugins.editor.format")
val viewPanel = JPanel(BorderLayout())
viewPanel.add(scrollPane, BorderLayout.CENTER)
viewPanel.add(toolbar, BorderLayout.EAST)
@@ -211,6 +220,8 @@ class EditorPanel(private val window: JFrame, private val file: File) : JPanel(B
}
})
saveBtn.addActionListener(textArea.actionMap.get("Save"))
textArea.actionMap.put("Format", object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
format()

View File

@@ -1,2 +1,6 @@
termora.plugins.editor.not-save=The file has not been saved. Are you sure you want to exit?
termora.plugins.editor.save=Save
termora.plugins.editor.first-line=Jump to first line
termora.plugins.editor.last-line=Jump to last line
termora.plugins.editor.soft-wrap=Soft-wrap
termora.plugins.editor.format=Format

View File

@@ -1 +1,6 @@
termora.plugins.editor.not-save=Файл не сохранён. Вы уверены, что хотите выйти?
termora.plugins.editor.not-save=Файл не сохранён. Вы уверены, что хотите выйти?
termora.plugins.editor.save=Сохранить
termora.plugins.editor.first-line=Перейти на первую строку
termora.plugins.editor.last-line=Перейти на последнюю строку
termora.plugins.editor.soft-wrap=Мягкий перенос
termora.plugins.editor.format=Формат

View File

@@ -1 +1,6 @@
termora.plugins.editor.not-save=文件尚未保存,你确定要退出吗?
termora.plugins.editor.save=保存
termora.plugins.editor.first-line=跳转到第一行
termora.plugins.editor.last-line=跳转到最后一行
termora.plugins.editor.soft-wrap=自动换行
termora.plugins.editor.format=格式化

View File

@@ -1 +1,6 @@
termora.plugins.editor.not-save=檔案尚未儲存,你確定要退出嗎?
termora.plugins.editor.not-save=檔案尚未儲存,你確定要退出嗎?
termora.plugins.editor.save=儲存
termora.plugins.editor.first-line=跳到第一行
termora.plugins.editor.last-line=跳到最後一行
termora.plugins.editor.soft-wrap=自動換行
termora.plugins.editor.format=格式化

View File

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 3V5.5H10.5V3M4.5 13V9.5H11.5V13M2.5 13.5V2.5H11.5L13.5 4.5V13.5H2.5Z" stroke="#6C707E" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.5 3V5.5H10.5V3M4.5 13V9.5H11.5V13M2.5 13.5V2.5H11.5L13.5 4.5V13.5H2.5Z" stroke="#CED0D6" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -9,7 +9,7 @@ dependencies {
compileOnly(project(":"))
implementation("com.maxmind.geoip2:geoip2:4.4.0")
// https://github.com/hstyi/geolite2
implementation("com.github.hstyi:geolite2:v1.0-202508180058")
implementation("com.github.hstyi:geolite2:v1.0-202510200054")
}
apply(from = "$rootDir/plugins/common.gradle.kts")

View File

@@ -13,7 +13,7 @@ dependencies {
testImplementation(libs.testcontainers.junit.jupiter)
testImplementation(project(":"))
implementation("io.minio:minio:8.5.17")
implementation("io.minio:minio:8.6.0")
compileOnly(project(":"))
}

View File

@@ -874,6 +874,8 @@ class NordDarkLaf : FlatPropertiesLaf("Nord Dark", Properties().apply {
TerminalColor.Basic.SELECTION_BACKGROUND,
TerminalColor.Cursor.BACKGROUND -> 0xeceff4
TerminalColor.Basic.SELECTION_FOREGROUND -> 0x3b4252
TerminalColor.Basic.FOREGROUND -> 0xd8dee9

View File

@@ -170,16 +170,18 @@ class WelcomePanel() : JPanel(BorderLayout()), Disposable, TerminalTab, DataProv
filterableTreeModel.addFilter(object : Filter {
override fun filter(node: Any): Boolean {
val text = searchTextField.text
val text = searchTextField.text.trim()
if (text.isBlank()) return true
if (node !is HostTreeNode) return false
if (node is TeamTreeNode || node.id == "0") return true
return node.host.name.contains(text) || node.host.host.contains(text)
|| node.host.username.contains(text)
return node.host.name.contains(text, ignoreCase = true)
|| node.host.host.contains(text, ignoreCase = true)
|| node.host.username.contains(text, ignoreCase = true)
|| node.host.remark.contains(text, ignoreCase = true)
}
override fun canFilter(): Boolean {
return searchTextField.text.isNotBlank()
return searchTextField.text.trim().isNotBlank()
}
})
@@ -264,4 +266,4 @@ class WelcomePanel() : JPanel(BorderLayout()), Disposable, TerminalTab, DataProv
}
}
}

View File

@@ -1,6 +1,7 @@
package app.termora.account
import app.termora.*
import app.termora.Application.ohMyJson
import app.termora.OptionsPane.Companion.FORM_MARGIN
import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent
@@ -8,21 +9,36 @@ import app.termora.database.DatabaseManager
import app.termora.plugin.internal.extension.DynamicExtensionHandler
import com.jgoodies.forms.builder.FormBuilder
import com.jgoodies.forms.layout.FormLayout
import com.sun.net.httpserver.HttpServer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import org.apache.commons.codec.binary.Hex
import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.time.DateFormatUtils
import org.jdesktop.swingx.JXBusyLabel
import org.jdesktop.swingx.JXHyperlink
import org.slf4j.LoggerFactory
import java.awt.BorderLayout
import java.awt.CardLayout
import java.net.InetSocketAddress
import java.net.URI
import java.net.URLEncoder
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import javax.swing.*
import kotlin.time.Duration.Companion.milliseconds
class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
companion object {
private val log = LoggerFactory.getLogger(AccountOption::class.java)
}
private val owner get() = SwingUtilities.getWindowAncestor(this)
private val databaseManager get() = DatabaseManager.getInstance()
@@ -30,18 +46,31 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
private val accountProperties get() = AccountProperties.getInstance()
private val userInfoPanel = JPanel(BorderLayout())
private val lastSynchronizationOnLabel = JLabel()
private val serverManager get() = ServerManager.getInstance()
private val cardLayout = CardLayout()
private val contentPanel = JPanel(cardLayout)
private val loginPanel = JPanel(BorderLayout())
private val busyLabel = JXBusyLabel()
private var httpServer: HttpServer? = null
init {
initView()
initEvents()
}
private fun initView() {
refreshUserInfoPanel()
add(userInfoPanel, BorderLayout.CENTER)
}
refreshLoginPanel()
contentPanel.add(userInfoPanel, "UserInfo")
contentPanel.add(loginPanel, "Login")
cardLayout.show(contentPanel, "UserInfo")
add(contentPanel, BorderLayout.CENTER)
}
private fun initEvents() {
// 服务器签名发生变更
@@ -99,11 +128,7 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
planBox.add(Box.createHorizontalStrut(16))
val upgrade = JXHyperlink(object : AnAction(I18n.getString("termora.settings.account.upgrade")) {
override fun actionPerformed(evt: AnActionEvent) {
if (I18n.isChinaMainland()) {
Application.browse(URI.create("https://www.termora.cn/pricing?version=${Application.getVersion()}"))
} else {
Application.browse(URI.create("https://www.termora.app/pricing?version=${Application.getVersion()}"))
}
Application.browse(URI.create("${accountManager.getServer()}/v1/client/redirect?to=upgrade&version=${Application.getVersion()}"))
}
})
upgrade.isFocusable = false
@@ -145,6 +170,29 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
.build()
}
private fun getLoginComponent(): JComponent {
val layout = FormLayout(
"default:grow",
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
)
val cancelBtn = JXHyperlink(object : AnAction(I18n.getString("termora.cancel")) {
override fun actionPerformed(evt: AnActionEvent) {
httpServer?.stop(0)
cardLayout.show(contentPanel, "UserInfo")
}
})
val tipLabel = JLabel(I18n.getString("termora.settings.account.wait-login"))
tipLabel.foreground = UIManager.getColor("TextField.placeholderForeground")
return FormBuilder.create().layout(layout).debug(false).padding("10dlu,0,0,0")
.add(busyLabel).xy(1, 1, "center, fill")
.add(tipLabel).xy(1, 3, "center, fill")
.add(cancelBtn).xy(1, 5, "center, fill")
.build()
}
private fun createActionPanel(isFreePlan: Boolean): JComponent {
val actionBox = Box.createHorizontalBox()
actionBox.add(Box.createHorizontalGlue())
@@ -219,11 +267,139 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
return actionBox
}
private fun showLoginPanel() {
refreshLoginPanel()
busyLabel.isBusy = true
cardLayout.show(contentPanel, "Login")
}
private fun onLogin() {
httpServer?.stop(0)
val dialog = LoginServerDialog(owner)
dialog.isVisible = true
val server = dialog.server ?: return
showLoginPanel()
onLogin(server)
}
private fun onLogin(server: Server) {
val httpServer = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0)
.apply { httpServer = this }
val future = processLogin(server, httpServer)
val loginJob = swingCoroutineScope.launch(Dispatchers.IO) {
try {
val loginResult = future.get(5, TimeUnit.MINUTES)
serverManager.login(server, loginResult.refreshToken, loginResult.password)
} catch (e: Exception) {
if (log.isErrorEnabled) log.error(e.message, e)
withContext(Dispatchers.Swing) {
OptionPane.showMessageDialog(
owner,
StringUtils.defaultIfBlank(
e.message ?: StringUtils.EMPTY,
I18n.getString("termora.settings.account.login-failed")
),
messageType = JOptionPane.ERROR_MESSAGE,
)
}
} finally {
withContext(Dispatchers.Swing) { cardLayout.show(contentPanel, "UserInfo") }
httpServer.stop(0)
}
}
Disposer.register(this, object : Disposable {
override fun dispose() {
loginJob.cancel()
httpServer.stop(0)
}
})
}
override fun dispose() {
busyLabel.isBusy = false
super.dispose()
}
private fun processLogin(server: Server, httpServer: HttpServer): CompletableFuture<LoginResult> {
val keypair = RSA.generateKeyPair(2048)
val future = CompletableFuture<LoginResult>()
httpServer.createContext("/callback") { exchange ->
val method = exchange.requestMethod
if (method.equals("OPTIONS", ignoreCase = true)) {
exchange.responseHeaders.add("Access-Control-Allow-Origin", "*")
exchange.responseHeaders.add("Access-Control-Allow-Methods", "POST, OPTIONS")
exchange.responseHeaders.add("Access-Control-Allow-Headers", "Content-Type")
exchange.sendResponseHeaders(204, -1)
} else {
var loginResult: LoginResult? = null
if (method.equals("POST", ignoreCase = true)) {
try {
val text = String(exchange.requestBody.readAllBytes())
loginResult = ohMyJson.decodeFromString<LoginResult>(text)
val secretKey = RSA.decrypt(keypair.private, Hex.decodeHex(loginResult.secretKey))
val secretIv = RSA.decrypt(keypair.private, Hex.decodeHex(loginResult.secretIv))
val password = AES.CBC.decrypt(secretKey, secretIv, Hex.decodeHex(loginResult.password))
val refreshToken = AES.CBC.decrypt(
secretKey, secretIv, Hex.decodeHex(loginResult.refreshToken)
)
loginResult = loginResult.copy(
password = String(password),
refreshToken = String(refreshToken)
)
} catch (e: Exception) {
if (log.isErrorEnabled) {
log.error(e.message, e)
}
}
}
val response = "OK".toByteArray()
exchange.responseHeaders.add("Access-Control-Allow-Origin", "*")
exchange.sendResponseHeaders(200, response.size.toLong())
exchange.responseBody.use { it.write(response) }
if (loginResult != null) {
future.complete(loginResult)
}
}
IOUtils.closeQuietly { exchange.close() }
}
httpServer.start()
val sb = StringBuilder()
val redirect = StringBuilder()
redirect.append("/device?callback=").append("http://127.0.0.1:${httpServer.address.port}/callback")
redirect.append("&from=device&publicKey=").append(keypair.public.encoded.toHexString())
redirect.append("&format=hex&device=termora&device-version=").append(Application.getVersion())
sb.append(server.server)
sb.append("/v1/client/redirect?to=login&from=device")
sb.append("&redirect=").append(URLEncoder.encode(redirect.toString(), Charsets.UTF_8))
Application.browse(URI.create(sb.toString()))
return future
}
@Serializable
private data class LoginResult(
val password: String,
val refreshToken: String,
val secretKey: String,
val secretIv: String,
)
private fun refreshUserInfoPanel() {
userInfoPanel.removeAll()
userInfoPanel.add(getCenterComponent(), BorderLayout.CENTER)
@@ -231,6 +407,13 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
userInfoPanel.repaint()
}
private fun refreshLoginPanel() {
loginPanel.removeAll()
loginPanel.add(getLoginComponent(), BorderLayout.CENTER)
loginPanel.revalidate()
loginPanel.repaint()
}
override fun getIcon(isSelected: Boolean): Icon {
return Icons.user
}

View File

@@ -9,12 +9,6 @@ import app.termora.database.DatabaseManager
import com.formdev.flatlaf.FlatClientProperties
import com.jgoodies.forms.builder.FormBuilder
import com.jgoodies.forms.layout.FormLayout
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import org.apache.commons.lang3.StringUtils
import org.jdesktop.swingx.JXHyperlink
import org.slf4j.LoggerFactory
@@ -24,12 +18,10 @@ import java.awt.Window
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.net.URI
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.*
import javax.swing.event.ListDataEvent
import javax.swing.event.ListDataListener
import kotlin.math.max
import kotlin.time.Duration.Companion.milliseconds
class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
companion object {
@@ -37,18 +29,14 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
}
private val serverComboBox = OutlineComboBox<Server>()
private val usernameTextField = OutlineTextField(128)
private val passwordField = OutlinePasswordField()
private val mfaTextField = OutlineTextField(128)
private val okAction = OkAction(I18n.getString("termora.settings.account.login"))
private val cancelAction = super.createCancelAction()
private val cancelButton = super.createJButtonForAction(cancelAction)
private val isLoggingIn = AtomicBoolean(false)
private val singaporeServer =
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()
var server: Server? = null
init {
isModal = true
@@ -60,12 +48,10 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
size = Dimension(max(preferredSize.width, UIManager.getInt("Dialog.width") - 250), preferredSize.height)
setLocationRelativeTo(owner)
passwordField.putClientProperty(FlatClientProperties.STYLE, mapOf("showCapsLock" to true))
addWindowListener(object : WindowAdapter() {
override fun windowOpened(e: WindowEvent) {
removeWindowListener(this)
usernameTextField.requestFocus()
}
})
}
@@ -73,7 +59,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
override fun createCenterPanel(): JComponent {
val layout = FormLayout(
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, pref",
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN"
"pref, $FORM_MARGIN"
)
var rows = 1
@@ -90,7 +76,6 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
serverComboBox.addItem(Server(server.name, server.server))
}
mfaTextField.placeholderText = I18n.getString("termora.settings.account.mfa")
serverComboBox.renderer = object : DefaultListCellRenderer() {
override fun getListCellRendererComponent(
@@ -153,40 +138,6 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
}
}
val registerAction = object : AnAction(I18n.getString("termora.settings.account.register")) {
override fun actionPerformed(evt: AnActionEvent) {
val server = serverComboBox.selectedItem as Server?
if (server == null) {
serverComboBox.outline = FlatClientProperties.OUTLINE_ERROR
serverComboBox.requestFocusInWindow()
return
}
try {
val text = AccountHttp.execute(
AccountHttp.client, Request.Builder()
.get().url("${server.server}/v1/client/system").build()
)
val json = runCatching { ohMyJson.decodeFromString<JsonObject>(text) }.getOrNull()
val allowRegister = json?.get("register")?.jsonPrimitive?.boolean ?: false
if (allowRegister.not()) {
throw IllegalStateException(I18n.getString("termora.settings.account.not-support-register"))
}
Application.browse(URI.create("${server.server}/v1/client/redirect?to=register&from=${Application.getName()}"))
} catch (e: Exception) {
if (log.isErrorEnabled) {
log.error(e.message, e)
}
OptionPane.showMessageDialog(
dialog,
e.message ?: I18n.getString("termora.settings.account.not-support-register"),
messageType = JOptionPane.ERROR_MESSAGE
)
}
}
}
fun refreshButton() {
if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer || serverComboBox.itemCount < 1) {
newAction.name = I18n.getString("termora.welcome.contextmenu.new")
@@ -214,21 +165,11 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
})
val registerLink = JXHyperlink(registerAction)
registerLink.isFocusable = false
return FormBuilder.create().layout(layout).debug(false).padding("0dlu, $FORM_MARGIN, 0dlu, $FORM_MARGIN")
.add("${I18n.getString("termora.settings.account.server")}:").xy(1, rows)
.add(serverComboBox).xy(3, rows)
.add(newServer).xy(5, rows).apply { rows += step }
.add("${I18n.getString("termora.settings.account")}:").xy(1, rows)
.add(usernameTextField).xy(3, rows)
.add(registerLink).xy(5, rows).apply { rows += step }
.add("${I18n.getString("termora.new-host.general.password")}:").xy(1, rows)
.add(passwordField).xy(3, rows).apply { rows += step }
.add("MFA:").xy(1, rows)
.add(mfaTextField).xy(3, rows).apply { rows += step }
.build()
}
@@ -315,95 +256,21 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
}
override fun doOKAction() {
if (isLoggingIn.get()) return
val server = serverComboBox.selectedItem as? Server
server = serverComboBox.selectedItem as? Server
if (server == null) {
serverComboBox.outline = FlatClientProperties.OUTLINE_ERROR
serverComboBox.requestFocusInWindow()
return
}
if (usernameTextField.text.isBlank()) {
usernameTextField.outline = FlatClientProperties.OUTLINE_ERROR
usernameTextField.requestFocusInWindow()
return
} else if (passwordField.password.isEmpty()) {
passwordField.outline = FlatClientProperties.OUTLINE_ERROR
passwordField.requestFocusInWindow()
return
}
if (isLoggingIn.compareAndSet(false, true)) {
okAction.isEnabled = false
usernameTextField.isEnabled = false
passwordField.isEnabled = false
mfaTextField.isEnabled = false
serverComboBox.isEnabled = false
cancelButton.isVisible = false
onLogin(server)
return
}
super.doOKAction()
}
private fun onLogin(server: Server) {
val job = swingCoroutineScope.launch(Dispatchers.IO) {
var c = 0
while (isActive) {
if (++c > 3) c = 0
okAction.name = I18n.getString("termora.settings.account.login") + ".".repeat(c)
delay(350.milliseconds)
}
}
val loginJob = swingCoroutineScope.launch(Dispatchers.IO) {
try {
serverManager.login(
server, usernameTextField.text,
String(passwordField.password), mfaTextField.text.trim()
)
withContext(Dispatchers.Swing) {
super.doOKAction()
}
} catch (e: Exception) {
if (log.isErrorEnabled) log.error(e.message, e)
withContext(Dispatchers.Swing) {
OptionPane.showMessageDialog(
this@LoginServerDialog,
StringUtils.defaultIfBlank(
e.message ?: StringUtils.EMPTY,
I18n.getString("termora.settings.account.login-failed")
),
messageType = JOptionPane.ERROR_MESSAGE,
)
}
} finally {
job.cancel()
withContext(Dispatchers.Swing) {
okAction.name = I18n.getString("termora.settings.account.login")
okAction.isEnabled = true
usernameTextField.isEnabled = true
passwordField.isEnabled = true
serverComboBox.isEnabled = true
cancelButton.isVisible = true
mfaTextField.isEnabled = true
}
isLoggingIn.compareAndSet(true, false)
}
}
Disposer.register(disposable, object : Disposable {
override fun dispose() {
if (loginJob.isActive)
loginJob.cancel()
}
})
}
override fun doCancelAction() {
if (isLoggingIn.get()) return
server = null
super.doCancelAction()
}
}

View File

@@ -67,7 +67,6 @@ class PullService private constructor() : SyncService(), Disposable, Application
private var lastChangeHash = StringUtils.EMPTY
private fun pullChanges() {
if (isFreePlan) return
val hash: String
try {

View File

@@ -1,7 +1,10 @@
package app.termora.account
import app.termora.*
import app.termora.AES
import app.termora.Application.ohMyJson
import app.termora.ApplicationScope
import app.termora.PBKDF2
import app.termora.RSA
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
@@ -9,7 +12,6 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.apache.commons.codec.binary.Base64
import org.apache.commons.codec.digest.DigestUtils
import java.util.concurrent.atomic.AtomicBoolean
class ServerManager private constructor() {
@@ -28,7 +30,7 @@ class ServerManager private constructor() {
/**
* 登录,不报错就是登录成功
*/
fun login(server: Server, username: String, password: String, mfa: String) {
fun login(server: Server, refreshToken: String, password: String) {
if (accountManager.isLocally().not()) {
throw IllegalStateException("Already logged in")
@@ -39,25 +41,25 @@ class ServerManager private constructor() {
}
try {
doLogin(server, username, password, mfa)
doLogin(server, refreshToken, password)
} finally {
isLoggingIn.compareAndSet(true, false)
}
}
private fun doLogin(server: Server, username: String, password: String, mfa: String) {
private fun doLogin(server: Server, refreshToken: String, password: String) {
// 服务器信息
val serverInfo = getServerInfo(server)
// call login
val loginResponse = callLogin(serverInfo, server, username, password, mfa)
val loginResponse = callToken(server, refreshToken)
// call me
val meResponse = callMe(server.server, loginResponse.accessToken)
// 解密
val salt = "${serverInfo.salt}:${username}".toByteArray()
val salt = "${serverInfo.salt}:${meResponse.email}".toByteArray()
val privateKeySecureKey = PBKDF2.hash(salt, password.toCharArray(), 1024, 256)
val privateKeySecureIv = PBKDF2.hash(salt, password.toCharArray(), 1024, 128)
val privateKeyEncoded = AES.CBC.decrypt(
@@ -106,29 +108,19 @@ class ServerManager private constructor() {
return ohMyJson.decodeFromString<ServerInfo>(AccountHttp.execute(request = request))
}
private fun callLogin(
serverInfo: ServerInfo,
private fun callToken(
server: Server,
username: String,
password: String,
mfa: String
refreshToken: String,
): LoginResponse {
val passwordHex = DigestUtils.sha256Hex("${serverInfo.salt}:${username}:${password}")
val requestBody = ohMyJson.encodeToString(mapOf("email" to username, "password" to passwordHex, "mfa" to mfa))
val body = ohMyJson.encodeToString(mapOf("refreshToken" to refreshToken))
.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("${server.server}/v1/login")
.post(requestBody)
val request = Request.Builder().url("${server.server}/v1/token")
.header("Authorization", "Bearer $refreshToken")
.post(body)
.build()
val response = AccountHttp.client.newCall(request).execute()
val text = response.use { response.body.use { it?.string() } }
if (text == null) {
throw ResponseException(response.code, response)
}
val text = response.use { response.body.use { it.string() } }
if (response.isSuccessful.not()) {
val message = ohMyJson.parseToJsonElement(text).jsonObject["message"]?.jsonPrimitive?.content

View File

@@ -287,6 +287,9 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
typeComboBox.addItem("RSA")
typeComboBox.addItem("ED25519")
typeComboBox.addItem("ECDSA-SHA2-NISTP256")
typeComboBox.addItem("ECDSA-SHA2-NISTP384")
typeComboBox.addItem("ECDSA-SHA2-NISTP521")
// 默认 RSA
lengthComboBox.addItem(1024)
@@ -396,6 +399,12 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
lengthComboBox.addItem(1024 * 4)
lengthComboBox.addItem(1024 * 8)
lengthComboBox.selectedItem = 1024 * 2
} else if (typeComboBox.selectedItem == "ECDSA-SHA2-NISTP256") {
lengthComboBox.addItem(256)
} else if (typeComboBox.selectedItem == "ECDSA-SHA2-NISTP384") {
lengthComboBox.addItem(384)
} else if (typeComboBox.selectedItem == "ECDSA-SHA2-NISTP521") {
lengthComboBox.addItem(521)
}
}
}
@@ -413,6 +422,17 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
super.doCancelAction()
}
private fun genKeyPair(): KeyPair {
val keyType = when (typeComboBox.selectedItem) {
"ED25519" -> KeyPairProvider.SSH_ED25519
"ECDSA-SHA2-NISTP256" -> KeyPairProvider.ECDSA_SHA2_NISTP256
"ECDSA-SHA2-NISTP384" -> KeyPairProvider.ECDSA_SHA2_NISTP384
"ECDSA-SHA2-NISTP521" -> KeyPairProvider.ECDSA_SHA2_NISTP521
else -> KeyPairProvider.SSH_RSA
}
return KeyUtils.generateKeyPair(keyType, lengthComboBox.selectedItem as Int)
}
override fun doOKAction() {
if (ohKeyPair == OhKeyPair.empty) {
@@ -422,9 +442,7 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
return
}
val keyType = if (typeComboBox.selectedItem == "RSA")
KeyPairProvider.SSH_RSA else KeyPairProvider.SSH_ED25519
val keyPair = KeyUtils.generateKeyPair(keyType, lengthComboBox.selectedItem as Int)
val keyPair = genKeyPair()
ohKeyPair = OhKeyPair(
id = randomUUID(),
name = nameTextField.text,

View File

@@ -2,6 +2,7 @@ package app.termora.keymgr
import app.termora.AES.decodeBase64
import app.termora.RSA
import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder
import org.apache.sshd.common.keyprovider.AbstractResourceKeyPairProvider
import org.apache.sshd.common.session.SessionContext
import org.apache.sshd.common.util.security.eddsa.Ed25519PublicKeyDecoder
@@ -25,6 +26,8 @@ class OhKeyPairKeyPairProvider(private val id: String) : AbstractResourceKeyPair
when (ohKeyPair.type) {
"RSA" -> RSA.generatePublic(ohKeyPair.publicKey.decodeBase64())
"ED25519" -> Ed25519PublicKeyDecoder.INSTANCE.generatePublicKey((X509EncodedKeySpec(ohKeyPair.publicKey.decodeBase64())))
"ECDSA-SHA2-NISTP256","ECDSA-SHA2-NISTP384","ECDSA-SHA2-NISTP521" ->
ECDSAPublicKeyEntryDecoder.INSTANCE.generatePublicKey(X509EncodedKeySpec(ohKeyPair.publicKey.decodeBase64()))
else -> throw UnsupportedOperationException("${ohKeyPair.type} is not supported")
}
} as PublicKey
@@ -33,6 +36,8 @@ class OhKeyPairKeyPairProvider(private val id: String) : AbstractResourceKeyPair
when (ohKeyPair.type) {
"RSA" -> RSA.generatePrivate(ohKeyPair.privateKey.decodeBase64())
"ED25519" -> Ed25519PublicKeyDecoder.INSTANCE.generatePrivateKey(PKCS8EncodedKeySpec(ohKeyPair.privateKey.decodeBase64()))
"ECDSA-SHA2-NISTP256","ECDSA-SHA2-NISTP384","ECDSA-SHA2-NISTP521" ->
ECDSAPublicKeyEntryDecoder.INSTANCE.generatePrivateKey(PKCS8EncodedKeySpec(ohKeyPair.privateKey.decodeBase64()))
else -> throw UnsupportedOperationException("${ohKeyPair.type} is not supported")
}
} as PrivateKey

View File

@@ -1,16 +1,20 @@
package app.termora.terminal
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.math.max
import kotlin.math.min
open class SelectionModelImpl(private val terminal: Terminal) : SelectionModel {
private var startPosition = Position.unknown
private var endPosition = Position.unknown
private var block = false
private val document = terminal.getDocument()
internal companion object {
private val log = LoggerFactory.getLogger(SelectionModelImpl::class.java)
fun isPointInsideArea(start: Position, end: Position, x: Int, y: Int, cols: Int): Boolean {
val top = min(start.y, end.y)
val bottom = max(start.y, end.y)
@@ -49,7 +53,13 @@ open class SelectionModelImpl(private val terminal: Terminal) : SelectionModel {
}
// 设置新的选择区域
setSelection(startPosition, endPosition)
try {
setSelection(startPosition, endPosition)
} catch (e: Exception) {
if (log.isTraceEnabled) {
log.trace(e.message)
}
}
}

View File

@@ -146,6 +146,26 @@ class NewHostTree : SimpleTree(), Disposable {
}
})
// 开启 ToolTip 功能
ToolTipManager.sharedInstance().registerComponent(this)
// 设置鼠标移动提示
addMouseMotionListener(object : java.awt.event.MouseMotionAdapter() {
override fun mouseMoved(e: MouseEvent) {
val path: TreePath? = getPathForLocation(e.x, e.y)
if (path != null) {
val node: HostTreeNode = path.lastPathComponent as HostTreeNode
if (node.host.remark.isNotEmpty()){
toolTipText = node.host.remark
}else{
toolTipText = null
}
} else {
toolTipText = null
}
}
})
actionMap.put("copy", object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
toolkit.systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null)
@@ -193,6 +213,9 @@ class NewHostTree : SimpleTree(), Disposable {
}
override fun dispose() {
// 销毁
ToolTipManager.sharedInstance().unregisterComponent(this)
val name = super.getName()
if (name.isNullOrBlank().not()) {
properties.putString("${name}.state", TreeUtils.saveExpansionState(this))
@@ -1151,4 +1174,4 @@ class NewHostTree : SimpleTree(), Disposable {
}
}
}

View File

@@ -89,11 +89,9 @@ termora.settings.plugin.install-from-disk-warning=<b>{0}</b> plugin will have ac
termora.settings.plugin.not-compatible=The plugin <b>{0}</b> is incompatible with the current version. Please reinstall <b>{0}</b>
termora.settings.account=Account
termora.settings.account.register=Register
termora.settings.account.not-support-register=This server does not support account registration
termora.settings.account.login=Log in
termora.settings.account.server=Server
termora.settings.account.mfa=MFA is optional
termora.settings.account.wait-login=Waiting for login in the default browser...
termora.settings.account.locally=locally
termora.settings.account.lifetime=Lifetime
termora.settings.account.upgrade=Upgrade

View File

@@ -49,7 +49,26 @@ termora.setting.security.enter-password-again=Повторите пароль
termora.setting.security.password-is-different=Пароли отличаются
termora.setting.security.mnemonic-note=Сохраните мнемоническую фразу в надежном месте, она может помочь восстановить данные, если вы забудете пароль
termora.settings.account=Учётная запись
termora.settings.account.login=Войти
termora.settings.account.server=Сервер
termora.settings.account.wait-login=Ожидание входа в браузере по умолчанию...
termora.settings.account.locally=локально
termora.settings.account.lifetime=Время действия
termora.settings.account.upgrade=Обновить
termora.settings.account.verify=Подтвердить
termora.settings.account.subscription=Подписка
termora.settings.account.valid-to=Действительна до
termora.settings.account.synchronization-on=Синхронизация вкл
termora.settings.account.sync-now = Синхронизировать сейчас
termora.settings.account.logout = Выйти
termora.settings.account.logout-confirm = Вы уверены, что хотите выйти?
termora.settings.account.unsynced-logout-confirm = Несинхронизировано Вы уверены, что хотите выйти?
termora.settings.account.server-singapore = Сингапур
termora.settings.account.server-china = Материковый Китай
termora.settings.account.new-server = Новый сервер
termora.settings.account.deploy-server = Развернуть
termora.settings.account.login-failed = Не удалось войти, повторите попытку позже
termora.settings.terminal=Терминал

View File

@@ -103,10 +103,8 @@ termora.settings.plugin.not-compatible=插件 <b>{0}</b> 与当前版本不兼
termora.settings.account=账号
termora.settings.account.login=登录
termora.settings.account.register=注册
termora.settings.account.not-support-register=该服务器不支持注册账号
termora.settings.account.server=服务器
termora.settings.account.mfa=多因素验证是可选的
termora.settings.account.wait-login=正在等待默认浏览器中登录...
termora.settings.account.locally=本地的
termora.settings.account.lifetime=长期
termora.settings.account.verify=验证

View File

@@ -114,10 +114,8 @@ termora.settings.plugin.not-compatible=插件 <b>{0}</b> 與目前版本不相
termora.settings.account=帳號
termora.settings.account.login=登入
termora.settings.account.register=註冊
termora.settings.account.not-support-register=此伺服器不支援註冊帳號
termora.settings.account.server=伺服器
termora.settings.account.mfa=多因素驗證是可選的
termora.settings.account.wait-login=正在等待預設瀏覽器登入...
termora.settings.account.locally=本地的
termora.settings.account.lifetime=長期
termora.settings.account.verify=驗證

View File

@@ -15,6 +15,19 @@ class KeyUtilsTest {
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair("ssh-rsa", 1024).public), 1024)
}
@Test
fun test_ECDSA() {
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair(KeyPairProvider.ECDSA_SHA2_NISTP256, 256).private), 256)
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair(KeyPairProvider.ECDSA_SHA2_NISTP256, 256).public), 256)
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair(KeyPairProvider.ECDSA_SHA2_NISTP384, 384).private), 384)
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair(KeyPairProvider.ECDSA_SHA2_NISTP384, 384).public), 384)
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair(KeyPairProvider.ECDSA_SHA2_NISTP521, 521).private), 521)
assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair(KeyPairProvider.ECDSA_SHA2_NISTP521, 521).public), 521)
}
@Test
fun test_ed25519() {
val keyPair = KeyUtils.generateKeyPair(KeyPairProvider.SSH_ED25519, 256)

View File

@@ -1,4 +1,4 @@
FROM linuxserver/openssh-server
FROM linuxserver/openssh-server:9.3_p2-r1-ls147
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk update && apk add wget tmux gcc zip p7zip g++ git make zsh htop stress-ng inetutils-telnet xclock xcalc xorg-server xinit && wget https://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz \
&& tar -xf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20 && ./configure && make && make install \