Added a significant overhaul to the server manager.

This commit is contained in:
2025-12-29 16:07:30 -05:00
parent 4f4e5276a6
commit 216e18a7b4
796 changed files with 528631 additions and 2671 deletions
@@ -0,0 +1,220 @@
<template>
<div>
<h2>Samba File Explorer</h2>
<label for="share-select">Select Share:</label>
<select id="share-select" v-model="shareName" @change="resetAndFetch">
<option v-for="share in availableShares" :key="share" :value="share">{{ share }}</option>
</select>
<p>Current Path: {{ shareName }}:{{ currentPath }}</p>
<ul>
<li v-for="item in files" :key="item.name">
<span @click="item.is_directory ? navigate(item) : viewFile(item.path)">
{{ item.name }} {{ item.is_directory ? '/' : '' }}
</span>
<button v-if="!item.is_directory" @click="viewFile(item.path)">View</button>
<button v-if="!item.is_directory" @click="downloadFile(item.path)">Download</button>
<button @click="renameItem(item.path, item.name, item.is_directory)">Rename</button>
<button @click="deleteItem(item.path, item.is_directory)">Delete</button>
</li>
</ul>
<button @click="goBack" :disabled="currentPath === '/'">Go Back</button>
<form @submit.prevent="uploadFile">
<input type="file" @change="handleFileUpload" />
<button type="submit">Upload</button>
</form>
<FileViewer
v-if="fileToView"
:file-path="fileToView"
:share-name="shareName"
@close="closeViewer"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import FileViewer from './FileViewer.vue';
const availableShares = ref(['media', 'isolation']);
const shareName = ref(availableShares.value[0]);
const currentPath = ref('/');
const files = ref([]);
const fileToUpload = ref(null);
const fileToView = ref(null);
const fetchFiles = async () => {
try {
const response = await fetch(`/api/samba/list/${shareName.value}?path=${currentPath.value}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
files.value = data;
} catch (error) {
console.error('Error fetching files:', error);
files.value = [];
}
};
const resetAndFetch = () => {
currentPath.value = '/';
fetchFiles();
};
const navigate = (item) => {
if (item.is_directory) {
currentPath.value = item.path;
fetchFiles();
}
};
const goBack = () => {
const parts = currentPath.value.split('/').filter(Boolean);
if (parts.length > 0) {
currentPath.value = '/' + parts.slice(0, -1).join('/') + (parts.length > 1 ? '/' : '');
} else {
currentPath.value = '/';
}
fetchFiles();
};
const viewFile = (filePath) => {
fileToView.value = filePath;
};
const closeViewer = () => {
fileToView.value = null;
};
const downloadFile = async (filePath) => {
try {
const response = await fetch(`/api/samba/files/download/${shareName.value}?path=${filePath}`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filePath.split('/').pop();
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Error downloading file:', error);
}
};
const deleteItem = async (itemPath, isDirectory) => {
if (!confirm(`Are you sure you want to delete ${itemPath}?`)) {
return;
}
try {
const response = await fetch(`/api/samba/files/${shareName.value}?path=${itemPath}`, {
method: 'DELETE',
});
if (response.ok) {
console.log(`${itemPath} deleted successfully.`);
fetchFiles();
} else {
console.error('Error deleting item:', await response.text());
}
} catch (error) {
console.error('Error deleting item:', error);
}
};
const renameItem = async (oldPath, oldName, isDirectory) => {
const newName = prompt(`Enter new name for ${oldName}:`, oldName);
if (!newName || newName === oldName) {
return;
}
const newPath = currentPath.value === '/' ? `/${newName}` : `${currentPath.value.replace(/\/$/, '')}/${newName}`;
try {
const response = await fetch(`/api/samba/files/rename/${shareName.value}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ old_path: oldPath, new_path: newPath }),
});
if (response.ok) {
console.log(`${oldName} renamed to ${newName} successfully.`);
fetchFiles();
} else {
console.error('Error renaming item:', await response.text());
}
} catch (error) {
console.error('Error renaming item:', error);
}
};
const handleFileUpload = (event) => {
fileToUpload.value = event.target.files[0];
};
const uploadFile = async () => {
if (!fileToUpload.value) return;
const formData = new FormData();
formData.append('file', fileToUpload.value);
try {
const response = await fetch(`/api/samba/files/upload/${shareName.value}?path=${currentPath.value}`, {
method: 'POST',
body: formData,
});
if (response.ok) {
console.log('File uploaded successfully.');
fileToUpload.value = null;
fetchFiles();
} else {
console.error('Error uploading file:', await response.text());
}
} catch (error) {
console.error('Error uploading file:', error);
}
};
onMounted(() => {
fetchFiles();
});
watch(shareName, () => {
resetAndFetch();
});
</script>
<style scoped>
/* Add some basic styling */
div {
font-family: sans-serif;
padding: 20px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin: 5px 0;
display: flex;
align-items: center;
}
li span {
cursor: pointer;
margin-right: 10px;
color: #007bff;
}
li span:hover {
text-decoration: underline;
}
button {
margin-left: 5px;
padding: 5px 10px;
cursor: pointer;
}
</style>