mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: SFTP file exists and prompts to overwrite (#426)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.1.10"
|
kotlin = "2.1.20"
|
||||||
slf4j = "2.0.17"
|
slf4j = "2.0.17"
|
||||||
pty4j = "0.13.2"
|
pty4j = "0.13.2"
|
||||||
tinylog = "2.7.0"
|
tinylog = "2.7.0"
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ object Icons {
|
|||||||
val sortedSet by lazy { DynamicIcon("icons/sortedSet.svg", "icons/sortedSet_dark.svg") }
|
val sortedSet by lazy { DynamicIcon("icons/sortedSet.svg", "icons/sortedSet_dark.svg") }
|
||||||
val colorPicker by lazy { DynamicIcon("icons/colorPicker.svg", "icons/colorPicker_dark.svg") }
|
val colorPicker by lazy { DynamicIcon("icons/colorPicker.svg", "icons/colorPicker_dark.svg") }
|
||||||
val folder by lazy { DynamicIcon("icons/folder.svg", "icons/folder_dark.svg") }
|
val folder by lazy { DynamicIcon("icons/folder.svg", "icons/folder_dark.svg") }
|
||||||
|
val file by lazy { DynamicIcon("icons/file.svg", "icons/file_dark.svg") }
|
||||||
val listFiles by lazy { DynamicIcon("icons/listFiles.svg", "icons/listFiles_dark.svg") }
|
val listFiles by lazy { DynamicIcon("icons/listFiles.svg", "icons/listFiles_dark.svg") }
|
||||||
val left by lazy { DynamicIcon("icons/left.svg", "icons/left_dark.svg") }
|
val left by lazy { DynamicIcon("icons/left.svg", "icons/left_dark.svg") }
|
||||||
val right by lazy { DynamicIcon("icons/right.svg", "icons/right_dark.svg") }
|
val right by lazy { DynamicIcon("icons/right.svg", "icons/right_dark.svg") }
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ object OptionPane {
|
|||||||
icon: Icon? = null,
|
icon: Icon? = null,
|
||||||
options: Array<Any>? = null,
|
options: Array<Any>? = null,
|
||||||
initialValue: Any? = null,
|
initialValue: Any? = null,
|
||||||
|
customizeDialog: (JDialog) -> Unit = {},
|
||||||
): Int {
|
): Int {
|
||||||
|
|
||||||
val panel = if (message is JComponent) {
|
val panel = if (message is JComponent) {
|
||||||
@@ -47,6 +48,9 @@ object OptionPane {
|
|||||||
override fun selectInitialValue() {
|
override fun selectInitialValue() {
|
||||||
super.selectInitialValue()
|
super.selectInitialValue()
|
||||||
if (message is JComponent) {
|
if (message is JComponent) {
|
||||||
|
if (message.getClientProperty("SKIP_requestFocusInWindow") == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
message.requestFocusInWindow()
|
message.requestFocusInWindow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +62,7 @@ object OptionPane {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
dialog.setLocationRelativeTo(parentComponent)
|
dialog.setLocationRelativeTo(parentComponent)
|
||||||
|
customizeDialog.invoke(dialog)
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
dialog.dispose()
|
dialog.dispose()
|
||||||
val selectedValue = pane.value
|
val selectedValue = pane.value
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import app.termora.*
|
|||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import app.termora.actions.SettingsAction
|
import app.termora.actions.SettingsAction
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
|
import com.formdev.flatlaf.extras.FlatSVGIcon
|
||||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
|
import com.jgoodies.forms.builder.FormBuilder
|
||||||
|
import com.jgoodies.forms.layout.FormLayout
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.swing.Swing
|
import kotlinx.coroutines.swing.Swing
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
|
import org.apache.commons.lang3.time.DateFormatUtils
|
||||||
import org.apache.sshd.sftp.client.SftpClient
|
import org.apache.sshd.sftp.client.SftpClient
|
||||||
import org.apache.sshd.sftp.client.fs.SftpFileSystem
|
import org.apache.sshd.sftp.client.fs.SftpFileSystem
|
||||||
import org.apache.sshd.sftp.client.fs.SftpPath
|
import org.apache.sshd.sftp.client.fs.SftpPath
|
||||||
@@ -18,6 +22,7 @@ import org.apache.sshd.sftp.client.fs.SftpPosixFileAttributes
|
|||||||
import org.jdesktop.swingx.action.ActionManager
|
import org.jdesktop.swingx.action.ActionManager
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
|
import java.awt.Dimension
|
||||||
import java.awt.Insets
|
import java.awt.Insets
|
||||||
import java.awt.datatransfer.DataFlavor
|
import java.awt.datatransfer.DataFlavor
|
||||||
import java.awt.datatransfer.StringSelection
|
import java.awt.datatransfer.StringSelection
|
||||||
@@ -37,6 +42,7 @@ import javax.swing.*
|
|||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
import kotlin.collections.ArrayDeque
|
import kotlin.collections.ArrayDeque
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
|
import kotlin.math.max
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
|
||||||
@@ -556,22 +562,6 @@ class FileSystemViewTable(
|
|||||||
fileSystemViewPanel.newFolderOrFile(text, isFile)
|
fileSystemViewPanel.newFolderOrFile(text, isFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transfer(
|
|
||||||
attrs: Array<FileSystemViewTableModel.Attr>,
|
|
||||||
fromLocalSystem: Boolean = false,
|
|
||||||
targetWorkdir: Path? = null
|
|
||||||
) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
doTransfer(attrs, fromLocalSystem, targetWorkdir)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (log.isErrorEnabled) {
|
|
||||||
log.error(e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deletePaths(paths: Array<Path>, rm: Boolean = false) {
|
private fun deletePaths(paths: Array<Path>, rm: Boolean = false) {
|
||||||
if (OptionPane.showConfirmDialog(
|
if (OptionPane.showConfirmDialog(
|
||||||
SwingUtilities.getWindowAncestor(this),
|
SwingUtilities.getWindowAncestor(this),
|
||||||
@@ -657,6 +647,183 @@ class FileSystemViewTable(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun transfer(
|
||||||
|
attrs: Array<FileSystemViewTableModel.Attr>,
|
||||||
|
fromLocalSystem: Boolean = false,
|
||||||
|
targetWorkdir: Path? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
assertEventDispatchThread()
|
||||||
|
|
||||||
|
val target = sftpPanel.getTarget(table) ?: return
|
||||||
|
val table = target.getData(SFTPDataProviders.FileSystemViewTable) ?: return
|
||||||
|
var overwriteAll = false
|
||||||
|
|
||||||
|
for (attr in attrs) {
|
||||||
|
|
||||||
|
if (!overwriteAll) {
|
||||||
|
val targetAttr = 0.rangeUntil(table.model.rowCount).map { table.model.getAttr(it) }
|
||||||
|
.find { it.name == attr.name }
|
||||||
|
if (targetAttr != null) {
|
||||||
|
val askTransfer = askTransfer(attr, targetAttr)
|
||||||
|
if (askTransfer.option != JOptionPane.YES_OPTION) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (askTransfer.action == AskTransfer.Action.Skip) {
|
||||||
|
if (askTransfer.applyAll) break
|
||||||
|
continue
|
||||||
|
} else if (askTransfer.action == AskTransfer.Action.Overwrite) {
|
||||||
|
overwriteAll = askTransfer.applyAll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
doTransfer(arrayOf(attr), fromLocalSystem, targetWorkdir)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class AskTransfer(
|
||||||
|
val option: Int,
|
||||||
|
val action: Action,
|
||||||
|
val applyAll: Boolean
|
||||||
|
) {
|
||||||
|
enum class Action {
|
||||||
|
Overwrite,
|
||||||
|
Skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun askTransfer(
|
||||||
|
sourceAttr: FileSystemViewTableModel.Attr,
|
||||||
|
targetAttr: FileSystemViewTableModel.Attr
|
||||||
|
): AskTransfer {
|
||||||
|
val formMargin = "7dlu"
|
||||||
|
val layout = FormLayout(
|
||||||
|
"left:pref, $formMargin, default:grow, 2dlu, left:pref",
|
||||||
|
"pref, 12dlu, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, 16dlu, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||||
|
)
|
||||||
|
|
||||||
|
val iconSize = 36
|
||||||
|
|
||||||
|
val targetIcon = if (SystemInfo.isWindows)
|
||||||
|
NativeFileIcons.getIcon(targetAttr.name, targetAttr.isFile, iconSize, iconSize).first
|
||||||
|
else if (targetAttr.isDirectory) {
|
||||||
|
FlatSVGIcon(Icons.folder.name, iconSize, iconSize)
|
||||||
|
} else {
|
||||||
|
FlatSVGIcon(Icons.file.name, iconSize, iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceIcon = if (SystemInfo.isWindows)
|
||||||
|
NativeFileIcons.getIcon(sourceAttr.name, sourceAttr.isFile, iconSize, iconSize).first
|
||||||
|
else if (sourceAttr.isDirectory) {
|
||||||
|
FlatSVGIcon(Icons.folder.name, iconSize, iconSize)
|
||||||
|
} else {
|
||||||
|
FlatSVGIcon(Icons.file.name, iconSize, iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceModified = if (sourceAttr.modified > 0) DateFormatUtils.format(
|
||||||
|
Date(sourceAttr.modified),
|
||||||
|
"yyyy/MM/dd HH:mm"
|
||||||
|
) else "-"
|
||||||
|
|
||||||
|
val targetModified = if (targetAttr.modified > 0) DateFormatUtils.format(
|
||||||
|
Date(targetAttr.modified),
|
||||||
|
"yyyy/MM/dd HH:mm"
|
||||||
|
) else "-"
|
||||||
|
|
||||||
|
val actionsComBoBox = JComboBox<AskTransfer.Action>()
|
||||||
|
actionsComBoBox.addItem(AskTransfer.Action.Overwrite)
|
||||||
|
actionsComBoBox.addItem(AskTransfer.Action.Skip)
|
||||||
|
actionsComBoBox.renderer = object : DefaultListCellRenderer() {
|
||||||
|
override fun getListCellRendererComponent(
|
||||||
|
list: JList<*>?,
|
||||||
|
value: Any?,
|
||||||
|
index: Int,
|
||||||
|
isSelected: Boolean,
|
||||||
|
cellHasFocus: Boolean
|
||||||
|
): Component {
|
||||||
|
var text = value?.toString() ?: StringUtils.EMPTY
|
||||||
|
if (value == AskTransfer.Action.Overwrite) {
|
||||||
|
text = I18n.getString("termora.transport.sftp.already-exists.overwrite")
|
||||||
|
} else if (value == AskTransfer.Action.Skip) {
|
||||||
|
text = I18n.getString("termora.transport.sftp.already-exists.skip")
|
||||||
|
}
|
||||||
|
return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val applyAllCheckbox = JCheckBox(I18n.getString("termora.transport.sftp.already-exists.apply-all"))
|
||||||
|
val box = Box.createHorizontalBox()
|
||||||
|
box.add(actionsComBoBox)
|
||||||
|
box.add(Box.createHorizontalStrut(8))
|
||||||
|
box.add(applyAllCheckbox)
|
||||||
|
box.add(Box.createHorizontalGlue())
|
||||||
|
|
||||||
|
val ttBox = Box.createVerticalBox()
|
||||||
|
ttBox.add(JLabel(I18n.getString("termora.transport.sftp.already-exists.message1")))
|
||||||
|
ttBox.add(JLabel(I18n.getString("termora.transport.sftp.already-exists.message2")))
|
||||||
|
|
||||||
|
val warningIcon = FlatSVGIcon(
|
||||||
|
Icons.warningIntroduction.name,
|
||||||
|
iconSize,
|
||||||
|
iconSize
|
||||||
|
)
|
||||||
|
|
||||||
|
var rows = 1
|
||||||
|
val step = 2
|
||||||
|
val panel = FormBuilder.create().layout(layout)
|
||||||
|
// tip
|
||||||
|
.add(JLabel(warningIcon)).xy(1, rows, "center, fill")
|
||||||
|
.add(ttBox).xyw(3, rows, 3).apply { rows += step }
|
||||||
|
// name
|
||||||
|
.add(JLabel("${I18n.getString("termora.transport.sftp.already-exists.name")}:")).xy(1, rows)
|
||||||
|
.add(sourceAttr.name).xyw(3, rows, 3).apply { rows += step }
|
||||||
|
// separator
|
||||||
|
.addSeparator(StringUtils.EMPTY).xyw(1, rows, 5).apply { rows += step }
|
||||||
|
// Destination
|
||||||
|
.add("${I18n.getString("termora.transport.sftp.already-exists.destination")}:").xy(1, rows)
|
||||||
|
.apply { rows += step }
|
||||||
|
// Folder
|
||||||
|
.add(JLabel(targetIcon)).xy(1, rows, "center, fill")
|
||||||
|
.add(targetModified).xyw(3, rows, 3).apply { rows += step }
|
||||||
|
// Source
|
||||||
|
.add("${I18n.getString("termora.transport.sftp.already-exists.source")}:").xy(1, rows)
|
||||||
|
.apply { rows += step }
|
||||||
|
// Folder
|
||||||
|
.add(JLabel(sourceIcon)).xy(1, rows, "center, fill")
|
||||||
|
.add(sourceModified).xyw(3, rows, 3).apply { rows += step }
|
||||||
|
// separator
|
||||||
|
.addSeparator(StringUtils.EMPTY).xyw(1, rows, 5).apply { rows += step }
|
||||||
|
// name
|
||||||
|
.add(JLabel("${I18n.getString("termora.transport.sftp.already-exists.actions")}:")).xy(1, rows)
|
||||||
|
.add(box).xyw(3, rows, 3).apply { rows += step }
|
||||||
|
.build()
|
||||||
|
panel.putClientProperty("SKIP_requestFocusInWindow", true)
|
||||||
|
|
||||||
|
return AskTransfer(
|
||||||
|
option = OptionPane.showConfirmDialog(
|
||||||
|
owner, panel,
|
||||||
|
messageType = JOptionPane.PLAIN_MESSAGE,
|
||||||
|
optionType = JOptionPane.OK_CANCEL_OPTION,
|
||||||
|
title = sourceAttr.name,
|
||||||
|
initialValue = JOptionPane.YES_OPTION,
|
||||||
|
) {
|
||||||
|
it.size = Dimension(max(UIManager.getInt("Dialog.width") - 220, it.width), it.height)
|
||||||
|
},
|
||||||
|
action = actionsComBoBox.selectedItem as AskTransfer.Action,
|
||||||
|
applyAll = applyAllCheckbox.isSelected
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始查找所有子,查找到之后立即添加任务,如果添加失败(任意一个)那么立即终止
|
* 开始查找所有子,查找到之后立即添加任务,如果添加失败(任意一个)那么立即终止
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.formdev.flatlaf.icons.FlatTreeLeafIcon
|
|||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.apache.commons.io.FilenameUtils
|
import org.apache.commons.io.FilenameUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.apache.commons.lang3.SystemUtils
|
import org.apache.commons.lang3.SystemUtils
|
||||||
import org.eclipse.jgit.util.LRUMap
|
import org.eclipse.jgit.util.LRUMap
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -24,7 +25,7 @@ object NativeFileIcons {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
if (SystemUtils.IS_OS_UNIX) {
|
if (SystemUtils.IS_OS_UNIX) {
|
||||||
cache[SystemUtils.USER_HOME] = Pair(FlatTreeClosedIcon(), I18n.getString("termora.folder"))
|
cache[SystemUtils.USER_HOME] = Pair(folderIcon, I18n.getString("termora.folder"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,35 +37,30 @@ object NativeFileIcons {
|
|||||||
return getIcon(filename, true).first
|
return getIcon(filename, true).first
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIcon(filename: String, isFile: Boolean = true): Pair<Icon, String> {
|
fun getIcon(filename: String, isFile: Boolean = true, width: Int = 16, height: Int = 16): Pair<Icon, String> {
|
||||||
if (isFile) {
|
val key = if (isFile) FilenameUtils.getExtension(filename) + "." + width + "@" + height
|
||||||
val extension = FilenameUtils.getExtension(filename)
|
else SystemUtils.USER_HOME + "." + width + "@" + height
|
||||||
if (cache.containsKey(extension)) {
|
|
||||||
return cache.getValue(extension)
|
if (cache.containsKey(key)) {
|
||||||
}
|
return cache.getValue(key)
|
||||||
} else {
|
|
||||||
if (cache.containsKey(SystemUtils.USER_HOME)) {
|
|
||||||
return cache.getValue(SystemUtils.USER_HOME)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDirectory = !isFile
|
val isDirectory = !isFile
|
||||||
|
|
||||||
if (SystemInfo.isWindows) {
|
if (SystemInfo.isWindows) {
|
||||||
|
|
||||||
val file = if (isDirectory) FileUtils.getFile(SystemUtils.USER_HOME) else
|
val file = if (isDirectory) FileUtils.getFile(SystemUtils.USER_HOME) else
|
||||||
FileUtils.getFile(Application.getTemporaryDir(), "${UUID.randomUUID()}.${filename}")
|
FileUtils.getFile(Application.getTemporaryDir(), "${UUID.randomUUID()}.${filename}")
|
||||||
if (isFile && !file.exists()) {
|
if (isFile && !file.exists()) {
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
}
|
}
|
||||||
val icon = getFileSystemView().getSystemIcon(file, 16, 16)
|
|
||||||
|
val icon = getFileSystemView().getSystemIcon(file, width, height) ?: if (isFile) fileIcon else folderIcon
|
||||||
val description = getFileSystemView().getSystemTypeDescription(file)
|
val description = getFileSystemView().getSystemTypeDescription(file)
|
||||||
|
?: StringUtils.defaultString(file.extension)
|
||||||
val pair = icon to description
|
val pair = icon to description
|
||||||
|
|
||||||
if (isDirectory) {
|
cache[key] = pair
|
||||||
cache[SystemUtils.USER_HOME] = pair
|
|
||||||
} else {
|
|
||||||
cache[FilenameUtils.getExtension(file.name)] = pair
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFile) FileUtils.deleteQuietly(file)
|
if (isFile) FileUtils.deleteQuietly(file)
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,16 @@ termora.transport.sftp.status.waiting=Waiting
|
|||||||
termora.transport.sftp.status.done=Done
|
termora.transport.sftp.status.done=Done
|
||||||
termora.transport.sftp.status.failed=Failed
|
termora.transport.sftp.status.failed=Failed
|
||||||
|
|
||||||
|
termora.transport.sftp.already-exists.message1=This folder already contains an object named as below
|
||||||
|
termora.transport.sftp.already-exists.message2=Select a task to do
|
||||||
|
termora.transport.sftp.already-exists.overwrite=Overwrite
|
||||||
|
termora.transport.sftp.already-exists.skip=Skip
|
||||||
|
termora.transport.sftp.already-exists.apply-all=Apply all
|
||||||
|
termora.transport.sftp.already-exists.name=Name
|
||||||
|
termora.transport.sftp.already-exists.destination=Destination
|
||||||
|
termora.transport.sftp.already-exists.source=Source
|
||||||
|
termora.transport.sftp.already-exists.actions=Actions
|
||||||
|
|
||||||
|
|
||||||
# transport job
|
# transport job
|
||||||
termora.transport.jobs.table.name=Name
|
termora.transport.jobs.table.name=Name
|
||||||
|
|||||||
@@ -293,6 +293,19 @@ termora.transport.sftp.status.done=已完成
|
|||||||
termora.transport.sftp.status.failed=已失败
|
termora.transport.sftp.status.failed=已失败
|
||||||
|
|
||||||
|
|
||||||
|
termora.transport.sftp.already-exists.message1=此文件夹已包含一下名称的对象
|
||||||
|
termora.transport.sftp.already-exists.message2=请选择要执行的操作
|
||||||
|
termora.transport.sftp.already-exists.overwrite=覆盖
|
||||||
|
termora.transport.sftp.already-exists.skip=跳过
|
||||||
|
termora.transport.sftp.already-exists.apply-all=应用全部
|
||||||
|
termora.transport.sftp.already-exists.name=名称
|
||||||
|
termora.transport.sftp.already-exists.destination=目标文件
|
||||||
|
termora.transport.sftp.already-exists.source=源文件
|
||||||
|
termora.transport.sftp.already-exists.actions=操作
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Permission
|
# Permission
|
||||||
termora.transport.permissions=更改权限
|
termora.transport.permissions=更改权限
|
||||||
termora.transport.permissions.file-folder-permissions=文件/文件夹权限
|
termora.transport.permissions.file-folder-permissions=文件/文件夹权限
|
||||||
|
|||||||
@@ -287,6 +287,16 @@ termora.transport.sftp.status.waiting=等待中
|
|||||||
termora.transport.sftp.status.done=已完成
|
termora.transport.sftp.status.done=已完成
|
||||||
termora.transport.sftp.status.failed=已失敗
|
termora.transport.sftp.status.failed=已失敗
|
||||||
|
|
||||||
|
termora.transport.sftp.already-exists.message1=此資料夾已包含一下名稱的對象
|
||||||
|
termora.transport.sftp.already-exists.message2=請選擇要執行的操作
|
||||||
|
termora.transport.sftp.already-exists.overwrite=覆蓋
|
||||||
|
termora.transport.sftp.already-exists.skip=跳過
|
||||||
|
termora.transport.sftp.already-exists.apply-all=應用全部
|
||||||
|
termora.transport.sftp.already-exists.name=名稱
|
||||||
|
termora.transport.sftp.already-exists.destination=目標文件
|
||||||
|
termora.transport.sftp.already-exists.source=原始檔
|
||||||
|
termora.transport.sftp.already-exists.actions=操作
|
||||||
|
|
||||||
# transport job
|
# transport job
|
||||||
termora.transport.jobs.table.name=名稱
|
termora.transport.jobs.table.name=名稱
|
||||||
termora.transport.jobs.table.status=狀態
|
termora.transport.jobs.table.status=狀態
|
||||||
|
|||||||
8
src/main/resources/icons/file.svg
Normal file
8
src/main/resources/icons/file.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<rect width="11" height="13" x="2.5" y="1.5" stroke="#6C707E" rx="1.5"/>
|
||||||
|
<line x1="5.5" x2="10.5" y1="5.5" y2="5.5" stroke="#6C707E" stroke-linecap="round"/>
|
||||||
|
<line x1="5.5" x2="10.5" y1="8" y2="8" stroke="#6C707E" stroke-linecap="round"/>
|
||||||
|
<line x1="5.5" x2="10.5" y1="10.5" y2="10.5" stroke="#6C707E" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
8
src/main/resources/icons/file_dark.svg
Normal file
8
src/main/resources/icons/file_dark.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<rect width="11" height="13" x="2.5" y="1.5" stroke="#CED0D6" rx="1.5"/>
|
||||||
|
<line x1="5.5" x2="10.5" y1="5.5" y2="5.5" stroke="#CED0D6" stroke-linecap="round"/>
|
||||||
|
<line x1="5.5" x2="10.5" y1="8" y2="8" stroke="#CED0D6" stroke-linecap="round"/>
|
||||||
|
<line x1="5.5" x2="10.5" y1="10.5" y2="10.5" stroke="#CED0D6" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 497 B |
Reference in New Issue
Block a user