Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Work with **SQLite**, **PostgreSQL** and **MySQL / MariaDB** in a single fast de
### πŸ‘‰ Everything you need. Nothing you don’t.

- Query faster with smart, schema-aware autocomplete
- Reach databases behind a bastion over an SSH tunnel
- Stream results - rows arrive as the driver yields them
- Run multi-statement scripts and get a result tab per output
- Explore schemas instantly
Expand Down Expand Up @@ -127,12 +128,12 @@ XenSQL focuses on one thing:

## πŸ—„οΈ Supported Databases

| Database | Read & write | Read-only mode | Secure transport |
|----------|:------------:|:--------------:|:-----------------|
| **PostgreSQL** | βœ… | βœ… | SSL - `disable` / `require` / `verify-full` |
| **MySQL** | βœ… | βœ… | TLS |
| **MariaDB** | βœ… | βœ… | TLS |
| **SQLite** | βœ… | βœ… | local file |
| Database | Read & write | Read-only mode | Secure transport | SSH tunnel |
|----------|:------------:|:--------------:|:-----------------|:----------:|
| **PostgreSQL** | βœ… | βœ… | SSL - `disable` / `require` / `verify-full` | βœ… |
| **MySQL** | βœ… | βœ… | TLS | βœ… |
| **MariaDB** | βœ… | βœ… | TLS | βœ… |
| **SQLite** | βœ… | βœ… | local file | n/a |

---

Expand All @@ -143,8 +144,20 @@ XenSQL focuses on one thing:
- Per-connection **tab colors**
- **Read-only mode** with defense-in-depth - blocked at both the Wails layer and inside each driver
- PostgreSQL SSL (`disable` / `require` / `verify-full`) and MySQL TLS
- **SSH tunnel** to reach databases behind a bastion (see below)
- SQLite file picker workflow

### πŸ” SSH Tunnel

Connect to a database that only its bastion can reach - no `ssh -L` in a side terminal.

- Auth by **private key** (with passphrase), **password**, or a running **ssh-agent**
- Host keys verified against `~/.ssh/known_hosts` by default, with an override for a custom file
- Unknown bastion? The error tells you the exact `ssh-keyscan` line to run - or tick **Skip host key check**
- Keepalives hold the tunnel open under a long, idle session
- The tunnel carries the driver's own traffic, so the database host stays what the bastion resolves
(usually `localhost`) and `sslmode=verify-full` still checks the real certificate

---

## 🧠 SQL Editor
Expand Down
6 changes: 6 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ <h2>Supported databases</h2>
<ul>
<li>Read &amp; write + read-only mode</li>
<li>SSL: <code>require</code> / <code>verify-full</code></li>
<li>SSH tunnel to a bastion</li>
<li>Powered by pgx</li>
</ul>
</div>
Expand All @@ -138,6 +139,7 @@ <h2>Supported databases</h2>
<ul>
<li>Read &amp; write + read-only mode</li>
<li>TLS transport</li>
<li>SSH tunnel to a bastion</li>
<li>Powered by go-sql-driver</li>
</ul>
</div>
Expand Down Expand Up @@ -194,6 +196,10 @@ <h3>πŸ“€ One-click export</h3>
<h3>πŸ“š Query library &amp; history</h3>
<p>Save, rename, filter and reuse queries with dirty-state tracking, plus per-connection history with success/error status and duration.</p>
</div>
<div class="feature-card">
<h3>πŸ” SSH tunnel</h3>
<p>Reach a database only its bastion can see - no <code>ssh -L</code> in a side terminal. Private key, password or ssh-agent auth, with host keys checked against <code>known_hosts</code>.</p>
</div>
<div class="feature-card">
<h3>πŸ”Œ Connection manager</h3>
<p>Create, test and organize connections into drag-and-drop folders, with per-connection tab colors and a defense-in-depth read-only mode.</p>
Expand Down
83 changes: 49 additions & 34 deletions frontend/bindings/xensql/internal/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,27 @@ export function GetEditorSession(): $CancellablePromise<storage$0.EditorSession>
});
}

export function GetPathDefaults(): $CancellablePromise<$models.PathDefaults> {
return $Call.ByID(230484794).then(($result: any) => {
return $$createType5($result);
});
}

export function GetPendingFile(): $CancellablePromise<{ [_ in string]?: string }> {
return $Call.ByID(109090272).then(($result: any) => {
return $$createType5($result);
return $$createType6($result);
});
}

export function GetQueryHistory(connectionID: string, limit: number): $CancellablePromise<database$0.HistoryEntry[]> {
return $Call.ByID(4033942967, connectionID, limit).then(($result: any) => {
return $$createType7($result);
return $$createType8($result);
});
}

export function GetSettings(): $CancellablePromise<{ [_ in string]?: string }> {
return $Call.ByID(222085978).then(($result: any) => {
return $$createType5($result);
return $$createType6($result);
});
}

Expand All @@ -158,7 +164,7 @@ export function InitStores(configDir: string): $CancellablePromise<void> {

export function InsertRow(connectionID: string, schema: string, table: string, values: { [_ in string]?: any }): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1655633052, connectionID, schema, table, values).then(($result: any) => {
return $$createType8($result);
return $$createType9($result);
});
}

Expand All @@ -172,37 +178,37 @@ export function IsDesktopMode(): $CancellablePromise<boolean> {

export function ListColumns(connectionID: string, schema: string, table: string): $CancellablePromise<database$0.ColumnInfo[]> {
return $Call.ByID(3118816600, connectionID, schema, table).then(($result: any) => {
return $$createType10($result);
return $$createType11($result);
});
}

export function ListConnections(): $CancellablePromise<database$0.ConnectionConfig[]> {
return $Call.ByID(1654481338).then(($result: any) => {
return $$createType12($result);
return $$createType13($result);
});
}

export function ListFolders(): $CancellablePromise<storage$0.ConnectionFolder[]> {
return $Call.ByID(1373072582).then(($result: any) => {
return $$createType14($result);
return $$createType15($result);
});
}

export function ListSavedQueries(connectionID: string): $CancellablePromise<database$0.SavedQuery[]> {
return $Call.ByID(2254370512, connectionID).then(($result: any) => {
return $$createType16($result);
return $$createType17($result);
});
}

export function ListSchemas(connectionID: string): $CancellablePromise<database$0.SchemaInfo[]> {
return $Call.ByID(2969331507, connectionID).then(($result: any) => {
return $$createType18($result);
return $$createType19($result);
});
}

export function ListTables(connectionID: string, schema: string): $CancellablePromise<database$0.TableInfo[]> {
return $Call.ByID(773846824, connectionID, schema).then(($result: any) => {
return $$createType20($result);
return $$createType21($result);
});
}

Expand All @@ -212,18 +218,26 @@ export function ListTables(connectionID: string, schema: string): $CancellablePr
*/
export function LoadSchemaData(connectionID: string): $CancellablePromise<database$0.SchemaBundle> {
return $Call.ByID(4233994986, connectionID).then(($result: any) => {
return $$createType21($result);
return $$createType22($result);
});
}

export function PickExportSavePath(ext: string): $CancellablePromise<string> {
return $Call.ByID(1154457340, ext);
}

export function PickKnownHostsFile(): $CancellablePromise<string> {
return $Call.ByID(155068216);
}

export function PickSQLiteFile(): $CancellablePromise<string> {
return $Call.ByID(2769801138);
}

export function PickSSHKeyFile(): $CancellablePromise<string> {
return $Call.ByID(4136702487);
}

export function QueryTableStream(connectionID: string, tabID: string, req: database$0.TableDataRequest): $CancellablePromise<void> {
return $Call.ByID(3607474293, connectionID, tabID, req);
}
Expand All @@ -241,7 +255,7 @@ export function RollbackTransaction(tabID: string): $CancellablePromise<void> {

export function SaveConnection(cfg: database$0.ConnectionConfig): $CancellablePromise<database$0.ConnectionConfig> {
return $Call.ByID(571429232, cfg).then(($result: any) => {
return $$createType11($result);
return $$createType12($result);
});
}

Expand All @@ -251,13 +265,13 @@ export function SaveEditorSession(session: storage$0.EditorSession): $Cancellabl

export function SaveFolder(f: storage$0.ConnectionFolder): $CancellablePromise<storage$0.ConnectionFolder> {
return $Call.ByID(1026390748, f).then(($result: any) => {
return $$createType13($result);
return $$createType14($result);
});
}

export function SaveSavedQuery(q: database$0.SavedQuery): $CancellablePromise<database$0.SavedQuery> {
return $Call.ByID(1936361457, q).then(($result: any) => {
return $$createType15($result);
return $$createType16($result);
});
}

Expand Down Expand Up @@ -293,7 +307,7 @@ export function SetWindowStateFlush(flush: any): $CancellablePromise<void> {

export function SettingsStore(): $CancellablePromise<storage$0.SettingsStore | null> {
return $Call.ByID(2329735545).then(($result: any) => {
return $$createType23($result);
return $$createType24($result);
});
}

Expand All @@ -318,22 +332,23 @@ const $$createType1 = $Create.Nullable($$createType0);
const $$createType2 = $models.AppInfo.createFrom;
const $$createType3 = database$0.ConnectionStatus.createFrom;
const $$createType4 = storage$0.EditorSession.createFrom;
const $$createType5 = $Create.Map($Create.Any, $Create.Any);
const $$createType6 = database$0.HistoryEntry.createFrom;
const $$createType7 = $Create.Array($$createType6);
const $$createType8 = $Create.Map($Create.Any, $Create.Any);
const $$createType9 = database$0.ColumnInfo.createFrom;
const $$createType10 = $Create.Array($$createType9);
const $$createType11 = database$0.ConnectionConfig.createFrom;
const $$createType12 = $Create.Array($$createType11);
const $$createType13 = storage$0.ConnectionFolder.createFrom;
const $$createType14 = $Create.Array($$createType13);
const $$createType15 = database$0.SavedQuery.createFrom;
const $$createType16 = $Create.Array($$createType15);
const $$createType17 = database$0.SchemaInfo.createFrom;
const $$createType18 = $Create.Array($$createType17);
const $$createType19 = database$0.TableInfo.createFrom;
const $$createType20 = $Create.Array($$createType19);
const $$createType21 = database$0.SchemaBundle.createFrom;
const $$createType22 = storage$0.SettingsStore.createFrom;
const $$createType23 = $Create.Nullable($$createType22);
const $$createType5 = $models.PathDefaults.createFrom;
const $$createType6 = $Create.Map($Create.Any, $Create.Any);
const $$createType7 = database$0.HistoryEntry.createFrom;
const $$createType8 = $Create.Array($$createType7);
const $$createType9 = $Create.Map($Create.Any, $Create.Any);
const $$createType10 = database$0.ColumnInfo.createFrom;
const $$createType11 = $Create.Array($$createType10);
const $$createType12 = database$0.ConnectionConfig.createFrom;
const $$createType13 = $Create.Array($$createType12);
const $$createType14 = storage$0.ConnectionFolder.createFrom;
const $$createType15 = $Create.Array($$createType14);
const $$createType16 = database$0.SavedQuery.createFrom;
const $$createType17 = $Create.Array($$createType16);
const $$createType18 = database$0.SchemaInfo.createFrom;
const $$createType19 = $Create.Array($$createType18);
const $$createType20 = database$0.TableInfo.createFrom;
const $$createType21 = $Create.Array($$createType20);
const $$createType22 = database$0.SchemaBundle.createFrom;
const $$createType23 = storage$0.SettingsStore.createFrom;
const $$createType24 = $Create.Nullable($$createType23);
3 changes: 2 additions & 1 deletion frontend/bindings/xensql/internal/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export {
};

export {
AppInfo
AppInfo,
PathDefaults
} from "./models.js";
34 changes: 34 additions & 0 deletions frontend/bindings/xensql/internal/app/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,37 @@ export class AppInfo {
return new AppInfo($$parsedSource as Partial<AppInfo>);
}
}

/**
* PathDefaults lets the UI show example paths that match the host, not one platform's convention.
*/
export class PathDefaults {
"platform": string;
"separator": string;

/**
* SSHKey and SSHKnownHosts are empty when the home directory cannot be resolved.
*/
"sshKey"?: string;
"sshKnownHosts"?: string;

/** Creates a new PathDefaults instance. */
constructor($$source: Partial<PathDefaults> = {}) {
if (!("platform" in $$source)) {
this["platform"] = "";
}
if (!("separator" in $$source)) {
this["separator"] = "";
}

Object.assign(this, $$source);
}

/**
* Creates a new PathDefaults instance from a string or object.
*/
static createFrom($$source: any = {}): PathDefaults {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new PathDefaults($$parsedSource as Partial<PathDefaults>);
}
}
2 changes: 2 additions & 0 deletions frontend/bindings/xensql/internal/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export {
QueryResult,
RowDelete,
RowUpdate,
SSHAuthMethod,
SSHConfig,
SavedQuery,
SchemaBundle,
SchemaInfo,
Expand Down
Loading
Loading