├── .babelrc.json ├── .eslintrc.js ├── .github └── workflows │ ├── lint.yml │ ├── semgrep.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── CHANGELOG.MD ├── CONTRIBUTING.md ├── README.md ├── assets ├── analytics-welcome.svg ├── details-arrows.png ├── global-caching.svg ├── hero-bg-clouds.png ├── icon-bolt.svg ├── icon-lock.svg ├── icon-pin.svg ├── icon-shield.svg ├── icons-seee324dde5.png ├── icons_2x-s6333fe7591.png ├── insight.svg ├── layers-2x.png ├── layers.png ├── logo-reverse.svg ├── logo-symbol.svg ├── logo.svg ├── modal-two-factor-auth.png ├── modal-two-factor-auth_2x.png ├── overview-welcome-yjs.svg ├── overview-welcome.svg ├── plan-changed-success.svg ├── request-submitted-success.svg ├── security.svg ├── select2-cf-white.png ├── select2-cf.png ├── select2x2-cf-white.png ├── select2x2-cf.png ├── spinner.gif ├── throbber.gif ├── vertical-range.png ├── web-optimization.svg ├── yjs-background.jpg ├── yjs-background_2x.jpg └── yjs-logo.svg ├── config.json.sample ├── fonts ├── FontAwesome.otf ├── cloudflare-font.eot ├── cloudflare-font.svg ├── cloudflare-font.ttf ├── cloudflare-font.woff ├── fontawesome-cloudflare.eot ├── fontawesome-cloudflare.svg ├── fontawesome-cloudflare.ttf ├── fontawesome-cloudflare.woff ├── fontawesome-webfont.eot ├── fontawesome-webfont.svg ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff ├── opensans-300.eot ├── opensans-300.ttf ├── opensans-300.woff ├── opensans-300.woff2 ├── opensans-300i.eot ├── opensans-300i.ttf ├── opensans-300i.woff ├── opensans-300i.woff2 ├── opensans-400.eot ├── opensans-400.ttf ├── opensans-400.woff ├── opensans-400.woff2 ├── opensans-400i.eot ├── opensans-400i.ttf ├── opensans-400i.woff ├── opensans-400i.woff2 ├── opensans-600.eot ├── opensans-600.ttf ├── opensans-600.woff ├── opensans-600.woff2 ├── opensans-700.eot ├── opensans-700.ttf ├── opensans-700.woff └── opensans-700.woff2 ├── lang ├── de.js ├── en.js ├── es.js ├── fr.js ├── it.js ├── nl.js └── pt.js ├── package.json ├── prettier.config.js ├── src ├── actions │ ├── activeZone.js │ ├── app.js │ ├── config.js │ ├── intl.js │ ├── notifications.js │ ├── pluginSettings.js │ ├── user.js │ ├── zoneDnsRecords.js │ ├── zoneEntitlements.js │ ├── zoneProvision.js │ ├── zonePurgeCache.js │ ├── zoneSettings.js │ └── zones.js ├── components │ ├── AppNavigationLiNode │ │ └── AppNavigationLiNode.js │ ├── BenefitsFeature │ │ └── BenefitsFeature.js │ ├── C3Wrapper │ │ └── C3Wrapper.js │ ├── CustomCardControl │ │ └── CustomCardControl.js │ ├── FeatureManager │ │ └── FeatureManager.js │ ├── FormattedMarkdown │ │ └── FormattedMarkdown.js │ ├── GradientBanner │ │ └── GradientBanner.js │ ├── MarketingFeature │ │ └── MarketingFeature.js │ ├── RenderCardsDynamically │ │ └── RenderCardsDynamically.js │ └── TimeSeriesChart │ │ └── TimeSeriesChart.js ├── constants │ ├── ActionTypes.js │ ├── Plans.js │ ├── Schemas.js │ └── UrlPaths.js ├── containers │ ├── ActivationCheckCard │ │ └── ActivationCheckCard.js │ ├── ActiveZoneSelector │ │ └── ActiveZoneSelector.js │ ├── AdvanceDDoSCard │ │ └── AdvanceDDoSCard.js │ ├── AlwaysOnlineCard │ │ └── AlwaysOnlineCard.js │ ├── AnalyticsPage │ │ └── AnaltyicsPage.js │ ├── App │ │ └── App.js │ ├── AppNavigation │ │ └── AppNavigation.js │ ├── ApplyDefaultSettingsCard │ │ └── ApplyDefaultSettingsCard.js │ ├── AutomaticHTTPSRewritesCard │ │ └── AutomaticHTTPSRewritesCard.js │ ├── AutomaticPlatformOptimization │ │ └── AutomaticPlatformOptimizationCard.js │ ├── BenefitsCollection │ │ └── BenefitsCollection.js │ ├── BrowserCacheTTLCard │ │ └── BrowserCacheTTLCard.js │ ├── BrowserIntegrityCheckCard │ │ └── BrowserIntegrityCheckCard.js │ ├── CacheLevelCard │ │ └── CacheLevelCard.js │ ├── ChallengePassageCard │ │ └── ChallengePassageCard.js │ ├── DNSManagementPage │ │ └── DNSManagementPage.js │ ├── DNSRecordEditor │ │ └── DNSRecordEditor.js │ ├── DevelopmentModeCard │ │ └── DevelopmentModeCard.js │ ├── GlobalNotifications │ │ └── GlobalNotifications.js │ ├── Header │ │ └── Header.js │ ├── HomePage │ │ └── HomePage.js │ ├── IPV6Card │ │ └── IPV6Card.js │ ├── ImageOptimizationCard │ │ └── ImageOptimizationCard.js │ ├── IpRewriteCard │ │ └── IpRewriteCard.js │ ├── LoginPage │ │ └── LoginPage.js │ ├── MarketingFeatureCollection │ │ └── MarketingFeatureCollection.js │ ├── MoreSettingsPage │ │ └── MoreSettingsPage.js │ ├── PluginSpecificCacheCard │ │ └── PluginSpecificCacheCard.js │ ├── PluginSpecificCacheTagCard │ │ └── PluginSpecificCachetTagCard.js │ ├── PurgeCacheCard │ │ └── PurgeCacheCard.js │ ├── SSLCard │ │ └── SSLCard.js │ ├── SecurityLevelCard │ │ └── SecurityLevelCard.js │ ├── SignUpPage │ │ └── SignUpPage.js │ ├── SplashPage │ │ └── SplashPage.js │ ├── UnderAttackButton │ │ └── UnderAttackButton.js │ ├── WAFCard │ │ └── WAFCard.js │ ├── WaitForSettings │ │ └── WaitForSettings.js │ └── ZoneProvisionContainer │ │ └── ZoneProvisionContainer.js ├── index.js ├── reducers │ ├── activeZone.js │ ├── app.js │ ├── config.js │ ├── index.js │ ├── intl.js │ ├── notifications.js │ ├── pluginSettings.js │ ├── user.js │ ├── zoneDnsRecords.js │ ├── zoneEntitlements.js │ ├── zonePurgeCache.js │ ├── zoneSettings.js │ └── zones.js ├── routes.js ├── selectors │ ├── activeZone.js │ ├── config.js │ ├── intl.js │ ├── pluginSettings.js │ ├── zoneSettings.js │ └── zones.js ├── store │ └── configureStore.js ├── test │ ├── _helpers │ │ └── createMockStore.js │ ├── components │ │ ├── BenefitsFeatureTest.js │ │ ├── FeatureManagerTest.js │ │ ├── GradientBannerTest.js │ │ ├── MarketingFeatureTest.js │ │ └── __snapshots__ │ │ │ ├── BenefitsFeatureTest.js.snap │ │ │ ├── FeatureManagerTest.js.snap │ │ │ ├── GradientBannerTest.js.snap │ │ │ └── MarketingFeatureTest.js.snap │ ├── containers │ │ ├── SplashPageTest.js │ │ └── __snapshots__ │ │ │ └── SplashPageTest.js.snap │ ├── reducers │ │ ├── __snapshots__ │ │ │ └── configTest.js.snap │ │ ├── activeZoneTest.js │ │ ├── configTest.js │ │ ├── pluginSettingsTest.js │ │ └── zonePurgeCacheTest.js │ ├── selectors │ │ └── configTest.js │ └── utils │ │ ├── PluginAPITest.js │ │ └── deduplicateZonesTest.js └── utils │ ├── Auth │ └── Auth.js │ ├── CFClientV4API │ └── CFClientV4API.js │ ├── CFHostAPI │ └── CFHostAPI.js │ ├── ImportCards.js │ ├── PluginAPI │ └── PluginAPI.js │ └── utils.js ├── stylesheets ├── cf.core.css └── components.css ├── webpack.config.js └── yarn.lock /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@babel/eslint-parser', 3 | parserOptions: { 4 | ecmaVersion: 6, 5 | sourceType: 'module', 6 | ecmaFeatures: { 7 | globalReturn: false, 8 | impliedStrict: true, 9 | jsx: true, 10 | experimentalObjectRestSpread: true 11 | }, 12 | babelOptions: { 13 | configFile: './.babelrc.json' 14 | } 15 | }, 16 | globals: { 17 | __webpack_public_path__: true, 18 | bootstrap: true, 19 | describe: true, 20 | it: true, 21 | beforeEach: true, 22 | afterEach: true, 23 | before: true, 24 | after: true, 25 | test: true, 26 | expect: true 27 | }, 28 | env: { 29 | browser: true, 30 | node: true, 31 | es6: true 32 | }, 33 | plugins: ['prettier', 'react', 'import', 'json', 'cflint', 'compat', 'mocha'], 34 | extends: [ 35 | 'plugin:react/recommended', 36 | 'plugin:import/errors', 37 | 'plugin:import/warnings' 38 | ], 39 | rules: { 40 | 'mocha/no-exclusive-tests': 2, 41 | 'block-scoped-var': 2, 42 | 'default-case': 2, 43 | 'guard-for-in': 2, 44 | 'no-else-return': 2, 45 | 'no-floating-decimal': 2, 46 | 'no-self-compare': 2, 47 | 'no-void': 2, 48 | radix: 2, 49 | 'wrap-iife': ['error', 'inside'], 50 | 'no-catch-shadow': 2, 51 | 'handle-callback-err': 2, 52 | camelcase: 0, 53 | 'no-unused-vars': [2, { vars: 'all', args: 'after-used' }], 54 | 'no-undef': [2, { typeof: false }], 55 | 'react/prop-types': [2, { skipUndeclared: true }], 56 | 'import/no-absolute-path': 2, 57 | 'import/no-deprecated': 1, 58 | 'import/no-mutable-exports': 1, 59 | 'import/no-extraneous-dependencies': 0, 60 | 'import/no-unresolved': 0, 61 | 'import/first': 1, 62 | 'prettier/prettier': [2, { trailingComma: 'none', singleQuote: true }], 63 | 'compat/compat': 0, 64 | 'no-use-before-define': ['error', { functions: false, classes: true }] 65 | }, 66 | settings: { 67 | 'import/resolver': { 68 | webpack: { 69 | config: './webpack.config.js' 70 | } 71 | }, 72 | react: { 73 | version: 'detect' 74 | } 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-node@v2 10 | with: 11 | node-version: '14' 12 | - run: yarn install 13 | - run: yarn run build:production 14 | - run: yarn run lint 15 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | schedule: 9 | - cron: '0 0 * * *' 10 | name: Semgrep config 11 | jobs: 12 | semgrep: 13 | name: semgrep/ci 14 | runs-on: ubuntu-latest 15 | env: 16 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 17 | SEMGREP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 20 | container: 21 | image: returntocorp/semgrep 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: semgrep ci 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-node@v2 10 | with: 11 | node-version: '14' 12 | - run: yarn install 13 | - run: yarn run build:production 14 | - run: yarn run test 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | compiled.js 4 | compiled.js.map 5 | *.DS_Store 6 | coverage/ 7 | .jest/ 8 | .vscode/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cloudflare Plugins 2 | 3 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 4 | 5 | ## How To Contribute 6 | 7 | We welcome community contribution to this repository. To help add functionality or address issues, please take the following steps: 8 | 9 | * Fork the repository from the master branch. 10 | * Create a new branch for your features / fixes. 11 | * Make the changes you wish to see. 12 | * Add tests for all changes. 13 | * Create a pull request with details of what changes have been made, explanation of new behaviour, and link to issue that is addressed. 14 | * Addressing (with @...) one or more of the maintainers in the description of the pull request 15 | * Ensure documentation contains the correct information. 16 | * Pull requests will be reviewed and hopefully merged into a release. 17 | 18 | ## Before Contributing 19 | 20 | Cloudflare has multiple plugins using shared codebases. 21 | 22 | [WordPress](https://github.com/cloudflare/Cloudflare-WordPress), [CPanel](https://github.com/cloudflare/CloudFlare-CPanel), [Magento](https://github.com/cloudflare/CloudFlare-Magento) are the main repositories of the plugins. Every plugin has a config.js file which allows them to control the frontend of the plugin. 23 | 24 | Below are Cloudflare maintained repositories the plugins depend on. 25 | 26 | * [cloudflare-frontend](https://github.com/cloudflare/CloudFlare-FrontEnd) is a generic frontend used in plugins. You can add/remove cards simply by editing [config](https://github.com/cloudflare/CloudFlare-FrontEnd/blob/master/config.js) file. 27 | * [cf-ui](https://github.com/cloudflare/cf-ui) is a Cloudflare UI Framework where cloudflare-frontend is using. 28 | * [cloudflare-plugin-backend](https://github.com/cloudflare/cloudflare-plugin-backend) is a generic backend plugins use. 29 | * [cf-ip-rewrite](https://github.com/cloudflare/cf-ip-rewrite) allows to rewrite Cloudflare IP's in Application level. 30 | * [mod_cloudflare](https://github.com/cloudflare/mod_cloudflare) allows Apache to rewrite Cloudflare IP's with user IP's. It is not used in plugins itself but it maybe be a better alternative then `cf-ip-rewrite`. 31 | 32 | ## Frontend Updates 33 | 34 | Each plugin may use different Frontend [versions]((https://github.com/cloudflare/CloudFlare-FrontEnd/releases)). When publishing a Frontend release we copy the following files to other plugins; 35 | 36 | * `assets/` 37 | * `fonts/` 38 | * `lang/` 39 | * `stylesheets/` 40 | * `compiled.js` which is created when `gulp compress` command is called within Frontend repository. 41 | 42 | ## Translations 43 | 44 | The plugins use a common language file which is located [here](https://github.com/cloudflare/CloudFlare-FrontEnd/tree/master/lang). English translation is always up to date where as other translations are not. If you have any issues or questions regarding with translations feel free to open an [issue](https://github.com/cloudflare/CloudFlare-FrontEnd/issues). 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build using [Yarn](https://yarnpkg.com/en/docs/install) 2 | 1. `yarn install` 3 | 2. `yarn run build` 4 | 5 | # Development Tasks 6 | ``` 7 | $ yarn run build 8 | $ OUTPUT_PATH=custom-path.js yarn run build 9 | $ yarn run build:production 10 | $ yarn run lint 11 | $ yarn run format 12 | $ yarn run test 13 | ``` 14 | 15 | # Production 16 | For production run `yarn run build:production` to get a minified version of compiled.js 17 | 18 | # Building Your Own Backend 19 | This repository serves as the front end for all of our 3rd party integrations. 20 | It is intended to be backend agnostic with the intention of making it as easy as 21 | possible to port it to new backends. If you would like to build a custom backend 22 | just follow these steps: 23 | 24 | 1. Implement RestProxyCallback() 25 | ``` 26 | /* 27 | * A callback for cf-util-http to proxy all calls to our backend 28 | * 29 | * @param {Object} [opts] 30 | * @param {String} [opts.method] - GET/POST/PUT/PATCH/DELETE 31 | * @param {String} [opts.url] 32 | * @param {Object} [opts.parameters] 33 | * @param {Object} [opts.headers] 34 | * @param {Object} [opts.body] 35 | * @param {Function} [opts.onSuccess] 36 | * @param {Function} [opts.onError] 37 | */ 38 | function RestProxyCallback(opts) {} 39 | ``` 40 | This method is called on every request before it is sent. It should route all 41 | absolute URLs to the endpoint for your backend. Requests with 42 | relative URLs for things like localization (./lang/*.js) and 43 | config (./config.json) should remain unchanged. 44 | 45 | 2. Build your backend data store 46 | Your backend needs to store the following information about each user: 47 | * Cloudflare [Client V4 API](https://api.cloudflare.com/) Key 48 | * Cloudflare Client V4 API Email 49 | * Cloudflare [Host API](https://www.cloudflare.com/docs/host-api.html) Key 50 | 51 | 3. In `index.html` create a variable in local storage called `cfEmail` which contains 52 | Cloudflare Client V4 API Email of the current user. 53 | 54 | 4. Build an API Client for the Cloudflare V4 API which adds the necessary headers 55 | to each request. 56 | 57 | 5. Build an API Client for the Cloudflare Host API which adds the Host Key to all requests. 58 | 59 | ## JSON response for endpoint /config 60 | 61 | ``` 62 | { 63 | "debug": false, 64 | "featureManagerIsFullZoneProvisioningEnabled": false, 65 | "isDNSPageEnabled": true, 66 | "isSubdomainCheckEnabled": true, 67 | "homePageCards": [ 68 | "ApplyDefaultSettingsCard", 69 | "AutomaticHTTPSRewritesCard", 70 | "IpRewriteCard", 71 | "PluginSpecificCacheCard", 72 | "PluginSpecificCacheTagCard" 73 | ], 74 | "moreSettingsCards": { 75 | "container.moresettings.speed": [ 76 | "AlwaysOnlineCard", 77 | "BrowserCacheTTLCard", 78 | "CacheLevelCard", 79 | "DevelopmentModeCard", 80 | "IPV6Card", 81 | "ImageOptimizationCard", 82 | "PurgeCacheCard", 83 | ], 84 | "container.moresettings.security": [ 85 | "AdvanceDDoSCard", 86 | "BrowserIntegrityCheckCard", 87 | "ChallengePassageCard", 88 | "SecurityLevelCard", 89 | "SSLCard", 90 | "WAFCard" 91 | ] 92 | }, 93 | "locale": "en", 94 | "integrationName": "frontend", 95 | "useHostAPILogin": true, 96 | "version": "2.8.1" 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /assets/analytics-welcome.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/details-arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/details-arrows.png -------------------------------------------------------------------------------- /assets/global-caching.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/hero-bg-clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/hero-bg-clouds.png -------------------------------------------------------------------------------- /assets/icon-bolt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon-lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon-pin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icon-shield.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons-seee324dde5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/icons-seee324dde5.png -------------------------------------------------------------------------------- /assets/icons_2x-s6333fe7591.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/icons_2x-s6333fe7591.png -------------------------------------------------------------------------------- /assets/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/layers-2x.png -------------------------------------------------------------------------------- /assets/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/layers.png -------------------------------------------------------------------------------- /assets/modal-two-factor-auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/modal-two-factor-auth.png -------------------------------------------------------------------------------- /assets/modal-two-factor-auth_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/modal-two-factor-auth_2x.png -------------------------------------------------------------------------------- /assets/plan-changed-success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/request-submitted-success.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/security.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/select2-cf-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/select2-cf-white.png -------------------------------------------------------------------------------- /assets/select2-cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/select2-cf.png -------------------------------------------------------------------------------- /assets/select2x2-cf-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/select2x2-cf-white.png -------------------------------------------------------------------------------- /assets/select2x2-cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/select2x2-cf.png -------------------------------------------------------------------------------- /assets/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/spinner.gif -------------------------------------------------------------------------------- /assets/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/throbber.gif -------------------------------------------------------------------------------- /assets/vertical-range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/vertical-range.png -------------------------------------------------------------------------------- /assets/web-optimization.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/yjs-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/yjs-background.jpg -------------------------------------------------------------------------------- /assets/yjs-background_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/assets/yjs-background_2x.jpg -------------------------------------------------------------------------------- /config.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "debug": false, 3 | "featureManagerIsFullZoneProvisioningEnabled": true, 4 | "locale": "en" 5 | } -------------------------------------------------------------------------------- /fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /fonts/cloudflare-font.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/cloudflare-font.eot -------------------------------------------------------------------------------- /fonts/cloudflare-font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/cloudflare-font.ttf -------------------------------------------------------------------------------- /fonts/cloudflare-font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/cloudflare-font.woff -------------------------------------------------------------------------------- /fonts/fontawesome-cloudflare.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/fontawesome-cloudflare.eot -------------------------------------------------------------------------------- /fonts/fontawesome-cloudflare.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/fontawesome-cloudflare.ttf -------------------------------------------------------------------------------- /fonts/fontawesome-cloudflare.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/fontawesome-cloudflare.woff -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /fonts/opensans-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300.eot -------------------------------------------------------------------------------- /fonts/opensans-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300.ttf -------------------------------------------------------------------------------- /fonts/opensans-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300.woff -------------------------------------------------------------------------------- /fonts/opensans-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300.woff2 -------------------------------------------------------------------------------- /fonts/opensans-300i.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300i.eot -------------------------------------------------------------------------------- /fonts/opensans-300i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300i.ttf -------------------------------------------------------------------------------- /fonts/opensans-300i.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300i.woff -------------------------------------------------------------------------------- /fonts/opensans-300i.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-300i.woff2 -------------------------------------------------------------------------------- /fonts/opensans-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400.eot -------------------------------------------------------------------------------- /fonts/opensans-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400.ttf -------------------------------------------------------------------------------- /fonts/opensans-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400.woff -------------------------------------------------------------------------------- /fonts/opensans-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400.woff2 -------------------------------------------------------------------------------- /fonts/opensans-400i.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400i.eot -------------------------------------------------------------------------------- /fonts/opensans-400i.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400i.ttf -------------------------------------------------------------------------------- /fonts/opensans-400i.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400i.woff -------------------------------------------------------------------------------- /fonts/opensans-400i.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-400i.woff2 -------------------------------------------------------------------------------- /fonts/opensans-600.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-600.eot -------------------------------------------------------------------------------- /fonts/opensans-600.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-600.ttf -------------------------------------------------------------------------------- /fonts/opensans-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-600.woff -------------------------------------------------------------------------------- /fonts/opensans-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-600.woff2 -------------------------------------------------------------------------------- /fonts/opensans-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-700.eot -------------------------------------------------------------------------------- /fonts/opensans-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-700.ttf -------------------------------------------------------------------------------- /fonts/opensans-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-700.woff -------------------------------------------------------------------------------- /fonts/opensans-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/cloudflare-plugin-frontend/5abb17f31044680e6a0568726370e89efe68c68b/fonts/opensans-700.woff2 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare-plugin-frontend", 3 | "version": "3.2.4", 4 | "description": "A React/Redux frontend for Cloudflare plugins", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "NODE_ENV=development webpack --progress", 8 | "build:production": "NODE_ENV=production webpack --progress", 9 | "clean": "rm -rf .jest && rm -rf node_modules", 10 | "cover": "jest --coverage", 11 | "format": "eslint src/ --fix --ext js --ext json", 12 | "lint": "eslint src/ --ext js --ext json", 13 | "test": "jest", 14 | "update-snapshot": "jest --updateSnapshot" 15 | }, 16 | "jest": { 17 | "cacheDirectory": ".jest", 18 | "collectCoverageFrom": [ 19 | "src/**/*.{js,jsx}" 20 | ], 21 | "testMatch": [ 22 | "**/test/**/*.js" 23 | ], 24 | "testPathIgnorePatterns": [ 25 | "/node_modules/|_helpers" 26 | ] 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "ssh://git@github.com:cloudflare/cloudflare-plugin-frontend.git" 31 | }, 32 | "keywords": [ 33 | "cloudflare" 34 | ], 35 | "author": "Cloudflare", 36 | "license": "BSD-3-Clause", 37 | "devDependencies": { 38 | "@babel/core": "^7.14.0", 39 | "@babel/eslint-parser": "^7.13.14", 40 | "@babel/preset-env": "^7.14.1", 41 | "@babel/preset-react": "^7.13.13", 42 | "babel-jest": "^26.6.3", 43 | "babel-loader": "^8.2.2", 44 | "babel-polyfill": "^6.26.0", 45 | "c3": "^0.4.10", 46 | "cf-component-button": "3.0.0", 47 | "cf-component-card": "^1.1.0", 48 | "cf-component-checkbox": "^3.0.0", 49 | "cf-component-dropdown": "^2.0.0", 50 | "cf-component-flex": "^2.0.0", 51 | "cf-component-form": "^3.0.3", 52 | "cf-component-heading": "^2.0.0", 53 | "cf-component-input": "^3.0.0", 54 | "cf-component-layout": "^1.2.1", 55 | "cf-component-link": "^4.0.0", 56 | "cf-component-list": "^2.0.0", 57 | "cf-component-loading": "^2.0.0", 58 | "cf-component-modal": "5.3.0", 59 | "cf-component-notifications": "^2.0.0", 60 | "cf-component-radio": "^2.0.0", 61 | "cf-component-select": "^2.5.0", 62 | "cf-component-table": "^2.1.0", 63 | "cf-component-tabs": "^5.2.0", 64 | "cf-component-text": "^2.0.0", 65 | "cf-component-textarea": "^3.0.1", 66 | "cf-component-toggle": "^2.0.1", 67 | "cf-util-http": "^2.0.1", 68 | "d3-format": "^1.0.2", 69 | "eslint": "^7.25.0", 70 | "eslint-import-resolver-webpack": "^0.13.0", 71 | "eslint-loader": "^4", 72 | "eslint-plugin-cflint": "^1.0.0", 73 | "eslint-plugin-compat": "^3", 74 | "eslint-plugin-import": "^2.3.0", 75 | "eslint-plugin-json": "^1.2.0", 76 | "eslint-plugin-mocha": "^8", 77 | "eslint-plugin-prettier": "^3", 78 | "eslint-plugin-react": "^7.1.0", 79 | "history": "3.2.1", 80 | "intl": "^1.1.0", 81 | "jest": "^26", 82 | "lodash": "^3.10.1", 83 | "markdown-it": "^8.3.1", 84 | "normalizr": "^2.0.0", 85 | "prettier": "^1.2.2", 86 | "react": "^15.5.4", 87 | "react-dom": "^15.5.4", 88 | "react-gateway": "^2.0.4", 89 | "react-intl": "^2.3.0", 90 | "react-redux": "^5.0.4", 91 | "react-router": "3.0.5", 92 | "react-router-redux": "4.0.7", 93 | "react-test-renderer": "^15.4.2", 94 | "redux": "^3.0.4", 95 | "redux-logger": "^2.6.1", 96 | "redux-mock-store": "^1.2.3", 97 | "redux-thunk": "^1.0.0", 98 | "webpack": "^4" 99 | }, 100 | "dependencies": { 101 | "@cloudflare/component-icon": "^5.15.2", 102 | "cf-component-box": "^2.2.1", 103 | "cf-style-provider": "^1.5.0", 104 | "prop-types": "^15.5.8", 105 | "webpack-cli": "^4.6.0" 106 | }, 107 | "browserslist": "> 1%" 108 | } 109 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'none', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true 6 | }; 7 | -------------------------------------------------------------------------------- /src/actions/activeZone.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | import { asyncDNSRecordFetchList } from './zoneDnsRecords'; 3 | import { asyncZoneEntitlements } from './zoneEntitlements'; 4 | import { asyncZoneFetchSettings } from './zoneSettings'; 5 | import { asyncPluginFetchSettings } from './pluginSettings'; 6 | 7 | export function zoneSetActiveZone(zone) { 8 | return { 9 | type: ActionTypes.ZONES_SET_ACTIVE_ZONE, 10 | zone 11 | }; 12 | } 13 | 14 | export function asyncZoneSetActiveZone(zone) { 15 | return dispatch => { 16 | dispatch(zoneSetActiveZone(zone)); 17 | if (typeof zone.id !== 'undefined') { 18 | dispatch(asyncDNSRecordFetchList(zone.id)); 19 | dispatch(asyncPluginFetchSettings(zone.id)); 20 | dispatch(asyncZoneFetchSettings(zone.id)); 21 | dispatch(asyncZoneEntitlements(zone.id)); 22 | } 23 | }; 24 | } 25 | 26 | export function zoneSetActiveZoneIfEmpty(zone) { 27 | return (dispatch, getState) => { 28 | if (getState().activeZone.name === '') { 29 | dispatch(asyncZoneSetActiveZone(zone)); 30 | } 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/actions/app.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | export function applicationInit() { 4 | return { 5 | type: ActionTypes.APPLICATION_INIT 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /src/actions/config.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | import { asyncIntlFetchTranslations } from './intl'; 3 | import { notificationAddError } from './notifications'; 4 | import { isLoggedIn, getEmail } from '../utils/Auth/Auth'; 5 | import { configGet } from '../utils/PluginAPI/PluginAPI'; 6 | import { asyncUserLoginSuccess } from '../actions/user'; 7 | import { ABSOLUTE_URL_BASE_KEY } from '../reducers/config'; 8 | 9 | export function configFetch() { 10 | return { 11 | type: ActionTypes.CONFIG_FETCH 12 | }; 13 | } 14 | 15 | export function configFetchSuccess() { 16 | return { 17 | type: ActionTypes.CONFIG_FETCH_SUCCESS 18 | }; 19 | } 20 | 21 | export function configFetchError() { 22 | return { 23 | type: ActionTypes.CONFIG_FETCH_ERROR 24 | }; 25 | } 26 | 27 | export function asyncConfigInit() { 28 | return dispatch => { 29 | /* 30 | * 1. Fetch config.js 31 | * 2. Fetch userConfig.js (which may not exist) 32 | * 3. Fetch translations with the language from the config. 33 | */ 34 | dispatch(asyncConfigFetch()); 35 | if (typeof window.absoluteUrlBase !== 'undefined') { 36 | /* 37 | * Some integrations don't work with relative paths because the URL doesn't match 38 | * the actual file path, this function allows integrations to configure a base absolute 39 | * url path to be used in components/Image. absoluteBaseUrl should be defined globally 40 | * on the page where the SPA is loaded. 41 | */ 42 | dispatch( 43 | configUpdateByKey(ABSOLUTE_URL_BASE_KEY, window.absoluteUrlBase) 44 | ); 45 | } 46 | //log user in if their email is in local storage 47 | if (isLoggedIn()) { 48 | dispatch(asyncUserLoginSuccess(getEmail())); 49 | } 50 | }; 51 | } 52 | 53 | export function asyncConfigFetch() { 54 | return dispatch => { 55 | dispatch(configFetch()); 56 | configGet(function(error, response) { 57 | if (response) { 58 | dispatch(configFetchSuccess()); 59 | try { 60 | let userConfig = JSON.parse(response.text).result; 61 | Object.keys(userConfig).map(function(key) { 62 | dispatch(configUpdateByKey(key, userConfig[key])); 63 | }); 64 | } catch (e) { 65 | dispatch(notificationAddError(`/config - ${e.message}`)); 66 | } 67 | dispatch(asyncIntlFetchTranslations()); 68 | } else { 69 | dispatch(configFetchError()); 70 | } 71 | }); 72 | }; 73 | } 74 | 75 | export function configUpdateByKey(key, value) { 76 | return { 77 | type: ActionTypes.CONFIG_UPDATE_BY_KEY, 78 | key, 79 | value 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/actions/intl.js: -------------------------------------------------------------------------------- 1 | import http from 'cf-util-http'; 2 | 3 | import * as ActionTypes from '../constants/ActionTypes'; 4 | import { applicationInit } from './app'; 5 | import { notificationAddError } from './notifications'; 6 | import { getConfigValue } from '../selectors/config.js'; 7 | import { getLocale } from '../selectors/intl.js'; 8 | 9 | export function intlFetchTranslations() { 10 | return { 11 | type: ActionTypes.INTL_FETCH_TRANSLATIONS 12 | }; 13 | } 14 | 15 | export function intlFetchTranslationsSuccess(locale, translations) { 16 | return { 17 | type: ActionTypes.INTL_FETCH_TRANSLATIONS_SUCCESS, 18 | locale, 19 | translations 20 | }; 21 | } 22 | 23 | export function intlFetchTranslationsError(error) { 24 | return { 25 | type: ActionTypes.INTL_FETCH_TRANSLATIONS_ERROR, 26 | error 27 | }; 28 | } 29 | 30 | export function asyncIntlFetchTranslations() { 31 | return (dispatch, getState) => { 32 | dispatch(intlFetchTranslations()); 33 | let locale = getConfigValue(getState().config, 'locale'); 34 | let currentLocale = getLocale(getState()); 35 | if (typeof locale != 'undefined' && locale != currentLocale) { 36 | let opts = {}; 37 | opts.headers = { Accept: 'application/javascript' }; 38 | http.get('./lang/' + locale + '.js', opts, function(error, response) { 39 | if (response) { 40 | let translations = JSON.parse(response.text); 41 | dispatch(intlFetchTranslationsSuccess(locale, translations)); 42 | dispatch(applicationInit()); 43 | } else { 44 | dispatch(notificationAddError(error)); 45 | } 46 | }); 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/actions/notifications.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | import { getZoneSettingsValueForZoneId } from '../selectors/zoneSettings'; 3 | import _ from 'lodash'; 4 | 5 | export function notificationAdd( 6 | level, 7 | message, 8 | localized = false, 9 | persistant = false, 10 | delay = 5000 11 | ) { 12 | return { 13 | type: ActionTypes.NOTIFICATION_ADD, 14 | level, 15 | message, 16 | localized, 17 | persistant, 18 | delay 19 | }; 20 | } 21 | 22 | export function notificationAddSuccess( 23 | message, 24 | localized = false, 25 | persistant = false, 26 | delay = 5000 27 | ) { 28 | return notificationAdd('success', message, localized, persistant, delay); 29 | } 30 | 31 | export function notificationAddInfo( 32 | message, 33 | localized = false, 34 | persistant = false, 35 | delay = 5000 36 | ) { 37 | return notificationAdd('info', message, localized, persistant, delay); 38 | } 39 | 40 | export function notificationAddWarning( 41 | message, 42 | localized = false, 43 | persistant = false, 44 | delay = 5000 45 | ) { 46 | return notificationAdd('warning', message, localized, persistant, delay); 47 | } 48 | 49 | export function notificationAddError( 50 | message, 51 | localized = false, 52 | persistant = false, 53 | delay = 5000 54 | ) { 55 | return notificationAdd('error', message, localized, persistant, delay); 56 | } 57 | 58 | export function notificationRemove(key) { 59 | return { 60 | type: ActionTypes.NOTIFICATION_REMOVE, 61 | key 62 | }; 63 | } 64 | 65 | export function notificationAddClientAPIError(errorAction, errorMessage) { 66 | return dispatch => { 67 | dispatch(errorAction); 68 | if (typeof errorMessage === 'string') { 69 | dispatch(notificationAddError(errorMessage)); 70 | } else { 71 | errorMessage.body.errors.forEach(function(error) { 72 | dispatch(notificationAddError(error.message)); 73 | }); 74 | } 75 | }; 76 | } 77 | 78 | export function notificationAddHostAPIError(errorAction, errorMessage) { 79 | return dispatch => { 80 | dispatch(errorAction); 81 | if (typeof errorMessage === 'string') { 82 | dispatch(notificationAddError(errorMessage)); 83 | } else { 84 | dispatch(notificationAddError(errorMessage.body.msg)); 85 | } 86 | }; 87 | } 88 | 89 | export function notificationHandleDevelopmentMode(activeZoneId) { 90 | return (dispatch, getState) => { 91 | let notifications = getState().notifications; 92 | let developmentModeValue = getZoneSettingsValueForZoneId( 93 | activeZoneId, 94 | 'development_mode', 95 | getState() 96 | ); 97 | 98 | var notificationKey = null; 99 | _.forEach(notifications, function(notification) { 100 | if ( 101 | notification['level'] === 'warning' && 102 | notification['message'] === 'warning.developmentmode' 103 | ) { 104 | notificationKey = notification['key']; 105 | } 106 | }); 107 | 108 | if (developmentModeValue === 'on' && notificationKey === null) { 109 | dispatch(notificationAddWarning('warning.developmentmode', true, true)); 110 | } 111 | 112 | if (developmentModeValue === 'off' && notificationKey !== null) { 113 | dispatch(notificationRemove(notificationKey)); 114 | } 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /src/actions/pluginSettings.js: -------------------------------------------------------------------------------- 1 | import { 2 | pluginSettingListGet, 3 | pluginSettingPatch 4 | } from '../utils/PluginAPI/PluginAPI'; 5 | import { 6 | notificationAddSuccess, 7 | notificationAddClientAPIError 8 | } from './notifications'; 9 | import * as ActionTypes from '../constants/ActionTypes'; 10 | 11 | export function pluginFetchSettings() { 12 | return { 13 | type: ActionTypes.PLUGIN_SETTINGS_FETCH 14 | }; 15 | } 16 | 17 | export function pluginFetchSettingsSuccess(zoneId, setting) { 18 | return { 19 | type: ActionTypes.PLUGIN_SETTINGS_FETCH_SUCCESS, 20 | zoneId, 21 | setting 22 | }; 23 | } 24 | 25 | export function pluginFetchSettingsError() { 26 | return { 27 | type: ActionTypes.PLUGIN_SETTINGS_FETCH_ERROR 28 | }; 29 | } 30 | 31 | export function pluginUpdateSetting(zoneId, setting) { 32 | return { 33 | type: ActionTypes.PLUGIN_SETTING_UPDATE, 34 | zoneId, 35 | setting 36 | }; 37 | } 38 | 39 | export function pluginUpdateSettingSuccess(zoneId, setting) { 40 | return { 41 | type: ActionTypes.PLUGIN_SETTING_UPDATE_SUCCESS, 42 | zoneId, 43 | setting 44 | }; 45 | } 46 | 47 | export function pluginUpdateSettingError(zoneId, setting) { 48 | return { 49 | type: ActionTypes.PLUGIN_SETTING_UPDATE_ERROR, 50 | zoneId, 51 | setting 52 | }; 53 | } 54 | 55 | export function asyncPluginFetchSettings(zoneId) { 56 | return dispatch => { 57 | dispatch(pluginFetchSettings()); 58 | pluginSettingListGet({ zoneId: zoneId }, function(error, response) { 59 | if (response) { 60 | dispatch(pluginFetchSettingsSuccess(zoneId, response.body.result)); 61 | } else { 62 | dispatch( 63 | notificationAddClientAPIError(pluginFetchSettingsError(), error) 64 | ); 65 | } 66 | }); 67 | }; 68 | } 69 | 70 | export function asyncPluginUpdateSetting(settingName, zoneId, value) { 71 | return (dispatch, getState) => { 72 | let oldSetting = getState().pluginSettings.entities[zoneId][settingName]; 73 | 74 | dispatch(pluginUpdateSetting(zoneId, { id: settingName, value: value })); 75 | pluginSettingPatch(zoneId, settingName, value, function(error, response) { 76 | if (response) { 77 | dispatch(pluginUpdateSettingSuccess(zoneId, response.body.result)); 78 | 79 | if (settingName == 'default_settings') { 80 | dispatch( 81 | notificationAddSuccess( 82 | 'container.applydefaultsettingscard.success', 83 | true 84 | ) 85 | ); 86 | } 87 | } else { 88 | dispatch( 89 | notificationAddClientAPIError( 90 | pluginUpdateSettingError(zoneId, oldSetting), 91 | error 92 | ) 93 | ); 94 | } 95 | }); 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /src/actions/user.js: -------------------------------------------------------------------------------- 1 | import { push } from 'react-router-redux'; 2 | import { userAuth, userCreate } from '../utils/CFHostAPI/CFHostAPI'; 3 | import { pluginAccountPost } from '../utils/PluginAPI/PluginAPI'; 4 | import { 5 | notificationAddHostAPIError, 6 | notificationAddClientAPIError 7 | } from './notifications'; 8 | import * as ActionTypes from '../constants/ActionTypes'; 9 | import * as UrlPaths from '../constants/UrlPaths'; 10 | import { getConfigValue } from '../selectors/config'; 11 | 12 | import { asyncFetchZones } from './zones'; 13 | 14 | export function userLogin() { 15 | return { 16 | type: ActionTypes.USER_LOGIN 17 | }; 18 | } 19 | 20 | /* 21 | * always call asyncUserLoginSuccess instead of userLoginSuccess 22 | * this is how we trigger GETs that need to occur after a successful login. 23 | * The user can also be logged in from localStorage automatically when the app loads 24 | * which is why this logic doens't live in asyncLogin(). 25 | */ 26 | export function userLoginSuccess(email) { 27 | return { 28 | type: ActionTypes.USER_LOGIN_SUCCESS, 29 | email 30 | }; 31 | } 32 | 33 | export function asyncUserLoginSuccess(email) { 34 | return (dispatch, getState) => { 35 | dispatch(userLoginSuccess(email)); 36 | dispatch(asyncFetchZones()); 37 | let route = UrlPaths.HOME_PAGE; 38 | if (getConfigValue(getState().config, 'integrationName') === 'cpanel') { 39 | route = UrlPaths.DOMAINS_OVERVIEW_PAGE; 40 | } 41 | dispatch(push(route)); 42 | }; 43 | } 44 | 45 | export function userLoginError(error) { 46 | return { 47 | type: ActionTypes.USER_LOGIN_ERROR, 48 | error 49 | }; 50 | } 51 | 52 | export function asyncLogin(email, password) { 53 | return dispatch => { 54 | dispatch(userLogin()); 55 | userAuth({ cloudflare_email: email, cloudflare_pass: password }, function( 56 | error, 57 | response 58 | ) { 59 | if (response) { 60 | dispatch( 61 | asyncUserLoginSuccess(response.body.response.cloudflare_email) 62 | ); 63 | } else { 64 | dispatch(notificationAddHostAPIError(userLoginError(), error)); 65 | } 66 | }); 67 | }; 68 | } 69 | 70 | export function asyncAPILogin(email, apiKey) { 71 | return dispatch => { 72 | dispatch(userLogin()); 73 | pluginAccountPost(email, apiKey, function(error, response) { 74 | if (response) { 75 | dispatch(asyncUserLoginSuccess(email)); 76 | } else { 77 | dispatch(userLoginError()); 78 | dispatch(notificationAddClientAPIError(userLoginError(), error)); 79 | } 80 | }); 81 | }; 82 | } 83 | 84 | export function userLogout() { 85 | return { 86 | type: ActionTypes.USER_LOGOUT 87 | }; 88 | } 89 | 90 | export function userSignup() { 91 | return { 92 | type: ActionTypes.USER_SIGNUP 93 | }; 94 | } 95 | 96 | export function userSignupSuccess() { 97 | return { 98 | type: ActionTypes.USER_SIGNUP_SUCCESS 99 | }; 100 | } 101 | 102 | export function userSignupError() { 103 | return { 104 | type: ActionTypes.USER_SIGNUP_ERROR 105 | }; 106 | } 107 | 108 | export function asyncUserSignup(email, password) { 109 | return dispatch => { 110 | dispatch(userSignup()); 111 | userCreate({ cloudflare_email: email, cloudflare_pass: password }, function( 112 | error, 113 | response 114 | ) { 115 | if (response) { 116 | dispatch(userSignupSuccess()); 117 | dispatch(asyncLogin(email, password)); 118 | } else { 119 | dispatch(notificationAddHostAPIError(userSignupError(), error)); 120 | } 121 | }); 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /src/actions/zoneDnsRecords.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | import { 3 | zoneDNSRecordGetAll, 4 | zoneDNSRecordPostNew, 5 | zoneDNSRecordPatch 6 | } from '../utils/CFClientV4API/CFClientV4API'; 7 | import { notificationAddClientAPIError } from './notifications'; 8 | 9 | export function dnsRecordClearAll(zoneId) { 10 | return { 11 | type: ActionTypes.DNS_RECORD_CLEAR_ALL, 12 | zoneId 13 | }; 14 | } 15 | 16 | export function dnsRecordCreate(name) { 17 | return { 18 | type: ActionTypes.DNS_RECORD_CREATE, 19 | name 20 | }; 21 | } 22 | 23 | export function dnsRecordCreateSuccess(zoneId, dnsRecord) { 24 | return { 25 | type: ActionTypes.DNS_RECORD_CREATE_SUCCESS, 26 | zoneId, 27 | dnsRecord 28 | }; 29 | } 30 | 31 | export function dnsRecordCreateError() { 32 | return { 33 | type: ActionTypes.DNS_RECORD_CREATE_ERROR 34 | }; 35 | } 36 | 37 | export function asyncDNSRecordCreate(zoneId, type, name, content) { 38 | return dispatch => { 39 | dispatch(dnsRecordCreate(name)); 40 | zoneDNSRecordPostNew( 41 | { zoneId: zoneId, type: type, name: name, content: content }, 42 | function(error, response) { 43 | if (response) { 44 | dispatch(dnsRecordCreateSuccess(zoneId, response.body.result)); 45 | //CloudFlare defaults new records with proxied = false. 46 | dispatch(asyncDNSRecordUpdate(zoneId, response.body.result, true)); 47 | } else { 48 | dispatch( 49 | notificationAddClientAPIError(dnsRecordCreateError(), error) 50 | ); 51 | } 52 | } 53 | ); 54 | }; 55 | } 56 | 57 | export function dnsRecordFetchList() { 58 | return { 59 | type: ActionTypes.DNS_RECORD_FETCH_LIST 60 | }; 61 | } 62 | 63 | export function dnsRecordFetchListSuccess(zoneId, dnsRecords) { 64 | return { 65 | type: ActionTypes.DNS_RECORD_FETCH_LIST_SUCCESS, 66 | zoneId, 67 | dnsRecords 68 | }; 69 | } 70 | 71 | export function dnsRecordFetchListError() { 72 | return { 73 | type: ActionTypes.DNS_RECORD_FETCH_LIST_ERROR 74 | }; 75 | } 76 | 77 | export function asyncDNSRecordFetchList(zoneId) { 78 | return dispatch => { 79 | dispatch(dnsRecordFetchList()); 80 | zoneDNSRecordGetAll(zoneId, function(error, response) { 81 | if (response) { 82 | dispatch(dnsRecordFetchListSuccess(zoneId, response.body.result)); 83 | } else { 84 | dispatch( 85 | notificationAddClientAPIError(dnsRecordFetchListError(), error) 86 | ); 87 | } 88 | }); 89 | }; 90 | } 91 | 92 | export function dnsRecordUpdate(name) { 93 | return { 94 | type: ActionTypes.DNS_RECORD_UPDATE, 95 | name 96 | }; 97 | } 98 | 99 | export function dnsRecordUpdateSuccess(zoneId, dnsRecord) { 100 | return { 101 | type: ActionTypes.DNS_RECORD_UPDATE_SUCCESS, 102 | zoneId, 103 | dnsRecord 104 | }; 105 | } 106 | 107 | export function dnsRecordUpdateError() { 108 | return { 109 | type: ActionTypes.DNS_RECORD_UPDATE_ERROR 110 | }; 111 | } 112 | 113 | export function asyncDNSRecordUpdate(zoneId, dnsRecord, proxied) { 114 | return dispatch => { 115 | dispatch(dnsRecordUpdate(dnsRecord.name)); 116 | zoneDNSRecordPatch( 117 | { zoneId: zoneId, dnsRecordId: dnsRecord.id, proxied: proxied }, 118 | function(error, response) { 119 | if (response) { 120 | dispatch(dnsRecordUpdateSuccess(zoneId, response.body.result)); 121 | } else { 122 | dispatch( 123 | notificationAddClientAPIError(dnsRecordUpdateError(), error) 124 | ); 125 | } 126 | } 127 | ); 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /src/actions/zoneEntitlements.js: -------------------------------------------------------------------------------- 1 | import { zoneGetEntitlements } from '../utils/CFClientV4API/CFClientV4API'; 2 | import { notificationAddClientAPIError } from './notifications'; 3 | import * as ActionTypes from '../constants/ActionTypes'; 4 | 5 | export function zoneEntitlements() { 6 | return { 7 | type: ActionTypes.ZONE_ENTITLEMENTS 8 | }; 9 | } 10 | 11 | export function zoneEntitlementsSuccess(zoneId, zoneEntitlements) { 12 | return { 13 | type: ActionTypes.ZONE_ENTITLEMENTS_SUCCESS, 14 | zoneId, 15 | zoneEntitlements 16 | }; 17 | } 18 | 19 | export function zoneEntitlementsError() { 20 | return { 21 | type: ActionTypes.ZONE_ENTITLEMENTS_ERROR 22 | }; 23 | } 24 | 25 | export function asyncZoneEntitlements(zoneId) { 26 | return dispatch => { 27 | dispatch(zoneEntitlements()); 28 | zoneGetEntitlements(zoneId, function(error, response) { 29 | if (response) { 30 | dispatch(zoneEntitlementsSuccess(zoneId, response.body.result)); 31 | } else { 32 | dispatch(notificationAddClientAPIError(zoneEntitlementsError(), error)); 33 | } 34 | }); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/actions/zonePurgeCache.js: -------------------------------------------------------------------------------- 1 | import { zonePurgeCache as v4ZonePurgeCache } from '../utils/CFClientV4API/CFClientV4API'; 2 | import { 3 | notificationAddClientAPIError, 4 | notificationAddSuccess 5 | } from './notifications'; 6 | import * as ActionTypes from '../constants/ActionTypes'; 7 | 8 | export function zonePurgeCache() { 9 | return { 10 | type: ActionTypes.ZONE_PURGE_CACHE 11 | }; 12 | } 13 | 14 | export function zonePurgeCacheSuccess() { 15 | return { 16 | type: ActionTypes.ZONE_PURGE_CACHE_SUCCESS 17 | }; 18 | } 19 | 20 | export function zonePurgeCacheError() { 21 | return { 22 | type: ActionTypes.ZONE_PURGE_CACHE_ERROR 23 | }; 24 | } 25 | 26 | export function asyncZonePurgeCacheIndividualFiles(zoneId, files) { 27 | return dispatch => { 28 | dispatch(zonePurgeCache()); 29 | 30 | // Get an unstructured string like " \nhttp://example.com\n\n \n http://example.com/hey \n " 31 | // Return ["http://example.com", "http://example.com/hey"] 32 | var formatedFiles = files.replace(/^\s+|\s+$/g, '').split(/\s+/); 33 | 34 | v4ZonePurgeCache({ zoneId: zoneId, files: formatedFiles }, function( 35 | error, 36 | response 37 | ) { 38 | if (response) { 39 | dispatch(zonePurgeCacheSuccess()); 40 | dispatch( 41 | notificationAddSuccess('container.purgeCacheCard.success', true) 42 | ); 43 | } else { 44 | dispatch(notificationAddClientAPIError(zonePurgeCacheError(), error)); 45 | } 46 | }); 47 | }; 48 | } 49 | 50 | export function asyncZonePurgeCacheEverything(zoneId) { 51 | return dispatch => { 52 | dispatch(zonePurgeCache()); 53 | v4ZonePurgeCache({ zoneId: zoneId, purge_everything: true }, function( 54 | error, 55 | response 56 | ) { 57 | if (response) { 58 | dispatch(zonePurgeCacheSuccess()); 59 | dispatch( 60 | notificationAddSuccess('container.purgeCacheByURLCard.success', true) 61 | ); 62 | } else { 63 | dispatch(notificationAddClientAPIError(zonePurgeCacheError(), error)); 64 | } 65 | }); 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /src/actions/zoneSettings.js: -------------------------------------------------------------------------------- 1 | import { 2 | zoneGetSettings, 3 | zonePatchSetting 4 | } from '../utils/CFClientV4API/CFClientV4API'; 5 | import { 6 | notificationAddClientAPIError, 7 | notificationHandleDevelopmentMode 8 | } from './notifications'; 9 | import * as ActionTypes from '../constants/ActionTypes'; 10 | 11 | export function zoneFetchSettings() { 12 | return { 13 | type: ActionTypes.ZONE_FETCH_SETTINGS 14 | }; 15 | } 16 | 17 | export function zoneFetchSettingsSuccess(zoneId, zoneSettings) { 18 | return { 19 | type: ActionTypes.ZONE_FETCH_SETTINGS_SUCCESS, 20 | zoneId, 21 | zoneSettings 22 | }; 23 | } 24 | 25 | export function zoneFetchSettingsError() { 26 | return { 27 | type: ActionTypes.ZONE_FETCH_SETTINGS_ERROR 28 | }; 29 | } 30 | 31 | export function asyncZoneFetchSettings(zoneId) { 32 | return dispatch => { 33 | dispatch(zoneFetchSettings()); 34 | zoneGetSettings(zoneId, function(error, response) { 35 | if (response) { 36 | dispatch(zoneFetchSettingsSuccess(zoneId, response.body.result)); 37 | 38 | // Lastly check if development mode value and add/remove notification 39 | dispatch(notificationHandleDevelopmentMode(zoneId)); 40 | } else { 41 | dispatch( 42 | notificationAddClientAPIError(zoneFetchSettingsError(), error) 43 | ); 44 | } 45 | }); 46 | }; 47 | } 48 | 49 | export function zoneUpdateSetting(zoneId, setting) { 50 | return { 51 | type: ActionTypes.ZONE_UPDATE_SETTING, 52 | zoneId, 53 | setting 54 | }; 55 | } 56 | 57 | export function zoneUpdateSettingSuccess(zoneId, setting) { 58 | return { 59 | type: ActionTypes.ZONE_UPDATE_SETTING_SUCCESS, 60 | zoneId, 61 | setting 62 | }; 63 | } 64 | 65 | export function zoneUpdateSettingError(zoneId, setting) { 66 | return { 67 | type: ActionTypes.ZONE_UPDATE_SETTING_ERROR, 68 | zoneId, 69 | setting 70 | }; 71 | } 72 | 73 | export function asyncZoneUpdateSetting(settingName, zoneId, value) { 74 | return (dispatch, getState) => { 75 | let oldSetting = getState().zoneSettings.entities[zoneId][settingName]; 76 | 77 | dispatch(zoneUpdateSetting(zoneId, { id: settingName, value: value })); 78 | zonePatchSetting(settingName, zoneId, value, function(error, response) { 79 | if (response) { 80 | dispatch(zoneUpdateSettingSuccess(zoneId, response.body.result)); 81 | 82 | // Lastly check if development mode value and add/remove notification 83 | dispatch(notificationHandleDevelopmentMode(zoneId)); 84 | } else { 85 | dispatch( 86 | notificationAddClientAPIError( 87 | zoneUpdateSettingError(zoneId, oldSetting), 88 | error 89 | ) 90 | ); 91 | } 92 | }); 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/actions/zones.js: -------------------------------------------------------------------------------- 1 | import { 2 | zoneGetAll, 3 | zoneDeleteZone 4 | } from '../utils/CFClientV4API/CFClientV4API'; 5 | import { notificationAddClientAPIError } from './notifications'; 6 | import * as ActionTypes from '../constants/ActionTypes'; 7 | import { zoneSetActiveZoneIfEmpty } from './activeZone'; 8 | import { dnsRecordClearAll } from './zoneDnsRecords'; 9 | 10 | export function zoneDelete() { 11 | return { 12 | type: ActionTypes.ZONES_DELETE_ZONE 13 | }; 14 | } 15 | 16 | export function zoneDeleteSuccess() { 17 | return { 18 | type: ActionTypes.ZONES_DELETE_ZONE_SUCCESS 19 | }; 20 | } 21 | 22 | export function zoneDeleteError(error) { 23 | return { 24 | type: ActionTypes.ZONES_DELETE_ZONE_ERROR, 25 | error 26 | }; 27 | } 28 | 29 | export function asyncZoneDelete(zoneId) { 30 | return dispatch => { 31 | dispatch(zoneDelete(zoneId)); 32 | 33 | zoneDeleteZone(zoneId, function(error, response) { 34 | if (response) { 35 | dispatch(zoneDeleteSuccess()); 36 | dispatch(dnsRecordClearAll(zoneId)); 37 | //after we provision a cname refresh the zone list 38 | dispatch(asyncFetchZones()); 39 | } else { 40 | dispatch(notificationAddClientAPIError(zoneDeleteError(), error)); 41 | } 42 | }); 43 | }; 44 | } 45 | 46 | export function zoneFetch() { 47 | return { 48 | type: ActionTypes.ZONES_FETCH 49 | }; 50 | } 51 | 52 | export function zoneFetchSuccess(zoneList) { 53 | return { 54 | type: ActionTypes.ZONES_FETCH_SUCCESS, 55 | zoneList 56 | }; 57 | } 58 | 59 | export function zoneFetchError(error) { 60 | return { 61 | type: ActionTypes.ZONES_FETCH_ERROR, 62 | error 63 | }; 64 | } 65 | 66 | export function asyncFetchZones() { 67 | return dispatch => { 68 | dispatch(zoneFetch()); 69 | 70 | zoneGetAll(function(error, response) { 71 | if (response) { 72 | dispatch(zoneFetchSuccess(response.body.result)); 73 | const activeZone = response.body.result.find( 74 | zone => zone.status === 'active' 75 | ); 76 | if (activeZone) { 77 | dispatch(zoneSetActiveZoneIfEmpty(activeZone)); 78 | } else if (response.body.result[0]) { 79 | dispatch(zoneSetActiveZoneIfEmpty(response.body.result[0])); 80 | } 81 | } else { 82 | dispatch(notificationAddClientAPIError(zoneFetchError(), error)); 83 | } 84 | }); 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/components/AppNavigationLiNode/AppNavigationLiNode.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { FormattedMessage } from 'react-intl'; 4 | 5 | import Link from 'cf-component-link'; 6 | 7 | export default class AppNavigationLiNode extends Component { 8 | render() { 9 | return ( 10 |
  • 11 | 12 | 13 | 20 | {this.props.children} 21 | 22 | 23 | 24 | 25 | 26 | 27 |
  • 28 | ); 29 | } 30 | } 31 | 32 | AppNavigationLiNode.propTypes = { 33 | onClick: PropTypes.func.isRequired, 34 | title: PropTypes.string.isRequired, 35 | children: PropTypes.node 36 | }; 37 | -------------------------------------------------------------------------------- /src/components/BenefitsFeature/BenefitsFeature.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class BenefitsFeature extends Component { 5 | render() { 6 | /* 7 | * These styles are stolen from the marketing site and aren't in our CSS 8 | */ 9 | let divStyles = { 10 | padding: '30px 15px 30px 15px' 11 | }; 12 | let iconStyles = { 13 | display: 'block', 14 | width: '75px', 15 | height: 'auto', 16 | maxWidth: '100px', 17 | margin: '0 auto' 18 | }; 19 | let largeLinkStyles = { 20 | padding: '20px 0 0 0', 21 | textAlign: 'center', 22 | fontSize: '16px', 23 | color: '#333333', 24 | width: '100%', 25 | display: 'block' 26 | }; 27 | let columnPStyles = { 28 | padding: '10px 0 0 0', 29 | fontSize: '12px', 30 | textAlign: 'center', 31 | color: '#9A9D9E' 32 | }; 33 | 34 | return ( 35 |
    36 | 37 | 38 | {this.props.title} 39 | 40 |

    {this.props.description}

    41 |
    42 | ); 43 | } 44 | } 45 | 46 | BenefitsFeature.propTypes = { 47 | imgSrc: PropTypes.string.isRequired, 48 | title: PropTypes.string.isRequired, 49 | description: PropTypes.string.isRequired 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/C3Wrapper/C3Wrapper.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import c3 from 'c3'; 4 | 5 | export default class C3Wrapper extends Component { 6 | updateC3(props) { 7 | props.config.bindto = this._container; 8 | if (this._chart) { 9 | this._chart.destroy(); 10 | } 11 | this._chart = c3.generate(props.config); 12 | } 13 | 14 | componentDidMount() { 15 | this.updateC3(this.props); 16 | } 17 | 18 | componentWillReceiveProps(props) { 19 | this.updateC3(props); 20 | } 21 | 22 | componentWillUnmount() { 23 | this._chart.destroy(); 24 | } 25 | 26 | render() { 27 | return
    (this._container = chart)} />; 28 | } 29 | } 30 | 31 | C3Wrapper.propTypes = { 32 | config: PropTypes.object.isRequired 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/CustomCardControl/CustomCardControl.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { FormattedMessage, injectIntl } from 'react-intl'; 5 | import { CardControl } from 'cf-component-card'; 6 | import { Button } from 'cf-component-button'; 7 | import { 8 | planNeedsUpgrade, 9 | getLocalizedPlanId, 10 | FREE_PLAN, 11 | getPlanUpdateParam 12 | } from '../../constants/Plans.js'; 13 | import { getConfigValue } from '../../selectors/config.js'; 14 | import { openWindow720x720 } from '../../utils/utils.js'; 15 | 16 | class CustomCardControl extends Component { 17 | render() { 18 | let { 19 | activeZone, 20 | purchaseSubscription, 21 | purchaseSubscriptionPath 22 | } = this.props; 23 | 24 | if (purchaseSubscription && purchaseSubscriptionPath) { 25 | let upgradeLink = `https://dash.cloudflare.com/${activeZone.account.id}/${activeZone.name}${purchaseSubscriptionPath}`; 26 | return ( 27 | 28 | {purchaseSubscription ? ( 29 | 35 | ) : ( 36 | this.props.children 37 | )} 38 | 39 | ); 40 | } 41 | 42 | var currentPlan = this.props.hasOwnProperty('currentPlan') 43 | ? this.props.currentPlan 44 | : FREE_PLAN; 45 | var minimumPlan = this.props.hasOwnProperty('minimumPlan') 46 | ? this.props.minimumPlan 47 | : FREE_PLAN; 48 | var needToUpgrade = planNeedsUpgrade(currentPlan, minimumPlan); 49 | var localizedPlanId = getLocalizedPlanId(minimumPlan); 50 | 51 | // Upgrade Plan Page can get the following parameters 52 | // /a/upgrade-plan?pt=[f|p|b|] 53 | let upgradeLink = `https://dash.cloudflare.com?to=/:account/${activeZone.name}/update-plan`; 54 | upgradeLink += '&pt=' + getPlanUpdateParam(minimumPlan); 55 | 56 | return ( 57 | 58 | {needToUpgrade ? ( 59 | 66 | ) : ( 67 | this.props.children 68 | )} 69 | 70 | ); 71 | } 72 | } 73 | 74 | CustomCardControl.propTypes = { 75 | name: PropTypes.string, 76 | indentifier: PropTypes.string.isRequired, 77 | integrationName: PropTypes.string, 78 | activeZone: PropTypes.object.isRequired, 79 | currentPlan: PropTypes.string, 80 | minimumPlan: PropTypes.string, 81 | purchaseSubscription: PropTypes.bool, 82 | purchaseSubscriptionPath: PropTypes.string, 83 | children: PropTypes.node 84 | }; 85 | 86 | function mapStateToProps(state) { 87 | return { 88 | integrationName: getConfigValue(state.config, 'integrationName'), 89 | activeZone: state.activeZone 90 | }; 91 | } 92 | export default injectIntl(connect(mapStateToProps)(CustomCardControl)); 93 | -------------------------------------------------------------------------------- /src/components/FeatureManager/FeatureManager.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class FeatureManager extends Component { 5 | render() { 6 | return ( 7 |
    8 | {this.props.isEnabled && this.props.children} 9 | {!this.props.isEnabled && this.props.error && this.props.error} 10 |
    11 | ); 12 | } 13 | } 14 | 15 | FeatureManager.propTypes = { 16 | isEnabled: PropTypes.bool.isRequired, 17 | error: PropTypes.string, 18 | children: PropTypes.node 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/FormattedMarkdown/FormattedMarkdown.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { injectIntl } from 'react-intl'; 5 | import MarkdownIt from 'markdown-it'; 6 | 7 | import { formatMessageForIntegration } from '../../utils/utils'; 8 | import { getConfigValue } from '../../selectors/config.js'; 9 | 10 | // To support different translations for differents integrations add 11 | // "mycomponent.myfeature.integrationName1" 12 | // "mycomponent.myfeature.integrationName2" 13 | // ... 14 | // and the default translation "mycomponent.myfeature" 15 | // 16 | // Afterwards call 17 | // 18 | class FormattedMarkdown extends Component { 19 | render() { 20 | let { integrationName, formattedMessage } = this.props; 21 | 22 | if (!formattedMessage) { 23 | formattedMessage = formatMessageForIntegration( 24 | this.props.intl, 25 | this.props.text, 26 | integrationName 27 | ); 28 | } 29 | 30 | var md = new MarkdownIt(); 31 | 32 | return ( 33 |
    38 | ); 39 | } 40 | } 41 | 42 | FormattedMarkdown.propTypes = { 43 | text: PropTypes.string, 44 | formattedMessage: PropTypes.string, 45 | intl: PropTypes.object, 46 | integrationName: PropTypes.string 47 | }; 48 | 49 | function mapStateToProps(state) { 50 | return { 51 | integrationName: getConfigValue(state.config, 'integrationName') 52 | }; 53 | } 54 | export default injectIntl(connect(mapStateToProps)(FormattedMarkdown)); 55 | -------------------------------------------------------------------------------- /src/components/GradientBanner/GradientBanner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createComponent } from 'cf-style-container'; 3 | 4 | const gradientStyles = () => ({ 5 | width: '100%', 6 | height: '150px', 7 | display: 'flex', 8 | justifyContent: 'center', 9 | background: 'linear-gradient(left, #8176B5, #76C4E2)', 10 | backgroundRepeat: 'no-repeat', 11 | backgroundSize: '100% 250px', 12 | backgroundColor: '#e6e6e6' 13 | }); 14 | 15 | const GradientBanner = ({ className, children }) => { 16 | return React.createElement('div', { className }, children); 17 | }; 18 | 19 | export default createComponent(gradientStyles, GradientBanner); 20 | -------------------------------------------------------------------------------- /src/components/MarketingFeature/MarketingFeature.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class MarketingFeature extends Component { 5 | render() { 6 | /* 7 | * These styles are stolen from the marketing site and aren't in our CSS 8 | */ 9 | let divStyles = { 10 | padding: '30px 15px 30px 15px' 11 | }; 12 | let iconStyles = { 13 | display: 'block', 14 | width: '40px', 15 | height: 'auto', 16 | maxWidth: '100px', 17 | margin: '0 auto' 18 | }; 19 | let largeLinkStyles = { 20 | padding: '20px 0 0 0', 21 | textAlign: 'center', 22 | fontSize: '16px', 23 | color: '#2f7bbf', 24 | width: '100%', 25 | display: 'block' 26 | }; 27 | let columnPStyles = { 28 | padding: '10px 0 0 0', 29 | fontSize: '12px', 30 | textAlign: 'center' 31 | }; 32 | 33 | return ( 34 |
    35 | 36 | 37 | {this.props.title} 38 | 39 |

    {this.props.description}

    40 |
    41 | ); 42 | } 43 | } 44 | 45 | MarketingFeature.propTypes = { 46 | imgSrc: PropTypes.string.isRequired, 47 | title: PropTypes.string.isRequired, 48 | description: PropTypes.string.isRequired 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/RenderCardsDynamically/RenderCardsDynamically.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import _ from 'lodash'; 3 | import { cardMapper } from '../../utils/ImportCards'; 4 | 5 | export function renderCards(cards) { 6 | // While rendering config.*isEnabled settings are not being checked 7 | return _.map(cards, function(cardName, i) { 8 | return React.createElement(cardMapper[cardName], { key: i }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/TimeSeriesChart/TimeSeriesChart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import C3Wrapper from '../C3Wrapper/C3Wrapper'; 4 | 5 | export default class TimeSeriesChart extends Component { 6 | /** 7 | * let xAxisValues = [{ 8 | * 'label': 'x', 9 | * 'values': [1,2,3,4,5] 10 | * }, 11 | * { 12 | * 'label': 'cached', 13 | * 'values: [1,2,3,4,5] 14 | * }]; 15 | */ 16 | render() { 17 | let { xAxisValues, yAxisLabel } = this.props; 18 | 19 | let columns = []; 20 | 21 | //First row of columms is the x axis key 22 | //The first element in the remaining rows column has to be the label 23 | for (let i = 0; i < xAxisValues.length; i++) { 24 | let label = xAxisValues[i].label; 25 | let values = xAxisValues[i].values; 26 | 27 | if (label && values) { 28 | columns.push([label].concat(values)); 29 | } 30 | } 31 | 32 | //for some reason this only renders correctly if we put the xformat in data AND axis 33 | let xformat = '%m/%d'; 34 | 35 | let config = { 36 | data: { 37 | x: 'x', 38 | xFormat: xformat, 39 | columns: columns 40 | }, 41 | axis: { 42 | x: { 43 | type: 'timeseries', 44 | tick: { 45 | format: xformat 46 | } 47 | }, 48 | y: { 49 | label: yAxisLabel 50 | } 51 | } 52 | }; 53 | 54 | return ( 55 |
    56 | 57 |
    58 | ); 59 | } 60 | } 61 | 62 | TimeSeriesChart.propTypes = { 63 | xAxisValues: PropTypes.array, 64 | yAxisLabel: PropTypes.string 65 | }; 66 | -------------------------------------------------------------------------------- /src/constants/Plans.js: -------------------------------------------------------------------------------- 1 | const LOCALIZED_PRO_PLAN_ID = 'constants.plans.pro'; 2 | const LOCALIZED_BIZ_PLAN_ID = 'constants.plans.biz'; 3 | const LOCALIZED_ENT_PLAN_ID = 'constants.plans.ent'; 4 | 5 | export const FREE_PLAN = 'free'; 6 | export const PRO_PLAN = 'pro'; 7 | export const BIZ_PLAN = 'business'; 8 | export const ENT_PLAN = 'enterprise'; 9 | 10 | export function planNeedsUpgrade(currentPlan, minimumPlan) { 11 | var planList = {}; 12 | planList[FREE_PLAN] = 0; 13 | planList[PRO_PLAN] = 1; 14 | planList[BIZ_PLAN] = 2; 15 | planList[ENT_PLAN] = 3; 16 | 17 | return planList[currentPlan] < planList[minimumPlan]; 18 | } 19 | 20 | export function getPlanUpdateParam(minimumPlan) { 21 | switch (minimumPlan) { 22 | case FREE_PLAN: 23 | return 'f'; 24 | case PRO_PLAN: 25 | return 'p'; 26 | case BIZ_PLAN: 27 | return 'b'; 28 | // Note that we dont support ENT plan 29 | default: 30 | // By default return a pro plan 31 | return 'p'; 32 | } 33 | } 34 | 35 | export function getLocalizedPlanId(planName) { 36 | var localizedPlanName = planName; 37 | switch (planName) { 38 | case PRO_PLAN: 39 | localizedPlanName = LOCALIZED_PRO_PLAN_ID; 40 | break; 41 | case BIZ_PLAN: 42 | localizedPlanName = LOCALIZED_BIZ_PLAN_ID; 43 | break; 44 | case ENT_PLAN: 45 | localizedPlanName = LOCALIZED_ENT_PLAN_ID; 46 | break; 47 | default: 48 | // This should never happen 49 | break; 50 | } 51 | 52 | return localizedPlanName; 53 | } 54 | -------------------------------------------------------------------------------- /src/constants/Schemas.js: -------------------------------------------------------------------------------- 1 | import { normalize, Schema, arrayOf } from 'normalizr'; 2 | 3 | const zoneSchema = new Schema('zones', { idAttribute: 'name' }); 4 | const zoneEntitlementsSchema = new Schema('entitlements', { 5 | idAttribute: 'id' 6 | }); 7 | 8 | export function normalizeZoneByIdGetAll(zoneId, result) { 9 | var zoneSchemaById = new Schema(zoneId, { idAttribute: 'id' }); 10 | return normalize(result, arrayOf(zoneSchemaById)); 11 | } 12 | 13 | export function normalizeZoneGetAll(result) { 14 | return normalize(result, arrayOf(zoneSchema)); 15 | } 16 | 17 | export function normalizeZoneEntitlements(result) { 18 | return normalize(result, arrayOf(zoneEntitlementsSchema)); 19 | } 20 | -------------------------------------------------------------------------------- /src/constants/UrlPaths.js: -------------------------------------------------------------------------------- 1 | export const ANALYTICS_PAGE = '/analytics'; 2 | export const CLOUDFLARE_API_KB_ARTICLE_PAGE = 3 | 'https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-CloudFlare-API-key-'; 4 | export const CLOUDFLARE_FORGOT_PASSWORD_PAGE = 5 | 'https://dash.cloudflare.com/forgot-password'; 6 | export const CLOUDFLARE_SIGNUP_PAGE = 'https://dash.cloudflare.com/sign-up'; 7 | export const CLOUDFLARE_DASHBOARD_PAGE = 'https://dash.cloudflare.com/overview'; 8 | export const DOMAINS_OVERVIEW_PAGE = '/zones'; 9 | export const HOME_PAGE = '/home'; 10 | export const MORE_SETTINGS_PAGE = '/more-settings'; 11 | export const LOGIN_PAGE = '/login'; 12 | export const SIGN_UP_PAGE = '/sign-up'; 13 | export const SUPPORT_PAGE = 'https://support.cloudflare.com/hc/en-us/'; 14 | export const TERMS_AND_CONDITIONS_PAGE = 'https://www.cloudflare.com/terms'; 15 | export const PRIVACY_POLICY_PAGE = 'https://www.cloudflare.com/security-policy'; 16 | export const CLOUDFLARE_ACCOUNT_PAGE = 'https://dash.cloudflare.com/profile'; 17 | export const CLOUDFLARE_ADD_SITE_PAGE = 18 | 'https://dash.cloudflare.com/?to=/:account/add-site'; 19 | -------------------------------------------------------------------------------- /src/containers/ActivationCheckCard/ActivationCheckCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { FormattedMessage, injectIntl } from 'react-intl'; 4 | 5 | import { asyncZoneActivationCheck } from '../../actions/zoneProvision'; 6 | import { Card, CardSection, CardContent, CardControl } from 'cf-component-card'; 7 | import { Button } from 'cf-component-button'; 8 | import { List, ListItem } from 'cf-component-list'; 9 | 10 | class ActivationCheckCard extends Component { 11 | handleButtonClick() { 12 | let { activeZone, dispatch } = this.props; 13 | dispatch(asyncZoneActivationCheck(activeZone.id)); 14 | } 15 | 16 | render() { 17 | const { formatMessage } = this.props.intl; 18 | let { zone } = this.props; 19 | 20 | return ( 21 |
    22 | 23 | 24 | 29 |

    30 | 34 |

    35 |

    36 | 37 |

    38 | 39 | {zone.name_servers.map(nameserver => ( 40 | {nameserver} 41 | ))} 42 | 43 |

    44 | 45 |

    46 |
    47 | 48 | 54 | 55 |
    56 |
    57 |
    58 | ); 59 | } 60 | } 61 | 62 | function mapStateToProps(state) { 63 | return { 64 | activeZone: state.activeZone, 65 | zone: state.zones.entities.zones[state.activeZone.name] 66 | }; 67 | } 68 | export default injectIntl(connect(mapStateToProps)(ActivationCheckCard)); 69 | -------------------------------------------------------------------------------- /src/containers/ActiveZoneSelector/ActiveZoneSelector.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { injectIntl } from 'react-intl'; 4 | import _ from 'lodash'; 5 | 6 | import Select from 'cf-component-select'; 7 | import { notificationAddWarning } from '../../actions/notifications'; 8 | import { getConfigValue } from '../../selectors/config'; 9 | import { asyncZoneSetActiveZone } from '../../actions/activeZone'; 10 | import { isSubdomain } from '../../utils/utils'; 11 | 12 | class ActiveZoneSelector extends Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | subdomainChecked: false 17 | }; 18 | } 19 | 20 | handleChange(event) { 21 | let { dispatch, zoneList } = this.props; 22 | 23 | _.values(zoneList).forEach(zone => { 24 | if (zone.name === event.value) { 25 | dispatch(asyncZoneSetActiveZone(zone)); 26 | } 27 | }); 28 | } 29 | 30 | componentWillReceiveProps() { 31 | let { activeZoneName, config, dispatch } = this.props; 32 | 33 | // If the current active zone is a subdomain show a notification 34 | // regarding that the changes are made to the original zone. 35 | var shouldUseSubdomain = getConfigValue(config, 'isSubdomainCheckEnabled'); 36 | if ( 37 | shouldUseSubdomain && 38 | !this.state.subdomainChecked && 39 | isSubdomain(activeZoneName) 40 | ) { 41 | this.setState({ subdomainChecked: true }); 42 | dispatch(notificationAddWarning('warning.usingSubdomain', true, true)); 43 | } 44 | } 45 | 46 | render() { 47 | let { activeZoneName, zoneList } = this.props; 48 | let zones = _.values(zoneList).map(zone => { 49 | return { value: zone.name, label: zone.name }; 50 | }); 51 | 52 | let isSingleZone = zones.length < 2; 53 | 54 | //vertically center the active zone selector 55 | let activeZoneSelectorStyles = { 56 | position: 'relative', 57 | top: '30px', // Hard code .header-main height/2 58 | transform: 'translateY(-50%)' 59 | }; 60 | 61 | return ( 62 |
    69 | {isSingleZone ? ( 70 | activeZoneName 71 | ) : ( 72 | 91 | 92 | 93 | 102 | ) 103 | } 104 | ]} 105 | /> 106 | 107 |
    108 | ); 109 | } 110 | } 111 | 112 | function mapStateToProps(state) { 113 | return { 114 | activeZoneId: state.activeZone.id, 115 | sslValue: getZoneSettingsValueForZoneId( 116 | state.activeZone.id, 117 | SETTING_NAME, 118 | state 119 | ), 120 | modifiedDate: getZoneSettingsModifiedDateForZoneId( 121 | state.activeZone.id, 122 | SETTING_NAME, 123 | state 124 | ) 125 | }; 126 | } 127 | export default injectIntl(connect(mapStateToProps)(SSLCard)); 128 | -------------------------------------------------------------------------------- /src/containers/UnderAttackButton/UnderAttackButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { FormattedMessage, injectIntl } from 'react-intl'; 4 | import { Button } from 'cf-component-button'; 5 | 6 | import { asyncZoneUpdateSetting } from '../../actions/zoneSettings'; 7 | import { getZoneSettingsValueForZoneId } from '../../selectors/zoneSettings'; 8 | 9 | const SETTING_NAME = 'security_level'; 10 | 11 | class UnderAttackButton extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | value: this.props.securityLevelValue 16 | }; 17 | } 18 | 19 | handleChange(value) { 20 | let { dispatch } = this.props; 21 | this.setState({ value: value }); 22 | dispatch( 23 | asyncZoneUpdateSetting(SETTING_NAME, this.props.activeZoneId, value) 24 | ); 25 | } 26 | 27 | render() { 28 | let { value } = this.state; 29 | let buttonText = 30 | value === 'under_attack' 31 | ? 'container.underAttackButton.turnOn' 32 | : 'container.underAttackButton.turnOff'; 33 | let buttonValue = 34 | value === 'under_attack' ? 'essentially_off' : 'under_attack'; 35 | let buttonType = value === 'under_attack' ? 'warning' : 'primary'; 36 | 37 | let underAttackStyles = { 38 | fontSize: '75%', 39 | textAlign: 'right', 40 | position: 'relative', 41 | top: '30px', // Hard code .header-main height/2 42 | left: '25%', 43 | width: '73%', 44 | transform: 'translateY(-50%)' 45 | }; 46 | 47 | return ( 48 |
    49 | 50 | 51 | 52 | 53 | 61 |
    62 | ); 63 | } 64 | } 65 | 66 | function mapStateToProps(state) { 67 | return { 68 | activeZoneId: state.activeZone.id, 69 | securityLevelValue: getZoneSettingsValueForZoneId( 70 | state.activeZone.id, 71 | SETTING_NAME, 72 | state 73 | ) 74 | }; 75 | } 76 | export default injectIntl(connect(mapStateToProps)(UnderAttackButton)); 77 | -------------------------------------------------------------------------------- /src/containers/WAFCard/WAFCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { FormattedMessage, injectIntl } from 'react-intl'; 4 | import { Card, CardSection, CardContent, CardDrawers } from 'cf-component-card'; 5 | import Toggle from 'cf-component-toggle'; 6 | 7 | import FormattedMarkdown from '../../components/FormattedMarkdown/FormattedMarkdown'; 8 | import { getLastModifiedDate } from '../../utils/utils'; 9 | import { 10 | getZoneSettingsValueForZoneId, 11 | getZoneSettingsModifiedDateForZoneId 12 | } from '../../selectors/zoneSettings'; 13 | import CustomCardControl from '../../components/CustomCardControl/CustomCardControl'; 14 | import { asyncZoneUpdateSetting } from '../../actions/zoneSettings'; 15 | import { PRO_PLAN } from '../../constants/Plans.js'; 16 | 17 | const SETTING_NAME = 'waf'; 18 | const MINIMUM_PLAN = PRO_PLAN; 19 | 20 | class WAFCard extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | activeDrawer: null 25 | }; 26 | this.handleDrawerClick = this.handleDrawerClick.bind(this); 27 | } 28 | 29 | handleDrawerClick(id) { 30 | this.setState({ 31 | activeDrawer: id === this.state.activeDrawer ? null : id 32 | }); 33 | } 34 | 35 | handleChange(value) { 36 | let { activeZoneId, dispatch } = this.props; 37 | value = value === true ? 'on' : 'off'; 38 | dispatch(asyncZoneUpdateSetting(SETTING_NAME, activeZoneId, value)); 39 | } 40 | 41 | render() { 42 | let { activeZone, zones, modifiedDate } = this.props; 43 | let zone = zones[activeZone.name]; 44 | 45 | const { formatMessage } = this.props.intl; 46 | return ( 47 |
    48 | 49 | 50 | 54 | 55 | 56 | 61 | 66 | 67 | 68 | 76 | } 77 | ]} 78 | /> 79 | 80 |
    81 | ); 82 | } 83 | } 84 | 85 | function mapStateToProps(state) { 86 | return { 87 | activeZoneId: state.activeZone.id, 88 | WAFValue: getZoneSettingsValueForZoneId( 89 | state.activeZone.id, 90 | SETTING_NAME, 91 | state 92 | ), 93 | modifiedDate: getZoneSettingsModifiedDateForZoneId( 94 | state.activeZone.id, 95 | SETTING_NAME, 96 | state 97 | ), 98 | activeZone: state.activeZone, 99 | zones: state.zones.entities.zones 100 | }; 101 | } 102 | export default injectIntl(connect(mapStateToProps)(WAFCard)); 103 | -------------------------------------------------------------------------------- /src/containers/WaitForSettings/WaitForSettings.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { isActiveZoneOnCloudflare } from '../../selectors/activeZone'; 5 | import Loading from 'cf-component-loading'; 6 | import Text from 'cf-component-text'; 7 | import { getPluginSettingsForZoneId } from '../../selectors/pluginSettings'; 8 | import { FormattedMessage, injectIntl } from 'react-intl'; 9 | import { getAllZoneSettingsForZoneId } from '../../selectors/zoneSettings'; 10 | import { isDNSPageEnabled } from '../../selectors/config'; 11 | import { push } from 'react-router-redux'; 12 | import { Link } from 'react-router'; 13 | import { 14 | CLOUDFLARE_ADD_SITE_PAGE, 15 | DOMAINS_OVERVIEW_PAGE 16 | } from '../../constants/UrlPaths.js'; 17 | 18 | class WaitForSettings extends Component { 19 | handleClick(path) { 20 | let { dispatch } = this.props; 21 | dispatch(push(path)); 22 | } 23 | 24 | render() { 25 | let { 26 | activeZone, 27 | zoneSettings, 28 | zonePluginSettings, 29 | settings, 30 | pluginSettings, 31 | config 32 | } = this.props; 33 | 34 | let isSettingsLoaded = true; 35 | let isPluginSettingsLoaded = true; 36 | 37 | if (settings) { 38 | isSettingsLoaded = getAllZoneSettingsForZoneId( 39 | activeZone.id, 40 | zoneSettings 41 | ); 42 | } 43 | 44 | if (pluginSettings) { 45 | isPluginSettingsLoaded = getPluginSettingsForZoneId( 46 | activeZone.id, 47 | zonePluginSettings 48 | ); 49 | } 50 | 51 | let isZoneOnCloudflare = isActiveZoneOnCloudflare(activeZone); 52 | 53 | let isEverythingLoaded = isSettingsLoaded && isPluginSettingsLoaded; 54 | 55 | var link = ( 56 | 57 | Cloudflare 58 | 59 | ); 60 | if (isDNSPageEnabled(config)) { 61 | link = ( 62 | this.handleClick(DOMAINS_OVERVIEW_PAGE)}> 63 | 64 | 65 | ); 66 | } 67 | 68 | return ( 69 |
    70 | {!isEverythingLoaded && isZoneOnCloudflare && ( 71 | 72 | 73 | 74 | )} 75 | {!isEverythingLoaded && !isZoneOnCloudflare && ( 76 | 77 | 81 | 82 | )} 83 | {isEverythingLoaded && isZoneOnCloudflare && this.props.children} 84 |
    85 | ); 86 | } 87 | } 88 | 89 | WaitForSettings.propTypes = { 90 | settings: PropTypes.bool, 91 | pluginSettings: PropTypes.bool, 92 | analytics: PropTypes.bool, 93 | dispatch: PropTypes.func, 94 | activeZone: PropTypes.object, 95 | zoneSettings: PropTypes.object, 96 | zonePluginSettings: PropTypes.object, 97 | config: PropTypes.object, 98 | intl: PropTypes.object, 99 | children: PropTypes.node 100 | }; 101 | 102 | function mapStateToProps(state) { 103 | return { 104 | activeZone: state.activeZone, 105 | zoneSettings: state.zoneSettings, 106 | zonePluginSettings: state.pluginSettings, 107 | config: state.config 108 | }; 109 | } 110 | export default injectIntl(connect(mapStateToProps)(WaitForSettings)); 111 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { Router } from 'react-router'; 5 | import configureStore from './store/configureStore'; 6 | import routes from './routes'; 7 | import createHistory from 'history/lib/createHashHistory'; 8 | import http from 'cf-util-http'; 9 | import { syncHistoryWithStore } from 'react-router-redux'; 10 | 11 | const store = configureStore(createHistory()); 12 | const history = syncHistoryWithStore(createHistory(), store); 13 | 14 | /* 15 | * Register our RestProxyCallback to send all cf-util-http calls to 16 | * our backend instead of their actual endpoint. 17 | */ 18 | http.beforeSend(window.RestProxyCallback); 19 | 20 | ReactDOM.render( 21 | 22 | {routes} 23 | , 24 | document.getElementById('root') 25 | ); 26 | -------------------------------------------------------------------------------- /src/reducers/activeZone.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | const initialState = { 4 | id: '', 5 | name: '' 6 | }; 7 | 8 | export function activeZoneReducer(state = initialState, action) { 9 | switch (action.type) { 10 | case ActionTypes.ZONES_SET_ACTIVE_ZONE: 11 | return Object.assign({}, state, { 12 | id: action.zone.id, 13 | name: action.zone.name, 14 | account: action.zone.account 15 | }); 16 | default: 17 | return state; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/reducers/app.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | const initialState = { 4 | isInitialized: false 5 | }; 6 | 7 | export function appReducer(state = initialState, action) { 8 | switch (action.type) { 9 | case ActionTypes.APPLICATION_INIT: 10 | return Object.assign({}, state, { 11 | isInitialized: true 12 | }); 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/reducers/config.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | const initialState = { 4 | config: {}, 5 | isFetching: false 6 | }; 7 | 8 | export const ABSOLUTE_URL_BASE_KEY = 'absoluteUrlBase'; 9 | 10 | export function configReducer(state = initialState, action) { 11 | switch (action.type) { 12 | case ActionTypes.CONFIG_FETCH: 13 | return Object.assign({}, state, { 14 | isFetching: true 15 | }); 16 | case ActionTypes.CONFIG_FETCH_SUCCESS: 17 | return Object.assign({}, state, { 18 | isFetching: false 19 | }); 20 | case ActionTypes.CONFIG_FETCH_ERROR: 21 | return Object.assign({}, state, { 22 | isFetching: false 23 | }); 24 | case ActionTypes.CONFIG_UPDATE_BY_KEY: 25 | return Object.assign({}, state, { 26 | config: Object.assign({}, state.config, { 27 | [action.key]: action.value 28 | }) 29 | }); 30 | default: 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | import { activeZoneReducer } from './activeZone'; 4 | import { appReducer } from './app'; 5 | import { configReducer } from './config'; 6 | import { dnsRecordsReducer } from './zoneDnsRecords.js'; 7 | import { intlReducer } from './intl'; 8 | import { notificationsReducer } from './notifications'; 9 | import { userReducer } from './user'; 10 | import { zonePurgeCacheReducer } from './zonePurgeCache'; 11 | import { zoneEntitlementsReducer } from './zoneEntitlements'; 12 | import { zoneSettingsReducer } from './zoneSettings'; 13 | import { zonesReducer } from './zones'; 14 | import { pluginSettingsReducer } from './pluginSettings'; 15 | 16 | const rootReducer = combineReducers({ 17 | activeZone: activeZoneReducer, 18 | app: appReducer, 19 | config: configReducer, 20 | intl: intlReducer, 21 | user: userReducer, 22 | notifications: notificationsReducer, 23 | routing: routerReducer, 24 | zones: zonesReducer, 25 | zoneDnsRecords: dnsRecordsReducer, 26 | zonePurgeCache: zonePurgeCacheReducer, 27 | zoneSettings: zoneSettingsReducer, 28 | pluginSettings: pluginSettingsReducer, 29 | zoneEntitlements: zoneEntitlementsReducer 30 | }); 31 | 32 | export default rootReducer; 33 | -------------------------------------------------------------------------------- /src/reducers/intl.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | const initialState = { 4 | locale: '', 5 | translations: {}, 6 | isFetching: false 7 | }; 8 | 9 | export function intlReducer(state = initialState, action) { 10 | switch (action.type) { 11 | case ActionTypes.INTL_FETCH_TRANSLATIONS: 12 | return Object.assign({}, state, { 13 | isFetching: true 14 | }); 15 | case ActionTypes.INTL_FETCH_TRANSLATIONS_SUCCESS: 16 | return Object.assign({}, state, { 17 | locale: action.locale, 18 | translations: action.translations, 19 | isFetching: false 20 | }); 21 | case ActionTypes.INTL_FETCH_TRANSLATIONS_ERROR: 22 | return Object.assign({}, state, { 23 | isFetching: false 24 | }); 25 | default: 26 | return state; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/reducers/notifications.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | const initialState = []; 4 | 5 | export function notificationsReducer(state = initialState, action) { 6 | switch (action.type) { 7 | case ActionTypes.NOTIFICATION_ADD: 8 | return [ 9 | { 10 | key: Date.now(), 11 | level: action.level, 12 | message: action.message, 13 | localized: action.localized, 14 | persistant: action.persistant, 15 | delay: action.delay 16 | }, 17 | ...state 18 | ]; 19 | case ActionTypes.NOTIFICATION_REMOVE: 20 | return state.filter(notification => notification.key !== action.key); 21 | default: 22 | return state; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/reducers/pluginSettings.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import * as ActionTypes from '../constants/ActionTypes'; 3 | import { normalizeZoneByIdGetAll } from '../constants/Schemas'; 4 | 5 | const initialState = { 6 | entities: {}, 7 | result: [], 8 | isFetching: '' 9 | }; 10 | 11 | export function pluginSettingsReducer(state = initialState, action) { 12 | switch (action.type) { 13 | case ActionTypes.PLUGIN_SETTINGS_FETCH: 14 | return Object.assign({}, state, { 15 | isFetching: 'FETCH ALL PLUGIN SETTINGS' 16 | }); 17 | case ActionTypes.PLUGIN_SETTINGS_FETCH_SUCCESS: 18 | let normalizedPluginSettings = normalizeZoneByIdGetAll( 19 | action.zoneId, 20 | action.setting 21 | ); 22 | 23 | return Object.assign({}, state, { 24 | entities: _.merge(state.entities, normalizedPluginSettings.entities), 25 | result: _.merge(state.result, normalizedPluginSettings.result), 26 | isFetching: '' 27 | }); 28 | case ActionTypes.PLUGIN_SETTINGS_FETCH_ERROR: 29 | return Object.assign({}, state, { 30 | isFetching: '' 31 | }); 32 | case ActionTypes.PLUGIN_SETTING_UPDATE: 33 | return Object.assign({}, state, { 34 | entities: pluginPatchSetting(action.zoneId, action.setting, state), 35 | isFetching: action.setting.id 36 | }); 37 | case ActionTypes.PLUGIN_SETTING_UPDATE_SUCCESS: 38 | return Object.assign({}, state, { 39 | entities: pluginPatchSetting(action.zoneId, action.setting, state), 40 | isFetching: '' 41 | }); 42 | case ActionTypes.PLUGIN_SETTING_UPDATE_ERROR: 43 | return Object.assign({}, state, { 44 | entities: pluginPatchSetting(action.zoneId, action.setting, state), 45 | isFetching: '' 46 | }); 47 | default: 48 | return state; 49 | } 50 | } 51 | 52 | function pluginPatchSetting(zoneId, setting, state) { 53 | let patchedEntities = Object.assign({}, state.entities); 54 | patchedEntities[zoneId][setting.id] = setting; 55 | return patchedEntities; 56 | } 57 | -------------------------------------------------------------------------------- /src/reducers/user.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | import { setEmail } from '../utils/Auth/Auth'; 3 | 4 | const initialState = { 5 | isLoggedIn: false, 6 | isFetching: false 7 | }; 8 | 9 | export function userReducer(state = initialState, action) { 10 | switch (action.type) { 11 | case ActionTypes.USER_LOGIN: 12 | return Object.assign({}, state, { 13 | isFetching: true 14 | }); 15 | case ActionTypes.USER_LOGIN_SUCCESS: 16 | //routes.js needs cfEmail in localStorage to detect logged in state 17 | setEmail(action.email); 18 | return Object.assign({}, state, { 19 | isLoggedIn: true, 20 | isFetching: false 21 | }); 22 | case ActionTypes.USER_LOGIN_ERROR: 23 | return Object.assign({}, state, { 24 | isFetching: false 25 | }); 26 | case ActionTypes.USER_SIGNUP: 27 | return Object.assign({}, state, { 28 | isFetching: true 29 | }); 30 | case ActionTypes.USER_SIGNUP_SUCCESS: 31 | return Object.assign({}, state, { 32 | isFetching: false 33 | }); 34 | case ActionTypes.USER_SIGNUP_ERROR: 35 | return Object.assign({}, state, { 36 | isFetching: false 37 | }); 38 | case ActionTypes.USER_LOGOUT: 39 | setEmail(''); 40 | return Object.assign({}, state, { 41 | isLoggedIn: false 42 | }); 43 | default: 44 | return state; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/reducers/zoneDnsRecords.js: -------------------------------------------------------------------------------- 1 | import { normalize, Schema, arrayOf } from 'normalizr'; 2 | import _ from 'lodash'; 3 | import * as ActionTypes from '../constants/ActionTypes'; 4 | 5 | const initialState = { 6 | entities: {}, 7 | result: [], 8 | isFetching: false, 9 | updateIsFetching: '' 10 | }; 11 | 12 | export function dnsRecordsReducer(state = initialState, action) { 13 | switch (action.type) { 14 | case ActionTypes.DNS_RECORD_CLEAR_ALL: 15 | let dnsRecordEntities = state.entities; 16 | dnsRecordEntities[action.zoneId] = {}; 17 | 18 | return Object.assign({}, state, { 19 | entities: dnsRecordEntities 20 | }); 21 | case ActionTypes.DNS_RECORD_CREATE: 22 | return Object.assign({}, state, { 23 | updateIsFetching: action.name 24 | }); 25 | case ActionTypes.DNS_RECORD_CREATE_SUCCESS: 26 | return Object.assign({}, state, { 27 | entities: patchDnsRecord( 28 | action.zoneId, 29 | state.entities, 30 | action.dnsRecord 31 | ), 32 | updateIsFetching: '' 33 | }); 34 | case ActionTypes.DNS_RECORD_CREATE_ERROR: 35 | return Object.assign({}, state, { 36 | updateIsFetching: '' 37 | }); 38 | case ActionTypes.DNS_RECORD_FETCH_LIST: 39 | return Object.assign({}, state, { 40 | isFetching: true 41 | }); 42 | case ActionTypes.DNS_RECORD_FETCH_LIST_SUCCESS: 43 | let dnsRecordSchema = new Schema(action.zoneId, { idAttribute: 'name' }); 44 | let normalizedDnsRecords = normalize( 45 | action.dnsRecords, 46 | arrayOf(dnsRecordSchema) 47 | ); 48 | 49 | return Object.assign({}, state, { 50 | entities: _.merge(state.entities, normalizedDnsRecords.entities), 51 | result: _.merge(state.result, normalizedDnsRecords.result), 52 | isFetching: false 53 | }); 54 | case ActionTypes.DNS_RECORD_FETCH_LIST_ERROR: 55 | return Object.assign({}, state, { 56 | isFetching: false 57 | }); 58 | case ActionTypes.DNS_RECORD_UPDATE: 59 | return Object.assign({}, state, { 60 | updateIsFetching: action.name 61 | }); 62 | case ActionTypes.DNS_RECORD_UPDATE_SUCCESS: 63 | return Object.assign({}, state, { 64 | entities: patchDnsRecord( 65 | action.zoneId, 66 | state.entities, 67 | action.dnsRecord 68 | ), 69 | updateIsFetching: '' 70 | }); 71 | case ActionTypes.DNS_RECORD_UPDATE_ERROR: 72 | return Object.assign({}, state, { 73 | updateIsFetching: '' 74 | }); 75 | default: 76 | return state; 77 | } 78 | } 79 | 80 | function patchDnsRecord(zoneId, dnsRecordList, dnsRecord) { 81 | dnsRecordList[zoneId][dnsRecord.name] = dnsRecord; 82 | return dnsRecordList; 83 | } 84 | -------------------------------------------------------------------------------- /src/reducers/zoneEntitlements.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | import { normalizeZoneEntitlements } from '../constants/Schemas'; 3 | 4 | const initialState = { 5 | entities: {}, 6 | isFetching: false 7 | }; 8 | 9 | export function zoneEntitlementsReducer(state = initialState, action) { 10 | switch (action.type) { 11 | case ActionTypes.ZONE_ENTITLEMENTS: 12 | return Object.assign({}, state, { 13 | isFetching: true 14 | }); 15 | case ActionTypes.ZONE_ENTITLEMENTS_SUCCESS: 16 | let normalizedZoneEntitlements = normalizeZoneEntitlements( 17 | action.zoneEntitlements 18 | ); 19 | let newEntities = Object.assign({}, state.entities); 20 | newEntities[action.zoneId] = 21 | normalizedZoneEntitlements.entities.entitlements; 22 | return Object.assign({}, state, { 23 | entities: newEntities, 24 | isFetching: false 25 | }); 26 | case ActionTypes.ZONE_ENTITLEMENTS_ERROR: 27 | return Object.assign({}, state, { 28 | isFetching: false 29 | }); 30 | default: 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/reducers/zonePurgeCache.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../constants/ActionTypes'; 2 | 3 | const initialState = { 4 | isFetching: false 5 | }; 6 | 7 | export function zonePurgeCacheReducer(state = initialState, action) { 8 | switch (action.type) { 9 | case ActionTypes.ZONE_PURGE_CACHE: 10 | return Object.assign({}, state, { 11 | isFetching: true 12 | }); 13 | case ActionTypes.ZONE_PURGE_CACHE_SUCCESS: 14 | return Object.assign({}, state, { 15 | isFetching: false 16 | }); 17 | case ActionTypes.ZONE_PURGE_CACHE_ERROR: 18 | return Object.assign({}, state, { 19 | isFetching: false 20 | }); 21 | default: 22 | return state; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/reducers/zoneSettings.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import * as ActionTypes from '../constants/ActionTypes'; 3 | import { normalizeZoneByIdGetAll } from '../constants/Schemas'; 4 | 5 | const initialState = { 6 | entities: {}, 7 | result: [], 8 | isFetching: '' 9 | }; 10 | 11 | export function zoneSettingsReducer(state = initialState, action) { 12 | switch (action.type) { 13 | case ActionTypes.ZONE_FETCH_SETTINGS: 14 | return Object.assign({}, state, { 15 | isFetching: 'fetchAllSettings' 16 | }); 17 | case ActionTypes.ZONE_FETCH_SETTINGS_SUCCESS: 18 | let normalizedZoneSettings = normalizeZoneByIdGetAll( 19 | action.zoneId, 20 | action.zoneSettings 21 | ); 22 | 23 | return Object.assign({}, state, { 24 | entities: _.merge(state.entities, normalizedZoneSettings.entities), 25 | result: _.merge(state.result, normalizedZoneSettings.result), 26 | isFetching: '' 27 | }); 28 | case ActionTypes.ZONE_FETCH_SETTINGS_ERROR: 29 | return Object.assign({}, state, { 30 | isFetching: '' 31 | }); 32 | case ActionTypes.ZONE_UPDATE_SETTING: 33 | return Object.assign({}, state, { 34 | entities: zonePatchSetting(action.zoneId, action.setting, state), 35 | isFetching: action.setting.id 36 | }); 37 | case ActionTypes.ZONE_UPDATE_SETTING_SUCCESS: 38 | return Object.assign({}, state, { 39 | entities: zonePatchSetting(action.zoneId, action.setting, state), 40 | isFetching: '' 41 | }); 42 | case ActionTypes.ZONE_UPDATE_SETTING_ERROR: 43 | return Object.assign({}, state, { 44 | entities: zonePatchSetting(action.zoneId, action.setting, state), 45 | isFetching: '' 46 | }); 47 | default: 48 | return state; 49 | } 50 | } 51 | 52 | function zonePatchSetting(zoneId, setting, state) { 53 | let patchedEntities = Object.assign({}, state.entities); 54 | patchedEntities[zoneId][setting.id] = setting; 55 | return patchedEntities; 56 | } 57 | -------------------------------------------------------------------------------- /src/reducers/zones.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import * as ActionTypes from '../constants/ActionTypes'; 3 | import { normalizeZoneGetAll } from '../constants/Schemas'; 4 | 5 | const initialState = { 6 | entities: {}, 7 | result: {}, 8 | zoneDeleteIsFetching: false, 9 | zoneFetchIsFetching: false, 10 | zoneProvisionCnameIsFetching: false, 11 | zoneProvisionFullIsFetching: false 12 | }; 13 | 14 | export function zonesReducer(state = initialState, action) { 15 | switch (action.type) { 16 | case ActionTypes.ZONES_DELETE_ZONE: 17 | return Object.assign({}, state, { 18 | zoneDeleteIsFetching: true 19 | }); 20 | case ActionTypes.ZONES_DELETE_ZONE_SUCCESS: 21 | return Object.assign({}, state, { 22 | zoneDeleteIsFetching: false 23 | }); 24 | case ActionTypes.ZONES_DELETE_ZONE_ERROR: 25 | return Object.assign({}, state, { 26 | zoneDeleteIsFetching: false 27 | }); 28 | case ActionTypes.ZONES_FETCH: 29 | return Object.assign({}, state, { 30 | zoneFetchIsFetching: true 31 | }); 32 | case ActionTypes.ZONES_FETCH_SUCCESS: 33 | let normalizedZoneList = normalizeZoneGetAll(action.zoneList); 34 | 35 | return Object.assign({}, state, { 36 | entities: _.merge(state.entities, normalizedZoneList.entities), 37 | result: _.merge(state.result, normalizedZoneList.result), 38 | zoneFetchIsFetching: false 39 | }); 40 | case ActionTypes.ZONES_FETCH_ERROR: 41 | return Object.assign({}, state, { 42 | zoneFetchIsFetching: false 43 | }); 44 | case ActionTypes.ZONES_PROVISION_CNAME: 45 | return Object.assign({}, state, { 46 | zoneProvisionCnameIsFetching: true 47 | }); 48 | case ActionTypes.ZONES_PROVISION_CNAME_SUCCESS: 49 | return Object.assign({}, state, { 50 | zoneProvisionCnameIsFetching: false 51 | }); 52 | case ActionTypes.ZONES_PROVISION_CNAME_ERROR: 53 | return Object.assign({}, state, { 54 | zoneProvisionCnameIsFetching: false 55 | }); 56 | case ActionTypes.ZONES_PROVISION_FULL: 57 | return Object.assign({}, state, { 58 | zoneProvisionFullIsFetching: true 59 | }); 60 | case ActionTypes.ZONES_PROVISION_FULL_SUCCESS: 61 | return Object.assign({}, state, { 62 | zoneProvisionFullIsFetching: false 63 | }); 64 | case ActionTypes.ZONES_PROVISION_FULL_ERROR: 65 | return Object.assign({}, state, { 66 | zoneProvisionFullIsFetching: false 67 | }); 68 | default: 69 | return state; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import * as UrlPaths from './constants/UrlPaths'; 4 | import { isLoggedIn } from './utils/Auth/Auth'; 5 | 6 | /* containers */ 7 | import AnalyticsPage from './containers/AnalyticsPage/AnaltyicsPage'; 8 | import App from './containers/App/App'; 9 | import DNSManagementPage from './containers/DNSManagementPage/DNSManagementPage'; 10 | import LoginPage from './containers/LoginPage/LoginPage'; 11 | import SplashPage from './containers/SplashPage/SplashPage'; 12 | import SignUpPage from './containers/SignUpPage/SignUpPage'; 13 | import HomePage from './containers/HomePage/HomePage'; 14 | import MoreSettingsPage from './containers/MoreSettingsPage/MoreSettingsPage'; 15 | 16 | function requireAuth(nextState, replaceState) { 17 | if (!isLoggedIn()) { 18 | replaceState( 19 | { nextPathname: nextState.location.pathname }, 20 | UrlPaths.LOGIN_PAGE 21 | ); 22 | } 23 | } 24 | 25 | export default ( 26 | 27 | 28 | 29 | 30 | 35 | 40 | 45 | 50 | 51 | ); 52 | -------------------------------------------------------------------------------- /src/selectors/activeZone.js: -------------------------------------------------------------------------------- 1 | export function isActiveZoneOnCloudflare(activeZone) { 2 | return activeZone.id !== undefined; 3 | } 4 | -------------------------------------------------------------------------------- /src/selectors/config.js: -------------------------------------------------------------------------------- 1 | import { ABSOLUTE_URL_BASE_KEY } from '../reducers/config'; 2 | 3 | export function getAbsoluteUrl(config, url) { 4 | let baseUrl = 5 | typeof config.config[ABSOLUTE_URL_BASE_KEY] !== 'undefined' 6 | ? config.config[ABSOLUTE_URL_BASE_KEY] 7 | : ''; 8 | return baseUrl + url; 9 | } 10 | 11 | export function getConfigValue(config, key) { 12 | return config.config[key]; 13 | } 14 | 15 | export function isDNSPageEnabled(config) { 16 | return getConfigValue(config, 'isDNSPageEnabled') === true; 17 | } 18 | -------------------------------------------------------------------------------- /src/selectors/intl.js: -------------------------------------------------------------------------------- 1 | export function getLocale(state) { 2 | return state.intl.locale; 3 | } 4 | -------------------------------------------------------------------------------- /src/selectors/pluginSettings.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export function getPluginSettingsForZoneId(zoneId, state) { 4 | return _.get(state, ['entities', zoneId], false); 5 | } 6 | 7 | export function getPluginSettingsIsFetching(state) { 8 | return _.get(state, ['pluginSettings', 'isFetching']); 9 | } 10 | 11 | export function getPluginSettingsValueForZoneId(zoneId, settingId, state) { 12 | // return false as default value 13 | return _.get( 14 | state, 15 | ['pluginSettings', 'entities', zoneId, settingId, 'value'], 16 | false 17 | ); 18 | } 19 | 20 | export function getPluginSettingsModifiedDateForZoneId( 21 | zoneId, 22 | settingId, 23 | state 24 | ) { 25 | // return '' as default value 26 | return _.get( 27 | state, 28 | ['pluginSettings', 'entities', zoneId, settingId, 'modified_on'], 29 | '' 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/selectors/zoneSettings.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export function getZoneSettingsValueForZoneId(zoneId, settingId, state) { 4 | // return false as default value 5 | return _.get( 6 | state, 7 | ['zoneSettings', 'entities', zoneId, settingId, 'value'], 8 | false 9 | ); 10 | } 11 | 12 | export function getZoneSettingsModifiedDateForZoneId(zoneId, settingId, state) { 13 | // return '' as default value 14 | return _.get( 15 | state, 16 | ['zoneSettings', 'entities', zoneId, settingId, 'modified_on'], 17 | '' 18 | ); 19 | } 20 | 21 | export function getAllZoneSettingsForZoneId(zoneId, state) { 22 | return _.get(state, ['entities', zoneId], false); 23 | } 24 | -------------------------------------------------------------------------------- /src/selectors/zones.js: -------------------------------------------------------------------------------- 1 | export function getZonePlanLegacyId(zoneName, zones) { 2 | return zones.entities.zones[zoneName].plan.legacy_id; 3 | } 4 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import createLogger from 'redux-logger'; 4 | import rootReducer from '../reducers'; 5 | import { routerMiddleware } from 'react-router-redux'; 6 | 7 | export default function configureStore(history, initialState) { 8 | const router = routerMiddleware(history); 9 | 10 | const logger = createLogger({ collapsed: true }); 11 | 12 | const createStoreWithMiddleware = applyMiddleware( 13 | thunkMiddleware, 14 | logger, 15 | router 16 | )(createStore); 17 | const store = createStoreWithMiddleware(rootReducer, initialState); 18 | 19 | return store; 20 | } 21 | -------------------------------------------------------------------------------- /src/test/_helpers/createMockStore.js: -------------------------------------------------------------------------------- 1 | import configureStore from 'redux-mock-store'; 2 | import thunk from 'redux-thunk'; 3 | const middlewares = [thunk]; 4 | 5 | export default state => configureStore(middlewares)(state); 6 | -------------------------------------------------------------------------------- /src/test/components/BenefitsFeatureTest.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BenefitsFeature from '../../components/BenefitsFeature/BenefitsFeature'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | it('renders enabled correctly', () => { 6 | const component = renderer 7 | .create( 8 | 13 | ) 14 | .toJSON(); 15 | expect(component).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /src/test/components/FeatureManagerTest.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FeatureManager from '../../components/FeatureManager/FeatureManager'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | it('renders enabled correctly', () => { 6 | const component = renderer 7 | .create( 8 | 9 | children 10 | 11 | ) 12 | .toJSON(); 13 | expect(component).toMatchSnapshot(); 14 | }); 15 | 16 | it('renders disabled correctly', () => { 17 | const component = renderer 18 | .create( 19 | 20 | children 21 | 22 | ) 23 | .toJSON(); 24 | expect(component).toMatchSnapshot(); 25 | }); 26 | -------------------------------------------------------------------------------- /src/test/components/GradientBannerTest.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GradientBanner from '../../components/GradientBanner/GradientBanner'; 3 | import renderer from 'react-test-renderer'; 4 | import { StyleProvider } from 'cf-style-provider'; 5 | 6 | it('renders enabled correctly', () => { 7 | const component = renderer 8 | .create( 9 | 15 | 20 | 21 | ) 22 | .toJSON(); 23 | expect(component).toMatchSnapshot(); 24 | }); 25 | -------------------------------------------------------------------------------- /src/test/components/MarketingFeatureTest.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MarketingFeature from '../../components/MarketingFeature/MarketingFeature'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | it('renders enabled correctly', () => { 6 | const component = renderer 7 | .create( 8 | 13 | ) 14 | .toJSON(); 15 | expect(component).toMatchSnapshot(); 16 | }); 17 | -------------------------------------------------------------------------------- /src/test/components/__snapshots__/BenefitsFeatureTest.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders enabled correctly 1`] = ` 4 |
    11 | 23 | 36 | title 37 | 38 |

    48 | description 49 |

    50 |
    51 | `; 52 | -------------------------------------------------------------------------------- /src/test/components/__snapshots__/FeatureManagerTest.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders disabled correctly 1`] = ` 4 |
    5 | error 6 |
    7 | `; 8 | 9 | exports[`renders enabled correctly 1`] = ` 10 |
    11 | children 12 |
    13 | `; 14 | -------------------------------------------------------------------------------- /src/test/components/__snapshots__/GradientBannerTest.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders enabled correctly 1`] = ` 4 |
    7 | `; 8 | -------------------------------------------------------------------------------- /src/test/components/__snapshots__/MarketingFeatureTest.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders enabled correctly 1`] = ` 4 |
    11 | 23 | 36 | title 37 | 38 |

    47 | description 48 |

    49 |
    50 | `; 51 | -------------------------------------------------------------------------------- /src/test/containers/SplashPageTest.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SplashPage from '../../containers/SplashPage/SplashPage'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | import createMockStore from '../_helpers/createMockStore'; 6 | 7 | import { Provider } from 'react-redux'; 8 | import { StyleProvider } from 'cf-style-provider'; 9 | import { IntlProvider } from 'react-intl'; 10 | 11 | const messages = { 12 | 'container.splashPage.heading.speedUp': 13 | 'Speed up and Optimize your WordPress Site with Cloudflare', 14 | 'container.splashPage.help.alreadyHaveAccount': 15 | 'Have an account already? Sign in', 16 | 'container.splashPage.help.here': 'here', 17 | 'container.splashPage.button.createFreeAccount': 'Create Your Free Account', 18 | 'component.benefitsFeature.globalCaching.title': 'Global Caching', 19 | 'component.benefitsFeature.globalCaching.description': 20 | "Shorten your visitor's load times with content caching in our 110+ global locations.", 21 | 'component.benefitsFeature.optimization.title': 'Website Optimization', 22 | 'component.benefitsFeature.optimization.description': 23 | "Deliver content to users faster by enabling Cloudflare's content optimizations.", 24 | 'component.benefitsFeature.security.title': 'Security', 25 | 'component.benefitsFeature.security.description': 26 | 'Cloudflare offers protections against vulnerabilities including DDoS protection.', 27 | 'component.benefitsFeature.insights.title': 'Insights', 28 | 'component.benefitsFeature.insights.description': 29 | 'Monitor bandwidth saved, threats blocked, and more with built-in analytics.' 30 | }; 31 | 32 | it('renders enabled correctly', () => { 33 | window.localStorage = {}; 34 | const component = renderer 35 | .create( 36 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | ) 53 | .toJSON(); 54 | expect(component).toMatchSnapshot(); 55 | }); 56 | -------------------------------------------------------------------------------- /src/test/reducers/__snapshots__/configTest.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Config Reducer should handle CONFIG_FETCH 1`] = ` 4 | Object { 5 | "config": Object {}, 6 | "isFetching": true, 7 | } 8 | `; 9 | 10 | exports[`Config Reducer should handle CONFIG_FETCH_ERROR 1`] = ` 11 | Object { 12 | "config": Object {}, 13 | "isFetching": false, 14 | } 15 | `; 16 | 17 | exports[`Config Reducer should handle CONFIG_FETCH_SUCCESS 1`] = ` 18 | Object { 19 | "config": Object {}, 20 | "isFetching": false, 21 | } 22 | `; 23 | 24 | exports[`Config Reducer should handle CONFIG_UPDATE_BY_KEY 1`] = ` 25 | Object { 26 | "config": Object { 27 | "key": "value", 28 | }, 29 | "isFetching": false, 30 | } 31 | `; 32 | 33 | exports[`Config Reducer should return the initial state 1`] = ` 34 | Object { 35 | "config": Object {}, 36 | "isFetching": false, 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /src/test/reducers/activeZoneTest.js: -------------------------------------------------------------------------------- 1 | import { activeZoneReducer } from '../..//reducers/activeZone'; 2 | import * as ActionTypes from '../..//constants/ActionTypes'; 3 | 4 | describe('Active Zone Reducer', () => { 5 | it('should return the initial state', () => { 6 | expect(activeZoneReducer(undefined, {})).toEqual({ 7 | id: '', 8 | name: '' 9 | }); 10 | }); 11 | 12 | it('should set active zone', () => { 13 | expect( 14 | activeZoneReducer( 15 | {}, 16 | { 17 | type: ActionTypes.ZONES_SET_ACTIVE_ZONE, 18 | zone: { id: 1, name: 'name' } 19 | } 20 | ) 21 | ).toEqual({ 22 | id: 1, 23 | name: 'name' 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/test/reducers/configTest.js: -------------------------------------------------------------------------------- 1 | import { configReducer } from '../..//reducers/config'; 2 | import * as ActionTypes from '../..//constants/ActionTypes'; 3 | 4 | describe('Config Reducer', () => { 5 | it('should return the initial state', () => { 6 | expect(configReducer(undefined, {})).toMatchSnapshot(); 7 | }); 8 | 9 | it('should handle CONFIG_FETCH', () => { 10 | expect( 11 | configReducer(undefined, { 12 | type: ActionTypes.CONFIG_FETCH 13 | }) 14 | ).toMatchSnapshot(); 15 | }); 16 | 17 | it('should handle CONFIG_FETCH_SUCCESS', () => { 18 | expect( 19 | configReducer(undefined, { 20 | type: ActionTypes.CONFIG_FETCH_SUCCESS 21 | }) 22 | ).toMatchSnapshot(); 23 | }); 24 | 25 | it('should handle CONFIG_FETCH_ERROR', () => { 26 | expect( 27 | configReducer(undefined, { 28 | type: ActionTypes.CONFIG_FETCH_ERROR 29 | }) 30 | ).toMatchSnapshot(); 31 | }); 32 | 33 | it('should handle CONFIG_UPDATE_BY_KEY', () => { 34 | expect( 35 | configReducer(undefined, { 36 | type: ActionTypes.CONFIG_UPDATE_BY_KEY, 37 | key: 'key', 38 | value: 'value' 39 | }) 40 | ).toMatchSnapshot(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/test/reducers/zonePurgeCacheTest.js: -------------------------------------------------------------------------------- 1 | import { zonePurgeCacheReducer } from '../..//reducers/zonePurgeCache'; 2 | import * as ActionTypes from '../..//constants/ActionTypes'; 3 | 4 | let initialState = { 5 | isFetching: false 6 | }; 7 | 8 | describe('Plugin Settings Reducer', () => { 9 | it('should return the initial state', () => { 10 | expect(zonePurgeCacheReducer(initialState, {})).toEqual(initialState); 11 | }); 12 | 13 | it('should handle ZONE_PURGE_CACHE', () => { 14 | expect( 15 | zonePurgeCacheReducer(initialState, { 16 | type: ActionTypes.ZONE_PURGE_CACHE 17 | }) 18 | ).toEqual({ 19 | isFetching: true 20 | }); 21 | }); 22 | 23 | it('should handle ZONE_PURGE_CACHE_SUCCESS', () => { 24 | expect( 25 | zonePurgeCacheReducer(initialState, { 26 | type: ActionTypes.ZONE_PURGE_CACHE_SUCCESS 27 | }) 28 | ).toEqual({ 29 | isFetching: false 30 | }); 31 | }); 32 | 33 | it('should handle ZONE_PURGE_CACHE_ERROR', () => { 34 | expect( 35 | zonePurgeCacheReducer(initialState, { 36 | type: ActionTypes.ZONE_PURGE_CACHE_ERROR 37 | }) 38 | ).toEqual({ 39 | isFetching: false 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/test/selectors/configTest.js: -------------------------------------------------------------------------------- 1 | import { getAbsoluteUrl, getConfigValue } from '../..//selectors/config'; 2 | import { ABSOLUTE_URL_BASE_KEY } from '../..//reducers/config.js'; 3 | 4 | describe('Config Selector', () => { 5 | it('getAbsoluteUrl should concatenate the absolute URL', () => { 6 | expect( 7 | getAbsoluteUrl( 8 | { 9 | config: { 10 | [ABSOLUTE_URL_BASE_KEY]: 'http://site.com/' 11 | } 12 | }, 13 | 'test.html' 14 | ) 15 | ).toBe('http://site.com/test.html'); 16 | }); 17 | 18 | it('getAbsoluteUrl should return url if no absolute url base is present', () => { 19 | expect( 20 | getAbsoluteUrl( 21 | { 22 | config: {} 23 | }, 24 | 'test.html' 25 | ) 26 | ).toBe('test.html'); 27 | }); 28 | 29 | it('getConfigValue should return value if key exists', () => { 30 | expect( 31 | getConfigValue( 32 | { 33 | config: { 34 | key: 'value' 35 | } 36 | }, 37 | 'key' 38 | ) 39 | ).toBe('value'); 40 | }); 41 | 42 | it('isDNSPageEnabled should return true if key is true', () => { 43 | expect( 44 | getConfigValue( 45 | { 46 | config: { 47 | isDNSPageEnabled: true 48 | } 49 | }, 50 | 'isDNSPageEnabled' 51 | ) 52 | ).toBeTruthy(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/test/utils/PluginAPITest.js: -------------------------------------------------------------------------------- 1 | import { pluginResponseOk } from '../..//utils/PluginAPI/PluginAPI'; 2 | 3 | describe('PluginAPI', () => { 4 | it('pluginResponseOk should return true for valid response', () => { 5 | expect( 6 | pluginResponseOk({ 7 | body: { 8 | success: true 9 | } 10 | }) 11 | ).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/test/utils/deduplicateZonesTest.js: -------------------------------------------------------------------------------- 1 | import { deduplicateOnActiveZones } from '../../utils/utils'; 2 | import expect from 'expect'; 3 | 4 | describe('deduplicateZones', () => { 5 | it('should not change a list of zones without any duplicate', () => { 6 | expect( 7 | deduplicateOnActiveZones([ 8 | { id: '1', name: 'cloudflare.com', status: 'active' }, 9 | { id: '2', name: 'blog.cloudflare.com', status: 'active' } 10 | ]) 11 | ).toEqual([ 12 | { id: '1', name: 'cloudflare.com', status: 'active' }, 13 | { id: '2', name: 'blog.cloudflare.com', status: 'active' } 14 | ]); 15 | }); 16 | 17 | it('should remove non-active zone in case of duplicates', () => { 18 | expect( 19 | deduplicateOnActiveZones([ 20 | { id: '1', name: 'cloudflare.com', status: 'active' }, 21 | { id: '2', name: 'blog.cloudflare.com', status: 'active' }, 22 | { id: '3', name: 'cloudflare.com', status: 'purged' } 23 | ]) 24 | ).toEqual([ 25 | { id: '1', name: 'cloudflare.com', status: 'active' }, 26 | { id: '2', name: 'blog.cloudflare.com', status: 'active' } 27 | ]); 28 | }); 29 | 30 | it('should not remove duplicates when there is no active zone', () => { 31 | expect( 32 | deduplicateOnActiveZones([ 33 | { id: '1', name: 'cloudflare.com', status: 'pending' }, 34 | { id: '2', name: 'blog.cloudflare.com', status: 'active' }, 35 | { id: '3', name: 'cloudflare.com', status: 'purged' } 36 | ]) 37 | ).toEqual([ 38 | { id: '1', name: 'cloudflare.com', status: 'pending' }, 39 | { id: '2', name: 'blog.cloudflare.com', status: 'active' }, 40 | { id: '3', name: 'cloudflare.com', status: 'purged' } 41 | ]); 42 | }); 43 | 44 | it('should remove multiple duplicates when there is one active zone', () => { 45 | expect( 46 | deduplicateOnActiveZones([ 47 | { id: '1', name: 'cloudflare.com', status: 'pending' }, 48 | { id: '2', name: 'cloudflare.com', status: 'purged' }, 49 | { id: '3', name: 'cloudflare.com', status: 'active' }, 50 | { id: '4', name: 'cloudflare.com', status: 'moved' } 51 | ]) 52 | ).toEqual([{ id: '3', name: 'cloudflare.com', status: 'active' }]); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/utils/Auth/Auth.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export function isLoggedIn() { 4 | if (_.isEmpty(localStorage.cfEmail)) { 5 | return false; 6 | } 7 | return true; 8 | } 9 | 10 | export function getEmail() { 11 | return localStorage.cfEmail; 12 | } 13 | 14 | export function setEmail(email) { 15 | localStorage.cfEmail = email; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/ImportCards.js: -------------------------------------------------------------------------------- 1 | // Cards 2 | import AdvanceDDoSCard from '../containers/AdvanceDDoSCard/AdvanceDDoSCard'; 3 | import AlwaysOnlineCard from '../containers/AlwaysOnlineCard/AlwaysOnlineCard'; 4 | import ApplyDefaultSettingsCard from '../containers/ApplyDefaultSettingsCard/ApplyDefaultSettingsCard'; 5 | import BrowserCacheTTLCard from '../containers/BrowserCacheTTLCard/BrowserCacheTTLCard'; 6 | import BrowserIntegrityCheckCard from '../containers/BrowserIntegrityCheckCard/BrowserIntegrityCheckCard'; 7 | import CacheLevelCard from '../containers/CacheLevelCard/CacheLevelCard'; 8 | import ChallengePassageCard from '../containers/ChallengePassageCard/ChallengePassageCard'; 9 | import DevelopmentModeCard from '../containers/DevelopmentModeCard/DevelopmentModeCard'; 10 | import IPV6Card from '../containers/IPV6Card/IPV6Card'; 11 | import ImageOptimizationCard from '../containers/ImageOptimizationCard/ImageOptimizationCard'; 12 | import IpRewriteCard from '../containers/IpRewriteCard/IpRewriteCard'; 13 | import PurgeCacheCard from '../containers/PurgeCacheCard/PurgeCacheCard'; 14 | import SSLCard from '../containers/SSLCard/SSLCard'; 15 | import SecurityLevelCard from '../containers/SecurityLevelCard/SecurityLevelCard'; 16 | import WAFCard from '../containers/WAFCard/WAFCard'; 17 | import PluginSpecificCacheCard from '../containers/PluginSpecificCacheCard/PluginSpecificCacheCard'; 18 | import PluginSpecificCacheTagCard from '../containers/PluginSpecificCacheTagCard/PluginSpecificCachetTagCard.js'; 19 | import AutomaticHTTPSRewritesCard from '../containers/AutomaticHTTPSRewritesCard/AutomaticHTTPSRewritesCard'; 20 | import AutomaticPlatformOptimizationCard from '../containers/AutomaticPlatformOptimization/AutomaticPlatformOptimizationCard'; 21 | 22 | // Pages 23 | import DNSManagementPage from '../containers/DNSManagementPage/DNSManagementPage'; 24 | 25 | const cardMapper = { 26 | AdvanceDDoSCard: AdvanceDDoSCard, 27 | AlwaysOnlineCard: AlwaysOnlineCard, 28 | ApplyDefaultSettingsCard: ApplyDefaultSettingsCard, 29 | AutomaticHTTPSRewritesCard: AutomaticHTTPSRewritesCard, 30 | AutomaticPlatformOptimizationCard: AutomaticPlatformOptimizationCard, 31 | BrowserCacheTTLCard: BrowserCacheTTLCard, 32 | BrowserIntegrityCheckCard: BrowserIntegrityCheckCard, 33 | CacheLevelCard: CacheLevelCard, 34 | ChallengePassageCard: ChallengePassageCard, 35 | DNSManagementPage: DNSManagementPage, 36 | DevelopmentModeCard: DevelopmentModeCard, 37 | IPV6Card: IPV6Card, 38 | ImageOptimizationCard: ImageOptimizationCard, 39 | IpRewriteCard: IpRewriteCard, 40 | PluginSpecificCacheCard: PluginSpecificCacheCard, 41 | PluginSpecificCacheTagCard: PluginSpecificCacheTagCard, 42 | PurgeCacheCard: PurgeCacheCard, 43 | SSLCard: SSLCard, 44 | SecurityLevelCard: SecurityLevelCard, 45 | WAFCard: WAFCard 46 | }; 47 | 48 | export { cardMapper }; 49 | -------------------------------------------------------------------------------- /src/utils/PluginAPI/PluginAPI.js: -------------------------------------------------------------------------------- 1 | import http from 'cf-util-http'; 2 | import { 3 | v4ResponseOk, 4 | v4Callback 5 | } from '../../utils/CFClientV4API/CFClientV4API'; 6 | 7 | /* 8 | * This endpoint isn't real but we'll use it to identify REST calls for 9 | * plugin specific functionality like saving a v4 API key/email or toggling 10 | * admin settings. The structure of this API's responses will mimic the client V4 API. 11 | */ 12 | const ENDPOINT = 'https://partners.cloudflare/plugins'; 13 | 14 | /* 15 | * Indicates api call success 16 | * 17 | * @param {Object} [response] 18 | * 19 | * @returns {Boolean} Successful 20 | */ 21 | export function pluginResponseOk(response) { 22 | return v4ResponseOk(response); 23 | } 24 | 25 | export function pluginCallback(callback) { 26 | return v4Callback(callback); 27 | } 28 | 29 | export function pluginAccountPost(email, apiKey, callback) { 30 | let opts = { 31 | body: { 32 | email: email.toString().trim(), 33 | apiKey: apiKey.toString().trim() 34 | } 35 | }; 36 | return http.post(ENDPOINT + '/account/', opts, pluginCallback(callback)); 37 | } 38 | 39 | export function pluginSettingListGet(zoneId, callback) { 40 | let opts = {}; 41 | 42 | return http.get( 43 | ENDPOINT + '/plugin/' + zoneId['zoneId'] + '/settings/', 44 | opts, 45 | pluginCallback(callback) 46 | ); 47 | } 48 | 49 | export function pluginSettingPatch(zoneId, settingName, value, callback) { 50 | let opts = { 51 | body: { 52 | value: value 53 | } 54 | }; 55 | 56 | return http.patch( 57 | ENDPOINT + '/plugin/' + zoneId + '/settings/' + settingName, 58 | opts, 59 | pluginCallback(callback) 60 | ); 61 | } 62 | 63 | export function configGet(callback) { 64 | let opts = {}; 65 | return http.get(ENDPOINT + '/config/', opts, pluginCallback(callback)); 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | function beginsWith(needle, haystack) { 2 | return haystack.substr(0, needle.length) == needle; 3 | } 4 | 5 | function endsWith(str, suffix) { 6 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 7 | } 8 | 9 | export function isSubdomain(selectedZoneName) { 10 | var currentDomainName = new URL(document.URL).hostname; 11 | 12 | if ( 13 | endsWith(currentDomainName, selectedZoneName) && 14 | !beginsWith('www.', currentDomainName) && 15 | selectedZoneName !== currentDomainName && 16 | currentDomainName && 17 | selectedZoneName 18 | ) { 19 | return true; 20 | } 21 | 22 | return false; 23 | } 24 | 25 | export function getLastModifiedDate(intl, modfiedDate) { 26 | const { formatMessage, formatRelative } = intl; 27 | 28 | if (!modfiedDate) { 29 | // Once you get the new code try this 30 | return null; 31 | } 32 | 33 | var formattedModefiedDate = formatRelative(new Date(modfiedDate), { 34 | now: Date.now() 35 | }); 36 | 37 | var value = { date: formattedModefiedDate }; 38 | return formatMessage({ id: 'utils.utils.lastmodifieddate' }, value); 39 | } 40 | 41 | export function humanFileSize(bytes) { 42 | var thresh = 1000; 43 | if (Math.abs(bytes) < thresh) { 44 | return bytes + ' B'; 45 | } 46 | var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 47 | var u = -1; 48 | do { 49 | bytes /= thresh; 50 | ++u; 51 | } while (Math.abs(bytes) >= thresh && u < units.length - 1); 52 | return bytes.toFixed(1) + ' ' + units[u]; 53 | } 54 | 55 | export function openWindow720x720(link) { 56 | window.open(link, '_blank', 'toolbar=0,status=0,width=720,height=700'); 57 | } 58 | 59 | export function formatMessageForIntegration( 60 | intl, 61 | translationId, 62 | integrationName 63 | ) { 64 | const { formatMessage } = intl; 65 | 66 | let integrationKey = translationId + '.' + integrationName; 67 | const messageId = !!intl.messages[integrationKey] 68 | ? integrationKey 69 | : translationId; 70 | 71 | return formatMessage({ id: messageId }); 72 | } 73 | 74 | // deduplicateOnActiveZones was introduced as a potential fix for CUSTESC-36595. The goal of this function is to 75 | // deduplicate the list of zones returned by the Cloudflare API based on the status of the zone. This way, we hope to 76 | // guarantee that when normalizing the list of zones, only the active zone would show up in case of duplicates. For 77 | // instance, in the mentioned tickets, two zones were potentially returned: a purged as well as an active zone. 78 | export function deduplicateOnActiveZones(zones) { 79 | let filteredZones = []; 80 | const isActive = z => z.status === 'active'; 81 | const isSameZone = (z1, z2) => z1.name === z2.name; 82 | 83 | zones.forEach(zone => { 84 | if (isActive(zone)) { 85 | // If the zone is active, we first remove all existing duplicates and then insert the active zone. 86 | filteredZones = filteredZones.filter(fZone => !isSameZone(zone, fZone)); 87 | filteredZones.push(zone); 88 | } else { 89 | // If the zone is not active, we only insert it if there is no existing active zone. 90 | const alreadyHasActiveZone = filteredZones.some( 91 | fZone => isSameZone(zone, fZone) && isActive(fZone) 92 | ); 93 | if (!alreadyHasActiveZone) { 94 | filteredZones.push(zone); 95 | } 96 | } 97 | }); 98 | 99 | return filteredZones; 100 | } 101 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | var isDev = process.env.NODE_ENV !== 'production'; 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | mode: isDev ? 'development' : 'production', 8 | devtool: isDev ? 'cheap-module-source-map' : false, 9 | output: { 10 | path: __dirname, 11 | filename: process.env.OUTPUT_PATH 12 | ? process.env.OUTPUT_PATH 13 | : './compiled.js' 14 | }, 15 | watch: isDev, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | loader: 'babel-loader' 22 | } 23 | ] 24 | } 25 | }; 26 | --------------------------------------------------------------------------------