├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── bin
└── copy-core.js
├── deploy
└── deploy.js
├── docs
├── TODO.md
└── wip-tests.md
├── jsconfig.json
├── package-lock.json
├── package.json
├── webapp
├── css
│ ├── SyntaxHighlighter.css
│ ├── aboutTab.css
│ ├── domTab.css
│ ├── domTree.css
│ ├── dragdrop.css
│ ├── harPreview.css
│ ├── harStats.css
│ ├── harView.css
│ ├── harViewer.css
│ ├── highlightjs
│ │ ├── default.min.css
│ │ └── tomorrow.min.css
│ ├── homeTab.css
│ ├── images
│ │ ├── ajax-loader.gif
│ │ ├── bg-button.gif
│ │ ├── blank.gif
│ │ ├── button-background.png
│ │ ├── checkmark.gif
│ │ ├── checkmark.png
│ │ ├── close-sprites.png
│ │ ├── contextMenuTarget.png
│ │ ├── contextMenuTargetHover.png
│ │ ├── download-sprites.png
│ │ ├── downloadButtons-aero.png
│ │ ├── group.gif
│ │ ├── loading_16.gif
│ │ ├── menu
│ │ │ ├── previewMenuHandle.png
│ │ │ ├── shadowAlpha.png
│ │ │ ├── tabMenuCheckbox.png
│ │ │ ├── tabMenuPin.png
│ │ │ └── tabMenuRadio.png
│ │ ├── netBarBlocking.gif
│ │ ├── netBarBlocking2.gif
│ │ ├── netBarCached.gif
│ │ ├── netBarConnecting.gif
│ │ ├── netBarLoaded.gif
│ │ ├── netBarReceiving.gif
│ │ ├── netBarResolving.gif
│ │ ├── netBarResponded.gif
│ │ ├── netBarSending.gif
│ │ ├── netBarWaiting.gif
│ │ ├── page-timeline.png
│ │ ├── save.png
│ │ ├── splitterh.png
│ │ ├── spriteArrows.gif
│ │ ├── spriteArrows.png
│ │ ├── tabEnabled.png
│ │ ├── timeline-sprites.png
│ │ ├── tooltipConnectorUp.png
│ │ └── twisty-sprites.png
│ ├── infoTip.css
│ ├── pageList.css
│ ├── pageTimeline.css
│ ├── popupMenu.css
│ ├── previewMenu.css
│ ├── previewTab.css
│ ├── requestBody.css
│ ├── requestList.css
│ ├── schemaTab.css
│ ├── search.css
│ ├── tabView.css
│ ├── tableView.css
│ ├── toolTip.css
│ ├── toolbar.css
│ ├── validationError.css
│ └── xhrSpy.css
├── demo.js
├── demos
│ └── demo-shell.html
├── examples
│ ├── browser-blocking-time.har
│ ├── embed.html
│ ├── google.com.har
│ ├── inline-scripts-block.har
│ ├── inline-scripts-block.harp
│ ├── inline-scripts-block.harp.js
│ └── softwareishard.com.har
├── har.js
├── highlightjs
│ └── highlight.min.js
├── index.html
├── loader.html
├── main.js
├── modules
│ ├── App.js
│ ├── AppContext.js
│ ├── Demo.js
│ ├── InfoTip.js
│ ├── InfoTipHolder.js
│ ├── Stats.js
│ ├── booleanFlipper.js
│ ├── buildInfo.js
│ ├── core
│ │ ├── StatsService.js
│ │ ├── array.js
│ │ ├── cookies.js
│ │ ├── css.js
│ │ ├── date.js
│ │ ├── dom.js
│ │ ├── dragdrop.js
│ │ ├── events.js
│ │ ├── json.js
│ │ ├── lib.js
│ │ ├── mime.js
│ │ ├── object.js
│ │ ├── rect.js
│ │ ├── sniff.js
│ │ ├── string.js
│ │ ├── trace.js
│ │ └── url.js
│ ├── deferred.js
│ ├── intersperse.js
│ ├── json-query
│ │ └── JSONQuery.js
│ ├── nettable
│ │ ├── Bar.js
│ │ ├── NetInfoRow.js
│ │ ├── NetRow.js
│ │ ├── NetSummaryRow.js
│ │ ├── NetSummaryRowModel.js
│ │ ├── NetTable.js
│ │ ├── PageTimingBar.js
│ │ ├── Phases.js
│ │ ├── defaultTimingDefinitions.js
│ │ └── testData.js
│ ├── nls
│ │ ├── domTab.js
│ │ ├── harModel.js
│ │ ├── harStats.js
│ │ ├── harViewer.js
│ │ ├── homeTab.js
│ │ ├── pageList.js
│ │ ├── pageTimeline.js
│ │ ├── previewTab.js
│ │ ├── requestBody.js
│ │ ├── requestList.js
│ │ ├── search.js
│ │ └── tableView.js
│ ├── pagetable
│ │ ├── PageInfoRow.js
│ │ ├── PageRow.js
│ │ └── PageTable.js
│ ├── pagetimeline
│ │ ├── PageDescContainer.js
│ │ ├── PageTimeline.js
│ │ ├── PageTimelineCol.js
│ │ ├── PageTimelineTable.js
│ │ └── Selection.js
│ ├── pie
│ │ ├── CachePie.js
│ │ ├── ContentPie.js
│ │ ├── Pie.js
│ │ ├── TimingPie.js
│ │ └── TrafficPie.js
│ ├── preview
│ │ ├── harModel.js
│ │ ├── harModelLoader.js
│ │ ├── harSchema.js
│ │ ├── jsonSchema.js
│ │ └── ref.js
│ ├── requestbodies
│ │ ├── DataURL.js
│ │ ├── ExternalImage.js
│ │ ├── Headers.js
│ │ ├── Highlighted.js
│ │ ├── Image.js
│ │ ├── JSONEntryTree.js
│ │ ├── ParamRow.js
│ │ ├── PlainResponse.js
│ │ ├── SendData.js
│ │ ├── TitleRow.js
│ │ ├── URLParameters.js
│ │ ├── XMLEntryTree.js
│ │ └── decoder.js
│ ├── setState.js
│ ├── tabs
│ │ ├── AboutTab.js
│ │ ├── HomeTab.js
│ │ ├── ObjectSearch.js
│ │ ├── PreviewTab.js
│ │ ├── PreviewTabToolbar.js
│ │ ├── SchemaTab.js
│ │ ├── aboutTab.html
│ │ ├── dom
│ │ │ ├── DOMBox.js
│ │ │ ├── DOMTab.js
│ │ │ └── SearchBox.js
│ │ ├── homeTab.html
│ │ └── preview
│ │ │ ├── PreviewList.js
│ │ │ └── ValidationError.js
│ ├── tabview
│ │ ├── Tab.js
│ │ ├── TabBar.js
│ │ ├── TabBodies.js
│ │ ├── TabBody.js
│ │ └── TabView.js
│ ├── timeinfotip
│ │ └── TimeInfoTip.js
│ ├── toolbar
│ │ └── Toolbar.js
│ └── tree
│ │ ├── ObjectTree.js
│ │ ├── Tree.js
│ │ ├── TreeRepresentations.js
│ │ └── XMLTree.js
└── preview.html
└── webpack.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Don't lint dist
2 | webapp/dist/**/*
3 |
4 | # Don't lint the old code yet
5 | webapp/har.js
6 | webapp/modules/**/nls/*
7 | webapp/modules/core/**/*
8 | webapp/modules/preview/harModel.js
9 | webapp/modules/preview/harModelLoader.js
10 | webapp/modules/preview/harSchema.js
11 | webapp/modules/preview/jsonSchema.js
12 | webapp/modules/preview/ref.js
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "google",
5 | "plugin:react/recommended",
6 | ],
7 | "settings": {
8 | "react": {
9 | "version": "16.6.0",
10 | },
11 | },
12 | "plugins": [
13 | "babel",
14 | "react",
15 | ],
16 | "rules": {
17 | "indent": ["error", 2],
18 | "linebreak-style": "off",
19 |
20 | "max-len": ["warn", {
21 | "code": 100,
22 | "tabWidth": 2,
23 | "ignoreUrls": true,
24 | // JSX expression: Line starting with spaces then "<"
25 | // JSX assignment: Line containing " = <\w"
26 | // JSX return: Line starting with "return <\w"
27 | "ignorePattern": "^ *<| = <\\w|^ *return <\\w",
28 | }],
29 |
30 | // E.g. to allow
31 | //
this.holder = ref}>
32 | "no-return-assign": "warn",
33 |
34 | "object-curly-spacing": ["error", "always"],
35 |
36 | "quotes": ["error", "double", { "allowTemplateLiterals": true }],
37 |
38 | "require-jsdoc": "off",
39 |
40 | // Use "babel/no-invalid-this" because we use ES7 class properties
41 | "no-invalid-this": "off",
42 | "babel/no-invalid-this": "error",
43 | },
44 | "env": {
45 | "browser": true,
46 | "jquery": true,
47 | },
48 | "parserOptions": {
49 | "ecmaVersion": 6,
50 | "sourceType": "module",
51 | "ecmaFeatures": {
52 | "jsx": true,
53 | "classes": true,
54 | "defaultParams": true,
55 | "experimentalObjectRestSpread": true
56 | },
57 | },
58 | };
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | typings/
3 | temp/
4 | dist/
5 | *.log
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "editor.detectIndentation": false,
4 | "editor.tabSize": 2,
5 | "javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false
6 | }
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 gitgrimbo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # harviewer-react
2 |
3 |
4 |
5 | WARNING - harviewer-react is under development. Until a 1.0.0 release, branches can and will be rebased (I will experiment with things and remove things).
6 |
7 | As this repo currently only has a single contributor (me) this isn't a problem.
8 |
9 | If you would like to start contributing, then I will choose a less destructive approach.
10 |
11 |
12 |
13 | ---
14 |
15 | harviewer-react is [HAR Viewer](https://github.com/janodvarko/harviewer)
16 | implemented in [React](https://facebook.github.io/react/)!
17 |
18 | Use it at [https://gitgrimbo.github.io/harviewer-react/](https://gitgrimbo.github.io/harviewer-react/).
19 |
20 | # Develop
21 |
22 | I use [Visual Studio Code](https://code.visualstudio.com).
23 |
24 | # Running the code
25 |
26 | harviewer-react uses [webpack](https://webpack.github.io/).
27 |
28 | ## Setup
29 |
30 | Using [npm](https://www.npmjs.com/), pull in the required packages with:
31 |
32 | npm install
33 |
34 | ## Start
35 |
36 | To start the [webpack-dev-server](https://webpack.github.io/docs/webpack-dev-server.html), use:
37 |
38 | npm start
39 |
40 | The Demo Shell will now be available at:
41 |
42 | - http://localhost:8080/webapp/demos/demo-shell.html
43 |
44 | And the main app will be available at:
45 |
46 | - http://localhost:8080/webapp/
47 |
48 | # Deploy
49 |
50 | npm run deploy-gh-pages
51 |
52 | This will build the production version of harviewer-react and deploy it as a
53 | [GitHub pages](https://pages.github.com/) site.
54 |
--------------------------------------------------------------------------------
/bin/copy-core.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copies code from old HAR Viewer to new.
3 | */
4 | const fs = require("fs-extra");
5 | const path = require("path");
6 | const mkdirp = require("mkdirp");
7 | const glob = require("glob");
8 |
9 | const args = process.argv.slice(2);
10 | const hvDir = args[0] || "../harviewer/";
11 |
12 | const copyConfig = {
13 | scripts: {
14 | from: path.resolve(hvDir, "webapp", "scripts"),
15 | to: path.resolve(__dirname, "../webapp", "modules"),
16 | patterns: [
17 | "core/**",
18 | "nls/**",
19 | "tabs/*.html",
20 | "tabs/ObjectSearch.js",
21 | "preview/**",
22 | "json-query/JSONQuery.js",
23 | ],
24 | },
25 | css: {
26 | from: path.resolve(hvDir, "webapp", "css"),
27 | to: path.resolve(__dirname, "../webapp", "css"),
28 | patterns: [
29 | "*.css",
30 | ],
31 | },
32 | };
33 |
34 | Object.keys(copyConfig).forEach((configKey) => {
35 | console.log(`Copying files for "${configKey}"`);
36 | const config = copyConfig[configKey];
37 |
38 | if (!fs.pathExistsSync(config.from)) {
39 | throw new Error(`from path "${config.from}" does not exist`);
40 | }
41 | if (!fs.pathExistsSync(config.to)) {
42 | throw new Error(`to path "${config.to}" does not exist`);
43 | }
44 |
45 | config.patterns.forEach((pattern) => {
46 | const files = glob.sync(pattern, {
47 | cwd: config.from,
48 | });
49 | files.forEach((file) => {
50 | const src = path.resolve(config.from, file);
51 | const dest = path.resolve(config.to, file);
52 | if (fs.statSync(src).isFile()) {
53 | mkdirp.sync(path.dirname(dest));
54 | fs.copySync(src, dest);
55 | console.log(`Copied ${src} to ${dest}`);
56 | }
57 | });
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/deploy/deploy.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node, shelljs */
2 | const fs = require("fs");
3 | const path = require("path");
4 | require("shelljs/global");
5 |
6 | const ghTemp = "./temp/gh-pages/";
7 | const repo = require("../package.json").repository.url;
8 |
9 | set("+v");
10 |
11 | if (!which("git")) {
12 | echo("git is required");
13 | exit(1);
14 | }
15 |
16 |
17 | // Clean and create the staging folder, ghTemp.
18 |
19 | rm("-rf", ghTemp);
20 | mkdir("-p", ghTemp);
21 |
22 | if (exec("git clone " + repo + " --branch gh-pages --single-branch " + ghTemp).code !== 0) {
23 | echo("git clone failed");
24 | exit(1);
25 | }
26 |
27 |
28 | // Run the build and copy the built resources to staging
29 |
30 | if (exec("npm run build:prod").code !== 0) {
31 | echo("webpack build failed");
32 | exit(1);
33 | }
34 |
35 | cp("-r", "./dist/*", ghTemp);
36 |
37 |
38 | // Copy the webapp to staging
39 |
40 | cp("-r", "./webapp/*", ghTemp);
41 |
42 |
43 | // Copy other required node_modules to staging
44 |
45 | function copyAndPreserveFolder(dest, folder, file) {
46 | const src = path.join(folder, file);
47 | mkdir("-p", path.join(dest, folder));
48 | cp(src, path.join(dest, folder));
49 | }
50 |
51 | copyAndPreserveFolder(ghTemp, "node_modules/jquery/dist", "jquery.js");
52 | copyAndPreserveFolder(ghTemp, "node_modules/urijs/src", "URI.js");
53 |
54 |
55 | // Fix the path differences between dev and prod.
56 |
57 | function fixLinks(file) {
58 | // When deploying we don't want to move 'out' of the webapp to locate the
59 | // node_modules folder.
60 | let str = fs.readFileSync(file, { encoding: "utf-8" });
61 | str = str.replace("../node_modules/", "node_modules/");
62 | fs.writeFileSync(file, str);
63 | }
64 |
65 | fixLinks(path.join(ghTemp, "index.html"));
66 | fixLinks(path.join(ghTemp, "demos", "demo-shell.html"));
67 |
68 | // https://github.com/blog/572-bypassing-jekyll-on-github-pages
69 | touch(path.join(ghTemp, ".nojekyll"));
70 |
71 | // Add the staging files to the gh-pages branch
72 |
73 | cd(ghTemp);
74 |
75 | exec("git add .");
76 | exec("git commit --amend --no-edit");
77 | exec("git push -f origin gh-pages");
78 |
--------------------------------------------------------------------------------
/docs/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - remove jQuery?
4 |
--------------------------------------------------------------------------------
/docs/wip-tests.md:
--------------------------------------------------------------------------------
1 | # General questions
2 |
3 | - Is there a simple way for test fixture HTML pages to support both old and new?
4 | - Unlikely as they import different resources. e.g.:
5 | - old - jquery/requirejs/old harviewer
6 | - new - jquery/vendor.js/main.js/prismjs
7 |
8 | ## testRemoveTab
9 |
10 | Reasons for failure
11 | - test page `testRemoveTabIndex.html` is legacy, doesn't import hv-react in the new way.
12 |
13 | ## testHideTabBar
14 |
15 | Reasons for failure
16 | - test page `testHideTabBarIndex.html` is legacy, doesn't import hv-react in the new way.
17 |
18 | ## testShowStatsAndTimeline
19 |
20 | Reasons for failure
21 | - test page `testShowStatsAndTimelineIndex.html` is legacy, doesn't import hv-react in the new way.
22 |
23 | ## testCustomPageTiming
24 |
25 | Reasons for failure
26 | - test page `testCustomPageTimingIndex.html` is legacy, doesn't import hv-react in the new way.
27 |
28 | ## testPhases
29 |
30 | Reasons for failure
31 | - phases aren't supported yet in hv-react.
32 |
33 | ## testLoadHarAPI
34 |
35 | Reasons for failure
36 | - test pages `testLoadHarAPIViewer.html`, `testLoadArchives.html`, `testLoadHarAPIPreview.html` is legacy, doesn't import hv-react in the new way.
37 |
38 | ## testCustomizeColumns
39 |
40 | Reasons for failure
41 | - test pages `testCustomizeColumnsPage.html`, `testCustomizeColumnsPage2.html`, `testCustomizeColumnsPage3.html` is legacy, doesn't import hv-react in the new way.
42 |
43 | # Failing tests
44 |
45 | ```
46 | Total: [✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓××××××✓] 144/144
47 | Passed: 128 Failed: 16 Skipped: 0
48 |
49 | Chr 71 Win: [✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓××××××✓] 144/144, 16 fail
50 |
51 | × chrome 71.0.3578.80 on Windows NT - testRemoveTab - testRemoveTab
52 | × chrome 71.0.3578.80 on Windows NT - testHideTabBar - testHideTabBar
53 | × chrome 71.0.3578.80 on Windows NT - testShowStatsAndTimeline - testShowStatsAndTimeline
54 | × chrome 71.0.3578.80 on Windows NT - testCustomPageTiming - testCustomPageTiming
55 | × chrome 71.0.3578.80 on Windows NT - testPhases - testPhases
56 | × chrome 71.0.3578.80 on Windows NT - testLoadHarAPI - testViewer
57 | × chrome 71.0.3578.80 on Windows NT - testLoadHarAPI - testViewer loadArchives
58 | × chrome 71.0.3578.80 on Windows NT - testLoadHarAPI - testPreview
59 | × chrome 71.0.3578.80 on Windows NT - testCustomizeColumns - 1
60 | × chrome 71.0.3578.80 on Windows NT - testCustomizeColumns - 2
61 | × chrome 71.0.3578.80 on Windows NT - testCustomizeColumns - 3
62 | × chrome 71.0.3578.80 on Windows NT - testSearchHAR - testSearchHAR
63 | × chrome 71.0.3578.80 on Windows NT - testPreviewExpand - testExpandSinglePage
64 | × chrome 71.0.3578.80 on Windows NT - testPreviewExpand - testExpandMultiplePages
65 | × chrome 71.0.3578.80 on Windows NT - testPreviewExpand - testExpandByDefault
66 | × chrome 71.0.3578.80 on Windows NT - testSearchJsonQuery - testSearchJsonQuery
67 | ```
68 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "harviewer-react",
3 | "version": "1.0.0",
4 | "description": "HAR Viewer built using React",
5 | "main": "index.js",
6 | "scripts": {
7 | "copy:core": "node bin/copy-core.js ../../harviewer2/",
8 | "deploy-gh-pages": "node deploy/deploy.js",
9 | "lint": "eslint ./webapp",
10 | "start": "webpack-dev-server --disableHostCheck=true --inline --hot --compress --content-base ./",
11 | "test": "echo \"Error: no test specified\" && exit 1",
12 | "build": "webpack --mode=development",
13 | "build:prod": "webpack --mode=production"
14 | },
15 | "author": {
16 | "name": "gitgrimbo",
17 | "email": "gitgrimbo@gmail.com",
18 | "url": "https://github.com/gitgrimbo"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/gitgrimbo/harviewer-react.git"
23 | },
24 | "bugs": "https://github.com/gitgrimbo/harviewer-react/issues",
25 | "license": "MIT",
26 | "devDependencies": {
27 | "@babel/core": "^7.2.2",
28 | "@babel/preset-env": "^7.3.1",
29 | "@babel/preset-react": "^7.0.0",
30 | "amdi18n-loader": "^0.9.1",
31 | "babel-core": "^6.26.3",
32 | "babel-eslint": "^10.0.1",
33 | "babel-loader": "^8.0.5",
34 | "babel-plugin-transform-class-properties": "^6.24.1",
35 | "babel-plugin-transform-object-assign": "^6.22.0",
36 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
37 | "babel-plugin-transform-runtime": "^6.23.0",
38 | "babel-preset-env": "^1.7.0",
39 | "babel-preset-es2015": "^6.24.1",
40 | "babel-preset-react": "^6.24.1",
41 | "bundle-loader": "^0.5.6",
42 | "core-js": "^2.6.3",
43 | "css-loader": "^2.1.0",
44 | "eslint": "^5.12.1",
45 | "eslint-config-google": "^0.11.0",
46 | "eslint-plugin-babel": "^5.3.0",
47 | "eslint-plugin-react": "^7.12.4",
48 | "file-loader": "^3.0.1",
49 | "fs-extra": "^7.0.1",
50 | "git-revision-webpack-plugin": "^3.0.3",
51 | "glob": "^7.1.3",
52 | "js-cookie": "^2.2.0",
53 | "mkdirp": "^0.5.1",
54 | "raw-loader": "^1.0.0",
55 | "shelljs": "^0.8.3",
56 | "style-loader": "^0.23.1",
57 | "urijs": "^1.19.1",
58 | "url-loader": "^1.1.2",
59 | "webpack": "^4.29.0",
60 | "webpack-bundle-analyzer": "^3.0.3",
61 | "webpack-cli": "^3.2.1",
62 | "webpack-dev-server": "^3.1.14"
63 | },
64 | "dependencies": {
65 | "@babel/polyfill": "^7.2.5",
66 | "babel-polyfill": "^6.26.0",
67 | "classnames": "^2.2.6",
68 | "file-saver": "^2.0.0",
69 | "jquery": "^3.3.1",
70 | "prop-types": "^15.6.2",
71 | "react": "^16.7.0",
72 | "react-dom": "^16.7.0",
73 | "react-router": "^4.3.1",
74 | "valuelink": "^1.5.5",
75 | "whatwg-fetch": "^3.0.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/webapp/css/aboutTab.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .AboutTab {
4 | margin-left: 100px;
5 | }
6 |
7 | .AboutTab .version {
8 | font-size: 11px;
9 | color: #DD467B;
10 | }
11 |
12 | .tabAboutBody {
13 | font-family: Verdana,Geneva,Arial,Helvetica,sans-serif;
14 | font-size: 11.7px;
15 | font-style: normal;
16 | font-weight: 400;
17 | }
18 |
19 | .aboutBody {
20 | padding: 8px;
21 | }
22 |
23 | .tabAboutBody code {
24 | color: green;
25 | }
26 |
--------------------------------------------------------------------------------
/webapp/css/domTab.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .tabDOMBody {
4 | font-family: Lucida Grande,Tahoma,sans-serif;
5 | font-size: 11px;
6 | }
7 |
8 | .tabDOMBody .domContent {
9 | position: absolute;
10 | top: 28px; /* tab view bar + toolbar */
11 | bottom: 0px;
12 | overflow: auto;
13 | width: 100%;
14 | }
15 |
16 | .tabDOMBody .domContent .domBox {
17 | width: 100%;
18 | }
19 |
20 | .tabDOMBody .domContent .domBox .title {
21 | color: gray;
22 | padding: 8px 0 0 8px;
23 | }
24 |
25 | .tabDOMBody .separator {
26 | border-bottom: 1px solid #D7D7D7;
27 | }
28 |
29 | .tabDOMBody .domTable {
30 | padding: 5px;
31 | }
32 |
33 | /*************************************************************************************************/
34 | /* Toolbar */
35 |
36 | .domToolbar > .toolbar {
37 | text-align: right;
38 | }
39 |
40 | /*************************************************************************************************/
41 | /* Search Results */
42 |
43 | .tabDOMBody .domContent .domBox .content,
44 | .tabDOMBody .domContent .domBox .results {
45 | vertical-align: top;
46 | }
47 |
48 | .resultsDefaultContent {
49 | color: #D7D7D7;
50 | font-size: 12px;
51 | font-family: Lucida Grande,Tahoma,sans-serif;
52 | margin: 60px;
53 | text-align: center;
54 | }
55 |
56 | .queryResultsViewType {
57 | border-bottom: 1px solid #EEEEEE;
58 | display: block;
59 | padding: 5px;
60 | }
61 |
62 | .queryResultsViewType .type {
63 | width: 13px;
64 | height: 13px;
65 | padding: 0;
66 | margin: 0;
67 | vertical-align: bottom;
68 | }
69 |
70 | .queryResultsViewType .label {
71 | padding-left: 5px;
72 | }
73 |
74 | /*************************************************************************************************/
75 | /* Splitter */
76 |
77 | .domBox .splitter {
78 | width: 4px;
79 | cursor: e-resize;
80 | background-color: #D7D7D7;
81 | }
82 |
83 | .domBox .splitter,
84 | .domBox .results {
85 | visibility: collapse;
86 | }
87 |
88 | .domBox .splitter.visible,
89 | .domBox .results.visible {
90 | visibility: visible;
91 | }
92 |
--------------------------------------------------------------------------------
/webapp/css/domTree.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .domTable {
4 | }
5 |
6 | .memberLabelCell {
7 | padding: 2px 50px 2px 0;
8 | vertical-align: top;
9 | }
10 |
11 | .memberValueCell {
12 | padding: 1px 0 1px 5px;
13 | display: block;
14 | overflow: hidden;
15 | }
16 |
17 | .memberLabel {
18 | cursor: default;
19 | -moz-user-select: none;
20 | overflow: hidden;
21 | /*position: absolute;*/
22 | padding-left: 18px;
23 | /*max-width: 30%;*/
24 | white-space: nowrap;
25 | /* background-color: #FFFFFF; breaks row highlighting */
26 | }
27 |
28 | .memberRow.hasChildren.opened > .memberLabelCell > .memberLabel {
29 | background-image: url(images/twisty-sprites.png);
30 | background-position: 3px -16px;
31 | background-color: transparent;
32 | }
33 |
34 | .memberRow.hasChildren > .memberLabelCell > .memberLabel:hover {
35 | cursor: pointer;
36 | color: blue;
37 | text-decoration: underline;
38 | }
39 |
40 | .memberRow.hasChildren > .memberLabelCell > .memberLabel {
41 | background-image: url(images/twisty-sprites.png);
42 | background-repeat: no-repeat;
43 | background-position: 3px 3px;
44 | }
45 |
46 | /*************************************************************************************************/
47 |
48 | .jumpHighlight {
49 | background-color: #C4F4FF !important;
50 | }
51 |
52 | /*************************************************************************************************/
53 |
54 | .objectBox-object {
55 | color: gray;
56 | }
57 |
58 | .objectBox-number {
59 | color: #000088;
60 | }
61 |
62 | .objectBox-string {
63 | color: #FF0000;
64 | white-space: pre-wrap;
65 | }
66 |
67 | .objectBox-null,
68 | .objectBox-undefined {
69 | font-style: italic;
70 | color: #787878;
71 | }
72 |
73 | .objectBox-array {
74 | color: gray;
75 | }
76 |
77 | /*************************************************************************************************/
78 |
--------------------------------------------------------------------------------
/webapp/css/dragdrop.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | body[vResizing="true"] * {
4 | cursor: e-resize !important;
5 | }
6 |
7 | body[hResizing="true"] * {
8 | cursor: s-resize !important;
9 | }
10 |
--------------------------------------------------------------------------------
/webapp/css/harPreview.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | @import url("harView.css");
4 |
5 | /* Domplate Widgets */
6 | @import url("tabView.css");
7 | @import url("toolbar.css");
8 | @import url("domTree.css");
9 | @import url("infoTip.css");
10 | @import url("popupMenu.css");
11 | /* @import url("tableView.css"); -- preview does not support tableView (part of JSON Query UI) */
12 |
13 | /* HAR Preview */
14 | @import url("pageList.css");
15 | @import url("requestList.css");
16 | @import url("requestBody.css");
17 |
18 | @import url("previewMenu.css");
19 |
20 | @import url("harStats.css");
21 | @import url("pageTimeline.css");
22 |
23 | @import url("validationError.css");
24 |
25 | /* Application Tabs */
26 | /* @import url("aboutTab.css"); -- viewer mode only */
27 | /* @import url("homeTab.css"); -- viewer mode only */
28 | /* @import url("domTab.css"); -- viewer mode only */
29 | /* @import url("schemaTab.css"); -- viewer mode only */
30 | /* @import url("previewTab.css"); -- viewer mode only */
31 | /* @import url("search.css"); -- viewer mode only */
32 |
33 | /* highlight.js */
34 | @import url("highlightjs/tomorrow.min.css");
35 |
36 | /* @import url("dragdrop.css"); -- viewer mode only */
37 |
--------------------------------------------------------------------------------
/webapp/css/harStats.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .pageStatsBody[class~=opened] {
4 | border-bottom: 1px solid #EEEEEE;
5 | }
6 |
7 | /************************************************************************************************/
8 |
9 | .pagePieTable {
10 | margin: 7px;
11 | border-right: 1px solid #EEEEEE;
12 | padding-right: 7px;
13 | display: inline-table;
14 | }
15 |
16 | .pieGraph {
17 | width: 100px;
18 | height: 100px;
19 | display: block;
20 | }
21 |
22 | .pieLabel {
23 | font-size: 11px;
24 | padding-left: 10px;
25 | cursor: default;
26 | }
27 |
28 | .pieLabel SPAN {
29 | vertical-align: middle;
30 | }
31 |
32 | .pieLabel .box {
33 | display: inline-block;
34 | width: 10px;
35 | height: 10px;
36 | margin-top: 1px;
37 | }
38 |
39 | .pieLabel .label {
40 | padding-left: 5px;
41 | }
42 |
43 | .TimingPie.Blocked { color: rgb(228, 214, 193) }
44 | .TimingPie.DNS { color: rgb(119, 192, 203) }
45 | .TimingPie.SSL { color: rgb(168, 196, 173) }
46 | .TimingPie.Connect { color: rgb(179, 222, 93) }
47 | .TimingPie.Send { color: rgb(224, 171, 157) }
48 | .TimingPie.Wait { color: rgb(163, 150, 190) }
49 | .TimingPie.Receive { color: rgb(194, 194, 194) }
50 |
51 | .ContentPie.HTMLText { color: rgb(174, 234, 218) }
52 | .ContentPie.JavaScript { color: rgb(245, 230, 186) }
53 | .ContentPie.CSS { color: rgb(212, 204, 219) }
54 | .ContentPie.Image { color: rgb(220, 171, 181) }
55 | .ContentPie.Flash { color: rgb(166, 156, 222) }
56 | .ContentPie.Others { color: rgb(229, 171, 255) }
57 |
58 | .TrafficPie.HeadersSent { color: rgb(247, 179, 227) }
59 | .TrafficPie.BodiesSent { color: rgb(226, 160, 241) }
60 | .TrafficPie.HeadersReceived { color: rgb(166, 232, 166) }
61 | .TrafficPie.BodiesReceived { color: rgb(168, 196, 173) }
62 |
63 | .CachePie.Downloaded { color: rgb(182, 182, 182) }
64 | .CachePie.Partial { color: rgb(218, 218, 218) }
65 | .CachePie.FromCache { color: rgb(239, 239, 239) }
66 |
--------------------------------------------------------------------------------
/webapp/css/harView.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .harBody {
4 | margin: 0;
5 | font-family: Lucida Grande, Tahoma, sans-serif;
6 | font-size: 13px;
7 | }
8 |
9 | #content a {
10 | text-decoration: none;
11 | }
12 |
13 | #content h1 {
14 | font-size: 17px;
15 | border-bottom: 1px solid threedlightshadow;
16 | }
17 |
18 | #content h2, #content h3, #content h4 {
19 | color: #DD467B;
20 | }
21 |
22 | #content h2 {
23 | font-size: 22.8px;
24 | font-weight: lighter;
25 | }
26 |
27 | #content h3 {
28 | font-weight: bold;
29 | }
30 |
31 | #content h4 {
32 | font-weight: bold;
33 | font-style: italic;
34 | }
35 |
36 | #content pre {
37 | margin: 0;
38 | font: inherit;
39 | }
40 |
41 | /*************************************************************************************************/
42 |
43 | .collapsed,
44 | [collapsed="true"] {
45 | display: none !important;
46 | }
47 |
48 | .link {
49 | color: blue;
50 | }
51 |
52 | .link:hover {
53 | cursor: pointer;
54 | }
55 |
56 | /*************************************************************************************************/
57 |
58 | .harViewBodies {
59 | position: absolute;
60 | top: 33px;
61 | bottom: 0px;
62 | }
63 |
64 | .harViewBar > .tab {
65 | font-size: 17px;
66 | font-family: Lucida Grande, Tahoma, sans-serif;
67 | }
68 |
69 | /*************************************************************************************************/
70 |
71 | /* Support for hiding the tabBar */
72 | .harView[hideTabBar="true"] .harViewBar {
73 | display: none;
74 | }
75 |
76 | .harView[hideTabBar="true"] .harViewBodies {
77 | position: inherit;
78 | }
79 |
--------------------------------------------------------------------------------
/webapp/css/harViewer.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | @import url("harView.css");
4 |
5 | /* Domplate Widgets */
6 | @import url("tabView.css");
7 | @import url("toolbar.css");
8 | @import url("domTree.css");
9 | @import url("infoTip.css");
10 | @import url("popupMenu.css");
11 | @import url("tableView.css");
12 |
13 | /* HAR Preview */
14 | @import url("pageList.css");
15 | @import url("requestList.css");
16 | @import url("requestBody.css");
17 |
18 | /* @import url("previewMenu.css"); -- preview mode only */
19 |
20 | @import url("harStats.css");
21 | @import url("pageTimeline.css");
22 |
23 | @import url("validationError.css");
24 |
25 | /* Application Tabs */
26 | @import url("aboutTab.css");
27 | @import url("homeTab.css");
28 | @import url("domTab.css");
29 | @import url("schemaTab.css");
30 | @import url("previewTab.css");
31 | @import url("search.css");
32 |
33 | /* highlight.js */
34 | @import url("highlightjs/tomorrow.min.css");
35 |
36 | @import url("dragdrop.css");
37 |
--------------------------------------------------------------------------------
/webapp/css/highlightjs/default.min.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
--------------------------------------------------------------------------------
/webapp/css/highlightjs/tomorrow.min.css:
--------------------------------------------------------------------------------
1 | .hljs-comment,.hljs-quote{color:#8e908c}.hljs-variable,.hljs-template-variable,.hljs-tag,.hljs-name,.hljs-selector-id,.hljs-selector-class,.hljs-regexp,.hljs-deletion{color:#c82829}.hljs-number,.hljs-built_in,.hljs-builtin-name,.hljs-literal,.hljs-type,.hljs-params,.hljs-meta,.hljs-link{color:#f5871f}.hljs-attribute{color:#eab700}.hljs-string,.hljs-symbol,.hljs-bullet,.hljs-addition{color:#718c00}.hljs-title,.hljs-section{color:#4271ae}.hljs-keyword,.hljs-selector-tag{color:#8959a8}.hljs{display:block;overflow-x:auto;background:white;color:#4d4d4c;padding:0.5em}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
--------------------------------------------------------------------------------
/webapp/css/homeTab.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .tabHomeBody {
4 | font-family: Verdana,Geneva,Arial,Helvetica,sans-serif;
5 | font-size: 11.7px;
6 | font-style: normal;
7 | font-weight: 400;
8 | }
9 |
10 | .homeBody {
11 | padding: 8px;
12 | }
13 |
--------------------------------------------------------------------------------
/webapp/css/images/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/ajax-loader.gif
--------------------------------------------------------------------------------
/webapp/css/images/bg-button.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/bg-button.gif
--------------------------------------------------------------------------------
/webapp/css/images/blank.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/blank.gif
--------------------------------------------------------------------------------
/webapp/css/images/button-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/button-background.png
--------------------------------------------------------------------------------
/webapp/css/images/checkmark.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/checkmark.gif
--------------------------------------------------------------------------------
/webapp/css/images/checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/checkmark.png
--------------------------------------------------------------------------------
/webapp/css/images/close-sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/close-sprites.png
--------------------------------------------------------------------------------
/webapp/css/images/contextMenuTarget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/contextMenuTarget.png
--------------------------------------------------------------------------------
/webapp/css/images/contextMenuTargetHover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/contextMenuTargetHover.png
--------------------------------------------------------------------------------
/webapp/css/images/download-sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/download-sprites.png
--------------------------------------------------------------------------------
/webapp/css/images/downloadButtons-aero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/downloadButtons-aero.png
--------------------------------------------------------------------------------
/webapp/css/images/group.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/group.gif
--------------------------------------------------------------------------------
/webapp/css/images/loading_16.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/loading_16.gif
--------------------------------------------------------------------------------
/webapp/css/images/menu/previewMenuHandle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/menu/previewMenuHandle.png
--------------------------------------------------------------------------------
/webapp/css/images/menu/shadowAlpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/menu/shadowAlpha.png
--------------------------------------------------------------------------------
/webapp/css/images/menu/tabMenuCheckbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/menu/tabMenuCheckbox.png
--------------------------------------------------------------------------------
/webapp/css/images/menu/tabMenuPin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/menu/tabMenuPin.png
--------------------------------------------------------------------------------
/webapp/css/images/menu/tabMenuRadio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/menu/tabMenuRadio.png
--------------------------------------------------------------------------------
/webapp/css/images/netBarBlocking.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarBlocking.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarBlocking2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarBlocking2.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarCached.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarCached.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarConnecting.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarConnecting.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarLoaded.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarLoaded.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarReceiving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarReceiving.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarResolving.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarResolving.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarResponded.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarResponded.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarSending.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarSending.gif
--------------------------------------------------------------------------------
/webapp/css/images/netBarWaiting.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/netBarWaiting.gif
--------------------------------------------------------------------------------
/webapp/css/images/page-timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/page-timeline.png
--------------------------------------------------------------------------------
/webapp/css/images/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/save.png
--------------------------------------------------------------------------------
/webapp/css/images/splitterh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/splitterh.png
--------------------------------------------------------------------------------
/webapp/css/images/spriteArrows.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/spriteArrows.gif
--------------------------------------------------------------------------------
/webapp/css/images/spriteArrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/spriteArrows.png
--------------------------------------------------------------------------------
/webapp/css/images/tabEnabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/tabEnabled.png
--------------------------------------------------------------------------------
/webapp/css/images/timeline-sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/timeline-sprites.png
--------------------------------------------------------------------------------
/webapp/css/images/tooltipConnectorUp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/tooltipConnectorUp.png
--------------------------------------------------------------------------------
/webapp/css/images/twisty-sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitgrimbo/harviewer-react/f9dd622074bf0ce3f8a9f907adf2659a957ade9b/webapp/css/images/twisty-sprites.png
--------------------------------------------------------------------------------
/webapp/css/infoTip.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .infoTip {
4 | z-index: 2147483647;
5 | position: fixed;
6 | padding: 2px 4px 3px 4px;
7 | background: LightYellow;
8 | font-family: Lucida Grande, Tahoma, sans-serif;
9 | color: #000000;
10 | display: none;
11 | white-space: nowrap;
12 | font-size: 11px;
13 | border: 1px solid rgb(126, 171, 205);
14 | background: url(images/tabEnabled.png) repeat-x scroll 0px 0px rgb(249, 249, 249);
15 | background-position-x: 0;
16 | background-position-y: 100%;
17 | background-repeat: repeat-x;
18 |
19 | -moz-border-radius: 3px;
20 | -webkit-border-radius: 3px;
21 | border-radius: 3px;
22 |
23 | -moz-box-shadow: gray 2px 2px 3px;
24 | -webkit-box-shadow: gray 2px 2px 3px;
25 | box-shadow: gray 2px 2px 3px;
26 |
27 | /* IE */
28 | filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray');
29 | /* slightly different syntax for IE8 */
30 | -ms-filter:"progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray')";
31 | }
32 |
33 | .infoTip[active="true"] {
34 | display: block;
35 | }
36 |
37 | .infoTip[multiline="true"] {
38 | background-image: none;
39 | }
40 |
--------------------------------------------------------------------------------
/webapp/css/pageList.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .pageList {
4 | width: 100%;
5 | }
6 |
7 | .pageTable {
8 | width: 100%;
9 | font-family: Lucida Grande, Tahoma, sans-serif;
10 | font-size: 11px;
11 | }
12 |
13 | .pageCol {
14 | white-space: nowrap;
15 | border-bottom: 1px solid #EEEEEE;
16 | }
17 |
18 | .pageRow {
19 | font-weight: bold;
20 | height: 17px;
21 | background-color: white;
22 | }
23 |
24 | .pageRow:hover {
25 | background: #EFEFEF;
26 | }
27 |
28 | .opened > .pageCol > .pageName {
29 | background-image: url(images/twisty-sprites.png);
30 | background-position: 3px -17px;
31 | }
32 |
33 | .pageName {
34 | background-image: url(images/twisty-sprites.png);
35 | background-repeat: no-repeat;
36 | background-position: 3px 2px;
37 | padding-left: 18px;
38 | font-weight: bold;
39 | cursor:pointer;
40 | }
41 |
42 | .pageID {
43 | color: gray;
44 | }
45 |
46 | .pageInfoCol {
47 | background: url(images/timeline-sprites.png) repeat-x scroll 0 -112px #FFFFFF;
48 | padding: 0px 0px 4px 17px;
49 | }
50 |
51 | /*************************************************************************************************/
52 | /* Column Customization */
53 |
54 | .pageRow:hover > .netOptionsCol > .netOptionsLabel {
55 | display: block;
56 | }
57 |
58 | .pageRow > .netOptionsCol {
59 | padding-right: 2px;
60 | }
61 |
62 | /*************************************************************************************************/
63 | /* Print support */
64 |
65 | @media print {
66 |
67 | .pageInfoCol {
68 | background: none;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/webapp/css/pageTimeline.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .pageTimeline {
4 | background-color: #FFFFFF;
5 | color: #000000;
6 | }
7 |
8 | .pageTimelineBody {
9 | width: 100%;
10 | }
11 |
12 | /* If the page timeline is displayed there is a bottom line to separate
13 | * the list of pages/requests. */
14 | .pageTimelineBody[class~=opened] {
15 | border-bottom: 1px solid #EEEEEE;
16 | }
17 |
18 | .pageTimelineTable {
19 | height: 100px;
20 | padding-left: 5px;
21 | }
22 |
23 | .pageTimelineCol {
24 | vertical-align: bottom;
25 | padding-left: 4px;
26 | outline: none;
27 | -moz-outline-style: none;
28 | -moz-user-focus: ignore;
29 | }
30 |
31 | .pageBar {
32 | width: 9px;
33 | background: url(images/page-timeline.png) repeat-y scroll 0px 0px #FFFFFF;
34 | cursor: pointer;
35 | }
36 |
37 | .pageBar.selected {
38 | opacity: 0.5; /* Safari, Opera */
39 | -moz-opacity: 0.50; /* FireFox */
40 | filter: alpha(opacity=50); /* IE */
41 | }
42 |
43 | .pageTimelineCol:hover .pageBar {
44 | background: url(images/page-timeline.png) repeat-y scroll -8px 0px #FFFFFF;
45 | }
46 |
47 | .pageTimelineBody .connector {
48 | margin-left: 16px;
49 | display: block;
50 | background: url(images/tooltipConnectorUp.png) no-repeat;
51 | width: 16px;
52 | height: 11px;
53 | position: relative;
54 | margin-bottom: -1px;
55 | }
56 |
57 | /************************************************************************************************/
58 |
59 | .pageDescBox .desc {
60 | font-size: 11px;
61 | border: 1px solid rgb(126, 171, 205);
62 | background: url(images/tabEnabled.png) repeat-x scroll 0px 0px rgb(234, 234, 234);
63 | padding: 3px;
64 | -moz-border-radius: 3px;
65 | -webkit-border-radius: 3px;
66 | border-radius: 3px;
67 | }
68 |
69 | .pageDescBox .desc .summary {
70 | font-weight: bold;
71 | }
72 |
73 | .pageDescBox .desc .time {
74 | padding-left: 10px;
75 | }
76 |
77 | .pageDescBox .desc .title {
78 | color: black;
79 | padding-left: 10px;
80 | }
81 |
82 | .pageDescBox .desc .comment {
83 | color: gray;
84 | padding-top: 1px;
85 | }
86 |
--------------------------------------------------------------------------------
/webapp/css/popupMenu.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .popupMenu {
4 | display: none;
5 | position: absolute;
6 | font-size: 11px;
7 | z-index: 2147483647;
8 | font-family: Lucida Grande, Tahoma, sans-serif;
9 | }
10 |
11 | .popupMenuContent {
12 | padding: 2px;
13 | }
14 |
15 | .popupMenuSeparator {
16 | display: block;
17 | position: relative;
18 | padding: 1px 18px 0;
19 | text-decoration: none;
20 | color: #000;
21 | cursor: default;
22 | background: #ACA899;
23 | margin: 2px 0;
24 | }
25 |
26 | .popupMenuOption
27 | {
28 | display: block;
29 | position: relative;
30 | padding: 2px 18px;
31 | text-decoration: none;
32 | color: #000;
33 | cursor: default;
34 | }
35 |
36 | .popupMenuOption:hover
37 | {
38 | color: #fff;
39 | background: #316AC5;
40 | }
41 |
42 | .popupMenuGroup {
43 | background: transparent url(images/menu/tabMenuPin.png) no-repeat right 0;
44 | }
45 |
46 | .popupMenuGroup:hover {
47 | background: #316AC5 url(images/menu/tabMenuPin.png) no-repeat right -17px;
48 | }
49 |
50 | .popupMenuGroupSelected {
51 | color: #fff;
52 | background: #316AC5 url(images/menu/tabMenuPin.png) no-repeat right -17px;
53 | }
54 |
55 | .popupMenuChecked {
56 | background: transparent url(images/menu/tabMenuCheckbox.png) no-repeat 4px 0;
57 | }
58 |
59 | .popupMenuChecked:hover {
60 | background: #316AC5 url(images/menu/tabMenuCheckbox.png) no-repeat 4px -17px;
61 | }
62 |
63 | .popupMenuRadioSelected {
64 | background: transparent url(images/menu/tabMenuRadio.png) no-repeat 4px 0;
65 | }
66 |
67 | .popupMenuRadioSelected:hover {
68 | background: #316AC5 url(images/menu/tabMenuRadio.png) no-repeat 4px -17px;
69 | }
70 |
71 | .popupMenuShortcut {
72 | padding-right: 85px;
73 | }
74 |
75 | .popupMenuShortcutKey {
76 | position: absolute;
77 | right: 0;
78 | top: 2px;
79 | width: 77px;
80 | }
81 |
82 | .popupMenuDisabled {
83 | color: #ACA899 !important;
84 | }
85 |
86 | /*************************************************************************************************/
87 | /* Shadow */
88 |
89 | .popupMenuShadow {
90 | float: left;
91 | background: url(images/menu/shadowAlpha.png) no-repeat bottom right !important;
92 | margin: 10px 0 0 10px !important;
93 | margin: 10px 0 0 5px;
94 | }
95 |
96 | .popupMenuShadowContent {
97 | display: block;
98 | position: relative;
99 | background-color: #fff;
100 | border: 1px solid #a9a9a9;
101 | top: -6px;
102 | left: -6px;
103 | }
104 |
105 | /*************************************************************************************************/
106 |
107 | #optionsMenu { /*xxxHonza*/
108 | top: 22px;
109 | left: 0;
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/webapp/css/previewMenu.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .menu {
4 | position: absolute;
5 | right: 2px;
6 | font-size: 10px;
7 | font-family: Lucida Grande,Tahoma,sans-serif;
8 | text-decoration: none;
9 | outline: none;
10 | white-space: nowrap;
11 | }
12 |
13 | .menu .menuContent {
14 | display: inline-block;
15 | overflow: hidden;
16 | vertical-align: top;
17 | line-height: 13px;
18 | }
19 |
20 | .menu .menuHandle {
21 | display: inline-block;
22 | width: 9px;
23 | height: 16px;
24 | background-image: url('images/menu/previewMenuHandle.png');
25 | }
26 |
27 | .menu .menuHandle.opened {
28 | background-image: url('images/menu/previewMenuHandle.png');
29 | background-position: 9px 0;
30 | }
31 |
32 | /*************************************************************************************************/
33 | /* Toolbar inside the menu */
34 |
35 | .menu .toolbar {
36 | border: 0;
37 | vertical-align: top;
38 | display: inline;
39 | padding-left: 0;
40 | }
41 |
42 | .menu .toolbarSeparator {
43 | }
44 |
45 | .menu .toolbarSeparator,
46 | .menu .toolbarButton {
47 | padding: 0;
48 | margin: 0 3px 1px 3px;
49 | border: none;
50 | color: gray;
51 | }
52 |
53 | .toolbarButton.text:hover {
54 | border: none;
55 | background: none;
56 | color: blue;
57 | }
58 |
--------------------------------------------------------------------------------
/webapp/css/previewTab.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /*************************************************************************************************/
4 | /* Preview */
5 |
6 | /*************************************************************************************************/
7 | /* Toolbar */
8 |
9 | /* Remove the dotted outline when focused */
10 | .harDownloadButton OBJECT {
11 | outline: none;
12 | -moz-user-focus: ignore;
13 | }
14 |
15 | .harSaveButton {
16 | background: url(images/save.png) no-repeat;
17 | }
18 |
--------------------------------------------------------------------------------
/webapp/css/requestBody.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .requestBodyBodies {
4 | border-left: 1px solid #D7D7D7;
5 | border-right: 1px solid #D7D7D7;
6 | border-bottom: 1px solid #D7D7D7;
7 | padding-top: 8px;
8 | }
9 |
10 | .netInfoRow .tabView {
11 | width: 99%; /* avoid 1px horizontal scrollbar when a requst is expanded and tabView visible */
12 | }
13 |
14 | .netInfoText {
15 | padding: 8px;
16 | background-color: #FFFFFF;
17 | font-family: Monaco, monospace;
18 | /*overflow-x: auto; HTML is damaged in case of big (2-3MB) responses */
19 | }
20 |
21 | .netInfoRequestURL {
22 | padding: 8px;
23 | }
24 |
25 | .netInfoText[selected="true"] {
26 | display: block;
27 | }
28 |
29 | /*************************************************************************************************/
30 |
31 | .netInfoParamName {
32 | padding: 0 10px 0 0;
33 | font-family: Lucida Grande, Tahoma, sans-serif;
34 | font-weight: bold;
35 | vertical-align: top;
36 | text-align: right;
37 | white-space: nowrap;
38 | }
39 |
40 | .netInfoParamValue > PRE {
41 | margin: 0
42 | }
43 |
44 | .netInfoHeadersText,
45 | .netInfoCookiesText {
46 | padding-top: 0;
47 | width: 100%;
48 | }
49 |
50 | .netInfoParamValue {
51 | width: 100%;
52 | }
53 |
54 | .netInfoHeadersGroup,
55 | .netInfoCookiesGroup {
56 | margin-bottom: 4px;
57 | border-bottom: 1px solid #D7D7D7;
58 | padding-top: 8px;
59 | padding-bottom: 2px;
60 | font-family: Lucida Grande, Tahoma, sans-serif;
61 | font-weight: bold;
62 | color: #565656;
63 | }
64 |
65 | /*************************************************************************************************/
66 | /* HTML Tab */
67 |
68 | .netInfoHtmlPreview {
69 | border: 0;
70 | width: 100%;
71 | height: 100px;
72 | }
73 |
74 | .netInfoHtmlText {
75 | padding: 0;
76 | }
77 |
78 | /* Preview resizer */
79 | .htmlPreviewResizer {
80 | width: 100%;
81 | height: 4px;
82 | background-image: url(images/splitterh.png);
83 | background-repeat: repeat-x;
84 | cursor: s-resize;
85 | }
86 |
87 | /* When HTML preview resizing is in progress mouse messages are not sent to
88 | the iframe document. */
89 | body[hResizing="true"] .netInfoHtmlPreview {
90 | pointer-events: none !important;
91 | }
92 |
--------------------------------------------------------------------------------
/webapp/css/schemaTab.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .tabSchemaBody {
4 | font-family: Monaco,monospace;
5 | font-size: 12px;
6 | }
7 |
--------------------------------------------------------------------------------
/webapp/css/search.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /*************************************************************************************************/
4 | /* Search */
5 |
6 | .searchTextBox {
7 | vertical-align: sub;
8 | margin-right: 3px;
9 | }
10 |
11 | .searchInput {
12 | outline: none; /* Remove webkit focus border */
13 | border: 1px solid #EEEEEE;
14 | padding: 1px 17px 1px 3px;
15 | width: 300px;
16 | }
17 |
18 | .searchInput[status=notfound] {
19 | background: rgb(255, 102, 102);
20 | color: white;
21 | }
22 |
23 | .searchBox .arrow {
24 | width: 11px;
25 | height: 10px;
26 | background: url(images/contextMenuTarget.png) no-repeat;
27 | display: inline-block;
28 | position: relative;
29 | margin-left: -15px;
30 | top: 1px;
31 | cursor: pointer;
32 |
33 | }
34 |
35 | .searchBox .arrow:hover {
36 | background-image: url(images/contextMenuTargetHover.png);
37 | }
38 |
39 | /* Disabled for IE */
40 | .searchBox .arrow[disabled=true] {
41 | display: none;
42 | }
43 |
44 | .searchBox .resizer {
45 | cursor: e-resize;
46 | }
47 |
--------------------------------------------------------------------------------
/webapp/css/tabView.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .tabView {
4 | width: 100%;
5 | background-color: #FFFFFF;
6 | color: #000000;
7 | }
8 |
9 | .tabViewCol {
10 | background: url(images/timeline-sprites.png) repeat-x scroll 0 -112px #FFFFFF;
11 | vertical-align:top;
12 | }
13 |
14 | .tabViewBody {
15 | margin: 2px 0px 0px 0px;
16 | }
17 |
18 | .tabBar {
19 | padding-left: 14px;
20 | border-bottom: 1px solid #D7D7D7;
21 | white-space: nowrap;
22 | }
23 |
24 | .tab {
25 | position: relative;
26 | top: 1px;
27 | padding: 4px 8px;
28 | border: 1px solid transparent;
29 | border-bottom: none;
30 | color: #565656;
31 | font-weight: bold;
32 | white-space: nowrap;
33 | -moz-user-select: none;
34 | display:inline-block;
35 | }
36 |
37 | .tab:hover {
38 | cursor: pointer;
39 | border-color: #D7D7D7;
40 | -moz-border-radius: 4px 4px 0 0;
41 | -webkit-border-top-left-radius: 4px;
42 | -webkit-border-top-right-radius: 4px;
43 | border-radius: 4px 4px 0 0;
44 | }
45 |
46 | .tab[selected="true"],
47 | .tab .selected {
48 | cursor: default !important;
49 | border-color: #D7D7D7;
50 | background-color: #FFFFFF;
51 | -moz-border-radius: 4px 4px 0 0;
52 | -webkit-border-top-left-radius: 4px;
53 | -webkit-border-top-right-radius: 4px;
54 | border-radius: 4px 4px 0 0;
55 | }
56 |
57 | /*
58 | * If fixed height is specified, the overflow works, otherwise the position must be
59 | * absolute with top: 33px (height of the tab-header and bottom: 0px to get available
60 | * vertical space
61 | */
62 | .tabBodies {
63 | width: 100%;
64 | overflow: auto;
65 | }
66 |
67 | .tabBody {
68 | display: none;
69 | margin: 0;
70 | }
71 |
72 | .tabBody[selected="true"],
73 | .tabBody.selected {
74 | display: block;
75 | }
76 |
77 | /*************************************************************************************************/
78 | /* Print support */
79 |
80 | @media print {
81 |
82 | /* This is what prevents the browser's print featurs to print more pages. */
83 | .tabBodies {
84 | overflow:visible;
85 | }
86 |
87 | .tabViewCol {
88 | background: none;
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/webapp/css/tableView.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /*************************************************************************************************/
4 | /* Console Panel */
5 |
6 | .dataTableSizer {
7 | margin: 7px;
8 | border: 1px solid #EEEEEE;
9 | }
10 |
11 | .dataTableSizer:focus {
12 | outline: none;
13 | }
14 |
15 | .dataTable {
16 | }
17 |
18 | /* If the table is displayed within a group the border is provided by that group. */
19 | .logGroup .dataTable {
20 | border: none;
21 | }
22 |
23 | .dataTable TBODY {
24 | overflow-x: hidden;
25 | overflow-y: scroll;
26 | }
27 |
28 | .dataTable > .dataTableTbody > tr:nth-child(even) {
29 | background-color: #EFEFEF;
30 | }
31 |
32 | .dataTable a {
33 | vertical-align:middle;
34 | }
35 |
36 | .dataTableTbody > tr > td {
37 | padding: 1px 4px 0 4px;
38 | }
39 |
40 | /* The last column needs more horizontal space since part of it is overlapped by
41 | the vertical scroll bar */
42 | .dataTableTbody > tr > td:last-child {
43 | padding-right: 20px;
44 | }
45 |
46 | .useA11y .dataTable *:focus {
47 | outline-offset: -2px;
48 | }
49 |
50 | /*************************************************************************************************/
51 | /* Console panel filter */
52 |
53 | .panelNode.hideType-table .logRow-table {
54 | display: none !important;
55 | }
56 |
57 | /*************************************************************************************************/
58 | /* Header for Net panel table */
59 |
60 | .headerCell {
61 | cursor: pointer;
62 | -moz-user-select: none;
63 | border-bottom: 1px solid #9C9C9C;
64 | padding: 0 !important;
65 | font-weight: bold;
66 | background: #C8C8C8 -moz-linear-gradient(top, rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0.2));
67 | background: #C8C8C8 -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0.3)), to(rgba(0, 0, 0, 0.2)));
68 | }
69 |
70 | .headerCellBox {
71 | padding: 2px 13px 2px 4px;
72 | border-left: 1px solid #D9D9D9;
73 | border-right: 1px solid #9C9C9C;
74 | white-space: nowrap;
75 | }
76 |
77 | .headerCell:hover:active {
78 | background-color: #B4B4B4;
79 | }
80 |
81 | .headerSorted {
82 | background-color: #8CA0BE;
83 | }
84 |
85 | .headerSorted > .headerCellBox {
86 | border-right-color: #6B7C93;
87 | background: url(chrome://firebug/skin/arrowDown.png) no-repeat right;
88 | }
89 |
90 | .headerSorted.sortedAscending > .headerCellBox {
91 | background-image: url(chrome://firebug/skin/arrowUp.png);
92 | }
93 |
94 | .headerSorted:hover:active {
95 | background-color: #6E87AA;
96 | }
97 |
98 | /*************************************************************************************************/
99 | /* Tree within a table cell */
100 |
101 | /* Rules for the (fake) root object that represents an expandable tree within a table-cell */
102 | .memberRow.tableCellRow .memberLabelCell,
103 | .memberRow.tableCellRow .memberValueCell {
104 | padding: 0;
105 | color: Gray;
106 | }
107 |
108 | /* So the height of the row is the same as if there would be an embedded tree object */
109 | .dataTableCell > .objectBox-number,
110 | .dataTableCell > .objectBox-string,
111 | .dataTableCell > .objectBox-null,
112 | .dataTableCell > .objectBox-undefined,
113 | .dataTableCell > .objectBox-array {
114 | padding: 5px;
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/webapp/css/toolTip.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .toolTip {
4 | z-index: 2147483647;
5 | position: absolute;
6 | padding: 2px 4px 3px 4px;
7 | font-family: Lucida Grande, Tahoma, sans-serif;
8 | color: #000000;
9 | display: none;
10 | font-size: 11px;
11 | border: 1px solid #a9a9a9;
12 | background: white;
13 | overflow: auto;
14 | }
15 |
16 | .toolTip {
17 | height: 150px;
18 | width: 500px;
19 | }
20 |
--------------------------------------------------------------------------------
/webapp/css/toolbar.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | .toolbar {
4 | font-family: Verdana,Geneva,Arial,Helvetica,sans-serif;
5 | font-size: 11px;
6 | font-weight: 400;
7 | font-style: normal;
8 | border-bottom: 1px solid #EEEEEE;
9 | padding: 0 3px 0 3px;
10 | }
11 |
12 | .toolbarButton,
13 | .toolbarSeparator {
14 | display: inline-block;
15 | vertical-align: middle;
16 | cursor: pointer;
17 | color: #000000;
18 | -moz-user-select: none;
19 | -moz-box-sizing: padding-box;
20 | }
21 |
22 | .toolbarButton.dropDown .arrow {
23 | width: 11px;
24 | height: 10px;
25 | background: url(images/contextMenuTarget.png) no-repeat;
26 | display: inline-block;
27 | margin-left: 3px;
28 | position: relative;
29 | right: 0;
30 | top: 1px;
31 | }
32 |
33 | .toolbarButton.image {
34 | padding: 0;
35 | height: 16px;
36 | width: 16px;
37 | }
38 |
39 | .toolbarButton.text,
40 | .toolbarSeparator {
41 | margin: 3px 0 3px 0;
42 | padding: 3px;
43 | border: 1px solid transparent;
44 | }
45 |
46 | .toolbarButton.text:hover {
47 | background: url(images/bg-button.gif) repeat-x scroll 0 0 #FFFFFF;
48 | border-top:1px solid #bbb;
49 | border-bottom:1px solid #aaa;
50 | border-left:1px solid #bbb;
51 | border-right:1px solid #aaa;
52 | -moz-border-radius: 3px;
53 | -webkit-border-radius: 3px;
54 | border-radius: 3px;
55 | }
56 |
57 | .toolbarButton.text:active {
58 | background-position: 0 -400px;
59 | }
60 |
--------------------------------------------------------------------------------
/webapp/css/validationError.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /*************************************************************************************************/
4 | /* Errors */
5 |
6 | .errorTable {
7 | margin: 8px;
8 | font-size: 13px;
9 | }
10 |
11 | .errorProperty {
12 | color: gray;
13 | font-style:italic;
14 | }
15 |
16 | .errorMessage {
17 | color: red;
18 | }
19 |
20 | .errorRow:hover {
21 | background: #EFEFEF;
22 | cursor: pointer;
23 | }
24 |
25 | /*************************************************************************************************/
26 | /* Context Menu */
27 |
28 | .errorOptionsTarget {
29 | width: 11px;
30 | height: 10px;
31 | background: url(images/contextMenuTarget.png) no-repeat;
32 | visibility: collapse;
33 | }
34 |
35 | .errorOptionsTarget:hover {
36 | background-image: url(images/contextMenuTargetHover.png);
37 | }
38 |
39 | /**
40 | * The context menu target is visible only if the user is hovering mouse over
41 | * the error entry and if the error is associated with a target HAR object
42 | */
43 | .errorRow:hover > .errorOptions.hasTarget > .errorOptionsTarget {
44 | visibility: visible;
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/webapp/css/xhrSpy.css:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | @import url("tabView.css");
4 | @import url("pageList.css");
5 | @import url("requestList.css");
6 | @import url("requestBody.css");
7 | @import url("infoTip.css");
8 | @import url("popupMenu.css");
9 | @import url("toolbar.css");
10 |
11 | .harBody {
12 | margin: 0;
13 | font-family: Lucida Grande, Tahoma, sans-serif;
14 | font-size: 11px;
15 | }
16 |
17 | .xhrSpyFrame {
18 | }
19 |
20 | .tabView.requestBody,
21 | .netInfoHeadersGroup {
22 | /* font-size: 11px;*/
23 | }
24 |
--------------------------------------------------------------------------------
/webapp/demo.js:
--------------------------------------------------------------------------------
1 | import "@babel/polyfill";
2 | import React from "react";
3 | import { render } from "react-dom";
4 |
5 | import HarModel from "./modules/preview/harModel";
6 | import HarModelLoader from "./modules/preview/harModelLoader";
7 |
8 | import DemoApp from "./modules/Demo";
9 |
10 | function loadHar(harUrl, callback, errback) {
11 | harUrl = harUrl || "../examples/softwareishard.com.har";
12 | HarModelLoader.loadArchives([harUrl], [], null, callback);
13 | }
14 |
15 | window.addEventListener("load", function() {
16 | function renderDemo(demoName, demoProps) {
17 | render(
18 |
, document.getElementById("demo-app")
19 | );
20 | }
21 |
22 | function createDemoProps(har) {
23 | const model = new HarModel();
24 | model.append(har);
25 | const firstPage = har.log.pages[0];
26 | const firstPageEntries = model.getPageEntries(firstPage);
27 | const firstEntryOfFirstPage = firstPageEntries[0];
28 | return {
29 | model,
30 | input: har,
31 | page: firstPage,
32 | entries: firstPageEntries,
33 | entry: firstEntryOfFirstPage,
34 | visible: true,
35 | };
36 | }
37 |
38 | // eslint-disable-next-line
39 | const uri = URI(window.location.href);
40 | const params = uri.query(true);
41 | if (params.demo) {
42 | loadHar(null, (har) => renderDemo(params.demo, createDemoProps(har)));
43 | } else {
44 | renderDemo();
45 | }
46 | });
47 |
--------------------------------------------------------------------------------
/webapp/demos/demo-shell.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/webapp/examples/embed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
HTTP Archive Viewer @VERSION@ - Embedding Samples
6 |
7 |
10 |
11 |
12 |
13 |
14 |
View source to see examples of how to embed HAR Viewer into your page.
15 |
16 |
17 | Embed as
18 | or
19 | with height
20 |
21 |
22 |
23 |
24 |
25 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
HAR/HTML
102 |
103 |
104 |
105 |
106 |
HARP/HTML
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
HTTP Archive Viewer @VERSION@
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
28 |
29 |
32 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/webapp/loader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
HAR Viewer - Service Loader
5 |
6 |
30 |
31 |
32 |
33 |
35 |
36 |
37 |
38 |
39 |
40 | Full Preview
41 | |
42 |
43 |
44 |
45 |
47 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/webapp/main.js:
--------------------------------------------------------------------------------
1 | import "@babel/polyfill";
2 | import React from "react";
3 | import { render } from "react-dom";
4 | import App from "./modules/App";
5 | import { AppContextProvider } from "./modules/AppContext";
6 |
7 | const path = window.location.href.split("?")[0];
8 | const mode = (path.endsWith("preview.html")) ? "preview" : "";
9 | const container = document.getElementById("content");
10 | render((
11 |
12 |
13 |
14 | ), container);
15 |
--------------------------------------------------------------------------------
/webapp/modules/AppContext.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import Url from "./core/url";
4 |
5 | import Cookies from "./core/cookies";
6 | import InfoTipHolder from "./InfoTipHolder";
7 | import defaultTimingDefinitions from "./nettable/defaultTimingDefinitions";
8 |
9 | const DEFAULT_STATE = {
10 | validate: true,
11 | expandAll: true,
12 | previewCols: [],
13 | pageTimingDefinitions: defaultTimingDefinitions.slice(),
14 | };
15 |
16 | const AppContext = React.createContext(DEFAULT_STATE);
17 |
18 | export default AppContext;
19 |
20 | export const AppContextConsumer = AppContext.Consumer;
21 |
22 | export class AppContextProvider extends Component {
23 | state = DEFAULT_STATE;
24 | infoTipHolderRef = React.createRef();
25 |
26 | getDefaultVisibleNetCols() {
27 | const cols = Cookies.getCookie("previewCols");
28 | if (cols) {
29 | // Columns names are separated by a space so, make sure to properly process
30 | // spaces in the cookie value.
31 | return unescape(cols.replace(/\+/g, " "))
32 | .split(" ");
33 | }
34 | const defaultVisibleNetCols = [
35 | "url",
36 | "status",
37 | "size",
38 | "uncompressedSize",
39 | "timeline",
40 | ];
41 | return defaultVisibleNetCols;
42 | }
43 |
44 | componentDidMount() {
45 | const expandAll = Url.getURLParameter("expand", window.location.href) === "true";
46 | const validate = Cookies.getCookie("validate") !== "false";
47 | const newState = {
48 | validate,
49 | expandAll,
50 | previewCols: this.getDefaultVisibleNetCols(),
51 | };
52 | this.setState(newState);
53 | }
54 |
55 | appendPreview(harObjectOrString) {
56 | }
57 |
58 | setPreviewCols = (cols, avoidCookies) => {
59 | if (!cols) {
60 | cols = this.getDefaultVisibleNetCols();
61 | }
62 |
63 | // If the parameter is an array, convert it to string.
64 | if (!Array.isArray(cols)) {
65 | cols = cols.split(/\s+/);
66 | }
67 |
68 | // Update cookie
69 | if (!avoidCookies) {
70 | Cookies.setCookie("previewCols", cols.join(" "));
71 | }
72 |
73 | this.setState({
74 | previewCols: cols,
75 | });
76 | }
77 |
78 | setValidate = (validate) => {
79 | Cookies.setCookie("validate", validate);
80 | this.setState({ validate });
81 | }
82 |
83 | getInfoTipHolder = () => {
84 | return this.infoTipHolderRef.current;
85 | }
86 |
87 | addPageTiming = (timing) => {
88 | this.setState(({ pageTimingDefinitions }) => ({
89 | pageTimingDefinitions: [...pageTimingDefinitions, timing],
90 | }));
91 | }
92 |
93 | render() {
94 | const state = this.state;
95 | return (
96 |
97 |
104 | {this.props.children}
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | AppContextProvider.propTypes = {
112 | children: PropTypes.node,
113 | };
114 |
--------------------------------------------------------------------------------
/webapp/modules/InfoTip.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class InfoTip extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | active: false,
9 | multiline: false,
10 | };
11 | this.domRef = React.createRef();
12 | }
13 |
14 | placeInfoTip(infoTip, x, y) {
15 | const { infoTipMargin, infoTipWindowPadding } = this.props;
16 |
17 | const docEl = infoTip.ownerDocument.documentElement;
18 | const panelWidth = docEl.clientWidth;
19 | const panelHeight = docEl.clientHeight;
20 |
21 | const style = {};
22 |
23 | if (x + infoTip.offsetWidth + infoTipMargin >
24 | panelWidth - infoTipWindowPadding) {
25 | style.left = "auto";
26 | style.right = ((panelWidth - x) + infoTipMargin) + "px";
27 | } else {
28 | style.left = (x + infoTipMargin) + "px";
29 | style.right = "auto";
30 | }
31 |
32 | if (y + infoTip.offsetHeight + infoTipMargin > panelHeight) {
33 | style.top = Math.max(0,
34 | panelHeight - (infoTip.offsetHeight + infoTipMargin)) + "px";
35 | style.bottom = "auto";
36 | } else {
37 | style.top = (y + infoTipMargin) + "px";
38 | style.bottom = "auto";
39 | }
40 |
41 | infoTip.style.top = style.top;
42 | infoTip.style.left = style.left;
43 | infoTip.style.right = style.right;
44 | infoTip.style.bottom = style.bottom;
45 | }
46 |
47 | refresh(props, state) {
48 | const { active, multiline, x, y } = state;
49 | this.setActiveAttr(this.domRef.current, active, multiline);
50 | if (active) {
51 | this.placeInfoTip(this.domRef.current, x, y);
52 | }
53 | }
54 |
55 | setActiveAttr(ref, active, multiline) {
56 | ref.setAttribute("active", active);
57 | // There is no background image for mulitline tooltips.
58 | ref.setAttribute("multiline", multiline);
59 | }
60 |
61 | componentDidMount() {
62 | this.refresh(this.props, this.state);
63 | if (this.props.onRef) {
64 | this.props.onRef(this.domRef.current);
65 | }
66 | }
67 |
68 | UNSAFE_componentWillUpdate(nextProps, nextState) {
69 | this.refresh(nextProps, nextState);
70 | }
71 |
72 | render() {
73 | return (
74 |
75 | {this.props.children}
76 |
77 | );
78 | }
79 | }
80 |
81 | InfoTip.propTypes = {
82 | children: PropTypes.node,
83 | onRef: PropTypes.func,
84 | infoTipMargin: PropTypes.number,
85 | infoTipWindowPadding: PropTypes.number,
86 | };
87 |
88 | export default InfoTip;
89 |
--------------------------------------------------------------------------------
/webapp/modules/InfoTipHolder.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { render, unmountComponentAtNode } from "react-dom";
4 |
5 | import * as Lib from "./core/lib";
6 | import InfoTip from "./InfoTip";
7 |
8 | class InfoTipHolder extends React.Component {
9 | listeners = [];
10 | maxWidth = 100;
11 | maxHeight = 80;
12 | infoTipMargin = 10;
13 | infoTipWindowPadding = 25;
14 | holder = React.createRef();
15 | infoTip = React.createRef();
16 |
17 | componentDidMount() {
18 | this.holder.current.addEventListener("mouseover", this.mousemove, false);
19 | this.holder.current.addEventListener("mouseover", this.mouseout, false);
20 | this.holder.current.addEventListener("mouseover", this.mousemove, false);
21 | }
22 |
23 | componentWillUnmount() {
24 | this.holder.current.removeEventListener("mouseover", this.mousemove, false);
25 | this.holder.current.removeEventListener("mouseover", this.mouseout, false);
26 | this.holder.current.removeEventListener("mouseover", this.mousemove, false);
27 | }
28 |
29 | setInfoTipState(state) {
30 | if (this.infoTip) {
31 | this.infoTip.current.setState(state);
32 | }
33 | }
34 |
35 | showInfoTip = (infoTip, target, x, y, rangeParent, rangeOffset) => {
36 | const scrollParent = Lib.getOverflowParent(target);
37 | const scrollX = x + (scrollParent ? scrollParent.scrollLeft : 0);
38 |
39 | // Distribute event to all registered listeners and show the info tip if
40 | // any of them return true.
41 | const dispatchArgs = [infoTip, target, scrollX, y, rangeParent, rangeOffset];
42 | const result = Lib.dispatch2(this.listeners, "showInfoTip", dispatchArgs);
43 |
44 | if (result && result.element) {
45 | unmountComponentAtNode(infoTip);
46 | render(result.element, infoTip);
47 |
48 | this.setInfoTipState({
49 | active: true,
50 | x,
51 | y,
52 | multiline: result.multiline,
53 | });
54 | } else {
55 | this.setInfoTipState({
56 | active: false,
57 | x,
58 | y,
59 | });
60 | }
61 | }
62 |
63 | onInfoTipRef = (ref) => {
64 | this.infoTipDom = ref;
65 | }
66 |
67 | addListener(listener) {
68 | this.listeners.push(listener);
69 | }
70 |
71 | removeListener(listener) {
72 | Lib.remove(this.listeners, listener);
73 | }
74 |
75 | mousemove = (e) => {
76 | const x = e.clientX;
77 | const y = e.clientY;
78 | this.showInfoTip(this.infoTipDom, e.target, x, y, e.rangeParent, e.rangeOffset);
79 | }
80 |
81 | mouseout = (e) => {
82 | if (!e.relatedTarget) {
83 | this.setInfoTipState({
84 | active: false,
85 | });
86 | }
87 | }
88 |
89 | render() {
90 | const { infoTipMargin, infoTipWindowPadding } = this;
91 | return (
92 |
93 | {this.props.children}
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | InfoTipHolder.propTypes = {
101 | children: PropTypes.node,
102 | };
103 |
104 | export default InfoTipHolder;
105 |
--------------------------------------------------------------------------------
/webapp/modules/Stats.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | import TimingPie from "./pie/TimingPie";
4 | import ContentPie from "./pie/ContentPie";
5 | import TrafficPie from "./pie/TrafficPie";
6 | import CachePie from "./pie/CachePie";
7 |
8 | class Stats extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default Stats;
22 |
--------------------------------------------------------------------------------
/webapp/modules/booleanFlipper.js:
--------------------------------------------------------------------------------
1 | export default function booleanFlipper(idx) {
2 | return (value, i) => (idx === i) ? !value : value;
3 | }
4 |
--------------------------------------------------------------------------------
/webapp/modules/buildInfo.js:
--------------------------------------------------------------------------------
1 | // __VERSION__ is supplied by webpack.
2 | // See webpage.config.js
3 | // eslint-disable-next-line
4 | export default __buildInfo__;
5 |
--------------------------------------------------------------------------------
/webapp/modules/core/array.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/array
5 | */
6 | define([
7 | "./trace"
8 | ],
9 |
10 | function(Trace) {
11 |
12 | //***********************************************************************************************//
13 |
14 | /**
15 | * Helper functions for arrays.
16 | * @alias module:core/array
17 | */
18 | var Arr = {};
19 |
20 | //*************************************************************************************************
21 | // Arrays
22 |
23 | /**
24 | * @param {Object} object
25 | */
26 | Arr.isArray = function(object)
27 | {
28 | // Supported by IE9
29 | // eslint-disable-next-line max-len
30 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
31 | return Array.isArray(object);
32 | };
33 |
34 | /**
35 | * @param {Array} array
36 | * @param {Function} fn
37 | */
38 | Arr.cloneArray = function(array, fn)
39 | {
40 | var newArray = [];
41 |
42 | if (fn)
43 | for (var i = 0; i < array.length; ++i)
44 | newArray.push(fn(array[i]));
45 | else
46 | for (var j = 0; j < array.length; ++j)
47 | newArray.push(array[j]);
48 |
49 | return newArray;
50 | };
51 |
52 | /**
53 | * @param {Array} array
54 | * @param {Number} index
55 | * @param {Array} other
56 | */
57 | Arr.arrayInsert = function(array, index, other)
58 | {
59 | for (var i = 0; i < other.length; ++i)
60 | array.splice(i+index, 0, other[i]);
61 | return array;
62 | };
63 |
64 | /**
65 | * @param {Array} list
66 | * @param {Object} item
67 | */
68 | Arr.remove = function(list, item)
69 | {
70 | for (var i = 0; i < list.length; ++i)
71 | {
72 | if (list[i] === item)
73 | {
74 | list.splice(i, 1);
75 | return true;
76 | }
77 | }
78 | return false;
79 | };
80 |
81 | // ********************************************************************************************* //
82 |
83 | return Arr;
84 |
85 | // ********************************************************************************************* //
86 | });
87 |
--------------------------------------------------------------------------------
/webapp/modules/core/cookies.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/cookies
5 | */
6 | define([
7 | "./string"
8 | ],
9 |
10 | function(Str) {
11 |
12 | //*************************************************************************************************
13 |
14 | /**
15 | * @return {string} Cookie path.
16 | */
17 | function generateCookiePath() {
18 | var path = window.location.pathname;
19 |
20 | // Firefox 64 and IE 11 use trailing slash for client-side set cookies,
21 | // Chrome 71 does not.
22 | // So ensure cookie path ends in "/" for consistency
23 | // https://stackoverflow.com/a/53784228/319878
24 | if (!window.location.pathname.match(/\/$/)) {
25 | var parts = path.split("/");
26 | parts.length -= 1;
27 | path = parts.join("/") + "/";
28 | }
29 |
30 | return path;
31 | }
32 |
33 | /**
34 | * Helper functions for handling cookies.
35 | * @alias module:core/cookies
36 | */
37 | var Cookies =
38 | {
39 | /**
40 | * @param {String} name The name of the cookie to get the value of.
41 | * @return {String} The cookie value if found, else `null` if the cookie exists but has no
42 | * value, else `undefined`.
43 | */
44 | getCookie: function(name)
45 | {
46 | var cookies = document.cookie.split(";");
47 | for (var i= 0; i
0 ? "-" : "+") +
101 | f(Math.abs(offsetHours)) + ":" + f(Math.abs(offsetMinutes));
102 |
103 | return result + prettyOffset;
104 | };
105 |
106 | // ********************************************************************************************* //
107 |
108 | return Date_;
109 |
110 | // ********************************************************************************************* //
111 | });
112 |
--------------------------------------------------------------------------------
/webapp/modules/core/json.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/json
5 | */
6 | define([
7 | "./trace"
8 | ],
9 |
10 | function(Trace) {
11 |
12 | //***********************************************************************************************//
13 |
14 | /**
15 | * @alias module:core/json
16 | */
17 | var Json = {};
18 |
19 | //***********************************************************************************************//
20 | // JSON
21 |
22 | /**
23 | * @param {Object} obj
24 | * @return {Object|null}
25 | */
26 | Json.cloneJSON = function(obj)
27 | {
28 | if (obj === null || typeof(obj) !== "object")
29 | return obj;
30 |
31 | try
32 | {
33 | var temp = obj.constructor();
34 | for (var key in obj)
35 | temp[key] = this.cloneJSON(obj[key]);
36 | return temp;
37 | }
38 | catch (err)
39 | {
40 | Trace.exception(err);
41 | }
42 |
43 | return null;
44 | };
45 |
46 | // ********************************************************************************************* //
47 |
48 | return Json;
49 |
50 | // ********************************************************************************************* //
51 | });
52 |
--------------------------------------------------------------------------------
/webapp/modules/core/lib.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/lib
5 | */
6 | define([
7 | "./array",
8 | "./css",
9 | "./date",
10 | "./dom",
11 | "./events",
12 | "./json",
13 | "./mime",
14 | "./object",
15 | "./rect",
16 | "./sniff",
17 | "./string",
18 | "./url",
19 | "./trace"
20 | ],
21 |
22 | function(Arr, Css, Date_, Dom, Events, Json, Mime, Obj, Rect, Sniff, Str, Url, Trace) {
23 |
24 | //***********************************************************************************************//
25 |
26 | /**
27 | * @alias module:core/lib
28 | */
29 | var Lib = {};
30 |
31 | //***********************************************************************************************//
32 | // Browser Version
33 |
34 | Obj.append(Lib, Sniff);
35 |
36 | //***********************************************************************************************//
37 | // Core concepts (extension, bind)
38 |
39 | Obj.append(Lib, Obj);
40 |
41 | //***********************************************************************************************//
42 | // Events
43 |
44 | Obj.append(Lib, Events);
45 |
46 | //***********************************************************************************************//
47 | // Rect {top, left, height, width}
48 |
49 | Obj.append(Lib, Rect);
50 |
51 | //*************************************************************************************************
52 | // Arrays
53 |
54 | Obj.append(Lib, Arr);
55 |
56 | //*************************************************************************************************
57 | // Text Formatting
58 |
59 | Obj.append(Lib, Str);
60 |
61 | //*************************************************************************************************
62 | // Date
63 |
64 | Obj.append(Lib, Date_);
65 |
66 | //*************************************************************************************************
67 | // MIME
68 |
69 | Obj.append(Lib, Mime);
70 |
71 | //*************************************************************************************************
72 | // URL
73 |
74 | Obj.append(Lib, Url);
75 |
76 | //*************************************************************************************************
77 | // DOM
78 |
79 | Obj.append(Lib, Dom);
80 |
81 | //***********************************************************************************************//
82 | // CSS
83 |
84 | Obj.append(Lib, Css);
85 |
86 | //***********************************************************************************************//
87 | // JSON
88 |
89 | Obj.append(Lib, Json);
90 |
91 | // ********************************************************************************************* //
92 |
93 | return Lib;
94 |
95 | // ********************************************************************************************* //
96 | });
97 |
--------------------------------------------------------------------------------
/webapp/modules/core/mime.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/mime
5 | */
6 | define([
7 | "./trace"
8 | ],
9 |
10 | function(Trace) {
11 |
12 | //***********************************************************************************************//
13 |
14 | /**
15 | * @alias module:core/mime
16 | */
17 | var Mime = {};
18 |
19 | //*************************************************************************************************
20 | // MIME handling
21 |
22 | /**
23 | * @param {String} mimeType
24 | * @return {String}
25 | */
26 | Mime.extractMimeType = function(mimeType) {
27 | if ("string" !== typeof mimeType) {
28 | throw new Error((typeof mimeType) + " is not of type string");
29 | }
30 | // remove parameters (if any)
31 | var idx = mimeType.indexOf(";");
32 | if (idx > -1) {
33 | mimeType = mimeType.substring(0, idx).trim();
34 | }
35 | return mimeType;
36 | };
37 |
38 | // ********************************************************************************************* //
39 |
40 | return Mime;
41 |
42 | // ********************************************************************************************* //
43 | });
44 |
--------------------------------------------------------------------------------
/webapp/modules/core/object.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/object
5 | */
6 | define([
7 | "./array",
8 | "./sniff"
9 | ],
10 |
11 | function(Arr, Sniff) {
12 |
13 | //***********************************************************************************************//
14 |
15 | /**
16 | * @alias module:core/object
17 | */
18 | var Obj = {};
19 |
20 | //***********************************************************************************************//
21 | // Type Checking
22 |
23 | var toString = Object.prototype.toString;
24 | var reFunction = /^\s*function(\s+[\w_$][\w\d_$]*)?\s*\(/;
25 |
26 | /**
27 | * @param {Object} object
28 | */
29 | Obj.isFunction = function(object)
30 | {
31 | if (!object)
32 | return false;
33 |
34 | return toString.call(object) === "[object Function]" ||
35 | Sniff.isIE && typeof object !== "string" &&
36 | reFunction.test(String(object));
37 | };
38 |
39 | //***********************************************************************************************//
40 | // Core concepts (extension, bind)
41 |
42 | /**
43 | * @param {Object} l
44 | * @param {Object} r
45 | */
46 | Obj.extend = function copyObject(l, r)
47 | {
48 | var m = {};
49 | Obj.append(m, l);
50 | Obj.append(m, r);
51 | return m;
52 | };
53 |
54 | /**
55 | * @param {Object} l
56 | * @param {Object} r
57 | */
58 | Obj.append = function(l, r)
59 | {
60 | for (var n in r)
61 | l[n] = r[n];
62 | return l;
63 | };
64 |
65 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
66 |
67 | Obj.bind = function() // fn, thisObject, args => thisObject.fn(args, arguments);
68 | {
69 | var args = Arr.cloneArray(arguments);
70 | var fn = args.shift();
71 | var object = args.shift();
72 | return function bind() {
73 | var allArgs = args.concat(Arr.cloneArray(arguments));
74 | return fn.apply(object, allArgs);
75 | };
76 | };
77 |
78 | Obj.bindFixed = function() // fn, thisObject, args => thisObject.fn(args);
79 | {
80 | var args = Arr.cloneArray(arguments);
81 | var fn = args.shift();
82 | var object = args.shift();
83 | return function bindFixed() {
84 | return fn.apply(object, args);
85 | };
86 | };
87 |
88 | // ********************************************************************************************* //
89 |
90 | return Obj;
91 |
92 | // ********************************************************************************************* //
93 | });
94 |
--------------------------------------------------------------------------------
/webapp/modules/core/rect.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * A module containing rectangle utilities.
5 | * @module core/rect
6 | */
7 | define([
8 | "./trace"
9 | ],
10 |
11 | function(Trace) {
12 |
13 | //***********************************************************************************************//
14 |
15 | /**
16 | * A rectangle type.
17 | * @typedef {Object} Rectangle
18 | * @property {Number} top
19 | * @property {Number} left
20 | * @property {Number} height
21 | * @property {Number} width
22 | */
23 |
24 | /**
25 | * @alias module:core/rect
26 | */
27 | var Rect = {};
28 |
29 | //***********************************************************************************************//
30 | // Rect {top, left, height, width}
31 |
32 | /**
33 | * @param {module:core/rect~Rectangle} rect The rectangle to inflate.
34 | * @param {Number} x
35 | * @param {Number} y
36 | */
37 | Rect.inflateRect = function(rect, x, y)
38 | {
39 | return {
40 | top: rect.top - y,
41 | left: rect.left - x,
42 | height: rect.height + 2*y,
43 | width: rect.width + 2*x
44 | };
45 | };
46 |
47 | /**
48 | * @param {module:core/rect~Rectangle} rect The rectangle to test.
49 | * @param {Number} x
50 | * @param {Number} y
51 | */
52 | Rect.pointInRect = function(rect, x, y)
53 | {
54 | return (y >= rect.top && y <= rect.top + rect.height &&
55 | x >= rect.left && x <= rect.left + rect.width);
56 | };
57 |
58 | // ********************************************************************************************* //
59 |
60 | return Rect;
61 |
62 | // ********************************************************************************************* //
63 | });
64 |
--------------------------------------------------------------------------------
/webapp/modules/core/sniff.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/sniff
5 | */
6 | define([
7 | "./trace"
8 | ],
9 |
10 | function(Trace) {
11 |
12 | //***********************************************************************************************//
13 |
14 | var Sniff = {};
15 |
16 | //***********************************************************************************************//
17 | // Browser Version
18 |
19 | var hasNav = ("undefined" !== typeof navigator);
20 | var hasWin = ("undefined" !== typeof window);
21 | var userAgent = hasNav ? navigator.userAgent.toLowerCase() : "";
22 |
23 | Sniff.isFirefox = /firefox/.test(userAgent);
24 | Sniff.isOpera = /opera/.test(userAgent);
25 | Sniff.isWebkit = /webkit/.test(userAgent);
26 | Sniff.isSafari = /webkit/.test(userAgent);
27 | Sniff.isIE = /msie/.test(userAgent) && !/opera/.test(userAgent);
28 | Sniff.isIE6 = hasNav && /msie 6/i.test(navigator.appVersion);
29 | Sniff.browserVersion = (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1];
30 | Sniff.isIElt8 = Sniff.isIE && (Sniff.browserVersion-0 < 8);
31 | Sniff.supportsSelectElementText = hasWin && ((window.getSelection && window.document.createRange) ||
32 | (window.document.body.createTextRange));
33 |
34 | // ********************************************************************************************* //
35 |
36 | return Sniff;
37 |
38 | // ********************************************************************************************* //
39 | });
40 |
--------------------------------------------------------------------------------
/webapp/modules/core/trace.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | /**
4 | * @module core/trace
5 | */
6 | define([
7 | ],
8 |
9 | function() {
10 |
11 | //*************************************************************************************************
12 |
13 | var Trace = {
14 | log: function(){},
15 | error: function(){},
16 | exception: function(){},
17 | time: function(){},
18 | timeEnd: function(){}
19 | };
20 |
21 | if (typeof(console) === "undefined")
22 | return Trace;
23 |
24 | //@ifdef DEBUG
25 | Trace.log = function()
26 | {
27 | if (typeof(console.log) === "function")
28 | console.log.apply(console, arguments);
29 | };
30 |
31 | Trace.time = function()
32 | {
33 | if (typeof(console.time) === "function")
34 | console.time.apply(console, arguments);
35 | };
36 |
37 | Trace.timeEnd = function(name, message)
38 | {
39 | if (typeof(console.timeEnd) === "function")
40 | console.timeEnd.apply(console, arguments);
41 | };
42 | //@endif
43 |
44 | Trace.error = function()
45 | {
46 | if (typeof(console.error) === "function")
47 | console.error.apply(console, arguments);
48 | };
49 |
50 | Trace.exception = function()
51 | {
52 | if (typeof(console.error) === "function")
53 | console.error.apply(console, arguments);
54 | };
55 |
56 | return Trace;
57 |
58 | //*************************************************************************************************
59 | });
60 |
--------------------------------------------------------------------------------
/webapp/modules/deferred.js:
--------------------------------------------------------------------------------
1 | export default function deferred() {
2 | let resolve;
3 | let reject;
4 | const promise = new Promise((...args) => {
5 | [resolve, reject] = args;
6 | });
7 | return {
8 | resolve,
9 | reject,
10 | promise,
11 | };
12 | };
13 |
--------------------------------------------------------------------------------
/webapp/modules/intersperse.js:
--------------------------------------------------------------------------------
1 | export default function intersperse(arr, supplier) {
2 | const len = arr.length;
3 | return arr.reduce((arr2, existingItem, i) => {
4 | const isLastItem = (i === len - 1);
5 | const newItem = (typeof supplier === "function")
6 | ? supplier(existingItem, i)
7 | : supplier;
8 | arr2.push(existingItem);
9 | if (!isLastItem) {
10 | arr2.push(newItem);
11 | }
12 | return arr2;
13 | }, []);
14 | }
15 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/Bar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class Bar extends Component {
5 | render() {
6 | const { bar } = this.props;
7 |
8 | const timeLabel = bar.timeLabel ? {bar.timeLabel} : null;
9 |
10 | return (
11 |
12 | {timeLabel}
13 |
14 | );
15 | }
16 | }
17 |
18 | Bar.propTypes = {
19 | bar: PropTypes.object,
20 | };
21 |
22 | export default Bar;
23 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/NetInfoRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Strings from "i18n!../nls/requestBody";
5 |
6 | import TabView from "../tabview/TabView";
7 | import Headers from "../requestbodies/Headers";
8 | import PlainResponse from "../requestbodies/PlainResponse";
9 | import Highlighted from "../requestbodies/Highlighted";
10 | import URLParameters from "../requestbodies/URLParameters";
11 | import SendData from "../requestbodies/SendData";
12 | import DataURL from "../requestbodies/DataURL";
13 | import JSONEntryTree from "../requestbodies/JSONEntryTree";
14 | import XMLEntryTree from "../requestbodies/XMLEntryTree";
15 | import ExternalImage from "../requestbodies/ExternalImage";
16 | import Image from "../requestbodies/Image";
17 |
18 | const responseBodyComponents = {
19 | Headers: {
20 | Component: Headers,
21 | id: "Headers",
22 | label: Strings.Headers,
23 | },
24 | PlainResponse: {
25 | Component: PlainResponse,
26 | id: "Response",
27 | label: Strings.Response,
28 | },
29 | Highlighted: {
30 | Component: Highlighted,
31 | id: "Highlighted",
32 | label: Strings.Highlighted,
33 | },
34 | URLParameters: {
35 | Component: URLParameters,
36 | id: "Params",
37 | label: Strings.URLParameters,
38 | },
39 | SendData: {
40 | Component: SendData,
41 | id: "Post",
42 | // TODO, this has to be determined on-the-fly by entry.request.method
43 | label: "Post",
44 | },
45 | DataURL: {
46 | Component: DataURL,
47 | id: "DataURL",
48 | label: Strings.DataURL,
49 | },
50 | JSON: {
51 | Component: JSONEntryTree,
52 | id: "JSON",
53 | label: Strings.JSON,
54 | },
55 | XML: {
56 | Component: XMLEntryTree,
57 | id: "XML",
58 | label: Strings.XML,
59 | },
60 | Image: {
61 | Component: Image,
62 | id: "Image",
63 | label: Strings.Image,
64 | },
65 | ExternalImage: {
66 | Component: ExternalImage,
67 | id: "ExternalImage",
68 | label: Strings.ExternalImage,
69 | },
70 | };
71 |
72 | function createTabs(entry) {
73 | const tabs = [];
74 | Object.keys(responseBodyComponents).forEach((name) => {
75 | const Component = responseBodyComponents[name].Component;
76 | if (!Component.canShowEntry || Component.canShowEntry(entry)) {
77 | const info = responseBodyComponents[name];
78 | tabs.push(Object.assign({}, info, {
79 | body: ,
80 | }));
81 | }
82 | });
83 | return tabs;
84 | }
85 |
86 | class NetInfoRow extends Component {
87 | state = {
88 | selectedTabIdx: 0,
89 | }
90 |
91 | setSelectedTab = (selectedTabIdx) => {
92 | if (typeof selectedTabIdx === "string") {
93 | selectedTabIdx = this.tabs.findIndex(({ id }) => id === selectedTabIdx);
94 | }
95 | this.setState({ selectedTabIdx });
96 | }
97 |
98 | render() {
99 | const { entry } = this.props;
100 | this.tabs = createTabs(entry);
101 | return (
102 |
103 |
104 |
110 | |
111 |
112 | );
113 | }
114 | }
115 |
116 | NetInfoRow.propTypes = {
117 | entry: PropTypes.object,
118 | };
119 |
120 | export default NetInfoRow;
121 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/NetRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Url from "../core/url";
5 | import * as Str from "../core/string";
6 | import Bar from "./Bar";
7 | import PageTimingBar from "./PageTimingBar";
8 |
9 | class NetRow extends Component {
10 | getHref(entry) {
11 | const fileName = Url.getFileName(this.getFullHref(entry));
12 | return unescape(entry.request.method + " " + fileName);
13 | }
14 |
15 | getFullHref(entry) {
16 | return unescape(entry.request.url);
17 | }
18 |
19 | getStatus(entry) {
20 | const status = entry.response.status > 0 ? (entry.response.status + " ") : "";
21 | return status + entry.response.statusText;
22 | }
23 |
24 | getType(entry) {
25 | return entry.response.content.mimeType;
26 | }
27 |
28 | getDomain(entry) {
29 | return Url.getPrettyDomain(entry.request.url);
30 | }
31 |
32 | getSize(entry) {
33 | const bodySize = entry.response.bodySize;
34 | const size = (bodySize && bodySize !== -1) ? bodySize : entry.response.content.size;
35 |
36 | return this.formatSize(size);
37 | }
38 |
39 | formatSize(bytes) {
40 | return Str.formatSize(bytes);
41 | }
42 |
43 | shouldComponentUpdate(nextProps, nextState) {
44 | const { entry, entryId, opened, bars, pageTimingBars } = this.props;
45 | // TODO - Very basic, needs improving
46 | return !(
47 | entry === nextProps.entry &&
48 | entryId === nextProps.entryId &&
49 | opened === nextProps.opened &&
50 | bars.length === nextProps.bars.length &&
51 | pageTimingBars.length === nextProps.pageTimingBars.length
52 | );
53 | }
54 |
55 | render() {
56 | const { entry, entryId, breakLayout, opened, bars, pageTimingBars, onClick } = this.props;
57 |
58 | const barComponents = (bars || []).map(
59 | (bar, i) =>
60 | );
61 |
62 | const pageTimingBarComponents = (pageTimingBars || []).map(
63 | (pageTimingBar, i) =>
64 | );
65 |
66 | return (
67 |
68 |
69 |
70 | {this.getHref(entry)}
71 |
72 |
73 | {this.getFullHref(entry)}
74 |
75 | |
76 |
77 |
78 | {this.getStatus(entry)}
79 |
80 | |
81 |
82 |
83 | {this.getType(entry)}
84 |
85 | |
86 |
87 |
88 | {this.getDomain(entry)}
89 |
90 | |
91 |
92 |
93 | {this.getSize(entry)}
94 |
95 | |
96 |
97 |
98 | {barComponents}
99 | {pageTimingBarComponents}
100 |
101 | |
102 |
103 |
104 | |
105 |
106 | );
107 | }
108 | }
109 |
110 | NetRow.propTypes = {
111 | entry: PropTypes.object,
112 | entryId: PropTypes.number,
113 | opened: PropTypes.bool,
114 | bars: PropTypes.array,
115 | pageTimingBars: PropTypes.array,
116 | onClick: PropTypes.func,
117 | };
118 |
119 | export default NetRow;
120 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/NetSummaryRow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Strings from "i18n!../nls/requestList";
5 | import * as Str from "../core/string";
6 | import { calculateSummaryInfo } from "./NetSummaryRowModel";
7 |
8 | class NetSummaryRow extends React.Component {
9 | formatRequestCount(count) {
10 | return count + " " + (count === 1 ? Strings.request : Strings.requests);
11 | }
12 |
13 | formatTotalTime(totalTime, onLoadTime) {
14 | const onLoadStr = onLoadTime > 0 ? " (onload: " + Str.formatTime(onLoadTime) + ")" : "";
15 | return Str.formatTime(totalTime) + onLoadStr;
16 | }
17 |
18 | getCacheSizeContent(cachedSize) {
19 | if (cachedSize <= 0) {
20 | return "";
21 | }
22 | return [
23 | "(",
24 | {Str.formatSize(cachedSize)},
25 | {Strings.summaryFromCache},
26 | ")",
27 | ];
28 | }
29 |
30 | render() {
31 | let { page, entries, summaryInfo } = this.props;
32 | if (!summaryInfo) {
33 | summaryInfo = calculateSummaryInfo(page, entries);
34 | }
35 |
36 | return (
37 |
38 | |
39 |
40 | {this.formatRequestCount(entries.length)}
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 |
48 | {Str.formatSize(summaryInfo.totalTransferredSize)}
49 | |
50 |
51 |
52 |
53 | {this.getCacheSizeContent(summaryInfo.cachedSize)}
54 |
55 |
56 | ({Str.formatSize(summaryInfo.totalUncompressedSize)}
57 | {Strings.uncompressed})
58 |
59 |
60 | {this.formatTotalTime(summaryInfo.totalTime, summaryInfo.onLoadTime)}
61 |
62 |
63 | |
64 | |
65 |
66 | );
67 | }
68 | };
69 |
70 | NetSummaryRow.propTypes = {
71 | entries: PropTypes.array.isRequired,
72 | page: PropTypes.object,
73 | summaryInfo: PropTypes.object,
74 | };
75 |
76 | export default NetSummaryRow;
77 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/NetSummaryRowModel.js:
--------------------------------------------------------------------------------
1 | import HarModel from "../preview/harModel";
2 | import * as Date_ from "../core/date";
3 |
4 | export function summarizeEntries(entries) {
5 | let cachedSize = 0;
6 | let totalTransferredSize = 0;
7 | let totalUncompressedSize = 0;
8 |
9 | let fileCount = 0;
10 | let minTime = 0;
11 | let maxTime = 0;
12 |
13 | for (let i = 0; i < entries.length; i++) {
14 | let file = entries[i];
15 | let startedDateTime = Date_.parseISO8601(file.startedDateTime);
16 |
17 | ++fileCount;
18 |
19 | let transferredSize = HarModel.getEntryTransferredSize(file);
20 | let uncompressedSize = HarModel.getEntryUncompressedSize(file);
21 |
22 | totalTransferredSize += transferredSize;
23 | totalUncompressedSize += uncompressedSize;
24 |
25 | if (HarModel.isCachedEntry(file)) {
26 | cachedSize += uncompressedSize;
27 | }
28 |
29 | if (!minTime || startedDateTime < minTime) {
30 | minTime = startedDateTime;
31 | }
32 |
33 | let fileEndTime = startedDateTime + file.time;
34 | if (fileEndTime > maxTime) {
35 | maxTime = fileEndTime;
36 | }
37 | }
38 |
39 | let totalTime = maxTime - minTime;
40 | return {
41 | cachedSize,
42 | totalUncompressedSize,
43 | totalTransferredSize,
44 | totalTime,
45 | fileCount
46 | };
47 | }
48 |
49 | export function calculateSummaryInfo(page, entries) {
50 | let summaryInfo = summarizeEntries(entries);
51 | summaryInfo.onLoadTime = 0;
52 | if (page) {
53 | summaryInfo.onLoadTime = page.pageTimings.onLoad;
54 | }
55 | return summaryInfo;
56 | }
57 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/PageTimingBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class PageTimingBar extends Component {
5 | render() {
6 | const { left, classes } = this.props;
7 | const style = {
8 | left: left,
9 | display: "block",
10 | };
11 | return (
12 |
13 | );
14 | }
15 | }
16 |
17 | PageTimingBar.propTypes = {
18 | classes: PropTypes.string,
19 | left: PropTypes.string,
20 | };
21 |
22 | export default PageTimingBar;
23 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/defaultTimingDefinitions.js:
--------------------------------------------------------------------------------
1 | import Strings from "i18n!../nls/requestList";
2 |
3 | export default [
4 | {
5 | name: "onContentLoad",
6 | classes: "netContentLoadBar",
7 | description: Strings.ContentLoad,
8 | },
9 | {
10 | name: "onLoad",
11 | classes: "netWindowLoadBar",
12 | description: Strings.WindowLoad,
13 | },
14 | ];
15 |
--------------------------------------------------------------------------------
/webapp/modules/nettable/testData.js:
--------------------------------------------------------------------------------
1 | const entry = {
2 | request: {
3 | method: "GET",
4 | url: "http://www.softwareishard.com/blog/firebug/firebug-16-beta-1-released/"
5 | },
6 | response: {
7 | status: 200,
8 | statusText: "OK",
9 | bodySize: 31918,
10 | content: {
11 | mimeType: "text/html"
12 | }
13 | }
14 | };
15 |
16 | const bars = [
17 | {
18 | className: "netBlockingBar",
19 | style: {
20 | left: "0%",
21 | width: "0%"
22 | }
23 | },
24 | {
25 | className: "netResolvingBar",
26 | style: {
27 | left: "0%",
28 | width: "0%"
29 | }
30 | },
31 | {
32 | className: "netConnectingBar",
33 | style: {
34 | left: "0%",
35 | width: "0.852%"
36 | }
37 | },
38 | {
39 | className: "netSendingBar",
40 | style: {
41 | left: "0%",
42 | width: "0.852%"
43 | }
44 | },
45 | {
46 | className: "netWaitingBar",
47 | style: {
48 | left: "0%",
49 | width: "73.442%"
50 | }
51 | },
52 | {
53 | className: "netReceivingBar",
54 | style: {
55 | left: "0%",
56 | width: "77.352%"
57 | },
58 | timeLabel: "2s"
59 | }
60 | ];
61 |
62 | export { entry, bars };
63 |
--------------------------------------------------------------------------------
/webapp/modules/nls/domTab.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "domTabLabel": "HAR",
7 | "searchDisabledForIE": "You need Mozilla or WebKit based browser to search in HAR",
8 | "searchOptionJsonQuery": "JSON Query",
9 | "tableView": "Table View",
10 | "searchResultsDefaultText": "JSON Query Results",
11 | "searchPlaceholder": "Search",
12 | "jsonQueryPlaceholder": "JSON Query",
13 | "queryResultsTableView": "Table View"
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/webapp/modules/nls/harModel.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "validationType": "HAR Validation",
7 | "validationSumTimeError": "Sum of request timings doesn't correspond to the total value: " +
8 | "%S (request.time: %S vs. sum: %S), request#: %S, parent page: %S",
9 | "validationNegativeTimeError": "Negative time is not allowed: " +
10 | "%S, request#: %S, parent page: %S"
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/webapp/modules/nls/harStats.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "pieLabelDNS": "DNS",
7 | "pieLabelSSL": "SSL/TLS",
8 | "pieLabelConnect": "Connect",
9 | "pieLabelBlocked": "Blocked",
10 | "pieLabelSend": "Send",
11 | "pieLabelWait": "Wait",
12 | "pieLabelReceive": "Receive",
13 |
14 | "pieLabelHTMLText": "HTML/Text",
15 | "pieLabelJavaScript": "JavaScript",
16 | "pieLabelCSS": "CSS",
17 | "pieLabelImage": "Image",
18 | "pieLabelFlash": "Flash",
19 | "pieLabelOthers": "Others",
20 |
21 | "pieLabelHeadersSent": "Headers Sent",
22 | "pieLabelBodiesSent": "Bodies Sent",
23 | "pieLabelHeadersReceived": "Headers Received",
24 | "pieLabelBodiesReceived": "Bodies Received",
25 |
26 | "pieLabelDownloaded": "Downloaded",
27 | "pieLabelPartial": "Partial",
28 | "pieLabelFromCache": "From Cache"
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/webapp/modules/nls/harViewer.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "aboutTabLabel": "About",
7 | "schemaTabLabel": "Schema"
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/webapp/modules/nls/homeTab.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "homeTabLabel": "Home",
7 | "loadingHar": "Loading..."
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/webapp/modules/nls/pageList.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "column.label.index": "Index",
7 | "column.label.url": "URL",
8 | "column.label.status": "Status",
9 | "column.label.type": "Type",
10 | "column.label.domain": "Domain",
11 | "column.label.serverIPAddress": "Server IP Address",
12 | "column.label.connection": "Connection",
13 | "column.label.size": "Compressed Size",
14 | "column.label.uncompressedSize": "Uncompressed Size",
15 | "column.label.timeline": "Timeline",
16 | "action.label.Reset": "Reset"
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/webapp/modules/nls/pageTimeline.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "pageLoad": "Page Load",
7 | "request": "Request",
8 | "requests": "Requests",
9 | "pageBarTooltip": "Click to select and include in statistics preview."
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/webapp/modules/nls/previewTab.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "previewTabLabel": "Preview",
7 | "showTimelineButton": "Show Page Timeline",
8 | "hideTimelineButton": "Hide Page Timeline",
9 | "showTimelineTooltip": "Show/hide statistic preview for selected pages in the timeline.",
10 | "showStatsButton": "Show Statistics",
11 | "hideStatsButton": "Hide Statistics",
12 | "showStatsTooltip": "Show/hide page timeline.",
13 | "clearButton": "Clear",
14 | "clearTooltip": "Remove all HAR logs from the viewer",
15 | "downloadTooltip": "Download all current data in one HAR file.",
16 | "downloadError": "Failed to save HAR data",
17 | "menuShowHARSource": "Show HAR Source"
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/webapp/modules/nls/requestBody.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "RequestVersion": "Request Version",
7 | "ResponseVersion": "Response Version",
8 | "RequestHeaders": "Request Headers",
9 | "ResponseHeaders": "Response Headers",
10 | "RequestCookies": "Request Cookies",
11 | "ResponseCookies": "Response Cookies",
12 | "URLParameters": "Params",
13 | "Headers": "Headers",
14 | "Post": "Post",
15 | "Put": "Put",
16 | "Get": "Get",
17 | "Cookies": "Cookies",
18 | "Response": "Response",
19 | "Highlighted": "Highlighted",
20 | "Image": "Img (from HAR)",
21 | "ImageTitle": "Uses image payload data from the HAR",
22 | "ExternalImage": "Img (from URL)",
23 | "ExternalImageTitle": "Creates an IMG tag whose src is the request URL",
24 | "Cache": "Cache",
25 | "HTML": "HTML",
26 | "JSON": "JSON",
27 | "XML": "XML",
28 | "DataURL": "Data URL"
29 | }
30 | });
31 |
--------------------------------------------------------------------------------
/webapp/modules/nls/requestList.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "summaryFromCache": "From Cache",
7 | "resourceFromCache": "Resource from cache",
8 | "uncompressed": "Uncompressed",
9 | "menuBreakLayout": "Break Timeline Layout",
10 | "menuOpenRequestInWindow": "Open Request in New Window",
11 | "menuOpenResponseInWindow": "Open Response in New Window",
12 | "request": "Request",
13 | "requests": "Requests",
14 |
15 | "tooltipSize": "%S (%S bytes)",
16 | "tooltipZippedSize": "%S (%S bytes) - compressed",
17 | "tooltipUnzippedSize": "%S (%S bytes) - uncompressed",
18 | "unknownSize": "Unknown size",
19 |
20 | "request.Started": "Request start time since the beginning",
21 | "request.phases.label": "Request phases start and elapsed time relative to the request start:",
22 | "request.phase.Resolving": "DNS Lookup",
23 | "request.phase.Connecting": "Connecting",
24 | "request.phase.Blocking": "Blocking",
25 | "request.phase.Sending": "Sending",
26 | "request.phase.Waiting": "Waiting",
27 | "request.phase.Receiving": "Receiving",
28 |
29 | "request.timings.label": "Event timing relative to the request start:",
30 | "ContentLoad": "DOM Loaded",
31 | "WindowLoad": "Page Loaded",
32 | "page.event.Load": "Page Loaded",
33 |
34 | "menuBreakTimeline": "Break Timeline Layout",
35 | "menuOpenRequest": "Open Request in New Window",
36 | "menuOpenResponse": "Open Response in New Window"
37 | }
38 | });
39 |
--------------------------------------------------------------------------------
/webapp/modules/nls/search.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "search": "Search",
7 | "caseSensitive": "Case Sensitive"
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/webapp/modules/nls/tableView.js:
--------------------------------------------------------------------------------
1 | /* See license.txt for terms of usage */
2 |
3 | define(
4 | {
5 | "root": {
6 | "objectProperties": "Object Properties"
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/webapp/modules/pagetable/PageInfoRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import NetTable from "../nettable/NetTable";
5 |
6 | class PageInfoRow extends Component {
7 | shouldComponentUpdate(nextProps, nextState) {
8 | const { model, page } = this.props;
9 | return !(model === nextProps.model && page === nextProps.page);
10 | }
11 |
12 | render() {
13 | const { model, page } = this.props;
14 | return (
15 |
16 |
17 |
18 | |
19 |
20 | );
21 | }
22 | }
23 |
24 | PageInfoRow.propTypes = {
25 | model: PropTypes.object,
26 | page: PropTypes.object,
27 | };
28 |
29 | export default PageInfoRow;
30 |
--------------------------------------------------------------------------------
/webapp/modules/pagetable/PageRow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import * as Str from "../core/string";
4 |
5 | function getPageTitle(page) {
6 | return Str.cropString(page.title, 100);
7 | }
8 |
9 | const PageRow = (props) => {
10 | return (
11 |
12 | {getPageTitle(props.page)} |
13 | |
14 |
15 | );
16 | };
17 |
18 | PageRow.propTypes = {
19 | onClick: PropTypes.func,
20 | opened: PropTypes.bool,
21 | page: PropTypes.object,
22 | };
23 |
24 | export default PageRow;
25 |
--------------------------------------------------------------------------------
/webapp/modules/pagetable/PageTable.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import booleanFlipper from "../booleanFlipper";
5 | import PageRow from "./PageRow";
6 | import PageInfoRow from "./PageInfoRow";
7 |
8 | class PageTable extends React.Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | const { model } = this.props;
13 |
14 | this.state = {};
15 |
16 | if (model && model.input) {
17 | this.state.pageRowExpandedState = this.getInitialExpandedState();
18 | }
19 | }
20 |
21 | getInitialExpandedState() {
22 | const { model, expandAll } = this.props;
23 | const pages = model.input.log.pages || [];
24 | const pageRowExpandedState = pages.map(() => expandAll);
25 | if (pageRowExpandedState.length === 1) {
26 | pageRowExpandedState[0] = true;
27 | }
28 | return pageRowExpandedState;
29 | }
30 |
31 | createPageRows(model) {
32 | const rows = [];
33 |
34 | if (!model || !model.input) {
35 | return rows;
36 | }
37 |
38 | const { pages } = model.input.log;
39 |
40 | if (pages) {
41 | const { pageRowExpandedState } = this.state;
42 |
43 | // Use concat to flatten an array of arrays to a flat array.
44 | return rows.concat(pages.map((page, i) => {
45 | const opened = pageRowExpandedState[i];
46 | const pageRow = ;
47 | if (!opened) {
48 | return pageRow;
49 | }
50 | return [pageRow, ];
51 | }));
52 | }
53 |
54 | return rows;
55 | }
56 |
57 | onPageRowClick(pageRowIdx) {
58 | this.setState(({ pageRowExpandedState }) => ({
59 | pageRowExpandedState: pageRowExpandedState.map(booleanFlipper(pageRowIdx)),
60 | }));
61 | }
62 |
63 | render() {
64 | const { model } = this.props;
65 | const pageRows = this.createPageRows(model);
66 |
67 | return (
68 |
69 |
70 | {pageRows}
71 |
72 |
73 | );
74 | }
75 | };
76 |
77 | PageTable.displayName = "pagetable/PageTable";
78 |
79 | PageTable.propTypes = {
80 | model: PropTypes.object,
81 | expandAll: PropTypes.bool,
82 | };
83 |
84 | export default PageTable;
85 |
--------------------------------------------------------------------------------
/webapp/modules/pagetimeline/PageDescContainer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Date_ from "../core/date";
5 | import * as Str from "../core/string";
6 | import Strings from "i18n!../nls/PageTimeline";
7 |
8 | class PageDescContainer extends Component {
9 | render() {
10 | const { page, numEntries } = this.props;
11 | return (
12 |
13 |
14 |
15 |
{
16 | page ? this.getSummary(page, numEntries) : ""
17 | }
18 |
{
19 | page ? this.getTime(this.props.page) : ""
20 | }
21 |
{
22 | page ? this.getTitle(this.props.page) : ""
23 | }
24 |
{
25 | page ? this.getComment(this.props.page) : ""
26 | }
27 |
28 |
29 | );
30 | }
31 |
32 | getSummary(page, numEntries) {
33 | let summary = "";
34 | const onLoad = page.pageTimings.onLoad;
35 | if (onLoad > 0) {
36 | summary += Strings.pageLoad + ": " + Str.formatTime(onLoad.toFixed(2)) + ", ";
37 | }
38 |
39 | const count = numEntries;
40 | summary += count + " " + (count === 1 ? Strings.request : Strings.requests);
41 |
42 | return summary;
43 | }
44 |
45 | getTime(page) {
46 | const pageStart = Date_.parseISO8601(page.startedDateTime);
47 | const date = new Date(pageStart);
48 | return date.toLocaleString();
49 | }
50 |
51 | getTitle(page) {
52 | return page.title;
53 | }
54 |
55 | getComment(page) {
56 | return page.comment ? page.comment : "";
57 | }
58 | }
59 |
60 | PageDescContainer.propTypes = {
61 | numEntries: PropTypes.number,
62 | page: PropTypes.object,
63 | };
64 |
65 | export default PageDescContainer;
66 |
--------------------------------------------------------------------------------
/webapp/modules/pagetimeline/PageTimelineCol.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class PageTimelineCol extends Component {
5 | tdRef = React.createRef()
6 |
7 | render() {
8 | const { onClick, onMouseOver, selected } = this.props;
9 | const title = "Click to select and include in statistics preview.";
10 | const style = {
11 | height: this.getHeight() + "px",
12 | };
13 | return (
14 |
15 |
20 | |
21 | );
22 | }
23 |
24 | getHeight() {
25 | const { page, maxElapsedTime } = this.props;
26 | const { onLoad } = page.pageTimings;
27 |
28 | let height = 1;
29 | if (onLoad > 0 && maxElapsedTime > 0) {
30 | height = Math.round((onLoad / maxElapsedTime) * 100);
31 | }
32 |
33 | return Math.max(1, height);
34 | }
35 | }
36 |
37 | PageTimelineCol.propTypes = {
38 | maxElapsedTime: PropTypes.number,
39 | onClick: PropTypes.func,
40 | onMouseOver: PropTypes.func,
41 | page: PropTypes.object,
42 | selected: PropTypes.bool,
43 | };
44 |
45 | export default PageTimelineCol;
46 |
--------------------------------------------------------------------------------
/webapp/modules/pagetimeline/PageTimelineTable.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class PageTimelineTable extends Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 | {this.props.pageTimelineCols}
11 |
12 |
13 |
14 | );
15 | }
16 | }
17 |
18 | PageTimelineTable.propTypes = {
19 | pageTimelineCols: PropTypes.node,
20 | };
21 |
22 | export default PageTimelineTable;
23 |
--------------------------------------------------------------------------------
/webapp/modules/pagetimeline/Selection.js:
--------------------------------------------------------------------------------
1 | import * as Css from "../core/css";
2 | import * as Dom from "../core/dom";
3 |
4 | const Selection = {
5 | isSelected: function(bar) {
6 | return Css.hasClass(bar, "selected");
7 | },
8 |
9 | toggle: function(bar) {
10 | Css.toggleClass(bar, "selected");
11 | },
12 |
13 | select: function(bar) {
14 | if (!this.isSelected(bar)) {
15 | Css.setClass(bar, "selected");
16 | }
17 | },
18 |
19 | unselect: function(bar) {
20 | if (this.isSelected(bar)) {
21 | Css.removeClass(bar, "selected");
22 | }
23 | },
24 |
25 | getSelection: function(row) {
26 | const bars = Array.from(Dom.getElementsByClass(row, "pageBar"));
27 | return bars
28 | .filter((bar) => this.isSelected(bar))
29 | .map((bar) => bar.page);
30 | },
31 |
32 | unselectAll: function(row, except) {
33 | const bars = Array.from(Dom.getElementsByClass(row, "pageBar"));
34 | bars
35 | .filter((bar) => except !== bar)
36 | .forEach((bar) => this.unselect(bar));
37 | },
38 | };
39 |
40 | export default Selection;
41 |
--------------------------------------------------------------------------------
/webapp/modules/pie/CachePie.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Pie from "./Pie";
5 | import * as Str from "../core/string";
6 | import Strings from "i18n!../nls/harStats";
7 |
8 | class CachePie extends Component {
9 | title = "Comparison of downloaded data from the server and browser cache."
10 |
11 | data = [
12 | {
13 | count: 0,
14 | value: 0,
15 | label: Strings.pieLabelDownloaded,
16 | color: "rgb(182, 182, 182)",
17 | },
18 | {
19 | count: 0,
20 | value: 0,
21 | label: Strings.pieLabelPartial,
22 | color: "rgb(218, 218, 218)",
23 | },
24 | {
25 | count: 0,
26 | value: 0,
27 | label: Strings.pieLabelFromCache,
28 | color: "rgb(239, 239, 239)",
29 | },
30 | ]
31 |
32 | getLabelTooltipText = (item) => {
33 | return item.count + "x " + item.label + ": " + Str.formatSize(item.value);
34 | }
35 |
36 | calcData() {
37 | // Iterate over all requests and compute stats.
38 | const { entries = [] } = this.props;
39 | const data = this.data.map((d) => ({ ...d }));
40 | entries.forEach((entry) => {
41 | if (!entry.timings) {
42 | return;
43 | }
44 |
45 | const { response } = entry;
46 | // TODO use transferred size
47 | const resBodySize = response.bodySize > 0 ? response.bodySize : 0;
48 |
49 | // TODO use proper stats routines
50 |
51 | // Get Cache info
52 | if (entry.response.status === 206) { // Partial content
53 | data[1].value += resBodySize;
54 | data[1].count++;
55 | } else if (entry.response.status === 304) { // From cache
56 | data[2].value += resBodySize;
57 | data[2].count++;
58 | } else if (resBodySize > 0) { // Downloaded
59 | data[0].value += resBodySize;
60 | data[0].count++;
61 | }
62 | });
63 | return data;
64 | }
65 |
66 | render() {
67 | return ;
68 | }
69 | }
70 |
71 | CachePie.propTypes = {
72 | entries: PropTypes.array,
73 | };
74 |
75 | export default CachePie;
76 |
--------------------------------------------------------------------------------
/webapp/modules/pie/ContentPie.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Pie from "./Pie";
5 | import * as Mime from "../core/mime";
6 | import * as Str from "../core/string";
7 | import Strings from "i18n!../nls/harStats";
8 |
9 | const jsTypes = {
10 | "text/javascript": 1,
11 | "text/jscript": 1,
12 | "application/javascript": 1,
13 | "application/x-javascript": 1,
14 | "text/js": 1,
15 | };
16 |
17 | const htmlTypes = {
18 | "text/plain": 1,
19 | "text/html": 1,
20 | };
21 |
22 | const cssTypes = {
23 | "text/css": 1,
24 | };
25 |
26 | const imageTypes = {
27 | "image/png": 1,
28 | "image/jpeg": 1,
29 | "image/gif": 1,
30 | };
31 |
32 | const flashTypes = {
33 | "application/x-shockwave-flash": 1,
34 | };
35 |
36 | const jsonTypes = {
37 | "text/x-json": 1,
38 | "text/x-js": 1,
39 | "application/json": 1,
40 | "application/x-js": 1,
41 | };
42 |
43 | const xmlTypes = {
44 | "application/xml": 1,
45 | "application/xhtml+xml": 1,
46 | "application/vnd.mozilla.xul+xml": 1,
47 | "text/xml": 1,
48 | "text/xul": 1,
49 | "application/rdf+xml": 1,
50 | };
51 |
52 | const unknownTypes = {
53 | "text/xsl": 1,
54 | "text/sgml": 1,
55 | "text/rtf": 1,
56 | "text/x-setext": 1,
57 | "text/richtext": 1,
58 | "text/tab-separated-values": 1,
59 | "text/rdf": 1,
60 | "text/xif": 1,
61 | "text/ecmascript": 1,
62 | "text/vnd.curl": 1,
63 | "text/vbscript": 1,
64 | "view-source": 1,
65 | "view-fragment": 1,
66 | "application/x-httpd-php": 1,
67 | "application/ecmascript": 1,
68 | "application/http-index-format": 1,
69 | };
70 |
71 | class ContentPie extends Component {
72 | title = "Summary of content types."
73 |
74 | data = [
75 | {
76 | count: 0,
77 | value: 0,
78 | label: Strings.pieLabelHTMLText,
79 | color: "rgb(174, 234, 218)",
80 | },
81 | {
82 | count: 0,
83 | value: 0,
84 | label: Strings.pieLabelJavaScript,
85 | color: "rgb(245, 230, 186)",
86 | },
87 | {
88 | count: 0,
89 | value: 0,
90 | label: Strings.pieLabelCSS,
91 | color: "rgb(212, 204, 219)",
92 | },
93 | {
94 | count: 0,
95 | value: 0,
96 | label: Strings.pieLabelImage,
97 | color: "rgb(220, 171, 181)",
98 | },
99 | {
100 | count: 0,
101 | value: 0,
102 | label: Strings.pieLabelFlash,
103 | color: "rgb(166, 156, 222)",
104 | },
105 | {
106 | count: 0,
107 | value: 0,
108 | label: Strings.pieLabelOthers,
109 | color: "rgb(229, 171, 255)",
110 | },
111 | ]
112 |
113 | getLabelTooltipText = (item) => {
114 | return item.count + "x " + item.label + ": " + Str.formatSize(item.value);
115 | }
116 |
117 | calcData() {
118 | // Iterate over all requests and compute stats.
119 | const { entries = [] } = this.props;
120 | const data = this.data.map((d) => ({ ...d }));
121 | entries.forEach((entry) => {
122 | if (!entry.timings) {
123 | return;
124 | }
125 |
126 | const { response } = entry;
127 | // TODO use transferred size
128 | const resBodySize = response.bodySize > 0 ? response.bodySize : 0;
129 |
130 | // Get Content type info. Make sure we read the right content type
131 | // even if there is also a charset specified.
132 | const mimeType = Mime.extractMimeType(response.content.mimeType);
133 |
134 | // Collect response sizes according to the contentType.
135 | if (htmlTypes[mimeType]) {
136 | data[0].value += resBodySize;
137 | data[0].count++;
138 | } else if (jsTypes[mimeType]) {
139 | data[1].value += resBodySize;
140 | data[1].count++;
141 | } else if (cssTypes[mimeType]) {
142 | data[2].value += resBodySize;
143 | data[2].count++;
144 | } else if (imageTypes[mimeType]) {
145 | data[3].value += resBodySize;
146 | data[3].count++;
147 | } else if (flashTypes[mimeType]) {
148 | data[4].value += resBodySize;
149 | data[4].count++;
150 | } else {
151 | data[5].value += resBodySize;
152 | data[5].count++;
153 | }
154 | });
155 | return data;
156 | }
157 |
158 | render() {
159 | return ;
160 | }
161 | }
162 |
163 | ContentPie.propTypes = {
164 | entries: PropTypes.array,
165 | };
166 |
167 | export default ContentPie;
168 |
--------------------------------------------------------------------------------
/webapp/modules/pie/TimingPie.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Pie from "./Pie";
5 | import * as Str from "../core/string";
6 | import Strings from "i18n!../nls/harStats";
7 |
8 | class TimingPie extends Component {
9 | title = "Summary of request times."
10 |
11 | data = [
12 | {
13 | value: 0,
14 | label: Strings.pieLabelBlocked,
15 | color: "rgb(228, 214, 193)",
16 | },
17 | {
18 | value: 0,
19 | label: Strings.pieLabelDNS,
20 | color: "rgb(119, 192, 203)",
21 | },
22 | {
23 | value: 0,
24 | label: Strings.pieLabelSSL,
25 | color: "rgb(168, 196, 173)",
26 | },
27 | {
28 | value: 0,
29 | label: Strings.pieLabelConnect,
30 | color: "rgb(179, 222, 93)",
31 | },
32 | {
33 | value: 0,
34 | label: Strings.pieLabelSend,
35 | color: "rgb(224, 171, 157)",
36 | },
37 | {
38 | value: 0,
39 | label: Strings.pieLabelWait,
40 | color: "rgb(163, 150, 190)",
41 | },
42 | {
43 | value: 0,
44 | label: Strings.pieLabelReceive,
45 | color: "rgb(194, 194, 194)",
46 | },
47 | ]
48 |
49 | getLabelTooltipText = (item) => {
50 | return item.label + ": " + Str.formatTime(item.value.toFixed(2));
51 | }
52 |
53 | calcData() {
54 | // Iterate over all requests and compute stats.
55 | const { entries = [] } = this.props;
56 | const data = this.data.map((d) => ({ ...d }));
57 | entries.forEach((entry) => {
58 | if (!entry.timings) {
59 | return;
60 | }
61 |
62 | // Get timing info (SSL is new in HAR 1.2)
63 | data[0].value += entry.timings.blocked;
64 | data[1].value += entry.timings.dns;
65 | data[2].value += entry.timings.ssl > 0 ? entry.timings.ssl : 0;
66 | data[3].value += entry.timings.connect;
67 | data[4].value += entry.timings.send;
68 | data[5].value += entry.timings.wait;
69 | data[6].value += entry.timings.receive;
70 |
71 | // The ssl time is also included in the connect field, see HAR 1.2 spec
72 | // (to ensure backward compatibility with HAR 1.1).
73 | if (entry.timings.ssl > 0) {
74 | data[3].value -= entry.timings.ssl;
75 | }
76 | });
77 | return data;
78 | }
79 |
80 | render() {
81 | return ;
82 | }
83 | }
84 |
85 | TimingPie.propTypes = {
86 | entries: PropTypes.array,
87 | };
88 |
89 | export default TimingPie;
90 |
--------------------------------------------------------------------------------
/webapp/modules/pie/TrafficPie.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Pie from "./Pie";
5 | import * as Str from "../core/string";
6 | import Strings from "i18n!../nls/harStats";
7 |
8 | class TrafficPie extends Component {
9 | title = "Summary of sent and received bodies & headers."
10 |
11 | data = [
12 | {
13 | value: 0,
14 | label: Strings.pieLabelHeadersSent,
15 | color: "rgb(247, 179, 227)",
16 | },
17 | {
18 | value: 0,
19 | label: Strings.pieLabelBodiesSent,
20 | color: "rgb(226, 160, 241)",
21 | },
22 | {
23 | value: 0,
24 | label: Strings.pieLabelHeadersReceived,
25 | color: "rgb(166, 232, 166)",
26 | },
27 | {
28 | value: 0,
29 | label: Strings.pieLabelBodiesReceived,
30 | color: "rgb(168, 196, 173)",
31 | },
32 | ]
33 |
34 | getLabelTooltipText = (item) => {
35 | return item.label + ": " + Str.formatSize(item.value);
36 | }
37 |
38 | calcData() {
39 | // Iterate over all requests and compute stats.
40 | const { entries = [] } = this.props;
41 | const data = this.data.map((d) => ({ ...d }));
42 | entries.forEach((entry) => {
43 | if (!entry.timings) {
44 | return;
45 | }
46 |
47 | const { response } = entry;
48 | // TODO use transferred size
49 | const resBodySize = response.bodySize > 0 ? response.bodySize : 0;
50 |
51 | // Get traffic info
52 | data[0].value += entry.request.headersSize > 0 ? entry.request.headersSize : 0;
53 | data[1].value += entry.request.bodySize > 0 ? entry.request.bodySize : 0;
54 | data[2].value += entry.response.headersSize > 0 ? entry.response.headersSize : 0;
55 | data[3].value += resBodySize;
56 | });
57 | return data;
58 | }
59 |
60 | render() {
61 | return ;
62 | }
63 | }
64 |
65 | TrafficPie.propTypes = {
66 | entries: PropTypes.array,
67 | };
68 |
69 | export default TrafficPie;
70 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/DataURL.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Str from "../core/string";
5 |
6 | class DataURL extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.domRef = React.createRef();
10 | }
11 |
12 | handleImage() { }
13 |
14 | componentDidMount() { }
15 |
16 | render() {
17 | const { entry } = this.props;
18 | const data = entry.request.url;
19 |
20 | const content = (data.indexOf("data:image") === 0) ?
21 |
:
22 | ;
23 |
24 | return (
25 |
26 | {content}
27 |
28 | );
29 | }
30 | };
31 |
32 | DataURL.propTypes = {
33 | entry: PropTypes.object,
34 | };
35 |
36 | DataURL.canShowEntry = function(entry) {
37 | return entry.request.url.indexOf("data:") === 0;
38 | };
39 |
40 | export default DataURL;
41 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/ExternalImage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Image from "./Image";
5 |
6 | class ExternalImage extends React.Component {
7 | render() {
8 | const { entry } = this.props;
9 | return (
10 |
11 |

12 |
13 | );
14 | }
15 | };
16 |
17 | ExternalImage.propTypes = {
18 | entry: PropTypes.object,
19 | };
20 |
21 | ExternalImage.canShowEntry = function(entry) {
22 | return Image.isFileImage(entry);
23 | };
24 |
25 | export default ExternalImage;
26 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/Headers.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import ParamRow from "./ParamRow";
5 | import TitleRow from "./TitleRow";
6 |
7 | import * as Str from "../core/string";
8 |
9 | class Headers extends React.Component {
10 | render() {
11 | const { entry } = this.props;
12 |
13 | const responseHeaders = entry.response.headers || [];
14 | const requestHeaders = entry.request.headers || [];
15 |
16 | return (
17 |
18 |
{Str.cropString(entry.request.url, 128)}
19 |
20 |
21 |
22 |
23 |
24 | {
25 | responseHeaders.map((header, i) =>
26 |
27 | )
28 | }
29 |
30 |
31 |
32 | {
33 | requestHeaders.map((header, i) =>
34 |
35 | )
36 | }
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | Headers.canShowEntry = function(entry) {
45 | return true;
46 | };
47 |
48 | Headers.propTypes = {
49 | entry: PropTypes.object,
50 | };
51 |
52 | export default Headers;
53 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/Highlighted.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Mime from "../core/mime";
5 | import { canDecode } from "./decoder";
6 |
7 | class Highlighted extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.domRef = React.createRef();
11 | }
12 |
13 | maybeDoHighlighting() {
14 | const { entry } = this.props;
15 | const text = entry.response.content.text;
16 | const brush = Highlighted.shouldHighlightAs(entry.response.content.mimeType);
17 |
18 | const pre = this.domRef.current;
19 | const code = pre.firstChild;
20 |
21 | // clear flag
22 | code.removeAttribute("highlighted");
23 |
24 | code.innerText = "";
25 | code.appendChild(document.createTextNode(text));
26 |
27 | if (brush) {
28 | code.className = brush;
29 | // Run highlightElement on the , not the
30 | hljs.highlightBlock(code);
31 |
32 | // test that highlighting has worked, and set a flag that helps with testing.
33 | const highlightedElement = code;
34 | if (code.classList.contains("hljs")) {
35 | highlightedElement.setAttribute("highlighted", true);
36 | }
37 | }
38 | }
39 |
40 | componentDidMount() {
41 | this.maybeDoHighlighting();
42 | }
43 |
44 | componentDidUpdate() {
45 | this.maybeDoHighlighting();
46 | }
47 |
48 | render() {
49 | // highlightjs needs a block inside a block.
50 | return (
51 |
56 | );
57 | }
58 | };
59 |
60 | Highlighted.propTypes = {
61 | entry: PropTypes.object,
62 | };
63 |
64 | Highlighted.canShowEntry = function(entry) {
65 | const { content } = entry.response;
66 |
67 | if (!content || !content.text) {
68 | return false;
69 | }
70 |
71 | if (!canDecode(content.encoding)) {
72 | return false;
73 | }
74 |
75 | // Remove any mime type parameters (if any)
76 | const mimeType = Mime.extractMimeType(content.mimeType || "");
77 |
78 | const shouldHighlightAs = Highlighted.shouldHighlightAs(mimeType);
79 | return (shouldHighlightAs !== null);
80 | };
81 |
82 | // TODO - Put this in the right place when we implement XML tab.
83 | const XmlTab = {};
84 |
85 | XmlTab.isXmlMimeType = function(mimeType) {
86 | mimeType = Mime.extractMimeType(mimeType);
87 | return [
88 | "text/xml",
89 | "application/xml",
90 | "image/svg+xml",
91 | "application/atom+xml",
92 | "application/xslt+xml",
93 | "application/mathml+xml",
94 | "application/rss+xml",
95 | ].indexOf(mimeType) > -1;
96 | };
97 |
98 | Highlighted.shouldHighlightAs = function(mimeType) {
99 | const mimeTypesToHighlight = {
100 | javascript: [
101 | "application/javascript",
102 | "text/javascript",
103 | "application/x-javascript",
104 | "text/ecmascript",
105 | "application/ecmascript",
106 | "application/json",
107 | ],
108 | css: ["text/css"],
109 | html: ["text/html", "application/xhtml+xml"],
110 | };
111 | for (const brush in mimeTypesToHighlight) {
112 | if (mimeTypesToHighlight[brush].indexOf(mimeType) > -1) {
113 | return brush;
114 | }
115 | }
116 | return XmlTab.isXmlMimeType(mimeType) ? "xml" : null;
117 | };
118 |
119 | export default Highlighted;
120 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/Image.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Mime from "../core/mime";
5 | import * as Str from "../core/string";
6 |
7 | class Image extends React.Component {
8 | createImageSrc(content) {
9 | const mimeType = Mime.extractMimeType(content.mimeType);
10 | // https://css-tricks.com/data-uris/
11 | return "data:" + mimeType + ";base64," + content.text;
12 | }
13 |
14 | render() {
15 | const { entry } = this.props;
16 | const { content } = entry.response;
17 | const src = this.createImageSrc(content);
18 | return (
19 |
20 |

21 |
22 | );
23 | }
24 | };
25 |
26 | Image.propTypes = {
27 | entry: PropTypes.object,
28 | };
29 |
30 | Image.isFileImage = function(entry) {
31 | const { content } = entry.response;
32 | if (!content) {
33 | return false;
34 | }
35 |
36 | const mimeType = Mime.extractMimeType(content.mimeType || "");
37 | return Str.startsWith(mimeType, "image/");
38 | }
39 |
40 | Image.canShowEntry = function(entry) {
41 | const { content } = entry.response;
42 | return Image.isFileImage(entry) && (content.text && content.encoding === "base64");
43 | };
44 |
45 | export default Image;
46 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/JSONEntryTree.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import ObjectTree, { hasChildren, populateChildren } from "../tree/ObjectTree";
5 | import { canDecode, decode } from "./decoder";
6 | import * as Mime from "../core/mime";
7 |
8 | function getRoot(entry) {
9 | const { content } = entry.response;
10 | const jsonStr = decode(content.text, content.encoding);
11 | return JSON.parse(jsonStr);
12 | }
13 |
14 | class JSONEntryTree extends React.Component {
15 | render() {
16 | const { entry } = this.props;
17 | const root = getRoot(entry);
18 | return ;
19 | }
20 | };
21 |
22 | JSONEntryTree.propTypes = {
23 | entry: PropTypes.object,
24 | };
25 |
26 | JSONEntryTree.canShowEntry = function(entry) {
27 | const { content } = entry.response;
28 |
29 | if (!content || !content.text) {
30 | return false;
31 | }
32 |
33 | if (!canDecode(content.encoding)) {
34 | return false;
35 | }
36 |
37 | const mimeType = Mime.extractMimeType(content.mimeType || "");
38 | return ["application/json"].indexOf(mimeType) > -1;
39 | };
40 |
41 | export default JSONEntryTree;
42 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/ParamRow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Str from "../core/string";
5 |
6 | function getParamValue(value) {
7 | // This value is inserted into PRE element and so, make sure the HTML isn't escaped (1210).
8 | // This is why the second parameter is true.
9 | // The PRE element preserves whitespaces so they are displayed the same, as they come from
10 | // the server (1194).
11 | return Str.wrapText(value, true);
12 | }
13 |
14 | const ParamRow = (props) => {
15 | return (
16 |
17 | {props.name} |
18 | {getParamValue(props.value)} |
19 |
20 | );
21 | };
22 |
23 | ParamRow.propTypes = {
24 | name: PropTypes.string,
25 | value: PropTypes.string,
26 | };
27 |
28 | export default ParamRow;
29 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/PlainResponse.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Str from "../core/string";
5 |
6 | class PlainResponse extends React.Component {
7 | render() {
8 | const { entry } = this.props;
9 | const { text } = entry.response.content;
10 |
11 | return (
12 |
13 |
14 | {Str.wrapText(text, true)}
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | PlainResponse.canShowEntry = function(entry) {
22 | return (entry.response.content.text && entry.response.content.text.length > 0);
23 | };
24 |
25 | PlainResponse.propTypes = {
26 | entry: PropTypes.object,
27 | };
28 |
29 | export default PlainResponse;
30 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/SendData.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import * as Arr from "../core/array";
5 | import ParamRow from "./ParamRow";
6 |
7 | class SendData extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | render() {
13 | const { entry } = this.props;
14 | const { postData } = entry.request;
15 | if (!postData) {
16 | return null;
17 | }
18 |
19 | let content = null;
20 | if (postData.mimeType === "application/x-www-form-urlencoded") {
21 | content = (
22 |
23 |
24 | {postData.params.map((param, i) => )}
25 |
26 |
27 | );
28 | } else {
29 | // TODO
30 | content = postData.text;
31 | }
32 | return (
33 |
34 | {content}
35 |
36 | );
37 | }
38 | };
39 |
40 | SendData.propTypes = {
41 | entry: PropTypes.object,
42 | };
43 |
44 | SendData.canShowEntry = function(entry) {
45 | const { postData } = entry.request;
46 | if (!postData) {
47 | // No post data at all.
48 | return false;
49 | }
50 |
51 | const paramsMissing = !Arr.isArray(postData.params) || (postData.params.length === 0);
52 | const textMissing = !postData.text;
53 |
54 | const postContentMissing = paramsMissing && textMissing;
55 |
56 | if (postContentMissing) {
57 | // (at least) Firefox 47 exports GET requests in HARs that have:
58 | // postData.mimeType = ""
59 | // postData.params = []
60 | // postData.text = ""
61 | // So double-check for this and only allow such 'empty' postData for PUT and POST
62 | return ["PUT", "POST"].indexOf(entry.request.method) > -1;
63 | }
64 |
65 | return true;
66 | };
67 |
68 | export default SendData;
69 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/TitleRow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const TitleRow = (props) => {
5 | return (
6 |
7 | {props.title} |
8 |
9 | );
10 | };
11 |
12 | TitleRow.propTypes = {
13 | title: PropTypes.string,
14 | titleType: PropTypes.string,
15 | };
16 |
17 | export default TitleRow;
18 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/URLParameters.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import ParamRow from "./ParamRow";
5 |
6 | class UrlParameters extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | render() {
12 | const { entry } = this.props;
13 | return (
14 |
15 |
16 | {
17 | entry.request.queryString.map((param, i) =>
18 |
19 | )
20 | }
21 |
22 |
23 | );
24 | }
25 | };
26 |
27 | UrlParameters.propTypes = {
28 | entry: PropTypes.object,
29 | };
30 |
31 | UrlParameters.canShowEntry = function(entry) {
32 | return (entry.request.queryString && entry.request.queryString.length);
33 | };
34 |
35 | export default UrlParameters;
36 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/XMLEntryTree.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import XMLTree, { hasChildren, populateChildren } from "../tree/XMLTree";
5 | import { canDecode, decode } from "./decoder";
6 | import * as Mime from "../core/mime";
7 |
8 | function getRoot(entry) {
9 | const { content } = entry.response;
10 | const xmlStr = decode(content.text, content.encoding);
11 | return $.parseXML(xmlStr);
12 | }
13 |
14 | class XMLEntryTree extends React.Component {
15 | render() {
16 | const { entry } = this.props;
17 | const root = getRoot(entry);
18 | return ;
19 | }
20 | };
21 |
22 | XMLEntryTree.propTypes = {
23 | entry: PropTypes.object,
24 | };
25 |
26 | XMLEntryTree.isXmlMimeType = function(mimeType) {
27 | mimeType = Mime.extractMimeType(mimeType);
28 | return [
29 | "text/xml",
30 | "application/xml",
31 | "image/svg+xml",
32 | "application/atom+xml",
33 | "application/xslt+xml",
34 | "application/mathml+xml",
35 | "application/rss+xml",
36 | ].indexOf(mimeType) > -1;
37 | };
38 |
39 | XMLEntryTree.canShowEntry = function(entry) {
40 | const { content } = entry.response;
41 |
42 | if (!content || !content.text) {
43 | return false;
44 | }
45 |
46 | if (!canDecode(content.encoding)) {
47 | return false;
48 | }
49 |
50 | const mimeType = Mime.extractMimeType(content.mimeType || "");
51 | return XMLEntryTree.isXmlMimeType(mimeType);
52 | };
53 |
54 | export default XMLEntryTree;
55 |
--------------------------------------------------------------------------------
/webapp/modules/requestbodies/decoder.js:
--------------------------------------------------------------------------------
1 | export function canDecode(encoding) {
2 | if (!encoding) {
3 | return true;
4 | }
5 |
6 | if (encoding !== "base64") {
7 | // only base64 supported
8 | return false;
9 | }
10 |
11 | if (typeof atob === "undefined") {
12 | // it's base64 but we can't decode it. E.g. IE9.
13 | return false;
14 | }
15 |
16 | return true;
17 | }
18 |
19 | export function decode(text, encoding) {
20 | return (encoding === "base64") ? atob(text) : text;
21 | }
22 |
--------------------------------------------------------------------------------
/webapp/modules/setState.js:
--------------------------------------------------------------------------------
1 | export default function setState(context, ...sources) {
2 | context.setState((prevState) => {
3 | const newState = Object.assign({}, context.state, ...sources);
4 | return newState;
5 | });
6 | }
7 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/AboutTab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import aboutHtml from "raw-loader!./aboutTab.html";
5 |
6 | class AboutTab extends Component {
7 | replace(s, pattern, replaceWith) {
8 | if (!replaceWith) {
9 | return s;
10 | }
11 | return s.replace(new RegExp(pattern, "g"), replaceWith);
12 | }
13 |
14 | render() {
15 | let { version, harSpecURL, harViewerDemoUrl } = this.props;
16 | if (!harSpecURL) {
17 | harSpecURL = "http://www.softwareishard.com/blog/har-12-spec/";
18 | }
19 |
20 | let html = aboutHtml;
21 | html = this.replace(html, "@VERSION@", version);
22 | html = this.replace(html, "@HAR_SPEC_URL@", harSpecURL);
23 | html = this.replace(html, "@HARVIEWER_DEMO_URL@", harViewerDemoUrl);
24 |
25 | return (
26 |
27 | );
28 | }
29 | }
30 |
31 | AboutTab.propTypes = {
32 | version: PropTypes.string,
33 | harSpecURL: PropTypes.string,
34 | harViewerDemoUrl: PropTypes.string,
35 | };
36 |
37 | export default AboutTab;
38 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/HomeTab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import AppContext from "../AppContext";
5 | import homeHtml from "raw-loader!./homeTab.html";
6 |
7 | // TODO Move me
8 | if (!Element.prototype.matches) {
9 | Element.prototype.matches = Element.prototype.msMatchesSelector;
10 | }
11 |
12 | function on(e, selector, listener) {
13 | const { target } = e;
14 | if (target.matches(selector)) {
15 | listener.call(null, e);
16 | }
17 | }
18 |
19 | export class HomeTab extends Component {
20 | constructor(props) {
21 | super(props);
22 | this.ref = React.createRef();
23 | }
24 |
25 | componentDidMount() {
26 | this.validate = this.ref.current.querySelector("#validate");
27 | this.validate.checked = this.context.validate;
28 | }
29 |
30 | componentWillUnmount() {
31 | this.validate = null;
32 | }
33 |
34 | componentDidUpdate(prevProps, prevState, snapshot) {
35 | this.validate.checked = this.context.validate;
36 | }
37 |
38 | onClick = (e) => {
39 | on(e, ".example", this.onExampleClick);
40 | on(e, ".linkAbout", this.onAboutClick);
41 | on(e, "#appendPreview", this.onPreviewClick);
42 | on(e, "#validate", this.onValidateClick);
43 | }
44 |
45 | onExampleClick = (e) => {
46 | e.preventDefault();
47 | const { target } = e;
48 | const har = target.getAttribute("har");
49 | const href = window.location.href;
50 | const page = href.split("?")[0];
51 | window.location = page + "?har=" + har;
52 | }
53 |
54 | onAboutClick = (e) => {
55 | e.preventDefault();
56 | this.props.requestTabChange("About");
57 | }
58 |
59 | onPreviewClick = (e) => {
60 | const json = document.getElementById("sourceEditor").value;
61 | const { appendPreview } = this.props;
62 | appendPreview(json);
63 | }
64 |
65 | onValidateClick = (e) => {
66 | e.stopPropagation();
67 | const checked = e.target.checked;
68 | const { setValidate } = this.context;
69 | setValidate(checked);
70 | }
71 |
72 | render() {
73 | return (
74 |
80 | );
81 | }
82 | };
83 |
84 | HomeTab.propTypes = {
85 | appendPreview: PropTypes.func,
86 | requestTabChange: PropTypes.func,
87 | };
88 |
89 | HomeTab.contextType = AppContext;
90 |
91 | export default HomeTab;
92 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/PreviewTab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import { saveAs } from "file-saver";
4 |
5 | import AppContext from "../AppContext";
6 | import Stats from "../Stats";
7 | import PageTimeline from "../pagetimeline/PageTimeline";
8 | import PreviewTabToolbar from "./PreviewTabToolbar";
9 | import PreviewList from "./preview/PreviewList";
10 |
11 | class PreviewTab extends Component {
12 | constructor(props) {
13 | super(props);
14 |
15 | this.toolbarRef = React.createRef();
16 |
17 | this.state = {
18 | timelineVisible: false,
19 | selectedPages: null,
20 | statsVisible: false,
21 | };
22 | }
23 |
24 | showStats() {
25 | this.setState(({ statsVisible }) => ({
26 | statsVisible: !statsVisible,
27 | }));
28 | }
29 |
30 | showTimeline() {
31 | this.setState(({ timelineVisible }) => ({
32 | timelineVisible: !timelineVisible,
33 | }));
34 | }
35 |
36 | addPageTiming(timing) {
37 | this.context.addPageTiming(timing);
38 | }
39 |
40 | onDownloadClick = (e) => {
41 | e.preventDefault();
42 | // TODO find out which model to copy.
43 | const { harModels } = this.props;
44 | const model = harModels[0];
45 | const json = model ? model.toJSON() : "";
46 | const blob = new Blob([json], { type: "text/plain;charset=" + document.characterSet });
47 | saveAs(blob, "netData.har");
48 | }
49 |
50 | onStatsClick = (e) => {
51 | e.preventDefault();
52 | this.showStats();
53 | }
54 |
55 | onTimelineClick = (e) => {
56 | e.preventDefault();
57 | this.showTimeline();
58 | }
59 |
60 | onClearClick = (e) => {
61 | e.preventDefault();
62 | const href = window.location.href;
63 | const index = href.indexOf("?");
64 | window.location = href.substr(0, index);
65 | }
66 |
67 | findPagelessEntries(har) {
68 | const { pages, entries } = har.log;
69 |
70 | let pageIds = {};
71 | if (pages && pages.length > 0) {
72 | pageIds = pages.reduce((ids, page) => {
73 | if (page.id) {
74 | ids[page.id] = 1;
75 | }
76 | return ids;
77 | }, {});
78 | } else {
79 | // No pages, so all entries are pageless
80 | return entries;
81 | }
82 |
83 | if (entries && entries.length > 0) {
84 | return entries.filter((e) => {
85 | if (!e.pageref) {
86 | // pageless
87 | return true;
88 | }
89 | // pageless if there isn't a matching page.id
90 | return !pageIds || !pageIds[e.pageref];
91 | });
92 | }
93 |
94 | return null;
95 | }
96 |
97 | getToolbar() {
98 | return this.toolbarRef.current;
99 | }
100 |
101 | onPageTimelinePageSelection = (selectedPages) => {
102 | this.setState({
103 | selectedPages,
104 | });
105 | }
106 |
107 | render() {
108 | const { harModels, errors } = this.props;
109 |
110 | if (!harModels) {
111 | return ;
112 | }
113 |
114 | // TODO - how to choose which model?
115 | const clickHandlers = {
116 | onStatsClick: this.onStatsClick,
117 | onTimelineClick: this.onTimelineClick,
118 | onClearClick: this.onClearClick,
119 | onDownloadClick: this.onDownloadClick,
120 | };
121 |
122 | // TODO - Stats currently only takes one model (harviewer does it this way)
123 | // harviewer-react has an array of models though
124 |
125 | const { selectedPages } = this.state;
126 | const statsEntries = selectedPages
127 | ? selectedPages.reduce((result, { harModel, page }) => result.concat(harModel.getPageEntries(page)), [])
128 | : harModels.reduce((result, harModel) => result.concat(harModel.getAllEntries()), []);
129 |
130 | return (
131 |
132 |
135 |
136 | {this.state.timelineVisible ?
: ""}
137 |
138 |
139 | {this.state.statsVisible ? : ""}
140 |
141 |
142 |
143 | );
144 | }
145 | };
146 |
147 | PreviewTab.propTypes = {
148 | harModels: PropTypes.array,
149 | errors: PropTypes.array,
150 | };
151 |
152 | PreviewTab.contextType = AppContext;
153 |
154 | export default PreviewTab;
155 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/PreviewTabToolbar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Toolbar, { Button } from "../toolbar/Toolbar";
5 |
6 | class PreviewTabToolbar extends Component {
7 | state = {
8 | showButtons: ["showTimeline", "showStats", "clear", "download"],
9 | }
10 |
11 | removeButton(buttonName) {
12 | this.setState(({ showButtons }) => ({
13 | showButtons: showButtons.filter((it) => it !== buttonName),
14 | }));
15 | }
16 |
17 | render() {
18 | const {
19 | onStatsClick,
20 | onTimelineClick,
21 | onClearClick,
22 | onDownloadClick,
23 | } = this.props;
24 | const { showButtons } = this.state;
25 |
26 | if (showButtons.length === 0) {
27 | return null;
28 | }
29 |
30 | const downloadImgStyle = {
31 | display: "inline-block",
32 | width: "16px",
33 | height: "16px",
34 | background: "url(./css/images/download-sprites.png) no-repeat",
35 | };
36 |
37 | const buttons = {
38 | showTimeline: ,
39 | showStats: ,
40 | clear: ,
41 | download: ,
44 | };
45 |
46 | return (
47 |
48 | {showButtons.map((buttonName) => buttons[buttonName])}
49 |
50 | );
51 | }
52 | };
53 |
54 | PreviewTabToolbar.propTypes = {
55 | onStatsClick: PropTypes.func,
56 | onTimelineClick: PropTypes.func,
57 | onClearClick: PropTypes.func,
58 | onDownloadClick: PropTypes.func,
59 | };
60 |
61 | export default PreviewTabToolbar;
62 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/SchemaTab.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class SchemaTab extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {};
8 | this.domRef = React.createRef();
9 | }
10 |
11 | maybeDoHighlighting() {
12 | const { text } = this.state;
13 | if (!text) {
14 | return;
15 | }
16 |
17 | const pre = this.domRef.current;
18 | const code = pre.firstChild;
19 |
20 | // clear flag
21 | code.removeAttribute("highlighted");
22 |
23 | code.innerText = "";
24 | code.appendChild(document.createTextNode(text));
25 |
26 | code.className = "javascript";
27 | // Run highlightElement on the , not the
28 | hljs.highlightBlock(code);
29 |
30 | // test that highlighting has worked, and set a flag that helps with testing.
31 | const highlightedElement = code;
32 | if (code.classList.contains("hljs")) {
33 | highlightedElement.setAttribute("highlighted", true);
34 | }
35 | }
36 |
37 | componentDidMount() {
38 | const { text } = this.state;
39 | if (text) {
40 | return;
41 | }
42 | fetch("modules/preview/harSchema.js")
43 | .then((response) => response.text())
44 | .then((text) => {
45 | const state = { text };
46 | this.setState(state, () => this.maybeDoHighlighting());
47 | });
48 | }
49 |
50 | render() {
51 | const brush = "javascript";
52 | return (
53 |
54 |
55 |
56 | );
57 | }
58 | }
59 |
60 | SchemaTab.propTypes = {
61 | version: PropTypes.string,
62 | harSpecURL: PropTypes.string,
63 | harViewerExampleApp: PropTypes.string,
64 | };
65 |
66 | export default SchemaTab;
67 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/dom/DOMBox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 | import classNames from "classnames";
4 |
5 | import ObjectTree from "../../tree/ObjectTree";
6 |
7 | function JSONQueryResults({ results }) {
8 | if (!results || results.length === 0) {
9 | return (
10 | JSON Query Results
11 | );
12 | }
13 | return (
14 | <>
15 |
16 |
17 | Table View
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | class DOMBox extends Component {
25 | objectTreeRef = React.createRef();
26 |
27 | getTree() {
28 | return this.objectTreeRef.current.getTree();
29 | }
30 |
31 | render() {
32 | const {
33 | har,
34 | title,
35 | jsonQueryMode = false,
36 | jsonQueryResults,
37 | } = this.props;
38 |
39 | if (!har) {
40 | return null;
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 |
48 | {title}
49 |
50 | |
51 | {
52 | jsonQueryMode && (
53 | <>
54 | |
55 |
56 |
57 | |
58 | >
59 | )
60 | }
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | DOMBox.propTypes = {
69 | har: PropTypes.object,
70 | title: PropTypes.string,
71 | };
72 |
73 | export default DOMBox;
74 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/dom/DOMTab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import "../../json-query/JSONQuery";
5 |
6 | import Dom from "../../core/dom";
7 | import Cookies from "../../core/cookies";
8 | import Trace from "../../core/trace";
9 | import Toolbar from "../../toolbar/Toolbar";
10 | import ObjectSearch from "../../tabs/ObjectSearch";
11 | import intersperse from "../../intersperse";
12 | import DOMBox from "./DOMBox";
13 | import SearchBox from "./SearchBox";
14 |
15 | const caseSensitiveCookieName = "searchCaseSensitive";
16 |
17 | class DOMTab extends Component {
18 | constructor(...args) {
19 | super(...args);
20 |
21 | this.domBoxRefs = this.props.harModels.map(() => React.createRef());
22 | this.searchRef = React.createRef();
23 |
24 | this.state = {
25 | jsonQueryResults: null,
26 | };
27 | }
28 |
29 | async selectText(search) {
30 | const key = search.stack
31 | .slice(1)
32 | .reduce((key, stackItem, i, stack) => {
33 | let propIndex = stackItem.propIndex;
34 | if (i < stack.length - 1) {
35 | // all indexes except the last are off-by-one when it comes to generating the key.
36 | propIndex -= 1;
37 | }
38 | return key ? key + "." + propIndex : String(propIndex);
39 | }, "");
40 |
41 |
42 | // The root of search data is the list of inputs.
43 | const { propIndex } = search.stack[0];
44 |
45 | const domBoxRef = this.domBoxRefs[propIndex - 1];
46 |
47 | // wait for the nodes to be shown before trying to select text
48 | await domBoxRef.current.getTree().showNode(key);
49 |
50 | const objectBox = domBoxRef.current.getTree().findObjectBox(key);
51 | if (objectBox) {
52 | const textNode = objectBox.firstChild;
53 | search.selectText(textNode);
54 | Dom.scrollIntoCenterView(objectBox);
55 | }
56 | }
57 |
58 | search = (text) => {
59 | if (this.isJSONQueryMode()) {
60 | return this.jsonQuery(text);
61 | }
62 |
63 | if (text.length < 3) {
64 | return true;
65 | }
66 |
67 | if (this.currentSearch && this.currentSearch.text !== text) {
68 | this.currentSearch = null;
69 | }
70 |
71 | if (!this.currentSearch) {
72 | const { harModels } = this.props;
73 | const inputs = harModels.map(({ input }) => input);
74 | this.currentSearch = new ObjectSearch(text, inputs, false, caseSensitiveCookieName);
75 | }
76 |
77 | if (this.currentSearch.findNext(text)) {
78 | this.selectText(this.currentSearch);
79 | return true;
80 | }
81 |
82 | this.currentSearch = null;
83 |
84 | return false;
85 | }
86 |
87 | jsonQuery(expr) {
88 | const { harModels } = this.props;
89 |
90 | const jsonQueryResults = harModels.map(({ input, i }) => {
91 | try {
92 | return JSONQuery(expr, input);
93 | } catch (err) {
94 | Trace.exception(err);
95 | }
96 | });
97 |
98 | this.setState({
99 | jsonQueryResults,
100 | });
101 |
102 | return true;
103 | }
104 |
105 | getTitle(har) {
106 | // Iterate all pages and get titles.
107 | // Some IE11 HARs (11.48.17134.0/11.0.65) don't have pages
108 | return (har.log.pages || [])
109 | .map(({ title }) => title)
110 | .join(", ");
111 | }
112 |
113 | isJSONQueryMode() {
114 | return (Cookies.getCookie("searchJsonQuery") === "true");
115 | }
116 |
117 | renderToolbar() {
118 | const placeholder = this.isJSONQueryMode() ? "JSON Query" : "Search";
119 | return (
120 |
121 |
122 |
123 |
124 |
125 | );
126 | }
127 |
128 | render() {
129 | const { harModels } = this.props;
130 | const { jsonQueryResults } = this.state;
131 | return (
132 | <>
133 | {this.renderToolbar()}
134 |
135 | {
136 | intersperse(
137 | harModels.map((model, i) => {
138 | const title = this.getTitle(model.input);
139 | return
;
147 | }),
148 | (_, i) =>
149 | )
150 | }
151 |
152 | >
153 | );
154 | }
155 | }
156 |
157 | DOMTab.propTypes = {
158 | appendPreview: PropTypes.func,
159 | requestTabChange: PropTypes.func,
160 | harModels: PropTypes.array,
161 | };
162 |
163 | export default DOMTab;
164 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/dom/SearchBox.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class SearchBox extends Component {
5 | state = {
6 | searchValue: "",
7 | status: null,
8 | };
9 |
10 | onChange = (e) => {
11 | const text = e.target.value;
12 | this.setState({
13 | searchValue: text,
14 | });
15 | }
16 |
17 | onKeyPress = (e) => {
18 | if (e.key === "Enter") {
19 | e.preventDefault();
20 | this.search(e.target.value);
21 | }
22 | }
23 |
24 | search(text) {
25 | const result = this.props.search(text);
26 | this.setState({
27 | status: result ? null : "notfound",
28 | });
29 | }
30 |
31 | render() {
32 | const { status } = this.state;
33 | const { placeholder = "Search" } = this.props;
34 | return (
35 |
36 |
37 |
38 |
47 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
54 | SearchBox.propTypes = {
55 | search: PropTypes.func,
56 | placeholder: PropTypes.string,
57 | };
58 |
59 | export default SearchBox;
60 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/homeTab.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | - Paste HAR
4 | log into the text box below and
5 | press the Preview button.
6 | - Or drop *.har file(s) anywhere on the page (if your browser supports that).
7 |
8 |
14 |
15 |
16 |
17 | |
18 |
19 |
20 |
21 |
HAR Log Examples
22 |
23 | -
24 | Inline scripts block - Inline scripts block the page load.
25 | -
26 | Blocking time - Impact of a limit of max number of parallel connections.
27 | -
28 | Browser cache - Impact of the browser cache on page load (the same page loaded three times).
29 | -
30 | Single page - Single page load (empty cache).
31 | - Embedding HAR Viewer - into other pages.
32 |
33 |
HAR Logs Online
34 | For all the ways you can load HAR files into HAR Viewer, see the
About tab.
35 |
36 |
37 |
This viewer supports HAR 1.2 (see the About tab).
38 |
39 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/preview/PreviewList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import ValidationError from "./ValidationError";
5 | import NetTable from "../../nettable/NetTable";
6 | import PageTable from "../../pagetable/PageTable";
7 |
8 | import AppContext from "../../AppContext";
9 |
10 | class PreviewList extends React.Component {
11 | findPagelessEntries(har) {
12 | const { pages, entries } = har.log;
13 |
14 | let pageIds = {};
15 | if (pages && pages.length > 0) {
16 | pageIds = pages.reduce((ids, page) => {
17 | if (page.id) {
18 | ids[page.id] = 1;
19 | }
20 | return ids;
21 | }, {});
22 | } else {
23 | // No pages, so all entries are pageless
24 | return entries;
25 | }
26 |
27 | if (entries && entries.length > 0) {
28 | return entries.filter((e) => {
29 | if (!e.pageref) {
30 | // pageless
31 | return true;
32 | }
33 | // pageless if there isn't a matching page.id
34 | return !pageIds || !pageIds[e.pageref];
35 | });
36 | }
37 |
38 | return null;
39 | }
40 |
41 | render() {
42 | const { harModels, errors } = this.props;
43 | const { expandAll } = this.context;
44 |
45 | return (
46 |
47 | {
48 | harModels.map((model, i) => {
49 | const pageTable =
;
50 |
51 | // If there are pageless entries in the HAR, show them in a standalone NetTable
52 | const pagelessEntries = this.findPagelessEntries(model.input);
53 | if (pagelessEntries && pagelessEntries.length > 0) {
54 | const netTable =
;
55 | return [netTable, pageTable];
56 | }
57 |
58 | return pageTable;
59 | })
60 | }
61 | {
62 | (errors || []).map((error, i) =>
)
63 | }
64 |
65 | );
66 | }
67 | }
68 |
69 | PreviewList.propTypes = {
70 | harModels: PropTypes.array,
71 | errors: PropTypes.array,
72 | };
73 |
74 | PreviewList.contextType = AppContext;
75 |
76 | export default PreviewList;
77 |
--------------------------------------------------------------------------------
/webapp/modules/tabs/preview/ValidationError.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class ValidationError extends React.Component {
5 | render() {
6 | const { error } = this.props;
7 | if (!error) {
8 | return null;
9 | }
10 | return (
11 |
12 |
13 |
14 | {error.property} |
15 | |
16 | |
17 | {error.message} |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | ValidationError.propTypes = {
26 | error: PropTypes.object,
27 | };
28 |
29 | export default ValidationError;
30 |
--------------------------------------------------------------------------------
/webapp/modules/tabview/Tab.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class Tab extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.linkRef = React.createRef();
8 | }
9 |
10 | updateSelected() {
11 | const { id, selected } = this.props;
12 | this.linkRef.current.setAttribute("selected", selected);
13 | this.linkRef.current.setAttribute("view", id);
14 | }
15 |
16 | componentDidMount() {
17 | this.updateSelected();
18 | }
19 |
20 | componentDidUpdate() {
21 | this.updateSelected();
22 | }
23 |
24 | render() {
25 | const { title, id, label, content, onSelect, selected } = this.props;
26 | return (
27 |
28 | {content || label || id}
29 |
30 | );
31 | }
32 | };
33 |
34 | Tab.propTypes = {
35 | content: PropTypes.node,
36 | id: PropTypes.string,
37 | label: PropTypes.string,
38 | onSelect: PropTypes.func,
39 | selected: PropTypes.bool,
40 | title: PropTypes.string,
41 | };
42 |
43 | export default Tab;
44 |
--------------------------------------------------------------------------------
/webapp/modules/tabview/TabBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Tab from "./Tab";
5 |
6 | class TabBar extends React.Component {
7 | onSelectTab(tabIdx, tab) {
8 | const { tabs, onSelectTab } = this.props;
9 | if (onSelectTab) {
10 | onSelectTab(tabIdx, tabs[tabIdx], tabs);
11 | }
12 | }
13 |
14 | render() {
15 | const { id, tabs, selectedTabIdx } = this.props;
16 | const tabElements = tabs.map((tab, i) =>
17 | this.onSelectTab(i, tab)} />
18 | );
19 | return (
20 |
21 | {tabElements}
22 |
23 | );
24 | }
25 | }
26 |
27 | TabBar.propTypes = {
28 | id: PropTypes.string,
29 | onSelectTab: PropTypes.func,
30 | selectedTabIdx: PropTypes.number,
31 | tabs: PropTypes.array,
32 | };
33 |
34 | export default TabBar;
35 |
--------------------------------------------------------------------------------
/webapp/modules/tabview/TabBodies.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import TabBody from "./TabBody";
5 |
6 | class TabBodies extends React.Component {
7 | constructor(props) {
8 | super(props);
9 |
10 | const { tabs } = this.props;
11 | this.tabBodyRefs = tabs.map(() => React.createRef());
12 | }
13 |
14 | getTab(name) {
15 | const { tabs } = this.props;
16 | const idx = tabs.findIndex((tab) => name === tab.id);
17 | const tab = this.tabBodyRefs[idx].current;
18 | return (idx < 0) ? null : tab;
19 | }
20 |
21 | render() {
22 | const { id, tabs, selectedTabIdx } = this.props;
23 |
24 | const tabBodies = tabs.map((tab, i) => {
25 | const selected = (selectedTabIdx === i);
26 | // Only include the body if it's selected
27 | const clone = React.cloneElement(tab.body, {
28 | ref: this.tabBodyRefs[i],
29 | });
30 | return {selected ? clone : ""};
31 | });
32 | return (
33 |
34 | {tabBodies}
35 |
36 | );
37 | }
38 | }
39 |
40 | TabBodies.propTypes = {
41 | id: PropTypes.string,
42 | selectedTabIdx: PropTypes.number,
43 | tabs: PropTypes.array,
44 | };
45 |
46 | export default TabBodies;
47 |
--------------------------------------------------------------------------------
/webapp/modules/tabview/TabBody.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | class TabBody extends React.Component {
5 | render() {
6 | const { id, selected } = this.props;
7 | const selectedClass = selected ? "selected" : "";
8 | return (
9 |
10 | {this.props.children}
11 |
12 | );
13 | }
14 | }
15 |
16 | TabBody.propTypes = {
17 | children: PropTypes.node,
18 | id: PropTypes.string,
19 | selected: PropTypes.bool,
20 | };
21 |
22 | export default TabBody;
23 |
--------------------------------------------------------------------------------
/webapp/modules/tabview/TabView.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import TabBar from "./TabBar";
5 | import TabBodies from "./TabBodies";
6 |
7 | class TabView extends React.Component {
8 | constructor(...args) {
9 | super(...args);
10 | this.tabBodiesRef = React.createRef();
11 | this.tableRef = React.createRef();
12 | }
13 |
14 | onSelectTab = (tabIdx, tab, tabs) => {
15 | const { onSelectedTabChange } = this.props;
16 | onSelectedTabChange(tabIdx, tab, tabs);
17 | }
18 |
19 | getTab(name) {
20 | return this.tabBodiesRef.current.getTab(name);
21 | }
22 |
23 | render() {
24 | const {
25 | id,
26 | tabs,
27 | selectedTabIdx,
28 | showTabBar = true,
29 | } = this.props;
30 |
31 | const hidetabbar = String(showTabBar === false);
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 | {showTabBar && }
40 |
41 |
42 | |
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | TabView.propTypes = {
51 | id: PropTypes.string,
52 | selectedTabIdx: PropTypes.number,
53 | tabs: PropTypes.array,
54 | onSelectedTabChange: PropTypes.func,
55 | showTabBar: PropTypes.bool,
56 | };
57 |
58 | export default TabView;
59 |
--------------------------------------------------------------------------------
/webapp/modules/toolbar/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export const Button = (props) => {
5 | return (
6 |
11 | {props.children}
12 |
13 | );
14 | };
15 |
16 | Button.propTypes = {
17 | buttonRef: PropTypes.string,
18 | children: PropTypes.node,
19 | command: PropTypes.func,
20 | title: PropTypes.string,
21 | };
22 |
23 | class Toolbar extends React.Component {
24 | createToolbarItems(children) {
25 | const style = { color: "gray" };
26 | return children.reduce((items, child, i) => {
27 | if (items.length > 0) {
28 | const span = |;
29 | items.push(span);
30 | }
31 | items.push(child);
32 | return items;
33 | }, []);
34 | }
35 |
36 | render() {
37 | let { children } = this.props;
38 | if (!Array.isArray(children)) {
39 | children = [children];
40 | }
41 | return (
42 |
43 | {this.createToolbarItems(children || [])}
44 |
45 | );
46 | }
47 | }
48 |
49 | Toolbar.propTypes = {
50 | children: PropTypes.node,
51 | };
52 |
53 | export default Toolbar;
54 |
--------------------------------------------------------------------------------
/webapp/modules/tree/ObjectTree.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Tree, { createUINode } from "./Tree";
5 |
6 | export function hasChildren(value) {
7 | if (typeof value === "string") {
8 | // Object.keys(string) returns numeric keys like an array.
9 | // We don't want that.
10 | return false;
11 | }
12 | // arrays have keys, so this works for arrays and objects
13 | return Object.keys(value).length > 0;
14 | }
15 |
16 | export function populateChildren(uiNode, level) {
17 | const object = uiNode.value;
18 | const children = uiNode.children = [];
19 |
20 | const handleChild = (prop, propObj) => {
21 | if (typeof propObj !== "function") {
22 | const child = createUINode("dom", prop, propObj, hasChildren(propObj), level);
23 | children.push(child);
24 | }
25 | };
26 |
27 | if (Array.isArray(object)) {
28 | object.forEach((item, i) => handleChild(i, item));
29 | } else {
30 | Object.keys(object).forEach((key) => handleChild(key, object[key]));
31 | }
32 | }
33 |
34 | class ObjectTree extends React.Component {
35 | treeRef = React.createRef();
36 |
37 | getTree() {
38 | return this.treeRef.current;
39 | }
40 |
41 | render() {
42 | return ;
43 | }
44 | };
45 |
46 | export default ObjectTree;
47 |
--------------------------------------------------------------------------------
/webapp/modules/tree/TreeRepresentations.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import * as Arr from "../core/array";
4 |
5 | function safeToString(object) {
6 | try {
7 | return object.toString();
8 | } catch (e) {
9 | return "";
10 | }
11 | }
12 |
13 | function objectAsStringOrType(object) {
14 | const label = safeToString(object);
15 | const re = /\[object (.*?)\]/;
16 | const m = re.exec(label);
17 | return m ? m[1] : label;
18 | }
19 |
20 | class Representation extends React.Component {
21 | render() {
22 | let { className, value, title } = this.props;
23 | className = className || "object";
24 | title = title || objectAsStringOrType(value);
25 | return (
26 | {title}
27 | );
28 | }
29 | }
30 |
31 | class NullRepresentation extends React.Component {
32 | render() {
33 | return ;
34 | }
35 | }
36 |
37 | NullRepresentation.supportsObject = function(object, type) {
38 | return object === null;
39 | };
40 |
41 | class NumberRepresentation extends React.Component {
42 | render() {
43 | const { value } = this.props;
44 | return ;
45 | }
46 | }
47 |
48 | NumberRepresentation.supportsObject = function(object, type) {
49 | return type === "boolean" || type === "number";
50 | };
51 |
52 | class StringRepresentation extends React.Component {
53 | render() {
54 | const { value } = this.props;
55 | return ;
56 | }
57 | }
58 |
59 | StringRepresentation.supportsObject = function(object, type) {
60 | return type === "string";
61 | };
62 |
63 | class ArrRepresentation extends React.Component {
64 | render() {
65 | const { value } = this.props;
66 | const title = "Array [" + value.length + "]";
67 | return ;
68 | }
69 | }
70 |
71 | ArrRepresentation.supportsObject = function(object, type) {
72 | return Arr.isArray(object);
73 | };
74 |
75 | const Representations = {
76 | reps: [
77 | NullRepresentation,
78 | NumberRepresentation,
79 | StringRepresentation,
80 | ArrRepresentation,
81 | ],
82 |
83 | getRep(object) {
84 | let type = typeof object;
85 | if (type === "object" && object instanceof String) {
86 | type = "string";
87 | }
88 | const rep = this.reps.find((rep) => rep.supportsObject(object, type));
89 | if (rep) {
90 | return rep;
91 | }
92 | return Representation;
93 | },
94 | };
95 |
96 | export default Representations;
97 |
--------------------------------------------------------------------------------
/webapp/modules/tree/XMLTree.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | import Tree, { createUINode } from "./Tree";
5 |
6 | function nodeChildElements(node, limit) {
7 | limit = ("number" === limit) ? limit : null;
8 |
9 | const elements = [];
10 | let child = node.firstChild;
11 | while (child) {
12 | if (Node.ELEMENT_NODE === child.nodeType) {
13 | elements.push(child);
14 | if (null !== limit && elements.length >= limit) {
15 | return elements;
16 | }
17 | }
18 | child = child.nextSibling;
19 | }
20 | return elements;
21 | }
22 |
23 | function nodeAttributes(node, limit) {
24 | limit = ("number" === limit) ? limit : null;
25 |
26 | const attrs = [];
27 | if (!node.attributes) {
28 | return attrs;
29 | }
30 | for (let i = node.attributes.length; --i >= 0;) {
31 | attrs.push(node.attributes[i]);
32 | if (null !== limit && attrs.length >= limit) {
33 | return attrs;
34 | }
35 | }
36 | return attrs;
37 | }
38 |
39 | function hasChildren(value) {
40 | const attrs = nodeAttributes(value);
41 | const elements = nodeChildElements(value);
42 | return (attrs.length > 0 || elements.length > 0);
43 | }
44 |
45 | function populateChildren(uiNode, level) {
46 | const object = uiNode.value;
47 | const children = uiNode.children = [];
48 |
49 | const attrs = nodeAttributes(object)
50 | .map((attr) => createUINode("dom", "@" + attr.name, attr.value, false, level));
51 |
52 | const elements = nodeChildElements(object).map((element) => {
53 | const hasChilds = hasChildren(element);
54 | return createUINode("dom", element.tagName, hasChilds ? element : element.firstChild.nodeValue, hasChilds, level);
55 | });
56 |
57 | const members = attrs;
58 |
59 | // If there are no child elements, then we add the element's firstChild content (if it exists).
60 | // This firstChild content could be text.
61 | if (elements.length === 0 && object.firstChild) {
62 | members.push(createUINode("dom", "value", object.firstChild.nodeValue, false, level));
63 | }
64 |
65 | children.push(...attrs);
66 | children.push(...elements);
67 | }
68 |
69 | class XMLTree extends React.Component {
70 | render() {
71 | const { root } = this.props;
72 | return ;
73 | }
74 | };
75 |
76 | XMLTree.propTypes = {
77 | root: PropTypes.object,
78 | };
79 |
80 | XMLTree.isXmlMimeType = function(mimeType) {
81 | mimeType = Mime.extractMimeType(mimeType);
82 | return [
83 | "text/xml",
84 | "application/xml",
85 | "image/svg+xml",
86 | "application/atom+xml",
87 | "application/xslt+xml",
88 | "application/mathml+xml",
89 | "application/rss+xml",
90 | ].indexOf(mimeType) > -1;
91 | };
92 |
93 | XMLTree.canShowEntry = function(entry) {
94 | const { content } = entry.response;
95 |
96 | if (!content || !content.text) {
97 | return false;
98 | }
99 |
100 | if (!canDecode(content.encoding)) {
101 | return false;
102 | }
103 |
104 | const mimeType = Mime.extractMimeType(content.mimeType || "");
105 | return XMLTree.isXmlMimeType(mimeType);
106 | };
107 |
108 | export default XMLTree;
109 |
--------------------------------------------------------------------------------
/webapp/preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HTTP Archive Preview @VERSION@
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
31 |
32 |
35 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const path = require("path");
3 | const packageJson = require("./package.json");
4 | const GitRevisionPlugin = require("git-revision-webpack-plugin");
5 | const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
6 |
7 | // Export some data to the webpack build. See buildInfo.js.
8 | const gitRevisionPlugin = new GitRevisionPlugin();
9 | const definePlugin = new webpack.DefinePlugin({
10 | __buildInfo__: {
11 | version: JSON.stringify(packageJson.version),
12 | gitVersion: JSON.stringify(gitRevisionPlugin.version()),
13 | gitCommit: JSON.stringify(gitRevisionPlugin.commithash()),
14 | },
15 | });
16 |
17 | function makeConfig({
18 | target = "",
19 | babelPresetEnvUseBuiltIns = false,
20 | }) {
21 | const babelPresetEnvTargets = (target === "legacy")
22 | ? {
23 | "ie": "11",
24 | }
25 | : [
26 | "last 2 versions",
27 | "not ie < 999",
28 | "not android < 999",
29 | "not dead",
30 | ];
31 |
32 | return {
33 | mode: "development",
34 | entry: {
35 | // polyfill required by (at least) IE11
36 | main: [
37 | "whatwg-fetch",
38 | "./webapp/main.js",
39 | ],
40 | demo: [
41 | "whatwg-fetch",
42 | "./webapp/demo.js",
43 | ],
44 | },
45 | output: {
46 | path: path.join(__dirname, "webapp", "dist"),
47 | publicPath: "/webapp/dist/",
48 | filename: `[name].${target ? target + "." : ""}js`,
49 | },
50 | devtool: "source-map",
51 | plugins: [
52 | definePlugin,
53 | new BundleAnalyzerPlugin({
54 | openAnalyzer: false,
55 | generateStatsFile: true,
56 | statsFilename: path.join(__dirname, "reports", (target ? target + "." : "") + "stats.json"),
57 | analyzerMode: "static",
58 | reportFilename: path.join(__dirname, "reports", (target ? target + "." : "") + "report.html"),
59 | }),
60 | ],
61 | optimization: {
62 | splitChunks: {
63 | cacheGroups: {
64 | commons: {
65 | test: /[\\/]node_modules[\\/]/,
66 | name: "vendor",
67 | chunks: "initial",
68 | },
69 | },
70 | },
71 | },
72 | module: {
73 | rules: [
74 | {
75 | // .js or .jsx
76 | test: /\.jsx?$/,
77 | use: {
78 | loader: "babel-loader",
79 | options: {
80 | presets: [
81 | ["@babel/preset-env", {
82 | useBuiltIns: babelPresetEnvUseBuiltIns,
83 | targets: babelPresetEnvTargets,
84 | debug: true,
85 | }],
86 | "@babel/preset-react",
87 | ],
88 | // transform-runtime required for things like Object.assign() for browsers that don't support that.
89 | // rest-spread transform required for "valuelink"" module
90 | plugins: [
91 | "transform-class-properties",
92 | ],
93 | },
94 | },
95 | },
96 | {
97 | test: /\.css$/,
98 | use: [
99 | {
100 | loader: "style-loader",
101 | },
102 | {
103 | loader: "css-loader",
104 | },
105 | ],
106 | },
107 | {
108 | // inline base64 URLs for <=8k images, direct URLs for the rest
109 | test: /\.(png|jpg)$/,
110 | loader: "url-loader?limit=8192",
111 | },
112 | ],
113 | },
114 | devServer: {
115 | // Until all the test resources are part of the harviewer-react project,
116 | // use the original harviewer resources via proxy.
117 | proxy: {
118 | "/selenium": "http://harviewer.lan:49001",
119 | },
120 | },
121 | resolveLoader: {
122 | alias: {
123 | "i18n": "amdi18n-loader",
124 | },
125 | },
126 | };
127 | }
128 |
129 | module.exports = (env, options) => {
130 | const prod = options && options.mode === "production";
131 | const babelPresetEnvUseBuiltIns = prod ? "entry" : false;
132 | return [
133 | makeConfig({
134 | babelPresetEnvUseBuiltIns,
135 | }),
136 | makeConfig({
137 | target: "legacy",
138 | babelPresetEnvUseBuiltIns,
139 | }),
140 | ];
141 | };
142 |
--------------------------------------------------------------------------------