Compare commits

..

113 Commits

Author SHA1 Message Date
13f80cdaad Merge remote-tracking branch 'origin/master' 2022-11-08 19:13:48 +08:00
28f32de0b2 Release v2.2.35 2022-11-08 19:13:39 +08:00
a6a4fb401d Merge pull request #1406 from GNS3/dependabot/npm_and_yarn/loader-utils-1.4.1
Bump loader-utils from 1.4.0 to 1.4.1
2022-11-08 18:24:57 +08:00
f62366440c Bump loader-utils from 1.4.0 to 1.4.1
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-08 08:31:46 +00:00
135ecbdc33 Merge pull request #1403 from GNS3/dependabot/npm_and_yarn/node-fetch-3.2.10
Bump node-fetch from 3.1.1 to 3.2.10
2022-10-18 22:02:17 +08:00
9ebbbb197b Bump node-fetch from 3.1.1 to 3.2.10
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 3.1.1 to 3.2.10.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v3.1.1...v3.2.10)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-07 08:23:13 +00:00
f90c074191 Merge pull request #1402 from GNS3/dependabot/npm_and_yarn/snyk-1.996.0
Bump snyk from 1.780.0 to 1.996.0
2022-10-07 10:22:46 +02:00
2d49ca30fa Bump snyk from 1.780.0 to 1.996.0
Bumps [snyk](https://github.com/snyk/snyk) from 1.780.0 to 1.996.0.
- [Release notes](https://github.com/snyk/snyk/releases)
- [Commits](https://github.com/snyk/snyk/compare/v1.780.0...v1.996.0)

---
updated-dependencies:
- dependency-name: snyk
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-06 18:40:49 +00:00
83f7d36e2d Add more video resolutions to Docker containers using VNC in WebUI. Fixes #1375 2022-08-07 23:59:44 +02:00
174053f297 Development on v2.2.34dev 2022-06-20 20:40:33 +02:00
0e4e124c14 Release v2.2.33 2022-06-20 20:24:06 +02:00
c928ab0342 Merge pull request #1332 from GNS3/bugfix/1329
Fix spice+agent and none console types not supported
2022-06-20 18:38:17 +02:00
9efd99dccb Fix spice+agent and none console types not supported. 2022-06-18 18:53:36 +02:00
7e43dc77cb Merge pull request #1322 from GNS3/dependabot/npm_and_yarn/async-2.6.4
Bump async from 2.6.3 to 2.6.4
2022-06-10 00:33:25 +08:00
d7752a4d7b Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 07:52:58 +00:00
40df0fe1ee Merge pull request #1321 from GNS3/dependabot/npm_and_yarn/ejs-3.1.8
Bump ejs from 3.1.6 to 3.1.8
2022-06-01 14:52:11 +07:00
d5bd84234d Bump ejs from 3.1.6 to 3.1.8
Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.8.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.8)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 07:13:32 +00:00
a9a7ecf3e7 Merge pull request #1320 from GNS3/dependabot/npm_and_yarn/eventsource-1.1.1
Bump eventsource from 1.1.0 to 1.1.1
2022-06-01 14:12:30 +07:00
5dc5a953e6 Bump eventsource from 1.1.0 to 1.1.1
Bumps [eventsource](https://github.com/EventSource/eventsource) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/EventSource/eventsource/releases)
- [Changelog](https://github.com/EventSource/eventsource/blob/master/HISTORY.md)
- [Commits](https://github.com/EventSource/eventsource/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: eventsource
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 01:20:11 +00:00
32c78450a2 Development on 2.2.33dev 2022-04-27 17:53:00 +07:00
82feb9aa92 Release web UI 2.2.32 2022-04-27 17:48:47 +07:00
a08a7e1476 Fix for issue in docker configurator 2022-04-27 10:35:54 +02:00
b5e4972bdb Fix for #1303 2022-04-26 11:24:00 +02:00
dc5c0d3d94 Merge pull request #1302 from GNS3/bugfix/1298
Fix capture file name
2022-04-26 14:52:49 +07:00
04936cfc8d Fix generated capture file is not valid 2022-04-21 13:53:27 +07:00
8728056b8d Merge pull request #1296 from GNS3/dependabot/npm_and_yarn/plist-3.0.5
Bump plist from 3.0.4 to 3.0.5
2022-04-13 16:15:02 +07:00
138d1f8552 Merge pull request #1283 from GNS3/dependabot/npm_and_yarn/electron-13.6.6
Bump electron from 13.6.1 to 13.6.6
2022-04-13 16:14:26 +07:00
dc31d51844 Bump plist from 3.0.4 to 3.0.5
Bumps [plist](https://github.com/TooTallNate/node-plist) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/TooTallNate/node-plist/releases)
- [Changelog](https://github.com/TooTallNate/plist.js/blob/master/History.md)
- [Commits](https://github.com/TooTallNate/node-plist/commits)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-12 10:07:20 +00:00
def33a353d Bump minimist from 1.2.5 to 1.2.6 (#1294)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-12 12:06:10 +02:00
ed3db2ea4d Bump electron from 13.6.1 to 13.6.6
Bumps [electron](https://github.com/electron/electron) from 13.6.1 to 13.6.6.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v13.6.1...v13.6.6)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-25 18:37:29 +00:00
7ad6de2256 Merge pull request #1261 from GNS3/dependabot/npm_and_yarn/url-parse-1.5.10
Bump url-parse from 1.5.3 to 1.5.10
2022-03-13 20:20:17 +10:00
6dcc5cdc2e Bump url-parse from 1.5.3 to 1.5.10
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.3 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.3...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 01:08:02 +00:00
089e66a02b Bump karma from 6.3.15 to 6.3.16 (#1263)
Bumps [karma](https://github.com/karma-runner/karma) from 6.3.15 to 6.3.16.
- [Release notes](https://github.com/karma-runner/karma/releases)
- [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md)
- [Commits](https://github.com/karma-runner/karma/compare/v6.3.15...v6.3.16)

---
updated-dependencies:
- dependency-name: karma
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 01:16:14 +01:00
da848d42af Release web UI 2.2.30 2022-02-16 01:02:04 +01:00
6b08fb8d9a Bump follow-redirects from 1.14.7 to 1.14.8 (#1254)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-16 00:49:50 +01:00
8874e7efbc Refreshing UI (#1253)
* Update font-fixer.ts

* Update interface-status.ts

* Updating project map UI

* Updating templates menu

* Fixing tests

* Update karma.conf.js
2022-02-08 23:48:45 +01:00
2b834768c6 Update karma.conf.js 2022-02-08 23:47:22 +01:00
eabdda0e74 Update dependency karma to v6.3.14 (#1251)
Co-authored-by: whitesource-for-github-com[bot] <whitesource-for-github-com[bot]@users.noreply.github.com>
2022-02-08 14:05:07 +01:00
9e3f667767 Update dependency karma to v6.3.9 (#1247)
Co-authored-by: whitesource-for-github-com[bot] <whitesource-for-github-com[bot]@users.noreply.github.com>
2022-01-31 14:07:59 +01:00
8898141bc1 Bump nanoid from 3.1.30 to 3.2.0 (#1236)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 10:42:53 +01:00
c8753ed45c Bump node-fetch from 3.0.0 to 3.1.1 (#1237)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 3.0.0 to 3.1.1.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v3.0.0...v3.1.1)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 10:22:43 +01:00
d496d8dc64 Bump log4js from 6.3.0 to 6.4.0 (#1235)
Bumps [log4js](https://github.com/log4js-node/log4js-node) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/log4js-node/log4js-node/releases)
- [Changelog](https://github.com/log4js-node/log4js-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/log4js-node/log4js-node/compare/v6.3.0...v6.4.0)

---
updated-dependencies:
- dependency-name: log4js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 00:21:55 +01:00
a8f9b6948d Bump follow-redirects from 1.14.5 to 1.14.7 (#1233)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.5 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.5...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 00:01:23 +01:00
ccd3ff61f1 Release web UI 2.2.29 2022-01-07 20:04:51 +01:00
abf9d8b387 Release web UI 2.2.28 2021-12-13 23:40:20 +01:00
f8cc654539 Update project-map.component.ts 2021-12-13 23:34:03 +01:00
56554f6d0c Update project-map.component.ts 2021-12-13 23:22:15 +01:00
7be137dc1c Merge pull request #1225 from GNS3/Remember-topology/servers-window-position
Update topology-summary.component.ts
2021-12-12 12:46:39 -08:00
1b45a2284d Merge pull request #1224 from GNS3/Mouse-pointer-#1219
Mouse pointer #1219
2021-12-12 12:46:11 -08:00
6b4f5186d0 Update topology-summary.component.ts 2021-12-09 02:27:19 +01:00
c3f2ebad0c Mouse pointer #1219 2021-12-08 19:18:38 +01:00
249f63a97a Update new-template-dialog.component.html 2021-12-07 20:03:36 +01:00
cc6b8cd28c Update configurator-docker.component.ts 2021-12-07 15:51:26 +01:00
96928d86f8 Updating snyk version 2021-12-02 00:51:24 +01:00
a213a7aca1 Angular version set to 12.2.12 2021-11-10 00:12:32 +01:00
fc1d17b921 Reverting updates 2021-11-09 23:28:52 +01:00
0ddf4f6e95 Release web UI 2.2.27 2021-11-08 18:09:29 +01:00
8d466d655e Updating dependencies 2021-11-08 18:03:17 +01:00
2c7dd5f179 Removing sentry/cli 2021-11-08 17:48:58 +01:00
64999f2b72 Removing electron builder 2021-11-08 17:36:51 +01:00
253c65b8c1 Fix for builds on github 2021-11-08 16:49:45 +01:00
74c1a82524 Update .gitignore 2021-11-08 13:41:11 +01:00
4e42bd7a54 Merge pull request #1199 from potats0/master
add progress bar when uploading qemu disks
2021-11-07 13:54:30 -08:00
891e65b094 Update yarn.lock 2021-11-07 18:46:57 +01:00
c808477914 Fix for error with component factory after migration to angular v13 2021-11-07 18:32:34 +01:00
8503a17187 Updating angular material 2021-11-07 18:24:48 +01:00
8afea664ff updating packages 2021-11-07 17:28:04 +01:00
538ae8b7fb Update yarn.lock 2021-11-07 16:58:08 +01:00
370694f3b0 Update package.json 2021-11-07 16:55:02 +01:00
5175b3beac Updating packages 2021-11-07 16:48:17 +01:00
2df1956dbc Updating packages 2021-11-02 22:39:08 +01:00
56384fbcc0 fix progress bar incorrect when upload file twice 2021-10-14 12:22:15 +08:00
15faca6d89 show upload file progress when uploading qemu template. 2021-10-14 10:56:17 +08:00
4142144d4d Update package.json 2021-10-06 18:08:56 +02:00
e2e87db039 Release 2.2.26 2021-10-06 17:09:46 +02:00
c868f08a25 Update yarn.lock 2021-10-06 17:03:49 +02:00
9aedd410bb Update yarn.lock 2021-10-06 14:02:26 +02:00
5fb76d7d11 Update yarn.lock 2021-10-06 13:52:57 +02:00
a7c343aa7c Merge pull request #1194 from GNS3/dependabot/npm_and_yarn/nth-check-2.0.1
Bump nth-check from 2.0.0 to 2.0.1
2021-10-06 04:47:47 -07:00
cfe8c4760b Updating dependencies 2021-10-06 13:34:19 +02:00
7cbcc84cc1 Update template.component.ts 2021-10-06 12:57:40 +02:00
357e478fb8 Fix for theming 2021-10-06 12:44:23 +02:00
063d8c9dc7 Bump nth-check from 2.0.0 to 2.0.1
Bumps [nth-check](https://github.com/fb55/nth-check) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/fb55/nth-check/releases)
- [Commits](https://github.com/fb55/nth-check/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: nth-check
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-22 12:57:14 +00:00
63ecacb6b6 Update package.json 2021-09-14 12:01:56 +02:00
6cecacf611 Release 2.2.25 2021-09-14 10:22:36 +02:00
c389404e58 Updating dependencies 2021-09-14 10:06:47 +02:00
b1aba60410 Option to access system status from servers page 2021-09-14 01:01:46 +02:00
4cd9f77732 Release 2.2.24 2021-08-25 11:52:56 +02:00
1619c3ec05 Fix for https://github.com/GNS3/gns3-web-ui/issues/1184 2021-08-24 17:07:47 +02:00
b33a01e225 Update yarn.lock 2021-08-24 15:12:51 +02:00
353740376e Updating angular-devkit/build-angular 2021-08-24 14:53:51 +02:00
9fe899e4df Removing vulnerabilities 2021-08-24 14:39:02 +02:00
dd1f16c53d Merge pull request #1187 from GNS3/dependabot/npm_and_yarn/url-parse-1.5.3
Bump url-parse from 1.5.1 to 1.5.3
2021-08-24 05:00:53 -07:00
3fa52d3c9c Updating dependencies 2021-08-24 13:55:15 +02:00
bc5dd0271f Bump url-parse from 1.5.1 to 1.5.3
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.3)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-24 11:04:39 +00:00
81ca3e2af2 Merge pull request #1183 from GNS3/dependabot/npm_and_yarn/tar-6.1.6
Bump tar from 6.1.0 to 6.1.6
2021-08-24 04:03:51 -07:00
d7a0d2f69a Bump tar from 6.1.0 to 6.1.6
Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.6.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.6)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-04 09:11:45 +00:00
8f0bbafa72 Updating dependencies 2021-08-04 11:10:41 +02:00
08f7456bb1 Release 2.2.23 2021-08-04 10:51:04 +02:00
752246c629 Merge branch 'master' of https://github.com/GNS3/gns3-web-ui 2021-07-30 10:58:16 +02:00
2709d8d102 Updating packages to latest versions 2021-07-30 10:57:19 +02:00
d949f536ab Merge pull request #1178 from GNS3/snyk-fix-350c45c5ff2d382873ad7318d0553fa0
[Snyk] Security upgrade snyk from 1.630.0 to 1.667.0
2021-07-30 01:25:15 -07:00
42d8bcebbb fix: package.json & yarn.lock to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-JSZIP-1251497
2021-07-28 08:00:09 +00:00
a153e97f55 Merge pull request #1169 from GNS3/dependabot/npm_and_yarn/ws-6.2.2
Bump ws from 6.2.1 to 6.2.2
2021-06-22 10:15:11 +02:00
7953a86fd9 Bump ws from 6.2.1 to 6.2.2
Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/6.2.1...6.2.2)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-22 07:57:45 +00:00
5c426ad822 Merge pull request #1170 from GNS3/dependabot/npm_and_yarn/postcss-7.0.36
Bump postcss from 7.0.35 to 7.0.36
2021-06-22 09:56:50 +02:00
a9b7e09da4 Merge pull request #1173 from GNS3/Contextual-menu-position
Contextual menu position
2021-06-22 09:43:02 +02:00
60bae5db3c Bump postcss from 7.0.35 to 7.0.36
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.35 to 7.0.36.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.35...7.0.36)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-15 19:16:01 +00:00
d6e5dee1aa Update main.yml 2021-06-15 15:32:59 +02:00
cb101e8202 Updating dependencies 2021-06-15 15:25:19 +02:00
0124018b02 Update project-map.component.ts 2021-06-15 14:56:00 +02:00
04fdcc6893 Update package.json 2021-06-14 16:12:32 +02:00
e54638a248 Updating dependencies 2021-06-08 18:03:18 +02:00
42 changed files with 3764 additions and 4626 deletions

File diff suppressed because one or more lines are too long

View File

@ -14,10 +14,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup node 12
- name: Setup node 14
uses: actions/setup-node@v2
with:
node-version: 12.x
node-version: 14.x
- uses: c-hive/gha-yarn-cache@v1
- name: Install JS dependencies
run: yarn install

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/.angular
/dist
/tmp
/out-tsc

View File

@ -47,10 +47,11 @@
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"buildOptimizer": true,
"sourceMap": true,
"optimization": false,
"namedChunks": true
"namedChunks": true,
"aot": true
},
"configurations": {
"production": {
@ -176,6 +177,7 @@
"src/styles.scss",
"src/theme.scss"
],
"sourceMap": false,
"assets": [
"src/assets",
"src/favicon.ico"

View File

@ -25,7 +25,7 @@ module.exports = function (config) {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
browsers: ['Chrome'],
singleRun: true
});
};

View File

@ -1,6 +1,6 @@
{
"name": "gns3-web-ui",
"version": "2.2.22",
"version": "2.2.35",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"
@ -36,31 +36,33 @@
"generate-licenses-file": "yarn license-checker --production --csv --out licenses.csv",
"prebuildforelectron": "node set-variables-in-env.js --set src/environments/environment.electron.prod.ts",
"postbuildforelectron": "node set-variables-in-env.js --unset src/environments/environment.electron.prod.ts",
"postinstall": "ngcc --properties es5 browser module main --first-only --create-ivy-entry-points && ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
"snyk-protect": "snyk protect",
"prepare": "yarn run snyk-protect"
},
"private": true,
"dependencies": {
"@angular/animations": "^12.0.2",
"@angular/cdk": "^12.0.2",
"@angular/common": "^12.0.2",
"@angular/compiler": "^12.0.2",
"@angular/core": "^12.0.2",
"@angular/forms": "^12.0.2",
"@angular/material": "^12.0.2",
"@angular/platform-browser": "^12.0.2",
"@angular/platform-browser-dynamic": "^12.0.2",
"@angular/router": "^12.0.2",
"@sentry/browser": "^6.3.6",
"@types/jest": "^26.0.23",
"@types/mocha": "^8.2.2",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.3",
"angular-draggable-droppable": "^4.6.0",
"angular-resizable-element": "^3.3.5",
"bootstrap": "^5.0.0",
"@angular/animations": "^12.2.12",
"@angular/cdk": "^12.2.12",
"@angular/common": "^12.2.12",
"@angular/compiler": "^12.2.12",
"@angular/core": "^12.2.12",
"@angular/forms": "^12.2.12",
"@angular/material": "^12.2.12",
"@angular/platform-browser": "^12.2.12",
"@angular/platform-browser-dynamic": "^12.2.12",
"@angular/router": "^12.2.12",
"@sentry/browser": "^6.14.1",
"@types/jest": "^27.0.2",
"@types/mocha": "^9.0.0",
"@types/react": "^17.0.34",
"@types/react-dom": "^17.0.11",
"angular-draggable-droppable": "^5.0.0",
"angular-resizable-element": "^3.4.0",
"bootstrap": "^5.1.3",
"command-exists": "^1.2.9",
"core-js": "^3.12.1",
"core-js": "^3.19.1",
"css-tree": "^1.1.3",
"d3-ng2-service": "^2.2.0",
"eev": "^0.1.5",
"ini": "^2.0.0",
@ -69,61 +71,61 @@
"ng-circle-progress": "^1.6.0",
"ng2-file-upload": "^1.4.0",
"ngx-childprocess": "^0.0.6",
"ngx-device-detector": "^2.0.9",
"ngx-device-detector": "^2.1.1",
"ngx-electron": "^2.2.0",
"node-fetch": "^2.6.1",
"node-fetch": "^3.2.10",
"notosans-fontface": "1.2.2",
"prettier-plugin-organize-imports": "^2.0.0",
"rxjs": "^6.5.3",
"rxjs-compat": "^6.5.3",
"prettier-plugin-organize-imports": "^2.3.4",
"rxjs": "^6.6.7",
"rxjs-compat": "^6.6.7",
"save-svg-as-png": "^1.4.17",
"snyk": "^1.589.0",
"spark-md5": "^3.0.1",
"svg-crowbar": "^0.6.5",
"snyk": "^1.996.0",
"spark-md5": "^3.0.2",
"svg-crowbar": "^0.7.0",
"tree-kill": "^1.2.2",
"tslib": "^2.2.0",
"tslib": "^2.3.1",
"typeface-roboto": "^1.1.13",
"xterm": "^4.11.0",
"xterm": "^4.15.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.5.0",
"yargs": "^17.0.1",
"yargs": "^17.2.1",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^12.0.2",
"@angular/cli": "^12.0.2",
"@angular/compiler-cli": "^12.0.2",
"@angular/language-service": "^12.0.2",
"@sentry/cli": "^1.64.2",
"@sentry/electron": "^2.4.1",
"@types/jasmine": "^3.7.1",
"@types/jasminewd2": "^2.0.9",
"@types/node": "15.6.1",
"@angular-devkit/build-angular": "^12.2.12",
"@angular/cli": "^12.2.12",
"@angular/compiler-cli": "^12.2.12",
"@angular/language-service": "^12.2.12",
"@sentry/cli": "^1.71.0",
"@sentry/electron": "^2.5.4",
"@types/jasmine": "^3.10.2",
"@types/jasminewd2": "^2.0.10",
"@types/node": "16.11.6",
"codelyzer": "^6.0.2",
"electron": "^13.0.1",
"electron-builder": "22.10.5",
"electron": "^13.6.6",
"electron-builder": "^22.9.1",
"file-loader": "^6.2.0",
"jasmine-core": "~3.7.1",
"jasmine-core": "~3.10.1",
"jasmine-spec-reporter": "~7.0.0",
"jquery": "^3.6.0",
"karma": "^6.3.2",
"karma": "^6.3.16",
"karma-chrome-launcher": "~3.1.0",
"karma-cli": "^2.0.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.6.0",
"karma-jasmine-html-reporter": "^1.7.0",
"license-checker": "^25.0.1",
"popper.js": "^1.16.1",
"prettier": "^2.3.0",
"prettier": "^2.4.1",
"protractor": "^7.0.0",
"replace": "^1.2.1",
"rxjs-tslint": "^0.1.8",
"ts-mockito": "^2.6.1",
"ts-node": "~10.0.0",
"ts-node": "~10.4.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "4.2.4",
"webpack": "5.38.0",
"typescript": "4.2.3",
"webpack": "5.62.1",
"yarn-upgrade-all": "^0.5.4"
},
"greenkeeper": {
@ -132,4 +134,4 @@
]
},
"snyk": true
}
}

View File

@ -1,6 +1,10 @@
GNS3 WebUI is web implementation of user interface for GNS3 software.
Current version: 2.2.22
Current version: 2.2.32
Bug Fixes & enhancements
- Fixed generated capture file is not valid
- Fixed Docker additional directories
Current version: 2020.4.0-beta.1

View File

@ -87,6 +87,7 @@ export class TextEditorComponent implements OnInit, OnDestroy {
`scale(${this.mapScaleService.getScale()})`
);
this.temporaryTextElement.nativeElement.focus();
document.documentElement.style.cursor = "default";
let textListener = () => {
this.drawingsEventSource.textAdded.emit(

View File

@ -16,8 +16,8 @@ describe('FontFixer', () => {
};
expect(fixer.fix(font)).toEqual({
font_family: 'Noto Sans',
font_size: 11,
font_family: 'Arial',
font_size: 12,
font_weight: 'bold',
});
});
@ -39,12 +39,12 @@ describe('FontFixer', () => {
it('should fix TypeWriter font and 10px size in styles', () => {
const styles = 'font-family: TypeWriter; font-size: 10px; font-weight: bold';
expect(fixer.fixStyles(styles)).toEqual('font-family:Noto Sans;font-size:11px;font-weight:bold');
expect(fixer.fixStyles(styles)).toEqual('font-family:Arial;font-size:12px;font-weight:bold');
});
it('should fix TypeWriter font and 10px size in styles with quotes', () => {
const styles = 'font-family: "TypeWriter"; font-size: 10px; font-weight: bold';
expect(fixer.fixStyles(styles)).toEqual('font-family:Noto Sans;font-size:11px;font-weight:bold');
expect(fixer.fixStyles(styles)).toEqual('font-family:Arial;font-size:12px;font-weight:bold');
});
});

View File

@ -9,8 +9,8 @@ import { Font } from '../models/font';
export class FontFixer {
static DEFAULT_FONT = 'TypeWriter';
static DEFAULT_SIZE = 10;
static REPLACE_BY_FONT = 'Noto Sans';
static REPLACE_BY_SIZE = 11;
static REPLACE_BY_FONT = 'Arial';
static REPLACE_BY_SIZE = 12;
public fix(font: Font): Font {
if (font.font_family === FontFixer.DEFAULT_FONT && font.font_size === FontFixer.DEFAULT_SIZE) {

View File

@ -53,6 +53,7 @@ export class Properties {
qemu_path: string;
environment: string;
extra_hosts: string;
extra_volumes: string[];
replicate_network_connection_state: boolean;
}

View File

@ -42,7 +42,7 @@ describe('TextDrawingWidget', () => {
const text_element = drew.nodes()[0];
expect(text_element.innerHTML).toEqual('<tspan xml:space="preserve" x="0" dy="0em">THIS IS TEXT</tspan>');
expect(text_element.getAttribute('fill')).toEqual('#000000');
expect(text_element.getAttribute('style')).toEqual('font-family: "Noto Sans"; font-size: 11pt; font-weight: bold');
expect(text_element.getAttribute('style')).toEqual('font-family: "Arial"; font-size: 12pt; font-weight: bold');
expect(text_element.getAttribute('text-decoration')).toEqual('line-through');
});

View File

@ -97,9 +97,9 @@ export class InterfaceStatusWidget implements Widget {
.attr('y', (ls: LinkStatus) => ls.y - 10)
.attr('rx', 8)
.attr('ry', 8)
.style('fill', 'white')
.style('fill', '#c7ffdf')
.attr('stroke', '#2ecc71')
.attr('stroke-width', 3);
.attr('stroke-width', 2);
status_started.exit().remove();
const status_started_label = link_group
.selectAll<SVGTextElement, LinkStatus>('text.status_started_label')
@ -111,7 +111,7 @@ export class InterfaceStatusWidget implements Widget {
.text((ls: LinkStatus) => ls.port)
.attr('x', (ls: LinkStatus) => ls.x - 25)
.attr('y', (ls: LinkStatus) => ls.y + 5)
.attr('fill', `black`);
.attr('fill', `#0e9647`);
status_started_label.exit().remove();
const status_stopped = link_group
@ -129,9 +129,9 @@ export class InterfaceStatusWidget implements Widget {
.attr('y', (ls: LinkStatus) => ls.y - 10)
.attr('rx', 8)
.attr('ry', 8)
.style('fill', 'white')
.style('fill', '#ffe3e3')
.attr('stroke', 'red')
.attr('stroke-width', 3);
.attr('stroke-width', 2);
status_stopped.exit().remove();
const status_stopped_label = link_group
.selectAll<SVGTextElement, LinkStatus>('text.status_stopped_label')
@ -143,7 +143,7 @@ export class InterfaceStatusWidget implements Widget {
.text((ls: LinkStatus) => ls.port)
.attr('x', (ls: LinkStatus) => ls.x - 25)
.attr('y', (ls: LinkStatus) => ls.y + 5)
.attr('fill', `black`);
.attr('fill', `red`);
status_stopped_label.exit().remove();
const status_suspended = link_group
@ -162,8 +162,8 @@ export class InterfaceStatusWidget implements Widget {
.attr('rx', 8)
.attr('ry', 8)
.style('fill', 'white')
.attr('stroke', '#FFFF00')
.attr('stroke-width', 3);
.attr('stroke', '#fffbc3')
.attr('stroke-width', 2);
status_suspended.exit().remove();
const status_suspended_label = link_group
.selectAll<SVGTextElement, LinkStatus>('text.status_suspended_label')
@ -175,7 +175,7 @@ export class InterfaceStatusWidget implements Widget {
.text((ls: LinkStatus) => ls.port)
.attr('x', (ls: LinkStatus) => ls.x - 25)
.attr('y', (ls: LinkStatus) => ls.y + 5)
.attr('fill', `black`);
.attr('fill', `#6b5633`);
status_suspended_label.exit().remove();
} else {
const status_started = link_group

View File

@ -110,6 +110,9 @@
placeholder="Please enter name"
/>
</mat-form-field>
<div *ngIf="uploadedFile">
<mat-progress-bar mode="determinate" [value]="uploadProgress" aria-valuemin="0" aria-valuemax="100"></mat-progress-bar>
</div>
</div>
</form>
</mat-step>

View File

@ -32,6 +32,8 @@ export class AddQemuVmTemplateComponent implements OnInit {
chosenImage: string = '';
qemuTemplate: QemuTemplate;
uploader: FileUploader;
uploadedFile: boolean = false;
uploadProgress: number = 0;
nameForm: FormGroup;
memoryForm: FormGroup;
@ -86,6 +88,9 @@ export class AddQemuVmTemplateComponent implements OnInit {
});
this.toasterService.success('Image uploaded');
};
this.uploader.onProgressItem = (progress: any) => {
this.uploadProgress = progress['progress'];
};
const server_id = this.route.snapshot.paramMap.get('server_id');
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
@ -127,6 +132,7 @@ export class AddQemuVmTemplateComponent implements OnInit {
}
uploadImageFile(event) {
this.uploadedFile = true;
let name = event.target.files[0].name;
this.diskForm.controls['fileName'].setValue(name);

View File

@ -33,20 +33,24 @@ export class ConsoleDeviceActionBrowserComponent {
this.node.console_host = this.server.host;
}
if (
this.node.console_type === 'telnet' ||
this.node.console_type === 'vnc' ||
this.node.console_type === 'spice'
) {
try {
try {
if (this.node.console_type === 'telnet') {
location.assign(
`gns3+${this.node.console_type}://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
`gns3+telnet://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
);
} catch (e) {
this.toasterService.error(e);
} else if (this.node.console_type === 'vnc') {
location.assign(
`gns3+vnc://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
);
} else if (this.node.console_type.startsWith('spice')) {
location.assign(
`gns3+spice://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
);
} else {
this.toasterService.error('Supported console types are: telnet, vnc, spice and spice+agent.');
}
} else {
this.toasterService.error('Supported console types: telnet, vnc, spice.');
} catch (e) {
this.toasterService.error(e);
}
}
}

View File

@ -29,7 +29,7 @@ export class ConsoleDeviceActionComponent implements OnInit {
let consoleCommand = this.settingsService.getConsoleSettings()
? this.settingsService.getConsoleSettings()
: this.nodeService.getDefaultCommand();
const startedNodes = this.nodes.filter((node) => node.status === 'started');
const startedNodes = this.nodes.filter((node) => node.status === 'started' && node.console_type !== 'none');
if (startedNodes.length === 0) {
this.toasterService.error('Device needs to be started in order to console to it.');
@ -37,7 +37,7 @@ export class ConsoleDeviceActionComponent implements OnInit {
}
for (var node of this.nodes) {
if (node.status !== 'started') {
if (node.status !== 'started' && node.console_type !== 'none') {
continue;
}

View File

@ -232,12 +232,12 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
location.assign(
`gns3+vnc://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
);
} else if (node.console_type === 'spice') {
} else if (node.console_type.startsWith('spice')) {
location.assign(
`gns3+spice://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
);
} else {
this.showCommand('Supported console types: telnet, vnc, spice.');
this.showCommand('Supported console types are: telnet, vnc, spice and spice+agent');
}
} else {
this.showCommand(`This node must be started before a console can be opened.`);
@ -297,28 +297,28 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
printNode(node: Node): string {
return (
`command_line: ${node.command_line},
compute_id: ${node.compute_id},
console: ${node.console},
console_host: ${node.console_host},
console_type: ${node.console_type},
first_port_name: ${node.first_port_name},
height: ${node.height},
label: ${node.label.text},
name: ${node.name},
node_directory: ${node.node_directory},
node_id: ${node.node_id},
node_type: ${node.node_type},
port_name_format: ${node.port_name_format},
`command_line: ${node.command_line},
compute_id: ${node.compute_id},
console: ${node.console},
console_host: ${node.console_host},
console_type: ${node.console_type},
first_port_name: ${node.first_port_name},
height: ${node.height},
label: ${node.label.text},
name: ${node.name},
node_directory: ${node.node_directory},
node_id: ${node.node_id},
node_type: ${node.node_type},
port_name_format: ${node.port_name_format},
port_segment_size: ${node.port_segment_size}, ` +
this.printPorts(node.ports) +
`project_id: ${node.project_id},
status: ${node.status},
symbol: ${node.symbol},
symbol_url: ${node.symbol_url},
width: ${node.width},
x: ${node.x},
y: ${node.y},
`project_id: ${node.project_id},
status: ${node.status},
symbol: ${node.symbol},
symbol_url: ${node.symbol_url},
width: ${node.width},
x: ${node.x},
y: ${node.y},
z: ${node.z}`
);
}
@ -328,31 +328,31 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
ports.forEach((port) => {
response =
response +
`adapter_number: ${port.adapter_number},
link_type: ${port.link_type},
name: ${port.name},
port_number: ${port.port_number},
`adapter_number: ${port.adapter_number},
link_type: ${port.link_type},
name: ${port.name},
port_number: ${port.port_number},
short_name: ${port.short_name}, `;
});
return response;
}
printLink(link: Link): string {
return `capture_file_name: ${link.capture_file_name},
capture_file_path: ${link.capture_file_path},
capturing: ${link.capturing},
link_id: ${link.link_id},
link_type: ${link.link_type},
project_id: ${link.project_id},
return `capture_file_name: ${link.capture_file_name},
capture_file_path: ${link.capture_file_path},
capturing: ${link.capturing},
link_id: ${link.link_id},
link_type: ${link.link_type},
project_id: ${link.project_id},
suspend: ${link.suspend}, `;
}
printDrawing(drawing: Drawing): string {
return `drawing_id: ${drawing.drawing_id},
project_id: ${drawing.project_id},
rotation: ${drawing.rotation},
x: ${drawing.x},
y: ${drawing.y},
return `drawing_id: ${drawing.drawing_id},
project_id: ${drawing.project_id},
rotation: ${drawing.rotation},
x: ${drawing.x},
y: ${drawing.y},
z: ${drawing.z}`;
}
}

View File

@ -196,6 +196,38 @@
</div>
</div>
<div class="list-item-inside" *ngIf="version.images.bios_image">
<span>
{{ version.images.bios_image }}
</span>
<div>
<span *ngIf="checkImageFromVersion(version.images.bios_image)"
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon></span
>
<span *ngIf="!checkImageFromVersion(version.images.bios_image)"
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon></span
>
<input
type="file"
class="non-visible"
#fileBios
(change)="importImage($event, version.images.bios_image)"
ng2FileSelect
[uploader]="uploaderImage"
/>
<button class="button" mat-raised-button (click)="fileBios.click()">Import</button>
<button
class="button"
mat-raised-button
(click)="downloadImageFromVersion(version.images.bios_image)"
>
Download
</button>
</div>
</div>
<div class="list-item-inside" *ngIf="version.images.hda_disk_image">
<span>
{{ version.images.hda_disk_image }}

View File

@ -101,7 +101,7 @@
<h6>Additional directories</h6>
<mat-form-field class="form-field">
<textarea matInput type="text" [(ngModel)]="node.properties.extra_volumes"></textarea>
<textarea matInput type="text" [(ngModel)]="additionalDirectories"></textarea>
</mat-form-field>
</mat-tab>

View File

@ -20,13 +20,14 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
name: string;
generalSettingsForm: FormGroup;
consoleTypes: string[] = [];
consoleResolutions: string[] = ['640x480', '800x600', '1024x768', '1280x800', '1280x1024', '1366x768', '1920x1080'];
consoleResolutions: string[] = ['2560x1440', '1920x1080', '1680x1050', '1440x900', '1366x768', '1280x1024', '1280x800', '1024x768', '800x600', '640x480'];
private conf = {
autoFocus: false,
width: '800px',
disableClose: true,
};
dialogRef;
additionalDirectories: string = "";
constructor(
public dialogReference: MatDialogRef<ConfiguratorDialogDockerComponent>,
@ -39,7 +40,7 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
this.generalSettingsForm = this.formBuilder.group({
name: new FormControl('', Validators.required),
adapter: new FormControl('', Validators.required),
startCommand: new FormControl('', Validators.required),
startCommand: new FormControl(''),
consoleHttpPort: new FormControl('', Validators.required),
consoleHttpPath: new FormControl('', Validators.required),
});
@ -50,6 +51,12 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
this.node = node;
this.name = node.name;
this.getConfiguration();
if (this.node.properties.extra_volumes && this.node.properties.extra_volumes.length>0) {
for (let index = 0; index < this.node.properties.extra_volumes.length - 1; index++) {
this.additionalDirectories = this.additionalDirectories + this.node.properties.extra_volumes[index] + "\n";
}
this.additionalDirectories = this.additionalDirectories + this.node.properties.extra_volumes[this.node.properties.extra_volumes.length - 1];
}
});
}
@ -72,7 +79,16 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
}
onSaveClick() {
var extraVolumes = this.additionalDirectories.split("\n").filter(elem => elem != "");
for (const item of extraVolumes) {
console.log(item);
if (!item.startsWith("/")) {
this.toasterService.error(`Wrong format for additional directories.`);
return;
}
}
if (this.generalSettingsForm.valid) {
this.node.properties.extra_volumes = extraVolumes;
this.nodeService.updateNode(this.server, this.node).subscribe(() => {
this.toasterService.success(`Node ${this.node.name} updated.`);
this.onCancelClick();

View File

@ -56,7 +56,7 @@ export class StartCaptureDialogComponent implements OnInit {
const sourcePort = sourceNode.ports[this.link.nodes[0].port_number];
const targetPort = targetNode.ports[this.link.nodes[1].port_number];
this.inputForm.controls['fileName'].setValue(
`${sourceNode.name}_${sourcePort.name}_to_${targetNode.name}_${targetPort.name}`
`${sourceNode.name}_${sourcePort.name}_to_${targetNode.name}_${targetPort.name}`.replace(new RegExp('[^0-9A-Za-z_-]', 'g'), '')
);
}

View File

@ -104,6 +104,13 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
}
public addDrawing(selectedObject: string) {
if ((selectedObject === 'rectangle' && this.drawTools.isRectangleChosen) || (selectedObject === 'ellipse' && this.drawTools.isEllipseChosen) ||
(selectedObject === 'line' && this.drawTools.isLineChosen) || (selectedObject === 'text' && this.drawTools.isTextChosen)) {
document.documentElement.style.cursor = "default";
} else {
document.documentElement.style.cursor = "crosshair";
}
switch (selectedObject) {
case 'rectangle':
this.drawTools.isTextChosen = false;
@ -140,6 +147,8 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
}
public resetDrawToolChoice() {
document.documentElement.style.cursor = "default";
this.drawTools.isRectangleChosen = false;
this.drawTools.isEllipseChosen = false;
this.drawTools.isLineChosen = false;

View File

@ -67,6 +67,14 @@
</div>
</div>
<div>
<button
class="map-settings-button"
matTooltip="Project Map Settings"
matTooltipClass="custom-tooltip"
mat-icon-button
[matMenuTriggerFor]="viewMenu">
<mat-icon>view_module</mat-icon>
</button>
<button
matTooltip="Toggle topology/servers summary"
matTooltipClass="custom-tooltip"
@ -83,39 +91,27 @@
<!-- GNS3 menu -->
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item [matMenuTriggerFor]="projectMenu">
<mat-icon>insert_drive_file</mat-icon>
<span>Project settings</span>
</button>
<button mat-menu-item [routerLink]="['/server', server.id, 'projects']">
<mat-icon>work</mat-icon>
<span>Go to projects</span>
<span>Projects</span>
</button>
<button mat-menu-item [routerLink]="['/servers']">
<mat-icon>developer_board</mat-icon>
<span>Go to servers</span>
</button>
<button mat-menu-item routerLink="/server/{{ server.id }}/preferences">
<mat-icon>settings_applications</mat-icon>
<span>Go to preferences</span>
<span>Servers</span>
</button>
<button mat-menu-item routerLink="/server/{{ server.id }}/systemstatus">
<mat-icon>info</mat-icon>
<span>Go to system status</span>
<mat-icon>data_usage</mat-icon>
<span>System Status</span>
</button>
<button mat-menu-item routerLink="/settings">
<mat-icon>settings</mat-icon>
<span>Go to settings</span>
</button>
<button mat-menu-item (click)="addNewTemplate()">
<mat-icon>control_point</mat-icon>
<span>New template</span>
<span>Settings</span>
</button>
<app-import-appliance [server]="server" [project]="project"></app-import-appliance>
<button mat-menu-item [matMenuTriggerFor]="projectMenu">
<mat-icon>settings</mat-icon>
<span>Project settings</span>
</button>
<button mat-menu-item [matMenuTriggerFor]="viewMenu">
<mat-icon>view_module</mat-icon>
<span>Map settings</span>
</button>
</mat-menu>
<!-- Project Settings sub-menu -->
@ -256,4 +252,4 @@
</div>
</div>
<ng-template #topologySummaryContainer></ng-template>
<ng-template #topologySummaryContainer></ng-template>

View File

@ -73,6 +73,10 @@ g.node:hover {
font-size: 28px !important;
}
.map-settings-button mat-icon {
font-size: 22px !important;
}
.selected {
background: rgba(0, 151, 167, 0.1);

View File

@ -334,7 +334,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.toggleShowTopologySummary(this.mapSettingsService.isTopologySummaryVisible);
this.recentlyOpenedProjectService.setServerId(this.server.id.toString());
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
if (this.project.status === 'opened') {
return new Observable<Project>((observer) => {
@ -423,12 +422,22 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.toasterService.success('Node has been deleted');
});
});
}
selected
.filter((item) => item instanceof MapDrawing)
.forEach((item: MapDrawing) => {
const drawing = this.mapDrawingToDrawing.convert(item);
this.drawingService.delete(this.server, drawing).subscribe((data) => {
this.toasterService.success('Drawing has been deleted');
});
});
}
});
}
onProjectLoad(project: Project) {
this.readonly = this.projectService.isReadOnly(project);
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
const subscription = this.projectService
.nodes(this.server, project.project_id)
@ -476,27 +485,27 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
const onLinkContextMenu = this.linkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.screenY - 60, eventLink.event.screenX);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
});
const onEthernetLinkContextMenu = this.ethernetLinkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.screenY - 60, eventLink.event.screenX);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
});
const onSerialLinkContextMenu = this.serialLinkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.screenY - 60, eventLink.event.screenX);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
});
const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
const node = this.mapNodeToNode.convert(eventNode.node);
this.contextMenu.openMenuForNode(node, eventNode.event.screenY - 60, eventNode.event.screenX);
this.contextMenu.openMenuForNode(node, eventNode.event.pageY, eventNode.event.pageX);
});
const onDrawingContextMenu = this.drawingsWidget.onContextMenu.subscribe((eventDrawing: DrawingContextMenu) => {
const drawing = this.mapDrawingToDrawing.convert(eventDrawing.drawing);
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.screenY - 60, eventDrawing.event.screenX);
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.pageY, eventDrawing.event.pageX);
});
const onLabelContextMenu = this.labelWidget.onContextMenu.subscribe((eventLabel: LabelContextMenu) => {
@ -512,8 +521,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.contextMenu.openMenuForInterfaceLabel(
linkNode,
link,
eventInterfaceLabel.event.screenY - 60,
eventInterfaceLabel.event.screenX
eventInterfaceLabel.event.pageY,
eventInterfaceLabel.event.pageX
);
}
);

View File

@ -2,6 +2,7 @@
<div class="default-header">
<div class="row">
<h1 class="col">Projects</h1>
<button class="col" mat-raised-button (click)="goToSystemStatus()" class="add-button">Go to system status</button>
<button class="col" mat-raised-button (click)="goToPreferences()" class="add-button">Go to preferences</button>
<button class="col" mat-raised-button color="primary" (click)="addBlankProject()" class="add-button">
Add blank project

View File

@ -89,6 +89,12 @@ export class ProjectsComponent implements OnInit {
.catch((error) => this.toasterService.error('Cannot navigate to the preferences'));
}
goToSystemStatus() {
this.router
.navigate(['/server', this.server.id, 'systemstatus'])
.catch((error) => this.toasterService.error('Cannot navigate to the system status'));
}
refresh() {
this.projectService.list(this.server).subscribe(
(projects: Project[]) => {

View File

@ -11,6 +11,7 @@
<div>
<mat-checkbox [(ngModel)]="settings.crash_reports">Send anonymous crash reports</mat-checkbox><br />
<mat-checkbox [(ngModel)]="settings.anonymous_statistics">Send anonymous usage statistics</mat-checkbox><br />
<mat-checkbox [(ngModel)]="integrateLinksLabelsToLinks">Integrate link labels to links</mat-checkbox><br />
<mat-checkbox [(ngModel)]="openConsolesInWidget">Open consoles in the widget instead of in new tabs after clicking start consoles for all nodes</mat-checkbox>
</div>

View File

@ -69,6 +69,7 @@ describe('SettingsComponent', () => {
const settings = {
crash_reports: true,
experimental_features: true,
anonymous_statistics: true,
angular_map: false,
console_command: '',
};

View File

@ -1,5 +1,5 @@
<div class="title-container">
<h1 mat-dialog-title>Add a node</h1>
<h1 mat-dialog-title>Insert New Node</h1>
<button
mat-button
class="top-button"

View File

@ -10,63 +10,95 @@
</button>
<mat-menu #mainMenu="matMenu">
<button mat-menu-item (click)="openDialog()">
<mat-icon>add_to_queue</mat-icon>
<span>Open dialog to configure</span>
</button>
<div class="templateMenu">
<div class="templateMenuHeader">
<button mat-menu-item (click)="openDialog()">
<mat-icon>add_to_queue</mat-icon>
<span>
Insert New Node...
</span>
</button>
<button mat-menu-item (click)="addNewTemplate()">
<mat-icon>library_add</mat-icon>
<span>
New Template...
</span>
</button>
</div>
<mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never">
<input
matInput
placeholder="Search by name"
(keyup)="filterTemplates($event)"
[(ngModel)]="searchText"
[ngModelOptions]="{ standalone: true }"
/>
</mat-form-field>
<mat-form-field (click)="$event.stopPropagation()" class="form-field">
<mat-select
[ngModelOptions]="{ standalone: true }"
placeholder="Filter templates by type"
(selectionChange)="filterTemplates($event)"
[(ngModel)]="selectedType"
>
<mat-option *ngFor="let type of templateTypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="templateFilterBar">
<mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never">
<input
matInput
placeholder="Filter by name"
search="search"
(keyup)="filterTemplates($event)"
[(ngModel)]="searchText"
[ngModelOptions]="{ standalone: true }"
/>
<mat-icon class="searchIcon" matSuffix>search</mat-icon>
</mat-form-field>
<mat-form-field (click)="$event.stopPropagation()" class="form-field">
<mat-select
[ngModelOptions]="{ standalone: true }"
placeholder="Filter by type"
(selectionChange)="filterTemplates($event)"
[(ngModel)]="selectedType"
>
<mat-option *ngFor="let type of templateTypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="menu">
<div class="templateList">
<mat-list-item *ngFor="let template of filteredTemplates; let i = index">
<span *ngIf="i % 4 === 0" class="templateRow">
<span class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
</div>
<div class="templateText">{{ filteredTemplates[i].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 1]" class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 1])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 1].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 2]" class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 2])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 2].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 3])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 3].name }}</div>
</span>
</span>
</mat-list-item>
</div>
</div>
</mat-menu>
<div class="menu">
<div class="templateList">
<mat-list-item *ngFor="let template of filteredTemplates; let i = index">
<span *ngIf="i % 4 === 0" class="templateRow">
<span class="templateIcon">
<div
mwlDraggable
(dragStart)="dragStart($event)"
(dragEnd)="dragEnd($event, filteredTemplates[i])"
class="iconContainer">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
</div>
<div class="templateText">{{ filteredTemplates[i].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 1]" class="templateIcon">
<div
mwlDraggable
(dragStart)="dragStart($event)"
(dragEnd)="dragEnd($event, filteredTemplates[i + 1])"
class="iconContainer">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 1].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 2]" class="templateIcon">
<div
mwlDraggable
(dragStart)="dragStart($event)"
(dragEnd)="dragEnd($event, filteredTemplates[i + 2])"
class="iconContainer">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 2].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon">
<div
mwlDraggable
(dragStart)="dragStart($event)"
(dragEnd)="dragEnd($event, filteredTemplates[i + 3])"
class="iconContainer">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 3].name }}</div>
</span>
</span>
</mat-list-item>
</div>
</div>
</div>
</mat-menu>

View File

@ -1,6 +1,7 @@
::ng-deep .mat-menu-panel {
max-width: 400px;
max-height: 500px;
max-height: 640px;
border-radius: 8px;
}
.menu {
@ -8,6 +9,26 @@
overflow-y: scroll;
scrollbar-color: darkgrey #263238;
scrollbar-width: thin;
font-size: 12px;
}
.templateMenuHeader {
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.templateFilterBar {
padding: 10px 2%;
background-color: rgba(0,0,0,0.2);
margin-bottom: 10px;
}
.templateFilterBar > .form-field {
font-size: 12px;
}
.templateFilterBar .searchIcon {
position: relative;
top: 5px;
}
::-webkit-scrollbar {
@ -24,20 +45,22 @@
}
.form-field {
width: 90%;
margin-left: 5%;
margin-right: 5%;
width: 44%;
margin-left: 3%;
margin-right: 3%;
}
.image {
width: 65px;
height: 65px;
display: inline-block;
width: 55px;
height: 55px;
filter: invert(0);
--webkit-filter: invert(0) !important;
}
.templateList {
width: 100%;
padding: 10px;
}
.templateRow {
@ -50,6 +73,23 @@
}
.templateIcon {
width: 80px !important;
padding: 10px;
width: 90px !important;
padding: 2px 5px;
text-align: center;
margin: 3px;
}
.templateIcon > .iconContainer {
display: inline-flex;
align-items: center;
justify-content: center;
width: 70px;
height: 70px;
margin-bottom: 10px;
border-radius: 50%;
cursor: move;
}
.templateIcon > .iconContainer:hover {
background-color: rgba(237, 246, 231, 0.08);
}

View File

@ -8,6 +8,7 @@ import { MapScaleService } from '../../services/mapScale.service';
import { SymbolService } from '../../services/symbol.service';
import { TemplateService } from '../../services/template.service';
import { NodeAddedEvent, TemplateListDialogComponent } from './template-list-dialog/template-list-dialog.component';
import { NewTemplateDialogComponent } from '../project-map/new-template-dialog/new-template-dialog.component';
@Component({
selector: 'app-template',
@ -127,6 +128,19 @@ export class TemplateComponent implements OnInit, OnDestroy {
});
}
addNewTemplate() {
const dialogRef = this.dialog.open(NewTemplateDialogComponent, {
width: '1000px',
maxHeight: '700px',
autoFocus: false,
disableClose: true,
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.project = this.project;
}
getImageSourceForTemplate(template: Template) {
return `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${template.symbol}/raw`;
}
@ -134,4 +148,4 @@ export class TemplateComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
}

View File

@ -78,7 +78,28 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
this.computes = computes;
});
this.style = { top: '60px', right: '0px', width: '320px', height: '400px' };
this.revertPosition();
}
revertPosition(){
let leftPosition = localStorage.getItem('leftPosition');
let rightPosition = localStorage.getItem('rightPosition');
let topPosition = localStorage.getItem('topPosition');
let widthOfWidget = localStorage.getItem('widthOfWidget');
let heightOfWidget = localStorage.getItem('heightOfWidget');
if (!topPosition) {
this.style = { top: '60px', right: '0px', width: '320px', height: '400px' };
} else {
this.style = {
position: 'fixed',
left: `${+leftPosition}px`,
right: `${+rightPosition}px`,
top: `${+topPosition}px`,
width: `${+widthOfWidget}px`,
height: `${+heightOfWidget}px`,
};
}
}
toggleDragging(value: boolean) {
@ -101,6 +122,12 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
width: `${width}px`,
height: `${height}px`,
};
localStorage.setItem('leftPosition', left.toString());
localStorage.setItem('topPosition', top.toString());
localStorage.setItem('widthOfWidget', width.toString());
localStorage.setItem('heightOfWidget', height.toString());
} else {
let right: number = Number(this.style['right'].split('px')[0]) - x;
this.style = {
@ -110,6 +137,11 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
width: `${width}px`,
height: `${height}px`,
};
localStorage.setItem('rightPosition', right.toString());
localStorage.setItem('topPosition', top.toString());
localStorage.setItem('widthOfWidget', width.toString());
localStorage.setItem('heightOfWidget', height.toString());
}
}
@ -140,6 +172,7 @@ export class TopologySummaryComponent implements OnInit, OnDestroy {
toggleTopologyVisibility(value: boolean) {
this.isTopologyVisible = value;
this.revertPosition();
}
compareAsc(first: Node, second: Node) {

View File

@ -19,7 +19,7 @@ export class DockerConfigurationService {
}
getConsoleResolutions() {
let consoleResolutions = ['1920x1080', '1366x768', '1280x1024', '1280x800', '1024x768', '800x600', '640x480'];
let consoleResolutions = ['2560x1440', '1920x1080', '1680x1050', '1440x900', '1366x768', '1280x1024', '1280x800', '1024x768', '800x600', '640x480'];
return consoleResolutions;
}

View File

@ -1,14 +1,17 @@
import { Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { environment } from '../../environments/environment';
import { SettingsService } from './settings.service';
declare var gtag: Function;
@Injectable()
export class GoogleAnalyticsService {
constructor(router: Router) {
private settingsService: SettingsService;
constructor(router: Router, settingsService: SettingsService) {
if (!environment.production) return;
router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
if (settingsService.getStatisticsSettings() && event instanceof NavigationEnd) {
gtag('set', 'page', event.url);
gtag('send', 'pageview');
}

View File

@ -78,13 +78,15 @@ export class NodeConsoleService {
let nodesToStart = 'Please start the following nodes if you want to open consoles for them: ';
let nodesToStartCounter = 0;
nodes.forEach((n) => {
if (n.status === 'started') {
this.mapSettingsService.logConsoleSubject.next(true);
// this timeout is required due to xterm.js implementation
setTimeout(() => { this.openConsoleForNode(n); }, 500);
} else {
nodesToStartCounter++;
nodesToStart += n.name + ' '
if (n.console_type !== "none") {
if (n.status === 'started') {
this.mapSettingsService.logConsoleSubject.next(true);
// this timeout is required due to xterm.js implementation
setTimeout(() => { this.openConsoleForNode(n); }, 500);
} else {
nodesToStartCounter++;
nodesToStart += n.name + ' '
}
}
});
if (nodesToStartCounter > 0) {
@ -93,16 +95,19 @@ export class NodeConsoleService {
}
openConsolesForAllNodesInNewTabs(nodes: Node[]) {
let nodesToStart = 'Please start the following nodes if you want to open consoles for them: ';
let nodesToStart = 'Please start the following nodes if you want to open consoles in tabs for them: ';
let nodesToStartCounter = 0;
nodes.forEach((n) => {
if (n.status === 'started') {
let url = this.router.url.split('/');
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${n.node_id}`;
window.open(urlString);
} else {
nodesToStartCounter++;
nodesToStart += n.name + ' '
// opening a console in tab is only supported for telnet type
if (n.console_type === "telnet") {
if (n.status === 'started') {
let url = this.router.url.split('/');
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${n.node_id}`;
window.open(urlString);
} else {
nodesToStartCounter++;
nodesToStart += n.name + ' '
}
}
});
if (nodesToStartCounter > 0) {

View File

@ -4,6 +4,7 @@ import { BehaviorSubject } from 'rxjs';
export interface Settings {
crash_reports: boolean;
console_command: string;
anonymous_statistics: boolean;
}
@Injectable({
@ -13,10 +14,12 @@ export class SettingsService {
private settings: Settings = {
crash_reports: true,
console_command: undefined,
anonymous_statistics: true
};
private readonly reportsSettings: string = 'crash_reports';
private readonly consoleSettings: string = 'console_command';
private readonly statisticsSettings: string = 'statistics_command';
constructor() {
if (this.getItem(this.reportsSettings))
@ -24,6 +27,9 @@ export class SettingsService {
if (this.getItem(this.consoleSettings))
this.settings.console_command = this.getItem(this.consoleSettings);
if (this.getItem(this.statisticsSettings))
this.settings.anonymous_statistics = this.getItem(this.statisticsSettings) === 'true' ? true : false;
}
setReportsSettings(value: boolean) {
@ -36,10 +42,24 @@ export class SettingsService {
}
}
setStatisticsSettings(value: boolean) {
this.settings.anonymous_statistics = value;
this.removeItem(this.statisticsSettings);
if (value) {
this.setItem(this.statisticsSettings, 'true');
} else {
this.setItem(this.statisticsSettings, 'false');
}
}
getReportsSettings() {
return this.getItem(this.reportsSettings) === 'true' ? true : false;
}
getStatisticsSettings() {
return this.getItem(this.statisticsSettings) === 'true' ? true : false;
}
setConsoleSettings(value: string) {
this.settings.console_command = value;
this.removeItem(this.consoleSettings);
@ -70,5 +90,6 @@ export class SettingsService {
this.settings = settings;
this.setConsoleSettings(settings.console_command);
this.setReportsSettings(settings.crash_reports);
this.setStatisticsSettings(settings.anonymous_statistics);
}
}

View File

@ -5,7 +5,9 @@ import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@ang
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
teardown: { destroyAfterEach: false }
});
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.

View File

@ -1,4 +1,4 @@
@import '~@angular/material/theming';
@import '@angular/material/theming';
@import '~material-design-icons/iconfont/material-icons.css';
@import '~typeface-roboto/index.css';
@include mat-core();

7648
yarn.lock

File diff suppressed because it is too large Load Diff