diff --git a/Changes b/Changes index ff227f3e..500b8ae9 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,22 @@ Release history for Zonemaster component Zonemaster-GUI +v4.2.0 2024-03-18 (public release version) + + [Release information] + - Translations have not been fully updated in this release. They will + be updated in an upcoming fix release. + + [Features] + - Improves GUI design (#434) + + [Fixes] + - Updates FAQ (#443, #456, #457) + - Fixe handling of unspecified test module (#444) + - Updates French translation (#450) + - Updates README file (#455) + + v4.1.1 2023-09-08 (public fix version) [Fixes] diff --git a/README.md b/README.md index 110ee2a3..f81e03ec 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ -Zonemaster Web GUI [![Build Status](https://app.travis-ci.com/zonemaster/zonemaster-gui.svg?branch=master)](https://app.travis-ci.com/zonemaster/zonemaster-gui) -========== +# Zonemaster Web GUI [![CI status](https://github.com/zonemaster/zonemaster-gui/actions/workflows/ci.yml/badge.svg)](https://github.com/zonemaster/zonemaster-gui/actions/workflows/ci.yml) -### Purpose -This module is the Web Interface part of the Zonemaster project. -### Installation +### Purpose -Follow the detailed [installation instructions]. +This module is the Web Interface part of the Zonemaster project. For an +overview of the Zonemaster software, please see the [Zonemaster repository]. -##### Prerequisites +### Prerequisites Before you install the Zonemaster Web GUI module, you need the Zonemaster Engine test framework installed. Please see the [Zonemaster-Engine @@ -17,103 +15,85 @@ installation] document. You also need a running Zonemaster-Backend component. Please see the [Zonemaster-Backend installation] document. -##### Configuration +### Installation + +Follow the detailed [installation instructions]. + +### Configuration -The configuration instructions for the backend can be found in the [Backend +The configuration instructions for the Backend can be found in the [Backend configuration] document. The configuration instructions for the GUI can be found in the [GUI configuration] document. +### Documentation + +There is a [public documentation]. Some more specific documents can be found in +the [docs directory](docs/). ### Contribution -This project was generate with Angular-CLI 1.6.8 and then updating. -It use the Angular 2+ framework with all its tools. - -The source code of the application is available in ``./src/app`` folder. -The ``app`` folder is structured as follow : -``` -+-- components -+-- models -| +-- alter.ts : Alert model (level, message) -| +-- index.ts : module export -+-- pipes -| +-- filer.pipe.* : Filter a list based on a string -| +-- filer-by-categories.pipe.* : Filter the list of result by categories -| +-- romanize.pipe.* : Transform latin number to roman number (1 -> I, 2 -> II) -| +-- safe-html.pipe.* : Sanitize text to HTML -+-- services -| +-- alert.service.* : to display alert messages -| +-- app.service.* : to manage app configuration (api url, etc.) -| +-- dns-check.service.* : to communicate with the Zonemaster Backend API -+-- app.* -``` - -The ``components`` folder is composed of subfolders that represent the main components. -Each folder of component contain three files : - - ``*.component.css`` : The component's style - - ``*.component.html`` : The component's html - - ``*.component.ts`` : The component's typescript - -All assets are available in ``src/assets`` folder. -It's split by concern, all translation files are in the ``i18n`` folder, the style in the ``css``, -images are in the ``images`` folder, etc. - -The configurations files of the application are in the ``src/environments`` folder. -``environment.ts`` is use for development purpose, and the ``environment.prod.ts`` for production. - -In order to contribute +This project was generated with Angular-CLI 1.6.8. +It uses the Angular 2+ framework with all its tools. + +The source code of the application is available in `./src/app` folder. + +The `components` folder is composed of subfolders that represent the main +components. Each folder of component contain three files: + + * `*.component.css`: the component's style configuration; + * `*.component.html`: the component's HTML code; + * `*.component.ts`: the component's TypeScript code. + +All assets are available in the `src/assets` folder. It is split by concern; +the style configurations are in the `css`, images are in the `images` folder, etc. + +All translation files are in the `src/locale` folder. + +The environment defaults of the application are in the `src/environments` +folder. `environment.ts` is used for development purposes, the +`environment.prod.ts` for production, and the `environment.test.ts` for testing. + +In order to contribute: + * Install [Nodejs](https://nodejs.org) * Fork the Zonemaster GUI repository on Github into your own user on Github. * Clone your fork to your working environment. * Go to the folder and install project dependencies with `npm install` -* Update environment files in `/src/environments/` to add a backend api endpoint (or leave the default) +* Update configuration files in `src/assets/app.config.json` to add a Backend + API endpoint (or leave the default) * Make your changes, test them and push them to your fork on Github -* From your fork, make a Pull Request against the zonemaster/zonemaster-gui repository. -Please always make the Pull Request against the develop branch. - +* From your fork, make a Pull Request against the zonemaster/zonemaster-gui + repository. Please always make the Pull Request against the develop branch. * Thank you for your contribution! -> In development mode, you probably have to enable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) on your browser. -> The Cross-Origin Resource Sharing (CORS) performed by the browser blocks every AJAX request that does not match the exact host, protocol, and port of your site. - -##### Development server -Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. - -##### Build -Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. -The script also change the FAQ markdown files to html files. - -##### Test -See [Testing](docs/Testing.md). +#### Development server -##### Create a release zip file -Run 'npm run release' to create a zip file with dist folder and zonemaster.conf file. Then upload it in github. +Run `npm start` for a dev server. Navigate to `http://localhost:4200/en`. +The app will automatically reload if you change any of the source files. -### Documentation +#### Build -There is a [public documentation]. Some more specific documents can be found in -the [docs directory](docs/). +Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. -#### Security of our dependencies -Based on the output of [david](https://david-dm.org/) and [npm audit](https://docs.npmjs.com/cli/audit), we reguraly -update our dependencies with the latest secure version. Notice that we can't follow the rythm of new versions, so you could -get security warning during the installation of development dependencies. +#### Test -#### Acknowledge -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.8. +See [Testing](docs/Testing.md). -License -======= +## License This is free software under a 2-clause BSD license. The full text of the license can be found in the [LICENSE](LICENSE) file included in this respository. +Images `src/assets/images/person_looking_at_computer.svg` and `src/assets/images/world_connected.svg` +are taken from , [full license](https://undraw.co/license). + [Backend Configuration]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/configuration/backend.md [GUI Configuration]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/configuration/gui.md [Installation instructions]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/installation/zonemaster-gui.md -[Public documentation]: https://github.com/zonemaster/zonemaster/blob/master/README.md#documentation +[Public documentation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/README.md [Zonemaster-Engine installation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/installation/zonemaster-engine.md [Zonemaster-Backend installation]: https://github.com/zonemaster/zonemaster/blob/master/docs/public/installation/zonemaster-backend.md +[Zonemaster repository]: https://github.com/zonemaster/zonemaster diff --git a/e2e/FR03.e2e-spec.ts b/e2e/FR03.e2e-spec.ts index 692ff97b..c60cb525 100644 --- a/e2e/FR03.e2e-spec.ts +++ b/e2e/FR03.e2e-spec.ts @@ -11,17 +11,17 @@ test.describe('Zonemaster test FR03 - [All appropriate fields should be writable test('should be able to write in the main input', async ({ page }) => { const testString = 'afnic.fr'; - const domainInput = page.locator('#input_domain_form'); + const domainInput = page.locator('#input-domain-form'); await domainInput.type(testString); await expect(domainInput).toHaveValue(testString); await showOptions(page); - const nsInput = page.locator('input[formControlName="ns"]'); + const nsInput = page.locator('input[formControlName="ns"]').first(); await nsInput.type(testString); await expect(nsInput).toHaveValue(testString); - const keytagInput = page.locator('input[formControlName="keytag"]'); + const keytagInput = page.locator('input[formControlName="keytag"]').first(); await keytagInput.type(testString); await expect(keytagInput).toHaveValue(testString); }); diff --git a/e2e/FR05.e2e-spec.ts b/e2e/FR05.e2e-spec.ts index a4fa3f99..2d795c46 100644 --- a/e2e/FR05.e2e-spec.ts +++ b/e2e/FR05.e2e-spec.ts @@ -21,15 +21,12 @@ test.describe('Zonemaster test FR05 - [Supports internationalization]', () => { test(`should have ${language} language option`, async ({ page }) => { const langNavLink = page.locator(`select#languageSelection > option[lang="${code}"]`); await expect(langNavLink).toHaveCount(1); + await expect(langNavLink).toHaveAttribute('lang', code); }) test(`should switch to ${language}`, async ({ page }) => { await setLang(page, code); - await expect(page.locator('input#input_domain_form')).toHaveAttribute('placeholder', expected); - - const langNavLink = page.locator(`select#languageSelection > option[lang="${code}"]`); - await expect(langNavLink).toHaveCount(1); - await expect(langNavLink).toHaveAttribute('lang', code); + await expect(page.locator('label[for="input-domain-form"]')).toHaveText(expected); }) } }); diff --git a/e2e/FR08.e2e-spec.ts b/e2e/FR08.e2e-spec.ts index efd826c0..7c7d0f8a 100644 --- a/e2e/FR08.e2e-spec.ts +++ b/e2e/FR08.e2e-spec.ts @@ -9,6 +9,6 @@ test.describe('Zonemaster test FR08 - [Presence of a default fallback language - }); test('should have a fallback language - English', async ({ page }) => { - await expect(page.locator('input#input_domain_form')).toHaveAttribute('placeholder', 'Domain name'); + await expect(page.locator('label[for="input-domain-form"]')).toHaveText('Domain name'); }); }); diff --git a/e2e/FR09.e2e-spec.ts b/e2e/FR09.e2e-spec.ts index 02a345eb..bfb9b881 100644 --- a/e2e/FR09.e2e-spec.ts +++ b/e2e/FR09.e2e-spec.ts @@ -9,9 +9,9 @@ test.describe('Zonemaster test FR09 - [Once a language is chosen, all other link }); test('should keep french when opening faq page', async ({ page }) => { - await expect(page.locator('input#input_domain_form')).toHaveAttribute('placeholder', 'Nom de domaine'); + await expect(page.locator('label[for="input-domain-form"]')).toHaveText('Nom de domaine'); await page.locator('a.nav-link[routerlink="/faq"]').click(); - await expect(page.locator('section.main > div > h1')).toHaveText('FAQ'); + await expect(page.locator('main h1')).toHaveText('Questions fréquentes'); await expect(page.locator('a.nav-link[routerlink="/run-test"]')).toHaveText("Lancer un test"); }); }); diff --git a/e2e/FR10.e2e-spec.ts b/e2e/FR10.e2e-spec.ts index e1ae035d..ac04f379 100644 --- a/e2e/FR10.e2e-spec.ts +++ b/e2e/FR10.e2e-spec.ts @@ -1,6 +1,6 @@ const { test, expect } = require('@playwright/test'); -import { goToHome, clearBrowserCache } from './utils/app.utils'; +import { goToHome, clearBrowserCache, showOptions } from './utils/app.utils'; test.describe('Zonemaster test FR10 - [On launching the URL opens with a default simple view]', () => { test.beforeEach(async ({ page }) => { @@ -9,21 +9,21 @@ test.describe('Zonemaster test FR10 - [On launching the URL opens with a default }); test('should have [Run domain test] label visible', async ({ page }) => { - await expect(page.locator('h1', { hasText: 'Run domain test'})).toBeVisible(); + await expect(page.locator('h1', { hasText: 'Check how your domain is doing'})).toBeVisible(); }); test('should have [Options] label visible and NOT selected', async ({ page }) => { - await expect(page.locator('label', { hasText: 'Options' })).toBeVisible(); + await expect(page.locator('#advanced-toggle', { hasText: 'Show options' })).toBeVisible(); await expect(page.locator('#advanced-toggle')).toHaveAttribute('aria-expanded', 'false'); }); test('should have [Nameservers label] NOT visible', async ({ page }) => { - await expect(page.locator('h4', { hasText: 'Nameservers'})).toBeHidden(); + await expect(page.locator('legend', { hasText: 'Nameservers'})).toBeHidden(); }); test('should have [DS records label] NOT visible', async ({ page }) => { - await expect(page.locator('h4', { hasText: 'Delegation Signers (DS records)'})).toBeHidden(); + await expect(page.locator('legend', { hasText: 'DS records'})).toBeHidden(); }); }); diff --git a/e2e/FR11.e2e-spec.ts-snapshots/domain-chromium-linux.png b/e2e/FR11.e2e-spec.ts-snapshots/domain-chromium-linux.png index 3c6d2d2b..e8c7a555 100644 Binary files a/e2e/FR11.e2e-spec.ts-snapshots/domain-chromium-linux.png and b/e2e/FR11.e2e-spec.ts-snapshots/domain-chromium-linux.png differ diff --git a/e2e/FR15.e2e-spec.ts b/e2e/FR15.e2e-spec.ts index b7dfb2c3..11124410 100644 --- a/e2e/FR15.e2e-spec.ts +++ b/e2e/FR15.e2e-spec.ts @@ -10,7 +10,7 @@ test.describe('Zonemaster test FR15 - [The advanced view should look the same in test('should match the domain page with options on', async ({ page}) => { await showOptions(page); - // Force wait 0.4s, for the switch animation to finish + // Force wait 0.4s, for the button icon animation to finish await page.waitForTimeout(400); expect(await page.screenshot()).toMatchSnapshot('domain_with_options.png'); }); diff --git a/e2e/FR15.e2e-spec.ts-snapshots/domain-with-options-chromium-linux.png b/e2e/FR15.e2e-spec.ts-snapshots/domain-with-options-chromium-linux.png index 491ccd8d..48819fca 100644 Binary files a/e2e/FR15.e2e-spec.ts-snapshots/domain-with-options-chromium-linux.png and b/e2e/FR15.e2e-spec.ts-snapshots/domain-with-options-chromium-linux.png differ diff --git a/e2e/FR16.e2e-spec.ts b/e2e/FR16.e2e-spec.ts index 1f0e6948..01b504f2 100644 --- a/e2e/FR16.e2e-spec.ts +++ b/e2e/FR16.e2e-spec.ts @@ -10,10 +10,10 @@ test.describe('Zonemaster test FR16 - [The advanced view should have a text desc }); test('should have a link to the proper faq answer', async ({ page }) => { - const alert = page.locator('.alert.alert-info'); + const alert = page.locator('#advanced-options aside .alert'); await expect(alert).toBeVisible(); await expect(alert.locator('a')).toHaveAttribute('routerlink', '/faq'); - await expect(alert.locator('a')).toHaveAttribute('fragment', 'q12'); + await expect(alert.locator('a')).toHaveAttribute('fragment', 'what-is-an-undelegated-domain-test'); }); test('should have a description text in multi languages', async ({ page }) => { @@ -26,7 +26,7 @@ test.describe('Zonemaster test FR16 - [The advanced view should have a text desc for (const {lang, text} of testSuite) { await setLang(page, lang); await showOptions(page); - await expect(page.locator('.alert.alert-info a')).toContainText(text); + await expect(page.locator('#advanced-options aside .alert a')).toContainText(text); } }); }); diff --git a/e2e/FR17.e2e-spec.ts b/e2e/FR17.e2e-spec.ts index c4d2c060..58cdfb23 100644 --- a/e2e/FR17.e2e-spec.ts +++ b/e2e/FR17.e2e-spec.ts @@ -14,25 +14,25 @@ test.describe.serial('Zonemaster test FR17 - [Able to specify delegation paramet }); test('should have one ns and digest form', async () => { - await expect(page.locator('div[formArrayName] .row')).toHaveCount(2); + await expect(page.locator('div[formArrayName] fieldset')).toHaveCount(2); await expect(page.locator('input[formControlName="keytag"]')).toHaveCount(1); await expect(page.locator('input[formControlName="ns"]')).toHaveCount(1); }); test('should be possible to add new ns form', async () => { - await page.locator('div[formArrayName="nameservers"] .row:first-child .btn.add').click(); + await page.locator('input[formControlName="ns"]').first().type('test'); await expect(page.locator('input[formControlName="ns"]')).toHaveCount(2); await expect(page.locator('input[formControlName="keytag"]')).toHaveCount(1); }); test('should be possible to add new digest form', async () => { - await page.locator('div[formArrayName="ds_info"] .row:first-child .btn.add').click(); + await page.locator('input[formControlName="keytag"]').first().type('1234'); await expect(page.locator('input[formControlName="ns"]')).toHaveCount(2); await expect(page.locator('input[formControlName="keytag"]')).toHaveCount(2); }); test('should be possible to delete ns forms', async () => { - await page.locator('div[formArrayName="nameservers"] .row:first-child .btn.delete').click(); + await page.locator('div[formArrayName="nameservers"] fieldset:first-child button.delete').click(); await expect(page.locator('input[formControlName="ns"]')).toHaveCount(1); await expect(page.locator('input[formControlName="keytag"]')).toHaveCount(2); }); diff --git a/e2e/FR18.e2e-spec.ts b/e2e/FR18.e2e-spec.ts index bba6fd88..6d182696 100644 --- a/e2e/FR18.e2e-spec.ts +++ b/e2e/FR18.e2e-spec.ts @@ -10,7 +10,7 @@ test.describe('Zonemaster test FR18 - [The GUI should be able to run tests by ju test('should display progress bar', async ({ page }) => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('progress.afNiC.Fr'); + await page.locator('#input-domain-form').type('progress.afNiC.Fr'); await page.locator('div button.launch').click(); await expect(page.locator('.progress-bar')).toBeVisible({ timeout: 10000}); }); diff --git a/e2e/FR19.e2e-spec.ts b/e2e/FR19.e2e-spec.ts index 21a523ff..840bbd51 100644 --- a/e2e/FR19.e2e-spec.ts +++ b/e2e/FR19.e2e-spec.ts @@ -11,7 +11,7 @@ test.describe('Zonemaster test FR19 - [The GUI should be able to run the test wi test('should NOT display progress bar when we add a NS ip', async ({ page }) => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('progress.afNiC.Fr'); + await page.locator('#input-domain-form').type('progress.afNiC.Fr'); await page.locator('input[formControlName="ip"]').type('192.134.4.1'); await page.locator('div button.launch').click(); @@ -28,7 +28,7 @@ test.describe('Zonemaster test FR19 - [The GUI should be able to run the test wi test('should display progress bar when we add a NS name', async ({ page }) => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('progress.afNiC.Fr'); + await page.locator('#input-domain-form').type('progress.afNiC.Fr'); await page.locator('input[formControlName="ns"]').type('ns1.nic.fr'); await page.locator('div button.launch').click(); await expect(page.locator('.progress-bar')).toBeVisible({ timeout: 10000 }); diff --git a/e2e/FR20.e2e-spec.ts b/e2e/FR20.e2e-spec.ts index e91f0d86..19148441 100644 --- a/e2e/FR20.e2e-spec.ts +++ b/e2e/FR20.e2e-spec.ts @@ -12,13 +12,13 @@ test.describe('Zonemaster test FR20 - [The user must be able to submit one or mo test('should display progress bar when we add a DS entry and launch a test', async ({ page }) => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('progress.afNiC.Fr'); + await page.locator('#input-domain-form').type('progress.afNiC.Fr'); - await page.locator('input[formControlName="keytag"]').type('37610'); - await page.locator('input[formControlName="digest"]').type('d2681e301f632bd76544e6d5b6631a12d97b5479ff07cd24efecd19203c77db3'); + await page.locator('input[formControlName="keytag"]').first().type('37610'); + await page.locator('input[formControlName="digest"]').first().type('d2681e301f632bd76544e6d5b6631a12d97b5479ff07cd24efecd19203c77db3'); - await page.locator('select[formControlName="algorithm"]').selectOption({ label: '8 - RSASHA256'}); - await page.locator('select[formControlName="digtype"]').selectOption({ label: '2 - SHA-256'}); + await page.locator('select[formControlName="algorithm"]').first().selectOption({ label: '8 - RSASHA256'}); + await page.locator('select[formControlName="digtype"]').first().selectOption({ label: '2 - SHA-256'}); await page.locator('div button.launch').click(); await expect(page.locator('.progress-bar')).toBeVisible({ timeout: 10000 }); diff --git a/e2e/FR21.e2e-spec.ts b/e2e/FR21.e2e-spec.ts index 7155e8a9..ca0794d9 100644 --- a/e2e/FR21.e2e-spec.ts +++ b/e2e/FR21.e2e-spec.ts @@ -18,15 +18,15 @@ test.describe.serial('Zonemaster test FR21 - [Able to provide a summarized resul test('should display summary', async () => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('results.afNiC.Fr'); + await page.locator('#input-domain-form').type('results.afNiC.Fr'); await page.locator('div button.launch').click(); - await expect(page.locator('div.result.container')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('section.result')).toBeVisible({ timeout: 10000 }); - const messageCountBadges = page.locator('.nav.nav-pills.vertical-align.filter > li > a'); + const messageCountBadges = page.locator('fieldset.severity-levels label'); const expectedLabels = ['All', 'Info', 'Notice', 'Warning', 'Error', 'Critical']; - await expect(messageCountBadges).toHaveCount(6); + await expect(messageCountBadges).toHaveCount(expectedLabels.length); for (const idx in expectedLabels) { await expect(messageCountBadges.nth(idx)).toContainText(expectedLabels[idx]); @@ -35,7 +35,7 @@ test.describe.serial('Zonemaster test FR21 - [Able to provide a summarized resul test('should display number of each level', async () => { const expectedCounts = ['52', '49', '3', '0', '0', '0']; - const messageCountBadges = page.locator('.nav.nav-pills.vertical-align.filter > li > a > span.badge'); + const messageCountBadges = page.locator('fieldset.severity-levels label span.badge'); for (const idx in expectedCounts) { await expect(messageCountBadges.nth(idx)).toHaveText(expectedCounts[idx]); @@ -43,7 +43,7 @@ test.describe.serial('Zonemaster test FR21 - [Able to provide a summarized resul }); test('should display summary with good colors', async () => { - const filterButtons = page.locator('.nav.nav-pills.vertical-align.filter > li > a'); + const filterButtons = page.locator('fieldset.severity-levels input[type="checkbox"]'); for (const idx of [1, 2, 3, 4, 5]) { await filterButtons.nth(idx).click(); diff --git a/e2e/FR21.e2e-spec.ts-snapshots/results-chromium-linux.png b/e2e/FR21.e2e-spec.ts-snapshots/results-chromium-linux.png index 029c3bde..cea31adb 100644 Binary files a/e2e/FR21.e2e-spec.ts-snapshots/results-chromium-linux.png and b/e2e/FR21.e2e-spec.ts-snapshots/results-chromium-linux.png differ diff --git a/e2e/FR22.e2e-spec.ts b/e2e/FR22.e2e-spec.ts index e0bb011f..80a44bcc 100644 --- a/e2e/FR22.e2e-spec.ts +++ b/e2e/FR22.e2e-spec.ts @@ -13,12 +13,12 @@ test.describe('Zonemaster test FR22 - [Provide the possibility to see more infor test('should display full messages', async({ page }) => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('results.afNiC.Fr'); - await page.locator('div button.launch').click(); + await page.locator('#input-domain-form').type('results.afNiC.Fr'); + await page.locator('button.launch').click(); // Basic header is the second one const basicHeader = page.locator('.result h3').nth(1); - const basicTestcases = page.locator('#module-BASIC article'); + const basicTestcases = page.locator('#module-BASIC section'); // Basic02 header is the second one in the Basic results const basic02Header = page.locator('#module-BASIC h4').nth(1); diff --git a/e2e/FR26.e2e-spec.ts b/e2e/FR26.e2e-spec.ts index aafa819a..7484ef23 100644 --- a/e2e/FR26.e2e-spec.ts +++ b/e2e/FR26.e2e-spec.ts @@ -10,8 +10,8 @@ test.describe('Zonemaster test FR26 - [Should be able to show a progress bar wit test('should display progress bar', async ({ page }) => { await expect(page.locator('.progress-bar')).toBeHidden(); - await page.locator('#input_domain_form').type('progress.afNiC.Fr'); - await page.locator('div button.launch').click(); + await page.locator('#input-domain-form').type('progress.afNiC.Fr'); + await page.locator('button.launch').click(); await expect(page.locator('.progress-bar')).toBeVisible({ timeout: 10000}); await expect(page.locator('.progress-value')).toHaveText('50%'); }); diff --git a/e2e/navigation.e2e-spec.ts b/e2e/navigation.e2e-spec.ts index 9086db90..c2cbd443 100644 --- a/e2e/navigation.e2e-spec.ts +++ b/e2e/navigation.e2e-spec.ts @@ -17,43 +17,43 @@ test.describe('Navigation should be consistent and honor browser behaviour', () // Create a new test await expect(page).toHaveURL(domainCheckUrl); - await page.locator('#input_domain_form').type(firstDomain); - await page.locator('div button.launch').click(); + await page.locator('#input-domain-form').type(firstDomain); + await page.locator('button.launch').click(); // Verify that when the test finishes the browser is redirect to the result page with an url /result/. // The "run domain test" form is visible. - await expect(page.locator('.result-header > h2')).toHaveText(firstDomain, { timeout: 10000 }); - await expect(page.locator('div.result.container')).toBeVisible(); + await expect(page.locator('section.result h2')).toContainText(firstDomain, { timeout: 10000 }); + await expect(page.locator('section.result')).toBeVisible(); await expect(page.locator('form.domain')).toBeVisible(); await expect(page).toHaveURL(firstTestUrl); // Press the back button in the browser. await page.goBack() // Verify that only the "run domain test" form is displayed. The url should be /run-test. await expect(page).toHaveURL(domainCheckUrl); - await expect(page.locator('div.result.container')).not.toBeVisible(); + await expect(page.locator('section.result')).not.toBeVisible(); await expect(page.locator('form.domain')).toBeVisible(); // Press the forward button in the browser. await page.goForward(); // Verify that the previous test result is displayed with the url /result/. // The "run domain test" form should still be visible. - await expect(page.locator('.result-header > h2')).toHaveText(firstDomain, { timeout: 10000 }); - await expect(page.locator('div.result.container')).toBeVisible(); + await expect(page.locator('section.result h2')).toContainText(firstDomain, { timeout: 10000 }); + await expect(page.locator('section.result')).toBeVisible(); await expect(page.locator('form.domain')).toBeVisible(); await expect(page).toHaveURL(firstTestUrl); // Create a second test for an other domain from the result page. - await page.locator('#input_domain_form').type(secondDomain); + await page.locator('#input-domain-form').type(secondDomain); await page.locator('div button.launch').click(); // When the second test finishes the url should change to /result/. // The result for the second test should be displayed and the "run domain test" form should still be visible. - await expect(page.locator('.result-header > h2')).toHaveText(secondDomain, { timeout: 10000 }); - await expect(page.locator('div.result.container')).toBeVisible(); + await expect(page.locator('section.result h2')).toContainText(secondDomain, { timeout: 10000 }); + await expect(page.locator('section.result')).toBeVisible(); await expect(page.locator('form.domain')).toBeVisible(); await expect(page).toHaveURL(secondTestUrl); // Refresh the page. await page.reload(); // The "run domain test" form is not visible, instead a Result header is displayed. await expect(page.locator('h1')).toHaveText('Result'); - await expect(page.locator('div.result.container')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('.result-header > h2')).toHaveText(secondDomain); + await expect(page.locator('section.result')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('section.result h2')).toContainText(secondDomain); await expect(page.locator('form.domain')).not.toBeVisible(); }); }); diff --git a/package-lock.json b/package-lock.json index 5390bbdb..b6a2c1ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zonemaster-gui", - "version": "3.6.1", + "version": "4.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "zonemaster-gui", - "version": "3.6.1", + "version": "4.1.0", "license": "BSD-2-Clause", "dependencies": { "@angular/animations": "^13.3.11", diff --git a/package.json b/package.json index 4e0fcfce..e7b4c52e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zonemaster-gui", - "version": "4.1.1", + "version": "4.2.0", "license": "BSD-2-Clause", "scripts": { "ng": "ng", diff --git a/src/app/app.component.css b/src/app/app.component.css index 58e4d84d..eaa3dbf9 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -3,3 +3,7 @@ app-root { display: flex; flex-direction: column; } + +main { + flex-grow: 1; +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 5e305546..d901ff2c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,13 +1,7 @@ + -
+
- - - - - - - diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 67f00245..f3782493 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import {ViewEncapsulation} from '@angular/core'; +import { ViewEncapsulation } from '@angular/core'; @Component({ selector: 'app-root', @@ -10,6 +10,4 @@ import {ViewEncapsulation} from '@angular/core'; encapsulation: ViewEncapsulation.None }) export class AppComponent { - title = 'app'; } - diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ec8052d2..4bb390d0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -17,6 +17,8 @@ import { FormComponent } from './components/form/form.component'; import { ResultComponent } from './components/result/result.component'; import { HistoryComponent } from './components/history/history.component'; import { AlertComponent } from './components/alert/alert.component'; +import { FaqQuestionComponent } from './components/faq-question/faq-question.component'; +import { MessageBannerComponent } from './components/message-banner/message-banner.component'; import { RomanizePipe } from './pipes/romanize.pipe'; import { SafeHtmlPipe } from './pipes/safe-html.pipe'; @@ -60,7 +62,9 @@ const appRoutes: Routes = [ ResultComponent, HistoryComponent, AlertComponent, - HeaderComponent + HeaderComponent, + FaqQuestionComponent, + MessageBannerComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/alert/alert.component.css b/src/app/components/alert/alert.component.css index 82d9c215..e9c9d794 100644 --- a/src/app/components/alert/alert.component.css +++ b/src/app/components/alert/alert.component.css @@ -1,10 +1,9 @@ .alert-area { z-index: 999; position: absolute; - margin-top: 80px; max-width: inherit; width: 100%; - padding-top: 20px; + padding-top: 1rem; } app-alert { diff --git a/src/app/components/alert/alert.component.html b/src/app/components/alert/alert.component.html index 4aaa210b..addcacd7 100644 --- a/src/app/components/alert/alert.component.html +++ b/src/app/components/alert/alert.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/src/app/components/faq-question/faq-question.component.css b/src/app/components/faq-question/faq-question.component.css new file mode 100644 index 00000000..565bd45f --- /dev/null +++ b/src/app/components/faq-question/faq-question.component.css @@ -0,0 +1,17 @@ +.faq-question { + margin-bottom: 1rem; +} + +.faq-question h3 { + display: inline; + font-size: 1.2rem; +} + +.faq-question header { + padding: 0; +} + +.faq-question header a { + padding: .75em; + width: 100%; +} diff --git a/src/app/components/faq-question/faq-question.component.html b/src/app/components/faq-question/faq-question.component.html new file mode 100644 index 00000000..0519e61d --- /dev/null +++ b/src/app/components/faq-question/faq-question.component.html @@ -0,0 +1,11 @@ + diff --git a/src/app/components/faq-question/faq-question.component.ts b/src/app/components/faq-question/faq-question.component.ts new file mode 100644 index 00000000..cec0b6f5 --- /dev/null +++ b/src/app/components/faq-question/faq-question.component.ts @@ -0,0 +1,61 @@ +import { Component, Input, AfterViewInit, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import {ViewEncapsulation} from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-faq-question', + templateUrl: './faq-question.component.html', + styleUrls: ['./faq-question.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class FaqQuestionComponent implements AfterViewInit, OnInit, OnDestroy { + @Input('questionId') questionId; + @ViewChild('entry') entryElement; + public isOpen: boolean = false; + private fragment: string = null; + private fragmentSub: Subscription; + private clicked: boolean = false; + + constructor(private router: Router, private route: ActivatedRoute) { + this.fragment = this.router.getCurrentNavigation().extractedUrl.fragment; + this.fragmentSub = route.fragment.subscribe(this.checkQuestionState.bind(this)); + } + + ngOnInit() { + this.checkQuestionState(this.fragment); + } + + ngOnDestroy() { + this.fragmentSub.unsubscribe(); + } + + ngAfterViewInit(){ + if (this.isOpen) { + this.entryElement.nativeElement.scrollIntoView(); + } + } + + private checkQuestionState(newFragment) { + if (this.clicked) { + this.clicked = false; + } else if (newFragment === this.questionId) { + this.isOpen = true; + if (this.entryElement) { + this.entryElement.nativeElement.scrollIntoView(); + } + } + } + + public toggleOpenEntry() { + if (!this.isOpen) { + this.router.navigate(['faq'], { fragment: this.questionId, state: { scroll: false } }); + } else { + this.router.navigate(['faq']); + } + + this.isOpen = !this.isOpen; + this.clicked = true; + } + +} diff --git a/src/app/components/faq/faq.component.css b/src/app/components/faq/faq.component.css index 9f9c905a..015645a5 100644 --- a/src/app/components/faq/faq.component.css +++ b/src/app/components/faq/faq.component.css @@ -1,8 +1,21 @@ -.faq span[id^=q] { - position: relative; - top: -90px; +.faq-header .main h1 { + margin-bottom: 2rem; } -.faq { - overflow-anchor: none; +.faq-description { + max-width: 30rem; + text-align: center; + margin: auto; + font-size: 1.4em; +} + +.faq-contact { + max-width: 45rem; + text-align: center; + margin: auto; + margin-top: 1rem; +} + +.faq-category:not(:last-of-type) { + margin-bottom: 4rem; } diff --git a/src/app/components/faq/faq.component.html b/src/app/components/faq/faq.component.html index 725bb1c2..6677a5dd 100644 --- a/src/app/components/faq/faq.component.html +++ b/src/app/components/faq/faq.component.html @@ -1,6 +1,217 @@ - -

FAQ

+ +

Frequently asked questions

+

+ Find answers to common questions about Zonemaster here. +

+

+ You still don't find the information you are looking for?
+ You can contact the Zonemaster project team using the user mailing list, the issue tracker or the contact e-mail address. +

-
-
-
+ +
+

General information

+ + + What is Zonemaster? + + +

Zonemaster is a software package that validates the quality of a DNS delegation.

+

The ambition of the Zonemaster project is to develop and maintain an open source DNS validation tool, offering improved performance over existing tools and providing extensive documentation which could be re-used by similar projects in the future.

+ +

Zonemaster consists of several modules or components:

+
    +
  • Engine, a test framework that supports all functionality to perform DNS tests;
  • +
  • CLI, a command-line interface to the Engine;
  • +
  • Backend, a server that allows you to run Zonemaster tests and save results using a JSON-RPC API and a database;
  • +
  • GUI, a web interface to the Backend.
  • +
+ +

The components will help different types of users to check domain servers for configuration errors and generate a report that will assist in fixing the errors.

+
+ + + + Who is behind Zonemaster? + + +

+ Zonemaster is a joint project between AFNIC, the registry of '.fr' TLD and several other TLDs like '.re', '.pm', '.tf', '.wf', '.yt' and '.paris', and The Swedish Internet Foundation, the registry of '.se' and '.nu' TLDs. +

+
+ + + + How can Zonemaster help me? + + +

The Zonemaster tool could help different kind of users:

+ +
    +
  • DNS administrators, experts and beginners alike, who want to check their DNS configuration;
  • +
  • users who want to know whether the domain name they own or use have any issues or not.
  • +
+ +

Users of the second category should contact their DNS operator in case there are errors or warnings for any test of their domain name.

+
+ + + + What makes Zonemaster different from other DNS zone validating software? + +
    +
  • + Zonemaster saves all history from earlier tests based on the tested domain name, which means you can go back to a test you did some time ago and compare it to the test you ran just a moment ago. +
  • +
  • + All tests that Zonemaster runs are defined in the Test Case Specification documents that can be found in the Test Case Specification documents. +
  • +
  • + Zonemaster can be used to test undelegated domain names. +
  • +
  • + Zonemaster can be used to test DS records before their publication in the parent zone. +
  • +
  • + This open source version of Zonemaster was built using modular code which basically means that you can integrate parts of it in your own systems, if you wish. For example, it is quite rare that you would want a complete program just to check for redelegations. +
  • +
+
+ + + + How can Zonemaster distinguish between what is right and wrong? + +

+ The judgement of Zonemaster is primarily based on the DNS standards as defined in RFCs. It also bases its judgement on DNS best practices, which can be more loosely defined. +

+ +

+ All Zonemaster tests are defined in the Test Case Specification documents in which the references to the standard documents for each test case are found. +

+

+ The descriptions of message levels such as NOTICE, WARNING and ERROR are found in Severity Level Definitions. +

+ +

+ Sometimes there are different interpretations of the standards or opinions on what is best practice, and the Zonemaster team is always open to input. If you think we have made a mistake in our judgement please do not hesitate to send us an email at zonemaster-users@lists.iis.se (moderated mailing list) with a link to your test result and an explanation as to why you think it shows something that you consider incorrect. +

+
+ + + + What kind of DNS queries does Zonemaster generate? + +

+ Zonemaster sends multiple DNS queries to the name servers hosting the domain name being tested and also to the name servers hosting the parent zone of that domain name. +

+

+ The web interface of Zonemaster does not show the queries that were sent, only the CLI interface can. If you want to see such queries, you will have to locally install a minimally working Zonemaster instance with both the Engine and CLI components. See the CLI installation document for more information, or if you prefer a Docker image is also available. +

+

+ Queries sent can be shown using the 'DEBUG' level option. Fair warning, the output from the CLI can be quite heavy. For more information see CLI usage documentation. +

+
+ + + + + Zonemaster and privacy + +

+ Nothing besides the test domain, other test parameters and the result is stored in the database. Specifically we do not store any information that could link a test to the user who initiated it. +

+

+ However, since zonemaster.net, like other public instances, is open to everyone, it is possible for anyone to test your domain and view its history of tests. +

+
+
+ +
+

Using Zonemaster

+ + + + How come my domain name cannot be tested? + +

+ There are several possibilities. +

+
    +
  • Your domain name is not yet delegated.
  • +
  • Your domain name is not reachable from public Internet.
  • +
  • Zonemaster can only test what is called a DNS zone, for example 'zonemaster.net', and not host names, like 'www.zonemaster.net'.
  • +
  • There is a 10 minutes protection between consecutive tests for a given domain name, with same test parameters. Running a test within that window will instead show the last available test for that domain name, and parameters.
  • +
  • You have misspelled your domain name.
  • +
+
+ + + + Zonemaster returns "Error" or "Warning" for my domain name. What does it mean? + +

+ It depends on which test failed for your domain name. Each test is accompanied with one or several messages describing the issues found. +

+

+ You can also get further insight about each test from the Test Case Specification documents. +

+
+ + + + + What is an undelegated domain test? + +

+ An undelegated domain test is a test performed on a domain name that may, or may not, be fully published in the DNS. +

+

+ This can be quite useful if one is going to migrate one's domain from one registrar to another, for example, migrate zone 'example.com' from the name server 'ns.example.com' to the name server 'ns.example.org'. In this scenario one could perform an undelegated domain test providing the zone ('example.com') and the name server you are migrating to ('ns.example.org') before you migrate your domain. +

+

+ When the results of the test doesn't show any errors or warnings one can be fairly certain that the domain's new location is working well. However there might still be other problems in the zone data itself that this test is unaware of. +

+
+ + + + Does Zonemaster support IPv6? + +

+ Yes. By default Zonemaster will query name servers both over IPv4 and IPv6, unless explicitly configured otherwise. Such configuration is accessible through the "Show options" button. +

+
+ + + + Does Zonemaster verify DNSSEC? + +

+ Yes. If DNSSEC is available for a domain name that is tested by Zonemaster, it will be checked automatically. +

+
+ + + + Can I test the DS records before they are published? + +

+ Yes. Use the "Show options" button and there add the Delegation Signer (DS) records to be tested. Zonemaster will then use those in the same way as if they were already added in the parent zone. +

+
+ + + + How can I test a "reverse" zone with Zonemaster? + +

+ To check a reverse zone with Zonemaster, one first needs to know what the reverse zone is, and enter it in the format it has in the DNS. A reserve zone is obtained by reversing an IP address and adding a suffix. IPv4 addresses use the suffix "in-addr.arpa" while IPv6 addresses use "ip6.arpa". +

+

Examples:

+
    +
  • for IPv4 prefix '198.51.100.0/24': 100.51.198.in-addr.arpa;
  • +
  • for IPv6 prefix '2001:db8::/32': 8.b.d.0.1.0.0.2.ip6.arpa.
  • +
+
+
diff --git a/src/app/components/faq/faq.component.ts b/src/app/components/faq/faq.component.ts index 46b465b2..3a7c8968 100644 --- a/src/app/components/faq/faq.component.ts +++ b/src/app/components/faq/faq.component.ts @@ -1,9 +1,9 @@ -import { Component, OnInit, AfterViewChecked, OnDestroy, Inject, LOCALE_ID } from '@angular/core'; +import { Component, OnInit, AfterViewChecked, OnDestroy } from '@angular/core'; import {ViewEncapsulation} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; -import { HttpClient } from '@angular/common/http'; +import {ActivatedRoute, Router} from '@angular/router'; import { Subscription } from 'rxjs'; import { Title } from '@angular/platform-browser'; +import { first } from 'rxjs/operators'; @Component({ selector: 'app-faq', @@ -11,48 +11,13 @@ import { Title } from '@angular/platform-browser'; styleUrls: ['./faq.component.css'], encapsulation: ViewEncapsulation.None }) -export class FaqComponent implements OnInit, OnDestroy, AfterViewChecked { - private fragment: string; - public faqTemplate = ''; - public url = ''; +export class FaqComponent implements OnInit { - private fragmentSubscription: Subscription; + constructor(private titleService: Title, private route: ActivatedRoute ) { - constructor(private _http: HttpClient, - private route: ActivatedRoute, - private titleService: Title, - @Inject(LOCALE_ID) private language: string) { } ngOnInit() { - this.loadFaq(this.language); - this.fragmentSubscription = this.route.fragment.subscribe(fragment => { this.fragment = fragment; }); - } - - ngOnDestroy() { - this.fragmentSubscription.unsubscribe(); - } - - ngAfterViewChecked(): void { - if (!this.fragment) { - return - } - - const faqEntry = document.getElementById(this.fragment); - if (faqEntry !== null) { - faqEntry.scrollIntoView(); - this.fragment = ''; - } - } - - loadFaq(lang) { - this.url = `assets/faqs/gui-faq-${lang}.html`; - - this._http.get(this.url, {responseType: 'text'}) - .subscribe(data => { - this.faqTemplate = data; - }); - this.titleService.setTitle(`${$localize `:@@zm.faq.title:FAQ`} · Zonemaster`); } } diff --git a/src/app/components/footer/footer.component.css b/src/app/components/footer/footer.component.css index b894c02d..4dd76cbd 100644 --- a/src/app/components/footer/footer.component.css +++ b/src/app/components/footer/footer.component.css @@ -1,5 +1,14 @@ footer { - box-shadow: inset -1px 7px 5px -9px rgba(0,0,0,0.75); + background-color: var(--primary-color-lighter); + border-top: 1px solid var(--primary-color-light); + display: flex; +} + +footer .program-versions, +footer .program-versions:hover { + border: none; + background: none; + padding: 0; } footer dl.version { @@ -20,11 +29,11 @@ footer dl.version dd { } footer .copyright { - color: #4d5357; + margin-top: 1rem; } -footer i.fa { - margin-right: .25rem; +footer a i.fa { + margin-right: .4rem; } footer nav ul { @@ -42,4 +51,3 @@ footer nav li { height: auto; margin-bottom: 0.5rem; } - diff --git a/src/app/components/footer/footer.component.html b/src/app/components/footer/footer.component.html index 9ecfb260..126c36eb 100644 --- a/src/app/components/footer/footer.component.html +++ b/src/app/components/footer/footer.component.html @@ -1,38 +1,38 @@ -