diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
deleted file mode 100644
index 4e844ef..0000000
--- a/.gitea/workflows/build.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: Build and Upload Binaries
-
-on:
- push:
- branches:
- - main
-
-jobs:
- build-and-upload:
- runs-on: build-htz-01
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
-
- - name: Build Windows and Linux AMD64 binaries
- run: |
- mkdir -p dist
-
- # Build Linux amd64 and Windows amd64 binaries
- docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace cr.jdbnet.co.uk/jdbnet/wails-builder:latest bash -c "
- # Install frontend dependencies
- cd frontend && npm install && cd ..
-
- # Linux amd64
- wails build -platform linux/amd64 -tags webkit2_41 -clean
- cp build/bin/goexplore dist/goexplore-linux-amd64
-
- # Windows amd64
- wails build -platform windows/amd64 -clean -nsis
- cp build/bin/GoExplore-amd64-installer.exe dist/goexplore-windows-installer.exe
- "
-
- - name: Upload to Cloudflare R2
- env:
- AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
- AWS_DEFAULT_REGION: auto
- R2_ENDPOINT: ${{ secrets.R2_ENDPOINT_URL }}
- run: |
- aws s3 cp dist/goexplore-linux-amd64 s3://apps/goexplore/goexplore-linux-amd64 --endpoint-url "$R2_ENDPOINT"
- aws s3 cp dist/goexplore-windows-installer.exe s3://apps/goexplore/goexplore-windows-installer.exe --endpoint-url "$R2_ENDPOINT"
-
- - name: Clean up
- run: sudo rm -rf dist build/bin/*
diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
new file mode 100644
index 0000000..a5e6b6e
--- /dev/null
+++ b/.gitea/workflows/release.yml
@@ -0,0 +1,81 @@
+name: Release
+
+on:
+ pull_request:
+ branches:
+ - main
+ types: [closed]
+
+jobs:
+ release:
+ if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'v')
+ runs-on: build-htz-01
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Extract Version
+ id: get_version
+ run: |
+ # Remove 'v' prefix for some contexts if needed, but we'll use it raw
+ RAW_VERSION=${{ github.head_ref }}
+ CLEAN_VERSION=${RAW_VERSION#v}
+ echo "VERSION=${RAW_VERSION}" >> $GITHUB_OUTPUT
+ echo "CLEAN_VERSION=${CLEAN_VERSION}" >> $GITHUB_OUTPUT
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Build Binaries
+ run: |
+ mkdir -p dist
+
+ # Build Linux amd64 and Windows amd64 binaries using custom docker image
+ docker run --rm -v ${{ github.workspace }}:/workspace -w /workspace cr.jdbnet.co.uk/jdbnet/wails-builder:latest bash -c "
+ # Inject version into wails.json for NSIS & executable properties
+ sed -i 's/\"productVersion\": \".*\"/\"productVersion\": \"${{ steps.get_version.outputs.CLEAN_VERSION }}\"/' wails.json
+
+ # Install frontend dependencies
+ cd frontend && npm install && cd ..
+
+ # Linux amd64
+ wails build -platform linux/amd64 -tags webkit2_41 -clean -ldflags \"-X main.Version=${{ steps.get_version.outputs.VERSION }}\"
+ cp build/bin/goexplore dist/goexplore-linux-amd64
+
+ # Windows amd64
+ wails build -platform windows/amd64 -clean -nsis -ldflags \"-X main.Version=${{ steps.get_version.outputs.VERSION }}\"
+ cp build/bin/GoExplore-amd64-installer.exe dist/goexplore-windows-installer.exe
+ "
+
+ # - name: Generate Changelog
+ # id: changelog
+ # uses: https://github.com/metcalfc/changelog-generator@v4.6.2
+ # with:
+ # myToken: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Create Gitea Release
+ uses: https://gitea.com/actions/gitea-release-action@v1
+ with:
+ tag_name: ${{ steps.get_version.outputs.VERSION }}
+ name: GoExplore ${{ steps.get_version.outputs.VERSION }}
+ body: ${{ steps.changelog.outputs.changelog }}
+ draft: false
+ prerelease: false
+ files: |
+ dist/goexplore-linux-amd64
+ dist/goexplore-windows-installer.exe
+
+ - name: Upload to Cloudflare R2
+ env:
+ AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+ AWS_DEFAULT_REGION: auto
+ R2_ENDPOINT: ${{ secrets.R2_ENDPOINT_URL }}
+ run: |
+ aws s3 cp dist/goexplore-linux-amd64 s3://apps/goexplore/goexplore-linux-amd64 --endpoint-url "$R2_ENDPOINT"
+ aws s3 cp dist/goexplore-windows-installer.exe s3://apps/goexplore/goexplore-windows-installer.exe --endpoint-url "$R2_ENDPOINT"
+
+ - name: Clean up
+ run: sudo rm -rf dist build/bin/*
diff --git a/app.go b/app.go
index 764e901..7788abf 100644
--- a/app.go
+++ b/app.go
@@ -44,6 +44,10 @@ func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
+func (a *App) GetVersion() string {
+ return Version
+}
+
func (a *App) GetConnections() []config.ConnectionConfig {
if a.cfg == nil {
return nil
diff --git a/frontend/index.html b/frontend/index.html
index 77c94c6..0e2d7dd 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -11,6 +11,7 @@
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 2c95fdd..5f5bd0e 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,4 +1,4 @@
-import { ListDir, GetConnections, SaveConnection, DeleteConnection, Delete, Rename, PromptUploadFiles, PromptUploadDirectory, PromptDownload, TransferItems, GetTransfers, ClearTransfers } from '../wailsjs/go/main/App.js';
+import { GetVersion, ListDir, GetConnections, SaveConnection, DeleteConnection, Delete, Rename, PromptUploadFiles, PromptUploadDirectory, PromptDownload, TransferItems, GetTransfers, ClearTransfers } from '../wailsjs/go/main/App.js';
let currentConn = 'local';
let currentPath = '';
@@ -10,9 +10,9 @@ let sortField = 'name';
let sortAsc = true;
function uuidv4() {
- return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
- (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
- );
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+ );
}
async function init() {
@@ -24,9 +24,17 @@ async function init() {
}
}
+ try {
+ const version = await GetVersion();
+ const vSpan = document.getElementById('app-version');
+ if (vSpan && version) {
+ vSpan.innerText = version;
+ }
+ } catch (e) { }
+
await loadConnections();
await loadDirectory(currentConn, currentPath);
-
+
document.getElementById('conn-form').addEventListener('submit', async (e) => {
e.preventDefault();
const conn = {
@@ -41,12 +49,12 @@ async function init() {
username: document.getElementById('conn-username').value
};
const secret = document.getElementById('conn-secret').value;
-
+
try {
await SaveConnection(conn, secret);
closeConnModal();
loadConnections();
- } catch(err) {
+ } catch (err) {
alert("Failed to save connection: " + err);
}
});
@@ -57,7 +65,7 @@ async function loadConnections() {
list.innerHTML = `
OS Local Filesystem
`;
-
+
try {
const conns = await GetConnections();
connectionsCache = conns || [];
@@ -70,7 +78,7 @@ async function loadConnections() {
list.appendChild(el);
});
}
- } catch(e) {
+ } catch (e) {
console.error(e);
}
}
@@ -95,9 +103,9 @@ window.editConnection = (id) => {
document.getElementById('conn-region').value = c.region || '';
document.getElementById('conn-pathstyle').checked = c.path_style || false;
document.getElementById('conn-username').value = c.username || '';
- document.getElementById('conn-secret').value = '';
+ document.getElementById('conn-secret').value = '';
document.getElementById('conn-delete-btn').style.display = 'block';
-
+
updateProtocolFields();
document.getElementById('conn-modal').style.display = 'flex';
};
@@ -117,10 +125,10 @@ window.closeConnModal = () => {
window.updateProtocolFields = () => {
const protocol = document.getElementById('conn-protocol').value;
-
+
const showBucket = protocol === 's3' || protocol === 'smb';
document.getElementById('bucket-fields').style.display = showBucket ? 'block' : 'none';
-
+
// Only show S3 specific fields when S3 is selected
const showS3Specific = protocol === 's3';
document.getElementById('region-group').style.display = showS3Specific ? 'flex' : 'none';
@@ -128,17 +136,17 @@ window.updateProtocolFields = () => {
};
window.deleteConnection = async () => {
- if(!confirm("Are you sure you want to delete this connection?")) return;
+ if (!confirm("Are you sure you want to delete this connection?")) return;
const id = document.getElementById('conn-id').value;
try {
await DeleteConnection(id);
closeConnModal();
- if(currentConn === id) {
+ if (currentConn === id) {
switchConn('local');
} else {
loadConnections();
}
- } catch(err) {
+ } catch (err) {
alert("Failed to delete connection: " + err);
}
};
@@ -147,13 +155,13 @@ async function loadDirectory(connId, path) {
const tbody = document.getElementById('file-list');
tbody.innerHTML = '
| Loading... |
';
document.getElementById('breadcrumb').innerText = path || '/';
-
+
try {
const files = await ListDir(connId, path);
tbody.innerHTML = '';
selectedItems = [];
lastSelectedPath = null;
-
+
let displayFiles = files || [];
if (!showHiddenFiles) {
displayFiles = displayFiles.filter(f => !f.name.startsWith('.'));
@@ -163,12 +171,12 @@ async function loadDirectory(connId, path) {
tbody.innerHTML = '
| Empty directory |
';
return;
}
-
+
displayFiles.sort((a, b) => {
if (a.is_dir !== b.is_dir) return a.is_dir ? -1 : 1;
-
+
let cmp = 0;
- switch(sortField) {
+ switch (sortField) {
case 'name':
cmp = a.name.localeCompare(b.name);
break;
@@ -203,7 +211,7 @@ async function loadDirectory(connId, path) {
tr.onclick = (e) => {
const path = f.path;
const isSelected = selectedItems.some(i => i.path === path);
-
+
if (e.ctrlKey || e.metaKey) {
if (isSelected) {
selectedItems = selectedItems.filter(i => i.path !== path);
@@ -219,13 +227,13 @@ async function loadDirectory(connId, path) {
const currIdx = allRows.findIndex(r => r.dataset.path === path);
const startIdx = Math.min(lastIdx, currIdx);
const endIdx = Math.max(lastIdx, currIdx);
-
+
selectedItems = [];
allRows.forEach(r => r.classList.remove('selected'));
-
- for(let i=startIdx; i<=endIdx; i++) {
+
+ for (let i = startIdx; i <= endIdx; i++) {
const row = allRows[i];
- if(row.dataset.path) {
+ if (row.dataset.path) {
row.classList.add('selected');
selectedItems.push({
path: row.dataset.path,
@@ -251,7 +259,7 @@ async function loadDirectory(connId, path) {
}
tbody.appendChild(tr);
});
- } catch(e) {
+ } catch (e) {
tbody.innerHTML = `
| Error: ${e} |
`;
}
}
@@ -279,7 +287,7 @@ function formatDate(dateString) {
if (!dateString) return '-';
const d = new Date(dateString);
if (isNaN(d.getTime())) return dateString;
- return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth()+1).padStart(2, '0')}/${d.getFullYear()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
+ return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}/${d.getFullYear()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
window.setSort = (field) => {
@@ -300,7 +308,7 @@ window.clearTransfers = async () => {
try {
await ClearTransfers();
pollTransfers();
- } catch(e) {
+ } catch (e) {
console.error(e);
}
};
@@ -311,7 +319,7 @@ async function pollTransfers() {
const list = document.getElementById('transfer-list');
const btn = document.getElementById('transfers-btn');
btn.innerText = `Transfers (${transfers ? transfers.length : 0})`;
-
+
if (!transfers || transfers.length === 0) {
list.innerHTML = '
No active transfers
';
return;
@@ -323,7 +331,7 @@ async function pollTransfers() {
const speed = t.status === 'active' ? `${t.speed_mbps.toFixed(2)} MB/s` : t.status;
const eta = t.status === 'active' ? `${t.eta_seconds}s remaining` : '';
const color = t.status === 'failed' ? 'var(--error)' : (t.status === 'complete' ? 'var(--success)' : 'var(--accent)');
-
+
html += `
${t.filename}
@@ -338,7 +346,7 @@ async function pollTransfers() {
`;
});
list.innerHTML = html;
- } catch(e) {
+ } catch (e) {
console.error(e);
}
}
@@ -359,7 +367,7 @@ document.addEventListener('contextmenu', (e) => {
if (row && row.dataset.path && !row.closest('#transfer-dest-list')) {
e.preventDefault();
-
+
if (!selectedItems.some(i => i.path === row.dataset.path)) {
selectedItems = [{
path: row.dataset.path,
@@ -384,7 +392,7 @@ document.addEventListener('contextmenu', (e) => {
menu.style.top = `${e.pageY}px`;
} else if (isBrowserPane && !e.target.closest('#transfer-dest-list')) {
e.preventDefault();
-
+
document.getElementById('ctx-rename').style.display = 'none';
document.getElementById('ctx-transfer').style.display = 'none';
document.getElementById('ctx-download').style.display = 'none';
@@ -421,7 +429,7 @@ window.handleContextMenu = async (action) => {
}
if (selectedItems.length === 0) return;
-
+
try {
if (action === 'delete') {
if (confirm(`Are you sure you want to delete ${selectedItems.length} items?`)) {
@@ -441,7 +449,7 @@ window.handleContextMenu = async (action) => {
const pathParts = item.path.split('/');
pathParts[pathParts.length - 1] = newName;
const newPath = pathParts.join('/');
-
+
await Rename(currentConn, item.path, newPath);
refreshCurrentDir();
}
@@ -494,11 +502,11 @@ window.loadTransferDestDirectory = async (connId, path) => {
const tbody = document.getElementById('transfer-dest-list');
tbody.innerHTML = '
| Loading... |
';
document.getElementById('transfer-dest-breadcrumb').innerText = path || '/';
-
+
try {
const files = await ListDir(connId, path);
tbody.innerHTML = '';
-
+
if (path && path !== '/') {
const tr = document.createElement('tr');
tr.innerHTML = `
📁 .. | `;
@@ -515,7 +523,7 @@ window.loadTransferDestDirectory = async (connId, path) => {
tbody.innerHTML += '
| Empty directory |
';
return;
}
-
+
files.forEach(f => {
const tr = document.createElement('tr');
tr.style.cursor = 'pointer';
@@ -527,7 +535,7 @@ window.loadTransferDestDirectory = async (connId, path) => {
}
tbody.appendChild(tr);
});
- } catch(e) {
+ } catch (e) {
tbody.innerHTML = `
| Error: ${e} |
`;
}
};
@@ -540,7 +548,7 @@ window.executeTransfer = async () => {
showTransfers();
selectedItems = [];
Array.from(document.querySelectorAll('#file-list tr')).forEach(r => r.classList.remove('selected'));
- } catch(e) {
+ } catch (e) {
alert("Transfer failed: " + e);
}
};
diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts
index 90d039c..c3f43c3 100755
--- a/frontend/wailsjs/go/main/App.d.ts
+++ b/frontend/wailsjs/go/main/App.d.ts
@@ -15,6 +15,8 @@ export function GetConnections():Promise
>;
export function GetTransfers():Promise>;
+export function GetVersion():Promise;
+
export function ListDir(arg1:string,arg2:string):Promise>;
export function MkDir(arg1:string,arg2:string):Promise;
diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js
index c0c70e3..1e0950e 100755
--- a/frontend/wailsjs/go/main/App.js
+++ b/frontend/wailsjs/go/main/App.js
@@ -22,6 +22,10 @@ export function GetTransfers() {
return window['go']['main']['App']['GetTransfers']();
}
+export function GetVersion() {
+ return window['go']['main']['App']['GetVersion']();
+}
+
export function ListDir(arg1, arg2) {
return window['go']['main']['App']['ListDir'](arg1, arg2);
}
diff --git a/main.go b/main.go
index c86cbff..7d90f1d 100644
--- a/main.go
+++ b/main.go
@@ -20,6 +20,8 @@ var assets embed.FS
//go:embed build/appicon.png
var icon []byte
+var Version string = "dev"
+
func installLinux() {
if runtime.GOOS != "linux" {
return
diff --git a/wails.json b/wails.json
index d71e659..ac2ddf6 100644
--- a/wails.json
+++ b/wails.json
@@ -11,6 +11,7 @@
},
"info": {
"companyName": "JDB-NET Limited",
- "productName": "GoExplore"
+ "productName": "GoExplore",
+ "productVersion": "0.0.0"
}
}
\ No newline at end of file