-
-
+
+
+
+
+ :class="[entry.linkBroken ? 'text-red-300 dark:text-red-800' : 'text-gray-100 dark:text-gray-900']" />
{{ entry.path.split('/').slice(-1 * (level + 1)).join('/') }}:
{{ entry.name }}:
- {{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }}
+ {{ entry.mode.toString(8) }}, {{ entry.owner }}:{{ entry.group }}, {{ entry.sizeHuman }},
+ modified: {{ entry.mtime }}
@@ -85,7 +88,7 @@
diff --git a/navigator/src/components/DirectoryEntryList.vue b/navigator/src/components/DirectoryEntryList.vue
index 9ae35ef..cc1f992 100644
--- a/navigator/src/components/DirectoryEntryList.vue
+++ b/navigator/src/components/DirectoryEntryList.vue
@@ -5,7 +5,8 @@
@startProcessing="(...args) => $emit('startProcessing', ...args)"
@stopProcessing="(...args) => $emit('stopProcessing', ...args)" ref="entryRefs" :level="level" :selectedCount="selectedCount"
@setEntryProp="(prop, value) => entry[prop] = value"
- @entryAction="(...args) => $emit('entryAction', ...args)"
+ @browserAction="(...args) => $emit('browserAction', ...args)"
+ @directoryViewAction="(...args) => $emit('directoryViewAction', ...args)"
:suppressBorderT="visibleEntries[index - cols]?.selected && !(visibleEntries[index - cols]?.dirOpen)"
:suppressBorderB="visibleEntries[index + cols]?.selected && !(entry.dirOpen)"
:suppressBorderL="settings.directoryView.view !== 'list' && (visibleEntries[index - 1]?.selected && (index) % cols !== 0)"
@@ -69,13 +70,9 @@ export default {
const sortCallbackComputed = computed(() => {
return (a, b) => {
if (settings.directoryView?.separateDirs) {
- const checkA = a.type === 'l' ? (a.target?.type ?? null) : a.type;
- const checkB = b.type === 'l' ? (b.target?.type ?? null) : b.type;
- if (checkA === null || checkB === null)
- return 0;
- if (checkA === 'd' && checkB !== 'd')
+ if (a.resolvedType === 'd' && b.resolvedType !== 'd')
return -1;
- else if (checkA !== 'd' && checkB === 'd')
+ else if (a.resolvedType !== 'd' && b.resolvedType === 'd')
return 1;
}
return props.sortCallback(a, b);
@@ -231,7 +228,7 @@ export default {
visibleEntries.value = entries.value.filter(entryFilterCallback);
// nextTick(() => console.timeEnd('updateVisibleEntries-' + props.path));
const _stats = visibleEntries.value.reduce((_stats, entry) => {
- if (entry.type === 'd' || (entry.type === 'l' && entry.target?.type === 'd'))
+ if (entry.resolvedType === 'd')
_stats.dirs++;
else
_stats.files++;
@@ -276,7 +273,8 @@ export default {
'stopProcessing',
'cancelShowEntries',
'deselectAll',
- 'entryAction',
+ 'browserAction',
+ 'directoryViewAction',
'tallySelected',
]
}
diff --git a/navigator/src/components/DirectoryView.vue b/navigator/src/components/DirectoryView.vue
index 0a87d90..4142b30 100644
--- a/navigator/src/components/DirectoryView.vue
+++ b/navigator/src/components/DirectoryView.vue
@@ -64,10 +64,10 @@
$emit('cd', ...args)"
- @edit="(...args) => $emit('edit', ...args)"
+ :searchFilterRegExp="searchFilterRegExp"
@startProcessing="processing++" @stopProcessing="processing--"
- @entryAction="handleEntryAction" ref="directoryEntryListRef"
+ @browserAction="(...args) => $emit('browserAction', ...args)"
+ @directoryViewAction="handleAction" ref="directoryEntryListRef"
@tallySelected="tallySelected"
:level="0" :cols="1" :selectedCount="selectedCount" />
@@ -109,7 +109,8 @@
$emit('browserAction', ...args)"
+ @directoryViewAction="handleAction"
@tallySelected="tallySelected" ref="directoryEntryListRef"
:level="0" :cols="cols" :selectedCount="selectedCount" />
@@ -291,7 +292,7 @@ export default {
let destination;
if (selected.length === 1) {
destination = selected[0];
- if (destination.type !== 'd' && !(destination.type === 'l' && destination.target.type === 'd')) {
+ if (destination.resolvedType !== 'd') {
notifications.value.constructNotification("Paste Failed", 'Cannot paste to non-directory.', 'error');
break;
}
@@ -326,13 +327,13 @@ export default {
}
}
- const handleEntryAction = (action, entry, event, ...args) => {
+ const handleAction = (action, ...args) => {
switch (action) {
case 'toggleSelected':
- toggleSelected(entry, event);
+ toggleSelected(...args);
break;
default:
- emit('entryAction', action, entry, event, ...args);
+ console.error('Unknown directoryViewAction:', action, args);
break;
}
}
@@ -377,7 +378,7 @@ export default {
selectAll,
deselectAll,
selectRectangle,
- handleEntryAction,
+ handleAction,
tallySelected,
}
},
@@ -389,7 +390,7 @@ export default {
DragSelectArea,
},
emits: [
- 'entryAction',
+ 'browserAction',
]
}
diff --git a/navigator/src/functions/getDirEntryObjects.js b/navigator/src/functions/getDirEntryObjects.js
index fe60e43..89a98f1 100644
--- a/navigator/src/functions/getDirEntryObjects.js
+++ b/navigator/src/functions/getDirEntryObjects.js
@@ -1,12 +1,12 @@
import { useSpawn, errorString } from "@45drives/cockpit-helpers";
import { UNIT_SEPARATOR, RECORD_SEPARATOR } from "../constants";
-import { szudzikPair } from "./szudzikPair";
+import { szudzikPair, hashString } from "./szudzikPair";
import { escapeStringHTML } from "./escapeStringHTML";
/**
* Get list of directory entry objects from list of directory entry names
*
- * find -H path -maxdepth 1 -mindepth 1 -printf '%D:%i%f:%m:%M:%s:%u:%g:%B@:%T@:%A@:%y:%Y:%l\n'
+ * find -H path -maxdepth 1 -mindepth 1 -printf '%D:%i%f:%p:%m:%M:%s:%u:%g:%B@:%T@:%A@:%y:%Y:%l\n'
*
* @param {String} cwd - Working directory to run find in
* @param {String} host - Host to run find on
@@ -25,7 +25,7 @@ async function getDirEntryObjects(cwd, host, extraFindArgs = [], failCallback =
'%s', // size
'%u', // owner
'%g', // group
- '%B@', // ctime
+ '%B@', // btime
'%T@', // mtime
'%A@', // atime
'%y', // type
@@ -96,17 +96,31 @@ async function getDirEntryStats(cwd, host, outputFormat, extraFindArguments = []
* @returns {DirectoryEntryObj[]}
*/
function parseRawEntryStats(records, cwd, host, failCallback, byteFormatter = cockpit.format_bytes) {
+ const typeHumanLUT = {
+ f: 'regular file',
+ d: 'directory',
+ l: 'symbolic link',
+ c: 'character device',
+ b: 'block device',
+ p: 'FIFO (named pipe)',
+ s: 'socket',
+ U: 'unknown file type',
+ L: 'broken link (loop)',
+ N: 'broken link (non-existent)',
+ D: 'door (Solaris)', // probably don't need this, but why not right?
+ }
+ const hostHash = hashString(host);
return records.map(fields => {
try {
- let [devId, inode, name, path, mode, modeStr, size, owner, group, ctime, mtime, atime, type, symlinkTargetType, symlinkTargetName] = fields;
- [size, ctime, mtime, atime] = [size, ctime, mtime, atime].map(num => parseInt(num));
+ let [devId, inode, name, path, mode, modeStr, size, owner, group, btime, mtime, atime, type, symlinkTargetType, symlinkTargetName] = fields;
+ [size, btime, mtime, atime] = [size, btime, mtime, atime].map(num => parseInt(num));
[devId, inode] = [devId, inode].map(num => BigInt(num));
- [ctime, mtime, atime] = [ctime, mtime, atime].map(ts => (ts && ts > 0) ? new Date(ts * 1000) : null);
- let [ctimeStr, mtimeStr, atimeStr] = [ctime, mtime, atime].map(date => date?.toLocaleString() ?? '-');
+ [btime, mtime, atime] = [btime, mtime, atime].map(ts => (ts && ts > 0) ? new Date(ts * 1000) : null);
+ let [btimeStr, mtimeStr, atimeStr] = [btime, mtime, atime].map(date => date?.toLocaleString() ?? '-');
let [nameHTML, symlinkTargetNameHTML] = [name, symlinkTargetName].map(escapeStringHTML);
mode = parseInt(mode, 8);
return {
- uniqueId: szudzikPair(host, devId, inode),
+ uniqueId: szudzikPair(hostHash, devId, inode),
devId,
inode,
name,
@@ -118,20 +132,21 @@ function parseRawEntryStats(records, cwd, host, failCallback, byteFormatter = co
sizeHuman: byteFormatter(size, 1000).replace(/(?
cd({ path })" @edit="openEditor"
- @entryAction="handleEntryAction"
+ @browserAction="handleAction"
ref="directoryViewRef" />