diff --git a/README.md b/README.md index 110ee2a3..5f020008 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,9 @@ 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 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/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..f3db2bee 100644 --- a/src/app/components/faq/faq.component.html +++ b/src/app/components/faq/faq.component.html @@ -1,6 +1,197 @@ - -

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 program designed to help people check, measure and hopefully also understand how the DNS (Domain Name System) works.

+ +

It consists of several components:

+
    +
  • Engine - a test framework that supports all functionality to perform DNS tests.
  • +
  • CLI - a command-line interface to the Engine.
  • +
  • Bakend - 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.
  • +
+ +

When a domain name (such as 'zonemaster.net') is submitted to Zonemaster (using CLI or GUI), it will verify the domain name’s general health with a series of tests. The tests conducted by Zonemaster can be found in the Defined Test Cases document.

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

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

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

The Zonemaster tool could help different kind of users:

+ +
    +
  • Users who are knowledgable about the DNS protocol.
  • +
  • Users who just 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? + + +

+ Firstly, 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. +

+

+ Secondly, all tests that Zonemaster runs are defined in Test Case specifications that can be found in the Defined Test Cases document. +

+

+ Thirdly, Zonemaster can be used to test undelegated domain names. See Question 12. +

+

+ Fourthly, Zonemaster can be used to test DS records before their publication in the parent zone (which is required to enable DNSSEC for a signed zone). See Question 13. +

+

+ Lastly, 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 Test Case Specifications in which the references to the standard documents for that 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 queries does Zonemaster generate? + +

+ Zonemaster send 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 GUI interface of Zonemaster does not show any queries 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 (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 Using The CLI. +

+
+ + + + + Zonemaster and privacy + +

+ Since Zonemaster.net is open to everyone it is possible for anyone to check your domain and its history of tests. However there is no way to tell who has run a specific test since nothing more than the test parameters and results are stored. Specifically, no cookies or information on the user's IP address is stored in the database. The user who initiated the test cannot be traced back from the information in the database. +

+
+
+ +
+

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 (e.g. 'zonemaster.net') and not host names (e.g. '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 are accompanied with one or several messages describing the issues found. You can also get further insight about each test from the Defined Test Cases document. +

+
+ + + + + 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, e.g., 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 "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 "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 @@ -