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

Fix/server web implement config #3368

Open
wants to merge 8 commits into
base: develop
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
77 changes: 77 additions & 0 deletions .scripts/configure.electron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as fs from 'fs';
import * as path from 'path';
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs';

interface Arguments {
type: 'server' | 'constant'
}
const argv = yargs(hideBin(process.argv))
.options({
type: {
type: 'string',
choices: ['server', 'constant'],
demandOption: true,
description: 'Type of configuration to modify'
}
})
.parseSync() as Arguments;

function modifiedNextServer() {
const filePath = path.resolve(__dirname, '../apps/server-web/release/app/dist/standalone/apps/web/server.js');
try {
let fileContent = fs.readFileSync(filePath, 'utf8');
const searchString = 'process.env.__NEXT_PRIVATE_STANDALONE_CONFIG';
const codeToInsert = `
nextConfig.serverRuntimeConfig = {
"GAUZY_API_SERVER_URL": process.env.GAUZY_API_SERVER_URL,
"NEXT_PUBLIC_GAUZY_API_SERVER_URL": process.env.NEXT_PUBLIC_GAUZY_API_SERVER_URL
}
`;

let lines = fileContent.split('\n');
const index = lines.findIndex((line) => line.includes(searchString));

if (index !== -1) {
lines.splice(index - 1, 0, codeToInsert);

fileContent = lines.join('\n');
fs.writeFileSync(filePath, fileContent, 'utf8');
console.log('Line of code successfully inserted.');
} else {
console.log(`The string "${searchString}" was not found in the file.`);
}
} catch (error) {
console.error('Failed to change static server configuration');
}
}

function updateWebConstant(setDesktopApp) {
const filePath = path.resolve(__dirname, '../apps/web/app/constants.ts');
try {
let fileContent = fs.readFileSync(filePath, 'utf8');
const envCheck = `export const IS_DESKTOP_APP = process.env.IS_DESKTOP_APP === 'true';`;
const hardcoded = `export const IS_DESKTOP_APP = true;`;

const [from, to] = setDesktopApp ? [envCheck, hardcoded] : [hardcoded, envCheck];

if (!fileContent.includes(from)) {
throw new Error(`Expected content not found in ${filePath}`);
}

fileContent = fileContent.replace(from, to);
fs.writeFileSync(filePath, fileContent, 'utf8');
console.log(`Successfully ${setDesktopApp ? 'set' : 'reverted'} IS_DESKTOP_APP`);
} catch (error) {
console.error('Failed to update constants:', error);
process.exit(1);
}
}


if (argv.type === 'server') {
modifiedNextServer();
updateWebConstant(false);
} else if (argv.type === 'constant') {
updateWebConstant(true);
}
34 changes: 34 additions & 0 deletions .scripts/copy-web-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { copy } from 'fs-extra';
import { join } from 'path';

async function copyWebBuild() {
const webDir = join(process.cwd(), 'apps/web');
const distDir = join(process.cwd(), 'apps/server-web/release/app/dist');

try {
// Copy standalone build
await copy(
join(webDir, '.next/standalone'),
join(distDir, 'standalone')
);

// Copy static files
await copy(
join(webDir, '.next/static'),
join(distDir, 'standalone/apps/web/.next/static')
);

// Copy public files
await copy(
join(webDir, 'public'),
join(distDir, 'standalone/apps/web/public')
);

console.log('Build files copied successfully');
} catch (error) {
console.error('Failed to copy build files:', error);
process.exit(1);
}
Comment on lines +8 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and consider parallel operations.

The current implementation has room for improvement in terms of performance and error handling:

  1. Copy operations could be executed in parallel
  2. Error handling could be more specific
  3. Consider cleanup on failure

Here's a suggested improvement:

 try {
+  const copyOperations = [
+    {
+      src: join(webDir, '.next/standalone'),
+      dest: join(distDir, 'standalone'),
+      type: 'standalone build'
+    },
+    {
+      src: join(webDir, '.next/static'),
+      dest: join(distDir, 'standalone/apps/web/.next/static'),
+      type: 'static files'
+    },
+    {
+      src: join(webDir, 'public'),
+      dest: join(distDir, 'standalone/apps/web/public'),
+      type: 'public files'
+    }
+  ];
+
+  await Promise.all(
+    copyOperations.map(async ({ src, dest, type }) => {
+      try {
+        await copy(src, dest);
+        console.log(`${type} copied successfully`);
+      } catch (err) {
+        throw new Error(`Failed to copy ${type}: ${err.message}`);
+      }
+    })
+  );
-    // Copy standalone build
-    await copy(
-      join(webDir, '.next/standalone'),
-      join(distDir, 'standalone')
-    );
-
-    // Copy static files
-    await copy(
-      join(webDir, '.next/static'),
-      join(distDir, 'standalone/apps/web/.next/static')
-    );
-
-    // Copy public files
-    await copy(
-      join(webDir, 'public'),
-      join(distDir, 'standalone/apps/web/public')
-    );

     console.log('Build files copied successfully');
   } catch (error) {
-    console.error('Failed to copy build files:', error);
+    console.error('Build copy failed:', error.message);
+    // Attempt cleanup
+    try {
+      await fs.remove(distDir);
+    } catch (cleanupError) {
+      console.error('Cleanup failed:', cleanupError.message);
+    }
     process.exit(1);
   }

Committable suggestion skipped: line range outside the PR's diff.

}

copyWebBuild();
24 changes: 12 additions & 12 deletions apps/server-web/src/locales/i18n/bg/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@
"SERVER_WINDOW": "Прозорец на сървъра"
},
"MENU_APP": {
"ABOUT": "Относно",
"QUIT": "Изход",
"WINDOW": "Прозорец",
"SUBMENU": {
"SETTING": "Настройки",
"SERVER_WINDOW": "Сървърен прозорец",
"LEARN_MORE": "Научете повече",
"DOC": "Документация",
"SETTING_DEV": "Настройки за разработчици",
"SERVER_DEV": "Сървър за разработчици"
"APP_ABOUT": "Относно",
"APP_QUIT": "Изход",
"APP_WINDOW": "Прозорец",
"APP_SUBMENU": {
"APP_SETTING": "Настройки",
"APP_SERVER_WINDOW": "Сървърен прозорец",
"APP_LEARN_MORE": "Научете повече",
"APP_DOC": "Документация",
"APP_SETTING_DEV": "Настройки за разработчици",
"APP_SERVER_DEV": "Сървър за разработчици"
},
"DEV": "Разработчик",
"HELP": "Помощ"
"APP_DEV": "Разработчик",
"APP_HELP": "Помощ"
Comment on lines +18 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove duplicate translation keys and improve consistency.

There are several issues with the translations:

  1. Duplicate keys: "APP_ABOUT" exists in both the root "MENU" object and "MENU_APP". This can lead to confusion and maintenance issues.
  2. Inconsistent capitalization: Some translations start with capital letters (e.g., "Относно") while others don't (e.g., "относно" in the root MENU).

Suggested fixes:

  1. Remove duplicate translations from the root "MENU" object
  2. Ensure consistent capitalization across translations
{
  "MENU": {
    "SERVER": "Сървър",
-   "APP_ABOUT": "относно",
-   "APP_QUIT": "Откажете се",
    // ... other MENU translations
  },
  "MENU_APP": {
    "APP_ABOUT": "Относно",
    "APP_QUIT": "Изход",
    // ... rest of MENU_APP translations
  }
}

Committable suggestion skipped: line range outside the PR's diff.

},
"FORM": {
"FIELDS": {
Expand Down
24 changes: 12 additions & 12 deletions apps/server-web/src/locales/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@
"SERVER_WINDOW": "Server Window"
},
"MENU_APP": {
"ABOUT": "About",
"QUIT": "Quit",
"WINDOW": "Window",
"SUBMENU": {
"SETTING": "Setting",
"SERVER_WINDOW": "Server Window",
"LEARN_MORE": "Learn More",
"DOC": "Documentation",
"SETTING_DEV": "Setting Dev.",
"SERVER_DEV": "Server Dev."
"APP_ABOUT": "About",
"APP_QUIT": "Quit",
"APP_WINDOW": "Window",
"APP_SUBMENU": {
"APP_SETTING": "Setting",
"APP_SERVER_WINDOW": "Server Window",
"APP_LEARN_MORE": "Learn More",
"APP_DOC": "Documentation",
"APP_SETTING_DEV": "Setting Dev.",
"APP_SERVER_DEV": "Server Dev."
},
"DEV": "Developer",
"HELP": "Help"
"APP_DEV": "Developer",
"APP_HELP": "Help"
},
"FORM": {
"FIELDS": {
Expand Down
4 changes: 3 additions & 1 deletion apps/server-web/src/main/helpers/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export const EventLists = {
SERVER_WINDOW: 'SERVER_WINDOW',
RESTART_SERVER: 'RESTART_SERVER',
CHANGE_THEME: 'CHANGE_THEME',
SETUP_WINDOW: 'SETUP_WINDOW'
SETUP_WINDOW: 'SETUP_WINDOW',
SETTING_WINDOW_DEV: 'SETTING_WINDOW_DEV',
SERVER_WINDOW_DEV: 'SERVER_WINDOW_DEV'
}

export const SettingPageTypeMessage = {
Expand Down
2 changes: 1 addition & 1 deletion apps/server-web/src/main/helpers/interfaces/i-constant.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type Channels = 'setting-page' | 'ipc-renderer' | 'language-set' | 'updater-page' | 'server-page' | 'theme-change' | 'current-theme';
export type Channels = 'setting-page' | 'ipc-renderer' | 'language-set' | 'updater-page' | 'server-page' | 'theme-change' | 'current-theme' | 'current-language';
15 changes: 15 additions & 0 deletions apps/server-web/src/main/helpers/interfaces/i-menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MenuItemConstructorOptions } from 'electron';
export interface AppMenu {
id?: string;
label: string; // Menu label
submenu?: (AppMenu | ElectronMenuItem)[]; // Nested menus or Electron menu items
role?: 'appMenu' | 'fileMenu' | 'editMenu' | 'viewMenu' | 'windowMenu' | 'help'; // Predefined menu roles
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Menu roles defined in interface are not utilized in implementation

The interface i-menu.ts defines menu roles ('appMenu', 'fileMenu', 'editMenu', 'viewMenu', 'windowMenu', 'help'), but the actual menu implementation in menu.ts doesn't use these roles. Instead, it:

  • Uses custom IDs for menu identification
  • Implements a basic menu structure with app, window, help, and dev sections
  • Handles menu construction through direct object composition

Consider either:

  • Removing unused role definitions from the interface
  • Or implementing the roles in the menu builder for better semantic organization
🔗 Analysis chain

Verify menu role support in the application.

Let's verify that all specified menu roles are actually supported in the implementation.


Let me try a different search strategy to verify the menu roles usage.


Let me check the menu builder implementation to complete the verification.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of menu roles in the codebase
echo "Searching for menu role usage..."
rg -t typescript "role: ['\"](?:appMenu|fileMenu|editMenu|viewMenu|windowMenu|help)['\"]" ./apps/server-web/src/

# Search for MenuBuilder implementation
echo "Checking MenuBuilder implementation..."
ast-grep --pattern 'class MenuBuilder {
  $$$
  buildMenu() {
    $$$
  }
  $$$
}'

Length of output: 400


Script:

#!/bin/bash
# Search for any mentions of these menu roles in the codebase
echo "Searching for menu role mentions..."
rg "appMenu|fileMenu|editMenu|viewMenu|windowMenu" ./apps/server-web/src/

# Look for menu-related files
echo "Looking for menu-related files..."
fd -t f menu ./apps/server-web/src/

# Check the content of the interface file
echo "Checking interface file content..."
cat ./apps/server-web/src/main/helpers/interfaces/i-menu.ts

Length of output: 2074


Script:

#!/bin/bash
# Check the menu builder implementation
echo "Checking menu builder implementation..."
cat ./apps/server-web/src/main/menu.ts

# Search for any usage of these roles in the menu builder
echo "Searching for role usage in menu builder..."
rg "role.*:|\.role\s*=" ./apps/server-web/src/main/menu.ts

Length of output: 4491

type?: 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio'; // Menu item type
click?: () => void; // Click handler for the menu item
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add event parameter to click handler.

The click handler should include the event parameter for consistency with Electron's API.

-    click?: () => void; // Click handler for the menu item
+    click?: (event: Electron.KeyboardEvent) => void; // Click handler for the menu item
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
click?: () => void; // Click handler for the menu item
click?: (event: Electron.KeyboardEvent) => void; // Click handler for the menu item

accelerator?: string; // Keyboard shortcut
enabled?: boolean; // Whether the item is enabled
visible?: boolean; // Whether the item is visible
checked?: boolean; // For 'checkbox' or 'radio' type
}

export type ElectronMenuItem = MenuItemConstructorOptions;
6 changes: 3 additions & 3 deletions apps/server-web/src/main/helpers/interfaces/i-server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
interface GeneralConfig {
export interface GeneralConfig {
lang?: string
autoUpdate?: boolean
updateCheckPeriode?: string
updateCheckPeriod?: string
theme?: string
setup?: boolean
[key: string]: any
}

interface ServerConfig {
export interface ServerConfig {
PORT: number;
NEXT_PUBLIC_GAUZY_API_SERVER_URL: string;
GAUZY_API_SERVER_URL: string;
Expand Down
1 change: 1 addition & 0 deletions apps/server-web/src/main/helpers/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './i-server';
export * from './i-desktop-dialog';
export * from './i-constant';
export * from './i-menu';
24 changes: 24 additions & 0 deletions apps/server-web/src/main/helpers/replace-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,27 @@ export const replaceConfig = async (folderPath: string, envOptions: EnvOptions)
console.log('error on replacing file', error);
}
}

export const clearDesktopConfig = async (folderPath: string): Promise<Boolean> => {
if (!folderPath || typeof folderPath !== 'string') {
throw new Error('Invalid folder path provided');
}
const DESKTOP_CONFIG_FILES = ['desktop-server.body', 'desktop-server.meta'] as const;
try {
// remove cached desktop server config
await Promise.all(
DESKTOP_CONFIG_FILES.map(async (file) => {
const filePath = path.join(folderPath, file);
try {
await fs.promises.unlink(filePath)
} catch (error: any) {
console.log('error unlink static web file', error.message)
}
})
)
return true;
} catch (error) {
console.log('Failed to clear static config');
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class DesktopServerFactory {
if (!this.apiInstance && !!env) {
this.apiInstance = new WebService(path, env, win, signal, eventEmitter);
}
this.apiInstance.updateEnv(env)
return this.apiInstance;
}
}
50 changes: 37 additions & 13 deletions apps/server-web/src/main/helpers/services/libs/desktop-store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Store from 'electron-store';
import { WebServer } from '../../interfaces';
import { WebServer, ServerConfig } from '../../interfaces';
const store = new Store();
const DEFAULT_CONFIG:any = {
const DEFAULT_CONFIG:WebServer = {
server: {
PORT: 3002,
GAUZY_API_SERVER_URL: 'http://localhost:3000',
Expand All @@ -11,7 +11,7 @@ const DEFAULT_CONFIG:any = {
general: {
lang: 'en',
autoUpdate: true,
updateCheckPeriode: '1140'
updateCheckPeriod: '1140' // Time in minutes
}
}
export const LocalStore = {
Expand All @@ -35,17 +35,41 @@ export const LocalStore = {
});
},

deepMerge<T>(target: T, source: Partial<T>): T {
const result: T = { ...target };
Object.keys(source).forEach(key => {
const value = source[key as keyof T];
if (value && typeof value === 'object') {
result[key as keyof T] = this.deepMerge(
target[key as keyof T],
value as any
);
} else if (value !== undefined) {
result[key as keyof T] = value as any;
}
});
return result;
},
Comment on lines +38 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure the deepMerge method properly handles arrays.

The deepMerge method may not handle arrays as intended because arrays are also of type 'object' in JavaScript. This could lead to unexpected behavior when merging configurations that contain arrays.

Consider modifying the method to correctly handle arrays. For example:

-    		if (value && typeof value === 'object') {
+    		if (value && typeof value === 'object' && !Array.isArray(value)) {

This change prevents recursive merging of arrays, allowing you to decide whether to replace or concatenate arrays based on your requirements.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
deepMerge<T>(target: T, source: Partial<T>): T {
const result: T = { ...target };
Object.keys(source).forEach(key => {
const value = source[key as keyof T];
if (value && typeof value === 'object') {
result[key as keyof T] = this.deepMerge(
target[key as keyof T],
value as any
);
} else if (value !== undefined) {
result[key as keyof T] = value as any;
}
});
return result;
},
deepMerge<T>(target: T, source: Partial<T>): T {
const result: T = { ...target };
Object.keys(source).forEach(key => {
const value = source[key as keyof T];
if (value && typeof value === 'object' && !Array.isArray(value)) {
result[key as keyof T] = this.deepMerge(
target[key as keyof T],
value as any
);
} else if (value !== undefined) {
result[key as keyof T] = value as any;
}
});
return result;
},


setDefaultServerConfig: () => {
const defaultConfig: WebServer | any = store.get('config') || {};
Object.keys(DEFAULT_CONFIG).forEach((key) => {
Object.keys(DEFAULT_CONFIG[key]).forEach((keySub) => {
defaultConfig[key] = defaultConfig[key] || {};
defaultConfig[key][keySub] = defaultConfig[key][keySub] || DEFAULT_CONFIG[key][keySub];
})
})
store.set({
config: defaultConfig
validateConfig(config: WebServer): void {
const required = ['PORT', 'GAUZY_API_SERVER_URL'];
required.forEach(field => {
if (!config || !config.server || !config?.server[field as keyof ServerConfig]) {
throw new Error(`Missing required field: ${field}`);
}
});
},

setDefaultServerConfig() {
try {
const storedConfig = store.get('config') as Partial<WebServer> || {};
const mergedConfig = this.deepMerge<WebServer>(DEFAULT_CONFIG, storedConfig)
this.validateConfig(mergedConfig || {});

store.set({ config: mergedConfig });
} catch (error) {
console.error('Failed to set default configuration:', error);
store.set({ config: DEFAULT_CONFIG });
}
}
};
16 changes: 8 additions & 8 deletions apps/server-web/src/main/helpers/services/web-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { EventEmitter } from 'stream';
export class WebService extends ServerTask {
constructor(
readonly path: string,
readonly env: any,
private env: any,
readonly window: BrowserWindow,
readonly signal: AbortSignal,
readonly eventEmitter: EventEmitter
Expand Down Expand Up @@ -38,11 +38,11 @@ export class WebService extends ServerTask {
}
}

private setApiConfig(): void {
// Object.assign(this.args, {
// API_HOST: '0.0.0.0',
// API_PORT: this.config.setting.PORT,
// API_BASE_URL: this.config.apiUrl
// });
public setApiConfig(): void {
Object.assign(this.args, {...this.env});
}
}

public updateEnv(env: any): void {
this.env = {...env};
}
}
Loading