Skip to content

Commit

Permalink
Merge branch 'origin/master' into 131326/NAS-131326
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Vasilenko committed Oct 9, 2024
2 parents dbca167 + 8f40cb9 commit 21ad32c
Show file tree
Hide file tree
Showing 932 changed files with 9,099 additions and 7,308 deletions.
8 changes: 8 additions & 0 deletions .github/actions/prepare/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ runs:
with:
node-version: '20'
cache: 'yarn'
- name: Cache Jest cache
uses: actions/cache@v3
id: jest-cache
with:
path: .jest/cache
key: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-jest-
- name: Install packages
shell: bash
run: yarn install --frozen-lockfile
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ src/assets/scripts/ie-support/ie-polyfills.min.js
# vsc history
.history
.history/*

# jest cache
.jest
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
##NODE temporary builder image
from node:20-alpine as uibuilder
from node:20-bookworm as uibuilder
COPY ./ /src-ui
WORKDIR /src-ui
RUN yarn install --frozen-lockfile
Expand Down
1 change: 1 addition & 0 deletions jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
coverageReporters: ['html', 'json'],
coverageDirectory: 'coverage/webui',
moduleDirectories: ['node_modules', 'src'],
cacheDirectory: "<rootDir>/.jest/cache",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}),
transformIgnorePatterns: [
`node_modules/(?!(${esmPatterns.join('|')}))`
Expand Down
48 changes: 29 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,34 @@
"type": "module",
"homepage": "https://github.com/truenas/webui/",
"scripts": {
"ng": "ng",
"commitlint": "commitlint",
"check-env": "cd $(git rev-parse --show-toplevel) && yarn run ui check-env",
"start": "yarn run check-env && node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng serve --proxy-config proxy.config.json",
"start:prod": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng serve --configuration production",
"prebuild": "yarn icons",
"prestart": "yarn icons",
"build": "yarn run clean:dist && node ./setup-production-env.js && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng build",
"build:prod": "node ./setup-production-env.js && node scripts/setup_prod.js && yarn run build --configuration production",
"build:prod:aot": "node ./setup-production-env.js && yarn run build:prod --base-href /ui/",
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand",
"test:pr": "yarn run check-env && echo 'Setting up temporary environment file...\\n' && yarn run ui remote -i 'headless.local' && jest --coverage --maxWorkers=2",
"test:changed": "node scripts/test_changed.js",
"lint": "ng lint && stylelint 'src/**/*.scss'",
"lint:fix": "ng lint --fix && stylelint --fix 'src/**/*.scss'",
"clean:dist": "rimraf dist",
"check-env": "cd $(git rev-parse --show-toplevel) && yarn run ui check-env",
"clean:coverage": "rimraf coverage",
"reinstall": "rimraf yarn.lock; rimraf node_modules; yarn cache clean -f; yarn install",
"clean:dist": "rimraf dist",
"commitlint": "commitlint",
"compile-grammar": "lezer-generator --typeScript --noTerms src/app/modules/forms/search-input/services/query-parser/query.grammar -o src/app/modules/forms/search-input/services/query-parser/query-grammar.ts",
"extract": "node scripts/extract_strings.js",
"extract-ui-search-elements": "tsx scripts/ui-search/extract-ui-search-elements.ts",
"validate-translations": "node scripts/validate_translations.js",
"lint": "ng lint && stylelint 'src/**/*.scss'",
"lint:fix": "ng lint --fix && stylelint --fix 'src/**/*.scss'",
"icons": "tsx scripts/icon-sprite/make-sprite.ts",
"ng": "ng",
"prepare": "husky",
"ui": "cd $(git rev-parse --show-toplevel) && tsx ./scripts/ui/ui.ts",
"reinstall": "rimraf yarn.lock; rimraf node_modules; yarn cache clean -f; yarn install",
"start": "yarn run check-env && node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng serve --proxy-config proxy.config.json",
"start:prod": "node --max_old_space_size=8192 ./node_modules/@angular/cli/bin/ng serve --configuration production",
"strict-null-checks": "node_modules/typescript/bin/tsc --project tsconfig.strictNullChecks.json",
"compile-grammar": "lezer-generator --typeScript --noTerms src/app/modules/forms/search-input/services/query-parser/query.grammar -o src/app/modules/forms/search-input/services/query-parser/query-grammar.ts"
"test": "jest",
"test:changed": "node scripts/test_changed.js",
"test:ci": "jest --runInBand",
"test:pr": "yarn run check-env && echo 'Setting up temporary environment file...\\n' && yarn run ui remote -i 'headless.local' && jest --coverage --maxWorkers=2",
"test:watch": "jest --watch",
"ui": "cd $(git rev-parse --show-toplevel) && tsx ./scripts/ui/ui.ts",
"validate-translations": "node scripts/validate_translations.js"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -78,8 +81,8 @@
"@lezer/common": "~1.2.1",
"@lezer/generator": "~1.7.1",
"@lezer/lr": "~1.4.2",
"@material-design-icons/font": "~0.14.13",
"@mdi/font": "~7.4.47",
"@material-design-icons/svg": "~0.14.13",
"@mdi/svg": "~7.4.47",
"@messageformat/core": "~3.3.0",
"@ngneat/reactive-forms": "~5.0.2",
"@ngneat/spectator": "~19.0.0",
Expand All @@ -102,13 +105,16 @@
"@types/dygraphs": "^2.1.10",
"@types/figlet": "~1.5.5",
"@types/fontfaceobserver": "^2.1.3",
"@types/glob": "~7.2.0",
"@types/jest": "~29.5.13",
"@types/jest-when": "^3.5.5",
"@types/js-yaml": "~4.0.8",
"@types/lodash-es": "~4.17.12",
"@types/mime-types": "~2.1.1",
"@types/node": "^18.19.1",
"@types/randomcolor": "~0.5.9",
"@types/svg-sprite": "~0.0.39",
"@types/vinyl": "~2.0.12",
"@typescript-eslint/eslint-plugin": "~6.18.1",
"@typescript-eslint/parser": "~6.18.1",
"@vendure/ngx-translate-extract": "~9.2.1",
Expand All @@ -127,6 +133,7 @@
"cron-parser": "~4.9.0",
"croner": "~8.1.1",
"cronstrue": "~2.50.0",
"crypto": "~1.0.1",
"d3": "~7.9.0",
"date-fns": "~2.28.0",
"date-fns-tz": "~1.3.8",
Expand All @@ -148,6 +155,7 @@
"figlet": "~1.7.0",
"fontfaceobserver": "^2.3.0",
"fuse.js": "~7.0.0",
"glob": "7.2.3",
"html2canvas": "~1.4.1",
"husky": "^9.1.6",
"immer": "~10.1.1",
Expand Down Expand Up @@ -186,12 +194,14 @@
"stylelint": "^14.9.1",
"stylelint-config-sass-guidelines": "~9.0.1",
"stylelint-config-standard": "^26.0.0",
"svg-sprite": "~2.0.4",
"text-security": "~3.2.1",
"ts-jest": "~29.2.5",
"tsconfig-paths": "~4.2.0",
"tsx": "~4.19.1",
"typescript": "~5.5.4",
"utility-types": "~3.11.0",
"vinyl": "~3.0.0",
"zone.js": "~0.14.10"
},
"lint-staged": {
Expand Down
25 changes: 25 additions & 0 deletions scripts/icon-sprite/lib/add-custom-icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from 'fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { resolve } from 'path';

export function addCustomIcons(usedIcons: Set<string>): Set<string> {
// TODO: Can be simplified in node 20.11+
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(fileURLToPath(import.meta.url));

const customIconsPath = resolve(__dirname, '../../../src/assets/icons/custom');

const customIcons = new Set<string>();

fs.readdirSync(customIconsPath).forEach((filename) => {
const icon = `ix-${filename.replace('.svg', '')}`;
if (!usedIcons.has(icon)) {
console.warn(`Custom icon "${icon}" does not appear to be used in the application.`);
}

customIcons.add(icon);
});

return new Set([...customIcons, ...usedIcons]);
}
25 changes: 25 additions & 0 deletions scripts/icon-sprite/lib/build-sprite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from 'fs';
import Spriter from 'svg-sprite';
import File from 'vinyl';

export type SpriteResult = Record<string, { sprite: File }>;

export async function buildSprite(icons: Map<string, string>): Promise<SpriteResult> {
const spriter = new Spriter({
mode: {
stack: true,
},
});

icons.forEach((path, name) => {
try {
spriter.add(name, null, fs.readFileSync(path, 'utf-8'));
} catch (error) {
console.error(`Failed to add icon "${name}": `);
throw error;
}
});

const { result } = await spriter.compileAsync() as { result: SpriteResult };
return result;
}
23 changes: 23 additions & 0 deletions scripts/icon-sprite/lib/find-icons-in-templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from 'fs';
import * as cheerio from 'cheerio';
import glob from 'glob';

export function findIconsInTemplates(path: string): Set<string> {
const iconNames = new Set<string>();

const templates = glob.sync(`${path}/**/*.html`);

templates.forEach((template) => {
const content = fs.readFileSync(template, 'utf-8');
const parsedTemplate = cheerio.load(content);

parsedTemplate('ix-icon').each((_, iconTag) => {
const name = parsedTemplate(iconTag).attr('name');
if (name) {
iconNames.add(name);
}
});
});

return iconNames;
}
18 changes: 18 additions & 0 deletions scripts/icon-sprite/lib/find-icons-with-marker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { execSync } from 'node:child_process';

export function findIconsWithMarker(path: string): Set<string> {
const command = `grep -rEo "iconMarker\\\\('[^']+'" --include="*.ts" --include="*.html" ${path}`;

const icons = new Set<string>();
const output = execSync(command, { encoding: 'utf-8' });
output
.split('\n')
.filter(Boolean)
.forEach((line) => {
const [, match] = line.split(':');
const value = match.match(/'([^']+)'/)[1];
icons.add(value);
});

return icons;
}
19 changes: 19 additions & 0 deletions scripts/icon-sprite/lib/get-icon-paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function getIconPaths(names: Set<string>): Map<string, string> {
const iconPaths = new Map<string, string>();

names.forEach((name) => {
if (name.startsWith('ix-')) {
iconPaths.set(name, `src/assets/icons/custom/${name.slice(3)}.svg`);
return;
}

if (name.startsWith('mdi-')) {
iconPaths.set(name, `node_modules/@mdi/svg/svg/${name.slice(4)}.svg`);
return;
}

iconPaths.set(name, `node_modules/@material-design-icons/svg/filled/${name}.svg`);
});

return iconPaths;
}
13 changes: 13 additions & 0 deletions scripts/icon-sprite/lib/warn-about-duplicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function warnAboutDuplicates(icons: Set<string>): void {
icons.forEach((icon) => {
if (icon.startsWith('mdi-')) {
return;
}

if (!icons.has(`mdi-${icon}`)) {
return;
}

console.warn(`Both "${icon}" and "mdi-${icon}" are used in the application. Consider only using the 'mdi' version.`);
});
}
58 changes: 58 additions & 0 deletions scripts/icon-sprite/make-sprite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import crypto from 'crypto';
import fs from 'fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { resolve } from 'path';
import { addCustomIcons } from './lib/add-custom-icons';
import { buildSprite } from './lib/build-sprite';
import { findIconsInTemplates } from './lib/find-icons-in-templates';
import { findIconsWithMarker } from './lib/find-icons-with-marker';
import { getIconPaths } from './lib/get-icon-paths';
import { warnAboutDuplicates } from './lib/warn-about-duplicates';

async function makeSprite(): Promise<void> {
try {
// TODO: Can be simplified in node 20.11+
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(fileURLToPath(import.meta.url));

const srcDir = resolve(__dirname, '../../src');
const targetPath = resolve(__dirname, '../../src/assets/icons/sprite.svg');
const configPath = resolve(__dirname, '../../src/assets/icons/sprite-config.json');

const templateIcons = findIconsInTemplates(srcDir);
const markerIcons = findIconsWithMarker(srcDir);
const usedIcons = new Set([...templateIcons, ...markerIcons]);

const allIcons = addCustomIcons(usedIcons);

warnAboutDuplicates(allIcons);

if (!allIcons.size) {
throw new Error('No icons found in the project.');
}

const icons = getIconPaths(allIcons);

const result = await buildSprite(icons);
const file = Object.values(result)[0].sprite;

const buffer = file.contents as Buffer;
const size = buffer.length / 1024;

fs.writeFileSync(targetPath, buffer);

const hash = crypto.createHash('md5').update(buffer).digest('hex').slice(0, 10);
const versionedUrl = `assets/icons/sprite.svg?v=${hash}`;

fs.writeFileSync(configPath, JSON.stringify({ iconUrl: versionedUrl }, null, 2));

console.info(`Generated icon sprite with ${allIcons.size} icons (${size.toFixed(2)} KiB).`);
console.info(`Versioned sprite URL: ${versionedUrl}`);
} catch (error) {
console.error('Error when building the icon sprite:', error);
throw error;
}
}

makeSprite();
4 changes: 1 addition & 3 deletions scripts/test_changed.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ function findTestFiles(directory) {
const entries = readdirSync(directory, { withFileTypes: true });
for (let entry of entries) {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
testFiles.push(...findTestFiles(entryPath));
} else if (entry.isFile() && entry.name.match(/\.spec\.ts$/)) {
if (entry.isFile() && entry.name.match(/\.spec\.ts$/)) {
testFiles.push(entryPath);
}
}
Expand Down
2 changes: 0 additions & 2 deletions scripts/ui-search/find-component-files.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable import/no-extraneous-dependencies */
import glob from 'glob';

export function findComponentFiles(pattern: string): Promise<string[]> {
Expand Down
2 changes: 1 addition & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { createTranslateLoader } from 'app/core/classes/icu-translations-loader'
import { MockEnclosureWebsocketService } from 'app/core/testing/mock-enclosure/mock-enclosure-websocket.service';
import { getWindow, WINDOW } from 'app/helpers/window.helper';
import { FeedbackModule } from 'app/modules/feedback/feedback.module';
import { IxIconRegistry } from 'app/modules/ix-icon/ix-icon.service';
import { IxIconRegistry } from 'app/modules/ix-icon/ix-icon-registry.service';
import { SnackbarModule } from 'app/modules/snackbar/snackbar.module';
import { TwoFactorGuardService } from 'app/services/auth/two-factor-guard.service';
import { ErrorHandlerService } from 'app/services/error-handler.service';
Expand Down
Loading

0 comments on commit 21ad32c

Please sign in to comment.