diff --git a/README.md b/README.md index 41df8d0223..06c977915a 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,10 @@ Installing Default configuration file is located in `config/` folder. -To change the default configuration values, create local files that override the parameters you need to change. You can use `appConfig.json` as a starting point. +To override the default configuration values, create local files that override the parameters you need to change. You can use `config.example.yml` as a starting point. -- Create a new `appConfig.(dev or development).json` file in `config/` for a `development` environment; -- Create a new `appConfig.(prod or production).ts` file in `config/` for a `production` environment; +- Create a new `config.(dev or development).yml` file in `config/` for a `development` environment; +- Create a new `config.(prod or production).yml` file in `config/` for a `production` environment; The settings can also be overwritten using an environment file or environment variables. @@ -144,9 +144,9 @@ The same settings can also be overwritten by setting system environment variable export DSPACE_HOST=api7.dspace.org ``` -The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `APP_CONFIG_PATH` overrides **`appConfig.(prod or dev).json`** +The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`** -These configuration sources are collected **at run time**, and written to `dist/browser/assets/appConfig.json` for production and `src/app/assets/appConfig.json` for development. +These configuration sources are collected **at run time**, and written to `dist/browser/assets/config.json` for production and `src/app/assets/config.json` for development. The configuration file can be externalized by using environment variable `APP_CONFIG_PATH`. @@ -251,7 +251,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con The test files can be found in the `./cypress/integration/` folder. -Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `appConfig.prod.json` or `appConfig.json`. You may override this using env variables, see [Configuring](#configuring). +Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `config.prod.yml` or `config.yml`. You may override this using env variables, see [Configuring](#configuring). Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results. @@ -331,7 +331,7 @@ File Structure ``` dspace-angular ├── config * -│ └── appConfig.json * Default app config +│ └── config.yml * Default app config ├── cypress * Folder for Cypress (https://cypress.io/) / e2e tests │ ├── downloads * │ ├── fixtures * Folder for e2e/integration test files diff --git a/config/.gitignore b/config/.gitignore index e1899191e1..a420ca4302 100644 --- a/config/.gitignore +++ b/config/.gitignore @@ -1 +1,2 @@ -appConfig.*.json +config.*.yml +!config.example.yml diff --git a/config/appConfig.json b/config/appConfig.json deleted file mode 100644 index a70d8ea6ab..0000000000 --- a/config/appConfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rest": { - "ssl": true, - "host": "api7.dspace.org", - "port": 443, - "nameSpace": "/server" - } -} diff --git a/config/config.example.yml b/config/config.example.yml new file mode 100644 index 0000000000..165d94b7bf --- /dev/null +++ b/config/config.example.yml @@ -0,0 +1,231 @@ +# NOTE: will log all redux actions and transfers in console +debug: false + +# Angular Universal server settings +# NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. +ui: + ssl: false + host: localhost + port: 4000 + # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: / + # The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). + rateLimiter: + windowMs: 60000 # 1 minute + max: 500 # limit each IP to 500 requests per windowMs + +# The REST API server settings +# NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. +rest: + ssl: true + host: api7.dspace.org + port: 443 + # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: /server + +# Caching settings +cache: + # NOTE: how long should objects be cached for by default + msToLive: + default: 900000 # 15 minutes + control: max-age=60 # revalidate browser + autoSync: + defaultTime: 0 + maxBufferSize: 100 + timePerMethod: + PATCH: 3 # time in seconds + +# Authentication settings +auth: + # Authentication UI settings + ui: + # the amount of time before the idle warning is shown + timeUntilIdle: 900000 # 15 minutes + # the amount of time the user has to react after the idle warning is shown before they are logged out. + idleGracePeriod: 300000 # 5 minutes + # Authentication REST settings + rest: + # If the rest token expires in less than this amount of time, it will be refreshed automatically. + # This is independent from the idle warning. + timeLeftBeforeTokenRefresh: 120000 # 2 minutes + +# Form settings +form: + # NOTE: Map server-side validators to comparative Angular form validators + validatorMap: + required: required + regex: pattern + +# Notification settings +notifications: + rtl: false + position: + - top + - right + maxStack: 8 + # NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically + timeOut: 5000 # 5 second + clickToClose: true + # NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' + animate: scale + +# Submission settings +submission: + autosave: + # NOTE: which metadata trigger an autosave + metadata: [] + # NOTE: after how many time (milliseconds) submission is saved automatically + # eg. timer: 5 * (1000 * 60); // 5 minutes + timer: 0 + icons: + metadata: + # NOTE: example of configuration + # # NOTE: metadata name + # - name: dc.author + # # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + # style: fas fa-user + - name: dc.author + style: fas fa-user + # default configuration + - name: default + style: '' + authority: + confidence: + # NOTE: example of configuration + # # NOTE: confidence value + # - name: dc.author + # # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + # style: fa-user + - value: 600 + style: text-success + - value: 500 + style: text-info + - value: 400 + style: text-warning + # default configuration + - value: default + style: text-muted + +# Default Language in which the UI will be rendered if the user's browser language is not an active language +defaultLanguage: en + +# Languages. DSpace Angular holds a message catalog for each of the following languages. +# When set to active, users will be able to switch to the use of this language in the user interface. +languages: + - code: en + label: English + active: true + - code: cs + label: Čeština + active: true + - code: de + label: Deutsch + active: true + - code: es + label: Español + active: true + - code: fr + label: Français + active: true + - code: lv + label: Latviešu + active: true + - code: hu + label: Magyar + active: true + - code: nl + label: Nederlands + active: true + - code: pt-PT + label: Português + active: true + - code: pt-BR + label: Português do Brasil + active: true + - code: fi + label: Suomi + active: true + +# Browse-By Pages +browseBy: + # Amount of years to display using jumps of one year (current year - oneYearLimit) + oneYearLimit: 10 + # Limit for years to display using jumps of five years (current year - fiveYearLimit) + fiveYearLimit: 30 + # The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) + defaultLowerLimit: 1900 + # List of all the active Browse-By types + # Adding a type will activate their Browse-By page and add them to the global navigation menu, + # as well as community and collection pages + # Allowed fields and their purpose: + # id: The browse id to use for fetching info from the rest api + # type: The type of Browse-By page to display + # metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') + types: + - id: title + type: title + - id: dateissued + type: date + metadataField: dc.date.issued + - id: author + type: metadata + - id: subject + type: metadata + +# Item Page Config +item: + edit: + undoTimeout: 10000 # 10 seconds + +# Collection Page Config +collection: + edit: + undoTimeout: 10000 # 10 seconds + +# Theme Config +themes: + # Add additional themes here. In the case where multiple themes match a route, the first one + # in this list will get priority. It is advisable to always have a theme that matches + # every route as the last one + # + # # A theme with a handle property will match the community, collection or item with the given + # # handle, and all collections and/or items within it + # - name: 'custom', + # handle: '10673/1233' + # + # # A theme with a regex property will match the route using a regular expression. If it + # # matches the route for a community or collection it will also apply to all collections + # # and/or items within it + # - name: 'custom', + # regex: 'collections\/e8043bc2.*' + # + # # A theme with a uuid property will match the community, collection or item with the given + # # ID, and all collections and/or items within it + # - name: 'custom', + # uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' + # + # # The extends property specifies an ancestor theme (by name). Whenever a themed component is not found + # # in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. + # - name: 'custom-A', + # extends: 'custom-B', + # # Any of the matching properties above can be used + # handle: '10673/34' + # + # - name: 'custom-B', + # extends: 'custom', + # handle: '10673/12' + # + # # A theme with only a name will match every route + # name: 'custom' + # + # # This theme will use the default bootstrap styling for DSpace components + # - name: BASE_THEME_NAME + # + - name: dspace + +# Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). +# For images, this enables a gallery viewer where you can zoom or page through images. +# For videos, this enables embedded video streaming +mediaViewer: + image: false + video: false diff --git a/config/config.yml b/config/config.yml new file mode 100644 index 0000000000..b5eecd112f --- /dev/null +++ b/config/config.yml @@ -0,0 +1,5 @@ +rest: + ssl: true + host: api7.dspace.org + port: 443 + nameSpace: /server diff --git a/docs/Configuration.md b/docs/Configuration.md index 82aaa42c40..d21a41e277 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,15 +1,15 @@ # Configuration -Default configuration file is located in `config/` folder. All configuration options should be listed in the default configuration file `src/config/default-app-config.ts`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change. You can use `appConfig.json` as a starting point. +Default configuration file is located at `config/config.yml`. All configuration options should be listed in the default typescript file `src/config/default-app-config.ts`. Please do not change this file directly! To override the default configuration values, create local files that override the parameters you need to change. You can use `config.example.yml` as a starting point. -- Create a new `appConfig.(dev or development).json` file in `config/` for `development` environment; -- Create a new `appConfig.(prod or production).json` file in `config/` for `production` environment; +- Create a new `config.(dev or development).yml` file in `config/` for `development` environment; +- Create a new `config.(prod or production).yml` file in `config/` for `production` environment; Alternatively, create a desired app config file at an external location and set the path as environment variable `APP_CONFIG_PATH`. e.g. ``` -APP_CONFIG_PATH=/usr/local/dspace/config/appConfig.json +APP_CONFIG_PATH=/usr/local/dspace/config/config.yml ``` Configuration options can be overridden by setting environment variables. diff --git a/package.json b/package.json index e0b07b1040..097f9f21ab 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint": "ng lint", "lint-fix": "ng lint --fix=true", "e2e": "ng e2e", - "clean:dev:config": "rimraf src/assets/appConfig.json", + "clean:dev:config": "rimraf src/assets/config.json", "clean:coverage": "rimraf coverage", "clean:dist": "rimraf dist", "clean:doc": "rimraf doc", @@ -35,7 +35,8 @@ "build:mirador": "webpack --config webpack/webpack.mirador.config.ts", "merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts", "cypress:open": "cypress open", - "cypress:run": "cypress run" + "cypress:run": "cypress run", + "env:yaml": "ts-node --project ./tsconfig.ts-node.json scripts/env-to-yaml.ts" }, "browser": { "fs": false, @@ -90,6 +91,7 @@ "http-proxy-middleware": "^1.0.5", "https": "1.0.0", "js-cookie": "2.2.1", + "js-yaml": "^4.1.0", "json5": "^2.1.3", "jsonschema": "1.4.0", "jwt-decode": "^3.1.2", diff --git a/scripts/env-to-yaml.ts b/scripts/env-to-yaml.ts new file mode 100644 index 0000000000..47f876f20a --- /dev/null +++ b/scripts/env-to-yaml.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs'; +import * as yaml from 'js-yaml'; +import { join } from 'path'; + +const args = process.argv.slice(2); + +if (args[0] === undefined) { + console.log(`Usage:\n\tyarn env:yaml [relative path to environment.ts file] (optional relative path to write yaml file)\n`); + process.exit(0); +} + +const envFullPath = join(process.cwd(), args[0]); + +if (!fs.existsSync(envFullPath)) { + console.error(`Error:\n${envFullPath} does not exist\n`); + process.exit(1); +} + +try { + const env = require(envFullPath); + + const config = yaml.dump(env); + if (args[1]) { + const ymlFullPath = join(process.cwd(), args[1]); + fs.writeFileSync(ymlFullPath, config); + } else { + console.log(config); + } +} catch (e) { + console.error(e); +} + diff --git a/server.ts b/server.ts index 70d01e7710..da3b877bc1 100644 --- a/server.ts +++ b/server.ts @@ -58,7 +58,7 @@ const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : ' const cookieParser = require('cookie-parser'); -const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/appConfig.json')); +const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/config.json')); // extend environment with app config for server extendEnvironmentWithAppConfig(environment, appConfig); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2a7a885794..b1039f9c6d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -109,8 +109,6 @@ export class AppComponent implements OnInit, AfterViewInit { @Optional() private googleAnalyticsService: GoogleAnalyticsService, ) { - console.log(this.appConfig); - if (!isEqual(environment, this.appConfig)) { throw new Error('environment does not match app config!'); } diff --git a/src/assets/.gitignore b/src/assets/.gitignore index c25cf8c104..d344ba6b06 100644 --- a/src/assets/.gitignore +++ b/src/assets/.gitignore @@ -1 +1 @@ -appConfig.json +config.json diff --git a/src/config/config.server.ts b/src/config/config.server.ts index 5e8d8e1587..25a04c20a7 100644 --- a/src/config/config.server.ts +++ b/src/config/config.server.ts @@ -1,5 +1,6 @@ import * as colors from 'colors'; import * as fs from 'fs'; +import * as yaml from 'js-yaml'; import { join } from 'path'; import { AppConfig } from './app-config.interface'; @@ -44,8 +45,12 @@ const getEnvironment = (): Environment => { }; const getLocalConfigPath = (env: Environment) => { - // default to config/appConfig.json - let localConfigPath = join(CONFIG_PATH, 'appConfig.json'); + // default to config/config.yml + let localConfigPath = join(CONFIG_PATH, 'config.yml'); + + if (!fs.existsSync(localConfigPath)) { + localConfigPath = join(CONFIG_PATH, 'config.yaml'); + } // determine app config filename variations let envVariations; @@ -63,9 +68,16 @@ const getLocalConfigPath = (env: Environment) => { // check if any environment variations of app config exist for (const envVariation of envVariations) { - const envLocalConfigPath = join(CONFIG_PATH, `appConfig.${envVariation}.json`); + let envLocalConfigPath = join(CONFIG_PATH, `config.${envVariation}.yml`); if (fs.existsSync(envLocalConfigPath)) { localConfigPath = envLocalConfigPath; + break; + } else { + envLocalConfigPath = join(CONFIG_PATH, `config.${envVariation}.yaml`); + if (fs.existsSync(envLocalConfigPath)) { + localConfigPath = envLocalConfigPath; + break; + } } } @@ -76,7 +88,7 @@ const overrideWithConfig = (config: Config, pathToConfig: string) => { try { console.log(`Overriding app config with ${pathToConfig}`); const externalConfig = fs.readFileSync(pathToConfig, 'utf8'); - mergeConfig(config, JSON.parse(externalConfig)); + mergeConfig(config, yaml.load(externalConfig)); } catch (err) { console.error(err); } @@ -190,7 +202,7 @@ export const buildAppConfig = (destConfigPath?: string): AppConfig => { if (isNotEmpty(destConfigPath)) { fs.writeFileSync(destConfigPath, JSON.stringify(appConfig, null, 2)); - console.log(`Angular ${colors.bold('appConfig.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); + console.log(`Angular ${colors.bold('config.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); } return appConfig; diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index bceea29b96..c1bb275921 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -20,7 +20,17 @@ import { UniversalConfig } from './universal-config.interface'; export class DefaultAppConfig implements AppConfig { production = false; - // Angular Universal server settings. + // Angular Universal settings + universal: UniversalConfig = { + preboot: true, + async: true, + time: false + }; + + // NOTE: will log all redux actions and transfers in console + debug = false; + + // Angular Universal server settings // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. ui: UIServerConfig = { ssl: false, @@ -36,7 +46,7 @@ export class DefaultAppConfig implements AppConfig { } }; - // The REST API server settings. + // The REST API server settings // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. rest: ServerConfig = { ssl: false, @@ -164,16 +174,6 @@ export class DefaultAppConfig implements AppConfig { } }; - // Angular Universal settings - universal: UniversalConfig = { - preboot: true, - async: true, - time: false - }; - - // NOTE: will log all redux actions and transfers in console - debug = false; - // Default Language in which the UI will be rendered if the user's browser language is not an active language defaultLanguage = 'en'; diff --git a/src/main.browser.ts b/src/main.browser.ts index c72ca0cf31..de4276ea93 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -34,7 +34,7 @@ const main = () => { return bootstrap(); } else { - return fetch('assets/appConfig.json') + return fetch('assets/config.json') .then((response) => response.json()) .then((appConfig: AppConfig) => { diff --git a/webpack/webpack.browser.ts b/webpack/webpack.browser.ts index 134eecb7ed..a71d749347 100644 --- a/webpack/webpack.browser.ts +++ b/webpack/webpack.browser.ts @@ -10,7 +10,7 @@ module.exports = Object.assign({}, commonExports, { }, devServer: { before(app, server) { - buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); + buildAppConfig(join(process.cwd(), 'src/assets/config.json')); } } }); diff --git a/yarn.lock b/yarn.lock index db63c66040..6fe2896b20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2753,6 +2753,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + aria-query@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" @@ -8012,6 +8017,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"