Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote Server #695

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
21 changes: 21 additions & 0 deletions backend/database/KnexRemoteSQLiteClient.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import KnexBetterSqlite3Client from 'knex/lib/dialects/better-sqlite3';
import fetch from 'node-fetch';

class SQLiteServerClient extends KnexBetterSqlite3Client {
async acquireRawConnection() {
return new this.driver(':memory:');
}

async _query(_, obj) {
if (!obj.sql) throw new Error('The query is empty');

const response = await fetch(this.connectionSettings.filename, {
method: 'POST',
body: JSON.stringify(obj),
headers: { 'Content-Type': 'application/json' },
});
return await response.json();
}
}

export default SQLiteServerClient;
6 changes: 5 additions & 1 deletion backend/database/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
UpdateSinglesConfig,
} from './types';

import { isValidUrl } from 'utils/misc';
import SQLiteServerClient from './KnexRemoteSQLiteClient.mjs';

/**
* # DatabaseCore
* This is the ORM, the DatabaseCore interface (function signatures) should be
Expand Down Expand Up @@ -56,7 +59,8 @@ export default class DatabaseCore extends DatabaseBase {
super();
this.dbPath = dbPath ?? ':memory:';
this.connectionParams = {
client: 'better-sqlite3',
// @ts-ignore
client: isValidUrl(dbPath ?? '') ? SQLiteServerClient : 'better-sqlite3',
connection: {
filename: this.dbPath,
},
Expand Down
28 changes: 14 additions & 14 deletions main/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,30 @@ import { Main } from 'main';
import config from 'utils/config';
import { BackendResponse } from 'utils/ipc/types';
import { IPC_CHANNELS } from 'utils/messages';
import { isValidUrl } from 'utils/misc';
import type { ConfigFilesWithModified } from 'utils/types';

export async function setAndGetCleanedConfigFiles() {
const files = config.get('files', []);

const cleanedFileMap: Map<string, ConfigFile> = new Map();
for (const file of files) {
const key = `${file.companyName}-${file.dbPath}`;

if (!file.companyName) continue;
if (isValidUrl(file.dbPath)) {
cleanedFileMap.set(key, file);
continue;
}

const exists = await fs
.access(file.dbPath, constants.W_OK)
.then(() => true)
.catch(() => false);

if (!file.companyName) {
continue;
}

const key = `${file.companyName}-${file.dbPath}`;
if (!exists || cleanedFileMap.has(key)) {
continue;
}

cleanedFileMap.set(key, file);
}

Expand All @@ -37,14 +40,11 @@ export async function setAndGetCleanedConfigFiles() {
export async function getConfigFilesWithModified(files: ConfigFile[]) {
const filesWithModified: ConfigFilesWithModified[] = [];
for (const { dbPath, id, companyName, openCount } of files) {
const { mtime } = await fs.stat(dbPath);
filesWithModified.push({
id,
dbPath,
companyName,
modified: mtime.toISOString(),
openCount,
});
const modified = isValidUrl(dbPath)
? ''
: (await fs.stat(dbPath)).mtime.toISOString();

filesWithModified.push({ id, dbPath, companyName, modified, openCount });
}

return filesWithModified;
Expand Down
10 changes: 9 additions & 1 deletion main/registerIpcMainActionListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { autoUpdater } from 'electron-updater';
import { constants } from 'fs';
import fs from 'fs-extra';
import path from 'path';
import config from 'utils/config';
import { isValidUrl } from 'utils/misc';
import { SelectFileOptions, SelectFileReturn } from 'utils/types';
import databaseManager from '../backend/database/manager';
import { emitMainProcessError } from '../backend/helpers';
Expand Down Expand Up @@ -177,7 +179,13 @@ export default function registerIpcMainActionListeners(main: Main) {
});

ipcMain.handle(IPC_ACTIONS.DELETE_FILE, async (_, filePath: string) => {
return getErrorHandledReponse(async () => await fs.unlink(filePath));
if (!isValidUrl(filePath)) {
return getErrorHandledReponse(async () => await fs.unlink(filePath));
}
const files = config.get('files', []);
const remainingFiles = files.filter(({ dbPath }) => filePath !== dbPath);
config.set('files', remainingFiles);
return {};
});

ipcMain.handle(IPC_ACTIONS.GET_DB_LIST, async () => {
Expand Down
7 changes: 6 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import { RTL_LANGUAGES } from 'fyo/utils/consts';
import { ModelNameEnum } from 'models/types';
import { systemLanguageRef } from 'src/utils/refs';
import { isValidUrl } from 'utils/misc';
import { defineComponent, provide, ref, Ref } from 'vue';
import WindowsTitleBar from './components/WindowsTitleBar.vue';
import { handleErrorWithDialog } from './errorHandling';
Expand Down Expand Up @@ -162,7 +163,11 @@ export default defineComponent({
},
async fileSelected(filePath: string): Promise<void> {
fyo.config.set('lastSelectedFilePath', filePath);
if (filePath !== ':memory:' && !(await ipc.checkDbAccess(filePath))) {
if (
!isValidUrl(filePath) &&
filePath !== ':memory:' &&
!(await ipc.checkDbAccess(filePath))
) {
await showDialog({
title: this.t`Cannot open file`,
type: 'error',
Expand Down
77 changes: 71 additions & 6 deletions src/pages/DatabaseSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,25 @@
</div>
</div>

<!-- Existing Company Server (Green Icon) -->
<div
class="px-4 h-row-largest flex flex-row items-center gap-4 p-2"
:class="creatingDemo ? '' : 'hover:bg-gray-50 cursor-pointer'"
@click="openServerModal = true && !creatingDemo"
>
<div class="w-8 h-8 rounded-full bg-green-500 relative flex-center">
<feather-icon name="link" class="w-4 h-4 text-white" />
</div>
<div>
<p class="font-medium">
{{ t`Existing Company Server` }}
</p>
<p class="text-sm text-gray-600">
{{ t`Load an existing company from a frappe server` }}
</p>
</div>
</div>

<!-- Create Demo (Pink Icon) -->
<div
v-if="!files?.length"
Expand Down Expand Up @@ -194,7 +213,7 @@
/>

<!-- Base Count Selection when Dev -->
<Modal :open-modal="openModal" @closemodal="openModal = false">
<Modal :open-modal="openDemoModal" @closemodal="openDemoModal = false">
<div class="p-4 text-gray-900 w-form">
<h2 class="text-xl font-semibold select-none">Set Base Count</h2>
<p class="text-base mt-2">
Expand All @@ -218,12 +237,12 @@
/>
</div>
<div class="flex justify-between">
<Button @click="openModal = false">Cancel</Button>
<Button @click="openDemoModal = false">Cancel</Button>
<Button
type="primary"
@click="
() => {
openModal = false;
openDemoModal = false;
startDummyInstanceSetup();
}
"
Expand All @@ -232,6 +251,45 @@
</div>
</div>
</Modal>

<Modal :open-modal="openServerModal" @closemodal="!openServerModal">
<div class="p-4 text-gray-900 w-form">
<h2 class="text-xl font-semibold select-none">Connect to Server</h2>
<p class="text-base mt-2">
Enter the adress of the server you want to connect to.
</p>
<div class="flex my-12 justify-center items-baseline gap-4 text-base">
<label for="basecount" class="text-gray-600">Adress</label>
<input
v-model="serverAdress"
type="url"
name="basecount"
class="
bg-gray-100
focus:bg-gray-200
rounded-md
px-2
py-1
w-2/3
outline-none
"
/>
</div>
<div class="flex justify-between">
<Button @click="openServerModal = false">Cancel</Button>
<Button
type="primary"
@click="
() => {
openServerModal = false;
connectToServer();
}
"
>Connect</Button
>
</div>
</div>
</Modal>
</div>
</template>
<script lang="ts">
Expand Down Expand Up @@ -263,15 +321,19 @@ export default defineComponent({
emits: ['file-selected', 'new-database'],
data() {
return {
openModal: false,
openDemoModal: false,
openServerModal: false,
serverAdress: '',
baseCount: 100,
creationMessage: '',
creationPercent: 0,
creatingDemo: false,
loadingDatabase: false,
files: [],
} as {
openModal: boolean;
openDemoModal: boolean;
openServerModal: boolean;
serverAdress: string;
baseCount: number;
creationMessage: string;
creationPercent: number;
Expand Down Expand Up @@ -330,7 +392,7 @@ export default defineComponent({
if (!fyo.store.isDevelopment) {
await this.startDummyInstanceSetup();
} else {
this.openModal = true;
this.openDemoModal = true;
}
},
async startDummyInstanceSetup() {
Expand Down Expand Up @@ -378,6 +440,9 @@ export default defineComponent({
const filePath = (await getSelectedFilePath())?.filePaths?.[0];
this.emitFileSelected(filePath);
},
connectToServer() {
this.emitFileSelected(this.serverAdress);
},
selectFile(file: ConfigFilesWithModified) {
if (this.creatingDemo) {
return;
Expand Down
6 changes: 6 additions & 0 deletions utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ export function logUnexpected(detail: Partial<UnexpectedLogObject>) {
});
window.dispatchEvent(event);
}

export function isValidUrl(string: string) {
const urlPattern =
/(?:https?):\/\/(\w+:?\w*)?(\S+)(:\d+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/;
return urlPattern.test(string);
}