Compare commits

...

11 commits

Author SHA1 Message Date
sepehr
539e8e95fe deps: update dependencies for enhanced features
- Update to support new UI components
- Add dependencies for advanced features
- Update Angular and related packages
- Update version to 1.2.0
2025-10-16 17:36:18 +03:00
sepehr
e2b412f8f1 assets: add comprehensive network device icon library
- Add SVG icons for network topology visualization
- Support for various network device types
- Icons for routers, switches, firewalls, servers
2025-10-16 17:34:36 +03:00
sepehr
433dcff5db feat: settings redesign and core improvements
- Redesign settings interface with improved UX
- Enhanced dashboard functionality
- Improved device detail views
- Updated core data providers
- Minor snippet management improvements
2025-10-16 17:34:28 +03:00
sepehr
07808822f7 feat: redesign backups management interface
- Complete UI/UX redesign of backups module
- Improved backup viewing and management
- Enhanced backup operations interface
- Better backup history and restoration
2025-10-16 17:34:19 +03:00
sepehr
069e53cec6 feat: redesign user management interface
- Complete UI/UX overhaul of user manager
- Improved user permissions handling
- Enhanced user role management
- Better user filtering and search capabilities
2025-10-16 17:34:13 +03:00
sepehr
746711fa24 feat: redesign password vault interface for pro users
- Complete UI/UX redesign of vault module
- Enhanced security interface
- Pro feature improvements
- Better password management workflow
2025-10-16 17:34:05 +03:00
sepehr
cdc2e0cabf feat: redesign sync and cloner interface for pro users
- Complete UI/UX overhaul of cloner module
- Enhanced configuration synchronization
- Pro feature improvements
- Better cloning workflow and interface
2025-10-16 17:33:57 +03:00
sepehr
2f68c49936 feat: redesign user tasks with advanced scheduling
- Complete UI/UX redesign of user tasks interface
- Add cron selector with examples
- Improved task scheduling interface
- Enhanced task management and monitoring
2025-10-16 17:33:47 +03:00
sepehr
b20a3d7826 feat: add network topology maps for pro users
- Add comprehensive network visualization module
- Pro feature with advanced topology mapping
- Interactive network device mapping
- Integration with navigation and routing
2025-10-16 17:33:27 +03:00
sepehr
996c189076 feat: redesign device group management interface
- Complete UI/UX redesign of device groups table
- Enhanced member addition interface
- Add user management directly from device groups
- Add firmware group actions (update and upgrade)
- Improved bulk operations for device groups
2025-10-16 17:33:19 +03:00
sepehr
5822fb5d1d feat: major device management enhancements
- Add direct web access button for device WebFig
- Add web proxy-based access with auto-login via MikroWizard
- Fix updatable/upgradable filters and add more filter options
- Redesign devices table UI with improved UX
- Add bulk scan feature with CSV import capability
- Add parallel scanning functionality
- Add upgrade firmware feature
- Add scan history button with enhanced report viewing
2025-10-16 17:33:07 +03:00
114 changed files with 12262 additions and 1343 deletions

332
package-lock.json generated
View file

@ -1,13 +1,13 @@
{ {
"name": "coreui-free-angular-admin-template", "name": "MikroWizard",
"version": "4.5.27", "version": "1.0.7",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "coreui-free-angular-admin-template", "name": "MikroWizard",
"version": "4.5.27", "version": "1.0.7",
"license": "MIT", "license": "AGPL",
"dependencies": { "dependencies": {
"@angular/animations": "^17.3.5", "@angular/animations": "^17.3.5",
"@angular/cdk": "^16.2.9", "@angular/cdk": "^16.2.9",
@ -28,11 +28,7 @@
"@coreui/icons-angular": "~4.5.27", "@coreui/icons-angular": "~4.5.27",
"@coreui/utils": "^2.0.2", "@coreui/utils": "^2.0.2",
"@easyfonts/font-awesome-v6": "^6.0.6", "@easyfonts/font-awesome-v6": "^6.0.6",
"@fortawesome/angular-fontawesome": "^0.13.0", "@fortawesome/angular-fontawesome": "^0.15.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@generic-ui/fabric": "^0.19.0", "@generic-ui/fabric": "^0.19.0",
"@generic-ui/hermes": "^0.19.0", "@generic-ui/hermes": "^0.19.0",
"@generic-ui/ngx-grid": "^0.19.0", "@generic-ui/ngx-grid": "^0.19.0",
@ -40,19 +36,25 @@
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-jalali": "^3.6.0-0", "date-fns-jalali": "^3.6.0-0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",
"diff-match-patch-ts": "^0.6.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"install": "^0.13.0", "install": "^0.13.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mat-progress-buttons": "^9.3.1", "mat-progress-buttons": "^9.3.1",
"ngx-cron-editor": "^0.8.1", "ngx-cron-editor": "^0.8.1",
"ngx-date-fns": "^11.0.0", "ngx-date-fns": "^11.0.0",
"ngx-diff": "^9.0.0",
"ngx-highlight-js": "^18.0.0",
"ngx-highlightjs": "^12.0.0", "ngx-highlightjs": "^12.0.0",
"ngx-infinite-scroll": "^18.0.0",
"ngx-mat-select-search": "^7.0.6", "ngx-mat-select-search": "^7.0.6",
"ngx-material-date-fns-adapter": "^18.0.0", "ngx-material-date-fns-adapter": "^18.0.0",
"ngx-scrollbar": "^13.0.3", "ngx-scrollbar": "^13.0.3",
"ngx-super-select": "^3.17.0", "ngx-super-select": "^3.17.0",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"vis-data": "^7.1.9",
"vis-network": "^9.1.9",
"zone.js": "~0.14.4" "zone.js": "~0.14.4"
}, },
"devDependencies": { "devDependencies": {
@ -2603,6 +2605,18 @@
"resolved": "https://registry.npmjs.org/@easyfonts/font-awesome-v6/-/font-awesome-v6-6.0.6.tgz", "resolved": "https://registry.npmjs.org/@easyfonts/font-awesome-v6/-/font-awesome-v6-6.0.6.tgz",
"integrity": "sha512-I3mV9KQuD6jJgfX3bU3mdv/RtYSd+X/Y9Yo2RrAmIfQJeDVCQdTPXBgmodMtGxfAU8lVHKFwr/fh9zexavXdNQ==" "integrity": "sha512-I3mV9KQuD6jJgfX3bU3mdv/RtYSd+X/Y9Yo2RrAmIfQJeDVCQdTPXBgmodMtGxfAU8lVHKFwr/fh9zexavXdNQ=="
}, },
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"dependencies": {
"@types/hammerjs": "^2.0.36"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.20.1", "version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz",
@ -2972,69 +2986,31 @@
} }
}, },
"node_modules/@fortawesome/angular-fontawesome": { "node_modules/@fortawesome/angular-fontawesome": {
"version": "0.13.0", "version": "0.15.0",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.15.0.tgz",
"integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==", "integrity": "sha512-oxmJDYGNSym5ycFR0LX4ZOPAU+wWmMAznYpkm5DNAtWWkhMLcrZl15eZQmVIEE+qruQ7JiVrg3tpo8bEkFlDgw==",
"dependencies": { "dependencies": {
"tslib": "^2.4.1" "@fortawesome/fontawesome-svg-core": "^6.5.2",
"tslib": "^2.6.2"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/core": "^16.0.0", "@angular/core": "^18.0.0"
"@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0"
} }
}, },
"node_modules/@fortawesome/fontawesome-common-types": { "node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.5.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
"integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
"hasInstallScript": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@fortawesome/fontawesome-svg-core": { "node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.5.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
"integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
"hasInstallScript": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2" "@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz",
"integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz",
"integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
"integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.5.2"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -4872,6 +4848,12 @@
"@types/send": "*" "@types/send": "*"
} }
}, },
"node_modules/@types/hammerjs": {
"version": "2.0.46",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
"peer": true
},
"node_modules/@types/http-errors": { "node_modules/@types/http-errors": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@ -6111,6 +6093,15 @@
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
"dev": true "dev": true
}, },
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/compressible": { "node_modules/compressible": {
"version": "2.0.18", "version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@ -6735,6 +6726,11 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true "dev": true
}, },
"node_modules/diff-match-patch-ts": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/diff-match-patch-ts/-/diff-match-patch-ts-0.6.0.tgz",
"integrity": "sha512-U0uPIJ+wJqgaBoVw2MFSFpGIk7q3mJJ+/sehbxDZFv4Gx6a1GOmrsSLmxVDDrGtRL4Q9de084aa5lVpCHn+eUw=="
},
"node_modules/dir-glob": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -9021,6 +9017,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"node_modules/kind-of": { "node_modules/kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -9846,6 +9848,28 @@
"date-fns": ">=3" "date-fns": ">=3"
} }
}, },
"node_modules/ngx-diff": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-diff/-/ngx-diff-9.0.0.tgz",
"integrity": "sha512-eoF3D/9KjNqdnscIofkS81JWj87Ffq3eI3Gqim0SG/N/m0YWtOjximFxT1JI5bS8Gi1PVgoEQntJIZb/zaNULw==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": ">=18.0.0",
"@angular/core": ">=18.0.0",
"diff-match-patch-ts": ">=0.5.0"
}
},
"node_modules/ngx-highlight-js": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlight-js/-/ngx-highlight-js-18.0.0.tgz",
"integrity": "sha512-r/LSijb5Ju95ZGm89+4e905kFWkDt1XdGCg2prR1wleTE77uROscjZMht0D40WSRbxmOg+J2zw9OOZvsWSLeeg==",
"dependencies": {
"highlight.js": "^11.9.0",
"tslib": "^2.3.0"
}
},
"node_modules/ngx-highlightjs": { "node_modules/ngx-highlightjs": {
"version": "12.0.0", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-12.0.0.tgz", "resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-12.0.0.tgz",
@ -9859,6 +9883,18 @@
"@angular/core": ">=17.0.0" "@angular/core": ">=17.0.0"
} }
}, },
"node_modules/ngx-infinite-scroll": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-18.0.0.tgz",
"integrity": "sha512-D183TDwpsd9Zl56UmItsl3RzHdN25srAISfg6lc3A8mEKkEgOq0s7ZzRAYcx8DHsAkMgtZqjIPEvMifD3DOB/g==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=18.0.0 <19.0.0",
"@angular/core": ">=18.0.0 <19.0.0"
}
},
"node_modules/ngx-mat-select-search": { "node_modules/ngx-mat-select-search": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.6.tgz", "resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.6.tgz",
@ -12746,7 +12782,6 @@
"version": "8.3.2", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
@ -12782,6 +12817,53 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/vis-data": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.9.tgz",
"integrity": "sha512-COQsxlVrmcRIbZMMTYwD+C2bxYCFDNQ2EHESklPiInbD/Pk3JZ6qNL84Bp9wWjYjAzXfSlsNaFtRk+hO9yBPWA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"vis-util": "^5.0.1"
}
},
"node_modules/vis-network": {
"version": "9.1.9",
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.9.tgz",
"integrity": "sha512-Ft+hLBVyiLstVYSb69Q1OIQeh3FeUxHJn0WdFcq+BFPqs+Vq1ibMi2sb//cxgq1CP7PH4yOXnHxEH/B2VzpZYA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0",
"keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0",
"uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"vis-data": "^6.3.0 || ^7.0.0",
"vis-util": "^5.0.1"
}
},
"node_modules/vis-util": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.7.tgz",
"integrity": "sha512-E3L03G3+trvc/X4LXvBfih3YIHcKS2WrP0XTdZefr6W6Qi/2nNCqZfe4JFfJU6DcQLm6Gxqj2Pfl+02859oL5A==",
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/visjs"
},
"peerDependencies": {
"@egjs/hammerjs": "^2.0.0",
"component-emitter": "^1.3.0 || ^2.0.0"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "5.1.7", "version": "5.1.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz",
@ -15497,6 +15579,15 @@
"resolved": "https://registry.npmjs.org/@easyfonts/font-awesome-v6/-/font-awesome-v6-6.0.6.tgz", "resolved": "https://registry.npmjs.org/@easyfonts/font-awesome-v6/-/font-awesome-v6-6.0.6.tgz",
"integrity": "sha512-I3mV9KQuD6jJgfX3bU3mdv/RtYSd+X/Y9Yo2RrAmIfQJeDVCQdTPXBgmodMtGxfAU8lVHKFwr/fh9zexavXdNQ==" "integrity": "sha512-I3mV9KQuD6jJgfX3bU3mdv/RtYSd+X/Y9Yo2RrAmIfQJeDVCQdTPXBgmodMtGxfAU8lVHKFwr/fh9zexavXdNQ=="
}, },
"@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"peer": true,
"requires": {
"@types/hammerjs": "^2.0.36"
}
},
"@esbuild/aix-ppc64": { "@esbuild/aix-ppc64": {
"version": "0.20.1", "version": "0.20.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz",
@ -15659,48 +15750,25 @@
"optional": true "optional": true
}, },
"@fortawesome/angular-fontawesome": { "@fortawesome/angular-fontawesome": {
"version": "0.13.0", "version": "0.15.0",
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.15.0.tgz",
"integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==", "integrity": "sha512-oxmJDYGNSym5ycFR0LX4ZOPAU+wWmMAznYpkm5DNAtWWkhMLcrZl15eZQmVIEE+qruQ7JiVrg3tpo8bEkFlDgw==",
"requires": { "requires": {
"tslib": "^2.4.1" "@fortawesome/fontawesome-svg-core": "^6.5.2",
"tslib": "^2.6.2"
} }
}, },
"@fortawesome/fontawesome-common-types": { "@fortawesome/fontawesome-common-types": {
"version": "6.5.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
"integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==" "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="
}, },
"@fortawesome/fontawesome-svg-core": { "@fortawesome/fontawesome-svg-core": {
"version": "6.5.2", "version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
"integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
"requires": { "requires": {
"@fortawesome/fontawesome-common-types": "6.5.2" "@fortawesome/fontawesome-common-types": "6.6.0"
}
},
"@fortawesome/free-brands-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz",
"integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.5.2"
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz",
"integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.5.2"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
"integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.5.2"
} }
}, },
"@generic-ui/fabric": { "@generic-ui/fabric": {
@ -17244,6 +17312,12 @@
"@types/send": "*" "@types/send": "*"
} }
}, },
"@types/hammerjs": {
"version": "2.0.46",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
"peer": true
},
"@types/http-errors": { "@types/http-errors": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@ -18210,6 +18284,12 @@
"integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
"dev": true "dev": true
}, },
"component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
"peer": true
},
"compressible": { "compressible": {
"version": "2.0.18", "version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@ -18673,6 +18753,11 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true "dev": true
}, },
"diff-match-patch-ts": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/diff-match-patch-ts/-/diff-match-patch-ts-0.6.0.tgz",
"integrity": "sha512-U0uPIJ+wJqgaBoVw2MFSFpGIk7q3mJJ+/sehbxDZFv4Gx6a1GOmrsSLmxVDDrGtRL4Q9de084aa5lVpCHn+eUw=="
},
"dir-glob": { "dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -20386,6 +20471,12 @@
"source-map-support": "^0.5.5" "source-map-support": "^0.5.5"
} }
}, },
"keycharm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz",
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==",
"peer": true
},
"kind-of": { "kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -20992,6 +21083,23 @@
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"ngx-diff": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-diff/-/ngx-diff-9.0.0.tgz",
"integrity": "sha512-eoF3D/9KjNqdnscIofkS81JWj87Ffq3eI3Gqim0SG/N/m0YWtOjximFxT1JI5bS8Gi1PVgoEQntJIZb/zaNULw==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-highlight-js": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlight-js/-/ngx-highlight-js-18.0.0.tgz",
"integrity": "sha512-r/LSijb5Ju95ZGm89+4e905kFWkDt1XdGCg2prR1wleTE77uROscjZMht0D40WSRbxmOg+J2zw9OOZvsWSLeeg==",
"requires": {
"highlight.js": "^11.9.0",
"tslib": "^2.3.0"
}
},
"ngx-highlightjs": { "ngx-highlightjs": {
"version": "12.0.0", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-12.0.0.tgz", "resolved": "https://registry.npmjs.org/ngx-highlightjs/-/ngx-highlightjs-12.0.0.tgz",
@ -21001,6 +21109,14 @@
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"ngx-infinite-scroll": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-18.0.0.tgz",
"integrity": "sha512-D183TDwpsd9Zl56UmItsl3RzHdN25srAISfg6lc3A8mEKkEgOq0s7ZzRAYcx8DHsAkMgtZqjIPEvMifD3DOB/g==",
"requires": {
"tslib": "^2.3.0"
}
},
"ngx-mat-select-search": { "ngx-mat-select-search": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.6.tgz", "resolved": "https://registry.npmjs.org/ngx-mat-select-search/-/ngx-mat-select-search-7.0.6.tgz",
@ -23083,8 +23199,7 @@
"uuid": { "uuid": {
"version": "8.3.2", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"dev": true
}, },
"validate-npm-package-license": { "validate-npm-package-license": {
"version": "3.0.4", "version": "3.0.4",
@ -23111,6 +23226,25 @@
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true "dev": true
}, },
"vis-data": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/vis-data/-/vis-data-7.1.9.tgz",
"integrity": "sha512-COQsxlVrmcRIbZMMTYwD+C2bxYCFDNQ2EHESklPiInbD/Pk3JZ6qNL84Bp9wWjYjAzXfSlsNaFtRk+hO9yBPWA==",
"requires": {}
},
"vis-network": {
"version": "9.1.9",
"resolved": "https://registry.npmjs.org/vis-network/-/vis-network-9.1.9.tgz",
"integrity": "sha512-Ft+hLBVyiLstVYSb69Q1OIQeh3FeUxHJn0WdFcq+BFPqs+Vq1ibMi2sb//cxgq1CP7PH4yOXnHxEH/B2VzpZYA==",
"requires": {}
},
"vis-util": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/vis-util/-/vis-util-5.0.7.tgz",
"integrity": "sha512-E3L03G3+trvc/X4LXvBfih3YIHcKS2WrP0XTdZefr6W6Qi/2nNCqZfe4JFfJU6DcQLm6Gxqj2Pfl+02859oL5A==",
"peer": true,
"requires": {}
},
"vite": { "vite": {
"version": "5.1.7", "version": "5.1.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "MikroWizard", "name": "MikroWizard",
"version": "1.0.1", "version": "1.2.0",
"copyright": "MikroWizard mikrowizard.com", "copyright": "MikroWizard mikrowizard.com",
"license": "AGPL", "license": "AGPL",
"author": "MikroWizard Team (https://github.com/MikroWizard)", "author": "MikroWizard Team (https://github.com/MikroWizard)",
@ -63,6 +63,8 @@
"ngx-super-select": "^3.17.0", "ngx-super-select": "^3.17.0",
"rxjs": "~7.8.1", "rxjs": "~7.8.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"vis-data": "^7.1.9",
"vis-network": "^9.1.9",
"zone.js": "~0.14.4" "zone.js": "~0.14.4"
}, },
"devDependencies": { "devDependencies": {

View file

@ -49,6 +49,11 @@ const routes: Routes = [
loadChildren: () => loadChildren: () =>
import('./views/devices_group/devgroup.module').then((m) => m.DevicesGroupModule) import('./views/devices_group/devgroup.module').then((m) => m.DevicesGroupModule)
}, },
{
path: 'maps',
loadChildren: () =>
import('./views/maps/maps.module').then((m) => m.MapsModule)
},
{ {
path: 'authlog', path: 'authlog',
loadChildren: () => loadChildren: () =>

View file

@ -28,6 +28,12 @@ export const navItems: INavData[] = [
// linkProps: { fragment: 'someAnchor' }, // linkProps: { fragment: 'someAnchor' },
icon: 'fa-solid fa-layer-group' icon: 'fa-solid fa-layer-group'
}, },
{
name: 'Network Maps',
url: '/maps',
icon:'fa-solid fa-map',
attributes: { 'pro':true }
},
// { // {
// name: 'Tools', // name: 'Tools',
// url: '/login', // url: '/login',

View file

@ -278,13 +278,14 @@ export class dataProvider {
return this.MikroWizardRPC.sendJsonRequest("/api/devgroup/update_save_group", data); return this.MikroWizardRPC.sendJsonRequest("/api/devgroup/update_save_group", data);
} }
get_snippets(name:string,desc:string,content:string,page:number=0,size:number=1000){ get_snippets(name:string,desc:string,content:string,page:number=0,size:number=1000,limit:any=false){
var data={ var data={
'name':name, 'name':name,
'description':desc, 'description':desc,
'content':content, 'content':content,
'page':page, 'page':page,
'size':size 'size':size,
'limit':limit
} }
return this.MikroWizardRPC.sendJsonRequest("/api/snippet/list", data); return this.MikroWizardRPC.sendJsonRequest("/api/snippet/list", data);
} }
@ -601,6 +602,34 @@ export class dataProvider {
} }
return this.MikroWizardRPC.sendJsonRequest("/api/dhcp-history/get", data); return this.MikroWizardRPC.sendJsonRequest("/api/dhcp-history/get", data);
} }
getNetworkMap(){
return this.MikroWizardRPC.sendJsonRequest("/api/networkmap/get", {});
}
bulk_add_devices(devices: any[]){
var data = {
'devices': devices
}
return this.MikroWizardRPC.sendJsonRequest("/api/dev/bulk_add", data);
}
bulk_add_status(taskId: string){
var data = {
'taskId': taskId
}
return this.MikroWizardRPC.sendJsonRequest("/api/dev/bulk_add_status", data);
}
group_firmware_action(groupId: number, action: string){
var data = {
'groupId': groupId,
'action': action
}
return this.MikroWizardRPC.sendJsonRequest("/api/devgroup/firmware_action", data);
}
//// ////
//// End api funcs //// End api funcs
//// ////

View file

@ -2,58 +2,97 @@
<c-col xs> <c-col xs>
<c-card class="mb-4"> <c-card class="mb-4">
<c-card-header> <c-card-header>
<c-row> <c-row class="align-items-center">
<c-col xs [lg]="8"> <c-col xs [lg]="6">
Backups <c-badge color="warning" *ngIf="devid!=0">Filtered Result For Device ID {{devid}}</c-badge> <div class="d-flex align-items-center">
<h5 class="mb-0 me-3"><i class="fa-solid fa-database me-2"></i>Backups</h5>
<c-badge color="warning" *ngIf="devid!=0" class="fs-6">
<i class="fa-solid fa-filter me-1"></i>Device ID {{devid}}
</c-badge>
</div>
</c-col> </c-col>
<c-col xs [lg]="3"> <c-col xs [lg]="6">
<c-row> <div class="d-flex justify-content-end align-items-center gap-2">
<c-col> <!-- Compare Selection Panel -->
<ng-container *ngIf="compareitems.length>0"> <div *ngIf="compareitems.length > 0" class="compare-panel me-2">
<div> <div class="d-flex align-items-center gap-2">
<c-badge color="dark" style="font-size: 0.7rem;" <c-badge color="info" class="fs-6">
*ngFor="let item of compareitems;index as i">{{item.id}}:{{item.devname}} {{item.createdC}} <span <i class="fa-solid fa-code-compare me-1"></i>{{compareitems.length}} Selected
style="cursor: pointer;" (click)="delete_compare(i)">X</span></c-badge> </c-badge>
<div class="selected-items d-flex gap-1">
<c-badge color="secondary" *ngFor="let item of compareitems; index as i"
class="selected-item d-flex align-items-center">
<span class="me-1">{{item.devname}}</span>
<i class="fa-solid fa-times cursor-pointer" (click)="delete_compare(i)"
title="Remove from comparison"></i>
</c-badge>
</div> </div>
</ng-container> <button *ngIf="compareitems.length > 1" (click)="start_compare()"
</c-col> cButton color="success" size="sm">
<c-col style="padding: 0;"> <i class="fa-solid fa-code-compare me-1"></i>Compare
<button *ngIf="compareitems.length>1" (click)="start_compare()" cButton class="me-1" </button>
color="primary">Compare</button> <button (click)="clearAllCompare()" cButton color="secondary" size="sm" variant="outline">
</c-col> <i class="fa-solid fa-trash me-1"></i>Clear
</c-row> </button>
</c-col> </div>
<c-col styyle="border-left: 1px solid #ccc;" xs [lg]="1"> </div>
<button (click)="toggleCollapse()" cButton class="me-1" color="primary"><i <!-- Filter Toggle -->
class="fa-solid fa-filter mr-1"></i>Filter</button> <button (click)="toggleCollapse()" cButton color="primary" variant="outline">
<i class="fa-solid fa-filter me-1"></i>Filters
<i class="fa-solid" [class]="filters_visible ? 'fa-chevron-up' : 'fa-chevron-down'"
style="margin-left: 0.5rem; font-size: 0.8rem;"></i>
</button>
</div>
</c-col> </c-col>
</c-row> </c-row>
</c-card-header> </c-card-header>
<c-card-body> <c-card-body>
<c-row> <!-- Enhanced Filter Panel -->
<div [visible]="filters_visible" cCollapse> <div [visible]="filters_visible" cCollapse class="mb-3">
<c-col xs [lg]="12" class="example-form"> <c-card class="border-0 bg-light">
<mat-form-field> <c-card-body class="py-3">
<mat-label>Start date</mat-label> <c-row class="g-3 align-items-end">
<input matInput [matDatepicker]="picker1" (dateChange)="reinitgrid('start',$event)" <c-col xs="12" md="4">
[(ngModel)]="filters['start_time']" /> <div class="filter-group">
<mat-datepicker-toggle matIconSuffix [for]="picker1"></mat-datepicker-toggle> <label class="form-label fw-semibold mb-2">
<mat-datepicker #picker1></mat-datepicker> <i class="fa-solid fa-calendar-days me-1 text-primary"></i>Start Date
</mat-form-field> </label>
<mat-form-field> <mat-form-field appearance="outline" class="w-100">
<mat-label>End date</mat-label> <input matInput [matDatepicker]="picker1" (dateChange)="reinitgrid('start',$event)"
<input matInput [matDatepicker]="picker2" (dateChange)="reinitgrid('end',$event)" [(ngModel)]="filters['start_time']" placeholder="Select start date" />
[(ngModel)]="filters['end_time']" /> <mat-datepicker-toggle matIconSuffix [for]="picker1"></mat-datepicker-toggle>
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle> <mat-datepicker #picker1></mat-datepicker>
<mat-datepicker #picker2></mat-datepicker> </mat-form-field>
</mat-form-field> </div>
<mat-form-field *ngIf="ispro"> </c-col>
<mat-label>Config search</mat-label> <c-col xs="12" md="4">
<input (ngModelChange)="reinitgrid('search',$event)" [(ngModel)]="filters['search']" matInput> <div class="filter-group">
</mat-form-field> <label class="form-label fw-semibold mb-2">
</c-col> <i class="fa-solid fa-calendar-days me-1 text-primary"></i>End Date
</div> </label>
</c-row> <mat-form-field appearance="outline" class="w-100">
<input matInput [matDatepicker]="picker2" (dateChange)="reinitgrid('end',$event)"
[(ngModel)]="filters['end_time']" placeholder="Select end date" />
<mat-datepicker-toggle matIconSuffix [for]="picker2"></mat-datepicker-toggle>
<mat-datepicker #picker2></mat-datepicker>
</mat-form-field>
</div>
</c-col>
<c-col xs="12" md="4" *ngIf="ispro">
<div class="filter-group">
<label class="form-label fw-semibold mb-2">
<i class="fa-solid fa-magnifying-glass me-1 text-primary"></i>Config Search
</label>
<mat-form-field appearance="outline" class="w-100">
<input (ngModelChange)="reinitgrid('search',$event)" [(ngModel)]="filters['search']"
matInput placeholder="Search in configurations...">
</mat-form-field>
</div>
</c-col>
</c-row>
</c-card-body>
</c-card>
</div>
<gui-grid [source]="source" [paging]="paging" [columnMenu]="columnMenu" [sorting]="sorting" <gui-grid [source]="source" [paging]="paging" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [infoPanel]="infoPanel" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]=true> [autoResizeWidth]=true>
@ -87,13 +126,29 @@
{{value}} {{value}}
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
<gui-grid-column header="Action" field="id"> <gui-grid-column header="Actions" field="id" width="280">
<ng-template let-value="item.id" let-item="item" let-index="index"> <ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton [disabled]="backuploading" color="info" size="sm" (click)="ShowBackup(item)" class="mx-1"> <div class="d-flex gap-1">
<i *ngIf="backuploading" style="margin: 1px 5px;color:#ffffff;" class="fa-solid fa-spinner fa-spin"></i> <button cButton [disabled]="backuploading" color="primary" size="sm"
<i *ngIf="!backuploading" style="margin: 1px 5px;color:#ffffff;" class="fa-solid fa-eye"></i>Show backup</button> (click)="ShowBackup(item)" variant="outline" title="View backup content">
<button *ngIf="ispro" cButton color="info" size="sm" (click)="add_for_compare(item)" class="mx-1"><i <i *ngIf="backuploading && currentBackup?.id === item.id"
style="margin: 1px 5px;color:#ffffff;" class="fa-solid fa-eye"></i>Compare</button> class="fa-solid fa-spinner fa-spin me-1"></i>
<i *ngIf="!backuploading || currentBackup?.id !== item.id"
class="fa-solid fa-eye me-1"></i>
View
</button>
<button *ngIf="ispro" cButton color="info" size="sm" variant="outline"
(click)="add_for_compare(item)" title="Add to comparison"
[disabled]="isInCompareList(item)">
<i class="fa-solid fa-code-compare me-1"></i>
{{isInCompareList(item) ? 'Added' : 'Compare'}}
</button>
<button *ngIf="ispro" cButton color="warning" size="sm" variant="outline"
(click)="restore_backup(false, false, item)" title="Restore this backup">
<i class="fa-solid fa-rotate-left me-1"></i>
Restore
</button>
</div>
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
</gui-grid> </gui-grid>
@ -164,31 +219,80 @@
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<!-- Initial Restore Confirmation -->
<c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal"> <c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal">
<c-modal-header> <c-modal-header class="bg-warning text-dark">
<h6 cModalTitle>Please Confirm Action </h6> <h5 cModalTitle><i class="fa-solid fa-exclamation-triangle me-2"></i>Confirm Backup Restore</h5>
<button [cModalToggle]="ConfirmModal.id" cButtonClose></button> <button [cModalToggle]="ConfirmModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-4">
<span>restore backup ?</span> <div class="text-center mb-3">
<ng-container> <i class="fa-solid fa-rotate-left fa-3x text-warning mb-3"></i>
Are you sure that You want to <code style="padding: 0!important;">Restore this configuration</code> on <h6>Restore Configuration Backup</h6>
device?<br /> </div>
<hr>
<p class="text-danger"> <div class="backup-info bg-light p-3 rounded mb-3">
All Current device configuration will be reset:<br /><br /> <h6 class="mb-2"><i class="fa-solid fa-info-circle me-2 text-primary"></i>Backup Details</h6>
* All state data/history on router will be reset<br /> <div class="row">
* All other local users on router will be deleted<br /> <div class="col-6"><strong>Device:</strong> {{currentBackup?.devname}}</div>
* After restore the password of the local user will be same as configured in MikroWizard<br /> <div class="col-6"><strong>IP:</strong> {{currentBackup?.devip}}</div>
</p> <div class="col-6"><strong>Date:</strong> {{currentBackup?.createdC}}</div>
</ng-container> <div class="col-6"><strong>Size:</strong> {{currentBackup?.filesize}}</div>
</div>
</div>
<c-alert color="danger" class="mb-0">
<h6 class="alert-heading"><i class="fa-solid fa-warning me-2"></i>Critical Warning</h6>
<p class="mb-2">This action will completely reset the device configuration:</p>
<ul class="mb-0">
<li>All current device configuration will be overwritten</li>
<li>All state data and history on router will be reset</li>
<li>All other local users on router will be deleted</li>
<li>Device will reboot and apply the restored configuration</li>
</ul>
</c-alert>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer class="d-flex justify-content-between">
<button *ngIf="ispro" (click)="restore_backup(true)" cButton color="info"> <button cButton [cModalToggle]="ConfirmModal.id" color="secondary">
Restore this <i class="fa-solid fa-times me-1"></i>Cancel
</button> </button>
<button cButton [cModalToggle]="ConfirmModal.id" color="info"> <button *ngIf="ispro" (click)="restore_backup(true, false)" cButton color="warning">
Cancel <i class="fa-solid fa-arrow-right me-1"></i>Continue to Final Confirmation
</button>
</c-modal-footer>
</c-modal>
<!-- Critical Double Confirmation -->
<c-modal #CriticalConfirmModal backdrop="static" [(visible)]="CriticalConfirmModalVisible" id="CriticalConfirmModal">
<c-modal-header class="bg-danger text-white">
<h5 cModalTitle><i class="fa-solid fa-skull-crossbones me-2"></i>CRITICAL: Final Confirmation Required</h5>
</c-modal-header>
<c-modal-body class="p-4">
<div class="text-center mb-4">
<i class="fa-solid fa-exclamation-triangle fa-4x text-danger mb-3"></i>
<h5 class="text-danger">DESTRUCTIVE ACTION</h5>
<p class="mb-0">You are about to permanently overwrite device configuration</p>
</div>
<div class="confirmation-box bg-light border border-danger p-3 rounded">
<p class="mb-3 fw-bold">To proceed with this critical action, type <code>CONFIRM</code> in the box below:</p>
<input cFormControl [(ngModel)]="confirmationText" placeholder="Type CONFIRM to proceed"
class="form-control text-center fw-bold" style="letter-spacing: 2px;" />
</div>
<c-alert color="info" class="mt-3 mb-0">
<small><i class="fa-solid fa-info-circle me-1"></i>
This confirmation ensures you understand the critical nature of this operation.
</small>
</c-alert>
</c-modal-body>
<c-modal-footer class="d-flex justify-content-between">
<button (click)="cancelCriticalRestore()" cButton color="secondary">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
<button (click)="restore_backup(true, true)" cButton color="danger"
[disabled]="confirmationText !== 'CONFIRM'">
<i class="fa-solid fa-rotate-left me-1"></i>RESTORE BACKUP
</button> </button>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>

View file

@ -1,7 +1,72 @@
@import 'ngx-diff/styles/default-theme'; @import 'ngx-diff/styles/default-theme';
::ng-deep .modal-xl { ::ng-deep .modal-xl {
--cui-modal-width: 90vw!important; --cui-modal-width: 90vw!important;
} }
::ng-deep pre { ::ng-deep pre {
display: block!important; display: block!important;
}
// Enhanced UI Styles
.compare-panel {
background: rgba(13, 110, 253, 0.1);
border: 1px solid rgba(13, 110, 253, 0.2);
border-radius: 0.375rem;
padding: 0.5rem;
}
.selected-item {
font-size: 0.75rem;
.fa-times {
font-size: 0.7rem;
opacity: 0.7;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
.cursor-pointer {
cursor: pointer;
}
.filter-group {
.form-label {
font-size: 0.875rem;
color: #495057;
}
::ng-deep .mat-mdc-form-field {
.mat-mdc-text-field-wrapper {
background-color: white;
}
}
}
.confirmation-box {
input {
font-size: 1.1rem;
&:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
}
}
// Action buttons spacing
.d-flex.gap-1 {
gap: 0.25rem !important;
}
// Backup info styling
.backup-info {
.row > div {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
} }

View file

@ -30,11 +30,13 @@ export class BackupsComponent implements OnInit {
public codeForHighlightAuto: string = ""; public codeForHighlightAuto: string = "";
public ispro: boolean = false; public ispro: boolean = false;
public ConfirmModalVisible: boolean = false; public ConfirmModalVisible: boolean = false;
public CriticalConfirmModalVisible: boolean = false;
public CompareModalVisible: boolean = false; public CompareModalVisible: boolean = false;
public compareitems:any=[]; public compareitems:any=[];
public comparecontents:any=[]; public comparecontents:any=[];
public compare_type="unified"; public compare_type="unified";
public copy_msg:boolean=false; public copy_msg:boolean=false;
public confirmationText: string = '';
constructor( constructor(
private data_provider: dataProvider, private data_provider: dataProvider,
@ -186,31 +188,55 @@ export class BackupsComponent implements OnInit {
this.filters_visible = !this.filters_visible; this.filters_visible = !this.filters_visible;
} }
restore_backup(apply:boolean=false){ restore_backup(apply: boolean = false, doubleConfirmed: boolean = false, backup?: any) {
var _slef=this; var _self = this;
if (!apply){
// Set current backup if provided
if (backup) {
this.currentBackup = backup;
}
if (!apply) {
// Step 1: Show initial confirmation
this.ConfirmModalVisible = true; this.ConfirmModalVisible = true;
return; return;
} }
if (!this.currentBackup)
if (!this.currentBackup) {
return; return;
if(apply){ }
_slef.ConfirmModalVisible = false;
_slef.BakcupModalVisible = true; if (apply && !doubleConfirmed) {
this.show_toast('Success', 'Backup restored successfully', 'success') // Step 2: Show critical confirmation
this.show_toast('Info', 'Wait for the router to reboot and apply config', 'info') this.ConfirmModalVisible = false;
this.CriticalConfirmModalVisible = true;
this.confirmationText = '';
return;
}
if (apply && doubleConfirmed) {
// Step 3: Execute restore
_self.CriticalConfirmModalVisible = false;
_self.BakcupModalVisible = false;
this.data_provider.restore_backup(this.currentBackup.id).then((res) => { this.data_provider.restore_backup(this.currentBackup.id).then((res) => {
if ('status' in res){ if ('status' in res) {
if(res['status']=='success'){ if (res['status'] == 'success') {
this.show_toast('Success', 'Backup restored successfully', 'success') this.show_toast('Success', 'Backup restored successfully', 'success');
this.show_toast('Info', 'Wait for the router to reboot and apply config', 'info') this.show_toast('Info', 'Wait for the router to reboot and apply config', 'info');
} else {
this.show_toast('Error', 'Error restoring backup', 'danger');
} }
else
this.show_toast('Error', 'Error restoring backup', 'danger')
} }
}); });
} }
} }
cancelCriticalRestore() {
this.CriticalConfirmModalVisible = false;
this.confirmationText = '';
this.currentBackup = null;
}
start_compare(){ start_compare(){
var _self=this; var _self=this;
@ -243,10 +269,20 @@ export class BackupsComponent implements OnInit {
this.compareitems.push(item); this.compareitems.push(item);
} }
} }
delete_compare(i:number){ delete_compare(i: number) {
//delete item index i from compareitems // Delete item index i from compareitems
this.compareitems.splice(i,1); this.compareitems.splice(i, 1);
}
clearAllCompare() {
// Clear all compare items
this.compareitems = [];
this.comparecontents = [];
}
isInCompareList(item: any): boolean {
// Check if item is already in compare list
return this.compareitems.some((compareItem: any) => compareItem.id === item.id);
} }
reinitgrid(field: string, $event: any) { reinitgrid(field: string, $event: any) {
if (field == "start") this.filters["start_time"] = $event.target.value; if (field == "start") this.filters["start_time"] = $event.target.value;

View file

@ -12,6 +12,7 @@ import {
ModalModule, ModalModule,
FormModule, FormModule,
ToastModule, ToastModule,
AlertModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { BackupsRoutingModule } from "./backups-routing.module"; import { BackupsRoutingModule } from "./backups-routing.module";
@ -34,10 +35,10 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
FormModule, FormModule,
FormsModule, FormsModule,
ButtonModule, ButtonModule,
ButtonModule,
GuiGridModule, GuiGridModule,
CollapseModule, CollapseModule,
BadgeModule, BadgeModule,
AlertModule,
Highlight, Highlight,
HighlightAuto, HighlightAuto,
HighlightLineNumbers, HighlightLineNumbers,

View file

@ -0,0 +1,494 @@
/* Modern Cloner Component Styles */
/* Form Sections */
.cloner-form-section {
border-radius: 6px;
background: #f8f9fa;
padding: 0.75rem;
border: 1px solid #e9ecef;
}
.section-header {
border-bottom: 1px solid #e9ecef;
padding-bottom: 0.5rem;
}
.section-title {
color: #495057;
font-weight: 600;
font-size: 0.95rem;
}
.form-input {
border-radius: 6px;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.form-input-sm {
border-radius: 4px;
border: 1px solid #ced4da;
padding: 0.375rem 0.5rem;
font-size: 0.85rem;
height: calc(1.5em + 0.75rem + 2px);
}
.form-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.form-label-sm {
display: block;
font-size: 0.8rem;
color: #6c757d;
margin-bottom: 0.25rem;
font-weight: 500;
}
.form-label-xs {
display: block;
font-size: 0.75rem;
color: #6c757d;
margin-bottom: 0.125rem;
font-weight: 500;
}
.form-select-sm {
font-size: 0.85rem;
height: calc(1.8em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
}
.form-select-xs {
font-size: 0.8rem;
height: calc(1.5em + 0.75rem + 2px);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
/* Commands Configuration */
.commands-container {
background: white;
border-radius: 8px;
border: 1px solid #dee2e6;
overflow: hidden;
}
.commands-container-compact {
background: white;
border-radius: 6px;
border: 1px solid #dee2e6;
overflow: hidden;
}
.commands-nav {
background: #f8f9fa;
border-bottom: 2px solid #e9ecef;
padding: 0.5rem 1rem;
}
.commands-nav-compact {
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
padding: 0.25rem 0.5rem;
}
.commands-nav .nav-item {
margin-bottom: -2px;
cursor: pointer;
}
.commands-nav .nav-link {
color: #6c757d;
border-style: none none solid;
border-width: 2px;
position: relative;
bottom: -1px;
cursor: pointer;
padding: 0.5rem 1rem;
font-size: 0.9rem;
font-weight: 500;
}
.commands-nav .nav-link:hover,
.commands-nav .nav-link:focus {
border-color: #0d6efd;
color: #0d6efd;
}
.commands-nav .nav-link.active {
color: #0d6efd;
background: transparent;
border-color: #0d6efd;
}
.command-sections {
padding: 1rem;
min-height: 200px;
}
.command-sections-compact {
padding: 0.5rem;
min-height: 120px;
}
.command-category {
margin-bottom: 1.5rem;
}
.command-category-compact {
margin-bottom: 0.75rem;
}
.category-title {
color: #0d6efd;
margin-bottom: 0.75rem;
font-size: 0.9rem;
font-weight: 600;
border-bottom: 1px solid #e9ecef;
padding-bottom: 0.25rem;
}
.category-title-compact {
color: #0d6efd;
margin-bottom: 0.375rem;
font-size: 0.8rem;
font-weight: 600;
border-bottom: 1px solid #e9ecef;
padding-bottom: 0.125rem;
}
.commands-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 0.5rem;
}
.commands-grid-compact {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.25rem;
}
.command-item {
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
transition: all 0.2s ease;
}
.command-item-compact {
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
transition: all 0.2s ease;
}
.command-item:hover {
border-color: #0d6efd;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.command-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
}
.command-content-compact {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.25rem 0.5rem;
}
.command-name {
font-size: 0.8rem;
font-weight: 600;
color: #495057;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
margin-right: 0.5rem;
}
.command-name-compact {
font-size: 0.7rem;
font-weight: 600;
color: #495057;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
margin-right: 0.25rem;
}
/* Custom Switch Styling */
.custom-switch {
position: relative;
width: 40px;
height: 20px;
flex-shrink: 0;
}
.custom-switch-compact {
position: relative;
width: 32px;
height: 16px;
flex-shrink: 0;
}
.custom-switch-compact input {
display: none;
}
.custom-switch-compact .custom-control-label {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 16px;
background: #ccc;
cursor: pointer;
transition: all 0.3s ease;
}
.custom-switch-compact .custom-control-label::after {
content: '';
position: absolute;
left: 2px;
top: 2px;
width: 12px;
height: 12px;
background: #fff;
border-radius: 50%;
transition: all 0.3s ease;
}
.custom-switch-compact input:checked + .custom-control-label {
background: #0d6efd;
}
.custom-switch-compact input:checked + .custom-control-label::after {
left: calc(100% - 14px);
}
.custom-switch input {
display: none;
}
.custom-control-label {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 20px;
background: #ccc;
cursor: pointer;
transition: all 0.3s ease;
}
.custom-control-label::after {
content: '';
position: absolute;
left: 2px;
top: 2px;
width: 16px;
height: 16px;
background: #fff;
border-radius: 50%;
transition: all 0.3s ease;
}
.custom-switch input:checked + .custom-control-label {
background: #0d6efd;
}
.custom-switch input:checked + .custom-control-label::after {
left: calc(100% - 18px);
}
/* Master Device Selection */
.master-selection {
background: white;
border-radius: 6px;
border: 1px solid #dee2e6;
padding: 0.75rem;
}
.master-selection-compact {
background: white;
border-radius: 4px;
border: 1px solid #dee2e6;
padding: 0.5rem;
}
.master-device {
display: flex;
align-items: center;
padding: 0.5rem;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
}
.master-device-compact {
display: flex;
align-items: center;
padding: 0.375rem;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 4px;
}
.master-info {
display: flex;
align-items: center;
}
.no-master {
display: flex;
align-items: center;
padding: 0.5rem;
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 6px;
}
.no-master-compact {
display: flex;
align-items: center;
padding: 0.375rem;
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 4px;
}
/* Peers Container */
.peers-container {
background: white;
border-radius: 8px;
border: 1px solid #dee2e6;
overflow: hidden;
}
.empty-peers {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem;
text-align: center;
}
.empty-icon {
margin-bottom: 1rem;
}
.empty-text strong {
display: block;
margin-bottom: 0.5rem;
color: #495057;
}
.add-members-section {
padding: 1rem;
background: #f8f9fa;
border-top: 1px solid #dee2e6;
}
/* Modal Enhancements */
.c-modal-header.bg-light {
background: #f8f9fa !important;
border-bottom: 1px solid #dee2e6;
}
.c-modal-header.bg-success {
background: #198754 !important;
border-bottom: 1px solid #146c43;
}
.c-modal-footer.bg-light {
background: #f8f9fa !important;
border-top: 1px solid #dee2e6;
}
.c-modal-body {
max-height: 80vh;
overflow-y: auto;
}
/* Button Groups */
.btn-group .btn {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
}
.btn-group .btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Responsive Design */
@media (max-width: 768px) {
.cloner-form-section {
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.commands-grid {
grid-template-columns: 1fr;
}
.command-content {
padding: 0.5rem;
}
.command-name {
font-size: 0.75rem;
}
.c-modal-dialog {
margin: 0.25rem;
}
.section-title {
font-size: 0.9rem;
}
.commands-nav .nav-link {
padding: 0.375rem 0.75rem;
font-size: 0.8rem;
}
.master-device,
.no-master {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
}
@media (max-width: 576px) {
.c-modal-footer {
flex-direction: column;
align-items: stretch;
}
.c-modal-footer > div {
width: 100%;
text-align: center;
margin-bottom: 0.5rem;
}
.c-modal-footer > div:last-child {
margin-bottom: 0;
}
}

View file

@ -15,17 +15,27 @@
<c-card-body> <c-card-body>
<gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting" <gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true> [infoPanel]="infoPanel" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<strong>{{value}}</strong>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Description" field="description"> <gui-grid-column header="Description" field="description">
<ng-template let-value="item.description" let-item="item" let-index="index"> <ng-template let-value="item.description" let-item="item" let-index="index">
{{value}} {{value}}
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
<gui-grid-column header="Members type" field="pair_type"> <gui-grid-column header="Direction" field="direction">
<ng-template let-value="item.pair_type" let-item="item" let-index="index"> <ng-template let-value="item.direction" let-item="item" let-index="index">
{{value}} <c-badge [color]="value == 'twoway' ? 'success' : 'warning'">{{value == 'twoway' ? 'Two Way' : 'Master Mode'}}</c-badge>
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
<gui-grid-column header="Runtime" field="desc_cron"> <gui-grid-column header="Live Mode" field="live_mode">
<ng-template let-value="item.live_mode" let-item="item" let-index="index">
<c-badge [color]="value ? 'success' : 'secondary'">{{value ? 'Active' : 'Inactive'}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Schedule" field="desc_cron">
<ng-template let-value="item.desc_cron" let-item="item" let-index="index"> <ng-template let-value="item.desc_cron" let-item="item" let-index="index">
{{value}} {{value}}
</ng-template> </ng-template>
@ -46,109 +56,190 @@
</c-col> </c-col>
</c-row> </c-row>
<c-modal #EditClonerModal backdrop="static" size="xl" [(visible)]="EditClonerModalVisible" id="EditClonerModal"> <c-modal #EditClonerModal backdrop="static" size="lg" [(visible)]="EditClonerModalVisible" id="EditClonerModal">
<c-modal-header> <c-modal-header class="bg-light">
<h5 *ngIf="SelectedCloner['action']=='edit'" cModalTitle>Editing Cloner {{SelectedCloner['name']}}</h5> <h5 *ngIf="SelectedCloner['action']=='edit'" cModalTitle><i class="fa-solid fa-edit me-2"></i>Edit Cloner: {{SelectedCloner['name']}}</h5>
<h5 *ngIf="SelectedCloner['action']=='add'" cModalTitle>Adding new task</h5> <h5 *ngIf="SelectedCloner['action']=='add'" cModalTitle><i class="fa-solid fa-plus me-2"></i>Add New Cloner</h5>
<button [cModalToggle]="EditClonerModal.id" cButtonClose></button> <button [cModalToggle]="EditClonerModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-3">
<label cLabel >General data:</label> <!-- Basic Information -->
<c-input-group class="mb-3"> <div class="cloner-form-section mb-3">
<label cInputGroupText for="floatingInput">Name</label> <div class="section-header mb-2">
<input cFormControl id="floatingInput" placeholder="SelectedCloner['name']" [(ngModel)]="SelectedCloner['name']" /> <h6 class="section-title mb-0"><i class="fa-solid fa-info-circle me-2"></i>Basic Information</h6>
<label cInputGroupText for="floatingInput">Description</label>
<input cFormControl id="floatingInput" placeholder="SelectedCloner['description']"
[(ngModel)]="SelectedCloner['description']" />
</c-input-group>
<label cLabel >Sync config and Oprating Modes:</label>
<c-input-group class="mb-3">
<label cInputGroupText for="Direction">
Direction
</label>
<select cSelect id="Direction" [(ngModel)]="SelectedCloner['direction']">
<option value="twoway">Two way</option>
<option value="oneway">Master mode</option>
</select>
<label cInputGroupText for="inputGroupSelect01">
Live Mode
</label>
<select cSelect id="inputGroupSelect01" [(ngModel)]="SelectedCloner['live_mode']">
<option [ngValue]="false">Deactive</option>
<option [ngValue]="true">Active</option>
</select>
<label *ngIf="SelectedCloner['direction']=='oneway'" cInputGroupText for="inputGroupSelect02">
Schedule
</label>
<select *ngIf="SelectedCloner['direction']=='oneway'" cSelect id="inputGroupSelect02" [(ngModel)]="SelectedCloner['schedule']">
<option [ngValue]="false">Deactive</option>
<option [ngValue]="true">Active</option>
</select>
<label cInputGroupText *ngIf="SelectedCloner['schedule'] && SelectedCloner['direction']=='oneway'" for="cron">cron</label>
<input cFormControl *ngIf="SelectedCloner['schedule'] && SelectedCloner['direction']=='oneway'" id="cron" placeholder="Cron" [(ngModel)]="SelectedCloner['cron']" />
</c-input-group>
<label cLabel >Peers Setting:</label>
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect03">
Peers type
</label>
<select cSelect id="inputGroupSelect03" (change)="form_changed()" [(ngModel)]="SelectedCloner['pair_type']">
<option value="devices">Devices</option>
<option value="groups" *ngIf="SelectedCloner['direction']=='oneway'">Groups</option>
</select>
</c-input-group>
<c-col xs style="border: 1px solid #ddd; border-radius: 4px; padding: 0;">
<div class="nav nav-underline" style="background: #fff; border-bottom: 2px solid #2c3e50;">
<div class="nav-item" *ngFor="let tab of tabs; let i = index">
<a class="nav-link" [active]="i==0" [cTabContent]="tabContent" [tabPaneIdx]="i">{{ tab.name }}</a>
</div>
</div> </div>
<c-tab-content class="command-sections" style="padding: 10px!important;min-height: 150px;" #tabContent="cTabContent"> <c-row class="g-2">
<c-tab-pane *ngFor="let tab of tabs; let i = index"> <c-col xs="12" md="6">
<div class="section" *ngFor="let section of tab.sections"> <input cFormControl placeholder="Cloner Name" [(ngModel)]="SelectedCloner['name']" class="form-input-sm" />
<h5 class="cloner-sections">{{ section.title }}</h5> </c-col>
<div class="row"> <c-col xs="12" md="6">
<div class="col-4" *ngFor="let command of section.commands"> <input cFormControl placeholder="Description" [(ngModel)]="SelectedCloner['description']" class="form-input-sm" />
<c-card style="margin-bottom: 5px;"> </c-col>
<c-card-body class="p-2"> </c-row>
<h6 class="card-title mb-1">{{ command }}</h6> </div>
<div class="custom-switch">
<!-- Sync Configuration -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-sync me-2"></i>Synchronization Settings</h6>
</div>
<c-row class="g-2">
<c-col xs="12" md="4">
<label class="form-label-xs">Direction</label>
<select cSelect [(ngModel)]="SelectedCloner['direction']" (change)="onDirectionChange()" class="form-select-xs">
<option value="twoway">Two Way Sync</option>
<option value="oneway">Master Mode</option>
</select>
</c-col>
<c-col xs="12" md="4" *ngIf="SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Live Mode</label>
<select cSelect [(ngModel)]="SelectedCloner['live_mode']" class="form-select-xs">
<option [ngValue]="false">Inactive</option>
<option [ngValue]="true">Active</option>
</select>
</c-col>
<c-col xs="12" md="4" *ngIf="SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Schedule</label>
<select cSelect [(ngModel)]="SelectedCloner['schedule']" class="form-select-xs">
<option [ngValue]="false">Inactive</option>
<option [ngValue]="true">Active</option>
</select>
</c-col>
<c-col xs="12" *ngIf="SelectedCloner['schedule'] && SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Cron Expression</label>
<input cFormControl placeholder="0 0 * * *" [(ngModel)]="SelectedCloner['cron']" class="form-input-sm" />
</c-col>
</c-row>
</div>
<!-- Peers Configuration (Currently disabled - only devices supported) -->
<!-- <div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-network-wired me-2"></i>Peers Configuration</h6>
<small class="text-muted">Select the type of peers to synchronize</small>
</div>
<c-row class="g-2">
<c-col xs="12" md="6">
<label class="form-label-xs">Peers Type</label>
<select cSelect (change)="form_changed()" [(ngModel)]="SelectedCloner['pair_type']" class="form-select-xs">
<option value="devices">Devices</option>
<option value="groups" *ngIf="SelectedCloner['direction']=='oneway'">Groups</option>
</select>
</c-col>
</c-row>
</div> -->
<!-- Commands Configuration -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-terminal me-2"></i>Commands Configuration</h6>
</div>
<div class="commands-container-compact">
<div class="nav nav-underline commands-nav-compact">
<div class="nav-item" *ngFor="let tab of tabs; let i = index">
<a class="nav-link" [active]="i==0" [cTabContent]="tabContent" [tabPaneIdx]="i">{{ tab.name }}</a>
</div>
</div>
<c-tab-content class="command-sections-compact" #tabContent="cTabContent">
<c-tab-pane *ngFor="let tab of tabs; let i = index">
<div class="command-category-compact" *ngFor="let section of tab.sections">
<h6 class="category-title-compact">{{ section.title }}</h6>
<div class="commands-grid-compact">
<div class="command-item-compact" *ngFor="let command of section.commands">
<div class="command-content-compact">
<span class="command-name-compact">{{ command }}</span>
<div class="custom-switch-compact">
<input type="checkbox" class="custom-control-input" [checked]="in_active_commands(command)" (click)="activate_command(command)" [id]="command.replace('/', '')"> <input type="checkbox" class="custom-control-input" [checked]="in_active_commands(command)" (click)="activate_command(command)" [id]="command.replace('/', '')">
<label class="custom-control-label" [for]="command.replace('/', '')"></label> <label class="custom-control-label" [for]="command.replace('/', '')"></label>
</div> </div>
</c-card-body> </div>
</c-card> </div>
</div> </div>
</div> </div>
</div> </c-tab-pane>
</c-tab-pane> </c-tab-content>
</c-tab-content> </div>
</c-col> </div>
<h5>Peers :</h5> <!-- Master Device Selection (for Master Mode) -->
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting" <div class="cloner-form-section mb-2" *ngIf="SelectedCloner['direction']=='oneway' && SelectedMembers.length > 0">
[rowSelection]="rowSelection" [autoResizeWidth]=true [paging]="paging"> <div class="section-header mb-2">
<gui-grid-column header="Name" field="name"> <h6 class="section-title mb-0"><i class="fa-solid fa-crown me-2 text-warning"></i>Master Device</h6>
<ng-template let-value="item.name" let-item="item" let-index="index"> </div>
<i class="fa-solid fa-m" style="color: #ff3300;" *ngIf="SelectedCloner['direction']=='oneway' && item.id==master"></i> <div class="master-selection-compact">
&nbsp; {{value}} </ng-template> <div class="master-device-compact" *ngIf="master > 0">
</gui-grid-column> <i class="fa-solid fa-server me-2 text-primary"></i>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="MAC" field="mac"> <strong>{{getMasterDeviceName()}}</strong>
<ng-template let-value="item.mac" let-item="item" let-index="index"> <c-badge color="warning" class="ms-2">Master</c-badge>
{{value}} </div>
</ng-template> <div class="no-master-compact" *ngIf="master == 0">
</gui-grid-column> <i class="fa-solid fa-exclamation-triangle me-2 text-warning"></i>
<gui-grid-column header="Actions" width="120" field="action"> <span class="text-muted">No master device selected</span>
<ng-template let-value="item.id" let-item="item" let-index="index"> </div>
<button cButton color="danger" size="sm" [cTooltip]="'Delete Member'" (click)="remove_member(item)"><i </div>
class="fa-regular fa-trash-can"></i></button> </div>
<button *ngIf="SelectedCloner['direction']=='oneway'" cButton color="success" size="sm" style="margin-left: 5px;" [cTooltip]="'Set as Master'" (click)="set_master(item.id)"><i class="fa-regular fa-star"></i></button>
</ng-template> <!-- Device Management -->
</gui-grid-column> <div class="cloner-form-section">
</gui-grid> <div class="section-header mb-2">
<hr /> <h6 class="section-title mb-0"><i class="fa-solid fa-server me-2"></i>Device Management</h6>
<button cButton color="primary" (click)="show_new_member_form()">+ Add new Members</button> <div class="d-flex justify-content-between align-items-center">
<c-badge color="info">{{SelectedMembers.length}} device(s)</c-badge>
</div>
</div>
<div class="peers-container">
<div *ngIf="SelectedMembers.length == 0" class="empty-peers">
<div class="empty-icon">
<i class="fa-solid fa-users-slash fa-2x text-muted opacity-50"></i>
</div>
<div class="empty-text">
<strong>No peers added</strong>
<p class="mb-0 text-muted">Click "Add Members" to start adding peers</p>
</div>
</div>
<div *ngIf="SelectedMembers.length > 0">
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting"
[rowSelection]="rowSelection" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-crown me-2 text-warning" *ngIf="SelectedCloner['direction']=='oneway' && item.id==master" title="Master Device"></i>
<i class="fa-solid fa-server me-2 text-primary" *ngIf="!(SelectedCloner['direction']=='oneway' && item.id==master)"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="MAC" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<div class="btn-group" role="group">
<button *ngIf="SelectedCloner['direction']=='oneway'" cButton color="warning" size="sm" variant="outline"
[cTooltip]="'Set as Master'" (click)="set_master(item.id)" [disabled]="item.id==master">
<i class="fa-solid fa-crown"></i>
</button>
<button cButton color="danger" size="sm" variant="outline"
[cTooltip]="'Remove Member'" (click)="remove_member(item)">
<i class="fa-solid fa-times"></i>
</button>
</div>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
<div class="add-members-section mt-3">
<button cButton color="success" (click)="show_new_member_form()">
<i class="fa-solid fa-plus me-1"></i>Add Members
</button>
</div>
</div>
</div>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer>
<button *ngIf="SelectedCloner['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button> <button *ngIf="SelectedCloner['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button>
@ -160,44 +251,75 @@
</c-modal> </c-modal>
<c-modal #NewMemberModal backdrop="static" size="lg" [(visible)]="NewMemberModalVisible" id="NewMemberModal"> <c-modal #NewMemberModal backdrop="static" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal">
<c-modal-header> <c-modal-header class="bg-success text-white">
<h5 cModalTitle>Editing Group </h5> <h5 cModalTitle><i class="fa-solid fa-user-plus me-2"></i>Add Members to Cloner</h5>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button> <button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-4">
<c-input-group class="mb-3"> <!-- Selection Summary -->
<h5>Group Members :</h5> <div class="mb-3" style="min-height: 58px;">
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching" <c-alert *ngIf="NewMemberRows.length > 0" color="info" class="d-flex align-items-center mb-0">
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" <i class="fa-solid fa-info-circle me-2"></i>
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true <span><strong>{{NewMemberRows.length}}</strong> {{SelectedCloner['pair_type']}} selected for addition</span>
[paging]="paging"> </c-alert>
<gui-grid-column header="Member Name" field="name"> </div>
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template> <!-- Available Members -->
</gui-grid-column> <c-card>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="IP Address" field="ip"> <c-card-header class="bg-light">
<ng-template let-value="item.ip" let-item="item" let-index="index"> <h6 class="mb-0">
{{value}} <i class="fa-solid me-2" [class.fa-server]="SelectedCloner['pair_type'] == 'devices'" [class.fa-layer-group]="SelectedCloner['pair_type'] != 'devices'"></i>
</ng-template> Available {{SelectedCloner['pair_type'] | titlecase}} ({{availbleMembers.length}} total)
</gui-grid-column> </h6>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="MAC Address" field="mac"> </c-card-header>
<ng-template let-value="item.mac" let-item="item" let-index="index"> <c-card-body class="p-0">
{{value}} <div *ngIf="availbleMembers.length == 0" class="text-center p-4 text-muted">
</ng-template> <i class="fa-solid fa-check-circle fa-3x mb-3 text-success opacity-50"></i>
</gui-grid-column> <h6>All {{SelectedCloner['pair_type']}} are already added</h6>
</gui-grid> <p class="mb-0">No available {{SelectedCloner['pair_type']}} to add to this cloner</p>
<br /> </div>
</c-input-group> <div *ngIf="availbleMembers.length > 0">
<hr /> <gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true
[paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid me-2 text-primary" [class.fa-server]="SelectedCloner['pair_type'] == 'devices'" [class.fa-layer-group]="SelectedCloner['pair_type'] != 'devices'"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer class="bg-light d-flex justify-content-between">
<button *ngIf="NewMemberRows.length!= 0" (click)="add_new_members()" cButton color="primary">Add {{ <div>
NewMemberRows.length }}</button> <small class="text-muted">Select {{SelectedCloner['pair_type']}} from the list above to add them to the cloner</small>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary"> </div>
Close <div>
</button> <button *ngIf="NewMemberRows.length > 0" (click)="add_new_members()" cButton color="success">
<i class="fa-solid fa-plus me-1"></i>Add {{NewMemberRows.length}} {{SelectedCloner['pair_type'] | titlecase}}
</button>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary" class="ms-2">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
</div>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>

View file

@ -317,16 +317,27 @@ export class ClonerComponent implements OnInit {
this.data_provider this.data_provider
.Add_cloner(_self.SelectedCloner, _self.SelectedClonerItems) .Add_cloner(_self.SelectedCloner, _self.SelectedClonerItems)
.then((res) => { .then((res) => {
_self.initGridTable(); if (res.status === 'failed') {
_self.show_toast("Error", res.err, "danger");
} else {
_self.show_toast("Success", "Cloner added successfully", "success");
_self.initGridTable();
_self.EditClonerModalVisible = false;
}
}); });
} else { } else {
this.data_provider this.data_provider
.Edit_cloner(_self.SelectedCloner, _self.SelectedClonerItems) .Edit_cloner(_self.SelectedCloner, _self.SelectedClonerItems)
.then((res) => { .then((res) => {
_self.initGridTable(); if (res.status === 'failed') {
_self.show_toast("Error", res.err, "danger");
} else {
_self.show_toast("Success", "Cloner updated successfully", "success");
_self.initGridTable();
_self.EditClonerModalVisible = false;
}
}); });
} }
this.EditClonerModalVisible = false;
} }
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void { onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
@ -380,6 +391,7 @@ export class ClonerComponent implements OnInit {
members: "", members: "",
action: "add", action: "add",
}; };
this.master=0;
this.SelectedMembers = []; this.SelectedMembers = [];
this.SelectedClonerItems = []; this.SelectedClonerItems = [];
this.EditClonerModalVisible = true; this.EditClonerModalVisible = true;
@ -411,6 +423,19 @@ export class ClonerComponent implements OnInit {
in_active_commands(command:string){ in_active_commands(command:string){
return this.active_commands.includes(command); return this.active_commands.includes(command);
} }
getMasterDeviceName(): string {
const masterDevice = this.SelectedMembers.find((m: any) => m.id == this.master);
return masterDevice ? masterDevice.name : '';
}
onDirectionChange() {
if (this.SelectedCloner['direction'] == 'twoway') {
this.SelectedCloner['live_mode'] = true;
this.SelectedCloner['schedule'] = false;
}
this.form_changed();
}
remove_member(item: any) { remove_member(item: any) {
var _self = this; var _self = this;
_self.SelectedMembers = _self.SelectedMembers.filter( _self.SelectedMembers = _self.SelectedMembers.filter(

View file

@ -1,5 +1,5 @@
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common"; import { CommonModule, TitleCasePipe } from "@angular/common";
import { FormsModule,ReactiveFormsModule } from "@angular/forms"; import { FormsModule,ReactiveFormsModule } from "@angular/forms";
import { import {
@ -13,6 +13,8 @@ import {
TooltipModule, TooltipModule,
NavModule, NavModule,
TabsModule, TabsModule,
BadgeModule,
AlertModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { ClonerRoutingModule } from "./cloner-routing.module"; import { ClonerRoutingModule } from "./cloner-routing.module";
import { ClonerComponent } from "./cloner.component"; import { ClonerComponent } from "./cloner.component";
@ -38,7 +40,10 @@ import { NgxSuperSelectModule} from "ngx-super-select";
TooltipModule, TooltipModule,
NavModule, NavModule,
TabsModule, TabsModule,
BadgeModule,
AlertModule,
], ],
declarations: [ClonerComponent], declarations: [ClonerComponent],
providers: [TitleCasePipe],
}) })
export class ClonerModule {} export class ClonerModule {}

View file

@ -1,3 +1,4 @@
@import 'cloner-styles';
.nav-underline { .nav-underline {
border-bottom: 2px solid var(--cui-nav-underline-border-color, #c4c9d0) border-bottom: 2px solid var(--cui-nav-underline-border-color, #c4c9d0)

View file

@ -1,11 +1,23 @@
<c-row *ngIf="stats"> <c-row>
<c-col xs> <c-col xs>
<c-card *ngIf="stats" class="mb-1"> <c-card class="mb-1">
<c-card-header>Past 24 Hour Statics</c-card-header> <c-card-header>Past 24 Hour Statics</c-card-header>
<c-card-body> <c-card-body>
<c-row> <c-row>
<c-col md="12" xl="12" xs="12"> <c-col md="12" xl="12" xs="12">
<c-row> <c-row *ngIf="!stats">
<c-col class="mb-sm-1 mb-0" *ngFor="let item of [1,2,3,4,5]">
<c-card class="mb-1">
<c-card-body>
<div class="placeholder-glow">
<div class="placeholder col-3 mb-2" style="height: 40px;"></div>
<div class="placeholder col-6"></div>
</div>
</c-card-body>
</c-card>
</c-col>
</c-row>
<c-row *ngIf="stats">
<c-col class="mb-sm-1 mb-0"> <c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Failed Logins'" class="mb-1" color="danger" padding <c-widget-stat-f [title]="'Failed Logins'" class="mb-1" color="danger" padding
value="{{stats['FailedLogins']}}"> value="{{stats['FailedLogins']}}">
@ -53,7 +65,17 @@
<c-col xs> <c-col xs>
<c-row> <c-row>
<c-col md="12" xl="12" xs="12"> <c-col md="12" xl="12" xs="12">
<c-row> <c-row *ngIf="!stats">
<c-col class="mb-0 pb-0" *ngFor="let item of [1,2,3,4,5]">
<div class="border-start border-start-4 border-start-info pt-1 px-3 mb-1">
<div class="placeholder-glow">
<div class="placeholder col-8"></div>
<div class="placeholder col-6"></div>
</div>
</div>
</c-col>
</c-row>
<c-row *ngIf="stats">
<c-col class="mb-0 pb-0"> <c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-info pt-1 px-3 mb-1"> <div class="border-start border-start-4 border-start-info pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total users</div> <div class="text-medium-emphasis small">Total users</div>
@ -120,16 +142,28 @@
</form> </form>
</c-col> </c-col>
</c-row> </c-row>
<c-chart [data]="chart_data" [options]="options" [height]="250" type="line"> <div *ngIf="!chart_data.datasets" style="height: 250px;">
<div class="placeholder-glow" style="height: 100%;">
<div class="placeholder col-12" style="height: 250px;"></div>
</div>
</div>
<c-chart *ngIf="chart_data.datasets" [data]="chart_data" [options]="options" [height]="250" type="line">
</c-chart> </c-chart>
</c-card-body> </c-card-body>
</c-card> </c-card>
<c-row> <c-row>
<c-col xl="6" *ngIf="stats" lg="12" class="h-100" style="min-height: 160px!important;display: grid"> <c-col xl="6" lg="12" class="h-100" style="min-height: 160px!important;display: grid">
<c-card class="mb-1 p-1 h-100" style="padding-left: 5px!important;"> <c-card class="mb-1 p-1 h-100" style="padding-left: 5px!important;">
<div class="my-1"> <div class="my-1">
<h4 style="display: inline-block;">Version and Serial information</h4> <h4 style="display: inline-block;">Version and Serial information</h4>
</div>
<div *ngIf="!stats">
<div class="placeholder-glow">
<div class="placeholder col-8"></div>
<div class="placeholder col-6"></div>
<div class="placeholder col-10"></div>
<div class="placeholder col-7"></div>
</div>
</div> </div>
<div *ngIf="!stats['license']" class="my-1"> <div *ngIf="!stats['license']" class="my-1">
<div style="display: inline-block;margin-right: 5px;"> <div style="display: inline-block;margin-right: 5px;">
@ -199,8 +233,18 @@
</c-card> </c-card>
</c-col> </c-col>
<c-col xl="6" lg="12" class="h-100" style="min-height: 160px!important;display: grid;"> <c-col xl="6" lg="12" class="h-100" style="min-height: 160px!important;display: grid;">
<c-card class="h-100" *ngIf="stats" style="padding: 0!important;margin: 0!important;"> <c-card class="h-100" style="padding: 0!important;margin: 0!important;">
<c-carousel [dark]="true" [animate]="false" [wrap]="false" [interval]="1000000"> <div *ngIf="!stats" style="padding: 20px;">
<div class="placeholder-glow">
<div class="placeholder col-4" style="height: 150px; float: left; margin-right: 20px;"></div>
<div class="placeholder col-7">
<div class="placeholder col-12"></div>
<div class="placeholder col-10"></div>
<div class="placeholder col-8"></div>
</div>
</div>
</div>
<c-carousel *ngIf="stats" [dark]="true" [animate]="false" [wrap]="false" [interval]="1000000">
<c-carousel-indicators></c-carousel-indicators> <c-carousel-indicators></c-carousel-indicators>
<c-carousel-inner> <c-carousel-inner>
<c-carousel-item style="display: flex;" *ngFor="let slide of stats['blog']; index as i;"> <c-carousel-item style="display: flex;" *ngFor="let slide of stats['blog']; index as i;">

View file

@ -32,7 +32,6 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
ReactiveFormsModule, ReactiveFormsModule,
ButtonModule, ButtonModule,
TemplateIdDirective, TemplateIdDirective,
ButtonModule,
ButtonGroupModule, ButtonGroupModule,
ChartjsModule, ChartjsModule,
CarouselModule, CarouselModule,

View file

@ -509,14 +509,16 @@ export class DeviceComponent implements OnInit, OnDestroy {
// loop in dhcp_server_data and create a new object with the data for chart for each dhcp server // loop in dhcp_server_data and create a new object with the data for chart for each dhcp server
_self.reloading = false; _self.reloading = false;
_self.dhcp_server_data.forEach((element:any) => { _self.dhcp_server_data.forEach((element:any) => {
var pooldata=element.pools[0]; if(element.pools.length>0){
element.chartpools = { var pooldata=element.pools[0];
labels: ['Used', 'Free'], element.chartpools = {
datasets: [{ labels: ['Used', 'Free'],
backgroundColor: [ '#E46651','#41B883'], datasets: [{
data: [pooldata.used_ips, pooldata.available_ips] backgroundColor: [ '#E46651','#41B883'],
}] data: [pooldata.used_ips, pooldata.available_ips]
}; }]
};
}
}); });
}); });
} }

View file

@ -8,19 +8,25 @@
</c-col> </c-col>
<c-col xs [lg]="9"> <c-col xs [lg]="9">
<h6 style="text-align: right;"> <h6 style="text-align: right;">
<button cButton color="danger" class="mx-1" size="sm" style="color: #fff;">{{updates.length}} Updatable <button cButton color="success" (click)="openAddDeviceModal()" class="mx-1"
</button> size="sm" style="color: #fff;"><i class="fa-solid fa-plus"></i> Bulk Add </button>
<button cButton color="warning" class="mx-1" size="sm" style="color: #fff;">{{upgrades.length}}
Upgradable</button>
|
<button cButton color="dark" (click)="scanwizard(1,'')" [cModalToggle]="ScannerModal.id" class="mx-1" <button cButton color="dark" (click)="scanwizard(1,'')" [cModalToggle]="ScannerModal.id" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-magnifying-glass"></i> Scanner</button> size="sm" style="color: #fff;"><i class="fa-solid fa-magnifying-glass"></i> Scan</button>
<button cButton color="primary" (click)="show_exec()" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-history"></i> History</button>
</h6> </h6>
</c-col> </c-col>
</c-row> </c-row>
</c-card-header> </c-card-header>
<c-card-body> <c-card-body>
<c-row> <c-row>
<c-col [lg]="9">
<button cButton color="danger" class="mx-1" size="sm" style="color: #fff;" (click)="filterUpdatable()">{{updates.length}} Updatable
</button>
<button cButton color="warning" class="mx-1" size="sm" style="color: #fff;" (click)="filterUpgradable()">{{upgrades.length}}
Upgradable</button>
<button cButton color="secondary" class="mx-1" size="sm" (click)="clearFilter()">Clear Filter</button>
</c-col>
<c-col [lg]="3"> <c-col [lg]="3">
<c-input-group *ngIf="groups.length>0"> <c-input-group *ngIf="groups.length>0">
<span cInputGroupText>Group</span> <span cInputGroupText>Group</span>
@ -37,6 +43,10 @@
(selectedRows)="onSelectedRows($event)" [autoResizeWidth]=true> (selectedRows)="onSelectedRows($event)" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name"> <gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index"> <ng-template let-value="item.name" let-item="item" let-index="index">
<button cButton size="sm" variant="ghost" color="primary" (click)="webAccess(item)"
style="padding: 2px 4px; margin-right: 5px; border: none;" cTooltip="Web Access">
<i class="fas fa-globe" style="font-size: 12px;"></i>
</button>
<img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" /> <img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" />
<i *ngIf="item.status=='updated'" cTooltip="Tooltip text" <i *ngIf="item.status=='updated'" cTooltip="Tooltip text"
style="color: green; margin-right: 3px;font-size: .7em;" class="fa-solid fa-check"></i> style="color: green; margin-right: 3px;font-size: .7em;" class="fa-solid fa-check"></i>
@ -55,7 +65,7 @@
<div>{{value}}</div> <div>{{value}}</div>
<i *ngIf="item.update_availble" cTooltip="Firmware Update availble" <i *ngIf="item.update_availble" cTooltip="Firmware Update availble"
class="fa-solid fa-up-long text-primary mx-1"></i> class="fa-solid fa-up-long text-primary mx-1"></i>
<i *ngIf="item.update_availble" cTooltip="Device Firmware not Upgraded" <i *ngIf="item.upgrade_availble" cTooltip="Device Firmware not Upgraded"
class="fa-solid fa-microchip text-danger mx-1"></i> class="fa-solid fa-microchip text-danger mx-1"></i>
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
@ -115,9 +125,9 @@
<button size="sm" (click)="single_device_action(item,'update')" style="padding: 4px 7px;" <button size="sm" (click)="single_device_action(item,'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-upload"></i><small> cListGroupItem><i class="text-primary fa-solid fa-upload"></i><small>
Update Firmware</small></button> Update Firmware</small></button>
<!-- <button size="sm" (click)="single_device_action(item,'upgrade')" style="padding: 4px 7px;" <button size="sm" (click)="single_device_action(item,'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-microchip"></i><small> cListGroupItem><i class="text-primary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button> --> Upgrade Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'devlogs')" style="padding: 4px 7px;" <button size="sm" (click)="single_device_action(item,'devlogs')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-regular fa-rectangle-list"></i><small> cListGroupItem><i class="fa-regular fa-rectangle-list"></i><small>
Device Logs</small></button> Device Logs</small></button>
@ -154,8 +164,9 @@
Firmware</button></li> Firmware</button></li>
<li><button cDropdownItem <li><button cDropdownItem
(click)="ConfirmAction='update';ConfirmModalVisible=true">Update</button></li> (click)="ConfirmAction='update';ConfirmModalVisible=true">Update</button></li>
<!-- <li><button cDropdownItem>Upgrade</button></li> <li><button cDropdownItem
<li><button cDropdownItem>Update and Upgrade</button></li> --> (click)="ConfirmAction='upgrade';ConfirmModalVisible=true">Upgrade</button></li>
<!-- <li><button cDropdownItem>Update and Upgrade</button></li> -->
</ul> </ul>
</c-dropdown> </c-dropdown>
</c-nav-item> </c-nav-item>
@ -259,8 +270,6 @@
</div> </div>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer>
<h6 style="margin: 0 auto;" *ngIf="scanwizard_step==1"><button cButton color="primary" (click)="show_exec()"
style="margin: 0 auto;" variant="outline">Device scan logs</button></h6>
<small *ngIf="scan_type=='ip'">Empty username and password means system default <small *ngIf="scan_type=='ip'">Empty username and password means system default
configuration</small> configuration</small>
</c-modal-footer> </c-modal-footer>
@ -269,39 +278,68 @@
<c-modal #ExecutedDataModal backdrop="static" size="xl" [(visible)]="ExecutedDataModalVisible" id="ExecutedDataModal"> <c-modal #ExecutedDataModal backdrop="static" size="xl" [(visible)]="ExecutedDataModalVisible" id="ExecutedDataModal">
<c-modal-header> <c-modal-header>
<h5 cModalTitle>Scan History : </h5> <h5 cModalTitle>Task History</h5>
<button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButtonClose></button> <button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body style="min-height: 400px; max-height: 70vh; overflow-y: auto;">
<c-input-group class="mb-3"> <div class="mb-3">
<gui-grid [autoResizeWidth]="true" *ngIf="ExecutedDataModalVisible" [searching]="searching" <c-input-group>
[source]="ExecutedData" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [paging]="paging"> <span cInputGroupText>Filter by Type</span>
<gui-grid-column header="Start time" field="start"> <select [(ngModel)]="selectedTaskType" (change)="filterByTaskType()" cSelect>
<ng-template let-value="item['started']" let-item="item" let-index="index"> <option value="all">All Tasks</option>
&nbsp; {{value}} </ng-template> <option value="ip-scan">IP Scan</option>
</gui-grid-column> <option value="bulk-add">Bulk Add</option>
<gui-grid-column header="Start ip" field="start_ip"> </select>
<ng-template let-value="item['start_ip']" let-item="item" let-index="index"> </c-input-group>
&nbsp; {{value}} </ng-template> </div>
</gui-grid-column> <gui-grid [autoResizeWidth]="true" *ngIf="ExecutedDataModalVisible" [searching]="searching"
<gui-grid-column header="End ip" field="end_ip"> [source]="filteredExecutedData" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [paging]="paging">
<ng-template let-value="item['end_ip']" let-item="item" let-index="index"> <gui-grid-column header="Type" field="task_type" [width]="100">
&nbsp; {{value}} </ng-template> <ng-template let-value="item['task_type']" let-item="item" let-index="index">
</gui-grid-column> <i *ngIf="item.task_type === 'ip-scan'" class="fas fa-search" style="margin-right: 3px; color: #0d6efd;"></i>
<gui-grid-column header="End time" field="end"> <i *ngIf="item.task_type === 'bulk-add'" class="fas fa-plus-circle" style="margin-right: 3px; color: #198754;"></i>
<ng-template let-value="item['ended']" let-item="item" let-index="index"> <span style="font-size: 11px;">{{getTaskTypeLabel(value)}}</span>
{{value}} </ng-template>
</ng-template> </gui-grid-column>
</gui-grid-column> <gui-grid-column header="Time" field="time" [width]="220">
<gui-grid-column header="Logs" field="mac" align="center"> <ng-template let-value="value" let-item="item" let-index="index">
<ng-template let-value="item['result']" let-item="item" let-index="index"> <div style="font-size: 10px; line-height: 1.3;">
<button (click)="exportToCsv(value)" color="primary" cButton>download</button> <div><strong>Start:</strong> {{item.started}}</div>
</ng-template> <div><strong>End:</strong> {{item.ended}}</div>
</gui-grid-column> </div>
</gui-grid> </ng-template>
<br /> </gui-grid-column>
</c-input-group> <gui-grid-column header="Details" field="details">
<hr /> <ng-template let-value="value" let-item="item" let-index="index">
<div *ngIf="item.task_type === 'ip-scan'" style="font-size: 12px; line-height: 1.3;">
<div>{{item.start_ip}} - {{item.end_ip}}</div>
<div>User: {{item.username}}</div>
</div>
<div *ngIf="item.task_type === 'bulk-add'" style="font-size: 12px; line-height: 1.3;">
<div>{{item.device_count}} devices</div>
<div style="word-break: break-all;">{{item.task_id}}</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Results" field="results" align="center" [width]="70">
<ng-template let-value="value" let-item="item" let-index="index">
<div style="font-size: 11px;">
<span style="color: green;">✓ {{item.success_count}}</span>
<span *ngIf="item.failed_count > 0" style="color: red; margin-left: 5px;">✗ {{item.failed_count}}</span>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="actions" align="center" [width]="160">
<ng-template let-value="value" let-item="item" let-index="index">
<button (click)="showTaskDetails(item)" color="info" size="sm" cButton class="me-1">
<i class="fas fa-eye"></i> Details
</button>
<button (click)="exportToCsv(item.result)" color="primary" size="sm" cButton>
<i class="fas fa-download"></i> CSV
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer>
<button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButton color="secondary"> <button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButton color="secondary">
@ -320,6 +358,10 @@
update?</span> update?</span>
<span *ngIf="ConfirmAction=='update'">Are you sure that You want to <code>update firmware</code> of selected <span *ngIf="ConfirmAction=='update'">Are you sure that You want to <code>update firmware</code> of selected
devices?</span> devices?</span>
<span *ngIf="ConfirmAction=='upgrade'">Are you sure that You want to <code>upgrade firmware</code> of selected
devices?</span>
<span *ngIf="ConfirmAction=='reboot'">Are you sure that You want to <code>reboot</code> the selected
devices?</span>
<ng-container *ngIf="ConfirmAction=='delete'"> <ng-container *ngIf="ConfirmAction=='delete'">
Are you sure that You want to<code>Delete Device {{selected_device.name}} ?</code><br /> Are you sure that You want to<code>Delete Device {{selected_device.name}} ?</code><br />
<hr> <hr>
@ -338,6 +380,12 @@
<button *ngIf="ConfirmAction=='update'" (click)="update_firmware()" cButton color="danger"> <button *ngIf="ConfirmAction=='update'" (click)="update_firmware()" cButton color="danger">
Yes Yes
</button> </button>
<button *ngIf="ConfirmAction=='upgrade'" (click)="upgrade_firmware()" cButton color="danger">
Yes
</button>
<button *ngIf="ConfirmAction=='reboot'" (click)="upgrade_firmware()" cButton color="danger">
Yes
</button>
<button *ngIf="ConfirmAction=='delete'" (click)="delete_device()" cButton color="danger"> <button *ngIf="ConfirmAction=='delete'" (click)="delete_device()" cButton color="danger">
Yes,Delete Device Yes,Delete Device
</button> </button>
@ -396,4 +444,213 @@
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<!-- Add Device Modal -->
<c-modal #AddDeviceModal backdrop="static" size="lg" [(visible)]="addDeviceModalVisible" id="AddDeviceModal">
<c-modal-header>
<h5 cModalTitle>Add Devices from CSV</h5>
<button cButtonClose (click)="closeAddDeviceModal()"></button>
</c-modal-header>
<c-modal-body>
<div *ngIf="addDeviceStep === 1">
<h6>Upload CSV File</h6>
<p>Please upload a CSV file containing device information with columns: IP Address, Username, Password, API Port</p>
<input type="file" accept=".csv" (change)="onFileSelected($event)" class="form-control mb-3">
<div *ngIf="csvPreview.length > 0">
<h6>Preview (First 3 rows):</h6>
<table class="table table-sm table-bordered">
<thead>
<tr>
<th *ngFor="let header of csvHeaders">{{header}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of csvPreview">
<td *ngFor="let cell of row">{{cell}}</td>
</tr>
</tbody>
</table>
<h6>Column Mapping:</h6>
<div class="row">
<div class="col-md-6">
<label>IP Address Column:</label>
<select [(ngModel)]="columnMapping.ip" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
<div class="col-md-6">
<label>Username Column:</label>
<select [(ngModel)]="columnMapping.username" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<label>Password Column:</label>
<select [(ngModel)]="columnMapping.password" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
<div class="col-md-6">
<label>API Port Column:</label>
<select [(ngModel)]="columnMapping.port" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
</div>
</div>
</div>
<div *ngIf="addDeviceStep === 2" class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<h5 class="mt-3">{{uploadStatus}}</h5>
</div>
<div *ngIf="addDeviceStep === 3">
<h5>Upload Complete</h5>
<div class="alert alert-success">
<strong>Success:</strong> {{uploadResult.success}} devices added successfully<br>
<strong>Failed:</strong> {{uploadResult.failed}} devices failed to add
</div>
<button cButton color="primary" (click)="downloadResults()" *ngIf="uploadResult.resultFile">
<i class="fas fa-download"></i> Download Results
</button>
</div>
</c-modal-body>
<c-modal-footer>
<button *ngIf="addDeviceStep === 1" cButton color="primary" (click)="uploadDevices()" [disabled]="!isValidMapping()">
Add Devices
</button>
<button *ngIf="addDeviceStep === 3" cButton color="success" (click)="closeAddDeviceModal()">
Close
</button>
<button *ngIf="addDeviceStep !== 2" cButton color="secondary" (click)="closeAddDeviceModal()">
Cancel
</button>
</c-modal-footer>
</c-modal>
<!-- Web Access Modal -->
<c-modal #WebAccessModal backdrop="static" [(visible)]="showWebAccessModal" id="WebAccessModal">
<c-modal-header>
<h6 cModalTitle>Web Access Options</h6>
<button cButtonClose (click)="closeWebAccessModal()"></button>
</c-modal-header>
<c-modal-body>
<p>Choose how to access the device:</p>
<div class="d-grid gap-2">
<button *ngIf="ispro" cButton color="primary" (click)="openProxyAccess()">
<i class="fas fa-shield-alt"></i> Proxy Access, through Mikrowizard server
</button>
<button cButton color="secondary" (click)="openDirectAccess()">
<i class="fas fa-external-link-alt"></i> Direct Access
</button>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="secondary" (click)="closeWebAccessModal()">Cancel</button>
</c-modal-footer>
</c-modal>
<!-- Task Details Modal -->
<c-modal #TaskDetailsModal backdrop="static" size="xl" [(visible)]="detailsModalVisible" id="TaskDetailsModal"
style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal-header style="background: rgba(255,255,255,0.95); backdrop-filter: blur(10px);">
<h5 cModalTitle>{{getTaskTypeLabel(selectedTaskDetails?.task_type)}} Results</h5>
<button (click)="closeDetailsModal()" cButtonClose></button>
</c-modal-header>
<c-modal-body *ngIf="selectedTaskDetails" style="background: rgba(255,255,255,0.98); backdrop-filter: blur(10px); max-height: 70vh; overflow-y: auto;">
<div class="mb-3">
<c-row>
<c-col md="6">
<strong>Task Type:</strong> {{getTaskTypeLabel(selectedTaskDetails.task_type)}}<br>
<strong>Started:</strong> {{selectedTaskDetails.started}}<br>
<strong>Completed:</strong> {{selectedTaskDetails.ended}}
</c-col>
<c-col md="6">
<div *ngIf="selectedTaskDetails.task_type === 'ip-scan'">
<strong>IP Range:</strong> {{selectedTaskDetails.start_ip}} - {{selectedTaskDetails.end_ip}}<br>
<strong>Username:</strong> {{selectedTaskDetails.username}}
</div>
<div *ngIf="selectedTaskDetails.task_type === 'bulk-add'">
<strong>Task ID:</strong> {{selectedTaskDetails.task_id}}<br>
<strong>Total Devices:</strong> {{selectedTaskDetails.device_count}}
</div>
</c-col>
</c-row>
</div>
<hr>
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Detailed Results:</h6>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" placeholder="Search IP or error..."
[(ngModel)]="detailsSearchTerm" (input)="onDetailsSearch()">
</div>
</div>
<table class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>IP Address</th>
<th>Status</th>
<th *ngIf="selectedTaskDetails.task_type === 'bulk-add'">Error Details</th>
<th *ngIf="selectedTaskDetails.task_type === 'ip-scan'">Error Details</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let result of detailsPaginatedResults">
<td>{{result.ip}}</td>
<td>
<c-badge [color]="result.added ? 'success' : 'danger'">
{{result.added ? 'Success' : 'Failed'}}
</c-badge>
</td>
<td *ngIf="selectedTaskDetails.task_type === 'bulk-add'">
{{result.failures || 'N/A'}}
</td>
<td *ngIf="selectedTaskDetails.task_type === 'ip-scan'">
{{result.faileres || 'N/A'}}
</td>
</tr>
</tbody>
</table>
<nav *ngIf="getTotalDetailsPages() > 1" class="mt-3">
<ul class="pagination pagination-sm justify-content-center">
<li class="page-item" [class.disabled]="detailsCurrentPage === 1">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage - 1)" [disabled]="detailsCurrentPage === 1">
Previous
</button>
</li>
<li class="page-item" *ngFor="let page of [].constructor(getTotalDetailsPages()); let i = index"
[class.active]="detailsCurrentPage === i + 1">
<button class="page-link" (click)="onDetailsPageChange(i + 1)">
{{i + 1}}
</button>
</li>
<li class="page-item" [class.disabled]="detailsCurrentPage === getTotalDetailsPages()">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage + 1)"
[disabled]="detailsCurrentPage === getTotalDetailsPages()">
Next
</button>
</li>
</ul>
</nav>
<div class="mt-3">
<c-badge color="success" class="me-2">Success: {{selectedTaskDetails.success_count}}</c-badge>
<c-badge color="danger">Failed: {{selectedTaskDetails.failed_count}}</c-badge>
</div>
</c-modal-body>
<c-modal-footer>
<button (click)="exportToCsv(selectedTaskDetails.result)" color="primary" cButton class="me-2">
<i class="fas fa-download"></i> Download CSV
</button>
<button (click)="closeDetailsModal()" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
<c-toaster position="fixed" placement="top-end"></c-toaster> <c-toaster position="fixed" placement="top-end"></c-toaster>

View file

@ -39,7 +39,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
public tz: string; public tz: string;
public ispro:boolean=false; public ispro:boolean=false;
constructor( constructor(
private data_provider: dataProvider, private data_provider: dataProvider,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
@ -72,6 +72,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
@ViewChild("grid", { static: true }) gridComponent: GuiGridComponent; @ViewChild("grid", { static: true }) gridComponent: GuiGridComponent;
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>; @ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = []; public source: Array<any> = [];
public originalSource: Array<any> = [];
public columns: Array<GuiColumn> = []; public columns: Array<GuiColumn> = [];
public loading: boolean = true; public loading: boolean = true;
public rows: any = []; public rows: any = [];
@ -94,6 +95,28 @@ export class DevicesComponent implements OnInit, OnDestroy {
public show_pass: boolean = false; public show_pass: boolean = false;
public ExecutedDataModalVisible: boolean = false; public ExecutedDataModalVisible: boolean = false;
public ExecutedData: any = []; public ExecutedData: any = [];
public filteredExecutedData: any = [];
public selectedTaskType: string = 'all';
public detailsModalVisible: boolean = false;
public selectedTaskDetails: any = null;
public detailsCurrentPage: number = 1;
public detailsPageSize: number = 10;
public detailsPaginatedResults: any[] = [];
public detailsSearchTerm: string = '';
public filteredDetailsResults: any[] = [];
public showWebAccessModal: boolean = false;
public currentDeviceInfo: any = null;
public addDeviceModalVisible: boolean = false;
public addDeviceStep: number = 1;
public csvFile: File | null = null;
public csvData: any[] = [];
public csvHeaders: string[] = [];
public csvPreview: any[] = [];
public columnMapping = { ip: '', username: '', password: '', port: '' };
public uploadStatus: string = 'Processing devices...';
public uploadResult = { success: 0, failed: 0, resultFile: null };
public currentTaskId: string = '';
public statusCheckTimer: any;
toasterForm = { toasterForm = {
autohide: true, autohide: true,
@ -165,10 +188,12 @@ export class DevicesComponent implements OnInit, OnDestroy {
this.check_firmware(); this.check_firmware();
break; break;
case "update": case "update":
this.update_firmware(); this.ConfirmAction = "update";
this.ConfirmModalVisible = true;
break; break;
case "upgrade": case "upgrade":
this.upgrade_firmware(); this.ConfirmAction = "upgrade";
this.ConfirmModalVisible = true;
break; break;
case "logauth": case "logauth":
this.router.navigate(["/authlog", { devid: dev.id }]); this.router.navigate(["/authlog", { devid: dev.id }]);
@ -183,7 +208,8 @@ export class DevicesComponent implements OnInit, OnDestroy {
this.router.navigate(["/backups", { devid: dev.id }]); this.router.navigate(["/backups", { devid: dev.id }]);
break; break;
case "reboot": case "reboot":
this.reboot_devices(); this.ConfirmAction = "reboot";
this.ConfirmModalVisible = true;
break; break;
case "delete": case "delete":
this.ConfirmAction = "delete"; this.ConfirmAction = "delete";
@ -374,6 +400,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
} }
check_firmware() { check_firmware() {
var _self = this; var _self = this;
this.ConfirmModalVisible = false;
this.data_provider this.data_provider
.check_firmware(this.Selectedrows.toString()) .check_firmware(this.Selectedrows.toString())
.then((res) => { .then((res) => {
@ -396,6 +423,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
update_firmware() { update_firmware() {
var _self = this; var _self = this;
this.ConfirmModalVisible = false;
this.data_provider this.data_provider
.update_firmware(this.Selectedrows.toString()) .update_firmware(this.Selectedrows.toString())
.then((res) => { .then((res) => {
@ -417,6 +445,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
upgrade_firmware() { upgrade_firmware() {
var _self = this; var _self = this;
this.ConfirmModalVisible = false;
this.data_provider this.data_provider
.upgrade_firmware(this.Selectedrows.toString()) .upgrade_firmware(this.Selectedrows.toString())
.then((res) => { .then((res) => {
@ -436,6 +465,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
reboot_devices() { reboot_devices() {
var _self = this; var _self = this;
this.ConfirmModalVisible = false;
this.data_provider this.data_provider
.reboot_devices(this.Selectedrows.toString()) .reboot_devices(this.Selectedrows.toString())
.then((res) => { .then((res) => {
@ -492,11 +522,12 @@ export class DevicesComponent implements OnInit, OnDestroy {
); );
} }
else{ else{
_self.source = res.map((x: any) => { _self.originalSource = res.map((x: any) => {
if (x.upgrade_availble) _self.upgrades.push(x); if (x.upgrade_availble) _self.upgrades.push(x);
if (x.update_availble) _self.updates.push(x); if (x.update_availble) _self.updates.push(x);
return x; return x;
}); });
_self.source = [..._self.originalSource];
_self.device_interval(); _self.device_interval();
_self.loading = false; _self.loading = false;
} }
@ -637,26 +668,273 @@ export class DevicesComponent implements OnInit, OnDestroy {
_self.tz, _self.tz,
"yyyy-MM-dd HH:mm:ss XXX" "yyyy-MM-dd HH:mm:ss XXX"
); );
d.start_ip=d.info.start_ip; d.start_ip=d.info.start_ip || 'N/A';
d.end_ip=d.info.end_ip; d.end_ip=d.info.end_ip || 'N/A';
d.task_id=d.info.task_id || 'N/A';
d.device_count=d.info.device_count || 0;
d.username=d.info.username || 'N/A';
d.result=JSON.parse(d.result); d.result=JSON.parse(d.result);
d.success_count = d.result.filter((r: any) => r.added === true).length;
d.failed_count = d.result.filter((r: any) => r.added === false).length;
index += 1; index += 1;
return d; return d;
}); });
_self.filteredExecutedData = [..._self.ExecutedData];
} }
}); });
} }
filterByTaskType() {
if (this.selectedTaskType === 'all') {
this.filteredExecutedData = [...this.ExecutedData];
} else {
this.filteredExecutedData = this.ExecutedData.filter((d: any) => d.task_type === this.selectedTaskType);
}
}
showTaskDetails(task: any) {
this.selectedTaskDetails = task;
this.detailsCurrentPage = 1;
this.updateDetailsPagination();
this.detailsModalVisible = true;
}
updateDetailsPagination() {
if (!this.selectedTaskDetails?.result) return;
// Filter results based on search term
this.filteredDetailsResults = this.selectedTaskDetails.result.filter((result: any) =>
result.ip.toLowerCase().includes(this.detailsSearchTerm.toLowerCase()) ||
(result.failures && result.failures.toLowerCase().includes(this.detailsSearchTerm.toLowerCase())) ||
(result.faileres && result.faileres.toLowerCase().includes(this.detailsSearchTerm.toLowerCase()))
);
const startIndex = (this.detailsCurrentPage - 1) * this.detailsPageSize;
const endIndex = startIndex + this.detailsPageSize;
this.detailsPaginatedResults = this.filteredDetailsResults.slice(startIndex, endIndex);
}
onDetailsPageChange(page: number) {
this.detailsCurrentPage = page;
this.updateDetailsPagination();
}
getTotalDetailsPages(): number {
if (!this.filteredDetailsResults) return 0;
return Math.ceil(this.filteredDetailsResults.length / this.detailsPageSize);
}
onDetailsSearch() {
this.detailsCurrentPage = 1;
this.updateDetailsPagination();
}
closeDetailsModal() {
this.detailsModalVisible = false;
this.selectedTaskDetails = null;
this.detailsPaginatedResults = [];
this.filteredDetailsResults = [];
this.detailsCurrentPage = 1;
this.detailsSearchTerm = '';
}
filterUpdatable() {
this.source = this.originalSource.filter(device => device.update_availble);
}
filterUpgradable() {
this.source = this.originalSource.filter(device => device.upgrade_availble);
}
clearFilter() {
this.source = [...this.originalSource];
}
webAccess(device: any) {
this.currentDeviceInfo = device;
if (this.ispro) {
this.showWebAccessModal = true;
} else {
this.openDirectAccess();
}
}
openProxyAccess() {
if (this.currentDeviceInfo?.id) {
window.open(`/api/proxy/init?devid=${this.currentDeviceInfo.id}`, '_blank');
} else {
const ip = this.currentDeviceInfo?.ip;
if (ip) {
window.open(`/api/proxy/init?dev_ip=${ip}`, '_blank');
}
}
this.showWebAccessModal = false;
}
openDirectAccess() {
const ip = this.currentDeviceInfo?.ip;
if (ip) {
window.open(`http://${ip}`, '_blank');
}
this.showWebAccessModal = false;
}
closeWebAccessModal() {
this.showWebAccessModal = false;
}
openAddDeviceModal() {
this.addDeviceModalVisible = true;
this.resetAddDeviceForm();
}
closeAddDeviceModal() {
this.addDeviceModalVisible = false;
this.resetAddDeviceForm();
}
resetAddDeviceForm() {
this.addDeviceStep = 1;
this.csvFile = null;
this.csvData = [];
this.csvHeaders = [];
this.csvPreview = [];
this.columnMapping = { ip: '', username: '', password: '', port: '' };
this.uploadStatus = 'Processing devices...';
this.uploadResult = { success: 0, failed: 0, resultFile: null };
this.currentTaskId = '';
clearTimeout(this.statusCheckTimer);
}
onFileSelected(event: any) {
const file = event.target.files[0];
if (file && file.type === 'text/csv') {
this.csvFile = file;
this.parseCSV(file);
}
}
parseCSV(file: File) {
const reader = new FileReader();
reader.onload = (e: any) => {
const csv = e.target.result;
const lines = csv.split('\n').filter((line: string) => line.trim());
if (lines.length > 0) {
this.csvHeaders = lines[0].split(',').map((header: string) => header.trim());
this.csvData = lines.slice(1).map((line: string) =>
line.split(',').map((cell: string) => cell.trim())
);
this.csvPreview = this.csvData.slice(0, 3);
}
};
reader.readAsText(file);
}
isValidMapping(): boolean {
return this.columnMapping.ip !== '' &&
this.columnMapping.username !== '' &&
this.columnMapping.password !== '' &&
this.columnMapping.port !== '' &&
this.csvData.length > 0;
}
uploadDevices() {
if (!this.isValidMapping()) return;
this.addDeviceStep = 2;
const devices = this.csvData.map(row => ({
ip: row[parseInt(this.columnMapping.ip)],
username: row[parseInt(this.columnMapping.username)],
password: row[parseInt(this.columnMapping.password)],
port: row[parseInt(this.columnMapping.port)]
}));
this.data_provider.bulk_add_devices(devices).then((res) => {
if ('error' in res) {
this.addDeviceStep = 3;
this.show_toast('Error', 'Failed to start device upload', 'danger');
this.uploadResult = { success: 0, failed: devices.length, resultFile: null };
} else if ('taskId' in res) {
this.currentTaskId = res.taskId;
this.uploadStatus = 'Processing devices...';
this.checkUploadStatus();
} else {
this.addDeviceStep = 3;
this.show_toast('Error', 'Invalid response from server', 'danger');
this.uploadResult = { success: 0, failed: devices.length, resultFile: null };
}
}).catch(() => {
this.addDeviceStep = 3;
this.uploadResult = { success: 0, failed: devices.length, resultFile: null };
this.show_toast('Error', 'Failed to upload devices', 'danger');
});
}
checkUploadStatus() {
clearTimeout(this.statusCheckTimer);
this.data_provider.bulk_add_status(this.currentTaskId).then((res) => {
if ('error' in res) {
this.addDeviceStep = 3;
this.show_toast('Error', 'Failed to check upload status', 'danger');
this.uploadResult = { success: 0, failed: 0, resultFile: null };
return;
}
if (res.status === 'completed') {
this.addDeviceStep = 3;
this.uploadResult = {
success: res.success || 0,
failed: res.failed || 0,
resultFile: res.resultFile || null
};
this.show_toast('Success', `${res.success} devices added successfully`, 'success');
this.initGridTable();
} else if (res.status === 'failed') {
this.addDeviceStep = 3;
this.show_toast('Error', res.message || 'Upload failed', 'danger');
this.uploadResult = { success: 0, failed: 0, resultFile: null };
} else {
// Still processing
this.uploadStatus = res.message || 'Processing devices...';
this.statusCheckTimer = setTimeout(() => {
this.checkUploadStatus();
}, 3000);
}
}).catch(() => {
this.addDeviceStep = 3;
this.show_toast('Error', 'Failed to check upload status', 'danger');
this.uploadResult = { success: 0, failed: 0, resultFile: null };
});
}
downloadResults() {
if (this.uploadResult.resultFile) {
const link = document.createElement('a');
link.href = this.uploadResult.resultFile;
link.download = 'device_upload_results.csv';
link.click();
}
}
getTaskTypeLabel(taskType: string): string {
switch(taskType) {
case 'ip-scan': return 'IP Scan';
case 'bulk-add': return 'Bulk Add';
default: return taskType;
}
}
getStatusColor(success: number, failed: number): string {
if (failed === 0) return 'success';
if (success === 0) return 'danger';
return 'warning';
}
ngOnDestroy(): void { ngOnDestroy(): void {
clearTimeout(this.scan_timer); clearTimeout(this.scan_timer);
clearTimeout(this.statusCheckTimer);
} }
} }

View file

@ -17,6 +17,7 @@ import {
ModalModule, ModalModule,
ListGroupModule, ListGroupModule,
TooltipModule, TooltipModule,
TableModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { MatMenuModule } from "@angular/material/menu"; import { MatMenuModule } from "@angular/material/menu";
import { DevicesRoutingModule } from "./devices-routing.module"; import { DevicesRoutingModule } from "./devices-routing.module";
@ -44,6 +45,7 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
ListGroupModule, ListGroupModule,
MatMenuModule, MatMenuModule,
TooltipModule, TooltipModule,
TableModule,
], ],
declarations: [DevicesComponent], declarations: [DevicesComponent],
}) })

View file

@ -25,29 +25,67 @@
<ng-container *ngIf="item.id==1 ; then Default;else NotDefault"> <ng-container *ngIf="item.id==1 ; then Default;else NotDefault">
</ng-container> </ng-container>
<ng-template #Default> <ng-template #Default>
<c-badge color="info">All Devices</c-badge> <c-badge color="info"><i class="fa-solid fa-network-wired me-1"></i>All Devices</c-badge>
</ng-template> </ng-template>
<ng-template #NotDefault> <ng-template #NotDefault>
<c-badge color="info" *ngIf="value[0]==null && item.id!=1">0 Members</c-badge> <c-badge color="info" *ngIf="value[0]==null && item.id!=1"
<c-badge color="info" *ngIf="value[0]!=null">{{value.length}} Members</c-badge> [cTooltip]="'No devices assigned'" style="cursor: help">
<i class="fa-solid fa-server me-1"></i>0
</c-badge>
<c-badge color="info" *ngIf="value[0]!=null"
[cTooltip]="getDevicesTooltip(item)"
[cTooltipPlacement]="'top'" style="cursor: help">
<i class="fa-solid fa-server me-1"></i>{{value.length}}
</c-badge>
</ng-template> </ng-template>
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
<gui-grid-column header="Create Time" field="created"> <gui-grid-column header="Create Time" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index"> <ng-template let-value="item.created" let-item="item" let-index="index">
{{formatCreateTime(value)}}
{{value}}
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
<gui-grid-column header="Actions" field="action"> <gui-grid-column header="Users" field="assigned_users" width="80" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index"> <ng-template let-value="item.assigned_users" let-item="item" let-index="index">
<button [disabled]="value==1" cButton color="warning" size="sm" (click)="editAddGroup(item,'showedit');" <c-badge color="info"
class="mx-1"><i class="fa-regular fa-pen-to-square"></i></button> [cTooltip]="getUsersTooltip(value)"
<button [disabled]="value==1" cButton color="info" size="sm" (click)="show_members(item.id);" [cTooltipPlacement]="'top'"
class="mx-1"><i class="fa-regular fa-eye"></i></button> style="cursor: help">
<button [disabled]="value==1" cButton color="danger" size="sm" (click)="show_delete_group(item);" <i class="fa-solid fa-users me-1"></i>{{value.length}}
class="mx-1"><i class="fa-regular fa-trash-can"></i></button> </c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column align="center" [cellEditing]="false" [sorting]="false" header="Action">
<ng-template let-value="value" let-item="item">
<button size="sm" shape="rounded-0" variant="outline" cButton color="primary" (click)="show_members(item.id)"
style="border: none;padding: 4px 7px;"><i class="fa-regular fa-eye"></i><small> View devices</small>
</button>
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
[matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<li cListGroupItem [active]="false" color="dark">Actions Menu</li>
<button size="sm" (click)="editAddGroup(item,'showedit')" style="padding: 4px 7px;"
[disabled]="item.id==1" cListGroupItem><i class="fa-solid fa-pencil"></i><small>
Edit Group</small></button>
<button size="sm" (click)="manageUsers(item)" style="padding: 4px 7px;"
cListGroupItem><i class="fa-solid fa-users-gear"></i><small>
Manage Users</small></button>
<button size="sm" (click)="groupFirmwareAction(item, 'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-success fa-solid fa-upload"></i><small>
Update Firmware</small></button>
<button size="sm" (click)="groupFirmwareAction(item, 'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-secondary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button>
<button size="sm" (click)="show_delete_group(item)" style="padding: 4px 7px;"
[disabled]="item.id==1" cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
Delete Group</small></button>
</div>
</mat-menu>
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
</gui-grid> </gui-grid>
@ -59,92 +97,146 @@
<c-modal #EditGroupModal backdrop="static" size="lg" [(visible)]="EditGroupModalVisible" id="EditGroupModal"> <c-modal #EditGroupModal backdrop="static" size="xl" [(visible)]="EditGroupModalVisible" id="EditGroupModal">
<c-modal-header> <c-modal-header class="bg-light">
<h5 cModalTitle> Group Edit</h5> <h5 cModalTitle><i class="fa-solid fa-edit me-2"></i>Edit Device Group</h5>
<button [cModalToggle]="EditGroupModal.id" cButtonClose></button> <button [cModalToggle]="EditGroupModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-4">
<c-input-group class="mb-3"> <!-- Group Basic Info -->
<div [cFormFloating]="true" class="mb-3"> <div class="bg-light p-3 rounded mb-3 d-flex align-items-center justify-content-between">
<input cFormControl id="floatingInput" placeholder="Group Name" [(ngModel)]="currentGroup['name']" /> <div class="d-flex align-items-center flex-grow-1 me-3">
<label cLabel for="floatingInput">Group Name</label> <label class="form-label me-2 mb-0 fw-semibold">Group Name:</label>
<input cFormControl [(ngModel)]="currentGroup['name']" class="form-control-sm" style="max-width: 300px;" />
</div> </div>
</c-input-group> <c-badge color="info" class="fs-6 p-2">
<c-input-group class="mb-3"> <i class="fa-solid fa-server me-1"></i>{{groupMembers.length}} Devices
<h5>Group Members :</h5> </c-badge>
<gui-grid [autoResizeWidth]="true" [searching]="searching" [source]="groupMembers" [columnMenu]="columnMenu" </div>
[sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
(selectedRows)="onSelectedRowsMembers($event)" [autoResizeWidth]=true [paging]="paging"> <!-- Current Members -->
<gui-grid-column header="Member Name" field="name"> <c-card class="mb-3">
<ng-template let-value="item.name" let-item="item" let-index="index"> <c-card-header class="d-flex justify-content-between align-items-center">
&nbsp; {{value}} </ng-template> <h6 class="mb-0"><i class="fa-solid fa-list me-2"></i>Current Group Members</h6>
</gui-grid-column> <div>
<gui-grid-column header="perm Name" field="ip"> <button *ngIf="MemberRows.length > 0" cButton color="danger" size="sm" variant="outline" class="me-2">
<ng-template let-value="item.ip" let-item="item" let-index="index"> <i class="fa-solid fa-trash me-1"></i>Remove {{MemberRows.length}} Selected
{{value}} </button>
</ng-template> <button cButton color="success" size="sm" (click)="show_new_member_form()">
</gui-grid-column> <i class="fa-solid fa-plus me-1"></i>Add Devices
<gui-grid-column header="Actions" width="120" field="action"> </button>
<ng-template let-value="item.id" let-item="item" let-index="index"> </div>
<button cButton color="danger" size="sm" (click)="remove_from_group(item.id)"><i </c-card-header>
class="fa-regular fa-trash-can"></i></button> <c-card-body class="p-0">
</ng-template> <div *ngIf="groupMembers.length === 0" class="text-center p-4 text-muted">
</gui-grid-column> <i class="fa-solid fa-server fa-3x mb-3 opacity-50"></i>
</gui-grid> <h6>No devices in this group</h6>
<br /> <p class="mb-0">Click "Add Devices" to start adding devices to this group</p>
<button *ngIf="MemberRows.length!= 0" style="margin: 10px 0;" cButton color="danger" size="sm"><i </div>
class="fa-regular fa-trash-can"></i>Delete {{MemberRows.length}} Selected</button> <div *ngIf="groupMembers.length > 0">
</c-input-group> <gui-grid [autoResizeWidth]="true" [searching]="searching" [source]="groupMembers" [columnMenu]="columnMenu"
<hr /> [sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
<button cButton color="primary" (click)="show_new_member_form()">+ Add new Members</button> (selectedRows)="onSelectedRowsMembers($event)" [paging]="paging">
<gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_from_group(item.id)" title="Remove from group">
<i class="fa-solid fa-times"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer class="bg-light">
<button cButton color="primary" (click)="save_group()">save</button> <button cButton color="primary" (click)="save_group()">
<i class="fa-solid fa-save me-1"></i>Save Changes
</button>
<button [cModalToggle]="EditGroupModal.id" cButton color="secondary"> <button [cModalToggle]="EditGroupModal.id" cButton color="secondary">
Close <i class="fa-solid fa-times me-1"></i>Cancel
</button> </button>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<c-modal #NewMemberModal backdrop="static" size="lg" [(visible)]="NewMemberModalVisible" id="NewMemberModal"> <c-modal #NewMemberModal [backdrop]="true" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal" style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal-header> <c-modal-header class="bg-success text-white">
<h5 cModalTitle>Members not in Group</h5> <h5 cModalTitle><i class="fa-solid fa-plus-circle me-2"></i>Add Devices to Group</h5>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button> <button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-4">
<c-input-group class="mb-3"> <!-- Selection Summary -->
<h5>Members Availble to add:</h5> <div class="mb-3" style="min-height: 58px;">
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching" <c-alert *ngIf="NewMemberRows.length > 0" color="info" class="d-flex align-items-center mb-0">
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" <i class="fa-solid fa-info-circle me-2"></i>
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true <span><strong>{{NewMemberRows.length}}</strong> device(s) selected for addition</span>
[paging]="paging"> </c-alert>
<gui-grid-column header="Group Name" field="name"> </div>
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template> <!-- Available Devices -->
</gui-grid-column> <c-card>
<gui-grid-column header="perm Name" field="ip"> <c-card-header>
<ng-template let-value="item.ip" let-item="item" let-index="index"> <h6 class="mb-0"><i class="fa-solid fa-server me-2"></i>Available Devices ({{availbleMembers.length}} total)</h6>
{{value}} </c-card-header>
</ng-template> <c-card-body class="p-0">
</gui-grid-column> <div *ngIf="availbleMembers.length === 0" class="text-center p-4 text-muted">
<gui-grid-column header="perm Name" field="mac"> <i class="fa-solid fa-check-circle fa-3x mb-3 text-success opacity-50"></i>
<ng-template let-value="item.mac" let-item="item" let-index="index"> <h6>All devices are already in groups</h6>
{{value}} <p class="mb-0">No available devices to add to this group</p>
</ng-template> </div>
</gui-grid-column> <div *ngIf="availbleMembers.length > 0">
</gui-grid> <gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
<br /> [source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
</c-input-group> [rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [paging]="paging">
<hr /> <gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer class="bg-light d-flex justify-content-between">
<button *ngIf="NewMemberRows.length!= 0" (click)="add_new_members()" cButton color="primary">Add {{ <div>
NewMemberRows.length }}</button> <small class="text-muted">Select devices from the list above to add them to the group</small>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary"> </div>
Close <div>
</button> <button *ngIf="NewMemberRows.length > 0" (click)="add_new_members()" cButton color="success">
<i class="fa-solid fa-plus me-1"></i>Add {{NewMemberRows.length}} Device(s)
</button>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary" class="ms-2">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
</div>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
@ -175,4 +267,200 @@
Close Close
</button> </button>
</c-modal-footer> </c-modal-footer>
</c-modal>
<c-modal #UserManagementModal backdrop="static" size="xl" [(visible)]="UserManagementModalVisible" id="UserManagementModal">
<c-modal-header>
<h5 cModalTitle><i class="fa-solid fa-users-gear me-2"></i>User Permissions - {{selectedGroup?.name}}</h5>
<button [cModalToggle]="UserManagementModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<c-card class="mb-3">
<c-card-header class="bg-light">
<h6 class="mb-0"><i class="fa-solid fa-user-plus me-2"></i>Add User Permission</h6>
</c-card-header>
<c-card-body>
<c-row class="g-3">
<c-col md="4">
<div class="search-select-wrapper">
<label class="search-label">User</label>
<input cFormControl
[(ngModel)]="userSearch"
(input)="filterUsers($event)"
(focus)="showUserDropdown = true"
(blur)="hideUserDropdown()"
placeholder="Search and select user..."
class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showUserDropdown && filteredUsers.length > 0" class="search-dropdown">
<div *ngFor="let user of filteredUsers"
class="search-option"
(mousedown)="selectUser(user)">
{{user.username}} ({{user.first_name}} {{user.last_name}})
</div>
</div>
<div *ngIf="showUserDropdown && filteredUsers.length === 0 && userSearch" class="search-no-results">
No users found
</div>
</div>
</c-col>
<c-col md="4">
<div class="search-select-wrapper">
<label class="search-label">Permission</label>
<input cFormControl
[(ngModel)]="permissionSearch"
(input)="filterPermissions($event)"
(focus)="showPermissionDropdown = true"
(blur)="hidePermissionDropdown()"
placeholder="Search and select permission..."
class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
<div *ngFor="let perm of filteredPermissions"
class="search-option"
(mousedown)="selectPermission(perm)">
{{perm.name}}
</div>
</div>
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch" class="search-no-results">
No permissions found
</div>
</div>
</c-col>
<c-col md="4" class="d-flex align-items-end">
<button cButton color="success" (click)="addUserPermission()" [disabled]="!selectedUser || !selectedPermission">
<i class="fa-solid fa-plus me-1"></i>Add Permission
</button>
</c-col>
</c-row>
</c-card-body>
</c-card>
<c-card>
<c-card-header class="bg-light">
<h6 class="mb-0"><i class="fa-solid fa-list-check me-2"></i>Current Permissions ({{selectedGroup?.assigned_users?.length || 0}} users)</h6>
</c-card-header>
<c-card-body class="p-0">
<div *ngIf="selectedGroup?.assigned_users?.length === 0" class="text-center p-4 text-muted">
<i class="fa-solid fa-users-slash fa-2x mb-2"></i>
<p class="mb-0">No users have permissions for this group</p>
</div>
<div *ngIf="selectedGroup?.assigned_users?.length > 0" class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th width="40">#</th>
<th>User</th>
<th>Name</th>
<th>Permission</th>
<th width="200">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of selectedGroup.assigned_users; let i = index">
<td class="text-muted">{{i + 1}}</td>
<td>
<div class="d-flex align-items-center">
<i class="fa-solid fa-user me-2 text-primary"></i>
<strong>{{user.username}}</strong>
</div>
</td>
<td>{{user.first_name}} {{user.last_name}}</td>
<td>
<c-badge [color]="getPermissionColor(user.perm_name)">{{user.perm_name}}</c-badge>
</td>
<td>
<div class="btn-group" role="group">
<button cButton color="warning" size="sm" variant="outline"
(click)="editUserPermission(user)" title="Change Permission"
[disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
<i class="fa-solid fa-edit"></i>
</button>
<button cButton color="danger" size="sm" variant="outline"
(click)="removeUserPermission(user)" title="Remove Permission"
[disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</c-card-body>
</c-card>
</c-modal-body>
<c-modal-footer>
<button [cModalToggle]="UserManagementModal.id" cButton color="secondary">
<i class="fa-solid fa-times me-1"></i>Close
</button>
</c-modal-footer>
</c-modal>
<c-modal #EditPermissionModal backdrop="static" [(visible)]="EditPermissionModalVisible" id="EditPermissionModal">
<c-modal-header>
<h5 cModalTitle>Change Permission for {{editingUser?.username}}</h5>
<button [cModalToggle]="EditPermissionModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<div class="mb-3">
<label class="form-label">Current Permission: <c-badge [color]="getPermissionColor(editingUser?.perm_name)">{{editingUser?.perm_name}}</c-badge></label>
</div>
<div class="mb-3">
<label class="form-label">New Permission</label>
<select cSelect [(ngModel)]="newPermissionId" class="form-select">
<option value="">Select Permission...</option>
<option *ngFor="let perm of availablePermissions" [value]="perm.id">{{perm.name}}</option>
</select>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="primary" (click)="updateUserPermission()" [disabled]="!newPermissionId">
<i class="fa-solid fa-save me-1"></i>Update
</button>
<button [cModalToggle]="EditPermissionModal.id" cButton color="secondary">
Cancel
</button>
</c-modal-footer>
</c-modal>
<c-modal #RemovePermissionModal backdrop="static" [(visible)]="RemovePermissionModalVisible" id="RemovePermissionModal">
<c-modal-header>
<h5 cModalTitle>Remove Permission</h5>
<button [cModalToggle]="RemovePermissionModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<div class="text-center">
<i class="fa-solid fa-exclamation-triangle fa-3x text-warning mb-3"></i>
<p>Are you sure you want to remove <strong>{{removingUser?.username}}</strong>'s permission from group <strong>{{selectedGroup?.name}}</strong>?</p>
<p class="text-muted small">This action cannot be undone.</p>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="danger" (click)="confirmRemovePermission()">
<i class="fa-solid fa-trash me-1"></i>Remove
</button>
<button [cModalToggle]="RemovePermissionModal.id" cButton color="secondary">
Cancel
</button>
</c-modal-footer>
</c-modal>
<c-modal #FirmwareConfirmModal backdrop="static" [(visible)]="FirmwareConfirmModalVisible" id="FirmwareConfirmModal">
<c-modal-header>
<h5 cModalTitle>Confirm Firmware {{firmwareAction | titlecase}}</h5>
<button [cModalToggle]="FirmwareConfirmModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<div class="text-center">
<i class="fa-solid fa-exclamation-triangle fa-3x text-warning mb-3"></i>
<p>Are you sure you want to <strong>{{firmwareAction}}</strong> firmware for all devices in group <strong>{{selectedGroupForFirmware?.name}}</strong>?</p>
<p class="text-muted small">This action will affect all devices in this group and may take some time to complete.</p>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="primary" (click)="confirmGroupFirmwareAction()">
<i class="fa-solid fa-{{firmwareAction === 'update' ? 'upload' : 'microchip'}} me-1"></i>{{firmwareAction | titlecase}} Firmware
</button>
<button [cModalToggle]="FirmwareConfirmModal.id" cButton color="secondary">
Cancel
</button>
</c-modal-footer>
</c-modal> </c-modal>

View file

@ -0,0 +1,108 @@
.users-summary {
min-height: 60px;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.user-badges {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.user-badges c-badge {
font-size: 0.7rem;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width: 768px) {
.users-summary {
min-height: auto;
}
.user-badges c-badge {
max-width: 60px;
font-size: 0.65rem;
}
}
/* Search Select Components */
.search-select-wrapper {
position: relative;
}
.search-label {
display: block;
font-size: 0.8rem;
color: #6c757d;
margin-bottom: 0.25rem;
font-weight: 500;
}
.search-input {
width: 100%;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 0.375rem 0.375rem;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.search-option {
padding: 0.5rem 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f1f3f4;
font-size: 0.85rem;
transition: background-color 0.2s ease;
}
.search-option:hover {
background-color: #f8f9fa;
}
.search-option:last-child {
border-bottom: none;
}
.search-no-results {
padding: 0.75rem;
text-align: center;
color: #6c757d;
font-size: 0.8rem;
font-style: italic;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 0.375rem 0.375rem;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1000;
}
.compact-select {
font-size: 0.85rem;
height: calc(1.8em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
}
.compact-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}

View file

@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data"; import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker"; import { loginChecker } from "../../providers/login_checker";
import { formatInTimeZone } from "date-fns-tz";
import { import {
GuiSearching, GuiSearching,
GuiSelectedRow, GuiSelectedRow,
@ -31,10 +32,12 @@ interface IUser {
@Component({ @Component({
templateUrl: "devgroup.component.html", templateUrl: "devgroup.component.html",
styleUrls: ["devgroup.component.scss"]
}) })
export class DevicesGroupComponent implements OnInit { export class DevicesGroupComponent implements OnInit {
public uid: number; public uid: number;
public uname: string; public uname: string;
public tz: string;
constructor( constructor(
private data_provider: dataProvider, private data_provider: dataProvider,
@ -50,6 +53,7 @@ export class DevicesGroupComponent implements OnInit {
this.data_provider.getSessionInfo().then((res) => { this.data_provider.getSessionInfo().then((res) => {
_self.uid = res.uid; _self.uid = res.uid;
_self.uname = res.name; _self.uname = res.name;
_self.tz = res.tz;
const userId = _self.uid; const userId = _self.uid;
if (res.role != "admin") { if (res.role != "admin") {
@ -83,6 +87,30 @@ export class DevicesGroupComponent implements OnInit {
id: 0, id: 0,
name: "", name: "",
}; };
public selectedGroup: any = null;
public UserManagementModalVisible: boolean = false;
public EditPermissionModalVisible: boolean = false;
public RemovePermissionModalVisible: boolean = false;
public availableUsers: any[] = [];
public availablePermissions: any[] = [];
public selectedUserId: string = "";
public selectedPermId: string = "";
public selectedUser: any = null;
public selectedPermission: any = null;
public userSearch: string = '';
public permissionSearch: string = '';
public filteredUsers: any[] = [];
public filteredPermissions: any[] = [];
public showUserDropdown: boolean = false;
public showPermissionDropdown: boolean = false;
public editingUser: any = null;
public removingUser: any = null;
public newPermissionId: string = "";
private deviceCache: { [key: number]: any[] } = {};
private loadingDevices: { [key: number]: boolean } = {};
public FirmwareConfirmModalVisible: boolean = false;
public firmwareAction: string = "";
public selectedGroupForFirmware: any = null;
public DefaultCurrentGroup: any = { public DefaultCurrentGroup: any = {
array_agg: [], array_agg: [],
created: "", created: "",
@ -182,6 +210,9 @@ export class DevicesGroupComponent implements OnInit {
save_group() { save_group() {
var _self = this; var _self = this;
this.data_provider.update_save_group(this.currentGroup).then((res) => { this.data_provider.update_save_group(this.currentGroup).then((res) => {
// Clear device cache for this group
delete this.deviceCache[this.currentGroup.id];
delete this.loadingDevices[this.currentGroup.id];
_self.initGridTable(); _self.initGridTable();
_self.EditGroupModalVisible = false; _self.EditGroupModalVisible = false;
}); });
@ -237,4 +268,225 @@ export class DevicesGroupComponent implements OnInit {
this.loading = false; this.loading = false;
}); });
} }
manageUsers(group: any): void {
this.selectedGroup = { ...group };
this.loadAvailableUsers();
this.loadAvailablePermissions();
this.UserManagementModalVisible = true;
}
loadAvailableUsers(): void {
this.data_provider.get_users(1, 1000, "").then((res) => {
this.availableUsers = res.filter((user: any) =>
!this.selectedGroup.assigned_users.some((assignedUser: any) => assignedUser.user_id === user.id)
);
this.filteredUsers = [...this.availableUsers];
});
}
loadAvailablePermissions(): void {
this.data_provider.get_perms(1, 1000, "").then((res) => {
this.availablePermissions = res;
this.filteredPermissions = [...this.availablePermissions];
});
}
filterUsers(event: any): void {
const query = event.target.value.toLowerCase();
this.filteredUsers = this.availableUsers.filter((user: any) =>
user.username.toLowerCase().includes(query) ||
(user.first_name + ' ' + user.last_name).toLowerCase().includes(query)
);
}
filterPermissions(event: any): void {
const query = event.target.value.toLowerCase();
this.filteredPermissions = this.availablePermissions.filter((perm: any) =>
perm.name.toLowerCase().includes(query)
);
}
selectUser(user: any): void {
this.selectedUser = user;
this.selectedUserId = user.id;
this.userSearch = user.username + ' (' + user.first_name + ' ' + user.last_name + ')';
this.showUserDropdown = false;
}
selectPermission(perm: any): void {
this.selectedPermission = perm;
this.selectedPermId = perm.id;
this.permissionSearch = perm.name;
this.showPermissionDropdown = false;
}
hideUserDropdown(): void {
setTimeout(() => this.showUserDropdown = false, 200);
}
hidePermissionDropdown(): void {
setTimeout(() => this.showPermissionDropdown = false, 200);
}
addUserPermission(): void {
if (!this.selectedUser || !this.selectedPermission) return;
console.log('Adding user permission:', {
userId: this.selectedUser.id,
permissionId: this.selectedPermission.id,
groupId: this.selectedGroup.id,
selectedUser: this.selectedUser,
selectedPermission: this.selectedPermission
});
this.data_provider.Add_user_perm(this.selectedUser.id, +this.selectedPermission.id, this.selectedGroup.id)
.then((res) => {
console.log('Add user permission response:', res);
this.initGridTable();
this.selectedUserId = "";
this.selectedPermId = "";
this.selectedUser = null;
this.selectedPermission = null;
this.userSearch = "";
this.permissionSearch = "";
// Refresh the selected group data
this.data_provider.get_devgroup_list().then((groups) => {
this.selectedGroup = groups.find((g: any) => g.id === this.selectedGroup.id);
this.loadAvailableUsers();
});
});
}
editUserPermission(user: any): void {
this.editingUser = { ...user };
this.newPermissionId = user.perm_id.toString();
this.EditPermissionModalVisible = true;
}
updateUserPermission(): void {
if (!this.newPermissionId) return;
// Remove old permission and add new one
this.data_provider.Delete_user_perm(this.editingUser.id).then(() => {
this.data_provider.Add_user_perm(this.editingUser.user_id, +this.newPermissionId, this.selectedGroup.id)
.then(() => {
this.EditPermissionModalVisible = false;
this.initGridTable();
// Refresh the selected group data
this.data_provider.get_devgroup_list().then((groups) => {
this.selectedGroup = groups.find((g: any) => g.id === this.selectedGroup.id);
});
});
});
}
removeUserPermission(user: any): void {
this.removingUser = { ...user };
this.RemovePermissionModalVisible = true;
}
confirmRemovePermission(): void {
this.data_provider.Delete_user_perm(this.removingUser.id).then(() => {
this.RemovePermissionModalVisible = false;
this.initGridTable();
// Refresh the selected group data
this.data_provider.get_devgroup_list().then((groups) => {
this.selectedGroup = groups.find((g: any) => g.id === this.selectedGroup.id);
this.loadAvailableUsers();
});
});
}
getPermissionColor(permName: string): string {
const colorMap: { [key: string]: string } = {
'full': 'success',
'read': 'info',
'write': 'warning',
'admin': 'danger',
'test': 'secondary'
};
return colorMap[permName] || 'primary';
}
getUsersTooltip(users: any[]): string {
if (users.length === 0) return 'No users assigned';
const maxShow = 10;
const userList = users.slice(0, maxShow).map((user, index) =>
`${user.username} (${user.perm_name})`
).join('\n');
return users.length > maxShow
? `${userList}\n━━━━━━━━━━━━━━━━\n+${users.length - maxShow} more users`
: userList;
}
getDevicesTooltip(group: any): string {
if (group.id === 1) return 'All devices in the system';
if (!group.array_agg || group.array_agg[0] === null) return 'No devices assigned';
// Check if data is cached
if (this.deviceCache[group.id]) {
const devices = this.deviceCache[group.id];
const maxShow = 10;
const deviceList = devices.slice(0, maxShow).map(device =>
`${device.name} (${device.ip})`
).join('\n');
return devices.length > maxShow
? `${deviceList}\n━━━━━━━━━━━━━━━━\n+${devices.length - maxShow} more devices`
: deviceList;
}
// Check if already loading
if (this.loadingDevices[group.id]) {
return 'Loading devices...';
}
// Start loading
this.loadingDevices[group.id] = true;
this.data_provider.get_devgroup_members(group.id).then((devices) => {
this.deviceCache[group.id] = devices;
this.loadingDevices[group.id] = false;
}).catch(() => {
this.loadingDevices[group.id] = false;
});
return 'Loading devices...';
}
formatCreateTime(dateString: string): string {
if (!dateString || !this.tz) return dateString;
return formatInTimeZone(
dateString.split(".")[0] + ".000Z",
this.tz,
"yyyy-MM-dd HH:mm:ss XXX"
);
}
groupFirmwareAction(group: any, action: string): void {
this.selectedGroupForFirmware = group;
this.firmwareAction = action;
this.FirmwareConfirmModalVisible = true;
}
confirmGroupFirmwareAction(): void {
if (!this.selectedGroupForFirmware) return;
this.data_provider.group_firmware_action(this.selectedGroupForFirmware.id, this.firmwareAction)
.then((res) => {
if ("error" in res) {
console.error('Firmware action failed:', res.error);
} else {
const actionText = this.firmwareAction === 'update' ? 'Update' : 'Upgrade';
console.log(`${actionText} firmware initiated for group: ${this.selectedGroupForFirmware.name}`);
}
this.FirmwareConfirmModalVisible = false;
})
.catch((error) => {
console.error('Firmware action error:', error);
this.FirmwareConfirmModalVisible = false;
});
}
} }

View file

@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { import {
AlertModule,
ButtonGroupModule, ButtonGroupModule,
ButtonModule, ButtonModule,
CardModule, CardModule,
@ -9,15 +10,19 @@ import {
GridModule, GridModule,
CollapseModule, CollapseModule,
ModalModule, ModalModule,
TooltipModule,
ListGroupModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { DevicesGroupRoutingModule } from "./devgroup-routing.module"; import { DevicesGroupRoutingModule } from "./devgroup-routing.module";
import { DevicesGroupComponent } from "./devgroup.component"; import { DevicesGroupComponent } from "./devgroup.component";
import { GuiGridModule } from "@generic-ui/ngx-grid"; import { GuiGridModule } from "@generic-ui/ngx-grid";
import { BadgeModule } from "@coreui/angular"; import { BadgeModule } from "@coreui/angular";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { MatMenuModule } from "@angular/material/menu";
@NgModule({ @NgModule({
imports: [ imports: [
DevicesGroupRoutingModule, DevicesGroupRoutingModule,
AlertModule,
CardModule, CardModule,
CommonModule, CommonModule,
GridModule, GridModule,
@ -29,6 +34,9 @@ import { FormsModule } from "@angular/forms";
CollapseModule, CollapseModule,
ModalModule, ModalModule,
BadgeModule, BadgeModule,
TooltipModule,
MatMenuModule,
ListGroupModule,
], ],
declarations: [DevicesGroupComponent], declarations: [DevicesGroupComponent],
}) })

View file

@ -0,0 +1,377 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { loginChecker } from "../../providers/login_checker";
import { Router } from "@angular/router";
import { formatInTimeZone } from "date-fns-tz";
import { Network } from 'vis-network/peer';
import { DataSet } from 'vis-data';
@Component({
templateUrl: "maps.component.html",
styleUrls: ["maps.component.scss"],
})
export class MapsComponent implements OnInit {
public uid: number;
public uname: string;
public ispro: boolean = false;
public tz: string;
public savedPositions: any = {};
public savedPositionsKey = "network-layout";
public selectedDevice: any = null;
constructor(
private data_provider: dataProvider,
private router: Router,
private login_checker: loginChecker
) {
var _self = this;
if (!this.login_checker.isLoggedIn()) {
setTimeout(function () {
_self.router.navigate(["login"]);
}, 100);
}
this.data_provider.getSessionInfo().then((res) => {
_self.uid = res.uid;
_self.uname = res.name;
_self.ispro = res.ISPRO;
if (!_self.ispro)
setTimeout(function () {
_self.router.navigate(["dashboard"]);
}, 100);
_self.tz = res.tz;
});
}
@ViewChild('network', { static: true }) networkContainer: ElementRef | undefined;
mikrotikData: any[] = [];
ngOnInit(): void {
this.loadFontAwesome();
this.savedPositions = JSON.parse(localStorage.getItem(this.savedPositionsKey) || "{}");
this.loadNetworkData();
}
loadNetworkData(): void {
this.data_provider.getNetworkMap().then((res) => {
this.mikrotikData = res;
console.dir(res);
setTimeout(() => {
this.createNetworkMap();
}, 100);
});
}
loadFontAwesome() {
if (!document.querySelector('link[href*="font-awesome"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css';
document.head.appendChild(link);
}
}
createNetworkMap() {
const container = this.networkContainer?.nativeElement;
if (!container) return;
let nodes = new DataSet<any>([]);
let edges = new DataSet<any>([]);
let deviceMap: { [key: string]: string } = {}; // uniqueId to nodeId mapping
let allDevices: { [key: string]: any } = {}; // uniqueId to device info mapping
let macToDevice: { [key: string]: string } = {}; // MAC -> uniqueId mapping
const hasSavedPositions = Object.keys(this.savedPositions).length > 0;
let nodeIdCounter = 1;
const getUniqueId = (obj: any): string => {
if (obj.device_id) return `dev_${obj.device_id}`;
if (obj.mac) return `mac_${obj.mac}`;
if (obj.software_id) return `sw_${obj.software_id}`;
if (obj.hostname) return `host_${obj.hostname}`;
return `unknown_${obj.address || Math.random().toString(36).slice(2)}`;
};
// Collect all devices
this.mikrotikData.forEach((device) => {
const deviceId = device.device_id || `${device.name}_${Date.now()}`;
if (!allDevices[deviceId]) {
allDevices[deviceId] = {
name: device.name,
type: 'Router',
brand: 'MikroTik'
};
}
Object.entries(device.interfaces).forEach(([_, iface]: [string, any]) => {
if (iface.mac) {
macToDevice[iface.mac] = deviceId;
}
iface.neighbors.forEach((neighbor: any) => {
const neighborId =
neighbor.device_id ||
neighbor.software_id ||
`${neighbor.hostname}_${neighbor.mac}_${neighbor.address || 'unknown'}`;
if (!allDevices[neighborId]) {
allDevices[neighborId] = {
name: neighbor.hostname || neighbor.mac || 'Unknown',
type: neighbor.type || 'Router',
brand: neighbor.brand || 'MikroTik'
};
}
if (neighbor.mac) {
macToDevice[neighbor.mac] = neighborId;
}
});
});
});
// Create nodes
Object.entries(allDevices).forEach(([uniqueId, device]: [string, any]) => {
const nodeId = `node_${nodeIdCounter++}`;
deviceMap[uniqueId] = nodeId;
nodes.add({
id: nodeId,
label: device.name,
shape: 'image',
image: this.getDeviceIcon(device.type || 'Unknown', device.brand || 'Unknown'),
size: 15,
font: { size: 11, color: '#333', face: 'Arial, sans-serif' },
...(hasSavedPositions && this.savedPositions[nodeId]
? { x: this.savedPositions[nodeId].x, y: this.savedPositions[nodeId].y }
: {})
} as any);
});
// Create edges
this.mikrotikData.forEach((device) => {
Object.entries(device.interfaces).forEach(([ifaceName, iface]: [string, any]) => {
const sourceDeviceId = macToDevice[iface.mac];
iface.neighbors.forEach((neighbor: any) => {
const targetDeviceId = macToDevice[neighbor.mac];
if (deviceMap[sourceDeviceId] && deviceMap[targetDeviceId]) {
const edgeId = `${sourceDeviceId}_${targetDeviceId}`;
const reverseId = `${targetDeviceId}_${sourceDeviceId}`;
if (!edges.get().find(e => e.id === edgeId || e.id === reverseId)) {
edges.add({
id: edgeId,
from: deviceMap[sourceDeviceId],
to: deviceMap[targetDeviceId],
label: ifaceName,
color: { color: '#34495e', highlight: '#3498db' },
width: 3,
smooth: { type: 'continuous', roundness: 0.1 },
font: {
size: 12,
color: '#2c3e50',
face: 'Arial, sans-serif',
strokeWidth: 1,
strokeColor: '#ffffff',
align: 'horizontal'
}
} as any);
}
}
});
});
});
const data = { nodes, edges };
const options = { physics: { enabled: true, stabilization: { iterations: 100 }, barnesHut: { gravitationalConstant: -8000, centralGravity: 0.3, springLength: 200, springConstant: 0.04, damping: 0.09 } }, interaction: { hover: true, dragNodes: true, dragView: true, zoomView: true, hoverConnectedEdges: false, selectConnectedEdges: false, navigationButtons: false, keyboard: false }, nodes: { borderWidth: 3, shadow: true }, edges: { shadow: true, smooth: true, length: 150 }, manipulation: { enabled: false } };
const network = new Network(container, data, options);
// Keep your existing events (dragEnd, click, stabilization, etc.)
// No changes needed below
network.on('dragEnd', () => {
const positions = network.getPositions();
this.savedPositions = positions;
localStorage.setItem(this.savedPositionsKey, JSON.stringify(positions));
});
network.on('click', (event: any) => {
if (event.nodes[0]) {
const clickedNode = nodes.get(event.nodes[0]);
const canvasPosition = network.canvasToDOM(event.pointer.canvas);
const containerRect = container.getBoundingClientRect();
const mainContainer = document.querySelector('.main-container') as HTMLElement;
const mainRect = mainContainer?.getBoundingClientRect() || containerRect;
let adjustedX = canvasPosition.x + containerRect.left - mainRect.left + 20;
let adjustedY = canvasPosition.y + containerRect.top - mainRect.top - 50;
const popupWidth = 280;
const popupHeight = 200;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
if (adjustedX + popupWidth > viewportWidth) adjustedX -= popupWidth + 40;
if (adjustedY + popupHeight > viewportHeight) adjustedY -= popupHeight + 40;
if (adjustedX < 20) adjustedX = 20;
if (adjustedY < 20) adjustedY = 20;
this.handleNodeClick(clickedNode, { x: adjustedX, y: adjustedY });
}
});
network.on('stabilizationIterationsDone', () => {
network.fit();
if (!hasSavedPositions) {
const positions = network.getPositions();
this.savedPositions = positions;
localStorage.setItem(this.savedPositionsKey, JSON.stringify(positions));
}
});
if (hasSavedPositions) {
setTimeout(() => network.fit(), 500);
}
}
handleNodeClick(node: any, position?: { x: number, y: number }) {
this.selectedDevice = node;
if (position) {
this.selectedDevice.popupPosition = position;
}
}
closeDeviceDetails() {
this.selectedDevice = null;
}
getDeviceInterfaces() {
if (!this.selectedDevice) return [];
const device = this.mikrotikData.find(d => d.name === this.selectedDevice.label);
if (!device) return [];
return Object.entries(device.interfaces).map(([name, data]: [string, any]) => ({
name,
address: data.address,
mac: data.mac
}));
}
getNodeColor(deviceType: string): string {
const colors = {
'gateway': '#dc3545',
'router': '#fd7e14',
'switch': '#6f42c1',
'ap': '#20c997',
'cpe': '#0dcaf0'
};
return (colors as any)[deviceType] || '#6c757d';
}
getNodeBorderColor(deviceType: string): string {
const borderColors = {
'gateway': '#b02a37',
'router': '#e8681a',
'switch': '#59359a',
'ap': '#1aa179',
'cpe': '#0baccc'
};
return (borderColors as any)[deviceType] || '#495057';
}
getDeviceIcon(deviceType: string, brand: string): string {
const basePath = './assets/Network-Icons-SVG/';
const type = deviceType.toLowerCase();
const brandName = brand.toLowerCase();
// MikroTik devices
if (brandName === 'mikrotik') {
if (type === 'switch') {
return `${basePath}cumulus-switch-v2.svg`;
}
return `${basePath}cumulus-router-v2.svg`;
}
// Cisco devices
if (brandName === 'cisco') {
if (type === 'switch') {
return `${basePath}cisco-switch-l2.svg`;
}
return `${basePath}cisco-router.svg`;
}
// Juniper devices
if (brandName === 'juniper') {
if (type === 'switch') {
return `${basePath}juniper-switch-l2.svg`;
}
return `${basePath}juniper-router.svg`;
}
// HPE/Aruba devices
if (brandName === 'hpe/aruba' || brandName === 'aruba' || brandName === 'hpe') {
if (type === 'server') {
return `${basePath}generic-server-1.svg`;
}
return `${basePath}arista-switch.svg`;
}
// Ubiquiti devices
if (brandName === 'ubiquiti' || brandName === 'ubnt') {
if (type === 'switch') {
return `${basePath}generic-switch-l2-v1-colour.svg`;
}
return `${basePath}generic-router-colour.svg`;
}
// Default icons by type
const defaultIcons = {
'switch': `${basePath}generic-switch-l2-v1-colour.svg`,
'router': `${basePath}generic-router-colour.svg`,
'router/switch': `${basePath}generic-router-colour.svg`,
'server': `${basePath}generic-server-1.svg`,
'unknown': `${basePath}generic-router-colour.svg`
};
return (defaultIcons as any)[type] || `${basePath}generic-router-colour.svg`;
}
getDefaultPosition(deviceName: string, index: number): { x: number, y: number } {
const positions = {
'Core Router': { x: 0, y: 0 },
'Edge Router': { x: -200, y: -100 },
'Distribution Switch': { x: 200, y: -100 },
'Access Point 1': { x: 100, y: 100 },
'Access Point 2': { x: 300, y: 100 },
'Customer Router 1': { x: 0, y: 200 },
'Customer Router 2': { x: 200, y: 200 }
};
return (positions as any)[deviceName] || { x: index * 100, y: index * 50 };
}
webAccess() {
if (!this.selectedDevice) return;
const device = this.mikrotikData.find(d => d.name === this.selectedDevice.label);
if (device) {
const firstInterface = Object.values(device.interfaces)[0] as any;
const ip = firstInterface.address.split('/')[0];
window.open(`http://${ip}`, '_blank');
}
}
showMoreInfo() {
console.log('More info for:', this.selectedDevice);
// Implement modal or detailed view
}
pingDevice() {
console.log('Ping device:', this.selectedDevice);
// Implement ping functionality
}
configureDevice() {
console.log('Configure device:', this.selectedDevice);
// Implement configuration interface
}
}

View file

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MapsComponent } from './maps.component';
const routes: Routes = [
{
path: '',
component: MapsComponent,
data: {
title: $localize`Maps Wall`
}
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MapsRoutingModule {
}

View file

@ -0,0 +1,82 @@
<c-row class="network-container">
<c-col [xs]="12" class="network-col">
<c-card class="network-card">
<button cButton color="primary" size="sm" (click)="refreshData()" class="refresh-btn">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<div #network class="network-canvas"></div>
</c-card>
</c-col>
</c-row>
<div *ngIf="selectedDevice" class="floating-sidebar" [ngStyle]="{
'left.px': selectedDevice.popupPosition?.x || 20,
'top.px': selectedDevice.popupPosition?.y || 20
}">
<div class="device-panel">
<div class="panel-header">
<span class="device-name">{{ selectedDevice.label }}</span>
<button cButton variant="ghost" size="sm" (click)="closeDeviceDetails()" class="close-btn">
<i class="fas fa-times"></i>
</button>
</div>
<div class="panel-content">
<div class="device-info">
<div><small><strong>Type:</strong> {{getDeviceInfo()?.type}} ({{getDeviceInfo()?.brand}})</small></div>
<div><small><strong>Board:</strong> {{getDeviceInfo()?.board}}</small></div>
<div><small><strong>Version:</strong> {{getDeviceInfo()?.version}}</small></div>
<div *ngIf="getDeviceInfo()?.systemDescription"><small><strong>System:</strong> {{getDeviceInfo()?.systemDescription}}</small></div>
<div><small><strong>IP:</strong> {{getPrimaryIP()}}</small></div>
<div><small><strong>Neighbors:</strong> {{getNeighborCount()}}</small></div>
</div>
<div class="interfaces-section">
<div *ngFor="let interface of getDeviceInterfaces()" class="interface-row">
<div class="if-header">
<span class="if-name">{{ interface.name }}</span>
<span class="if-neighbors">({{ interface.neighbors }} neighbors)</span>
</div>
<span class="if-ip">{{ interface.address }}</span>
</div>
</div>
<div class="actions-section">
<button cButton color="primary" size="sm" (click)="webAccess()" class="compact-btn">
<i class="fas fa-globe"></i> Web
</button>
<button *ngIf="getDeviceInfo()?.deviceId" cButton color="info" size="sm" (click)="showMoreInfo()" class="compact-btn">
<i class="fas fa-info-circle"></i> Info
</button>
<!-- <button cButton color="success" size="sm" (click)="pingDevice(getDeviceInfo()?.deviceId)" class="compact-btn">
<i class="fas fa-play"></i> Ping
</button>
<button cButton color="warning" size="sm" (click)="configureDevice(getDeviceInfo()?.deviceId)" class="compact-btn">
<i class="fas fa-cog"></i> Config
</button> -->
</div>
</div>
</div>
</div>
<!-- Web Access Modal -->
<c-modal #WebAccessModal backdrop="static" [(visible)]="showWebAccessModal" id="WebAccessModal">
<c-modal-header>
<h6 cModalTitle>Web Access Options</h6>
<button cButtonClose (click)="closeWebAccessModal()"></button>
</c-modal-header>
<c-modal-body>
<p>Choose how to access the device:</p>
<div class="d-grid gap-2">
<button *ngIf="ispro" cButton color="primary" (click)="openProxyAccess()">
<i class="fas fa-shield-alt"></i> Proxy Access , through Mikrowizard server
</button>
<button cButton color="secondary" (click)="openDirectAccess()">
<i class="fas fa-external-link-alt"></i> Direct Access
</button>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="secondary" (click)="closeWebAccessModal()">Cancel</button>
</c-modal-footer>
</c-modal>

View file

@ -0,0 +1,256 @@
:host {
.network-container {
height: calc(100vh - 165px);
margin: 0;
position: relative;
}
.network-col {
padding: 0;
}
.network-card {
height: 100%;
border: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border-radius: 12px;
overflow: hidden;
position: relative;
}
.refresh-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
padding: 6px 12px;
font-size: 12px;
}
.network-canvas {
width: 100%;
height: calc(100vh - 200px);
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 8px;
border: 1px solid #dee2e6;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
::ng-deep .vis-network {
cursor: default;
.vis-item {
cursor: pointer;
&:hover {
cursor: pointer;
}
&:active {
cursor: grabbing;
}
}
}
}
.floating-sidebar {
position: fixed;
z-index: 10000;
animation: slideIn 0.3s ease;
}
.device-panel {
background: rgba(44, 62, 80, 0.95);
backdrop-filter: blur(10px);
border-radius: 8px;
border: 1px solid rgba(52, 73, 94, 0.8);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
width: 280px;
max-height: calc(100vh - 40px);
color: white;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 12px 16px;
border-bottom: 1px solid rgba(52, 73, 94, 0.5);
display: flex;
justify-content: space-between;
align-items: center;
}
.device-name {
font-weight: 600;
font-size: 14px;
color: #ecf0f1;
}
.close-btn {
color: #bdc3c7;
border: none;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background: rgba(255, 255, 255, 0.2);
color: white;
}
}
.panel-content {
padding: 16px;
overflow-y: auto;
flex: 1;
/* Override global scrollbar styles */
scrollbar-width: thin !important;
scrollbar-color: #bdc3c7 rgba(52, 73, 94, 0.3) !important;
/* Custom scrollbar styling for webkit */
&::-webkit-scrollbar {
width: 8px !important;
}
&::-webkit-scrollbar-track {
background: rgba(52, 73, 94, 0.3) !important;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #bdc3c7 !important;
border-radius: 4px;
&:hover {
background: #ecf0f1 !important;
}
}
}
.interfaces-section {
margin-bottom: 16px;
margin-top: 3px;
.interface-row {
max-height: none;
}
}
.section-title {
font-size: 12px;
font-weight: 600;
color: #95a5a6;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.interface-row {
display: flex;
flex-direction: column;
padding: 1px 0;
border-bottom: 1px solid rgba(52, 73, 94, 0.3);
font-size: 12px;
gap: 2px;
&:last-child {
border-bottom: none;
}
}
.if-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.if-name {
color: #3498db;
font-weight: 500;
}
.if-ip {
color: #ecf0f1;
font-family: monospace;
word-break: break-all;
}
.if-neighbors {
color: #95a5a6;
font-size: 11px;
}
.actions-section {
display: flex;
gap: 8px;
&:has(button:only-child) {
button {
flex: 1;
}
}
&:not(:has(button:only-child)) {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
.compact-btn {
padding: 6px 8px;
font-size: 11px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 4px;
justify-content: center;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
}
.mdc-line-ripple.mdc-line-ripple--deactivating.ng-star-inserted {
display: none!important;
}
::ng-deep .main-container{
padding:0!important;
margin-top:-10px;
}
::ng-deep .header{
margin-bottom: 0.9rem!important;
}
@media only screen and (max-width: 768px) {
:host .floating-sidebar {
position: fixed;
top: 10px;
right: 10px;
left: 10px;
width: auto;
}
:host .device-panel {
width: 100%;
}
}

View file

@ -0,0 +1,479 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { loginChecker } from "../../providers/login_checker";
import { Router } from "@angular/router";
import { formatInTimeZone } from "date-fns-tz";
import { Network } from 'vis-network/peer';
import { DataSet } from 'vis-data';
@Component({
templateUrl: "maps.component.html",
styleUrls: ["maps.component.scss"],
})
export class MapsComponent implements OnInit {
public uid: number;
public uname: string;
public ispro: boolean = false;
public tz: string;
public savedPositions: any = {};
public savedPositionsKey = "network-layout";
public selectedDevice: any = null;
public showWebAccessModal: boolean = false;
public showMoreInfoModal: boolean = false;
public currentDeviceInfo: any = null;
constructor(
private data_provider: dataProvider,
private router: Router,
private login_checker: loginChecker
) {
var _self = this;
if (!this.login_checker.isLoggedIn()) {
setTimeout(function () {
_self.router.navigate(["login"]);
}, 100);
}
this.data_provider.getSessionInfo().then((res) => {
_self.uid = res.uid;
_self.uname = res.name;
_self.ispro = res.ISPRO;
if (!_self.ispro)
setTimeout(function () {
_self.router.navigate(["dashboard"]);
}, 100);
_self.tz = res.tz;
});
}
@ViewChild('network', { static: true }) networkContainer: ElementRef | undefined;
mikrotikData: any[] = [];
ngOnInit(): void {
this.loadFontAwesome();
this.savedPositions = JSON.parse(localStorage.getItem(this.savedPositionsKey) || "{}");
this.loadNetworkData();
}
loadNetworkData(): void {
this.data_provider.getNetworkMap().then((res) => {
this.mikrotikData = res;
console.dir(res);
setTimeout(() => {
this.createNetworkMap();
}, 100);
});
}
refreshData(): void {
this.selectedDevice = null;
this.loadNetworkData();
}
loadFontAwesome() {
if (!document.querySelector('link[href*="font-awesome"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css';
document.head.appendChild(link);
}
}
createNetworkMap() {
const container = this.networkContainer?.nativeElement;
if (!container) return;
let nodes = new DataSet<any>([]);
let edges = new DataSet<any>([]);
let deviceMap: { [key: string]: string } = {}; // uniqueId (hostname) to nodeId mapping
let allDevices: { [key: string]: any } = {}; // uniqueId to device info mapping
let macToDevice: { [key: string]: string } = {}; // MAC -> uniqueId (hostname) mapping
const hasSavedPositions = Object.keys(this.savedPositions).length > 0;
let nodeIdCounter = 1;
// Collect all devices using hostname as consistent unique ID
this.mikrotikData.forEach((device) => {
const deviceId = device.name; // Use name (hostname) as unique ID
if (!allDevices[deviceId]) {
allDevices[deviceId] = {
name: device.name,
type: 'Router',
brand: 'MikroTik'
};
}
Object.entries(device.interfaces).forEach(([_, iface]: [string, any]) => {
if (iface.mac) {
macToDevice[iface.mac] = deviceId; // Map to hostname
}
if (iface.neighbors && Array.isArray(iface.neighbors)) {
iface.neighbors.forEach((neighbor: any) => {
const neighborId = neighbor.hostname || 'Unknown'; // Use hostname
if (!allDevices[neighborId]) {
allDevices[neighborId] = {
name: neighbor.hostname || neighbor.mac || 'Unknown',
type: neighbor.type || 'Router',
brand: neighbor.brand || 'MikroTik'
};
}
if (neighbor.mac) {
macToDevice[neighbor.mac] = neighborId; // Map to hostname
}
});
}
});
});
// Create nodes
Object.entries(allDevices).forEach(([uniqueId, device]: [string, any]) => {
const nodeId = `node_${nodeIdCounter++}`;
deviceMap[uniqueId] = nodeId;
nodes.add({
id: nodeId,
label: device.name,
shape: 'image',
image: this.getDeviceIcon(device.type || 'Unknown', device.brand || 'Unknown'),
size: 15,
font: { size: 11, color: '#333', face: 'Arial, sans-serif' },
...(hasSavedPositions && this.savedPositions[nodeId]
? { x: this.savedPositions[nodeId].x, y: this.savedPositions[nodeId].y }
: {})
} as any);
});
// Create edges - collect all connections first
let connectionMap: { [key: string]: string[] } = {};
this.mikrotikData.forEach((device) => {
const deviceName = device.name;
Object.entries(device.interfaces).forEach(([ifaceName, iface]: [string, any]) => {
const sourceDeviceId = iface.mac ? macToDevice[iface.mac] : deviceName;
if (iface.neighbors && Array.isArray(iface.neighbors)) {
iface.neighbors.forEach((neighbor: any) => {
const targetDeviceId = neighbor.mac ? macToDevice[neighbor.mac] : null;
if (deviceMap[sourceDeviceId] && targetDeviceId && deviceMap[targetDeviceId]) {
const connectionKey = [sourceDeviceId, targetDeviceId].sort().join('_');
const interfacePair = neighbor.interface ? `${ifaceName}${neighbor.interface}` : ifaceName;
if (!connectionMap[connectionKey]) {
connectionMap[connectionKey] = [];
}
if (!connectionMap[connectionKey].includes(interfacePair)) {
connectionMap[connectionKey].push(interfacePair);
}
}
});
}
});
});
// Create edges with combined labels
Object.entries(connectionMap).forEach(([connectionKey, interfacePairs]) => {
const [sourceDeviceId, targetDeviceId] = connectionKey.split('_');
let edgeLabel = interfacePairs.join('\n');
// Limit to max 2 interface pairs to avoid overcrowding
if (interfacePairs.length > 2) {
edgeLabel = interfacePairs.slice(0, 2).join('\n') + '\n+' + (interfacePairs.length - 2);
}
edges.add({
id: connectionKey,
from: deviceMap[sourceDeviceId],
to: deviceMap[targetDeviceId],
label: edgeLabel,
color: { color: '#34495e', highlight: '#3498db' },
width: 3,
smooth: { type: 'continuous', roundness: 0.1 },
font: {
size: 9,
color: '#2c3e50',
face: 'Arial, sans-serif',
strokeWidth: 2,
strokeColor: '#ffffff',
align: 'horizontal'
}
} as any);
});
const data = { nodes, edges };
const options = { physics: { enabled: true, stabilization: { iterations: 100 }, barnesHut: { gravitationalConstant: -8000, centralGravity: 0.3, springLength: 200, springConstant: 0.04, damping: 0.09 } }, interaction: { hover: true, dragNodes: true, dragView: true, zoomView: true, hoverConnectedEdges: false, selectConnectedEdges: false, navigationButtons: false, keyboard: false }, nodes: { borderWidth: 3, shadow: true }, edges: { shadow: true, smooth: true, length: 150 }, manipulation: { enabled: false } };
const network = new Network(container, data, options);
// Keep your existing events (dragEnd, click, stabilization, etc.)
// No changes needed below
network.on('dragEnd', () => {
const positions = network.getPositions();
this.savedPositions = positions;
localStorage.setItem(this.savedPositionsKey, JSON.stringify(positions));
});
network.on('click', (event: any) => {
if (event.nodes[0]) {
const clickedNode = nodes.get(event.nodes[0]);
const canvasPosition = network.canvasToDOM(event.pointer.canvas);
const containerRect = container.getBoundingClientRect();
const mainContainer = document.querySelector('.main-container') as HTMLElement;
const mainRect = mainContainer?.getBoundingClientRect() || containerRect;
let adjustedX = canvasPosition.x + containerRect.left - mainRect.left + 20;
let adjustedY = canvasPosition.y + containerRect.top - mainRect.top - 50;
const popupWidth = 280;
const maxPopupHeight = window.innerHeight - 40;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
if (adjustedX + popupWidth > viewportWidth) adjustedX -= popupWidth + 40;
if (adjustedY + maxPopupHeight > viewportHeight) adjustedY = viewportHeight - maxPopupHeight - 20;
if (adjustedX < 20) adjustedX = 20;
if (adjustedY < 20) adjustedY = 20;
this.handleNodeClick(clickedNode, { x: adjustedX, y: adjustedY });
}
});
network.on('stabilizationIterationsDone', () => {
network.fit();
if (!hasSavedPositions) {
const positions = network.getPositions();
this.savedPositions = positions;
localStorage.setItem(this.savedPositionsKey, JSON.stringify(positions));
}
});
if (hasSavedPositions) {
setTimeout(() => network.fit(), 500);
}
}
handleNodeClick(node: any, position?: { x: number, y: number }) {
this.selectedDevice = node;
if (position) {
this.selectedDevice.popupPosition = position;
}
}
closeDeviceDetails() {
this.selectedDevice = null;
}
getDeviceInfo() {
if (!this.selectedDevice) return null;
const device = this.mikrotikData.find(d => d.name === this.selectedDevice.label);
if (device) {
// Main device found
const interfaces = Object.entries(device.interfaces).map(([name, data]: [string, any]) => ({
name,
address: data.address || 'N/A',
mac: data.mac || 'N/A',
neighbors: data.neighbors?.length || 0
}));
const interfaceWithNeighbors = Object.values(device.interfaces)
.find((iface: any) => iface.neighbors?.length > 0) as any;
const firstNeighbor = interfaceWithNeighbors?.neighbors?.[0];
return {
name: device.name,
deviceId: device.device_id,
type: firstNeighbor?.type || 'Router',
brand: firstNeighbor?.brand || 'MikroTik',
board: firstNeighbor?.board || 'Unknown',
version: firstNeighbor?.version || 'Unknown',
systemDescription: firstNeighbor?.['system-description'] || null,
softwareId: firstNeighbor?.['software-id'] || 'N/A',
interfaces
};
} else {
// Search in neighbor data
for (const mainDevice of this.mikrotikData) {
for (const iface of Object.values(mainDevice.interfaces)) {
const neighbor = (iface as any).neighbors?.find((n: any) => n.hostname === this.selectedDevice.label);
if (neighbor) {
return {
name: neighbor.hostname,
deviceId: null,
type: neighbor.type || 'Router',
brand: neighbor.brand || 'MikroTik',
board: neighbor.board || 'Unknown',
version: neighbor.version || 'Unknown',
systemDescription: neighbor['system-description'] || null,
softwareId: neighbor['software-id'] || 'N/A',
interfaces: [{ name: neighbor.interface || 'Unknown', address: neighbor.address || 'N/A', mac: neighbor.mac || 'N/A', neighbors: 0 }]
};
}
}
}
}
return null;
}
getNodeColor(deviceType: string): string {
const colors = {
'gateway': '#dc3545',
'router': '#fd7e14',
'switch': '#6f42c1',
'ap': '#20c997',
'cpe': '#0dcaf0'
};
return (colors as any)[deviceType] || '#6c757d';
}
getNodeBorderColor(deviceType: string): string {
const borderColors = {
'gateway': '#b02a37',
'router': '#e8681a',
'switch': '#59359a',
'ap': '#1aa179',
'cpe': '#0baccc'
};
return (borderColors as any)[deviceType] || '#495057';
}
getDeviceIcon(deviceType: string, brand: string): string {
const basePath = './assets/Network-Icons-SVG/';
const type = deviceType.toLowerCase();
const brandName = brand.toLowerCase();
// MikroTik devices
if (brandName === 'mikrotik') {
if (type === 'switch') {
return `${basePath}cumulus-switch-v2.svg`;
}
return `${basePath}cumulus-router-v2.svg`;
}
// Cisco devices
if (brandName === 'cisco') {
if (type === 'switch') {
return `${basePath}cisco-switch-l2.svg`;
}
return `${basePath}cisco-router.svg`;
}
// Juniper devices
if (brandName === 'juniper') {
if (type === 'switch') {
return `${basePath}juniper-switch-l2.svg`;
}
return `${basePath}juniper-router.svg`;
}
// HPE/Aruba devices
if (brandName === 'hpe/aruba' || brandName === 'aruba' || brandName === 'hpe') {
if (type === 'server') {
return `${basePath}generic-server-1.svg`;
}
return `${basePath}arista-switch.svg`;
}
// Ubiquiti devices
if (brandName === 'ubiquiti' || brandName === 'ubnt') {
if (type === 'switch') {
return `${basePath}generic-switch-l2-v1-colour.svg`;
}
return `${basePath}generic-router-colour.svg`;
}
// Default icons by type
const defaultIcons = {
'switch': `${basePath}generic-switch-l2-v1-colour.svg`,
'router': `${basePath}generic-router-colour.svg`,
'router/switch': `${basePath}generic-router-colour.svg`,
'server': `${basePath}generic-server-1.svg`,
'unknown': `${basePath}generic-router-colour.svg`
};
return (defaultIcons as any)[type] || `${basePath}generic-router-colour.svg`;
}
getDefaultPosition(deviceName: string, index: number): { x: number, y: number } {
const positions = {
'Core Router': { x: 0, y: 0 },
'Edge Router': { x: -200, y: -100 },
'Distribution Switch': { x: 200, y: -100 },
'Access Point 1': { x: 100, y: 100 },
'Access Point 2': { x: 300, y: 100 },
'Customer Router 1': { x: 0, y: 200 },
'Customer Router 2': { x: 200, y: 200 }
};
return (positions as any)[deviceName] || { x: index * 100, y: index * 50 };
}
webAccess() {
if (!this.selectedDevice) return;
this.currentDeviceInfo = this.getDeviceInfo();
this.showWebAccessModal = true;
this.closeDeviceDetails();
}
openProxyAccess() {
if (this.currentDeviceInfo?.deviceId) {
window.open(`/api/proxy/init?devid=${this.currentDeviceInfo.deviceId}`, '_blank');
} else {
const ip = this.currentDeviceInfo?.interfaces.find((iface: any) => iface.address !== 'N/A')?.address?.split('/')[0];
if (ip) {
window.open(`/api/proxy/init?dev_ip=${ip}`, '_blank');
}
}
this.showWebAccessModal = false;
}
openDirectAccess() {
const ip = this.currentDeviceInfo?.interfaces.find((iface: any) => iface.address !== 'N/A')?.address?.split('/')[0];
if (ip) {
window.open(`http://${ip}`, '_blank');
}
this.showWebAccessModal = false;
}
closeWebAccessModal() {
this.showWebAccessModal = false;
}
getNeighborCount() {
const deviceInfo = this.getDeviceInfo();
return deviceInfo?.interfaces.reduce((total, iface) => total + iface.neighbors, 0) || 0;
}
getPrimaryIP() {
const deviceInfo = this.getDeviceInfo();
const primaryInterface = deviceInfo?.interfaces.find(iface => iface.address !== 'N/A');
return primaryInterface?.address?.split('/')[0] || 'N/A';
}
getDeviceInterfaces() {
const deviceInfo = this.getDeviceInfo();
return deviceInfo?.interfaces || [];
}
showMoreInfo() {
const deviceInfo = this.getDeviceInfo();
if (deviceInfo?.deviceId) {
window.open(`/#/device-stats;id=${deviceInfo.deviceId}`, '_blank');
}
}
pingDevice(devid: number) {
console.log('Ping device:', this.selectedDevice);
// Implement ping functionality
}
configureDevice(devid: number) {
console.log('Configure device:', this.selectedDevice);
// Implement configuration interface
}
}

View file

@ -0,0 +1,60 @@
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { ReactiveFormsModule } from "@angular/forms";
import { FormsModule } from "@angular/forms";
import {
ButtonGroupModule,
ButtonModule,
CardModule,
GridModule,
WidgetModule,
ProgressModule,
TemplateIdDirective,
TooltipModule,
BadgeModule,
CarouselModule,
ListGroupModule,
ModalModule,
TableModule,
UtilitiesModule
} from "@coreui/angular";
import { IconModule } from "@coreui/icons-angular";
import { ChartjsModule } from "@coreui/angular-chartjs";
import { NgScrollbarModule } from 'ngx-scrollbar';
import { MapsRoutingModule } from "./maps-routing.module";
import { MapsComponent } from "./maps.component";
import { ClipboardModule } from "@angular/cdk/clipboard";
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
@NgModule({
imports: [
MapsRoutingModule,
CardModule,
WidgetModule,
CommonModule,
GridModule,
ProgressModule,
ReactiveFormsModule,
ButtonModule,
ModalModule,
FormsModule,
TemplateIdDirective,
ButtonModule,
ButtonGroupModule,
ChartjsModule,
CarouselModule,
BadgeModule,
ClipboardModule,
ListGroupModule,
NgScrollbarModule,
TableModule,
TooltipModule,
UtilitiesModule,
InfiniteScrollModule,
IconModule
],
declarations: [MapsComponent],
})
export class MapsModule {}

View file

@ -1,230 +1,510 @@
<c-row> <div class="settings-container">
<c-col xs> <c-card class="settings-card">
<c-card class="mb-4"> <c-card-header class="settings-header">
<c-card-header>Firmware Manager</c-card-header> <div class="d-flex align-items-center justify-content-between">
<c-card-body> <div class="d-flex align-items-center">
<c-input-group class="mb-3"> <i class="fa-solid fa-cog me-2 text-primary"></i>
<h5>Firmware in repository:</h5> <h4 class="mb-0">System Settings</h4>
<gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting" </div>
[infoPanel]="infoPanel" [autoResizeWidth]=true [paging]="paging"> <c-badge color="info" class="fs-6">Admin Panel</c-badge>
<gui-grid-column header="Version" field="version"> </div>
<ng-template let-value="item.version" let-item="item" let-index="index"> </c-card-header>
&nbsp; {{value}} </ng-template>
</gui-grid-column> <div class="settings-tabs">
<gui-grid-column header="arch" field="architecture"> <button class="tab-link" [class.active]="activeTab === 'firmware'" (click)="activeTab = 'firmware'">
<ng-template let-value="item.architecture" let-item="item" let-index="index"> <i class="fa-solid fa-microchip me-2"></i>Firmware Manager
{{value}} </button>
</ng-template> <button class="tab-link" [class.active]="activeTab === 'system'" (click)="activeTab = 'system'">
</gui-grid-column> <i class="fa-solid fa-server me-2"></i>System Configuration
<gui-grid-column header="sha256" field="sha256"> </button>
<ng-template let-value="item.sha256" let-item="item" let-index="index"> </div>
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" (click)="delete_fimrware(item);"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-input-group>
<hr /> <!-- Firmware Tab -->
<table> <c-card-body *ngIf="activeTab === 'firmware'" class="tab-content">
<td> <!-- Repository Status -->
<span>Add new Permission</span> <div class="status-section mb-4">
</td> <div class="d-flex align-items-center justify-content-between mb-3">
<td *ngIf="loading"> <h5 class="section-title mb-0">
<button cButton class="m-1" disabled> <i class="fa-solid fa-database me-2 text-success"></i>Firmware Repository
<c-spinner aria-hidden="true" size="sm"></c-spinner> <i class="fa-solid fa-info-circle ms-2 text-info help-icon"
Fetching Information from mikrotik website... [cTooltip]="'Local firmware repository stores RouterOS versions for offline installation. Devices can be updated without internet access.'"
</button> cTooltipPlacement="top"></i>
</td> </h5>
<td *ngIf="!loading"> <c-badge color="success" class="fs-6">
<mat-form-field> <i class="fa-solid fa-check-circle me-1"></i>{{source.length}} Versions Available
<mat-select cFormControl [(ngModel)]="firmtodownload" placeholder="Select Version For Download Group" </c-badge>
#singleSelect> </div>
<mat-option> <div class="info-banner mb-3">
<ngx-mat-select-search placeholderLabel="Search" <i class="fa-solid fa-lightbulb me-2"></i>
[hideClearSearchButton]="true"></ngx-mat-select-search> <span><strong>Tip:</strong> Keep multiple firmware versions for compatibility. Always test updates on non-critical devices first.</span>
</mat-option> </div>
<mat-option *ngFor="let firm of firms" [value]="firm">
{{firm}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
<td>
<button *ngIf="!loading" cButton color="primary" (click)="ConfirmModalVisible=true">Download to
repository</button>
<!-- <button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="loading=!loading">++</button> -->
</td>
</table>
<hr />
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
V6 Firmware update Behavior
</label>
<select cSelect [(ngModel)]="updateBehavior" id="inputGroupSelect01">
<option>Choose...</option>
<option value="keep">Keep v6 and don't update to v7</option>
<option value="update">install latest</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Choose how Mikrowizard should update old v6 firmwares</c-form-feedback>
</c-input-group>
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Firmware version to install
</label>
<select cSelect [(ngModel)]="firmwaretoinstall" id="inputGroupSelect01">
<option>Choose...</option>
<option *ngFor="let f of available_firmwares" [value]="f">{{f}}</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* The version of firmware to install routers</c-form-feedback>
</c-input-group>
<c-input-group *ngIf="updateBehavior=='keep'" class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Firmware version v6 to install
</label>
<select cSelect [(ngModel)]="firmwaretoinstallv6" id="inputGroupSelect01">
<option>Choose...</option>
<option *ngFor="let f of available_firmwaresv6" [value]="f">{{f}}</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* The version of firmware to install on V6 routers</c-form-feedback>
</c-input-group>
<button cButton color="primary" (click)="saveFirmwareSetting()">Save</button>
</c-card-body>
</c-card>
<c-card class="mb-4">
<c-card-header>System Settings</c-card-header>
<c-card-body *ngIf="!SysConfigloading">
<c-input-group class="mt-3">
<span cInputGroupText>Rad Secret</span>
<input cFormControl id="floatingInput" placeholder="rad_secret"
[(ngModel)]="sysconfigs['rad_secret']['value']" />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Radius Secret of Mikrowizard Radius Server</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>System URL</span>
<input cFormControl id="floatingInput" placeholder="System URL"
[(ngModel)]="sysconfigs['system_url']['value']" />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default system access URl</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>Default IP</span>
<input cFormControl id="floatingInput" placeholder="System URL"
[(ngModel)]="sysconfigs['default_ip']['value']" />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default Mikrowizard Access IP</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>System Time Zone</span>
<mat-form-field class="form-control" subscriptSizing="dynamic">
<mat-label>Select event type</mat-label>
<mat-select cFormControl [(ngModel)]="sysconfigs['timezone']['value']"
placeholder="Select Version For Download Group" #singleSelect>
<mat-option>
<ngx-mat-select-search placeholderLabel="Search" [hideClearSearchButton]="true"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let tz of timezones" [value]="tz.utc[0]">
{{tz.text}}
</mat-option>
</mat-select>
</mat-form-field>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default TimeZone for the system</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>Default User</span>
<input aria-label="Username" type="password" [(ngModel)]="sysconfigs['default_user']['value']" cFormControl />
<span cInputGroupText>Default password</span>
<input aria-label="Password" type="password" [(ngModel)]="sysconfigs['default_password']['value']"
cFormControl />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default username and Password for searching new devices</c-form-feedback>
</c-input-group>
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Mikrowizard Update Mode
</label>
<select cSelect [(ngModel)]="sysconfigs['update_mode']['value']['mode']" id="inputGroupSelect01">
<option>Choose...</option>
<option value="auto">Automatic Update</option>
<option value="manual">Show update only/Update manually</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Choose if Mikrowizard should download updates automaticaly when availble or wait for user to download/apply updates</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3 mb-3"> <gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting"
<span cInputGroupText>License Username</span> [infoPanel]="infoPanel" [paging]="paging" class="compact-grid">
<input aria-label="Username" type="text" [(ngModel)]="sysconfigs['username']['value']" cFormControl /> <gui-grid-column header="Version" field="version">
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"> <ng-template let-value="item.version" let-item="item" let-index="index">
* The username that you registred in Mikrowizard.com,Required for License Activation</c-form-feedback> <c-badge color="primary" class="version-badge">{{value}}</c-badge>
</c-input-group> </ng-template>
</gui-grid-column>
<gui-grid-column header="Architecture" field="architecture">
<ng-template let-value="item.architecture" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="SHA256" field="sha256">
<ng-template let-value="item.sha256" let-item="item" let-index="index">
<small class="text-muted font-monospace">{{value.substring(0, 16)}}...</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="delete_fimrware(item)" title="Delete firmware">
<i class="fa-solid fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
<!-- Download Section -->
<div class="download-section mb-4">
<div class="section-card">
<div class="section-header">
<i class="fa-solid fa-download me-2 text-primary"></i>
<span class="fw-semibold">Download New Firmware</span>
<i class="fa-solid fa-question-circle ms-2 text-info help-icon"
[cTooltip]="'Downloads firmware from MikroTik servers. Requires internet access. Downloaded files are verified for integrity.'"
cTooltipPlacement="top"></i>
</div>
<div class="warning-banner mb-2" *ngIf="!loading">
<i class="fa-solid fa-wifi me-2"></i>
<span><strong>Internet Required:</strong> Downloads from mikrotik.com. Ensure stable connection and access to mikrotik.com domain.</span>
</div>
<c-row class="g-3 mt-2" *ngIf="!loading">
<c-col md="8">
<div class="search-select-wrapper">
<label class="form-label">Select RouterOS Version</label>
<input cFormControl
[(ngModel)]="firmwareSearch"
(input)="filterFirmwares($event)"
(focus)="showFirmwareDropdown = true"
(blur)="hideFirmwareDropdown()"
placeholder="Search and select version..."
class="search-input"
autocomplete="off" />
<div *ngIf="showFirmwareDropdown && filteredFirmwares.length > 0" class="search-dropdown">
<div *ngFor="let firm of filteredFirmwares"
class="search-option"
(mousedown)="selectFirmware(firm)">
{{firm}}
</div>
</div>
<div *ngIf="showFirmwareDropdown && filteredFirmwares.length === 0 && firmwareSearch" class="search-no-results">
No versions found
</div>
</div>
</c-col>
<c-col md="4" class="d-flex align-items-end">
<button cButton color="primary" class="w-100" (click)="ConfirmModalVisible=true" [disabled]="!firmtodownload">
<i class="fa-solid fa-download me-1"></i>Download
</button>
</c-col>
</c-row>
<div *ngIf="loading" class="text-center py-3">
<c-spinner size="sm" class="me-2"></c-spinner>
<span class="text-muted">Fetching available versions from MikroTik...</span>
</div>
</div>
</div>
<!-- Firmware Settings -->
<div class="firmware-settings">
<h5 class="section-title mb-3">
<i class="fa-solid fa-cogs me-2 text-warning"></i>Update Configuration
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'These settings control how MikroWizard handles firmware updates across your network. Changes affect all future update operations.'"
cTooltipPlacement="top"></i>
</h5>
<div class="info-banner mb-3">
<i class="fa-solid fa-shield-alt me-2"></i>
<span><strong>Best Practice:</strong> Use "Keep v6" for legacy devices. Test new firmware versions in lab environment first.</span>
</div>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">V6 Firmware Behavior
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Choose how to handle RouterOS v6 devices: keep them on v6 with selected v6 firmware, or upgrade them to v7 with selected v7 firmware.'"
cTooltipPlacement="top"></i>
</label>
<select cSelect [(ngModel)]="updateBehavior" class="form-select">
<option value="">Choose behavior...</option>
<option value="keep">🛡️ Keep v6 (v6 devices get V6 Firmware Version, v7 devices get Default Firmware Version)</option>
<option value="update">⬆️ Upgrade to v7 (all v6 routers will be upgraded to v7)</option>
</select>
<small class="setting-help">📋 <strong>Keep v6:</strong> v6 devices use V6 Firmware Version, v7 devices use Default Firmware Version</small>
</div>
</c-col>
<c-form-check [switch]="true" sizing="xl"> <c-col md="6">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_perms']['value']" type="checkbox" /> <div class="setting-group">
<label cFormCheckLabel>Force Perms</label> <label class="setting-label">Default Firmware Version
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"> <i class="fa-solid fa-question-circle ms-1 text-info help-icon"
* Force User Groups under user>groups configuration of each router to match Mikrowizard Permissions and [cTooltip]="'This version will be installed on new devices added to MikroWizard. Choose stable LTS versions for production.'"
monitor for any change to prevent/fix the configuration.</c-form-feedback> cTooltipPlacement="top"></i>
</c-form-check> </label>
<select cSelect [(ngModel)]="firmwaretoinstall" class="form-select">
<option value="">Choose version...</option>
<option *ngFor="let f of available_firmwares" [value]="f">{{f}}</option>
</select>
<small class="setting-help">💡 <strong>Tip:</strong> Choose stable releases for production. LTS versions are only available for v6 firmware</small>
</div>
</c-col>
<c-col md="6" *ngIf="updateBehavior === 'keep'">
<div class="setting-group">
<label class="setting-label">V6 Firmware Version</label>
<select cSelect [(ngModel)]="firmwaretoinstallv6" class="form-select">
<option value="">Choose v6 version...</option>
<option *ngFor="let f of available_firmwaresv6" [value]="f">{{f}}</option>
</select>
<small class="setting-help">Firmware version for RouterOS v6 devices</small>
</div>
</c-col>
</c-row>
<div class="mt-3">
<button cButton color="primary" (click)="saveFirmwareSetting()">
<i class="fa-solid fa-save me-1"></i>Save Firmware Settings
</button>
</div>
</div>
</c-card-body>
<c-form-check [switch]="true" sizing="xl"> <!-- System Tab -->
<input cFormCheckInput [(ngModel)]="sysconfigs['force_radius']['value']" type="checkbox" /> <c-card-body *ngIf="activeTab === 'system' && !SysConfigloading" class="tab-content">
<label cFormCheckLabel>Force Radius</label> <!-- Network Configuration -->
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"> <div class="config-section mb-4">
* Force Radius config under radius>client and user>aaa setting of each router that added to Mikrowizard and <h5 class="section-title mb-3">
monitor for any change to prevent/fix the configuration.</c-form-feedback> <i class="fa-solid fa-network-wired me-2 text-primary"></i>Network Configuration
</c-form-check> <i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Basic network settings for MikroWizard system access and device communication. Ensure these match your network setup.'"
cTooltipPlacement="top"></i>
</h5>
<div class="info-banner mb-3">
<i class="fa-solid fa-network-wired me-2"></i>
<span><strong>Network Setup:</strong> Configure these settings to match your network infrastructure for proper device communication.</span>
</div>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">System URL
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'URL or IP address where MikroWizard is accessible. Used for email notifications and API callbacks. Must be reachable by managed devices.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['system_url']['value']"
placeholder="https://mikrowizard.yourdomain.com or 192.168.1.100" class="form-control" />
<small class="setting-help">🌐 <strong>Examples:</strong> https://mikrowizard.company.com or 192.168.1.100</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default IP Address
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'IP address where MikroWizard web interface is accessible. Used as fallback when domain is not available.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['default_ip']['value']"
placeholder="192.168.1.100" class="form-control" />
<small class="setting-help">🔧 <strong>Tip:</strong> Use static IP outside DHCP range (e.g., 192.168.1.100-200)</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">RADIUS Secret
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Secret used by MikroWizard internal RADIUS server for authenticating MikroTik system users. This is for device admin authentication, not end users.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['rad_secret']['value']"
placeholder="Enter MikroWizard RADIUS secret" class="form-control" type="password" />
<small class="setting-help">🔐 <strong>Internal RADIUS:</strong> Secret for MikroWizard's built-in RADIUS server (for MikroTik admin authentication)</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">System Timezone</label>
<div class="search-select-wrapper">
<input cFormControl
[(ngModel)]="timezoneSearch"
(input)="filterTimezones($event)"
(focus)="showTimezoneDropdown = true"
(blur)="hideTimezoneDropdown()"
placeholder="Search and select timezone..."
class="search-input"
autocomplete="off" />
<div *ngIf="showTimezoneDropdown && filteredTimezones.length > 0" class="search-dropdown">
<div *ngFor="let tz of filteredTimezones"
class="search-option"
(mousedown)="selectTimezone(tz)">
{{tz.text}}
</div>
</div>
<div *ngIf="showTimezoneDropdown && filteredTimezones.length === 0 && timezoneSearch" class="search-no-results">
No timezones found
</div>
</div>
<small class="setting-help">Default system timezone</small>
</div>
</c-col>
</c-row>
</div>
<c-form-check [switch]="true" sizing="xl"> <!-- Authentication -->
<input cFormCheckInput [(ngModel)]="sysconfigs['force_syslog']['value']" type="checkbox" /> <div class="config-section mb-4">
<label cFormCheckLabel>Force Syslog</label> <h5 class="section-title mb-3">
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"> <i class="fa-solid fa-key me-2 text-warning"></i>Device Discovery Credentials
* Force Syslog config under system>logs setting of each router that added to Mikrowizard and monitor syslog <i class="fa-solid fa-info-circle ms-2 text-info help-icon"
setting for any change to prevent/fix the configuration.</c-form-feedback> [cTooltip]="'Default credentials for discovering and connecting to new MikroTik devices. Keep these secure and change default passwords immediately.'"
</c-form-check> cTooltipPlacement="top"></i>
</h5>
<div class="warning-banner mb-3">
<i class="fa-solid fa-shield-exclamation me-2"></i>
<span><strong>Security Warning:</strong> Change default passwords immediately after device setup. Use strong, unique credentials.</span>
</div>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default Username
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Username for discovering and connecting to MikroTik devices in your network. This is used to find devices with default/common credentials.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['default_user']['value']"
placeholder="admin" class="form-control" type="password" />
<small class="setting-help">🔍 <strong>Device Discovery:</strong> Username for finding MikroTik devices with default credentials in your network</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default Password
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Password for discovering and connecting to MikroTik devices. Many new devices have empty password. Used to find devices with default/common credentials.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['default_password']['value']"
placeholder="(often empty on new devices)" class="form-control" type="password" />
<small class="setting-help">🔍 <strong>Device Discovery:</strong> Password for finding MikroTik devices with default credentials (often empty on new devices)</small>
</div>
</c-col>
</c-row>
</div>
<c-form-check *ngIf="ispro" [switch]="true" sizing="xl"> <!-- License Configuration -->
<input cFormCheckInput [(ngModel)]="sysconfigs['safe_install']['value']" type="checkbox" /> <div class="config-section mb-4">
<label cFormCheckLabel>Safe Update</label> <h5 class="section-title mb-3">
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"><code style="padding: 0!important;">PRO</code> <i class="fa-solid fa-certificate me-2 text-primary"></i>License Configuration
* Download and install reqired firmware before installing the target firmware . for example it will install <i class="fa-solid fa-info-circle ms-2 text-info help-icon"
latest 7.12 then upgrade to newer version >7.13 or install Required packages before update</c-form-feedback> [cTooltip]="'MikroWizard license activation and registration settings. Required for system activation and PRO features.'"
</c-form-check> cTooltipPlacement="top"></i>
</h5>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">License Username
<i class="fa-solid fa-exclamation-triangle ms-1 text-warning help-icon"
[cTooltip]="'Your username from MikroWizard.com account. Same username you use to login to mikrowizard.com website. Required for license activation.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['username']['value']"
placeholder="Same as your mikrowizard.com login" class="form-control" />
<small class="setting-help text-warning">🔑 <strong>Same username</strong> you use to login to mikrowizard.com website</small>
</div>
</c-col>
</c-row>
</div>
<c-form-check *ngIf="ispro" [switch]="true" sizing="xl"> <!-- System Behavior -->
<input cFormCheckInput [(ngModel)]="sysconfigs['otp_force']['value']" type="checkbox" /> <div class="config-section mb-4">
<label cFormCheckLabel>Force device otp</label> <h5 class="section-title mb-3">
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"><code style="padding: 0!important;">PRO</code> <i class="fa-solid fa-cogs me-2 text-success"></i>System Behavior
* Force login to devices using otp for all users.(you can make exceptions for each user)</c-form-feedback> <i class="fa-solid fa-info-circle ms-2 text-info help-icon"
</c-form-check> [cTooltip]="'Controls how MikroWizard handles automatic updates and system maintenance. Choose based on your change management policy.'"
<button cButton color="primary" (click)="saveSysSetting()">Save</button> cTooltipPlacement="top"></i>
</h5>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Update Mode
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Automatic updates install immediately when available. Manual mode shows notifications in dashboard and waits for user confirmation.'"
cTooltipPlacement="top"></i>
</label>
<select cSelect [(ngModel)]="sysconfigs['update_mode']['value']['mode']" class="form-select">
<option value="">Choose mode...</option>
<option value="auto">🔄 Automatic Updates (Install immediately)</option>
<option value="manual">✋ Manual Updates (Confirm actions in dashboard view)</option>
</select>
<small class="setting-help">📊 <strong>Manual Mode:</strong> Updates require confirmation and actions in dashboard view</small>
</div>
</c-col>
</c-row>
</div>
</c-card-body> <!-- Security Features -->
</c-card> <div class="config-section mb-4">
</c-col> <h5 class="section-title mb-3">
</c-row> <i class="fa-solid fa-shield-alt me-2 text-danger"></i>Security & Enforcement
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Security policies automatically enforced on all managed devices. These settings override local device configurations for consistency.'"
cTooltipPlacement="top"></i>
</h5>
<div class="warning-banner mb-3">
<i class="fa-solid fa-exclamation-triangle me-2"></i>
<span><strong>Important:</strong> These settings will override device configurations. Test in lab environment before enabling in production.</span>
</div>
<div class="security-switches">
<div class="switch-item">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force Permissions
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Synchronizes MikroTik user groups with MikroWizard permissions. Ensures consistent access control across all managed devices.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🔄 Sync MikroTik user groups with MikroWizard permissions
<br><small class="text-info">💡 Ensures consistent user access control across all devices</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_perms']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force RADIUS
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Configures MikroTik devices to use MikroWizard RADIUS for admin user authentication. This is for MikroTik system users, not end users.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🔐 Configure RADIUS on MikroTik devices for admin user authentication
<br><small class="text-info">👥 For MikroTik system users authentication (not end users)</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_radius']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force Syslog
<i class="fa-solid fa-exclamation-triangle ms-1 text-warning help-icon"
[cTooltip]="'CRITICAL: Will overwrite syslog configuration on all devices. If MikroWizard server is unreachable, devices may stop logging or experience performance issues.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">📊 Automatically configure syslog settings on managed devices
<br><small class="text-warning">⚠️ Will OVERWRITE existing syslog configurations on devices</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_syslog']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item" *ngIf="ispro">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Safe Updates <c-badge color="warning" class="ms-2">PRO</c-badge>
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Prevents firmware update failures by installing intermediate versions first. Reduces risk of device brick but takes longer.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🛡️ Install required intermediate firmware versions before target version
<br><small class="text-success">✅ Highly recommended for production environments</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['safe_install']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item" *ngIf="ispro">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force Device OTP <c-badge color="warning" class="ms-2">PRO</c-badge>
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Requires two-factor authentication for device access. Users must have OTP configured. Significantly improves security.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🔐 Require OTP authentication for all device access
<br><small class="text-warning">⚠️ Ensure users have OTP configured before enabling</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['otp_force']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item" *ngIf="ispro">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">WebFig Auto Login <c-badge color="warning" class="ms-2">PRO</c-badge>
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Creates temporary OTP password for automatic WebFig login. Users don\'t need to enter credentials when accessing device WebFig through MikroWizard.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🌐 Create temporary OTP for automatic WebFig login
<br><small class="text-success">🔑 Users don't need to input passwords to access WebFig admin interface</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['proxy_auto_login']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
</div>
</div>
<div class="save-section">
<button cButton color="primary" size="lg" (click)="saveSysSetting()">
<i class="fa-solid fa-save me-2"></i>Save System Settings
</button>
</div>
</c-card-body>
<!-- Loading State -->
<c-card-body *ngIf="activeTab === 'system' && SysConfigloading" class="text-center py-5">
<c-spinner size="sm" class="mb-3"></c-spinner>
<h5 class="text-muted">Loading system configuration...</h5>
</c-card-body>
</c-card>
</div>
<c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal"> <c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal">
<c-modal-header> <c-modal-header>

View file

@ -5,9 +5,629 @@
} }
} }
} }
.mdc-line-ripple.mdc-line-ripple--deactivating.ng-star-inserted { .mdc-line-ripple.mdc-line-ripple--deactivating.ng-star-inserted {
display: none!important; display: none!important;
} }
.form-check-label{
font-weight: bold; /* Settings Container */
.settings-container {
max-width: 1200px;
margin: 0 auto;
}
.settings-card {
border: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border-radius: 12px;
overflow: hidden;
}
.settings-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
padding: 1.25rem 1.5rem;
}
/* Tabs */
.settings-tabs {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 0 1.5rem;
display: flex;
}
.tab-link {
border: none;
background: transparent;
border-radius: 0;
padding: 1rem 1.5rem;
font-weight: 500;
color: #6c757d;
transition: all 0.2s ease;
border-bottom: 3px solid transparent;
cursor: pointer;
}
.tab-link:hover {
color: #495057;
background: rgba(13, 110, 253, 0.05);
}
.tab-link.active {
color: #0d6efd;
background: white;
border-bottom-color: #0d6efd;
}
/* Tab Content */
.tab-content {
padding: 2rem;
}
/* Section Titles */
.section-title {
color: #495057;
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
}
/* Status Section */
.status-section {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1.5rem;
}
/* Download Section */
.download-section {
.section-card {
background: linear-gradient(135deg, #e7f3ff 0%, #f8f9fa 100%);
border: 2px dashed #0d6efd;
border-radius: 8px;
padding: 1.5rem;
transition: all 0.2s ease;
}
.section-card:hover {
border-color: #0a58ca;
background: linear-gradient(135deg, #cfe2ff 0%, #e7f3ff 100%);
}
.section-header {
display: flex;
align-items: center;
font-size: 1rem;
font-weight: 600;
color: #495057;
}
}
/* Firmware Settings */
.firmware-settings {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
border: 1px solid #e9ecef;
}
/* Config Sections */
.config-section {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
border: 1px solid #e9ecef;
}
/* Setting Groups */
.setting-group {
margin-bottom: 1rem;
}
.setting-label {
display: block;
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.setting-help {
color: #6c757d;
font-size: 0.8rem;
margin-top: 0.25rem;
display: block;
}
/* Security Switches */
.security-switches {
display: grid;
gap: 1rem;
}
.switch-item {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1.25rem;
transition: all 0.2s ease;
}
.switch-item:hover {
border-color: #0d6efd;
box-shadow: 0 2px 4px rgba(13, 110, 253, 0.1);
}
.switch-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.switch-info {
flex: 1;
margin-right: 1rem;
}
.switch-title {
font-size: 1rem;
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
display: flex;
align-items: center;
}
.switch-description {
color: #6c757d;
font-size: 0.85rem;
margin: 0;
line-height: 1.4;
}
/* Compact Grid */
.compact-grid {
border-radius: 6px;
overflow: hidden;
border: 1px solid #e9ecef;
}
/* Version Badge */
.version-badge {
font-family: 'Courier New', monospace;
font-weight: 600;
}
/* Form Controls */
.form-control, .form-select {
border-radius: 6px;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
/* Save Section */
.save-section {
text-align: center;
padding-top: 1rem;
border-top: 1px solid #e9ecef;
}
/* Form Check Labels */
.form-check-label {
font-weight: 500;
}
/* Button Enhancements */
.btn {
border-radius: 6px;
font-weight: 500;
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Badge Enhancements */
.badge {
font-weight: 500;
padding: 0.4em 0.6em;
}
/* Mobile Responsive for Informative Elements */
@media (max-width: 768px) {
.info-banner,
.warning-banner,
.success-banner {
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
}
.help-icon {
font-size: 0.75rem;
}
.setting-help {
font-size: 0.75rem;
}
.switch-description {
font-size: 0.8rem;
}
.switch-description small {
font-size: 0.7rem;
}
::ng-deep .tooltip-inner {
max-width: 250px;
font-size: 0.75rem;
}
}
@media (max-width: 576px) {
.info-banner,
.warning-banner,
.success-banner {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
.setting-label {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
}
/* Original Responsive Design */
@media (max-width: 768px) {
.settings-header {
padding: 1rem;
}
.settings-tabs {
padding: 0 1rem;
}
.tab-content {
padding: 1rem;
}
.tab-link {
padding: 0.75rem 1rem;
font-size: 0.9rem;
}
.status-section,
.config-section,
.firmware-settings {
padding: 1rem;
}
.download-section .section-card {
padding: 1rem;
}
.switch-content {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.switch-info {
margin-right: 0;
}
.section-title {
font-size: 1rem;
}
}
@media (max-width: 576px) {
.settings-container {
margin: 0 0.5rem;
}
.settings-card {
border-radius: 8px;
}
.tab-content {
padding: 0.75rem;
}
.status-section,
.config-section,
.firmware-settings {
padding: 0.75rem;
}
}
/* Search Select Components */
.search-select-wrapper {
position: relative;
width: 100%;
}
.search-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 0.9rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.search-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
outline: 0;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 6px 6px;
max-height: 300px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.search-option {
padding: 0.5rem 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f8f9fa;
transition: background-color 0.15s ease-in-out;
font-size: 0.85rem;
}
.search-option:hover {
background-color: #f8f9fa;
}
.search-option:last-child {
border-bottom: none;
}
.search-no-results {
padding: 0.75rem;
text-align: center;
color: #6c757d;
font-style: italic;
font-size: 0.8rem;
}
/* Animation for dropdown */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.search-dropdown {
animation: fadeIn 0.2s ease-out;
}
/* Informative Design Elements */
.help-icon {
font-size: 0.8rem;
cursor: help;
opacity: 0.7;
transition: opacity 0.2s ease;
}
.help-icon:hover {
opacity: 1;
}
.help-icon.text-danger {
color: #dc3545 !important;
opacity: 1;
}
.help-icon.text-warning {
color: #fd7e14 !important;
opacity: 0.9;
}
.info-banner {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border: 1px solid #2196f3;
border-radius: 6px;
padding: 0.75rem 1rem;
color: #0d47a1;
font-size: 0.85rem;
display: flex;
align-items: center;
}
.warning-banner {
background: linear-gradient(135deg, #fff3e0 0%, #ffcc80 100%);
border: 1px solid #ff9800;
border-radius: 6px;
padding: 0.75rem 1rem;
color: #e65100;
font-size: 0.85rem;
display: flex;
align-items: center;
}
.success-banner {
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
border: 1px solid #4caf50;
border-radius: 6px;
padding: 0.75rem 1rem;
color: #2e7d32;
font-size: 0.85rem;
display: flex;
align-items: center;
}
/* Enhanced Setting Help */
.setting-help {
color: #6c757d;
font-size: 0.8rem;
margin-top: 0.25rem;
display: block;
line-height: 1.4;
}
.setting-help strong {
color: #495057;
}
/* Enhanced Form Labels */
.setting-label {
display: flex;
align-items: center;
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
/* Enhanced Switch Descriptions */
.switch-description {
color: #6c757d;
font-size: 0.85rem;
margin: 0;
line-height: 1.4;
}
.switch-description small {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
}
.switch-description .text-warning {
color: #856404 !important;
}
.switch-description .text-info {
color: #0c5460 !important;
}
.switch-description .text-success {
color: #155724 !important;
}
.switch-description .text-danger {
color: #721c24 !important;
}
.setting-help.text-danger {
color: #721c24 !important;
font-weight: 600;
}
.setting-help.text-warning {
color: #856404 !important;
font-weight: 600;
}
/* Enhanced Section Titles */
.section-title {
color: #495057;
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
}
/* Tooltip Enhancements */
::ng-deep .tooltip {
font-size: 0.8rem;
}
::ng-deep .tooltip-inner {
max-width: 300px;
text-align: left;
background-color: #2c3e50;
border-radius: 6px;
padding: 0.5rem 0.75rem;
}
/* Enhanced Form Controls with Status */
.form-control.has-warning {
border-color: #ffc107;
box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.25);
}
.form-control.has-success {
border-color: #28a745;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}
.form-control.has-error {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
/* Enhanced Select Options */
.form-select option {
padding: 0.5rem;
}
/* Status Indicators */
.status-indicator {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
margin-left: 0.5rem;
}
.status-indicator.recommended {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-indicator.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.status-indicator.critical {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Grid Enhancements */
::ng-deep .gui-grid {
.gui-grid-header {
background: #f8f9fa;
font-weight: 600;
}
.gui-grid-cell {
padding: 0.75rem 0.5rem;
}
} }

View file

@ -37,6 +37,17 @@ export class SettingsComponent implements OnInit {
public filters: any = {}; public filters: any = {};
public firms: any = {}; public firms: any = {};
public firmtodownload: any = {}; public firmtodownload: any = {};
public activeTab: string = 'firmware';
// Search functionality properties
public firmwareSearch: string = '';
public showFirmwareDropdown: boolean = false;
public filteredFirmwares: any[] = [];
public timezoneSearch: string = '';
public showTimezoneDropdown: boolean = false;
public filteredTimezones: any[] = [];
constructor( constructor(
private data_provider: dataProvider, private data_provider: dataProvider,
private router: Router, private router: Router,
@ -293,6 +304,12 @@ export class SettingsComponent implements OnInit {
_self.sysconfigs["default_user"]["value"] = ""; _self.sysconfigs["default_user"]["value"] = "";
_self.sysconfigs["default_password"]["value"] = ""; _self.sysconfigs["default_password"]["value"] = "";
_self.timezones = _self.TimeZones.timezones; _self.timezones = _self.TimeZones.timezones;
_self.filteredTimezones = _self.TimeZones.timezones;
// Set initial timezone search display
const currentTz = _self.timezones.find((tz: any) => tz.utc[0] === _self.sysconfigs['timezone']['value']);
if (currentTz) {
_self.timezoneSearch = currentTz.text;
}
_self.sysconfigs["force_syslog"]["value"] = /true/i.test( _self.sysconfigs["force_syslog"]["value"] = /true/i.test(
_self.sysconfigs["force_syslog"]["value"] _self.sysconfigs["force_syslog"]["value"]
); );
@ -310,6 +327,16 @@ export class SettingsComponent implements OnInit {
_self.sysconfigs["otp_force"]["value"] _self.sysconfigs["otp_force"]["value"]
); );
} }
if(_self.ispro && "proxy_auto_login" in _self.sysconfigs){
_self.sysconfigs["proxy_auto_login"]["value"] = /true/i.test(
_self.sysconfigs["proxy_auto_login"]["value"]
);
}
else if(_self.ispro){
_self.sysconfigs["proxy_auto_login"] = {
"value": true
}
}
//check if update_mode is in the sysconfigs //check if update_mode is in the sysconfigs
if ("update_mode" in _self.sysconfigs){ if ("update_mode" in _self.sysconfigs){
//convert string to json //convert string to json
@ -335,7 +362,50 @@ export class SettingsComponent implements OnInit {
this.data_provider.get_downloadable_firms().then((res) => { this.data_provider.get_downloadable_firms().then((res) => {
let index = 1; let index = 1;
_self.firms = res.versions; _self.firms = res.versions;
_self.filteredFirmwares = res.versions;
_self.loading = false; _self.loading = false;
}); });
} }
// Firmware search methods
filterFirmwares(event: any): void {
const searchTerm = event.target.value.toLowerCase();
this.firmwareSearch = searchTerm;
this.filteredFirmwares = this.firms.filter((firm: string) =>
firm.toLowerCase().includes(searchTerm)
);
}
selectFirmware(firmware: string): void {
this.firmtodownload = firmware;
this.firmwareSearch = firmware;
this.showFirmwareDropdown = false;
}
hideFirmwareDropdown(): void {
setTimeout(() => {
this.showFirmwareDropdown = false;
}, 200);
}
// Timezone search methods
filterTimezones(event: any): void {
const searchTerm = event.target.value.toLowerCase();
this.timezoneSearch = searchTerm;
this.filteredTimezones = this.timezones.filter((tz: any) =>
tz.text.toLowerCase().includes(searchTerm)
);
}
selectTimezone(timezone: any): void {
this.sysconfigs['timezone']['value'] = timezone.utc[0];
this.timezoneSearch = timezone.text;
this.showTimezoneDropdown = false;
}
hideTimezoneDropdown(): void {
setTimeout(() => {
this.showTimezoneDropdown = false;
}, 200);
}
} }

View file

@ -10,14 +10,14 @@ import {
SpinnerModule, SpinnerModule,
ToastModule, ToastModule,
ModalModule, ModalModule,
BadgeModule,
TooltipModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { SettingsRoutingModule } from "./settings-routing.module"; import { SettingsRoutingModule } from "./settings-routing.module";
import { SettingsComponent } from "./settings.component"; import { SettingsComponent } from "./settings.component";
import { GuiGridModule } from "@generic-ui/ngx-grid"; import { GuiGridModule } from "@generic-ui/ngx-grid";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { MatSelectModule } from "@angular/material/select";
import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
@NgModule({ @NgModule({
imports: [ imports: [
@ -30,11 +30,11 @@ import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
ButtonModule, ButtonModule,
ButtonGroupModule, ButtonGroupModule,
GuiGridModule, GuiGridModule,
MatSelectModule,
NgxMatSelectSearchModule,
SpinnerModule, SpinnerModule,
ToastModule, ToastModule,
ModalModule, ModalModule,
BadgeModule,
TooltipModule,
], ],
declarations: [SettingsComponent], declarations: [SettingsComponent],
}) })

View file

@ -318,7 +318,7 @@ export class SnippetsComponent implements OnInit, OnDestroy {
initGridTable(): void { initGridTable(): void {
var _self = this; var _self = this;
_self.data_provider.get_snippets("", "", "", 0, 1000).then((res) => { _self.data_provider.get_snippets("", "", "", 0, 1000,false).then((res) => {
_self.source = res.map((x: any) => { _self.source = res.map((x: any) => {
x.created = [ x.created = [
x.created.split("T")[0], x.created.split("T")[0],

View file

@ -56,124 +56,200 @@
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal"> <c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
<c-modal-header> <c-modal-header>
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle>Editing User {{SelectedUser['name']}}</h5> <h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle><i class="fa-solid fa-user-pen me-1"></i>Edit: {{SelectedUser['username']}}</h5>
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle>Adding new User</h5> <h5 *ngIf="SelectedUser['action']=='add'" cModalTitle><i class="fa-solid fa-user-plus me-1"></i>Add User</h5>
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button> <button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-3">
<div [cFormFloating]="true" class="mb-3"> <!-- User Details -->
<input cFormControl id="floatingInput" placeholder="User Name" [(ngModel)]="SelectedUser['username']" /> <div class="user-form-section mb-4">
<label cLabel for="floatingInput">User Name</label> <div class="section-header mb-3">
</div> <h6 class="section-title mb-1"><i class="fa-solid fa-user me-2"></i>Basic Information</h6>
<small class="text-muted">Enter the user's personal details and login credentials</small>
<c-input-group class="mb-3"> </div>
<span cInputGroupText>First Name</span> <c-row class="g-3">
<input cFormControl id="floatingInput" placeholder="First Name" [(ngModel)]="SelectedUser['first_name']" /> <c-col xs="12" md="6">
<input cFormControl placeholder="Username (required for login)" [(ngModel)]="SelectedUser['username']"
<span cInputGroupText>Last Name</span> class="form-input" title="Unique username for system login" />
<input cFormControl id="floatingInput" placeholder="Last Name" [(ngModel)]="SelectedUser['last_name']" /> </c-col>
</c-input-group> <c-col xs="12" md="6">
<input cFormControl placeholder="Email Address (for notifications)" [(ngModel)]="SelectedUser['email']"
<div [cFormFloating]="true" class="mb-3"> class="form-input" type="email" title="Valid email address for system notifications" />
<input cFormControl id="floatingInput" placeholder="Email Address" [(ngModel)]="SelectedUser['email']" /> </c-col>
<label cLabel for="floatingInput">Email Address</label> <c-col xs="12" md="4">
</div> <input cFormControl placeholder="First Name (optional)" [(ngModel)]="SelectedUser['first_name']"
class="form-input" title="User's first name" />
<div [cFormFloating]="true" class="mb-3"> </c-col>
<input type="password" cFormControl id="floatingInput" placeholder="Password" [(ngModel)]="SelectedUser['password']" /> <c-col xs="12" md="4">
<label cLabel for="floatingInput">Password</label> <input cFormControl placeholder="Last Name (optional)" [(ngModel)]="SelectedUser['last_name']"
</div> class="form-input" title="User's last name" />
<c-input-group> </c-col>
<h5>MikroWizard permisssions :</h5> <c-col xs="12" md="4">
<c-container> <input type="password" cFormControl placeholder="Password (min 6 characters)" [(ngModel)]="SelectedUser['password']"
<c-row> class="form-input" title="Secure password for user login" />
<c-col *ngFor='let perm of adminperms | keyvalue' [md]="6" class="mb-1">
<label cFormCheckLabel style="text-transform: capitalize">{{ perm.key}} :</label>
<c-form-check class="md-6" [switch]="true" style="float: right;">
<c-button-group>
<c-button-group aria-label="Basic example" role="group">
<button cButton color="info" variant="outline" size="sm" [active]="adminperms[perm.key]=='read'"
(click)="setRadioValue(perm.key,'read')">Read</button>
<button cButton color="danger" variant="outline" size="sm" [active]="adminperms[perm.key]=='write'"
(click)="setRadioValue(perm.key,'write')">Write</button>
<button cButton color="success" variant="outline" size="sm" [active]="adminperms[perm.key]=='full'"
(click)="setRadioValue(perm.key,'full')">Full</button>
<button cButton color="dark" variant="outline" size="sm" [active]="adminperms[perm.key]=='none'"
(click)="setRadioValue(perm.key,'none')">None</button>
</c-button-group>
</c-button-group>
</c-form-check>
</c-col> </c-col>
</c-row> </c-row>
</c-container>
</c-input-group>
<c-input-group *ngIf="userperms.length>0" class="mb-3">
<h5>Mikrotik permisssions :</h5>
<gui-grid [autoResizeWidth]="true" [source]="userperms" [columnMenu]="columnMenu" [sorting]="sorting"
[autoResizeWidth]=true [paging]="paging" >
<gui-grid-column header="Group Name" field="group_name">
<ng-template let-value="item.group_name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="perm Name" field="perm_name">
<ng-template let-value="item.perm_name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" (click)="confirm_delete_perm(item);"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-input-group>
<hr />
<table >
<td style="width: 30%;">
<span>Add new Permission</span>
</td>
<td>
<mat-form-field>
<mat-select cFormControl [(ngModel)]="devgroup" placeholder="Device Group" #singleSelect>
<mat-option>
<ngx-mat-select-search></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let group of allDevGroups" [value]="group">
{{group.name}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
<td>
<mat-form-field>
<mat-select cFormControl placeholder="Permission" [(ngModel)]="permission" #singleSelect>
<mat-option>
<ngx-mat-select-search></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let perm of allPerms" [value]="perm">
{{perm.name}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
<td>
<button *ngIf="SelectedUser['action']=='edit'" cButton color="primary" (click)="add_user_perm()">Add+</button>
<button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="add_new_user_perm()">Add+</button>
<!-- <button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="loading=!loading">++</button> -->
</td>
</table>
</c-modal-body>
<c-modal-footer style="justify-content: space-between;">
<div>
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger">Deactive</button>
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success">Activate</button>
</div> </div>
<!-- System Permissions -->
<div class="permissions-section mb-4">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-shield-halved me-2"></i>System Access Levels</h6>
<small class="text-muted">Control what system features this user can access and modify</small>
</div>
<div class="permission-legend mb-2">
<span class="legend-item"><span class="legend-color bg-info"></span>R = Read Only</span>
<span class="legend-item"><span class="legend-color bg-warning"></span>W = Read & Write</span>
<span class="legend-item"><span class="legend-color bg-success"></span>F = Full Control</span>
<span class="legend-item"><span class="legend-color bg-secondary"></span>× = No Access</span>
</div>
<div class="permissions-compact">
<div *ngFor='let perm of adminperms | keyvalue' class="perm-row">
<span class="perm-label">{{ perm.key.replace('_', ' ') }}</span>
<div class="perm-controls">
<button cButton size="sm" variant="outline" color="info" [active]="adminperms[perm.key]=='read'"
(click)="setRadioValue(perm.key,'read')" title="Read Only Access">R</button>
<button cButton size="sm" variant="outline" color="warning" [active]="adminperms[perm.key]=='write'"
(click)="setRadioValue(perm.key,'write')" title="Read & Write Access">W</button>
<button cButton size="sm" variant="outline" color="success" [active]="adminperms[perm.key]=='full'"
(click)="setRadioValue(perm.key,'full')" title="Full Control Access">F</button>
<button cButton size="sm" variant="outline" color="secondary" [active]="adminperms[perm.key]=='none'"
(click)="setRadioValue(perm.key,'none')" title="No Access">×</button>
</div>
</div>
</div>
</div>
<!-- Device Permissions -->
<div class="device-permissions-section">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-network-wired me-2"></i>Device Access Control</h6>
<small class="text-muted">Grant access to specific device groups with defined permission levels</small>
</div>
<!-- Add New Permission -->
<div class="add-permission-card mb-3">
<div class="add-header">
<i class="fa-solid fa-plus-circle text-primary me-2"></i>
<span class="fw-semibold">Add Device Permission</span>
</div>
<c-row class="g-2 mt-2">
<c-col xs="12" md="5">
<div class="search-select-wrapper">
<label class="search-label">Device Group</label>
<input cFormControl
[(ngModel)]="devgroupSearch"
(input)="filterDevGroups($event)"
(focus)="showDevGroupDropdown = true"
(blur)="hideDevGroupDropdown()"
placeholder="Search and select device group..."
class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length > 0" class="search-dropdown">
<div *ngFor="let group of filteredDevGroups"
class="search-option"
(mousedown)="selectDevGroup(group)">
{{group.name}}
</div>
</div>
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length === 0 && devgroupSearch" class="search-no-results">
No groups found
</div>
</div>
</c-col>
<c-col xs="12" md="5">
<div class="search-select-wrapper">
<label class="search-label">Permission Level</label>
<input cFormControl
[(ngModel)]="permissionSearch"
(input)="filterPermissions($event)"
(focus)="showPermissionDropdown = true"
(blur)="hidePermissionDropdown()"
placeholder="Search and select permission..."
class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
<div *ngFor="let perm of filteredPermissions"
class="search-option"
(mousedown)="selectPermission(perm)">
{{perm.name}}
</div>
</div>
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch" class="search-no-results">
No permissions found
</div>
</div>
</c-col>
<c-col xs="12" md="2" class="d-flex align-items-end">
<button *ngIf="SelectedUser['action']=='edit'" cButton color="success" size="sm" class="w-100 add-btn"
(click)="add_user_perm()" [disabled]="!devgroup || !permission">
<i class="fa-solid fa-plus me-1"></i>Add Access
</button>
<button *ngIf="SelectedUser['action']=='add'" cButton color="success" size="sm" class="w-100 add-btn"
(click)="add_new_user_perm()" [disabled]="!devgroup || !permission">
<i class="fa-solid fa-plus me-1"></i>Add Access
</button>
</c-col>
</c-row>
</div>
<!-- Current Permissions -->
<div class="current-permissions">
<div class="permissions-header">
<h6 class="mb-1"><i class="fa-solid fa-list-check me-2 text-success"></i>Current Device Access</h6>
<span class="badge bg-info">{{userperms.length}} permission(s)</span>
</div>
<div *ngIf="userperms.length>0" class="permissions-list mt-2">
<div *ngFor="let perm of userperms; let i = index" class="permission-item">
<div class="perm-number">{{i + 1}}</div>
<div class="perm-info">
<div class="perm-main">
<i class="fa-solid fa-server me-1 text-primary"></i>
<span class="group-name">{{perm.group_name}}</span>
</div>
<div class="perm-detail">
<i class="fa-solid fa-key me-1 text-warning"></i>
<span class="perm-name">{{perm.perm_name}}</span>
</div>
</div>
<button cButton color="danger" size="sm" variant="outline" (click)="confirm_delete_perm(perm)"
title="Remove this permission" class="remove-btn">
<i class="fa-solid fa-trash-can"></i>
</button>
</div>
</div>
<div *ngIf="userperms.length==0" class="empty-permissions">
<div class="empty-icon">
<i class="fa-solid fa-shield-xmark text-muted"></i>
</div>
<div class="empty-text">
<strong>No device access granted</strong>
<p class="mb-0 text-muted">Add permissions above to grant access to device groups</p>
</div>
</div>
</div>
</div>
</c-modal-body>
<c-modal-footer class="d-flex justify-content-between flex-wrap gap-2 p-2">
<div> <div>
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button> <button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger" size="sm">
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button> <i class="fa-solid fa-user-slash me-1"></i><span class="d-none d-sm-inline">Deactivate</span>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary"> </button>
Close <button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success" size="sm">
<i class="fa-solid fa-user-check me-1"></i><span class="d-none d-sm-inline">Activate</span>
</button>
</div>
<div class="d-flex gap-2">
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary" size="sm">
<i class="fa-solid fa-plus me-1"></i>Add
</button>
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary" size="sm">
<i class="fa-solid fa-save me-1"></i>Save
</button>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary" size="sm">
<i class="fa-solid fa-times me-1"></i>Close
</button> </button>
</div> </div>
</c-modal-footer> </c-modal-footer>

View file

@ -78,6 +78,12 @@ export class UserManagerComponent implements OnInit {
public permission: any = {}; public permission: any = {};
public allDevGroups: any = []; public allDevGroups: any = [];
public allPerms: any = []; public allPerms: any = [];
public devgroupSearch: string = '';
public permissionSearch: string = '';
public filteredDevGroups: any = [];
public filteredPermissions: any = [];
public showDevGroupDropdown: boolean = false;
public showPermissionDropdown: boolean = false;
public DeletePermConfirmModalVisible: boolean = false; public DeletePermConfirmModalVisible: boolean = false;
public userperms: any = {}; public userperms: any = {};
public userresttrictions: any = false; public userresttrictions: any = false;
@ -129,6 +135,40 @@ export class UserManagerComponent implements OnInit {
this.adminperms[key] = value; this.adminperms[key] = value;
} }
filterDevGroups(event: any): void {
const query = event.target.value.toLowerCase();
this.filteredDevGroups = this.allDevGroups.filter((group: any) =>
group.name.toLowerCase().includes(query)
);
}
filterPermissions(event: any): void {
const query = event.target.value.toLowerCase();
this.filteredPermissions = this.allPerms.filter((perm: any) =>
perm.name.toLowerCase().includes(query)
);
}
selectDevGroup(group: any): void {
this.devgroup = group;
this.devgroupSearch = group.name;
this.showDevGroupDropdown = false;
}
selectPermission(perm: any): void {
this.permission = perm;
this.permissionSearch = perm.name;
this.showPermissionDropdown = false;
}
hideDevGroupDropdown(): void {
setTimeout(() => this.showDevGroupDropdown = false, 200);
}
hidePermissionDropdown(): void {
setTimeout(() => this.showPermissionDropdown = false, 200);
}
public rowSelection: boolean | GuiRowSelection = { public rowSelection: boolean | GuiRowSelection = {
enabled: true, enabled: true,
type: GuiRowSelectionType.CHECKBOX, type: GuiRowSelectionType.CHECKBOX,
@ -226,6 +266,7 @@ export class UserManagerComponent implements OnInit {
_self.allPerms = res.map((x: any) => { _self.allPerms = res.map((x: any) => {
return { id: x["id"], name: x.name }; return { id: x["id"], name: x.name };
}); });
_self.filteredPermissions = [..._self.allPerms];
_self.data_provider.get_devgroup_list().then((res) => { _self.data_provider.get_devgroup_list().then((res) => {
if ("error" in res && res.error.indexOf("Unauthorized")) { if ("error" in res && res.error.indexOf("Unauthorized")) {
_self.show_toast( _self.show_toast(
@ -238,6 +279,7 @@ export class UserManagerComponent implements OnInit {
_self.allDevGroups = res.map((x: any) => { _self.allDevGroups = res.map((x: any) => {
return { id: x["id"], name: x.name }; return { id: x["id"], name: x.name };
}); });
_self.filteredDevGroups = [..._self.allDevGroups];
} }
}); });
} }
@ -254,6 +296,10 @@ export class UserManagerComponent implements OnInit {
action: "add", action: "add",
}; };
this.adminperms = { ...this.defadminperms }; this.adminperms = { ...this.defadminperms };
this.devgroup = {};
this.permission = {};
this.devgroupSearch = '';
this.permissionSearch = '';
this.EditTaskModalVisible = true; this.EditTaskModalVisible = true;
return; return;
} }
@ -263,6 +309,10 @@ export class UserManagerComponent implements OnInit {
} else this.adminperms = { ...this.defadminperms }; } else this.adminperms = { ...this.defadminperms };
_self.SelectedUser["action"] = "edit"; _self.SelectedUser["action"] = "edit";
_self.get_user_perms(_self.SelectedUser["id"]); _self.get_user_perms(_self.SelectedUser["id"]);
_self.devgroup = {};
_self.permission = {};
_self.devgroupSearch = '';
_self.permissionSearch = '';
_self.EditTaskModalVisible = true; _self.EditTaskModalVisible = true;
} }

View file

@ -1,4 +1,462 @@
table tr td{ table tr td{
padding-bottom:20px; padding-bottom:20px;
vertical-align:top vertical-align:top
}
/* Enhanced Modal Styles */
.permission-item {
transition: all 0.2s ease;
background: #fafafa;
min-height: 60px;
}
.permission-item:hover {
background: #f0f0f0;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Modern Form Sections */
.user-form-section, .permissions-section, .device-permissions-section {
border-radius: 8px;
background: #f8f9fa;
padding: 1rem;
border: 1px solid #e9ecef;
}
.section-title {
color: #495057;
font-weight: 600;
margin-bottom: 0.75rem;
font-size: 0.95rem;
}
.form-input {
border-radius: 6px;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.form-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
/* Compact Permissions */
.permissions-compact {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 0.5rem;
}
.perm-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.4rem 0.6rem;
background: white;
border: 1px solid #dee2e6;
border-radius: 6px;
transition: all 0.2s ease;
}
.perm-row:hover {
background: #f1f3f4;
border-color: #adb5bd;
}
.perm-label {
font-size: 0.85rem;
font-weight: 500;
color: #495057;
text-transform: capitalize;
flex: 1;
}
.perm-controls {
display: flex;
gap: 2px;
}
.perm-controls .btn {
padding: 0.2rem 0.4rem;
font-size: 0.7rem;
font-weight: 600;
min-width: 24px;
border-radius: 4px;
}
/* Section Headers */
.section-header {
border-bottom: 1px solid #e9ecef;
padding-bottom: 0.5rem;
}
/* Permission Legend */
.permission-legend {
display: flex;
flex-wrap: wrap;
gap: 1rem;
padding: 0.5rem;
background: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
}
.legend-item {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.75rem;
color: #6c757d;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
display: inline-block;
}
/* Add Permission Card */
.add-permission-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px dashed #0d6efd;
border-radius: 8px;
padding: 1rem;
transition: all 0.2s ease;
}
.add-permission-card:hover {
border-color: #0a58ca;
background: linear-gradient(135deg, #e7f3ff 0%, #cfe2ff 100%);
}
.add-header {
display: flex;
align-items: center;
font-size: 0.9rem;
color: #495057;
margin-bottom: 0.5rem;
}
.add-btn {
font-weight: 600;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.add-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Current Permissions */
.current-permissions {
background: white;
border-radius: 8px;
border: 1px solid #dee2e6;
overflow: hidden;
}
.permissions-header {
background: #f8f9fa;
padding: 0.75rem 1rem;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
}
.permissions-list {
max-height: 250px;
overflow-y: auto;
}
.permission-item {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
border-bottom: 1px solid #f1f3f4;
transition: all 0.2s ease;
}
.permission-item:last-child {
border-bottom: none;
}
.permission-item:hover {
background: #f8f9fa;
}
.perm-number {
background: #0d6efd;
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
margin-right: 0.75rem;
flex-shrink: 0;
}
.perm-info {
flex: 1;
}
.perm-main {
display: flex;
align-items: center;
margin-bottom: 0.25rem;
}
.perm-detail {
display: flex;
align-items: center;
}
.group-name {
font-size: 0.85rem;
font-weight: 600;
color: #495057;
}
.perm-name {
font-size: 0.75rem;
color: #6c757d;
}
.remove-btn {
margin-left: 0.5rem;
flex-shrink: 0;
}
/* Empty State */
.empty-permissions {
display: flex;
align-items: center;
padding: 2rem 1rem;
text-align: center;
}
.empty-icon {
font-size: 2rem;
margin-right: 1rem;
opacity: 0.5;
}
.empty-text strong {
display: block;
margin-bottom: 0.25rem;
color: #495057;
}
.compact-select {
font-size: 0.85rem;
height: calc(1.8em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
}
.compact-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
/* Search Select Components */
.search-select-wrapper {
position: relative;
}
.search-label {
display: block;
font-size: 0.8rem;
color: #6c757d;
margin-bottom: 0.25rem;
font-weight: 500;
}
.search-input {
width: 100%;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 0.375rem 0.375rem;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.search-option {
padding: 0.5rem 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f1f3f4;
font-size: 0.85rem;
transition: background-color 0.2s ease;
}
.search-option:hover {
background-color: #f8f9fa;
}
.search-option:last-child {
border-bottom: none;
}
.search-no-results {
padding: 0.75rem;
text-align: center;
color: #6c757d;
font-size: 0.8rem;
font-style: italic;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 0.375rem 0.375rem;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1000;
}
.badge {
font-size: 0.7rem;
}
.add-permission-form {
border: 1px dashed #dee2e6;
}
.c-modal-body {
max-height: 85vh;
overflow-y: auto;
}
.c-card-header {
border-bottom: 1px solid #e9ecef;
}
.permission-item small {
color: #6c757d;
font-weight: 500;
}
.c-button-group .btn {
font-size: 0.75rem;
font-weight: 500;
min-width: 28px;
}
.compact-field {
font-size: 0.85rem;
}
.compact-field .mat-form-field-wrapper {
padding-bottom: 0.5em;
}
/* Mobile Responsive */
@media (max-width: 576px) {
.c-modal-dialog {
margin: 0.25rem;
}
.user-form-section, .permissions-section, .device-permissions-section {
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.permissions-compact {
grid-template-columns: 1fr;
}
.perm-row {
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
padding: 0.5rem;
}
.perm-label {
text-align: center;
margin-bottom: 0.25rem;
}
.perm-controls {
justify-content: center;
}
.add-permission-card {
padding: 0.75rem;
}
.permission-legend {
gap: 0.5rem;
justify-content: center;
}
.permissions-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.empty-permissions {
flex-direction: column;
text-align: center;
}
.empty-icon {
margin-right: 0;
margin-bottom: 0.5rem;
}
.section-title {
font-size: 0.9rem;
}
.c-button-group .btn {
font-size: 0.7rem;
padding: 0.25rem 0.3rem;
min-width: 24px;
}
.c-modal-body {
max-height: 90vh;
padding: 0.5rem !important;
}
.c-card-body {
padding: 0.75rem !important;
}
.add-permission-form {
padding: 0.75rem !important;
}
}
@media (max-width: 768px) {
.c-modal-footer {
flex-direction: column;
align-items: stretch;
}
.c-modal-footer > div {
width: 100%;
text-align: center;
}
} }

View file

@ -57,146 +57,255 @@
</c-row> </c-row>
<c-modal #EditTaskModal backdrop="static" size="xl" [(visible)]="EditTaskModalVisible" id="EditTaskModal"> <c-modal #EditTaskModal backdrop="static" size="xl" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
<c-modal-header> <c-modal-header class="bg-light">
<h5 *ngIf="SelectedTask['action']=='edit'" cModalTitle>Editing device {{SelectedTask['name']}}</h5> <h5 *ngIf="SelectedTask['action']=='edit'" cModalTitle>
<h5 *ngIf="SelectedTask['action']=='add'" cModalTitle>Adding new task</h5> <i class="fa-solid fa-edit me-2"></i>Edit Task: {{SelectedTask['name']}}
</h5>
<h5 *ngIf="SelectedTask['action']=='add'" cModalTitle>
<i class="fa-solid fa-plus me-2"></i>Create New Task
</h5>
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button> <button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-4">
<div [cFormFloating]="true" class="mb-3"> <!-- Basic Information Section -->
<input cFormControl id="floatingInput" placeholder="SelectedTask['name']" [(ngModel)]="SelectedTask['name']" /> <div class="task-form-section mb-4">
<label cLabel for="floatingInput">Name</label> <div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-info-circle me-2"></i>Basic Information</h6>
<small class="text-muted">Define the task name, description and type</small>
</div>
<c-row class="g-3">
<c-col xs="12" md="6">
<input cFormControl placeholder="Task Name (required)" [(ngModel)]="SelectedTask['name']"
class="form-input" title="Unique name for this task" />
</c-col>
<c-col xs="12" md="6">
<select cSelect [(ngModel)]="SelectedTask['task_type']" (change)="onTaskTypeChange()" class="form-select" title="Select task type">
<option value="">Choose Task Type...</option>
<option value="backup">📁 Backup Configuration</option>
<option value="snippet">📝 Execute Script/Snippet</option>
<option value="firmware" *ngIf="ispro">🔄 Firmware Update</option>
</select>
</c-col>
<c-col xs="12">
<textarea cFormControl placeholder="Task Description (optional)" [(ngModel)]="SelectedTask['description']"
class="form-input" rows="2" title="Brief description of what this task does"></textarea>
</c-col>
</c-row>
</div> </div>
<!-- Task Configuration Section -->
<div [cFormFloating]="true" class="mb-3"> <div class="task-config-section mb-4" *ngIf="SelectedTask['task_type']">
<input cFormControl id="floatingInput" placeholder="SelectedTask['description']" <div class="section-header mb-3">
[(ngModel)]="SelectedTask['description']" /> <h6 class="section-title mb-1"><i class="fa-solid fa-cog me-2"></i>Task Configuration</h6>
<label cLabel for="floatingInput">Description</label> <small class="text-muted">Configure task-specific settings and parameters</small>
</div>
<!-- Backup Configuration -->
<div *ngIf="SelectedTask['task_type']=='backup'" class="backup-config mb-3">
<c-card class="mb-3">
<c-card-header class="bg-success text-white">
<h6 class="mb-0"><i class="fa-solid fa-database me-2"></i>Backup Configuration</h6>
</c-card-header>
<c-card-body>
<div class="alert alert-info">
<i class="fa-solid fa-info-circle me-2"></i>
This task will create configuration backups of selected devices. Backups are stored securely and can be restored later.
</div>
</c-card-body>
</c-card>
</div>
<!-- Firmware Configuration -->
<c-card *ngIf="SelectedTask['task_type']=='firmware'" class="mb-3">
<c-card-header class="bg-warning text-dark">
<h6 class="mb-0"><i class="fa-solid fa-microchip me-2"></i>Firmware Update Strategy</h6>
</c-card-header>
<c-card-body>
<div class="strategy-buttons mb-3">
<c-button-group role="group" class="w-100">
<button cButton [active]="SelectedTask['data']['strategy']=='system'"
(click)="firmware_type_changed('system')" color="info" variant="outline" class="flex-fill">
<i class="fa-solid fa-cogs me-1"></i>System Default
</button>
<button cButton [active]="SelectedTask['data']['strategy']=='defined'"
(click)="firmware_type_changed('defined')" color="warning" variant="outline" class="flex-fill">
<i class="fa-solid fa-list me-1"></i>Custom Version
</button>
<button cButton [active]="SelectedTask['data']['strategy']=='latest'"
(click)="firmware_type_changed('latest')" color="success" variant="outline" class="flex-fill">
<i class="fa-solid fa-download me-1"></i>Latest Available
</button>
</c-button-group>
</div>
<div *ngIf=" SelectedTask['data']['strategy']=='system'" class="alert alert-info">
<i class="fa-solid fa-info-circle me-2"></i>
Uses global MikroWizard update strategy settings. Check Settings page for configuration.
</div>
<div *ngIf="firms_loaded && SelectedTask['data']['strategy']=='latest'" class="alert alert-success">
<i class="fa-solid fa-cloud-download-alt me-2"></i>
Downloads latest firmware from mikrotik.com. Server needs internet access.
</div>
<div *ngIf="firms_loaded && SelectedTask['data']['strategy']=='defined'">
<c-row class="g-3">
<c-col xs="12" md="6">
<label class="form-label">RouterOS v7+ Version</label>
<select cSelect [(ngModel)]="SelectedTask['data']['version_to_install']" class="form-select">
<option value="">Choose version...</option>
<option *ngFor="let f of available_firmwares" [value]="f">{{f}}</option>
</select>
</c-col>
<c-col xs="12" md="6" *ngIf="updateBehavior=='keep'">
<label class="form-label">RouterOS v6 Version</label>
<select cSelect [(ngModel)]="SelectedTask['data']['version_to_install_6']" class="form-select">
<option value="">Choose version...</option>
<option *ngFor="let f of available_firmwaresv6" [value]="f">{{f}}</option>
</select>
</c-col>
</c-row>
</div>
</c-card-body>
</c-card>
<!-- Snippet Configuration -->
<div *ngIf="SelectedTask['task_type']=='snippet'" class="snippet-config mb-3">
<c-card class="mb-3">
<c-card-header class="bg-primary text-white">
<h6 class="mb-0"><i class="fa-solid fa-code me-2"></i>Script/Snippet Configuration</h6>
</c-card-header>
<c-card-body>
<label class="form-label">Select Script/Snippet to Execute</label>
<ngx-super-select [dataSource]="Snippets" [options]="options"
(selectionChanged)="onSelectValueChanged($event)" [selectedItemValues]="[SelectedTask['snippetid']]"
(searchChanged)="onSnippetsValueChanged($event)" class="styled"></ngx-super-select>
<small class="text-muted mt-2 d-block">
<i class="fa-solid fa-info-circle me-1"></i>
The selected script will be executed on all target devices when this task runs.
</small>
</c-card-body>
</c-card>
</div>
</div> </div>
<c-input-group class="mb-3"> <!-- Schedule Configuration -->
<label cInputGroupText for="inputGroupSelect01"> <div class="schedule-section mb-4">
Options <div class="section-header mb-3">
</label> <h6 class="section-title mb-1"><i class="fa-solid fa-clock me-2"></i>Schedule Configuration</h6>
<select cSelect id="inputGroupSelect01" [(ngModel)]="SelectedTask['task_type']"> <small class="text-muted">Set when this task should run automatically</small>
<option>Choose...</option> </div>
<option value="backup">Backup</option> <div class="cron-input-wrapper">
<option value="snippet">Snippet</option> <label class="form-label">Execution Schedule (Cron Expression)</label>
<option value="firmware" *ngIf="ispro">Firmware</option> <div class="search-select-wrapper">
</select> <div class="input-group">
</c-input-group> <input cFormControl
<h6 *ngIf="SelectedTask['task_type']=='firmware'" >Update Version Strategy</h6> [(ngModel)]="SelectedTask['cron']"
<c-card *ngIf="SelectedTask['task_type']=='firmware'"> (input)="onCronInputChange($event)"
<c-card-header style="padding: 0;"> (focus)="onCronInputFocus()"
<c-input-group> (blur)="hideCronDropdown()"
<c-button-group aria-label="Basic radio toggle button group" placeholder="Enter cron expression or search presets..."
role="group"> class="search-input"
<input class="btn-check" type="radio" value="Radio2" /> autocomplete="off" />
<label (click)="firmware_type_changed('system')" [active]="SelectedTask['data']['strategy']=='system'" <button class="btn btn-outline-secondary" type="button" (click)="onCronInputFocus()" title="Show presets">
cButton cFormCheckLabel color="dark" variant="ghost">System setting <i class="fa-solid fa-clock"></i>
defined</label> </button>
<input class="btn-check" type="radio" value="Radio1" /> </div>
<label (click)="firmware_type_changed('defined')" [active]="SelectedTask['data']['strategy']=='defined'" <div *ngIf="showCronDropdown && filteredCrons.length > 0" class="search-dropdown cron-dropdown">
cButton cFormCheckLabel color="dark" variant="ghost">Custom <div *ngFor="let cron of filteredCrons"
Version</label> class="search-option cron-option"
<input class="btn-check" type="radio" value="Radio3" /> [class.selected]="selectedCronPreset?.value === cron.value"
<label (click)="firmware_type_changed('latest')" [active]="SelectedTask['data']['strategy']=='latest'" (mousedown)="selectCron(cron)">
cButton cFormCheckLabel color="dark" variant="ghost">Latest <div class="cron-label">{{cron.label}}</div>
availble</label> <div class="cron-value">{{cron.value}}</div>
</c-button-group> <div class="cron-description">{{cron.description}}</div>
</c-input-group> </div>
</c-card-header> </div>
<c-card-body> <div *ngIf="showCronDropdown && filteredCrons.length === 0 && cronSearch" class="search-no-results">
No matching cron presets found
<c-input-group </div>
*ngIf="firms_loaded && SelectedTask['task_type']=='firmware' && SelectedTask['data']['strategy']=='system'"> </div>
<c-form-feedback style="display: block;color: #48515a;margin-top: 0;" [valid]="true"> <small class="text-muted mt-1 d-block">
The version of firmware will be selected based on global settings of Mikrowizard Update strategy. <i class="fa-solid fa-info-circle me-1"></i>{{getCronDescription()}}
<br/> </small>
Please check settings page for more info and configuration <div class="mt-2">
</c-form-feedback> <small class="text-muted">
</c-input-group> <strong>Quick Examples:</strong>
<c-input-group <code class="me-2">* * * * *</code> = every minute |
*ngIf="firms_loaded && SelectedTask['task_type']=='firmware' && SelectedTask['data']['strategy']=='latest'"> <code class="me-2">0 2 * * *</code> = daily at 2 AM |
<code class="me-2">0 */6 * * *</code> = every 6 hours
<c-form-feedback style="display: block;color: #48515a;margin-top: 0;" [valid]="true"> </small>
The version of firmware will be selected based on latest availble version from Mikrotik website!. </div>
<br/> </div>
<b>V6 Firmware update Behavior</b> and <b>safe install</b> is based on global Mikrowizard setting.(check settings page)
<br/>
<code style="padding: 0!important;">**with this option MikroWizard will download latest availble firmware from mikrotik.com. Please keep in mind that server needs internet access to mikrotik.com</code></c-form-feedback>
</c-input-group>
<c-input-group
*ngIf="firms_loaded && SelectedTask['task_type']=='firmware' && SelectedTask['data']['strategy']=='defined'">
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Firmware version to install
</label>
<select cSelect [(ngModel)]="SelectedTask['data']['version_to_install']" id="inputGroupSelect01">
<option>Choose...</option>
<option *ngFor="let f of available_firmwares" [value]="f">{{f}}</option>
</select>
<c-form-feedback style="display: block;color: #979797;margin-top: 0;" [valid]="true">
* The version of firmware to install routers</c-form-feedback>
</c-input-group>
<c-input-group *ngIf="updateBehavior=='keep'" >
<label cInputGroupText for="inputGroupSelect01">
Firmware version v6 to install
</label>
<select cSelect [(ngModel)]="SelectedTask['data']['version_to_install_6']" id="inputGroupSelect01">
<option>Choose...</option>
<option *ngFor="let f of available_firmwaresv6" [value]="f">{{f}}</option>
</select>
<c-form-feedback style="display: block;color: #979797;margin-top: 0;" [valid]="true">
* The version of firmware to install on V6 routers</c-form-feedback>
</c-input-group>
</c-input-group>
</c-card-body>
</c-card>
<c-input-group class="mb-3">
<ngx-super-select *ngIf="SelectedTask['task_type']=='snippet'" [dataSource]="Snippets" [options]="options"
(selectionChanged)="onSelectValueChanged($event)" [selectedItemValues]="[SelectedTask['snippetid']]"
(searchChanged)="onSnippetsValueChanged($event)" class="styled"></ngx-super-select>
</c-input-group>
<div [cFormFloating]="true" class="mb-3">
<input cFormControl id="floatingInput" placeholder="SelectedTask['name']" [(ngModel)]="SelectedTask['cron']" />
<label cLabel for="floatingInput">cron</label>
</div> </div>
<!-- Target Selection -->
<div class="target-section mb-4">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-bullseye me-2"></i>Target Selection</h6>
<small class="text-muted">Choose which devices or groups this task will affect</small>
</div>
<div class="target-type-selector mb-3">
<c-button-group role="group" class="w-100">
<button cButton [active]="SelectedTask['selection_type']=='devices'"
(click)="SelectedTask['selection_type']='devices'; form_changed()"
color="primary" variant="outline" class="flex-fill">
<i class="fa-solid fa-server me-1"></i>Individual Devices
</button>
<button cButton [active]="SelectedTask['selection_type']=='groups'"
(click)="SelectedTask['selection_type']='groups'; form_changed()"
color="info" variant="outline" class="flex-fill">
<i class="fa-solid fa-layer-group me-1"></i>Device Groups
</button>
</c-button-group>
</div>
<c-input-group class="mb-3"> <!-- Selected Targets -->
<label cInputGroupText for="inputGroupSelect01"> <c-card>
Member type <c-card-header class="d-flex justify-content-between align-items-center">
</label> <h6 class="mb-0">
<select cSelect id="inputGroupSelect01" (change)="form_changed()" [(ngModel)]="SelectedTask['selection_type']"> <i class="fa-solid fa-list me-2"></i>Selected {{SelectedTask['selection_type'] === 'devices' ? 'Devices' : 'Groups'}}
<option value="devices">Devices</option> </h6>
<option value="groups">Groups</option> <div>
</select> <c-badge color="info" class="me-2">{{SelectedMembers.length}} selected</c-badge>
</c-input-group> <button cButton color="success" size="sm" (click)="show_new_member_form()">
<i class="fa-solid fa-plus me-1"></i>Add {{SelectedTask['selection_type'] === 'devices' ? 'Devices' : 'Groups'}}
<h5>Members :</h5> </button>
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting" </div>
[infoPanel]="infoPanel" [rowSelection]="rowSelection" [autoResizeWidth]=true [paging]="paging"> </c-card-header>
<gui-grid-column header="Name" field="name"> <c-card-body class="p-0">
<ng-template let-value="item.name" let-item="item" let-index="index"> <div *ngIf="SelectedMembers.length === 0" class="text-center p-4 text-muted">
&nbsp; {{value}} </ng-template> <i class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} fa-3x mb-3 opacity-50"></i>
</gui-grid-column> <h6>No {{SelectedTask['selection_type']}} selected</h6>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC" field="mac"> <p class="mb-0">Click "Add {{SelectedTask['selection_type'] === 'devices' ? 'Devices' : 'Groups'}}" to select targets for this task</p>
<ng-template let-value="item.mac" let-item="item" let-index="index"> </div>
{{value}} <div *ngIf="SelectedMembers.length > 0">
</ng-template> <gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting"
</gui-grid-column> [infoPanel]="infoPanel" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="Actions" width="120" field="action"> <gui-grid-column header="Name" field="name">
<ng-template let-value="item.id" let-item="item" let-index="index"> <ng-template let-value="item.name" let-item="item" let-index="index">
<button cButton color="danger" size="sm" (click)="remove_member(item)"><i <div class="d-flex align-items-center">
class="fa-regular fa-trash-can"></i></button> <i class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2 text-primary"></i>
</ng-template> <strong>{{value}}</strong>
</gui-grid-column> </div>
</gui-grid> </ng-template>
<hr /> </gui-grid-column>
<button cButton color="primary" (click)="show_new_member_form()">+ Add new Members</button> <gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_member(item)" title="Remove from task">
<i class="fa-solid fa-times"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</div>
<!-- <!--
<c-input-group class="mb-3"> <c-input-group class="mb-3">
<ngx-super-select <ngx-super-select
@ -220,54 +329,96 @@
--> -->
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer class="bg-light d-flex justify-content-between">
<button *ngIf="SelectedTask['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button> <div>
<button *ngIf="SelectedTask['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button> <small class="text-muted">All fields marked with * are required</small>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary"> </div>
Close <div>
</button> <button *ngIf="SelectedTask['action']=='add'" (click)="submit('add')" cButton color="primary">
<i class="fa-solid fa-plus me-1"></i>Create Task
</button>
<button *ngIf="SelectedTask['action']=='edit'" (click)="submit('edit')" cButton color="primary">
<i class="fa-solid fa-save me-1"></i>Save Changes
</button>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary" class="ms-2">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
</div>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<c-modal #NewMemberModal backdrop="static" size="lg" [(visible)]="NewMemberModalVisible" id="NewMemberModal"> <c-modal #NewMemberModal backdrop="static" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal">
<c-modal-header> <c-modal-header class="bg-success text-white">
<h5 cModalTitle>Editing Group </h5> <h5 cModalTitle>
<i class="fa-solid fa-plus-circle me-2"></i>Add {{SelectedTask['selection_type'] === 'devices' ? 'Devices' : 'Groups'}} to Task
</h5>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button> <button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-4">
<c-input-group class="mb-3"> <!-- Selection Summary -->
<h5>Group Members :</h5> <div class="mb-3" style="min-height: 58px;">
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching" <c-alert *ngIf="NewMemberRows.length > 0" color="info" class="d-flex align-items-center mb-0">
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" <i class="fa-solid fa-info-circle me-2"></i>
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true <span><strong>{{NewMemberRows.length}}</strong> {{SelectedTask['selection_type']}}(s) selected for addition to this task</span>
[paging]="paging"> </c-alert>
<gui-grid-column header="Member Name" field="name"> </div>
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template> <!-- Available Items -->
</gui-grid-column> <c-card>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="IP Address" field="ip"> <c-card-header>
<ng-template let-value="item.ip" let-item="item" let-index="index"> <h6 class="mb-0">
{{value}} <i class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2"></i>
</ng-template> Available {{SelectedTask['selection_type'] === 'devices' ? 'Devices' : 'Groups'}} ({{availbleMembers.length}} total)
</gui-grid-column> </h6>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac"> </c-card-header>
<ng-template let-value="item.mac" let-item="item" let-index="index"> <c-card-body class="p-0">
{{value}} <div *ngIf="availbleMembers.length === 0" class="text-center p-4 text-muted">
</ng-template> <i class="fa-solid fa-check-circle fa-3x mb-3 text-success opacity-50"></i>
</gui-grid-column> <h6>All {{SelectedTask['selection_type']}} are already assigned</h6>
</gui-grid> <p class="mb-0">No available {{SelectedTask['selection_type']}} to add to this task</p>
<br /> </div>
</c-input-group> <div *ngIf="availbleMembers.length > 0">
<hr /> <gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true
[paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer class="bg-light d-flex justify-content-between">
<button *ngIf="NewMemberRows.length!= 0" (click)="add_new_members()" cButton color="primary">Add {{ <div>
NewMemberRows.length }}</button> <small class="text-muted">Select {{SelectedTask['selection_type']}} from the list above to add them to this task</small>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary"> </div>
Close <div>
</button> <button *ngIf="NewMemberRows.length > 0" (click)="add_new_members()" cButton color="success">
<i class="fa-solid fa-plus me-1"></i>Add {{NewMemberRows.length}} {{SelectedTask['selection_type']}}(s)
</button>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary" class="ms-2">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
</div>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
@ -340,4 +491,6 @@
Close Close
</button> </button>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<c-toaster position="fixed" placement="top-end"></c-toaster>

View file

@ -0,0 +1,371 @@
/* Task Form Sections */
.task-form-section, .task-config-section, .schedule-section, .target-section {
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 0.75rem;
background: #f8f9fa;
}
.section-header {
border-bottom: 1px solid #dee2e6;
padding-bottom: 0.375rem;
margin-bottom: 0.75rem;
}
.section-title {
color: #495057;
font-weight: 600;
font-size: 0.875rem;
}
/* Form Inputs */
.form-input {
border: 1px solid #ced4da;
border-radius: 4px;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.form-input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
/* Strategy Buttons */
.strategy-buttons .btn-group {
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.strategy-buttons button {
font-size: 0.8rem;
padding: 0.375rem 0.75rem;
}
/* Cron Dropdown Styles */
.cron-input-wrapper {
position: relative;
}
.input-group {
position: relative;
display: flex;
flex-wrap: nowrap;
align-items: stretch;
width: 100%;
}
.input-group .search-input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
flex: 1;
}
.input-group .btn {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 1px solid #ced4da;
flex-shrink: 0;
}
.search-select-wrapper {
position: relative;
width: 100%;
}
.search-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 0.9rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.search-input:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
outline: 0;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 6px 6px;
max-height: 300px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.search-option {
padding: 0.5rem 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f8f9fa;
transition: background-color 0.15s ease-in-out;
}
.search-option:hover {
background-color: #f8f9fa;
}
.search-option:last-child {
border-bottom: none;
}
.search-no-results {
padding: 0.75rem;
text-align: center;
color: #6c757d;
font-style: italic;
}
/* Cron-specific dropdown styles */
.cron-dropdown {
max-height: 400px;
}
.cron-option {
padding: 0.75rem;
border-bottom: 1px solid #e9ecef;
}
.cron-option:hover {
background-color: #e3f2fd;
}
.cron-option.selected {
background-color: #bbdefb;
border-left: 4px solid #2196f3;
}
.cron-option.selected:hover {
background-color: #90caf9;
}
.cron-label {
font-weight: 600;
color: #495057;
font-size: 0.9rem;
}
.cron-value {
font-family: 'Courier New', monospace;
color: #007bff;
font-size: 0.85rem;
margin: 0.25rem 0;
background: #f8f9fa;
padding: 0.25rem 0.5rem;
border-radius: 4px;
display: inline-block;
}
.cron-description {
color: #6c757d;
font-size: 0.8rem;
font-style: italic;
}
/* Target Type Selector */
.target-type-selector .btn-group {
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.target-type-selector button {
font-size: 0.8rem;
padding: 0.375rem 0.75rem;
}
/* Card Enhancements */
.c-card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border: 1px solid #e9ecef;
}
.c-card-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
font-weight: 600;
}
/* Alert Enhancements */
.alert {
border-radius: 8px;
border: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.alert-info {
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);
color: #0c5460;
}
.alert-success {
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
color: #155724;
}
.alert-warning {
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
color: #856404;
}
/* Badge Enhancements */
.badge {
font-size: 0.7rem;
padding: 0.25em 0.5em;
border-radius: 4px;
}
/* Button Enhancements */
.btn {
border-radius: 4px;
font-weight: 500;
transition: all 0.15s ease-in-out;
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Modal Enhancements */
.modal-header {
border-bottom: 1px solid #dee2e6;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
.modal-footer {
border-top: 1px solid #dee2e6;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
}
/* Grid Enhancements */
.gui-grid {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Responsive Design */
@media (max-width: 768px) {
.task-form-section, .task-config-section, .schedule-section, .target-section {
padding: 0.5rem;
}
.section-title {
font-size: 0.8rem;
}
.strategy-buttons button,
.target-type-selector button {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
}
.form-input {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
}
.cron-label {
font-size: 0.8rem;
}
.cron-value {
font-size: 0.75rem;
}
.cron-description {
font-size: 0.7rem;
}
.c-modal-body {
padding: 0.75rem !important;
}
}
@media (max-width: 576px) {
.task-form-section, .task-config-section, .schedule-section, .target-section {
padding: 0.375rem;
margin-bottom: 0.75rem;
}
.section-header {
margin-bottom: 0.5rem;
}
.strategy-buttons button,
.target-type-selector button {
font-size: 0.7rem;
padding: 0.25rem 0.375rem;
}
.input-group .btn {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
}
}
/* Animation for smooth transitions */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.search-dropdown {
animation: fadeIn 0.2s ease-out;
}
/* Focus states for accessibility */
.search-option:focus,
.cron-option:focus {
outline: 2px solid #007bff;
outline-offset: -2px;
background-color: #e3f2fd;
}
/* Loading states */
.loading-spinner {
display: inline-block;
width: 1rem;
height: 1rem;
border: 2px solid #f3f3f3;
border-top: 2px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Code examples */
code {
background-color: #f8f9fa;
color: #e83e8c;
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.875em;
font-family: 'Courier New', monospace;
}
/* Improved spacing */
.mt-1 { margin-top: 0.25rem !important; }
.mt-2 { margin-top: 0.5rem !important; }
.me-1 { margin-right: 0.25rem !important; }
.me-2 { margin-right: 0.5rem !important; }
.ms-2 { margin-left: 0.5rem !important; }
.d-block { display: block !important; }

View file

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy } from "@angular/core"; import { Component, OnInit, OnDestroy, QueryList, ViewChildren } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data"; import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker"; import { loginChecker } from "../../providers/login_checker";
@ -16,16 +16,28 @@ import {
} from "@generic-ui/ngx-grid"; } from "@generic-ui/ngx-grid";
import { NgxSuperSelectOptions } from "ngx-super-select"; import { NgxSuperSelectOptions } from "ngx-super-select";
import { _getFocusedElementPierceShadowDom } from "@angular/cdk/platform"; import { _getFocusedElementPierceShadowDom } from "@angular/cdk/platform";
import { AppToastComponent } from "../toast-simple/toast.component";
import { ToasterComponent } from "@coreui/angular";
@Component({ @Component({
templateUrl: "user_tasks.component.html", templateUrl: "user_tasks.component.html",
styleUrls: ["user_tasks.component.scss"]
}) })
export class UserTasksComponent implements OnInit { export class UserTasksComponent implements OnInit {
public uid: number; public uid: number;
public uname: string; public uname: string;
public ispro: boolean = false; public ispro: boolean = false;
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
toasterForm = {
autohide: true,
delay: 10000,
position: "fixed",
fade: true,
closeButton: true,
};
constructor( constructor(
private data_provider: dataProvider, private data_provider: dataProvider,
private router: Router, private router: Router,
@ -76,6 +88,55 @@ export class UserTasksComponent implements OnInit {
public firmwaretoinstallv6: string = "none"; public firmwaretoinstallv6: string = "none";
public updateBehavior: string = "keep"; public updateBehavior: string = "keep";
public firms_loaded: boolean = false; public firms_loaded: boolean = false;
public predefinedCrons: any[] = [
// High Frequency Monitoring
{ label: 'Every minute', value: '* * * * *', description: 'Critical monitoring - runs every minute' },
{ label: 'Every 2 minutes', value: '*/2 * * * *', description: 'High frequency monitoring' },
{ label: 'Every 5 minutes', value: '*/5 * * * *', description: 'Standard monitoring interval' },
{ label: 'Every 10 minutes', value: '*/10 * * * *', description: 'Regular monitoring checks' },
{ label: 'Every 15 minutes', value: '*/15 * * * *', description: 'Moderate monitoring frequency' },
{ label: 'Every 30 minutes', value: '*/30 * * * *', description: 'Low frequency monitoring' },
// Hourly Operations
{ label: 'Every hour', value: '0 * * * *', description: 'Hourly network checks' },
{ label: 'Every 2 hours', value: '0 */2 * * *', description: 'Bi-hourly operations' },
{ label: 'Every 4 hours', value: '0 */4 * * *', description: 'Quarterly daily checks' },
{ label: 'Every 6 hours', value: '0 */6 * * *', description: 'Four times daily' },
{ label: 'Every 8 hours', value: '0 */8 * * *', description: 'Three times daily' },
{ label: 'Every 12 hours', value: '0 */12 * * *', description: 'Twice daily operations' },
// Daily Maintenance
{ label: 'Daily at midnight', value: '0 0 * * *', description: 'Daily maintenance at 00:00' },
{ label: 'Daily at 1 AM', value: '0 1 * * *', description: 'Daily backup at 01:00' },
{ label: 'Daily at 2 AM', value: '0 2 * * *', description: 'Daily maintenance at 02:00' },
{ label: 'Daily at 3 AM', value: '0 3 * * *', description: 'Low traffic maintenance at 03:00' },
{ label: 'Daily at 6 AM', value: '0 6 * * *', description: 'Pre-business hours check' },
{ label: 'Daily at 6 PM', value: '0 18 * * *', description: 'End of business day backup' },
{ label: 'Daily at 10 PM', value: '0 22 * * *', description: 'Evening maintenance at 22:00' },
// Business Hours
{ label: 'Workdays at 8 AM', value: '0 8 * * 1-5', description: 'Start of business day - Mon to Fri' },
{ label: 'Workdays at 9 AM', value: '0 9 * * 1-5', description: 'Business hours start check' },
{ label: 'Workdays at 12 PM', value: '0 12 * * 1-5', description: 'Midday check - Mon to Fri' },
{ label: 'Workdays at 5 PM', value: '0 17 * * 1-5', description: 'End of business day - Mon to Fri' },
{ label: 'Workdays at 6 PM', value: '0 18 * * 1-5', description: 'After hours backup - Mon to Fri' },
// Weekly Operations
{ label: 'Weekly (Sunday midnight)', value: '0 0 * * 0', description: 'Weekly maintenance - Sunday 00:00' },
{ label: 'Weekly (Monday midnight)', value: '0 0 * * 1', description: 'Weekly start - Monday 00:00' },
{ label: 'Weekly (Friday 6 PM)', value: '0 18 * * 5', description: 'End of week backup - Friday 18:00' },
{ label: 'Weekly (Saturday 2 AM)', value: '0 2 * * 6', description: 'Weekend maintenance - Saturday 02:00' },
// Monthly Operations
{ label: 'Monthly (1st at midnight)', value: '0 0 1 * *', description: 'Monthly maintenance - 1st of month' },
{ label: 'Monthly (1st at 2 AM)', value: '0 2 1 * *', description: 'Monthly backup - 1st at 02:00' },
{ label: 'Monthly (15th at midnight)', value: '0 0 15 * *', description: 'Mid-month maintenance - 15th' },
{ label: 'Monthly (last day)', value: '0 0 28-31 * *', description: 'End of month operations' }
];
public showCronDropdown: boolean = false;
public cronSearch: string = '';
public selectedCronPreset: any = null;
public filteredCrons: any[] = [];
public sorting = { public sorting = {
enabled: true, enabled: true,
multiSorting: true, multiSorting: true,
@ -156,22 +217,43 @@ export class UserTasksComponent implements OnInit {
this.initGridTable(); this.initGridTable();
} }
show_toast(title: string, body: string, color: string) {
const { ...props } = { ...this.toasterForm, color, title, body };
const componentRef = this.viewChildren.first.addToast(
AppToastComponent,
props,
{}
);
componentRef.instance["closeButton"] = props.closeButton;
}
submit(action: string) { submit(action: string) {
var _self = this; var _self = this;
if (action == "add") { if (action == "add") {
this.data_provider this.data_provider
.Add_task(_self.SelectedTask, _self.SelectedTaskItems) .Add_task(_self.SelectedTask, _self.SelectedTaskItems)
.then((res) => { .then((res) => {
_self.initGridTable(); if (res && res.status === 'failed') {
_self.show_toast("Error", res.err, "danger");
} else {
_self.show_toast("Success", "Task created successfully", "success");
_self.initGridTable();
_self.EditTaskModalVisible = false;
}
}); });
} else { } else {
this.data_provider this.data_provider
.Edit_task(_self.SelectedTask, _self.SelectedTaskItems) .Edit_task(_self.SelectedTask, _self.SelectedTaskItems)
.then((res) => { .then((res) => {
_self.initGridTable(); if (res && res.status === 'failed') {
_self.show_toast("Error", res.err, "danger");
} else {
_self.show_toast("Success", "Task updated successfully", "success");
_self.initGridTable();
_self.EditTaskModalVisible = false;
}
}); });
} }
this.EditTaskModalVisible = false;
} }
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void { onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
@ -197,7 +279,7 @@ export class UserTasksComponent implements OnInit {
this.SelectedTask = { this.SelectedTask = {
id: 0, id: 0,
action: "add", action: "add",
taskcron: "* * * * *", cron: "0 2 * * *",
desc_cron: "", desc_cron: "",
description: "", description: "",
members: "", members: "",
@ -206,7 +288,9 @@ export class UserTasksComponent implements OnInit {
snippetid: "", snippetid: "",
task_type: "backup", task_type: "backup",
}; };
this.SelectedTask['data'] = { 'strategy': 'system', 'version_to_install': '', 'version_to_install_6': '' } this.SelectedTask['data'] = { 'strategy': 'system', 'version_to_install': '', 'version_to_install_6': '' };
this.cronSearch = '';
this.selectedCronPreset = null;
this.SelectedMembers = []; this.SelectedMembers = [];
this.SelectedTaskItems = []; this.SelectedTaskItems = [];
this.EditTaskModalVisible = true; this.EditTaskModalVisible = true;
@ -215,6 +299,12 @@ export class UserTasksComponent implements OnInit {
var _self = this; var _self = this;
this.SelectedTask = { ...item }; this.SelectedTask = { ...item };
// Initialize cron search and preset tracking
this.cronSearch = '';
const currentCron = this.SelectedTask['cron'];
this.selectedCronPreset = this.predefinedCrons.find(cron => cron.value === currentCron) || null;
if (this.SelectedTask['task_type'] == 'firmware' && 'data' in this.SelectedTask && this.SelectedTask['data']) { if (this.SelectedTask['task_type'] == 'firmware' && 'data' in this.SelectedTask && this.SelectedTask['data']) {
this.SelectedTask['data'] = JSON.parse(this.SelectedTask['data']); this.SelectedTask['data'] = JSON.parse(this.SelectedTask['data']);
if (this.SelectedTask['data']['strategy'] == 'defined') { if (this.SelectedTask['data']['strategy'] == 'defined') {
@ -243,7 +333,7 @@ export class UserTasksComponent implements OnInit {
} }
} }
_self.data_provider.get_snippets("", "", "", 0, 1000).then((res) => { _self.data_provider.get_snippets("", "", "", 0, 1000,false).then((res) => {
_self.Snippets = res.map((x: any) => { _self.Snippets = res.map((x: any) => {
return { id: x.id, name: x.name }; return { id: x.id, name: x.name };
}); });
@ -315,7 +405,7 @@ export class UserTasksComponent implements OnInit {
onSnippetsValueChanged(v: any) { onSnippetsValueChanged(v: any) {
var _self = this; var _self = this;
if (v == "" || v.length < 3) return; if (v == "" || v.length < 3) return;
_self.data_provider.get_snippets(v, "", "", 0, 1000).then((res) => { _self.data_provider.get_snippets(v, "", "", 0, 1000,false).then((res) => {
_self.Snippets = res.map((x: any) => { _self.Snippets = res.map((x: any) => {
return { id: String(x.id), name: x.name }; return { id: String(x.id), name: x.name };
}); });
@ -333,7 +423,12 @@ export class UserTasksComponent implements OnInit {
} else { } else {
var _self = this; var _self = this;
this.data_provider.Delete_task(_self.SelectedTask["id"]).then((res) => { this.data_provider.Delete_task(_self.SelectedTask["id"]).then((res) => {
_self.initGridTable(); if (res && res.status === 'failed') {
_self.show_toast("Error", res.err, "danger");
} else {
_self.show_toast("Success", "Task deleted successfully", "success");
_self.initGridTable();
}
_self.DeleteConfirmModalVisible = false; _self.DeleteConfirmModalVisible = false;
}); });
} }
@ -363,4 +458,79 @@ export class UserTasksComponent implements OnInit {
_self.loading = false; _self.loading = false;
}); });
} }
selectCron(cron: any): void {
this.SelectedTask['cron'] = cron.value;
this.selectedCronPreset = cron;
this.cronSearch = '';
this.showCronDropdown = false;
}
filterCrons(event: any): void {
const searchTerm = event.target.value.toLowerCase();
this.cronSearch = searchTerm;
this.selectedCronPreset = null;
if (searchTerm.length > 0) {
this.filteredCrons = this.predefinedCrons.filter(cron =>
cron.label.toLowerCase().includes(searchTerm) ||
cron.description.toLowerCase().includes(searchTerm) ||
cron.value.includes(searchTerm)
);
} else {
this.filteredCrons = this.predefinedCrons;
}
}
hideCronDropdown(): void {
setTimeout(() => {
this.showCronDropdown = false;
}, 200);
}
onCronInputFocus(): void {
this.filteredCrons = this.predefinedCrons;
this.showCronDropdown = true;
// If current cron matches a preset, highlight it
const currentCron = this.SelectedTask['cron'];
this.selectedCronPreset = this.predefinedCrons.find(cron => cron.value === currentCron) || null;
}
onCronInputChange(event: any): void {
this.SelectedTask['cron'] = event.target.value;
this.selectedCronPreset = null;
// Check if the entered value matches any preset
const enteredValue = event.target.value;
const matchingPreset = this.predefinedCrons.find(cron => cron.value === enteredValue);
if (matchingPreset) {
this.selectedCronPreset = matchingPreset;
}
}
getCronDescription(): string {
if (this.selectedCronPreset) {
return this.selectedCronPreset.description;
}
const currentCron = this.SelectedTask['cron'];
const matchingPreset = this.predefinedCrons.find(cron => cron.value === currentCron);
return matchingPreset ? matchingPreset.description : 'Custom cron expression';
}
onTaskTypeChange(): void {
if (this.SelectedTask['task_type'] === 'snippet') {
this.loadSnippets();
}
}
loadSnippets(): void {
var _self = this;
_self.data_provider.get_snippets("", "", "", 0, 10).then((res) => {
_self.Snippets = res.map((x: any) => {
return { id: x.id, name: x.name };
});
});
}
} }

View file

@ -9,6 +9,9 @@ import {
GridModule, GridModule,
ModalModule, ModalModule,
ButtonGroupModule, ButtonGroupModule,
BadgeModule,
AlertModule,
ToastModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { UserTasksRoutingModule } from "./user_tasks-routing.module"; import { UserTasksRoutingModule } from "./user_tasks-routing.module";
import { UserTasksComponent } from "./user_tasks.component"; import { UserTasksComponent } from "./user_tasks.component";
@ -25,6 +28,9 @@ import { NgxSuperSelectModule} from "ngx-super-select";
FormModule, FormModule,
ButtonModule, ButtonModule,
ButtonGroupModule, ButtonGroupModule,
BadgeModule,
AlertModule,
ToastModule,
GuiGridModule, GuiGridModule,
ModalModule, ModalModule,
ReactiveFormsModule, ReactiveFormsModule,

View file

@ -1,360 +1,398 @@
<c-row> <c-card class="mb-4" style="margin-bottom: 0 !important;">
<c-col xs style="padding-right: 0;"> <c-card-header style="padding: 0;">
<div class="nav nav-underline" style="background: #fff;"> <c-row class="align-items-center">
<div calss="nav-item"> <c-col xs="12" md="6">
<a [active]="true" class="nav-link" [cTabContent]="tabContent" (click)="activetab=0" [tabPaneIdx]="0">Settings</a> <div class="nav nav-underline bg-transparent">
</div> <div class="nav-item">
<div calss="nav-item"> <a [active]="activetab==0" class="nav-link" [cTabContent]="tabContent" (click)="activetab=0" [tabPaneIdx]="0">
<a [cTabContent]="tabContent" (click)="get_passwords();activetab=1" class="nav-link" [routerLink] [tabPaneIdx]="1">Passwords</a> <i class="fas fa-cog me-1"></i>Settings
</div> </a>
</div> </div>
</c-col> <div class="nav-item">
<c-col style="padding-left: 0;"> <a [active]="activetab==1" class="nav-link" [cTabContent]="tabContent" (click)="get_passwords();activetab=1" [tabPaneIdx]="1">
<div class="nav nav-underline" style="background: #fff;padding: 3px;flex-direction: row-reverse;"> <i class="fas fa-key me-1"></i>Passwords
<button *ngIf="activetab==0" cButton size="sm" shape="rounded-0" class="mx-2" (click)="runConfirmModalVisible=!runConfirmModalVisible" color="danger">Execute Now</button> </a>
<button *ngIf="activetab==1" cButton size="sm" shape="rounded-0" class="mx-2" (click)="toggleCollapse()" color="info">filters</button> </div>
</div> </div>
</c-col> </c-col>
</c-row> <c-col xs="12" md="6" class="text-end mt-2 mt-md-0">
<c-tab-content style="padding: 0!important;" #tabContent="cTabContent"> <button *ngIf="activetab==0" cButton size="sm" class="me-2" (click)="runConfirmModalVisible=!runConfirmModalVisible" color="danger">
<i class="fas fa-play me-1"></i>Execute Now
</button>
<button *ngIf="activetab==1" cButton size="sm" (click)="toggleCollapse()" color="info">
<i class="fas fa-filter me-1"></i>Filters
</button>
</c-col>
</c-row>
</c-card-header>
</c-card>
<c-tab-content #tabContent="cTabContent">
<c-tab-pane> <c-tab-pane>
<c-row> <c-card class="mb-4" *ngIf="settings" style="border: none;">
<c-col xs> <c-card-body>
<c-card class="mb-4" style="border-radius: 0;" *ngIf="settings"> <!-- Settings Tab Header -->
<c-card-body> <div class="tab-info-header mb-4">
<c-row> <div class="d-flex align-items-center">
<c-col md="6"> <i class="fas fa-cog text-primary me-2 fs-4"></i>
<c-input-group class="mb-3"> <div>
<label cInputGroupText for="inputGroupSelect01"> <h5 class="mb-1">Password Vault Settings</h5>
Status <p class="text-muted mb-0 small">Configure automated password management for your device groups. Set schedules, define password policies, and manage affected devices.</p>
</label> </div>
<select cSelect id="inputGroupSelect01" [(ngModel)]="settings['enable']"> </div>
<option>Choose...</option> </div>
<option value="enable">Enable</option>
<option value="disable">Disable</option> <!-- Password Vault Configuration -->
</select> <div class="mb-4 pb-4 border-bottom">
</c-input-group> <h6 class="mb-3"><i class="fas fa-shield-alt me-2"></i>Password Vault Configuration</h6>
</c-col> <c-row class="g-3">
<c-col md="6"> <c-col xs="12" md="6" lg="3">
<c-input-group class="mb-3"> <label class="form-label small">Status</label>
<label cInputGroupText for="inputGroupSelect01"> <select cSelect size="sm" [(ngModel)]="settings['enable']" class="form-select-sm">
Strategy <option>Choose...</option>
</label> <option value="enable">Enable</option>
<select cSelect id="inputGroupSelect01" [(ngModel)]="settings['strategy']"> <option value="disable">Disable</option>
<option>Choose...</option> </select>
<option value="all">All local</option> </c-col>
<option value="mikrowizard">Defined in MikroWizard</option> <c-col xs="12" md="6" lg="3">
</select> <label class="form-label small">Strategy <i class="fas fa-info-circle text-info ms-1" [cTooltip]="strategyTooltipTemplate" cTooltipPlacement="top"></i></label>
</c-input-group> <select cSelect size="sm" [(ngModel)]="settings['strategy']" class="form-select-sm">
</c-col> <option>Choose...</option>
<c-col md="6"> <option value="all">All Local Users</option>
<c-input-group class="mb-3"> <option value="mikrowizard">MikroWizard Users Only</option>
<label cInputGroupText for="inputGroupSelect01"> </select>
Interval </c-col>
</label> <c-col xs="12" md="6" lg="3">
<select cSelect id="inputGroupSelect01" [(ngModel)]="settings['interval']"> <label class="form-label small">Interval <i class="fas fa-info-circle text-info ms-1" [cTooltip]="intervalTooltipTemplate" cTooltipPlacement="top"></i></label>
<option>Choose...</option> <select cSelect size="sm" [(ngModel)]="settings['interval']" class="form-select-sm">
<option value="daily">Daily</option> <option>Choose...</option>
<option value="weekly">Weekly</option> <option value="daily">Daily</option>
<option value="monthly">Monthly</option> <option value="weekly">Weekly</option>
<option value="yearly">Yearly</option> <option value="monthly">Monthly</option>
<option value="manual">Manual</option> <option value="yearly">Yearly</option>
<option value="custom">Custom</option> <option value="manual">Manual</option>
</select> <option value="custom">Custom</option>
</c-input-group> </select>
<c-input-group *ngIf="settings['interval']=='custom'" class="mb-3"> </c-col>
<label cInputGroupText for="inputGroupSelect01"> <c-col xs="12" md="6" lg="3">
Custom Cron <label class="form-label small">Password Type <i class="fas fa-info-circle text-info ms-1" [cTooltip]="passwordTypeTooltipTemplate" cTooltipPlacement="top"></i></label>
</label> <select cSelect size="sm" [(ngModel)]="settings['password_type']" class="form-select-sm">
<input cFormControl id="floatingInput" style="border-radius: 0;" placeholder="Cron" [(ngModel)]="settings['cron']" /> <option>Choose...</option>
</c-input-group> <option value="random">Random</option>
</c-col> <option value="defined">Pre-defined</option>
<c-col md="6"> </select>
<c-input-group class="mb-3"> </c-col>
<label cInputGroupText for="inputGroupSelect01"> <c-col xs="12" *ngIf="settings['interval']=='custom'">
Password <label class="form-label small">Custom Cron Expression</label>
</label> <input cFormControl size="sm" placeholder="0 0 * * *" [(ngModel)]="settings['cron']" />
<select cSelect id="inputGroupSelect01" [(ngModel)]="settings['password_type']"> </c-col>
<option>Choose...</option> </c-row>
<option value="random">Random</option> </div>
<option value="defined">Pre-defined</option> <!-- User Exceptions -->
</select> <div class="mb-4 pb-4 border-bottom" *ngIf="settings && settings['strategy']=='all'">
</c-input-group> <c-row class="align-items-center mb-3">
</c-col> <c-col>
<c-col md="12" *ngIf="settings['strategy']=='all'"> <h6 class="mb-0"><i class="fas fa-user-shield me-2"></i>User Exceptions</h6>
<hr width="70%" style="margin: 10px auto;border-color: #304193;border-width: 2px;" /> <small class="text-muted">Users excluded from password changes</small>
<c-row class="gui-header" style="background: #f9fafb;padding: 10px 0px;margin: 0 auto;height: unset;border: 1px solid #e8e8e8;border-bottom: unset;"> </c-col>
<c-col md="2" style="display: flex;align-items: center;"> <c-col xs="auto">
<h6>User Exceptions</h6> <div class="input-group input-group-sm">
</c-col> <input cFormControl size="sm" placeholder="Username" [(ngModel)]="new_exception" />
<c-col style="display: flex;flex-direction: row-reverse;" md="10"> <button cButton color="primary" size="sm" (click)="add_exception()">
<table> <i class="fas fa-plus"></i>
<td> </button>
<div> </div>
<input cFormControl style="border-radius: 0;" id="floatingInput" placeholder="Username Exception" </c-col>
[(ngModel)]="new_exception" /> </c-row>
</div> <div *ngIf="settings['exceptions']?.length > 0">
</td> <gui-grid [autoResizeWidth]="true" [source]="settings['exceptions']" [columnMenu]="columnMenu" [paging]="paging" [sorting]="sorting">
<td style="vertical-align: top;"> <gui-grid-column header="Username" field="name">
<button cButton color="dark" shape="rounded-0" (click)="add_exception()">Add Username</button> <ng-template let-value="item" let-item="item" let-index="index">
</td> <i class="fas fa-user me-2 text-muted"></i>{{value}}
</table>
</c-col>
</c-row>
<c-input-group class="mb-3">
<gui-grid [autoResizeWidth]="true" [source]="settings['exceptions']" [columnMenu]="columnMenu" [paging]="paging"
[sorting]="sorting" [autoResizeWidth]=true>
<gui-grid-column header="UserName" field="name">
<ng-template let-value="item" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="70" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button (click)="remove_exception(item)" class=" mx-1" cButton color="danger" size="sm"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-input-group>
</c-col>
<c-col md="12" *ngIf="settings['password_type']=='defined'">
<hr width="70%" style="margin: 10px auto;border-color: #304193;border-width: 2px;"/>
<c-row class="gui-header" style="background: #f9fafb;padding: 10px 0px;margin: 0 auto;height: unset;border: 1px solid #e8e8e8;border-bottom: unset;">
<c-col md="2" style="display: flex;align-items: center;">
<h6>Password list</h6>
</c-col>
<c-col style="display: flex;flex-direction: row-reverse;" md="10">
<table>
<td>
<div>
<input cFormControl id="floatingInput" style="border-radius: 0;" placeholder="Password" [(ngModel)]="new_password" />
</div>
</td>
<td style="vertical-align: top;">
<button cButton color="dark" shape="rounded-0" (click)="add_password()">Add Password</button>
</td>
</table>
</c-col>
</c-row>
<gui-grid [autoResizeWidth]="true" [source]="settings['passwords']" [columnMenu]="columnMenu" [sorting]="sorting"
[paging]="paging" [autoResizeWidth]=true>
<gui-grid-column header="Password" field="name">
<ng-template let-value="item" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="70" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button class=" mx-1" cButton color="danger" size="sm"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-col>
</c-row>
</c-card-body>
<c-card-footer style="display: flex;flex-direction: row-reverse;">
<button cButton color="info" shape="rounded-0" (click)="save_settings()" style="color: #fff;">Save Settings</button>
</c-card-footer>
</c-card>
</c-col>
</c-row>
<c-row>
<c-col xs>
<c-card class="mb-4" style="border-radius: 0;" *ngIf="settings">
<c-card-body>
<h6>Efected Groups</h6>
<gui-grid [autoResizeWidth]="true" [source]="Members" [columnMenu]="columnMenu" [sorting]="sorting"
[paging]="paging" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="70" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button (click)="delete_group(item.id)" class=" mx-1" cButton color="danger" size="sm"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-card-body>
<c-card-footer style="display: flex;flex-direction: row-reverse;">
<button cButton color="info" shape="rounded-0" (click)="save_settings()" style="color: #fff;">Save Settings</button>
<button cButton color="primary" class="mx-1" (click)="show_new_member_form()">+ Add new Members</button>
</c-card-footer>
</c-card>
</c-col>
</c-row>
<c-row>
<c-col xs>
<c-card class="mb-4" style="border-radius: 0;" *ngIf="settings">
<c-card-body *ngIf="vault_history">
<h6>Reports</h6>
<gui-grid [autoResizeWidth]="true" [source]="vault_history" [columnMenu]="columnMenu" [sorting]="sorting"
[paging]="paging" [autoResizeWidth]=true>
<gui-grid-column header="Start Time" field="name">
<ng-template let-value="item.started" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="End Time" field="name">
<ng-template let-value="item.ended" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Logs" field="mac" align="center">
<ng-template let-value="item['result']" let-item="item" let-index="index">
<button (click)="exportToCsv(value)" color="primary" cButton>download</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-card-body>
</c-card>
</c-col>
</c-row>
</c-tab-pane>
<c-tab-pane>
<c-row>
<div [visible]="filters_visible" cCollapse>
<c-col xs [lg]="12" class="example-form" style="background: #fff;padding: 0 10px;">
<mat-form-field *ngIf="ispro">
<mat-label>Username</mat-label>
<input (ngModelChange)="reinitgrid('username',$event)" [(ngModel)]="filters['username']" matInput>
</mat-form-field>
<mat-form-field *ngIf="ispro">
<mat-label>Device IP</mat-label>
<input (ngModelChange)="reinitgrid('dev_ip',$event)" [(ngModel)]="filters['dev_ip']" matInput>
</mat-form-field>
<mat-form-field *ngIf="ispro">
<mat-label>Device Name</mat-label>
<input (ngModelChange)="reinitgrid('dev_name',$event)" [(ngModel)]="filters['dev_name']" matInput>
</mat-form-field>
</c-col>
</div>
</c-row>
<c-row>
<c-col xs>
<c-card class="mb-4">
<c-card-body *ngIf="passwords">
<gui-grid [autoResizeWidth]="true" [source]="passwords" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true>
<gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Device IP" field="devip">
<ng-template let-value="item.devip" let-item="item" let-index="index">
{{value}}
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
<gui-grid-column header="UserName" field="username"> <gui-grid-column header="Actions" width="80" field="action" align="center">
<ng-template let-value="item.username" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Last Changed" field="desc_cron">
<ng-template let-value="item.changed" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index"> <ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton *ngIf="ispro" (click)="reveal_password(item.devid,item.username)" color="info" variant="outline"> <button (click)="remove_exception(item)" cButton color="danger" size="sm" variant="outline">
<i class="fa-solid fa-eye"></i> <i class="fas fa-trash"></i>
</button> </button>
</ng-template> </ng-template>
</gui-grid-column> </gui-grid-column>
</gui-grid> </gui-grid>
</c-card-body> </div>
</c-card> </div>
</c-col> <!-- Pre-defined Passwords -->
</c-row> <div class="mb-4 pb-4 border-bottom" *ngIf="settings && settings['password_type']=='defined'">
<c-row class="align-items-center mb-3">
<c-col>
<h6 class="mb-0"><i class="fas fa-key me-2"></i>Password List</h6>
<small class="text-muted">Pre-defined passwords for rotation</small>
</c-col>
<c-col xs="auto">
<div class="input-group input-group-sm">
<input cFormControl size="sm" type="password" placeholder="Password" [(ngModel)]="new_password" />
<button cButton color="primary" size="sm" (click)="add_password()">
<i class="fas fa-plus"></i>
</button>
</div>
</c-col>
</c-row>
<div *ngIf="settings['passwords']?.length > 0">
<gui-grid [autoResizeWidth]="true" [source]="settings['passwords']" [columnMenu]="columnMenu" [sorting]="sorting" [paging]="paging">
<gui-grid-column header="Password" field="name">
<ng-template let-value="item" let-item="item" let-index="index">
<i class="fas fa-lock me-2 text-muted"></i>••••••••
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="80" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_password(item)">
<i class="fas fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</div>
<!-- Affected Groups -->
<div class="mb-4 pb-4 border-bottom">
<c-row class="align-items-center mb-3">
<c-col>
<h6 class="mb-0"><i class="fas fa-network-wired me-2"></i>Affected Device Groups</h6>
<small class="text-muted">Device groups where password changes will be applied</small>
</c-col>
<c-col xs="auto">
<button cButton color="primary" size="sm" (click)="show_new_member_form()">
<i class="fas fa-plus me-1"></i>Add Groups
</button>
</c-col>
</c-row>
<div *ngIf="Members?.length > 0" class="mb-3">
<gui-grid [autoResizeWidth]="true" [source]="Members" [columnMenu]="columnMenu" [sorting]="sorting" [paging]="paging">
<gui-grid-column header="Group Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<i class="fas fa-server me-2 text-primary"></i>{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="80" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button (click)="delete_group(item.id)" cButton color="danger" size="sm" variant="outline">
<i class="fas fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
<div class="text-end">
<button cButton color="success" (click)="save_settings()">
<i class="fas fa-save me-1"></i>Save Configuration
</button>
</div>
</div>
<!-- Execution History -->
<div *ngIf="vault_history">
<h6 class="mb-3"><i class="fas fa-history me-2"></i>Execution Reports</h6>
<gui-grid [autoResizeWidth]="true" [source]="vault_history" [columnMenu]="columnMenu" [sorting]="sorting" [paging]="paging">
<gui-grid-column header="Start Time" field="started">
<ng-template let-value="item.started" let-item="item" let-index="index">
<small>{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="End Time" field="ended">
<ng-template let-value="item.ended" let-item="item" let-index="index">
<small>{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Results" field="result" align="center" width="100">
<ng-template let-value="item['result']" let-item="item" let-index="index">
<button (click)="exportToCsv(value)" color="primary" size="sm" cButton variant="outline">
<i class="fas fa-download"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-tab-pane>
<c-tab-pane>
<c-card class="mb-4">
<c-card-body>
<!-- Passwords Tab Header -->
<div class="tab-info-header mb-4">
<div class="d-flex align-items-center">
<i class="fas fa-key text-warning me-2 fs-4"></i>
<div>
<h5 class="mb-1">Stored Passwords</h5>
<p class="text-muted mb-0 small">View and manage passwords stored by the vault system. Use filters to find specific devices or users.</p>
</div>
</div>
</div>
<!-- Filters -->
<div class="mb-4 pb-4 border-bottom" [visible]="filters_visible" cCollapse>
<h6 class="mb-3"><i class="fas fa-filter me-2"></i>Filters</h6>
<c-row class="g-3">
<c-col xs="12" md="4" *ngIf="ispro">
<mat-form-field appearance="outline" class="w-100">
<mat-label>Username</mat-label>
<input (ngModelChange)="reinitgrid('username',$event)" [(ngModel)]="filters['username']" matInput>
</mat-form-field>
</c-col>
<c-col xs="12" md="4" *ngIf="ispro">
<mat-form-field appearance="outline" class="w-100">
<mat-label>Device IP</mat-label>
<input (ngModelChange)="reinitgrid('dev_ip',$event)" [(ngModel)]="filters['dev_ip']" matInput>
</mat-form-field>
</c-col>
<c-col xs="12" md="4" *ngIf="ispro">
<mat-form-field appearance="outline" class="w-100">
<mat-label>Device Name</mat-label>
<input (ngModelChange)="reinitgrid('dev_name',$event)" [(ngModel)]="filters['dev_name']" matInput>
</mat-form-field>
</c-col>
</c-row>
</div>
<!-- Stored Passwords -->
<div *ngIf="passwords">
<h6 class="mb-3"><i class="fas fa-database me-2"></i>Stored Passwords</h6>
<gui-grid [autoResizeWidth]="true" [source]="passwords" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel">
<gui-grid-column header="Device" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fas fa-router me-2 text-primary"></i>
<div>
<div class="fw-semibold">{{value}}</div>
<small class="text-muted">{{item.devip}}</small>
</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Username" field="username">
<ng-template let-value="item.username" let-item="item" let-index="index">
<i class="fas fa-user me-2 text-muted"></i>{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Last Changed" field="changed">
<ng-template let-value="item.changed" let-item="item" let-index="index">
<small>{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton *ngIf="ispro" (click)="reveal_password(item.devid,item.username)" color="info" size="sm" variant="outline">
<i class="fas fa-eye"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-tab-pane> </c-tab-pane>
</c-tab-content> </c-tab-content>
<!-- Password Reveal Modal -->
<c-modal #PasswordModal backdrop="static" [(visible)]="PasswordModalVisible" id="PasswordModal"> <c-modal #PasswordModal backdrop="static" [(visible)]="PasswordModalVisible" id="PasswordModal">
<c-modal-header> <c-modal-header>
<h6 cModalTitle>Password</h6> <h6 cModalTitle><i class="fas fa-eye me-2"></i>Password Reveal</h6>
<button [cModalToggle]="PasswordModal.id" cButtonClose></button> <button [cModalToggle]="PasswordModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body>
<p> <div class="alert alert-warning">
<c-input-group class="mb-3"> <i class="fas fa-exclamation-triangle me-2"></i>
<label cInputGroupText for="inputGroupSelect01"> <strong>Security Notice:</strong> Your attempt to reveal this password is logged in the system.
Password </div>
</label> <div class="input-group">
<input [value]="password" cFormControl disabled="true"/> <span class="input-group-text"><i class="fas fa-key"></i></span>
</c-input-group> <input [value]="password" cFormControl disabled="true" class="form-control"/>
</p> <button cButton color="secondary" variant="outline" (click)="copyToClipboard(password)">
<code> <i class="fas fa-copy"></i>
Your attempt to reveal password is logged in system! </button>
</code> </div>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer>
<button [cModalToggle]="PasswordModal.id" cButton color="info"> <button [cModalToggle]="PasswordModal.id" cButton color="secondary">Close</button>
Close
</button>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<!-- Execute Confirmation Modal -->
<c-modal #runConfirmModal backdrop="static" [(visible)]="runConfirmModalVisible" id="runConfirmModal"> <c-modal #runConfirmModal backdrop="static" [(visible)]="runConfirmModalVisible" id="runConfirmModal">
<c-modal-header> <c-modal-header>
<h6 cModalTitle>Confirm RUN {{ SelectedTask['name'] }}</h6> <h6 cModalTitle><i class="fas fa-exclamation-triangle me-2 text-warning"></i>Confirm Execution</h6>
<button [cModalToggle]="runConfirmModal.id" cButtonClose></button> <button [cModalToggle]="runConfirmModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body>
Are you sure that You want to run Vault Password Job ? <div class="alert alert-warning">
<br /> <strong>Warning:</strong> This will execute the password vault job and change passwords on all configured device groups.
</div>
<p>Are you sure you want to proceed with the password vault execution?</p>
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer>
<button cButton color="danger" (click)="exec_vault()"> <button cButton color="danger" (click)="exec_vault()">
Yes,Run! <i class="fas fa-play me-1"></i>Yes, Execute
</button>
<button [cModalToggle]="runConfirmModal.id" cButton color="info">
Close
</button> </button>
<button [cModalToggle]="runConfirmModal.id" cButton color="secondary">Cancel</button>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<!-- Add Device Groups Modal -->
<c-modal #NewMemberModal backdrop="static" size="lg" [(visible)]="NewMemberModalVisible" id="NewMemberModal"> <c-modal #NewMemberModal backdrop="static" size="lg" [(visible)]="NewMemberModalVisible" id="NewMemberModal">
<c-modal-header> <c-modal-header>
<h5 cModalTitle>Editing Group </h5> <h6 cModalTitle><i class="fas fa-plus me-2"></i>Add Device Groups</h6>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button> <button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body>
<c-input-group class="mb-3"> <p class="text-muted mb-3">Select device groups to include in the password vault task:</p>
<h5>Group Members :</h5> <gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching" [source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [paging]="paging">
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true <gui-grid-column header="Group Name" field="name">
[paging]="paging"> <ng-template let-value="item.name" let-item="item" let-index="index">
<gui-grid-column header="Member Name" field="name"> <i class="fas fa-server me-2 text-primary"></i>{{value}}
<ng-template let-value="item.name" let-item="item" let-index="index"> </ng-template>
&nbsp; {{value}} </ng-template> </gui-grid-column>
</gui-grid-column> <gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="IP Address" field="ip">
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="IP Address" field="ip"> <ng-template let-value="item.ip" let-item="item" let-index="index">
<ng-template let-value="item.ip" let-item="item" let-index="index"> {{value}}
{{value}} </ng-template>
</ng-template> </gui-grid-column>
</gui-grid-column> <gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac">
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac"> <ng-template let-value="item.mac" let-item="item" let-index="index">
<ng-template let-value="item.mac" let-item="item" let-index="index"> {{value}}
{{value}} </ng-template>
</ng-template> </gui-grid-column>
</gui-grid-column> </gui-grid>
</gui-grid>
<br />
</c-input-group>
<hr />
</c-modal-body> </c-modal-body>
<c-modal-footer> <c-modal-footer>
<button *ngIf="NewMemberRows.length!= 0" (click)="add_new_members()" cButton color="primary">Add {{ <button *ngIf="NewMemberRows.length != 0" (click)="add_new_members()" cButton color="primary">
NewMemberRows.length }}</button> <i class="fas fa-plus me-1"></i>Add {{NewMemberRows.length}} Group(s)
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary">
Close
</button> </button>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary">Cancel</button>
</c-modal-footer> </c-modal-footer>
</c-modal> </c-modal>
<!-- Tooltip Templates -->
<ng-template #strategyTooltipTemplate>
<strong>All Local Users:</strong><br>
Changes passwords for all router users except exceptions.<br><br>
<strong>MikroWizard Users Only:</strong><br>
Changes only passwords for users defined in MikroWizard system.
</ng-template>
<ng-template #intervalTooltipTemplate>
<strong>Frequency:</strong><br>
How often passwords should be changed automatically.<br><br>
<strong>Manual:</strong><br>
Only when executed manually.
</ng-template>
<ng-template #passwordTypeTooltipTemplate>
<strong>Random:</strong><br>
System generates random passwords.<br><br>
<strong>Pre-defined:</strong><br>
Use passwords from your custom list.
</ng-template>
<c-toaster position="fixed" placement="top-end"></c-toaster> <c-toaster position="fixed" placement="top-end"></c-toaster>

View file

@ -435,6 +435,14 @@ export class VaultComponent implements OnInit {
copyToClipboard(text: string) {
navigator.clipboard.writeText(text).then(() => {
this.show_toast('Success', 'Password copied to clipboard', 'success');
}).catch(() => {
this.show_toast('Error', 'Failed to copy password', 'danger');
});
}
logger(item: any) { logger(item: any) {
console.dir(item); console.dir(item);
} }

View file

@ -12,6 +12,7 @@ import {
TabsModule, TabsModule,
ToastModule, ToastModule,
CollapseModule, CollapseModule,
TooltipModule,
} from "@coreui/angular"; } from "@coreui/angular";
import { VaultRoutingModule } from "./vault-routing.module"; import { VaultRoutingModule } from "./vault-routing.module";
import { VaultComponent } from "./vault.component"; import { VaultComponent } from "./vault.component";
@ -36,7 +37,8 @@ import { MatFormFieldModule } from "@angular/material/form-field";
ToastModule, ToastModule,
MatInputModule, MatInputModule,
MatFormFieldModule, MatFormFieldModule,
CollapseModule CollapseModule,
TooltipModule
], ],
declarations: [VaultComponent], declarations: [VaultComponent],
}) })

View file

@ -1,29 +1,278 @@
// Compact Navigation Styles
.nav-underline { .nav-underline {
border-bottom: 2px solid var(--cui-nav-underline-border-color, #c4c9d0) border-bottom: 2px solid var(--cui-nav-underline-border-color, #c4c9d0);
.nav-item {
margin-bottom: -2px;
cursor: pointer;
}
.nav-link {
color: var(--cui-nav-underline-link-color, #768192);
border-style: none none solid!important;
border-width: 2px;
position: relative;
bottom: -1px;
cursor: pointer;
font-size: 0.9rem;
padding: 0.5rem 1rem;
&:hover, &:focus {
border-color: var(--cui-nav-underline-link-active-border-color, #321fdb);
}
&.active, .show > & {
color: var(--cui-nav-underline-link-active-color, #321fdb);
background: transparent;
border-color: var(--cui-nav-underline-link-active-border-color, #321fdb);
}
}
} }
.nav-underline .nav-item { // Compact Form Styles
margin-bottom: -2px; .form-label.small {
cursor: pointer; font-size: 0.8rem;
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
} }
.nav-underline .nav-link { .form-select-sm {
color: var(--cui-nav-underline-link-color, #768192); font-size: 0.85rem;
border-style: none none solid!important; padding: 0.25rem 0.5rem;
border-width:2px;
position:relative;
bottom:-1px;
cursor: pointer;
} }
.nav-underline .nav-link:hover,.nav-underline .nav-link:focus { // Card Header Improvements
border-color: var(--cui-nav-underline-link-active-border-color, #321fdb) .c-card-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 0.75rem 1rem;
h6 {
font-size: 0.9rem;
font-weight: 600;
color: #495057;
i {
color: #6c757d;
}
}
.text-muted {
font-size: 0.75rem;
}
} }
.nav-underline .nav-link.active,.nav-underline .show>.nav-link { // Grid Improvements
color: var(--cui-nav-underline-link-active-color, #321fdb); gui-grid {
background: transparent; .gui-grid {
border-color: var(--cui-nav-underline-link-active-border-color, #321fdb) font-size: 0.85rem;
}
} }
// Input Group Compact
.input-group-sm {
.form-control, .input-group-text {
font-size: 0.8rem;
padding: 0.25rem 0.5rem;
}
}
// Alert Improvements
.alert {
font-size: 0.85rem;
padding: 0.5rem 0.75rem;
i {
margin-right: 0.5rem;
}
}
// Button Improvements
.btn {
font-size: 0.85rem;
&.btn-sm {
font-size: 0.8rem;
padding: 0.25rem 0.5rem;
}
i {
font-size: 0.8rem;
}
}
// Modal Improvements
.c-modal-header {
padding: 0.75rem 1rem;
h6 {
font-size: 0.95rem;
font-weight: 600;
}
}
.c-modal-body {
padding: 1rem;
font-size: 0.9rem;
}
.c-modal-footer {
padding: 0.75rem 1rem;
}
// Responsive Design
@media (max-width: 768px) {
.c-card-header {
.nav-underline {
margin-bottom: 0.5rem;
}
.text-end {
text-align: left !important;
}
}
.input-group-sm {
flex-direction: column;
.form-control {
margin-bottom: 0.25rem;
}
.btn {
width: 100%;
}
}
.c-modal-dialog {
margin: 0.5rem;
}
.c-card-footer {
.btn {
width: 100%;
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
}
}
}
@media (max-width: 576px) {
.nav-underline .nav-link {
font-size: 0.8rem;
padding: 0.4rem 0.8rem;
}
.c-card-header h6 {
font-size: 0.85rem;
}
.form-label.small {
font-size: 0.75rem;
}
.btn {
font-size: 0.8rem;
&.btn-sm {
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
}
}
}
// Material Form Field Compact
::ng-deep .mat-form-field {
.mat-form-field-wrapper {
padding-bottom: 0.5em;
}
.mat-form-field-infix {
padding: 0.5em 0;
}
.mat-form-field-label {
font-size: 0.85rem;
}
input {
font-size: 0.85rem;
}
}
// Grid Cell Improvements
::ng-deep gui-grid {
.gui-grid-cell {
padding: 0.5rem;
font-size: 0.85rem;
}
.gui-grid-header-cell {
font-size: 0.8rem;
font-weight: 600;
padding: 0.5rem;
}
}
// Tab Info Headers
.tab-info-header {
padding: 1rem 0;
h5 {
color: #495057;
font-weight: 600;
font-size: 1.1rem;
}
.fs-4 {
font-size: 1.5rem !important;
}
}
// Utility Classes
.fw-semibold {
font-weight: 600;
}
.text-xs {
font-size: 0.75rem;
}
.compact-spacing {
.c-row {
margin-bottom: 0.5rem;
}
.c-col {
padding: 0.25rem;
}
}
// Responsive tab headers
@media (max-width: 576px) {
.tab-info-header {
padding: 0.75rem 0;
h5 {
font-size: 1rem;
}
.fs-4 {
font-size: 1.25rem !important;
}
.d-flex {
flex-direction: column;
text-align: center;
}
.me-2 {
margin-right: 0 !important;
margin-bottom: 0.5rem;
}
}
}

View file

@ -0,0 +1,6 @@
# Network Icons SVG
A collection of SVG format network icons. These can be used freely and don't adhere to any vendor at all, meaning so that you can use the router icon to represent any brand router in your diagram.
Being SVG a vectorial graphics format, no resizing, format change or conversion operation will degrade icon quality.
This icons were originally shared by BobTheButcher.

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4965"
viewBox="0 0 195.74482 196.75038"
height="55.527328mm"
width="55.243538mm">
<defs
id="defs4967" />
<metadata
id="metadata4970">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-250.69902,-416.84416)"
id="layer1">
<g
transform="translate(-562.33748,292.79637)"
id="g4683">
<rect
style="fill:#a1a2a3;fill-opacity:1;stroke:#616161;stroke-width:5.52306032;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4349-8-9"
width="190.22176"
height="191.22731"
x="815.79803"
y="126.80932" />
<path
style="display:inline;fill:#5400ff;fill-opacity:1;fill-rule:evenodd;stroke:#4000c3;stroke-width:2.0192306;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 879.38505,148.3746 -24.11204,21.32513 17.05428,0.45539 -0.0826,32.96459 -0.0113,0.01 0.0113,0.008 0,0.10279 0.14752,0 27.756,19.15098 -27.91827,19.19262 0.0325,0.0221 -0.0177,0 0.0826,33.08455 -17.05428,0.4554 24.11204,21.32512 25.29227,-22.63254 -18.06042,0.47987 -0.0738,-28.59672 23.75208,-16.32805 23.82877,16.44068 0.0708,28.85624 -17.05724,0.4554 24.11499,21.32512 25.29227,-22.63254 -18.06043,0.47987 -0.0856,-32.74178 -0.0944,0 -27.81204,-19.19262 27.80615,-19.11671 0.10028,0 0.0856,-32.73932 18.06043,0.47987 -25.29227,-22.63255 -24.11499,21.32513 17.05724,0.45539 -0.0708,28.88319 -23.77862,16.34518 -23.80223,-16.42599 0.0738,-28.43022 18.06042,0.47987 -25.29227,-22.63255 z"
id="path5466-3-8-1-7-7-4" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4347"
viewBox="0 0 149.65765 157.94562"
height="44.575764mm"
width="42.236713mm">
<defs
id="defs4349" />
<metadata
id="metadata4352">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-333.74263,-396.24655)"
id="layer1">
<g
id="g4220">
<g
id="g8200-3"
transform="matrix(1.0076627,0,0,1.0076627,-230.71888,-285.33301)">
<rect
ry="0.26629567"
y="794.66962"
x="563.3208"
height="37.427334"
width="143.88431"
id="rect6782-4-6-6"
style="opacity:1;fill:none;fill-opacity:1;stroke:#888888;stroke-width:4.45221663;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g8191-7">
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 613.64003,795.05129 42.72239,0 0,18.70732 -42.72239,0 z"
id="path7877-5" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 682.77868,814.19081 22.9485,0 0,17.17924 -22.9485,0 z"
id="path7875-3" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 635.12988,814.38281 46.94048,0 0,16.98724 -46.94048,0 z"
id="path7873-5" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 565.46659,814.19081 22.94851,0 0,17.17924 -22.94851,0 z"
id="path7871-6" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 588.17173,814.38281 46.94047,0 0,16.98724 -46.94047,0 z"
id="path7869-2" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 656.97146,795.05129 48.75572,0 0,18.65165 -48.75572,0 z"
id="path7867-9" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 565.42858,795.05129 48.75572,0 0,18.65165 -48.75572,0 z"
id="path7857-1" />
</g>
</g>
<rect
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:4.08422089;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5784-1-2"
width="145.57339"
height="112.29983"
x="336.71738"
y="399.47974" />
<g
id="g4213">
<path
id="path5466-3-6-7-2-7-0-7-7"
d="m 360.85819,439.82588 -5e-5,-5.5737 41.5993,0 -0.56273,-7.04627 28.69171,9.88937 -27.1577,9.3851 -0.53396,-6.65422 -42.03775,-4e-5 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.18764484;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path5466-3-6-7-2-7-0-9-1-0"
d="m 445.58853,455.88888 1.4e-4,5.37015 -46.31732,-10e-6 0.62659,6.78898 -31.94578,-9.52825 30.2377,-9.04239 0.59458,6.41122 46.80546,5e-5 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.23009062;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
rx="0"
ry="1.550243"
y="454.27634"
x="445.30862"
height="3.100486"
width="15.995984"
id="rect7798-9"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#606f79;stroke-width:0.48253697;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<rect
rx="0"
ry="1.4011024"
y="430.98013"
x="351.3764"
height="2.8022048"
width="13.644124"
id="rect7798-7-3"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#606f79;stroke-width:0.42367512;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
id="path7682-6"
d="m 439.06189,495.30104 -7.9454,-8.04607 -1.14837,0.7862 c -3.61149,2.47256 -4.63104,3.08084 -7.53184,4.49366 -10.65499,5.18946 -22.33918,5.9091 -33.61831,2.07059 -11.23485,-3.82344 -20.50509,-12.05763 -25.81527,-22.93017 -7.48449,-15.32438 -5.84894,-33.76502 4.20465,-47.40682 6.69395,-9.08307 16.12,-15.13706 27.36262,-17.57399 2.28836,-0.49602 2.71792,-0.91593 8.05743,-0.91593 5.3395,0 6.84049,0.41994 9.12885,0.91593 11.18467,2.42435 20.70757,8.54068 27.36208,17.57399 8.75302,11.88196 11.20004,27.6104 6.49581,41.75225 -1.76747,5.3134 -4.92777,11.01056 -8.30774,14.97664 -0.68691,0.80601 -1.2194,1.55408 -1.18332,1.66234 10.96632,12.96854 5.2213,6.32193 18.27193,20.11924 -1.01475,2.31585 -1.54375,2.12461 -3.2045,4.39283 -8.30501,-8.55895 -3.74741,-3.3866 -12.12861,-11.87069 z m -19.94646,-9.59137 c 0,0 8.40425,-2.78374 17.00538,-16.56642 7.53877,-12.08034 6.15416,-22.68992 -0.16305,-35.73085 -5.05482,-10.43489 -9.86145,-16.00849 -20.69065,-19.60304 -9.77324,-3.24407 -15.96974,-3.20276 -25.58063,0.67681 -11.35948,4.58541 -18.83585,11.86307 -22.77744,23.65204 -3.80228,11.37227 -4.14101,19.47128 1.19876,30.12717 2.02954,4.05009 3.07474,6.28077 6.33549,9.66934 7.40718,7.69756 13.90163,11.20411 25.07199,12.29671 1.85125,0.18109 11.62881,-1.16482 11.62881,-1.16482 z"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#606f79;stroke-width:0.73655474;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg9306"
viewBox="0 0 164.51003 164.51003"
height="46.428387mm"
width="46.428387mm">
<defs
id="defs9308" />
<metadata
id="metadata9311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-309.17356,-461.53576)"
id="layer1">
<g
transform="translate(-167.69087,455.61918)"
id="g8332">
<circle
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:6.09184361;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4136-6-5-5"
cx="559.11945"
cy="88.171593"
r="79.209091" />
<g
transform="matrix(0.823168,0,0,0.823168,253.51017,-732.24499)"
id="g8143-3">
<rect
ry="2.1805694"
y="957.65753"
x="300.04675"
height="79.028679"
width="141.36179"
id="rect6782-4-65"
style="opacity:1;fill:none;fill-opacity:1;stroke:#cdc9ca;stroke-width:6.4125905;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g8128-6">
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 349.45615,989.24954 42.64882,0 0,16.18666 -42.64882,0 z"
id="path8126-3" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 417.88037,1005.7241 22.90899,0 0,14.8645 -22.90899,0 z"
id="path8124-9" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 370.31363,1005.8091 46.85964,0 0,14.6983 -46.85964,0 z"
id="path8122-4" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 300.7703,1005.7241 22.90898,0 0,14.8645 -22.90898,0 z"
id="path8120-8" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 323.43633,1005.8091 46.85964,0 0,14.6983 -46.85964,0 z"
id="path8118-1" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 392.15692,989.1021 48.67176,0 0,16.1385 -48.67176,0 z"
id="path8116-2" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 417.91007,974.01083 22.91856,0 0,14.59619 -22.91856,0 z"
id="path8114-9" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 370.28045,974.20771 46.85966,0 0,14.69832 -46.85966,0 z"
id="path8112-3" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 300.73236,974.12281 22.91856,0 0,14.59618 -22.91856,0 z"
id="path8110-9" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 323.40317,974.20771 46.85964,0 0,14.69832 -46.85964,0 z"
id="path8108-0" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 300.73236,989.16516 48.67175,0 0,16.13844 -48.67175,0 z"
id="path8106-8" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 300.73236,1020.2383 140.09627,0 0,16.2336 -140.09627,0 z"
id="path8104-8" />
<path
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#cdc9ca;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 300.73236,957.75687 140.09627,0 0,16.23354 -140.09627,0 z"
id="rect5963-6-2-1-6-4-5" />
</g>
</g>
<g
id="g6823">
<path
id="path5466-5"
d="m 514.49217,32.215744 -9.37091,9.370913 24.16949,24.169555 -12.17336,11.519886 33.29679,0.04349 0,-31.55773 -11.49769,10.877368 -24.42432,-24.424288 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path5466-3-4"
d="m 605.16029,141.71329 9.37091,-9.37091 -24.16949,-24.16956 12.17352,-11.519725 -33.29682,-0.04349 0,31.557695 11.49766,-10.8774 24.42442,24.42429 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path5466-3-6-76"
d="m 564.73554,72.005775 9.37088,9.370913 24.16959,-24.169524 11.51976,12.173485 0.0435,-33.296754 -31.55777,0 10.87744,11.497656 -24.42429,24.424385 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path5466-3-6-7-5"
d="m 551.65408,103.23429 -9.37088,-9.370945 -24.16959,24.169625 -11.51976,-12.17359 -0.0435,33.29679 31.55773,0 -10.87743,-11.49766 24.42429,-24.42438 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg8669"
viewBox="0 0 164.51002 164.51002"
height="46.428383mm"
width="46.428383mm">
<defs
id="defs8671" />
<metadata
id="metadata8674">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-277.74501,-401.53577)"
id="layer1">
<g
transform="matrix(3.2216066,0,0,3.2216066,-1037.1702,-358.97986)"
id="g6069"
style="display:inline">
<circle
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:1.89093339;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4136-6-5"
cx="433.68741"
cy="261.59949"
r="24.586828" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 419.83492,244.23056 -2.90877,2.90877 7.50231,7.50233 -3.77866,3.57582 10.33546,0.0135 0,-9.79565 -3.56893,3.37638 -7.58141,-7.5814 z"
id="path5466" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 447.97868,278.21905 2.90877,-2.90877 -7.50231,-7.50233 3.77871,-3.57577 -10.33547,-0.0135 0,9.79564 3.56892,-3.37639 7.58144,7.5814 z"
id="path5466-3" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 435.43067,256.58155 2.90876,2.90877 7.50234,-7.50232 3.57578,3.7787 0.0135,-10.33545 -9.79566,0 3.3764,3.56892 -7.5814,7.58143 z"
id="path5466-3-6" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 431.37013,266.27501 -2.90876,-2.90878 -7.50234,7.50235 -3.57578,-3.77873 -0.0135,10.33546 9.79565,0 -3.3764,-3.56892 7.5814,-7.58143 z"
id="path5466-3-6-7" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg6164"
viewBox="0 0 148.51957 141.32653"
height="39.885487mm"
width="41.915524mm">
<defs
id="defs6166" />
<metadata
id="metadata6169">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-411.4545,-467.41322)"
id="layer1">
<g
id="g4144"
transform="translate(0.5118018,-0.0474185)">
<rect
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:4.48458481;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect5784-3"
width="144.03497"
height="136.84193"
x="413.69681"
y="469.65552" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 479.88752,583.90055 3e-5,-12.60789 -33.82967,0 0.45763,-15.93893 -23.33284,22.37013 22.08534,21.22945 0.43424,-15.05209 34.18624,-9e-5 z"
id="path5466-3-6-7-2-7-67" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 494.75491,530.88618 4e-5,-12.60792 -33.82968,0 0.45763,-15.93893 -23.33287,22.37012 22.08537,21.22946 0.43424,-15.05209 34.18624,-10e-5 z"
id="path5466-3-6-7-2-7-0-5" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 488.40487,491.6943 -1e-4,12.60788 33.82974,-3e-5 -0.45766,15.93896 23.3329,-22.37013 -22.08534,-21.22945 -0.43427,15.05209 -34.18627,10e-5 z"
id="path5466-3-6-7-2-7-0-9-35" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:1.61080325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 476.3934,547.17088 -10e-5,12.60788 33.82974,-3e-5 -0.45766,15.93896 23.3329,-22.37012 -22.08534,-21.22946 -0.43427,15.05212 -34.18627,7e-5 z"
id="path5466-3-6-7-2-7-0-9-3-6" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7418"
viewBox="0 0 146.52122 203.53003"
height="57.440697mm"
width="41.351543mm">
<defs
id="defs7420" />
<metadata
id="metadata7423">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-292.45367,-367.74006)"
id="layer1">
<g
transform="translate(-10.876593,-253.63388)"
id="g7285">
<g
style="display:inline"
id="g6050-3"
transform="matrix(4.3512606,0,0,4.3512606,-1517.2224,-830.04365)">
<rect
y="348.80347"
x="418.905"
height="31.025654"
width="32.656502"
id="rect5784-0-6"
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:1.01677299;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path5466-3-6-7-2-7-0-9-6-1"
d="m 435.28711,350.91797 -4.35938,4.53711 3.0918,0.0879 0,2.97461 a 6.0267854,6.0267854 0 0 0 -1.54297,0.71094 l -4.07031,-4.06836 2.38281,-2.24805 -6.63867,-0.14063 0.125,6.29102 2.24805,-2.12305 4.13867,4.13867 a 6.0267854,6.0267854 0 0 0 -0.74805,1.76172 l -4.05468,0 0.0957,-3.27343 -4.79297,4.59375 4.53516,4.36132 0.0898,-3.09179 4.06055,0 a 6.0267854,6.0267854 0 0 0 0.6914,1.84765 l -3.9707,3.97071 -2.24805,-2.38086 -0.14062,6.63672 6.29101,-0.125 -2.12304,-2.24805 3.93164,-3.93359 a 6.0267854,6.0267854 0 0 0 1.92187,0.89843 l 0,3.68555 -3.27344,-0.0937 4.59375,4.79297 4.35938,-4.53711 -3.08984,-0.0879 0,-3.63672 a 6.0267854,6.0267854 0 0 0 2.23828,-0.875 l 3.77148,3.77148 -2.38086,2.24805 6.63672,0.14063 -0.12305,-6.29102 -2.25,2.12305 L 440.8457,367.5 a 6.0267854,6.0267854 0 0 0 0.85547,-2.25195 l 3.63086,0 -0.0937,3.27343 4.79297,-4.5957 -4.53711,-4.35937 -0.0879,3.09179 -3.84375,0 a 6.0267854,6.0267854 0 0 0 -0.85938,-1.81054 l 3.82032,-3.82032 2.24804,2.38282 0.14063,-6.63868 -6.29102,0.125 2.12305,2.25 -3.94141,3.93946 a 6.0267854,6.0267854 0 0 0 -2.19531,-0.76367 l 0,-2.70508 3.27344,0.0937 -4.59375,-4.79297 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
id="g7281">
<rect
y="623.60687"
x="305.5632"
height="59.76096"
width="141.95378"
id="rect6083-0"
style="opacity:1;fill:#e83d1c;fill-opacity:1;stroke:#c5282e;stroke-width:4.46587038;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.84313725" />
<path
id="rect6085-7-9-8-2"
d="m 318.07372,632.68112 0,16.86523 25.51367,0 0,-4.91992 23.60156,10.25977 -23.60156,10.06836 0,-5.21289 -25.51367,0 0,16.86328 25.51367,0 0,-3.84375 1.18164,-0.50391 62.38086,0 1.33398,0.58008 0,3.76758 25.51172,0 0,-16.86328 -25.51172,0 0,5.7832 -23.74219,-10.32031 23.74219,-10.13086 0,4.47265 25.51172,0 0,-16.86523 -25.51172,0 0,4.58594 -0.81445,0.34765 -63.38867,0 -0.69336,-0.30078 0,-4.63281 -25.51367,0 z m 36.06445,9.21875 43.48828,0 -21.94922,9.36328 -21.53906,-9.36328 z m 22.11328,16.92578 21.04102,9.14649 -42.47852,0 21.4375,-9.14649 z"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#844043;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg6813"
viewBox="0 0 147.46868 140.32655"
height="39.603271mm"
width="41.618938mm">
<defs
id="defs6815" />
<metadata
id="metadata6818">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-117.69423,-287.9132)"
id="layer1">
<g
transform="matrix(3.8195597,0,0,3.8195597,-1481.6868,-1084.6941)"
id="g6050"
style="display:inline">
<g
transform="matrix(1.1465715,0,0,1.1465715,-60.987233,-39.98251)"
id="g6265">
<rect
y="348.80347"
x="418.905"
height="31.025654"
width="32.656502"
id="rect5784-0"
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:1.01677299;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path5466-3-6-7-2-7-0-9-6"
d="m 435.28711,350.91797 -4.35938,4.53711 3.0918,0.0879 0,2.97461 a 6.0267854,6.0267854 0 0 0 -1.54297,0.71094 l -4.07031,-4.06836 2.38281,-2.24805 -6.63867,-0.14063 0.125,6.29102 2.24805,-2.12305 4.13867,4.13867 a 6.0267854,6.0267854 0 0 0 -0.74805,1.76172 l -4.05468,0 0.0957,-3.27343 -4.79297,4.59375 4.53516,4.36132 0.0898,-3.09179 4.06055,0 a 6.0267854,6.0267854 0 0 0 0.6914,1.84765 l -3.9707,3.97071 -2.24805,-2.38086 -0.14062,6.63672 6.29101,-0.125 -2.12304,-2.24805 3.93164,-3.93359 a 6.0267854,6.0267854 0 0 0 1.92187,0.89843 l 0,3.68555 -3.27344,-0.0937 4.59375,4.79297 4.35938,-4.53711 -3.08984,-0.0879 0,-3.63672 a 6.0267854,6.0267854 0 0 0 2.23828,-0.875 l 3.77148,3.77148 -2.38086,2.24805 6.63672,0.14063 -0.12305,-6.29102 -2.25,2.12305 L 440.8457,367.5 a 6.0267854,6.0267854 0 0 0 0.85547,-2.25195 l 3.63086,0 -0.0937,3.27343 4.79297,-4.5957 -4.53711,-4.35937 -0.0879,3.09179 -3.84375,0 a 6.0267854,6.0267854 0 0 0 -0.85938,-1.81054 l 3.82032,-3.82032 2.24804,2.38282 0.14063,-6.63868 -6.29102,0.125 2.12305,2.25 -3.94141,3.93946 a 6.0267854,6.0267854 0 0 0 -2.19531,-0.76367 l 0,-2.70508 3.27344,0.0937 -4.59375,-4.79297 z"
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg8031"
viewBox="0 0 147.56303 204.26426"
height="57.647915mm"
width="41.645565mm">
<defs
id="defs8033" />
<metadata
id="metadata8036">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-306.21848,-398.8015)"
id="layer1">
<g
transform="translate(-179.04191,5.5403099)"
id="g7520">
<g
id="g7512">
<rect
y="459.42532"
x="487.57816"
height="135.8737"
width="143.01582"
id="rect5784-0-2"
style="opacity:1;fill:#396c85;fill-opacity:1;stroke:#314b58;stroke-width:4.45285368;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g7506">
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.87532747;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 540.34211,524.56253 c -0.32107,-1.13312 -0.8296,-2.20446 -1.50448,-3.16964 l 6.68807,-6.68805 3.93554,4.17149 0.24619,-11.62205 -11.01339,0.21885 3.71672,3.93897 -6.90005,6.89664 c -1.1822,-0.6908 -2.48754,-1.14487 -3.84323,-1.33693 l 0,-4.73565 5.73066,0.16403 -8.04207,-8.39084 -6e-5,8e-5 -7.63178,7.94293 5.41268,0.15387 0,5.20752 c -0.95466,0.28525 -1.86409,0.70428 -2.70122,1.24461 l -7.12569,-7.1223 4.17148,-3.93556 -11.62203,-0.24618 0.21884,11.0134 3.93556,-3.71674 7.24537,7.24539 c -0.59803,0.95173 -1.04012,1.99292 -1.30957,3.08416 l -7.09836,0 0.16755,-5.73063 -8.39084,8.04206 7.93951,7.63517 0.1572,-5.41265 7.10863,0 c 0.22307,1.13736 0.63202,2.23019 1.2104,3.23459 l -6.95132,6.95133 -3.93558,-4.16805 -0.24616,11.6186 11.01338,-0.21883 -3.71671,-3.93555 6.88295,-6.88637 c 1.01968,0.7191 2.15884,1.25164 3.36453,1.57284 l 0,6.45213 -5.73066,-0.16405 8.04207,8.39084 7.63176,-7.9429 -5.40924,-0.15389 0,-6.36665 c 1.39575,-0.24236 2.72839,-0.76333 3.91845,-1.5318 l 6.60258,6.60256 -4.16807,3.93554 11.6186,0.2462 -0.21541,-11.0134 -3.93898,3.71672 -6.71876,-6.71549 c 0.75944,-1.19995 1.26882,-2.54086 1.49764,-3.94238"
id="path5466-3-6-7-2-7-0-9-6-7-2" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#606f79;stroke-width:0.87532747;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 540.50615,529.2229 36.92157,0.19158 c 0.22306,1.13736 0.63202,2.23019 1.21041,3.23459 l -6.95133,6.95133 -3.93557,-4.16805 -0.24618,11.6186 11.01339,-0.21883 -3.7167,-3.93555 6.88294,-6.88637 c 1.01969,0.7191 2.15885,1.25164 3.36452,1.57284 l 0,6.45213 -5.73066,-0.16405 8.04207,8.39084 7.63178,-7.9429 -5.40924,-0.15389 0,-6.36665 c 1.39573,-0.24236 2.72838,-0.76333 3.91844,-1.5318 l 6.60258,6.60256 -4.16807,3.93554 11.6186,0.2462 -0.21541,-11.0134 -3.93897,3.71672 -6.71877,-6.71549 c 0.75944,-1.19995 1.26882,-2.54086 1.49764,-3.94238 l 6.35638,0 -0.16403,5.73064 8.39084,-8.04548 -7.94292,-7.63177 -0.15388,5.41267 -6.72908,0 c -0.32108,-1.13312 -0.8296,-2.20446 -1.50448,-3.16964 l 6.68807,-6.68805 3.93553,4.17149 0.24619,-11.62205 -11.0134,0.21885 3.71673,3.93897 -6.90005,6.89664 c -1.18219,-0.6908 -2.48754,-1.14487 -3.84322,-1.33693 l 0,-4.73565 5.73065,0.16403 -8.04206,-8.39084 -6e-5,8e-5 -7.63178,7.94293 5.41267,0.15387 0,5.20752 c -0.95466,0.28525 -1.86409,0.70428 -2.70121,1.24461 l -7.1257,-7.1223 4.17148,-3.93556 -11.62203,-0.24618 0.21885,11.0134 3.93554,-3.71674 7.24539,7.24539 c -0.59803,0.95173 -1.04013,1.99292 -1.30958,3.08416 l -37.19687,-0.12081"
id="path5466-3-6-7-2-7-0-9-6-7-2-2" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#606f79;stroke-width:0.18944347;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.96078431"
d="m 558.51732,543.71683 c -1.09964,-1.37716 -1.98976,-4.3033 -2.64706,-8.70175 -0.47048,-3.14831 -0.47048,-12.74399 0,-15.8923 0.6573,-4.39844 1.54742,-7.32459 2.64706,-8.70174 1.03458,-1.29568 1.95332,-0.82845 2.97767,1.5143 1.46911,3.35998 2.16131,8.20677 2.16131,15.13359 0,6.92682 -0.6922,11.77362 -2.16131,15.13359 -1.02435,2.34275 -1.94309,2.80998 -2.97767,1.51431 z m 2.24065,-2.41603 c 2.99004,-3.43175 3.00208,-25.27566 0.0158,-28.75734 -0.79344,-0.92508 -1.38739,-0.8975 -2.25441,0.1047 -1.72734,1.99666 -3.08218,9.52792 -2.80864,15.6127 0.42439,9.44057 2.81098,15.60655 5.04722,13.03994 z"
id="path7451" />
<rect
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.60000002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6.4000001, 1.60000002;stroke-dashoffset:0;stroke-opacity:1"
id="rect7477"
width="119.14749"
height="92.630989"
x="499.21738"
y="479.95926"
ry="13.081475" />
</g>
</g>
<g
transform="matrix(1.0078089,0,0,0.99971949,366.11848,-428.18566)"
id="g7293-6">
<rect
y="823.91028"
x="120.45169"
height="59.76096"
width="141.95378"
id="rect6083-0-8-1"
style="opacity:1;fill:#e83d1c;fill-opacity:1;stroke:#c5282e;stroke-width:4.46587038;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.84313725" />
<path
id="rect6085-7-9-8-2-7-5"
d="m 132.96222,832.98451 0,16.86523 25.51367,0 0,-4.91992 23.60156,10.25977 -23.60156,10.06836 0,-5.21289 -25.51367,0 0,16.86328 25.51367,0 0,-3.84375 1.18164,-0.50391 62.38086,0 1.33398,0.58008 0,3.76758 25.51172,0 0,-16.86328 -25.51172,0 0,5.7832 -23.74219,-10.32031 23.74219,-10.13086 0,4.47265 25.51172,0 0,-16.86523 -25.51172,0 0,4.58594 -0.81445,0.34765 -63.38867,0 -0.69336,-0.30078 0,-4.63281 -25.51367,0 z m 36.06445,9.21875 43.48828,0 -21.94922,9.36328 -21.53906,-9.36328 z m 22.11328,16.92578 21.04102,9.14649 -42.47852,0 21.4375,-9.14649 z"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#844043;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4367"
viewBox="0 0 204.31426 120.75318"
height="34.079231mm"
width="57.662025mm">
<defs
id="defs4369" />
<metadata
id="metadata4372">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-214.98573,-183.41419)"
id="layer1">
<g
transform="matrix(0.86352245,0,0,0.86352245,-190.39364,-408.4426)"
id="g8402">
<path
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#b0b0b1;stroke-width:6.44321299;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 573.51527,688.61987 a 74.01992,66.697717 0 0 0 -73.05874,56.51654 32.498662,30.057924 0 0 0 -27.78637,29.70547 32.498662,30.057924 0 0 0 32.49923,30.0578 32.498662,30.057924 0 0 0 14.97544,-3.3914 74.01992,66.697717 0 0 0 53.37044,20.5063 74.01992,66.697717 0 0 0 50.11109,-17.7 42.624461,45.471982 0 0 0 17.0078,3.8005 42.624461,45.471982 0 0 0 24.1998,-8.0981 30.470047,25.588576 0 0 0 7.52549,0.8243 30.470047,25.588576 0 0 0 30.47312,-25.59039 30.470047,25.588576 0 0 0 -21.03482,-24.31307 42.624461,45.471982 0 0 0 -41.16359,-33.77023 42.624461,45.471982 0 0 0 -6.10972,0.51598 74.01992,66.697717 0 0 0 -61.00917,-29.0637 z"
id="path6269" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.18078947;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 561.96976,817.14207 c -11.4574,-1.4376 -25.37914,-7.30672 -34.34845,-14.48059 -6.05961,-4.84663 -6.32523,-4.90246 -13.9836,-2.93968 -18.98899,4.86673 -36.56354,-6.85969 -36.56354,-24.3966 0,-11.96224 4.89303,-18.62229 18.4202,-25.07229 6.97273,-3.32473 8.12025,-4.62355 10.61835,-12.0184 9.38325,-27.77617 35.18642,-44.98739 67.4454,-44.98739 21.23462,0 35.73245,5.50591 50.82947,19.30374 8.33727,7.61979 9.83738,8.36467 17.80969,8.8434 15.02973,0.90251 27.37844,9.89079 33.91226,24.68375 2.38726,5.40493 4.86257,8.23915 8.84711,10.12994 13.54778,6.42885 17.76979,20.72045 9.14813,30.96669 -5.56656,6.61549 -12.57843,9.44814 -23.38763,9.44814 -4.78742,0 -9.14091,0.70628 -9.6744,1.5695 -0.5335,0.86323 -5.0343,2.75102 -10.00176,4.1951 -7.76785,2.25818 -10.42356,2.34896 -18.97738,0.64873 -9.91613,-1.97101 -9.96806,-1.96202 -17.50477,3.03009 -13.59407,9.00434 -34.31615,13.36865 -52.58908,11.07587 z"
id="path8400" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4367"
viewBox="0 0 198.45323 142.30469"
height="40.161549mm"
width="56.007912mm">
<defs
id="defs4369" />
<metadata
id="metadata4372">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(2.984375,-3.1992184)"
id="g833">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5.87959719;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 96.115873,6.1383778 C 67.04368,6.1386378 42.859826,25.402508 41.041419,50.228338 c -22.57151,0.2689 -41.08562249,17.19958 -41.08520249,38.34412 7.28e-4,21.313322 18.79784549,38.391562 41.61437649,38.391722 h 0.01282 0.01282 c 1.193604,-0.01 2.379096,-0.14116 3.566894,-0.24537 5.979661,9.46057 16.363344,15.31689 27.643431,15.33143 h 0.0037 0.0018 c 8.671185,-0.004 16.819685,-3.59129 22.928465,-9.62586 6.148587,6.34487 14.508777,10.13929 23.433817,10.14038 11.38276,-0.002 21.85323,-5.95327 27.81372,-15.54383 1.2983,0.12298 2.59448,0.27384 3.90015,0.28382 h 0.011 0.011 c 22.81653,-1.6e-4 41.61733,-17.07839 41.61805,-38.391722 1.7e-4,-21.2174 -18.63701,-38.2138 -41.31427,-38.36792 -1.629,-24.89951 -25.74953,-44.2728502 -54.821627,-44.3957402 l -0.10071,-0.011 z"
id="path947-3" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#2c2c2c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5.87959719;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 96.115234,3.1992188 C 66.693539,3.1994839 41.737088,22.181724 38.429688,47.558594 15.594742,49.077727 -2.9848126,66.549428 -2.984375,88.572266 -2.9835884,111.61403 17.286827,129.90413 41.570312,129.9043 h 0.01367 0.02344 0.01367 c 0.777578,-0.007 1.422564,-0.0841 2.121094,-0.14258 6.631016,9.40311 17.381132,15.21346 29.060546,15.22851 h 0.002 0.0059 0.002 c 8.552267,-0.004 16.529767,-3.32323 22.890625,-8.75195 6.439523,5.74065 14.644393,9.26455 23.470703,9.26563 11.7839,-0.002 22.61984,-5.9042 29.24219,-15.43555 0.80227,0.073 1.55469,0.16894 2.44921,0.17578 h 0.0117 0.0215 0.0117 c 24.28348,-1.7e-4 44.55782,-18.28797 44.55859,-41.330078 3.2e-4,-22.091637 -18.68964,-39.624807 -41.625,-41.048828 C 150.71103,22.423159 125.81665,3.3333166 96.404297,3.2089844 l 0.306641,0.017578 -0.259766,-0.027344 z m 0,5.8789062 h 0.01563 l 0.0957,0.00977 0.152344,0.00195 C 124.15189,9.207246 146.76905,27.6241 148.28125,50.736328 l -4.79696,4.237951 7.70907,-1.489902 c 21.21941,0.144206 38.39482,15.933832 38.39453,35.429687 -0.002,19.583646 -17.32888,35.451016 -38.67773,35.451166 h -0.0117 c -1.09136,-0.009 -2.29697,-0.14494 -3.63282,-0.27148 l -5.73195,-1.98085 2.95656,3.35585 c -5.43355,8.74271 -14.95799,14.15443 -25.3164,14.15625 -8.09205,-9.9e-4 -15.69284,-3.43695 -21.322268,-9.24609 l -1.915658,-3.33684 -2.262076,3.29191 c -5.593326,5.52538 -13.002041,8.77372 -20.863281,8.77735 h -0.0039 c -10.263407,-0.0146 -19.70774,-5.33954 -25.158229,-13.96289 l 2.96242,-3.32265 -5.704608,1.96523 c -1.227778,0.10771 -2.330381,0.22531 -3.322266,0.23437 -0.0035,3e-5 -0.0082,-3e-5 -0.01172,0 h -0.002 C 20.220734,124.02524 2.897153,108.15716 2.8964844,88.572266 2.8960984,69.143811 19.955279,53.419583 41.076172,53.167969 l 6.920168,1.325484 -4.023684,-4.050094 C 45.660435,27.400945 68.341672,9.0783753 96.115234,9.078125 Z"
id="path947" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="49.483669mm"
height="49.483669mm"
viewBox="0 0 175.33583 175.33583"
id="svg9976"
version="1.1">
<defs
id="defs9978" />
<metadata
id="metadata9981">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<circle
r="86.282326"
cy="87.667915"
cx="87.667915"
id="path3338-7"
style="fill:#00ad69;fill-opacity:1;fill-rule:evenodd;stroke:#149965;stroke-width:2.77117658;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
transform="matrix(0.96622991,0,0,0.96622991,-177.4313,-263.75284)"
id="g4662-2-3">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#149965;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 224.07227,308.66406 a 4.0004,4.0004 0 0 0 -3.71485,5.06836 l 8.73438,31.44531 a 4.0004,4.0004 0 0 0 0.0605,0.18165 4.0004,4.0004 0 0 0 0.0488,0.14453 4.0004,4.0004 0 0 0 0.0801,0.1914 4.0004,4.0004 0 0 0 0.084,0.18555 4.0004,4.0004 0 0 0 0.0859,0.16016 4.0004,4.0004 0 0 0 0.10938,0.1914 4.0004,4.0004 0 0 0 0.10351,0.15039 4.0004,4.0004 0 0 0 0.1211,0.16992 4.0004,4.0004 0 0 0 0.13281,0.15821 4.0004,4.0004 0 0 0 0.12695,0.14453 4.0004,4.0004 0 0 0 0.15625,0.1543 4.0004,4.0004 0 0 0 0.13281,0.12109 4.0004,4.0004 0 0 0 0.15625,0.12695 4.0004,4.0004 0 0 0 0.16211,0.12305 4.0004,4.0004 0 0 0 0.17774,0.11328 4.0004,4.0004 0 0 0 0.15234,0.0937 4.0004,4.0004 0 0 0 0.21289,0.10938 4.0004,4.0004 0 0 0 0.14453,0.0684 4.0004,4.0004 0 0 0 0.21289,0.084 4.0004,4.0004 0 0 0 0.16407,0.0606 4.0004,4.0004 0 0 0 0.24414,0.0684 4.0004,4.0004 0 0 0 0.12695,0.0332 4.0004,4.0004 0 0 0 0.22461,0.0391 4.0004,4.0004 0 0 0 0.19922,0.0293 4.0004,4.0004 0 0 0 0.15234,0.0117 4.0004,4.0004 0 0 0 0.28321,0.0137 4.0004,4.0004 0 0 0 2.57617,-0.93945 4.0004,4.0004 0 0 0 0.29297,-0.27344 4.0004,4.0004 0 0 0 0.0488,-0.0508 l 3.77344,-4.02539 17.63281,17.02343 -19.01953,6.23243 a 4.0004,4.0004 0 0 0 -1.40039,6.80078 l 4.20117,3.70703 -20.95312,20.57226 a 4.0004,4.0004 0 0 0 2.80273,6.85547 4.0004,4.0004 0 0 0 3.20899,-1.54492 l 23.61328,-23.18555 a 4.0004,4.0004 0 0 0 -0.15625,-5.85351 l -2.18359,-1.92578 18.6875,-6.1211 a 4.0004,4.0004 0 0 0 1.5332,-6.67968 L 242.2753,334.24036 a 4.0004,4.0004 0 0 0 -5.69531,0.14258 l -1.6836,1.79688 -4.94921,-17.81446 17.38281,4.7168 -1.58594,1.69922 a 4.0004,4.0004 0 0 0 0.15625,5.61719 l 20.96289,20.08984 a 4.0004,4.0004 0 1 0 5.53516,-5.77539 l -18.11133,-17.35937 3.41992,-3.66407 a 4.0004,4.0004 0 0 0 -1.87695,-6.58984 l -30.57031,-8.29688 a 4.0004,4.0004 0 0 0 -1.1875,-0.13867 z"
id="path4226-8-2-6" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#149965;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 312.22266,311.83398 -22.74414,22.74219 -3.29883,-3.76953 c -2.0761,-2.37117 -5.95134,-1.52204 -6.84571,1.5 l -16.43359,55.56641 -0.92187,-0.92188 c -1.55242,-1.55298 -4.06646,-1.56431 -5.63282,-0.0254 l -25.33008,24.89258 c -3.80403,3.73829 1.80275,9.44466 5.60743,5.70703 l 22.50195,-22.11523 2.875,2.875 c 2.14565,2.14432 5.80396,1.21366 6.66406,-1.69532 l 16.28125,-55.04492 1.32813,1.51758 c 1.52361,1.74215 4.20323,1.83177 5.83984,0.19531 l 24.59375,-24.5957 c 2.6028,-7.0172 -4.48437,-6.82812 -4.48437,-6.82812 z"
id="path4228-56-5-7" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#149965;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 325.74414,325.9082 a 4.0004,4.0004 0 0 0 -2.76172,1.1836 l -23.58203,23.14648 a 4.0004,4.0004 0 0 0 0.40234,6.05469 l 1.00977,0.75781 -15.22656,5.37305 a 4.0004,4.0004 0 0 0 -1.47071,6.62695 l 22.71094,22.27344 a 4.0004,4.0004 0 0 0 5.4336,0.1543 l 2.20117,-1.92579 5.13086,18.46875 L 301.75977,403 l 2.05078,-2.1875 a 4.0004,4.0004 0 0 0 -0.15039,-5.62305 l -20.55274,-19.69726 a 4.0004,4.0004 0 0 0 -3.17773,-1.50586 4.0004,4.0004 0 0 0 -2.76758,6.88867 l 18.10547,17.35156 -3.84375,4.10156 a 4.0004,4.0004 0 0 0 1.83203,6.58594 l 31.00781,8.73438 a 4.0004,4.0004 0 0 0 4.93945,-4.91992 l -8.73437,-31.44532 a 4.0004,4.0004 0 0 0 -6.48828,-1.93945 l -4.19922,3.67383 -15.48437,-15.18555 16.2246,-5.72461 a 4.0004,4.0004 0 0 0 1.06836,-6.97265 l -3.26367,-2.44727 20.26172,-19.88477 a 4.0004,4.0004 0 0 0 -2.84375,-6.89453 z"
id="path4230-2-7-5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="48.730789mm"
height="48.730789mm"
viewBox="0 0 172.66815 172.66815"
id="svg9976"
version="1.1">
<defs
id="defs9978">
<filter
style="color-interpolation-filters:sRGB"
id="filter4183">
<feBlend
mode="lighten"
in2="BackgroundImage"
id="feBlend4185" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter3710">
<feBlend
mode="lighten"
in2="BackgroundImage"
id="feBlend3708" />
</filter>
</defs>
<metadata
id="metadata9981">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<circle
r="84.969566"
cy="86.334076"
cx="86.334076"
id="path3338-7"
style="fill:#00ad69;fill-opacity:1;fill-rule:evenodd;stroke:#149965;stroke-width:2.72901416;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g853"
transform="matrix(1.374736,0,0,1.374736,-476.83104,1.2854058)">
<path
style="opacity:1;fill:#f0faf6;fill-opacity:1;stroke:#139a65;stroke-width:0.9375;stroke-opacity:1"
d="m 374.56807,42.41031 c 1.53749,-1.49427 2.83189,-3.04338 4.63315,-4.4088 1.27987,-0.97019 3.02078,-1.84764 4.36586,-2.54678 4.26668,-2.21771 8.78035,-3.00725 13.18472,-2.90662 3.50142,0.08 7.20739,1.00413 10.04696,2.12718 3.65543,1.44573 6.0929,3.22108 7.10483,3.94958 1.07362,0.81759 3.01599,2.51896 4.44626,4.18633 1.32716,1.54717 2.39484,3.22378 3.00134,3.99169 0.44313,0.56106 1.37044,2.20241 2.12434,4.04447 0.46098,1.12633 0.82043,2.42859 1.07202,3.30975 0.31968,1.11965 0.54815,3.06672 0.59103,4.28806 0.0326,0.92933 -0.0673,4.08468 -0.13887,4.66747 -0.0645,0.52471 -0.0294,1.27777 -0.15931,2.04041 -0.0835,0.49013 -0.22804,1.03167 -0.39866,1.53126 -0.17348,0.50794 -0.3821,1.11126 -0.59338,1.55133 -0.5053,1.05251 -1.02573,1.72429 -1.11656,1.91768 -0.5013,-3.31294 -0.61243,-6.33366 -1.22289,-9.05418 -0.36326,-1.61884 -0.89411,-3.13139 -1.4201,-4.53596 -0.86289,-2.30421 -1.83879,-4.31784 -2.97375,-6.03346 -1.59873,-2.41667 -3.85755,-4.28478 -5.773,-5.93448 -2.30028,-1.98115 -4.888,-3.17286 -7.67095,-3.98559 -2.72426,-0.79558 -5.6356,-1.23313 -8.64751,-1.22086 -2.53493,0.0103 -5.05115,0.57002 -7.44659,1.53997 -1.36352,0.7782 -2.97103,1.61867 -4.36724,2.77401 -1.86936,1.54685 -3.41901,3.42179 -4.06316,4.20315 -0.73543,0.89209 -1.97569,2.53311 -2.7967,4.13623 -0.70513,1.37686 -1.02928,2.72577 -1.28014,3.54826 -0.81285,2.66499 -0.81637,8.36304 -0.6803,9.26724 0.32268,2.14412 1.13704,4.97393 2.38828,7.45609 1.7882,3.54733 4.19035,6.07524 5.39189,7.00329 1.20639,0.9318 2.80515,2.31055 4.76776,3.06648 2.74705,1.05806 5.75781,1.25035 7.40204,1.1814 1.75309,2.65706 3.7453,4.83586 5.89564,6.69842 -3.38358,1.41078 -6.16598,0.81764 -9.007,0.41993 -8.46249,-2.31358 -14.38403,-5.52917 -19.033,-13.38909 -2.09264,-3.71059 -4.73504,-9.2745 -4.8037,-15.08277 -0.0927,-7.84141 3.25232,-16.0318 7.17669,-19.80109 z"
id="rect833" />
<path
style="opacity:1;fill:#f0faf6;fill-opacity:1;stroke:#139a65;stroke-width:0.93749994;stroke-opacity:1"
d="m 444.74345,81.31528 c -1.53713,1.49466 -2.83114,3.04409 -4.63205,4.40996 -1.27964,0.9705 -3.02033,1.84838 -4.36523,2.54785 -4.26614,2.21877 -8.77961,3.00943 -13.184,2.90989 -3.50144,-0.0791 -7.20763,-1.00235 -10.04749,-2.1247 -3.65579,-1.44482 -6.09369,-3.21957 -7.10581,-3.94782 -1.07383,-0.81732 -3.01662,-2.51821 -4.4473,-4.18523 -1.32754,-1.54684 -2.39564,-3.22318 -3.00232,-3.99094 -0.44327,-0.56095 -1.37098,-2.20208 -2.12534,-4.04394 -0.46126,-1.12622 -0.82103,-2.42839 -1.07284,-3.30949 -0.31996,-1.11957 -0.5489,-3.06659 -0.59209,-4.28792 -0.0328,-0.92932 0.0662,-4.08469 0.1376,-4.6675 0.0644,-0.52472 0.0291,-1.27778 0.15877,-2.04045 0.0833,-0.49016 0.22779,-1.03172 0.39829,-1.53136 0.17335,-0.50798 0.38181,-1.11136 0.59299,-1.55148 0.50504,-1.05264 1.0253,-1.72454 1.11608,-1.91796 0.50212,3.31282 0.614,6.33351 1.22514,9.05388 0.36366,1.61875 0.89488,3.13117 1.42122,4.53561 0.86345,2.304 1.83985,4.31738 2.97524,6.03273 1.59933,2.41626 3.85862,4.28382 5.77446,5.93305 2.30078,1.98057 4.8888,3.17165 7.67194,3.98368 2.72447,0.79491 5.63592,1.23174 8.64782,1.21872 2.53492,-0.011 5.05101,-0.57126 7.44621,-1.54181 1.36331,-0.77854 2.97063,-1.6194 4.36655,-2.77509 1.86897,-1.54732 3.41816,-3.42263 4.06212,-4.20415 0.73521,-0.89227 1.97507,-2.5336 2.79567,-4.13693 0.70479,-1.37703 1.02861,-2.72602 1.27927,-3.54857 0.81218,-2.6652 0.8143,-8.36324 0.678,-9.26741 -0.3232,-2.14404 -1.13827,-4.97365 -2.39012,-7.45549 -1.78908,-3.5469 -4.19185,-6.07421 -5.39363,-7.00197 -1.20662,-0.9315 -2.80572,-2.30985 -4.76851,-3.06529 -2.74731,-1.05738 -5.75812,-1.24893 -7.40234,-1.17957 -1.75374,-2.65663 -3.7465,-4.83493 -5.8973,-6.69697 3.38323,-1.41161 6.16579,-0.81916 9.0069,-0.42215 8.46307,2.31148 14.3854,5.5256 19.03632,13.38438 2.09355,3.71007 4.73733,9.27333 4.80743,15.08158 0.0946,7.84139 -3.24835,16.0326 -7.1718,19.80286 z"
id="rect833-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg9976"
viewBox="0 0 174.679 175.57633"
height="49.551544mm"
width="49.298298mm">
<defs
id="defs9978" />
<metadata
id="metadata9981">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g951">
<rect
style="fill:#00ad69;fill-opacity:1;stroke:#616161;stroke-width:4.92867565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4349-8-9"
width="169.75032"
height="170.64766"
x="2.4643335"
y="2.4643373" />
<g
id="g887"
transform="translate(2.2378468e-6,-2.8634556e-5)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#616161;stroke-width:1.19049227;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 27.465947,22.263606 a 4.7625255,4.7625255 0 0 0 -4.42257,6.033945 L 33.44176,65.73358 a 4.7625255,4.7625255 0 0 0 0.07202,0.216257 4.7625255,4.7625255 0 0 0 0.05809,0.172065 4.7625255,4.7625255 0 0 0 0.09536,0.227864 4.7625255,4.7625255 0 0 0 0.1,0.2209 4.7625255,4.7625255 0 0 0 0.102267,0.190672 4.7625255,4.7625255 0 0 0 0.130216,0.227863 4.7625255,4.7625255 0 0 0 0.123229,0.179042 4.7625255,4.7625255 0 0 0 0.144173,0.202292 4.7625255,4.7625255 0 0 0 0.158112,0.188351 4.7625255,4.7625255 0 0 0 0.151134,0.172065 4.7625255,4.7625255 0 0 0 0.186017,0.183695 4.7625255,4.7625255 0 0 0 0.158112,0.14416 4.7625255,4.7625255 0 0 0 0.186017,0.151136 4.7625255,4.7625255 0 0 0 0.192995,0.146492 4.7625255,4.7625255 0 0 0 0.211602,0.134861 4.7625255,4.7625255 0 0 0 0.181367,0.111551 4.7625255,4.7625255 0 0 0 0.253445,0.130218 4.7625255,4.7625255 0 0 0 0.17206,0.08143 4.7625255,4.7625255 0 0 0 0.253455,0.100004 4.7625255,4.7625255 0 0 0 0.195324,0.07214 4.7625255,4.7625255 0 0 0 0.290649,0.08143 4.7625255,4.7625255 0 0 0 0.151142,0.03952 4.7625255,4.7625255 0 0 0 0.267393,0.04655 4.7625255,4.7625255 0 0 0 0.237177,0.03488 4.7625255,4.7625255 0 0 0 0.181368,0.01393 4.7625255,4.7625255 0 0 0 0.337159,0.01631 4.7625255,4.7625255 0 0 0 3.066963,-1.118427 4.7625255,4.7625255 0 0 0 0.348787,-0.325533 4.7625255,4.7625255 0 0 0 0.05809,-0.06048 l 4.492328,-4.792276 20.992082,20.266603 -22.642988,7.419785 a 4.7625255,4.7625255 0 0 0 -1.667182,8.096413 l 5.001547,4.413267 -24.944947,24.49153 a 4.7625255,4.7625255 0 0 0 3.336685,8.16152 4.7625255,4.7625255 0 0 0 3.820338,-1.83924 l 28.1119,-27.60268 A 4.7625255,4.7625255 0 0 0 57.81923,99.361053 l -2.599587,-2.292665 22.247698,-7.287245 a 4.7625255,4.7625255 0 0 0 1.825294,-7.952241 L 49.136863,52.712503 a 4.7625255,4.7625255 0 0 0 -6.78034,0.169743 l -2.004341,2.139208 -5.892099,-21.208335 20.694446,5.615409 -1.888074,2.022943 a 4.7625255,4.7625255 0 0 0 0.186017,6.687333 l 24.956575,23.917203 a 4.7625255,4.7625255 0 1 0 6.589682,-6.875673 L 63.43696,44.513789 67.508423,40.151669 A 4.7625255,4.7625255 0 0 0 65.273891,32.306383 L 28.879577,22.428849 A 4.7625255,4.7625255 0 0 0 27.46584,22.263761 Z"
id="path4226-8-2-6-6" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#616161;stroke-width:1.19049227;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 132.41008,26.037435 105.3329,53.112292 101.4056,48.62462 c -2.471624,-2.822907 -7.085146,-1.812007 -8.149908,1.785769 l -19.564388,66.152501 -1.097495,-1.09752 c -1.848175,-1.84883 -4.841177,-1.86232 -6.705942,-0.0302 l -30.155775,29.63492 c -4.528746,4.45048 2.146195,11.24399 6.675717,6.79429 l 26.788847,-26.32845 3.422719,3.42272 c 2.554424,2.55284 6.909691,1.44488 7.933653,-2.0183 l 19.38303,-65.531655 1.581152,1.806699 c 1.81388,2.074051 5.004,2.180744 6.9524,0.232518 l 29.27916,-29.281484 c 3.09867,-8.354063 -5.33869,-8.128961 -5.33869,-8.128961 z"
id="path4228-56-5-7-7" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#616161;stroke-width:1.19049227;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 148.50756,42.792967 a 4.7625255,4.7625255 0 0 0 -3.28786,1.40909 L 117.145,71.758228 a 4.7625255,4.7625255 0 0 0 0.479,7.208183 l 1.20214,0.902182 -18.12741,6.396682 a 4.7625255,4.7625255 0 0 0 -1.750895,7.889466 l 27.037655,26.516799 a 4.7625255,4.7625255 0 0 0 6.46877,0.1837 l 2.62052,-2.29268 6.10835,21.98728 -21.22925,-5.97814 2.44147,-2.60424 a 4.7625255,4.7625255 0 0 0 -0.17904,-6.69431 L 97.748019,101.82332 a 4.7625255,4.7625255 0 0 0 -3.783125,-1.79275 4.7625255,4.7625255 0 0 0 -3.294832,8.20105 l 21.554778,20.65725 -4.57603,4.88296 a 4.7625255,4.7625255 0 0 0 2.18106,7.84064 l 36.91518,10.39839 a 4.7625255,4.7625255 0 0 0 5.88047,-5.85723 l -10.39837,-37.43604 a 4.7625255,4.7625255 0 0 0 -7.72438,-2.30894 l -4.99922,4.37374 -18.43434,-18.078586 19.31559,-6.815219 a 4.7625255,4.7625255 0 0 0 1.2719,-8.301026 l -3.88544,-2.913505 24.12182,-23.673064 a 4.7625255,4.7625255 0 0 0 -3.38552,-8.208023 z"
id="path4230-2-7-5-5" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4787"
viewBox="0 0 134.45085 132.89714"
height="37.506527mm"
width="37.945019mm">
<defs
id="defs4789" />
<metadata
id="metadata4792">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-209.57079,-310.09781)"
id="layer1">
<rect
ry="23.230007"
y="311.09781"
x="210.57079"
height="130.89714"
width="132.45085"
id="rect4148"
style="opacity:1;fill:#e21d38;fill-opacity:1;stroke:#aa1604;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path4153-1"
d="m 277.13853,322.42496 a 11.194822,11.194822 0 0 0 -11.19647,11.19273 11.194822,11.194822 0 0 0 8.28772,10.79915 l 0,16.79285 a 19.191829,19.191829 0 0 0 -13.89534,10.21441 l -15.70583,-4.77173 a 11.194822,11.194822 0 0 0 0.0826,-1.28943 11.194822,11.194822 0 0 0 -11.19274,-11.19273 11.194822,11.194822 0 0 0 -11.19646,11.19273 11.194822,11.194822 0 0 0 11.19646,11.19648 11.194822,11.194822 0 0 0 8.94372,-4.48309 l 16.04691,4.87667 a 19.191829,19.191829 0 0 0 -0.28486,3.1824 19.191829,19.191829 0 0 0 5.97869,13.88784 l -10.51802,12.15983 a 11.194822,11.194822 0 0 0 -3.68845,-0.63723 11.194822,11.194822 0 0 0 -11.19646,11.19275 11.194822,11.194822 0 0 0 11.19646,11.19647 11.194822,11.194822 0 0 0 11.19276,-11.19647 11.194822,11.194822 0 0 0 -2.67637,-7.24193 l 10.49926,-12.13358 a 19.191829,19.191829 0 0 0 8.40394,1.96416 19.191829,19.191829 0 0 0 7.75172,-1.66055 l 10.61547,13.69292 a 11.194822,11.194822 0 0 0 -1.97166,6.34982 11.194822,11.194822 0 0 0 11.19275,11.19273 11.194822,11.194822 0 0 0 11.19646,-11.19273 11.194822,11.194822 0 0 0 -11.19646,-11.1965 11.194822,11.194822 0 0 0 -4.50559,0.96335 L 290.3031,394.32686 a 19.191829,19.191829 0 0 0 6.30107,-14.19146 19.191829,19.191829 0 0 0 -0.34857,-3.57224 l 15.07983,-5.33397 a 11.194822,11.194822 0 0 0 9.66338,5.57386 11.194822,11.194822 0 0 0 11.1965,-11.19647 11.194822,11.194822 0 0 0 -11.1965,-11.19273 11.194822,11.194822 0 0 0 -11.19276,11.19273 11.194822,11.194822 0 0 0 0.004,0.10495 l -15.43969,5.46143 a 19.191829,19.191829 0 0 0 -14.08245,-9.98951 l 0,-16.83034 a 11.194822,11.194822 0 0 0 8.04407,-10.73543 11.194822,11.194822 0 0 0 -11.19273,-11.19273 z"
style="opacity:1;fill:#f9fbfa;fill-opacity:1;stroke:#4f4f4f;stroke-width:2.00758219;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4137"
viewBox="0 0 134.45085 132.89714"
height="37.506527mm"
width="37.945019mm">
<defs
id="defs4139" />
<metadata
id="metadata4142">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-215.63172,-131.62792)"
id="layer1">
<g
id="layer2"
transform="translate(-95.716733,26.567293)">
<rect
style="opacity:1;fill:#e21d38;fill-opacity:1;stroke:#aa1604;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4148"
width="132.45085"
height="130.89714"
x="312.34845"
y="106.06062"
ry="23.230007" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#aa1604;stroke-width:2.36471558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 377.08683,115.67351 c -1.36466,0.0335 -2.7184,0.60584 -3.62707,1.65638 l -15.74601,18.20449 c -1.5508,1.79293 -1.20076,4.27909 0.78482,5.57411 l 0.28577,0.18625 c 1.98558,1.29502 4.83254,0.89434 6.38334,-0.8986 l 7.11447,-8.22561 0,21.32275 c 0,0.20988 0.01,0.41684 0.0274,0.62072 a 21.493014,21.261909 0 0 0 -14.33219,10.79411 l -20.08768,-7.00293 11.4599,-4.54634 c 2.20351,-0.87416 3.51887,-3.43061 2.94965,-5.73182 l -0.0819,-0.33121 c -0.56922,-2.30121 -2.8017,-3.45017 -5.00521,-2.57601 l -22.37305,8.87599 c -1.38479,0.54937 -2.41787,1.76343 -2.8539,3.16069 -0.003,0.007 -0.007,0.0144 -0.009,0.0218 -0.51786,1.3605 -0.45931,2.94379 0.27976,4.2279 l 12.00665,20.86086 c 1.1825,2.05458 3.64545,2.54248 5.52191,1.09387 l 0.27,-0.20841 c 1.87646,-1.44861 2.43521,-4.26868 1.2527,-6.32326 l -5.4254,-9.42574 19.61716,6.83883 a 21.493014,21.261909 0 0 0 -0.036,1.01464 21.493014,21.261909 0 0 0 16.93337,20.75159 l 0,10.1779 a 13.00356,12.863737 0 0 0 -8.19256,11.94581 13.00356,12.863737 0 0 0 13.00325,12.86356 13.00356,12.863737 0 0 0 13.00364,-12.86356 13.00356,12.863737 0 0 0 -8.75058,-12.14108 l 0,-9.95485 a 21.493014,21.261909 0 0 0 16.98895,-20.77937 21.493014,21.261909 0 0 0 -0.0867,-1.71046 l 21.10982,-7.46106 -6.10358,10.7115 c -1.17368,2.05963 -0.60292,4.87745 1.27974,6.318 l 0.27075,0.20728 c 1.88266,1.44055 4.34373,0.94211 5.51741,-1.11752 l 11.91652,-20.91231 c 0.7376,-1.29438 0.78532,-2.88794 0.25235,-4.25118 -0.002,-0.008 -0.004,-0.015 -0.006,-0.0225 -0.44611,-1.38568 -1.48151,-2.58496 -2.86103,-3.12539 l -22.41139,-8.77992 c -2.20723,-0.86469 -4.43461,0.29379 -4.99394,2.59742 l -0.0804,0.33158 c -0.55934,2.30364 0.7672,4.85436 2.97444,5.71906 l 10.12607,3.96692 -19.75422,6.98191 a 21.493014,21.261909 0 0 0 -14.09037,-10.20606 c 0.0172,-0.20071 0.0263,-0.40446 0.0263,-0.61096 l 0,-22.4215 8.06526,9.32436 c 1.5508,1.79294 4.39776,2.19362 6.38334,0.8986 l 0.28577,-0.18625 c 1.98558,-1.29502 2.33562,-3.78118 0.78482,-5.57411 l -15.746,-18.20449 c -0.9746,-1.12677 -2.46119,-1.70274 -3.92411,-1.65451 -0.008,-1.9e-4 -0.0157,-0.001 -0.0237,-0.001 -0.0909,-0.003 -0.18202,-0.003 -0.273,-3.7e-4 z m -0.13105,46.3195 a 13.00356,12.863737 0 0 1 13.00326,12.86394 13.00356,12.863737 0 0 1 -13.00326,12.86356 13.00356,12.863737 0 0 1 -13.00363,-12.86356 13.00356,12.863737 0 0 1 13.00363,-12.86394 z"
id="path4150" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 257.79912 103.53112"
height="29.218782mm"
width="72.756638mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
ry="1.058059"
y="144.38962"
x="178.69905"
height="44.737236"
width="114.87411"
id="rect4136-2-3-3-1-5"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)" />
<rect
ry="0.92879671"
y="57.148861"
x="1.663313"
height="44.718945"
width="153.66602"
id="rect4136-2-3-3-1"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<g
transform="matrix(1.0767436,0,-0.69071933,0.38775698,100.42788,0)"
id="g840">
<g
transform="translate(-310.19216,-103.4594)"
id="layer1" />
<rect
ry="0"
y="2.8232944"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.91520715;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 73.032665,18.093584 c -1.306805,0.03208 -2.603151,0.580155 -3.473297,1.586156 L 54.480932,37.112425 c -1.485051,1.716916 -1.149852,4.097672 0.751548,5.337788 l 0.273654,0.178353 c 1.901399,1.240116 4.627658,0.856423 6.112709,-0.860502 l 6.812842,-7.876874 v 20.418742 c 0,0.200982 0.0096,0.399167 0.02624,0.594404 A 20.581788,20.360481 0 0 0 54.733366,65.240814 L 35.497332,58.534783 46.471373,54.181192 c 2.110089,-0.837099 3.369683,-3.285165 2.824596,-5.488812 l -0.07843,-0.317168 c -0.545085,-2.203647 -2.682915,-3.303895 -4.793004,-2.466796 l -21.424514,8.49968 c -1.326079,0.526079 -2.315361,1.688667 -2.732904,3.026688 -0.0029,0.0067 -0.0067,0.01379 -0.0086,0.02088 -0.495904,1.30282 -0.439837,2.818984 0.267899,4.048652 l 11.497612,19.97644 c 1.132366,1.967474 3.490895,2.434689 5.2878,1.047494 l 0.258553,-0.199574 c 1.796905,-1.387194 2.331966,-4.087703 1.19959,-6.055176 l -5.195382,-9.026129 18.785463,6.548885 a 20.581788,20.360481 0 0 0 -0.03447,0.971632 20.581788,20.360481 0 0 0 16.215455,19.871798 v 9.746384 a 12.452256,12.318361 0 0 0 -7.845225,11.43935 12.452256,12.318361 0 0 0 12.45196,12.31819 12.452256,12.318361 0 0 0 12.452332,-12.31819 12.452256,12.318361 0 0 0 -8.379587,-11.62634 v -9.532792 a 20.581788,20.360481 0 0 0 16.26868,-19.8984 20.581788,20.360481 0 0 0 -0.08302,-1.637952 l 20.214833,-7.144734 -5.84481,10.257377 c -1.12392,1.972309 -0.57736,4.670663 1.22548,6.050139 l 0.25928,0.198492 c 1.80284,1.379476 4.15957,0.902168 5.28349,-1.070141 l 11.4113,-20.025709 c 0.70633,-1.239503 0.75203,-2.765502 0.24165,-4.070945 -0.002,-0.0077 -0.004,-0.01436 -0.006,-0.02155 -0.4272,-1.326933 -1.4187,-2.475367 -2.73974,-2.992885 l -21.46099,-8.407683 c -2.113652,-0.82803 -4.246594,0.281334 -4.782211,2.487299 l -0.07699,0.317522 c -0.535626,2.205974 0.734673,4.648552 2.848329,5.476592 l 9.696752,3.798737 -18.916698,6.685902 A 20.581788,20.360481 0 0 0 77.266862,54.89499 c 0.01647,-0.192201 0.02519,-0.387313 0.02519,-0.585058 V 32.839023 l 7.723322,8.929041 c 1.485051,1.716925 4.211311,2.100618 6.112709,0.860502 l 0.27365,-0.178353 c 1.901398,-1.240116 2.236598,-3.620872 0.751546,-5.337788 L 77.074853,19.67974 c -0.93328,-1.078999 -2.356844,-1.63055 -3.757742,-1.584365 -0.0077,-1.82e-4 -0.01503,-9.58e-4 -0.02269,-9.58e-4 -0.08705,-0.0029 -0.174303,-0.0029 -0.261426,-3.54e-4 z M 72.90717,62.449304 A 12.452256,12.318361 0 0 1 85.359138,74.767865 12.452256,12.318361 0 0 1 72.90717,87.086056 12.452256,12.318361 0 0 1 60.454847,74.767865 12.452256,12.318361 0 0 1 72.90717,62.449304 Z"
id="path4150" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg2"
viewBox="0 0 146.47219 146.23581"
height="41.270996mm"
width="41.337708mm">
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-310.19216,-103.4594)"
id="layer1" />
<rect
ry="8.9952965"
y="2.8232944"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.91520715;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 73.032665,18.093584 c -1.306805,0.03208 -2.603151,0.580155 -3.473297,1.586156 L 54.480932,37.112425 c -1.485051,1.716916 -1.149852,4.097672 0.751548,5.337788 l 0.273654,0.178353 c 1.901399,1.240116 4.627658,0.856423 6.112709,-0.860502 l 6.812842,-7.876874 v 20.418742 c 0,0.200982 0.0096,0.399167 0.02624,0.594404 A 20.581788,20.360481 0 0 0 54.733366,65.240814 L 35.497332,58.534783 46.471373,54.181192 c 2.110089,-0.837099 3.369683,-3.285165 2.824596,-5.488812 l -0.07843,-0.317168 c -0.545085,-2.203647 -2.682915,-3.303895 -4.793004,-2.466796 l -21.424514,8.49968 c -1.326079,0.526079 -2.315361,1.688667 -2.732904,3.026688 -0.0029,0.0067 -0.0067,0.01379 -0.0086,0.02088 -0.495904,1.30282 -0.439837,2.818984 0.267899,4.048652 l 11.497612,19.97644 c 1.132366,1.967474 3.490895,2.434689 5.2878,1.047494 l 0.258553,-0.199574 c 1.796905,-1.387194 2.331966,-4.087703 1.19959,-6.055176 l -5.195382,-9.026129 18.785463,6.548885 a 20.581788,20.360481 0 0 0 -0.03447,0.971632 20.581788,20.360481 0 0 0 16.215455,19.871798 v 9.746384 a 12.452256,12.318361 0 0 0 -7.845225,11.43935 12.452256,12.318361 0 0 0 12.45196,12.31819 12.452256,12.318361 0 0 0 12.452332,-12.31819 12.452256,12.318361 0 0 0 -8.379587,-11.62634 v -9.532792 a 20.581788,20.360481 0 0 0 16.26868,-19.8984 20.581788,20.360481 0 0 0 -0.08302,-1.637952 l 20.214833,-7.144734 -5.84481,10.257377 c -1.12392,1.972309 -0.57736,4.670663 1.22548,6.050139 l 0.25928,0.198492 c 1.80284,1.379476 4.15957,0.902168 5.28349,-1.070141 l 11.4113,-20.025709 c 0.70633,-1.239503 0.75203,-2.765502 0.24165,-4.070945 -0.002,-0.0077 -0.004,-0.01436 -0.006,-0.02155 -0.4272,-1.326933 -1.4187,-2.475367 -2.73974,-2.992885 l -21.46099,-8.407683 c -2.113652,-0.82803 -4.246594,0.281334 -4.782211,2.487299 l -0.07699,0.317522 c -0.535626,2.205974 0.734673,4.648552 2.848329,5.476592 l 9.696752,3.798737 -18.916698,6.685902 A 20.581788,20.360481 0 0 0 77.266862,54.89499 c 0.01647,-0.192201 0.02519,-0.387313 0.02519,-0.585058 V 32.839023 l 7.723322,8.929041 c 1.485051,1.716925 4.211311,2.100618 6.112709,0.860502 l 0.27365,-0.178353 c 1.901398,-1.240116 2.236598,-3.620872 0.751546,-5.337788 L 77.074853,19.67974 c -0.93328,-1.078999 -2.356844,-1.63055 -3.757742,-1.584365 -0.0077,-1.82e-4 -0.01503,-9.58e-4 -0.02269,-9.58e-4 -0.08705,-0.0029 -0.174303,-0.0029 -0.261426,-3.54e-4 z M 72.90717,62.449304 A 12.452256,12.318361 0 0 1 85.359138,74.767865 12.452256,12.318361 0 0 1 72.90717,87.086056 12.452256,12.318361 0 0 1 60.454847,74.767865 12.452256,12.318361 0 0 1 72.90717,62.449304 Z"
id="path4150" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg2"
viewBox="0 0 146.47219 146.23581"
height="41.270996mm"
width="41.337708mm">
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-310.19216,-103.4594)"
id="layer1" />
<rect
ry="8.9952965"
y="2.8232944"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3"
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.91520727;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 73.03267,18.093578 c -1.306804,0.03207 -2.603151,0.580155 -3.473297,1.586156 L 54.480938,37.112419 c -1.485052,1.716916 -1.149852,4.097672 0.751547,5.337787 l 0.273654,0.178354 c 1.901399,1.240116 4.627658,0.856424 6.112709,-0.860503 l 6.812842,-7.876874 v 20.418744 c 0,0.200981 0.0096,0.399166 0.02624,0.594402 A 20.581787,20.360481 0 0 0 54.733372,65.240809 L 35.497337,58.534777 46.471378,54.181185 c 2.110089,-0.837098 3.369683,-3.285164 2.824596,-5.48881 l -0.07843,-0.317169 C 48.672457,46.17156 46.534627,45.071311 44.424538,45.90841 l -21.424513,8.49968 c -1.326081,0.526078 -2.315361,1.688667 -2.732905,3.026689 -0.0029,0.0067 -0.0067,0.01379 -0.0086,0.02088 -0.495904,1.30282 -0.439837,2.818985 0.2679,4.048654 l 11.497611,19.976439 c 1.132365,1.967473 3.490894,2.434688 5.2878,1.047494 l 0.258554,-0.199574 c 1.796904,-1.387194 2.331965,-4.087703 1.199588,-6.055176 l -5.195381,-9.026129 18.785463,6.548885 a 20.581787,20.360481 0 0 0 -0.03448,0.971633 20.581787,20.360481 0 0 0 16.215456,19.871796 v 9.746389 a 12.452256,12.318361 0 0 0 -7.845226,11.43935 12.452256,12.318361 0 0 0 12.451961,12.3182 12.452256,12.318361 0 0 0 12.452331,-12.3182 12.452256,12.318361 0 0 0 -8.379587,-11.62634 v -9.532796 a 20.581787,20.360481 0 0 0 16.268679,-19.898399 20.581787,20.360481 0 0 0 -0.08302,-1.637953 l 20.214851,-7.144735 -5.84482,10.257377 c -1.12392,1.97231 -0.57736,4.670665 1.22548,6.05014 l 0.25927,0.198492 c 1.80285,1.379477 4.15957,0.902169 5.28349,-1.070141 l 11.41131,-20.02571 c 0.70633,-1.239502 0.75203,-2.765501 0.24166,-4.070944 -0.002,-0.0076 -0.004,-0.01436 -0.006,-0.02155 -0.4272,-1.326932 -1.41871,-2.475367 -2.73973,-2.992885 l -21.46123,-8.407683 c -2.113653,-0.828031 -4.246602,0.281334 -4.782219,2.487299 l -0.07699,0.317522 c -0.535626,2.205974 0.734674,4.648553 2.848326,5.476593 l 9.696763,3.798736 -18.916705,6.685902 A 20.581787,20.360481 0 0 0 77.266867,54.894984 c 0.01647,-0.192201 0.02518,-0.387313 0.02518,-0.585057 v -21.47091 l 7.723323,8.92904 c 1.48505,1.716927 4.21131,2.100619 6.112709,0.860503 l 0.273654,-0.178354 c 1.901399,-1.240115 2.236598,-3.620871 0.751547,-5.337787 L 77.074858,19.679734 c -0.93328,-1.078999 -2.356844,-1.630551 -3.757742,-1.584365 -0.0076,-1.82e-4 -0.01503,-9.58e-4 -0.0227,-9.58e-4 -0.08705,-0.0029 -0.174303,-0.0029 -0.261426,-3.54e-4 z m -0.125495,44.35572 A 12.452256,12.318361 0 0 1 85.359144,74.767859 12.452256,12.318361 0 0 1 72.907175,87.086051 12.452256,12.318361 0 0 1 60.454852,74.767859 12.452256,12.318361 0 0 1 72.907175,62.449298 Z"
id="path4150" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4294"
viewBox="0 0 477.15656 123.40902"
height="34.828766mm"
width="134.66418mm">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
ry="7.544374"
y="2.7482815"
x="2.7482815"
height="117.91245"
width="471.66003"
id="rect4136-2-3-9"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:4.83579502;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
ry="6.2240515"
style="fill:#ffffff;stroke:#0c3c86;stroke-width:3.29919542;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
y="22.093039"
x="24.019857"
height="79.222946"
width="325.79764"
id="rect3729-3-6-3" />
<rect
ry="2.1987813"
y="21.959221"
x="367.10852"
height="79.688713"
width="86.592178"
id="rect6782-4-65-3-0-6"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:4.6875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<g
style="stroke:#2c2c2c;stroke-opacity:1"
transform="translate(-118.88555,-54.925258)"
id="g931">
<rect
y="77.126175"
x="486.69263"
height="16.334656"
width="85.19503"
id="rect5963-5"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="139.99684"
x="486.69263"
height="16.334656"
width="85.19503"
id="rect5963-6-3"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="108.76521"
x="486.69263"
height="16.238991"
width="29.59816"
id="rect5963-6-2-5"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.580429"
x="500.47913"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-6"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.631813"
x="486.69263"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-2"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.580429"
x="528.98602"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-9"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.631813"
x="557.95044"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-9-1"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="108.76521"
x="542.28949"
height="16.238991"
width="29.59816"
id="rect5963-6-2-2-2"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.47609"
x="500.4993"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-8-7"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.39251"
x="486.7157"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-97-0"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.47609"
x="529.00616"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-3-9"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.39251"
x="557.93237"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-9-6-3"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="108.74097"
x="516.32239"
height="16.287457"
width="25.935509"
id="rect5963-6-2-1-6"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4294"
viewBox="0 0 476.49581 122.74825"
height="34.642281mm"
width="134.47771mm">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-0.33038402,-0.33038402)"
id="g898">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:4.83579493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-9"
width="471.66003"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.544374" />
<rect
id="rect3729-3-6-3"
width="325.79764"
height="79.222946"
x="24.019857"
y="22.093035"
style="fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.29919553;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
ry="6.2240515" />
<g
transform="matrix(0.57098342,0,0,0.57802428,365.48396,19.372268)"
id="g869">
<rect
ry="3.816956"
y="4.0686131"
x="4.0686092"
height="138.33496"
width="150.31897"
id="rect6782-4-65-3-0-6-3"
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:8.13722706;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-5-6"
width="147.8936"
height="28.35601"
x="5.2812467"
y="4.4881825" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-3-7"
width="147.8936"
height="28.35601"
x="5.2812467"
y="113.628" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-5-5"
width="51.38068"
height="28.189941"
x="5.2812467"
y="59.411709" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-6-3"
width="49.467712"
height="25.67437"
x="29.213806"
y="33.051807" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-2-5"
width="24.194138"
height="25.49596"
x="5.2812467"
y="33.141006" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-9-6"
width="49.467712"
height="25.67437"
x="78.700119"
y="33.051807" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-1-2"
width="24.194138"
height="25.49596"
x="128.98065"
y="33.141006" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-2-2-9"
width="51.38068"
height="28.189941"
x="101.79416"
y="59.411709" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-8-7-1"
width="49.467712"
height="25.67437"
x="29.248823"
y="88.420822" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-97-0-2"
width="24.184027"
height="25.964542"
x="5.3212972"
y="88.275734" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-3-9-7"
width="49.467712"
height="25.67437"
x="78.735085"
y="88.420822" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-6-3-0"
width="24.184027"
height="25.964542"
x="128.9493"
y="88.275734" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-1-6-9"
width="45.02253"
height="28.274075"
x="56.716785"
y="59.369617" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="134.66418mm"
height="34.828766mm"
viewBox="0 0 477.15656 123.40902"
id="svg4294"
version="1.1">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:4.83579502;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-9"
width="471.66003"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.544374" />
<rect
id="rect3729-3-6-3"
width="325.79764"
height="79.222946"
x="24.019857"
y="22.093039"
style="fill:#ffffff;stroke:#0c3c86;stroke-width:3.29919542;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
ry="6.2240515" />
<rect
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#0c3c86;stroke-width:4.6875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect6782-4-65-3-0-6"
width="86.592178"
height="79.688713"
x="367.10852"
y="21.959221"
ry="2.1987813" />
<rect
y="22.200916"
x="367.80707"
height="16.334656"
width="85.19503"
id="rect5963-5"
style="opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="85.071587"
x="367.80707"
height="16.334656"
width="85.19503"
id="rect5963-6-3"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="53.839954"
x="367.80707"
height="16.238991"
width="29.59816"
id="rect5963-6-2-5"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.65517"
x="381.59357"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-6"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.706554"
x="367.80707"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-2"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.65517"
x="410.10046"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-9"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.706554"
x="439.06488"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-9-1"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="53.839954"
x="423.40393"
height="16.238991"
width="29.59816"
id="rect5963-6-2-2-2"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.550835"
x="381.61374"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-8-7"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.467255"
x="367.83014"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-97-0"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.550835"
x="410.12061"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-3-9"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.467255"
x="439.04681"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-9-6-3"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="53.815708"
x="397.43683"
height="16.287457"
width="25.935509"
id="rect5963-6-2-1-6"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="134.47771mm"
height="34.642281mm"
viewBox="0 0 476.49581 122.74825"
id="svg4294"
version="1.1">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:4.83579493;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-9"
width="471.66003"
height="117.91245"
x="2.4178975"
y="2.4178975"
ry="7.544374" />
<rect
id="rect3729-3-6-3"
width="325.79764"
height="79.222946"
x="23.689474"
y="21.762651"
style="fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.29919553;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
ry="6.2240515" />
<g
transform="translate(-31.124585,128.44941)"
id="g938">
<rect
ry="2.198781"
y="-106.91965"
x="398.3342"
height="79.688713"
width="86.592171"
id="rect6782-4-65-3-0-6"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:4.68749952;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<g
style="stroke:#2c2c2c;stroke-opacity:1"
transform="translate(-87.659869,-183.80413)"
id="g931">
<rect
y="77.126175"
x="486.69263"
height="16.334656"
width="85.19503"
id="rect5963-5"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="139.99684"
x="486.69263"
height="16.334656"
width="85.19503"
id="rect5963-6-3"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="108.76521"
x="486.69263"
height="16.238991"
width="29.59816"
id="rect5963-6-2-5"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.580429"
x="500.47913"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-6"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.631813"
x="486.69263"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-2"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.580429"
x="528.98602"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-9"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="93.631813"
x="557.95044"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-9-1"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="108.76521"
x="542.28949"
height="16.238991"
width="29.59816"
id="rect5963-6-2-2-2"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.47609"
x="500.4993"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-8-7"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.39251"
x="486.7157"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-97-0"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.47609"
x="529.00616"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-3-9"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="125.39251"
x="557.93237"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-9-6-3"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="108.74097"
x="516.32239"
height="16.287457"
width="25.935509"
id="rect5963-6-2-1-6"
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="72.756638mm"
height="29.378555mm"
viewBox="0 0 257.79912 104.09725"
id="svg7778"
version="1.1"
sodipodi:docname="generic-firewall-v2-colour-3d.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2017"
inkscape:window-height="1713"
id="namedview10"
showgrid="false"
inkscape:zoom="3.0259348"
inkscape:cx="65.911355"
inkscape:cy="27.710951"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7778"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)"
style="opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1-5"
width="114.87411"
height="44.737236"
x="178.69905"
y="144.95575"
ry="1.058059" />
<rect
style="opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1"
width="153.66602"
height="44.718945"
x="1.663313"
y="57.714989"
ry="0.92879671" />
<g
id="g845"
transform="matrix(1.7930508,0,-1.1914402,0.68483634,-533.33826,-13.433387)">
<rect
ry="2.1987813"
y="21.959221"
x="367.10852"
height="79.688713"
width="86.592178"
id="rect6782-4-65-3-0-6"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#0c3c86;stroke-width:4.6875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-5"
width="85.19503"
height="16.334656"
x="367.80707"
y="22.200916" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-3"
width="85.19503"
height="16.334656"
x="367.80707"
y="85.071587" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-5"
width="29.59816"
height="16.238991"
x="367.80707"
y="53.839954" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-6"
width="28.496183"
height="14.78988"
x="381.59357"
y="38.65517" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-2"
width="13.937183"
height="14.687106"
x="367.80707"
y="38.706554" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-9"
width="28.496183"
height="14.78988"
x="410.10046"
y="38.65517" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-1"
width="13.937183"
height="14.687106"
x="439.06488"
y="38.706554" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-2-2"
width="29.59816"
height="16.238991"
x="423.40393"
y="53.839954" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-8-7"
width="28.496183"
height="14.78988"
x="381.61374"
y="70.550835" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-97-0"
width="13.931359"
height="14.957036"
x="367.83014"
y="70.467255" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-3-9"
width="28.496183"
height="14.78988"
x="410.12061"
y="70.550835" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-6-3"
width="13.931359"
height="14.957036"
x="439.04681"
y="70.467255" />
<rect
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-1-6"
width="25.935509"
height="16.287457"
x="397.43683"
y="53.815708" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg4294"
viewBox="0 0 158.4562 146.47219"
height="41.337704mm"
width="44.71986mm">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="matrix(1.7359417,0,0,1.7359417,-633.21038,-34.051314)"
id="g845">
<rect
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#0c3c86;stroke-width:4.6875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect6782-4-65-3-0-6"
width="86.592178"
height="79.688713"
x="367.10852"
y="21.959221"
ry="2.1987813" />
<rect
y="22.200916"
x="367.80707"
height="16.334656"
width="85.19503"
id="rect5963-5"
style="opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="85.071587"
x="367.80707"
height="16.334656"
width="85.19503"
id="rect5963-6-3"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="53.839954"
x="367.80707"
height="16.238991"
width="29.59816"
id="rect5963-6-2-5"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.65517"
x="381.59357"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-6"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.706554"
x="367.80707"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-2"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.65517"
x="410.10046"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-9"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="38.706554"
x="439.06488"
height="14.687106"
width="13.937183"
id="rect5963-6-2-9-2-9-1"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="53.839954"
x="423.40393"
height="16.238991"
width="29.59816"
id="rect5963-6-2-2-2"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.550835"
x="381.61374"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-8-7"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.467255"
x="367.83014"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-97-0"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.550835"
x="410.12061"
height="14.78988"
width="28.496183"
id="rect5963-6-2-9-3-3-9"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="70.467255"
x="439.04681"
height="14.957036"
width="13.931359"
id="rect5963-6-2-9-2-9-6-3"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="53.815708"
x="397.43683"
height="16.287457"
width="25.935509"
id="rect5963-6-2-1-6"
style="display:inline;opacity:1;fill:#828282;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="44.719856mm"
height="41.337704mm"
viewBox="0 0 158.45619 146.47219"
id="svg4294"
version="1.1">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(4.2915344e-6)"
id="g869">
<rect
ry="3.816956"
y="4.0686131"
x="4.0686092"
height="138.33496"
width="150.31897"
id="rect6782-4-65-3-0-6"
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:8.13722706;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-5"
width="147.8936"
height="28.35601"
x="5.2812467"
y="4.4881825" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-3"
width="147.8936"
height="28.35601"
x="5.2812467"
y="113.628" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-5"
width="51.38068"
height="28.189941"
x="5.2812467"
y="59.411709" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-6"
width="49.467712"
height="25.67437"
x="29.213806"
y="33.051807" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-2"
width="24.194138"
height="25.49596"
x="5.2812467"
y="33.141006" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-9"
width="49.467712"
height="25.67437"
x="78.700119"
y="33.051807" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-1"
width="24.194138"
height="25.49596"
x="128.98065"
y="33.141006" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-2-2"
width="51.38068"
height="28.189941"
x="101.79416"
y="59.411709" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-8-7"
width="49.467712"
height="25.67437"
x="29.248823"
y="88.420822" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-97-0"
width="24.184027"
height="25.964542"
x="5.3212972"
y="88.275734" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-3-9"
width="49.467712"
height="25.67437"
x="78.735085"
y="88.420822" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-6-3"
width="24.184027"
height="25.964542"
x="128.9493"
y="88.275734" />
<rect
style="display:inline;opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:6.50978184;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-1-6"
width="45.02253"
height="28.274075"
x="56.716785"
y="59.369617" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 257.79912 103.13295"
height="29.106411mm"
width="72.756638mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
ry="1.058059"
y="143.99146"
x="178.69905"
height="44.737236"
width="114.87411"
id="rect4136-2-3-3-1-5"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)" />
<rect
ry="0.92879671"
y="56.750698"
x="1.663313"
height="44.718945"
width="153.66602"
id="rect4136-2-3-3-1"
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g70"
transform="matrix(1.7449076,0,-1.1901581,0.66860356,-209.97425,-76.364166)">
<rect
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:4.68749952;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect6782-4-65-3-0-6"
width="86.592171"
height="79.688713"
x="257.57465"
y="116.55817"
ry="0.23402384" />
<g
id="g931-3"
transform="translate(-228.41943,39.673689)"
style="stroke:#2c2c2c;stroke-opacity:1">
<rect
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-5"
width="85.19503"
height="16.334656"
x="486.69263"
y="77.126175" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-3"
width="85.19503"
height="16.334656"
x="486.69263"
y="139.99684" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-5"
width="29.59816"
height="16.238991"
x="486.69263"
y="108.76521" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-6"
width="28.496183"
height="14.78988"
x="500.47913"
y="93.580429" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-2"
width="13.937183"
height="14.687106"
x="486.69263"
y="93.631813" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-9"
width="28.496183"
height="14.78988"
x="528.98602"
y="93.580429" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-1"
width="13.937183"
height="14.687106"
x="557.95044"
y="93.631813" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-2-2"
width="29.59816"
height="16.238991"
x="542.28949"
y="108.76521" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-8-7"
width="28.496183"
height="14.78988"
x="500.4993"
y="125.47609" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-97-0"
width="13.931359"
height="14.957036"
x="486.7157"
y="125.39251" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-3-9"
width="28.496183"
height="14.78988"
x="529.00616"
y="125.47609" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-6-3"
width="13.931359"
height="14.957036"
x="557.93237"
y="125.39251" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-1-6"
width="25.935509"
height="16.287457"
x="516.32239"
y="108.74097" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="44.763035mm"
height="41.305332mm"
viewBox="0 0 158.60918 146.35749"
id="svg4294"
version="1.1">
<defs
id="defs4296" />
<metadata
id="metadata4299">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g70"
transform="matrix(1.7376179,0,0,1.7345823,-443.49377,-198.11431)">
<rect
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:4.68749952;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect6782-4-65-3-0-6"
width="86.592171"
height="79.688713"
x="257.57465"
y="116.55817"
ry="2.1987813" />
<g
id="g931-3"
transform="translate(-228.41943,39.673689)"
style="stroke:#2c2c2c;stroke-opacity:1">
<rect
style="opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-5"
width="85.19503"
height="16.334656"
x="486.69263"
y="77.126175" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-3"
width="85.19503"
height="16.334656"
x="486.69263"
y="139.99684" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-5"
width="29.59816"
height="16.238991"
x="486.69263"
y="108.76521" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-6"
width="28.496183"
height="14.78988"
x="500.47913"
y="93.580429" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-2"
width="13.937183"
height="14.687106"
x="486.69263"
y="93.631813" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-9"
width="28.496183"
height="14.78988"
x="528.98602"
y="93.580429" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-1"
width="13.937183"
height="14.687106"
x="557.95044"
y="93.631813" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-2-2"
width="29.59816"
height="16.238991"
x="542.28949"
y="108.76521" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-8-7"
width="28.496183"
height="14.78988"
x="500.4993"
y="125.47609" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-97-0"
width="13.931359"
height="14.957036"
x="486.7157"
y="125.39251" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-3-3-9"
width="28.496183"
height="14.78988"
x="529.00616"
y="125.47609" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-9-2-9-6-3"
width="13.931359"
height="14.957036"
x="557.93237"
y="125.39251" />
<rect
style="display:inline;opacity:1;fill:#d3453a;fill-opacity:1;stroke:#2c2c2c;stroke-width:3.75000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1"
id="rect5963-6-2-1-6"
width="25.935509"
height="16.287457"
x="516.32239"
y="108.74097" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 257.79912 103.25225"
height="29.140079mm"
width="72.756638mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
ry="1.058059"
y="144.11075"
x="178.69905"
height="44.737236"
width="114.87411"
id="rect4136-2-3-3-1-5"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)" />
<rect
ry="0.92879671"
y="56.869995"
x="1.663313"
height="44.718945"
width="153.66602"
id="rect4136-2-3-3-1"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<g
transform="matrix(1.0692696,0,-0.69788904,0.38537514,101.97779,0)"
id="g844">
<rect
ry="0"
y="2.8232937"
x="2.8232937"
height="140.58919"
width="140.82558"
id="rect4136-2-3-3-9"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.64658737;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.29441714;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 73.218317,21.079846 A 11.143052,11.143052 0 0 0 62.075163,32.223004 11.143052,11.143052 0 0 0 69.723288,42.794367 V 58.241171 A 18.905098,18.905098 0 0 0 56.786943,67.486253 L 41.357049,63.07264 A 11.143052,11.143052 0 0 0 30.227428,52.328722 11.143052,11.143052 0 0 0 19.084273,63.47188 11.143052,11.143052 0 0 0 30.227428,74.615031 11.143052,11.143052 0 0 0 39.408223,69.78526 l 15.120315,4.325648 a 18.905098,18.905098 0 0 0 -0.214844,2.69824 18.905098,18.905098 0 0 0 5.066608,12.856845 l -9.02516,13.888767 a 11.143052,11.143052 0 0 0 -3.807992,-0.68514 11.143052,11.143052 0 0 0 -11.143155,11.14316 11.143052,11.143052 0 0 0 11.143155,11.14315 11.143052,11.143052 0 0 0 11.143155,-11.14315 11.143052,11.143052 0 0 0 -1.827024,-6.107 L 65.0187,93.817389 a 18.905098,18.905098 0 0 0 8.199617,1.89639 18.905098,18.905098 0 0 0 8.537954,-2.040178 l 8.974409,13.810949 a 11.143052,11.143052 0 0 0 -2.126453,6.52823 11.143052,11.143052 0 0 0 11.143155,11.14315 11.143052,11.143052 0 0 0 11.143168,-11.14315 11.143052,11.143052 0 0 0 -11.143168,-11.14316 11.143052,11.143052 0 0 0 -3.337699,0.52442 L 87.30501,89.386861 A 18.905098,18.905098 0 0 0 92.12294,76.809148 18.905098,18.905098 0 0 0 91.892871,73.960347 L 106.97767,69.64485 a 11.143052,11.143052 0 0 0 9.26707,4.970181 A 11.143052,11.143052 0 0 0 127.38789,63.47188 11.143052,11.143052 0 0 0 116.24474,52.328722 11.143052,11.143052 0 0 0 105.13035,62.903473 L 89.563415,67.355994 A 18.905098,18.905098 0 0 0 76.713346,58.241171 V 42.789286 A 11.143052,11.143052 0 0 0 84.361472,32.223004 11.143052,11.143052 0 0 0 73.218317,21.079846 Z"
id="path829" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="41.3377mm"
height="41.270985mm"
viewBox="0 0 146.47216 146.23577"
id="svg4787"
version="1.1">
<defs
id="defs4789" />
<metadata
id="metadata4792">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g844">
<rect
ry="8.9952946"
y="2.8232937"
x="2.8232937"
height="140.58919"
width="140.82558"
id="rect4136-2-3-3"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.64658737;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.29441714;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 73.218317,21.079846 A 11.143052,11.143052 0 0 0 62.075163,32.223004 11.143052,11.143052 0 0 0 69.723288,42.794367 V 58.241171 A 18.905098,18.905098 0 0 0 56.786943,67.486253 L 41.357049,63.07264 A 11.143052,11.143052 0 0 0 30.227428,52.328722 11.143052,11.143052 0 0 0 19.084273,63.47188 11.143052,11.143052 0 0 0 30.227428,74.615031 11.143052,11.143052 0 0 0 39.408223,69.78526 l 15.120315,4.325648 a 18.905098,18.905098 0 0 0 -0.214844,2.69824 18.905098,18.905098 0 0 0 5.066608,12.856845 l -9.02516,13.888767 a 11.143052,11.143052 0 0 0 -3.807992,-0.68514 11.143052,11.143052 0 0 0 -11.143155,11.14316 11.143052,11.143052 0 0 0 11.143155,11.14315 11.143052,11.143052 0 0 0 11.143155,-11.14315 11.143052,11.143052 0 0 0 -1.827024,-6.107 L 65.0187,93.817389 a 18.905098,18.905098 0 0 0 8.199617,1.89639 18.905098,18.905098 0 0 0 8.537954,-2.040178 l 8.974409,13.810949 a 11.143052,11.143052 0 0 0 -2.126453,6.52823 11.143052,11.143052 0 0 0 11.143155,11.14315 11.143052,11.143052 0 0 0 11.143168,-11.14315 11.143052,11.143052 0 0 0 -11.143168,-11.14316 11.143052,11.143052 0 0 0 -3.337699,0.52442 L 87.30501,89.386861 A 18.905098,18.905098 0 0 0 92.12294,76.809148 18.905098,18.905098 0 0 0 91.892871,73.960347 L 106.97767,69.64485 a 11.143052,11.143052 0 0 0 9.26707,4.970181 A 11.143052,11.143052 0 0 0 127.38789,63.47188 11.143052,11.143052 0 0 0 116.24474,52.328722 11.143052,11.143052 0 0 0 105.13035,62.903473 L 89.563415,67.355994 A 18.905098,18.905098 0 0 0 76.713346,58.241171 V 42.789286 A 11.143052,11.143052 0 0 0 84.361472,32.223004 11.143052,11.143052 0 0 0 73.218317,21.079846 Z"
id="path829" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="41.3377mm"
height="41.270985mm"
viewBox="0 0 146.47216 146.23577"
id="svg4787"
version="1.1">
<defs
id="defs4789" />
<metadata
id="metadata4792">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.64658737;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="140.82558"
height="140.58919"
x="2.8232937"
y="2.8232937"
ry="8.9952946" />
<path
id="path829"
d="m 73.218247,20.874536 a 11.187016,11.187016 0 0 0 -11.187118,11.187122 11.187016,11.187016 0 0 0 7.6783,10.613071 V 58.182476 A 18.979685,18.979685 0 0 0 56.722045,67.464033 L 41.231274,63.033006 A 11.187016,11.187016 0 0 0 30.057743,52.246701 11.187016,11.187016 0 0 0 18.870624,63.433822 11.187016,11.187016 0 0 0 30.057743,74.620937 11.187016,11.187016 0 0 0 39.27476,69.772111 l 15.17997,4.342714 a 18.979685,18.979685 0 0 0 -0.215692,2.708886 18.979685,18.979685 0 0 0 5.086598,12.907569 l -9.060767,13.94357 a 11.187016,11.187016 0 0 0 -3.823016,-0.68784 11.187016,11.187016 0 0 0 -11.187119,11.18712 11.187016,11.187016 0 0 0 11.187119,11.18711 11.187016,11.187016 0 0 0 11.187118,-11.18711 11.187016,11.187016 0 0 0 -1.834232,-6.1311 L 64.98628,93.899055 a 18.979685,18.979685 0 0 0 8.231967,1.903872 18.979685,18.979685 0 0 0 8.571639,-2.048227 l 9.009816,13.86543 a 11.187016,11.187016 0 0 0 -2.134842,6.554 11.187016,11.187016 0 0 0 11.187118,11.18711 11.187016,11.187016 0 0 0 11.187132,-11.18711 11.187016,11.187016 0 0 0 -11.187132,-11.18712 11.187016,11.187016 0 0 0 -3.350867,0.52648 L 87.360517,89.451047 A 18.979685,18.979685 0 0 0 92.197455,76.823711 18.979685,18.979685 0 0 0 91.966478,73.96367 l 15.144312,-4.332523 a 11.187016,11.187016 0 0 0 9.30363,4.98979 11.187016,11.187016 0 0 0 11.18712,-11.187115 11.187016,11.187016 0 0 0 -11.18712,-11.187121 11.187016,11.187016 0 0 0 -11.15824,10.616471 L 89.627832,67.33326 A 18.979685,18.979685 0 0 0 76.727065,58.182476 V 42.669628 a 11.187016,11.187016 0 0 0 7.6783,-10.60797 11.187016,11.187016 0 0 0 -11.187118,-11.187122 z"
style="opacity:1;fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:2.30346966;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="41.337784mm"
height="41.333061mm"
viewBox="0 0 146.47246 146.45574"
id="svg6315"
version="1.1">
<defs
id="defs6317">
<linearGradient
id="linearGradient819"
osb:paint="solid">
<stop
style="stop-color:#dddddd;stop-opacity:1;"
offset="0"
id="stop817" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.99449835,0,0,0.99438474,0.4032344,0.40318833)"
xlink:href="#linearGradient819"
id="linearGradient821"
x1="3.5762787e-06"
y1="73.235916"
x2="146.47183"
y2="73.235916"
gradientUnits="userSpaceOnUse" />
</defs>
<metadata
id="metadata6320">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g836">
<ellipse
cy="73.227867"
cx="73.236229"
id="path815"
style="fill:url(#linearGradient821);fill-opacity:1;stroke:#0c3c86;stroke-width:5.6151762;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1;paint-order:normal;opacity:0.94"
rx="70.428482"
ry="70.420441" />
<path
style="opacity:0.93999999;fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.82743692;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 33.08893,33.085407 V 66.30168 L 45.089123,54.302878 65.699612,73.227871 45.094895,92.158627 33.08893,80.15406 v 33.21629 h 33.220052 l -12.005955,-12.00464 18.9331,-20.602204 18.92699,20.607914 -12.000209,11.99889 33.218622,-0.002 0.002,-33.214768 L 101.37168,92.163963 80.772686,73.227807 101.37737,54.296763 113.38335,66.301337 V 33.085425 L 80.163271,33.085389 92.169217,45.090009 73.236135,65.692181 54.297616,45.095782 66.309345,33.085425 Z"
id="rect827-0-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg6315"
viewBox="0 0 477.15662 123.40902"
height="34.828766mm"
width="134.6642mm">
<defs
id="defs6317" />
<metadata
id="metadata6320">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
ry="7.544374"
y="2.7482817"
x="2.7482817"
height="117.91245"
width="471.66006"
id="rect4136-2-3-9"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.49656343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<rect
ry="6.2240515"
style="fill:#ffffff;stroke:#0c3c86;stroke-width:3.75000095;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
y="22.093035"
x="24.019859"
height="79.222946"
width="325.79767"
id="rect3729-3-6-3" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.01963258;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 368.13424,17.337658 v 36.7118 l 13.26153,-13.2615 22.77684,20.91657 -22.77046,20.92294 -13.26791,-13.26787 v 36.711782 h 36.71179 l -13.26789,-13.267922 20.92314,-22.77028 20.91639,22.77659 -13.26154,13.261572 36.71019,-0.002 0.002,-36.710122 -13.27426,13.27419 -22.76408,-20.92894 22.77042,-20.92326 13.26792,13.26788 v -36.71141 l -36.71179,-4e-5 13.26788,13.26793 -20.92312,22.77025 -20.92913,-22.76387 13.27427,-13.27427 z"
id="rect827-0-3" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg6315"
viewBox="0 0 477.15662 123.40902"
height="34.828766mm"
width="134.6642mm">
<defs
id="defs6317" />
<metadata
id="metadata6320">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g856">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.49656343;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-9"
width="471.66006"
height="117.91245"
x="2.7482817"
y="2.7482817"
ry="7.544374" />
<rect
id="rect3729-3-6-3"
width="325.79767"
height="79.222946"
x="24.019859"
y="22.093035"
style="fill:#fdfdfd;stroke:#2c2c2c;stroke-width:3.75000095;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98431373;fill-opacity:1"
ry="6.2240515" />
<path
id="rect827-0-3"
d="m 368.13424,17.337658 v 36.7118 l 13.26153,-13.2615 22.77684,20.91657 -22.77046,20.92294 -13.26791,-13.26787 v 36.711782 h 36.71179 l -13.26789,-13.267922 20.92314,-22.77028 20.91639,22.77659 -13.26154,13.261572 36.71019,-0.002 0.002,-36.710122 -13.27426,13.27419 -22.76408,-20.92894 22.77042,-20.92326 13.26792,13.26788 v -36.71141 l -36.71179,-4e-5 13.26788,13.26793 -20.92312,22.77025 -20.92913,-22.76387 13.27427,-13.27427 z"
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:2.01963258;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="svg6315"
viewBox="0 0 146.47212 146.45606"
height="41.333153mm"
width="41.337688mm">
<defs
id="defs6317">
<linearGradient
osb:paint="solid"
id="linearGradient819">
<stop
id="stop817"
offset="0"
style="stop-color:#dddddd;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="73.235916"
x2="146.47183"
y1="73.235916"
x1="3.5762787e-06"
id="linearGradient821"
xlink:href="#linearGradient819"
gradientTransform="matrix(0.99449835,0,0,0.99438474,0.4032344,0.40318833)" />
</defs>
<metadata
id="metadata6320">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g857">
<ellipse
ry="70.420441"
rx="70.428482"
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.6151762;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:3.875;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path815"
cx="73.236069"
cy="73.228027" />
<path
id="rect827-0-3"
d="M 33.088769,33.085566 V 66.301839 L 45.088962,54.303037 65.699451,73.22803 45.094734,92.158786 33.088769,80.154219 v 33.216291 h 33.220052 l -12.005955,-12.00464 18.9331,-20.602205 18.92699,20.607915 -12.000209,11.99889 33.218623,-0.002 0.002,-33.214769 L 101.37152,92.164122 80.772525,73.227966 101.37721,54.296922 113.38319,66.301496 V 33.085584 L 80.16311,33.085548 92.169056,45.090168 73.235974,65.69234 54.297455,45.095941 66.309184,33.085584 Z"
style="opacity:1;fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.82743692;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="72.886703mm"
height="29.504496mm"
viewBox="0 0 258.25999 104.5435"
id="svg7778"
version="1.1"
sodipodi:docname="generic-router-square-colour-3d.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2971"
inkscape:window-height="1654"
id="namedview7"
showgrid="false"
inkscape:zoom="1.5129674"
inkscape:cx="190.51907"
inkscape:cy="-51.859844"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7778"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g4743"
transform="translate(55.715086,-85.177241)">
<g
transform="matrix(1.1026301,0,-0.75205069,0.40555211,48.097005,85.177241)"
id="g856">
<rect
ry="10.720011"
y="2.8232942"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="rect827-0-3"
d="M 18.688663,18.570687 V 63.706329 L 34.993156,47.401867 62.996336,73.117919 35.001,98.841804 18.688663,82.529508 V 127.66515 H 63.824278 L 47.511957,111.35277 73.23609,83.357647 98.951932,111.36053 82.647411,127.6651 l 45.133669,-0.002 0.002,-45.133594 -16.3197,16.319603 L 83.475881,73.117845 111.47117,47.393568 127.78353,63.705874 V 18.570712 l -45.135628,-4.9e-5 16.31231,16.31238 L 73.236102,62.878116 47.504606,34.890887 63.82477,18.570712 Z"
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.48305464;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
<rect
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1-5"
width="114.87411"
height="44.737236"
x="114.94167"
y="199.58194"
ry="1.058059" />
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1"
width="153.66602"
height="44.718945"
x="-54.051773"
y="143.33849"
ry="0.92879671" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="41.337704mm"
height="41.270992mm"
viewBox="0 0 146.47219 146.23581"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
ry="8.9952965"
y="2.8232942"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
id="rect827-0-3"
d="M 18.688663,18.570687 V 63.706329 L 34.993156,47.401867 62.996336,73.117919 35.001,98.841804 18.688663,82.529508 V 127.66515 H 63.824278 L 47.511957,111.35277 73.23609,83.357647 98.951932,111.36053 82.647411,127.6651 l 45.133669,-0.002 0.002,-45.133594 -16.3197,16.319603 L 83.475881,73.117845 111.47117,47.393568 127.78353,63.705874 V 18.570712 l -45.135628,-4.9e-5 16.31231,16.31238 L 73.236102,62.878116 47.504606,34.890887 63.82477,18.570712 Z"
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.48305464;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="41.337704mm"
height="41.270988mm"
viewBox="0 0 146.47219 146.2358"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g865">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="140.82561"
height="140.58922"
x="2.8232944"
y="2.8232942"
ry="8.9952965" />
<path
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:2.48305464;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 18.688665,18.570684 V 63.706326 L 34.993158,47.401864 62.996338,73.117916 35.001002,98.841801 18.688665,82.529505 V 127.66515 H 63.82428 L 47.511959,111.35277 73.236092,83.357644 98.951934,111.36053 82.647413,127.6651 l 45.133667,-0.002 0.002,-45.133597 -16.3197,16.319603 L 83.475883,73.117842 111.47117,47.393565 127.78353,63.705871 V 18.570709 L 82.647904,18.57066 98.960214,34.88304 73.236104,62.878113 47.504608,34.890884 63.824772,18.570709 Z"
id="rect827-0-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 135.22932 37.519578"
height="37.519577mm"
width="135.22932mm">
<defs
id="defs2">
<filter
id="filter4183"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend4185"
in2="BackgroundImage"
mode="lighten" />
</filter>
<filter
id="filter4183-4"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend4185-6"
in2="BackgroundImage"
mode="lighten" />
</filter>
<filter
id="filter4326"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend4328"
in2="BackgroundImage"
mode="lighten" />
</filter>
</defs>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-19.178164,-127.42057)"
id="layer1">
<g
id="g1188">
<g
id="g1136">
<rect
y="130.76498"
x="22.522587"
height="30.830734"
width="128.54048"
id="rect826"
style="opacity:1;fill:#b9b9b9;fill-opacity:1;stroke:#7e7e7e;stroke-width:6.6888442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill" />
<rect
y="130.76498"
x="22.522587"
height="30.830734"
width="128.54047"
id="rect1124"
style="opacity:1;fill:none;fill-opacity:1;stroke:#4b4b4b;stroke-width:0.79374999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill" />
</g>
<g
id="g1179">
<rect
ry="0.5021199"
y="137.78133"
x="139.47626"
height="16.798052"
width="3.3196149"
id="rect828"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<rect
ry="0.5021199"
y="137.78133"
x="33.307011"
height="16.798052"
width="3.3196149"
id="rect828-7"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<rect
ry="0.50909281"
y="140.55286"
x="101.06827"
height="11.254977"
width="29.225002"
id="rect828-3-6"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="135.22932mm"
height="37.519577mm"
viewBox="0 0 135.22932 37.519578"
version="1.1"
id="svg8">
<defs
id="defs2">
<filter
style="color-interpolation-filters:sRGB"
id="filter4183">
<feBlend
mode="lighten"
in2="BackgroundImage"
id="feBlend4185" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter4183-4">
<feBlend
mode="lighten"
in2="BackgroundImage"
id="feBlend4185-6" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter4326">
<feBlend
mode="lighten"
in2="BackgroundImage"
id="feBlend4328" />
</filter>
</defs>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-19.178165,-127.42056)"
id="g1136">
<rect
style="opacity:1;fill:#b9b9b9;fill-opacity:1;stroke:#7e7e7e;stroke-width:6.6888442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="rect826"
width="128.54048"
height="30.830734"
x="22.522587"
y="130.76498" />
<rect
style="opacity:1;fill:none;fill-opacity:1;stroke:#4b4b4b;stroke-width:0.79374999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="rect1124"
width="128.54047"
height="30.830734"
x="22.522587"
y="130.76498" />
</g>
<g
transform="translate(-7.2053528e-7,7.9711151e-6)"
id="g895">
<g
id="g889">
<rect
transform="matrix(-0.00585336,0.99998287,-0.99999753,-0.00222174,0,0)"
ry="0.81501383"
y="-38.33847"
x="11.609586"
height="27.265688"
width="3.3196502"
id="rect828-7"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.34835279;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<rect
transform="matrix(-0.00585336,0.99998287,-0.99999753,-0.00222174,0,0)"
ry="0.81501383"
y="-38.402103"
x="22.481047"
height="27.265688"
width="3.3196502"
id="rect828-7-3"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.34835279;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
</g>
<circle
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.34831667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="path883"
cx="108.63422"
cy="18.759781"
r="9.0152884" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg8"
version="1.1"
viewBox="0 0 135.22932 37.519578"
height="37.519577mm"
width="135.22932mm">
<defs
id="defs2">
<filter
id="filter4183"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend4185"
in2="BackgroundImage"
mode="lighten" />
</filter>
<filter
id="filter4183-4"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend4185-6"
in2="BackgroundImage"
mode="lighten" />
</filter>
<filter
id="filter4326"
style="color-interpolation-filters:sRGB">
<feBlend
id="feBlend4328"
in2="BackgroundImage"
mode="lighten" />
</filter>
</defs>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#b9b9b9;fill-opacity:1;stroke:#7e7e7e;stroke-width:6.6888442;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="rect826"
width="128.54048"
height="30.830734"
x="3.3444219"
y="3.3444242" />
<rect
style="opacity:1;fill:none;fill-opacity:1;stroke:#4b4b4b;stroke-width:0.79374999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="rect1124"
width="128.54047"
height="30.830734"
x="3.3444219"
y="3.3444242" />
<circle
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
id="path883"
cx="18.259447"
cy="18.75979"
r="5.3082652" />
<rect
ry="0.5021199"
y="10.360765"
x="121.69202"
height="16.798052"
width="3.3196146"
id="rect828-3"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<rect
ry="0.5021199"
y="10.360765"
x="112.44859"
height="16.798052"
width="3.3196146"
id="rect828-3-5"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833328;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
<rect
ry="0.5021199"
y="10.360765"
x="103.20518"
height="16.798052"
width="3.3196146"
id="rect828-3-5-6"
style="opacity:1;fill:#7e7e7e;fill-opacity:1;stroke:#4b4b4b;stroke-width:1.05833333;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 477.15661 123.40902"
height="34.828766mm"
width="134.6642mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.49656296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="471.66006"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.5443735" />
<rect
id="rect3729-3-6"
width="325.79764"
height="79.222939"
x="24.019842"
y="22.093039"
style="fill:#ffffff;stroke:#0c3c86;stroke-width:3.75000048;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
ry="6.2240515" />
<g
transform="matrix(0.94393867,0,0,1.0236306,-115.55316,-88.777415)"
id="g884">
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.65750003;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 567.41048,109.59324 38.10242,16.66714 -38.10242,16.66714 -0.22522,-0.0981 10e-6,-12.87097 -55.58533,0.89664 v -9.18932 l 55.58533,0.89654 -10e-6,-12.87087 z"
id="rect854" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.65749991;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 549.70223,151.08855 -38.10242,16.66714 38.10242,16.66714 0.22522,-0.0981 -10e-6,-12.87097 55.58533,0.89664 v -9.18932 l -55.58533,0.89654 10e-6,-12.87087 z"
id="rect854-6" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="134.6642mm"
height="34.828766mm"
viewBox="0 0 477.15661 123.40902"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g888">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.49656296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="471.66006"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.5443735" />
<rect
id="rect3729-3-6"
width="325.79764"
height="79.222939"
x="24.019842"
y="22.093039"
style="fill:#fdfdfd;stroke:#2c2c2c;stroke-width:3.75000048;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
ry="6.2240515" />
<path
id="rect854"
d="m 420.04753,23.405579 35.96635,17.060995 -35.96635,17.060994 -0.21259,-0.100418 10e-6,-13.175119 -52.46914,0.917828 V 35.76339 l 52.46914,0.917726 -10e-6,-13.175016 z"
style="fill:#2c2c2c;fill-opacity:1;stroke:#3a3a3a;stroke-width:1.62928498;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
<path
id="rect854-6"
d="m 403.33203,65.881448 -35.96635,17.060995 35.96635,17.060997 0.2126,-0.100421 -10e-6,-13.175119 52.46914,0.917828 v -9.406469 l -52.46914,0.917726 10e-6,-13.175016 z"
style="fill:#2c2c2c;fill-opacity:1;stroke:#3a3a3a;stroke-width:1.62928486;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="134.6642mm"
height="34.828766mm"
viewBox="0 0 477.15661 123.40902"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g844">
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.49656296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="471.66006"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.5443735" />
<rect
id="rect3729-3-6"
width="325.79764"
height="79.222939"
x="24.019842"
y="22.093039"
style="fill:#fdfdfd;fill-opacity:1;stroke:#0c3c86;stroke-width:3.75000048;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
ry="6.2240515" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.91971529;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 435.4315,16.179916 c -2.89757,1.181198 -4.70595,3.158544 -5.80688,5.652746 l 12.36279,14.579769 -74.60561,-0.470416 c -1.07179,2.989684 -1.23701,6.076513 0,9.313602 L 442.00481,44.785201 429.50292,59.3215 c 1.02042,2.912039 3.03455,4.708568 5.76921,5.696216 l 20.93112,-24.340497 z"
id="rect864" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.91971529;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 388.15382,107.2291 c 2.89757,-1.1812 4.70595,-3.15855 5.80688,-5.65275 l -12.36279,-14.579768 74.60561,0.470416 c 1.07179,-2.989683 1.23701,-6.076512 0,-9.313604 l -74.623,0.470415 12.50188,-14.536292 c -1.02041,-2.912047 -3.03454,-4.708573 -5.76921,-5.696216 l -20.93111,24.340495 z"
id="rect864-2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="134.6642mm"
height="34.828766mm"
viewBox="0 0 477.15661 123.40902"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g831">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.49656296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="471.66006"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.5443735" />
<rect
id="rect3729-3-6"
width="325.79764"
height="79.222939"
x="24.019842"
y="22.093039"
style="fill:#fdfdfd;stroke:#2c2c2c;stroke-width:3.75000048;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
ry="6.2240515" />
<path
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.91971529;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 435.4315,16.179916 c -2.89757,1.181198 -4.70595,3.158544 -5.80688,5.652746 l 12.36279,14.579769 -74.60561,-0.470416 c -1.07179,2.989684 -1.23701,6.076513 0,9.313602 L 442.00481,44.785201 429.50292,59.3215 c 1.02042,2.912039 3.03455,4.708568 5.76921,5.696216 l 20.93112,-24.340497 z"
id="rect864" />
<path
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.91971529;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 388.15382,107.2291 c 2.89757,-1.1812 4.70595,-3.15855 5.80688,-5.65275 l -12.36279,-14.579768 74.60561,0.470416 c 1.07179,-2.989683 1.23701,-6.076512 0,-9.313604 l -74.623,0.470415 12.50188,-14.536292 c -1.02041,-2.912047 -3.03454,-4.708573 -5.76921,-5.696216 l -20.93111,24.340495 z"
id="rect864-2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="134.6642mm"
height="34.828766mm"
viewBox="0 0 477.15661 123.40901"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.49656296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3"
width="471.66006"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.5443735" />
<rect
id="rect3729-3-6"
width="325.79764"
height="79.222939"
x="24.019842"
y="22.093039"
style="fill:#ffffff;stroke:#0c3c86;stroke-width:3.75000048;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
ry="6.2240515" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.45191705;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 385.71018,17.076762 -19.26531,19.174073 h 12.53002 v 12.82604 l 22.30187,12.308744 -22.30187,12.307431 v 12.782713 h -12.53002 l 19.26531,19.174057 19.26529,-19.174057 h -12.07889 v -9.60016 l 18.22455,-10.058382 18.22456,10.058382 v 9.60016 h -12.0789 l 19.2653,19.174057 19.26532,-19.174057 H 443.26606 V 73.69305 L 420.96419,61.384307 443.26606,49.076875 v -12.82604 h 12.53135 l -19.26532,-19.174073 -19.2653,19.174073 h 12.0789 v 9.643491 L 411.12113,55.952703 392.89658,45.894326 v -9.643491 h 12.07889 z"
id="rect817-9" />
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 477.15661 123.40901"
height="34.828766mm"
width="134.6642mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g841">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.49656296;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3"
width="471.66006"
height="117.91245"
x="2.7482815"
y="2.7482815"
ry="7.5443735" />
<rect
id="rect3729-3-6"
width="325.79764"
height="79.222939"
x="24.019842"
y="22.093039"
style="fill:#fdfdfd;stroke:#2c2c2c;stroke-width:3.75000048;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
ry="6.2240515" />
<path
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.45191705;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 385.71018,17.076762 -19.26531,19.174073 h 12.53002 v 12.82604 l 22.30187,12.308744 -22.30187,12.307431 v 12.782713 h -12.53002 l 19.26531,19.174057 19.26529,-19.174057 h -12.07889 v -9.60016 l 18.22455,-10.058382 18.22456,10.058382 v 9.60016 h -12.0789 l 19.2653,19.174057 19.26532,-19.174057 H 443.26606 V 73.69305 L 420.96419,61.384307 443.26606,49.076875 v -12.82604 h 12.53135 l -19.26532,-19.174073 -19.2653,19.174073 h 12.0789 v 9.643491 L 411.12113,55.952703 392.89658,45.894326 v -9.643491 h 12.07889 z"
id="rect817-9" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="72.771095mm"
height="29.475048mm"
viewBox="0 0 257.85035 104.43915"
id="svg7778"
version="1.1"
sodipodi:docname="generic-switch-l2-v1-colour-3d.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2017"
inkscape:window-height="1713"
id="namedview10"
showgrid="false"
inkscape:zoom="4.279318"
inkscape:cx="129.71378"
inkscape:cy="38.306915"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7778"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1-5"
width="114.87411"
height="44.737236"
x="178.75769"
y="145.32616"
ry="1.058059" />
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1"
width="153.66602"
height="44.718945"
x="1.7145537"
y="58.0569"
ry="0.92879671" />
<g
id="g4539"
transform="matrix(1.075021,0,-0.72736773,0.40484182,102.99422,9.6521802e-8)">
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="140.82561"
height="140.58922"
x="2.8232944"
y="2.8232942"
ry="3.8205848" />
<g
id="g4533"
transform="matrix(0.97263932,0,0,1,1.696654,0)">
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.98120928;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 83.819427,28.396005 129.36324,48.318232 83.819427,68.24046 83.550221,68.123201 83.550233,52.738534 17.109105,53.810288 V 42.826296 l 66.441128,1.071634 -1.2e-5,-15.384547 z"
id="rect854"
inkscape:connector-curvature="0" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.98120916;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 62.652761,77.995336 -45.543812,19.922228 45.543812,19.922226 0.269205,-0.11726 -1.2e-5,-15.38466 66.441126,1.07175 V 92.425628 l -66.441126,1.071634 1.2e-5,-15.384547 z"
id="rect854-6"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="41.337708mm"
height="41.270992mm"
viewBox="0 0 146.4722 146.23581"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g4539">
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="140.82561"
height="140.58922"
x="2.8232944"
y="2.8232942"
ry="8.9952965" />
<g
id="g4533"
transform="matrix(0.97263932,0,0,1,1.696654,0)">
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.98120928;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 83.819427,28.396005 129.36324,48.318232 83.819427,68.24046 83.550221,68.123201 83.550233,52.738534 17.109105,53.810288 V 42.826296 l 66.441128,1.071634 -1.2e-5,-15.384547 z"
id="rect854" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:1.98120916;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 62.652761,77.995336 -45.543812,19.922228 45.543812,19.922226 0.269205,-0.11726 -1.2e-5,-15.38466 66.441126,1.07175 V 92.425628 l -66.441126,1.071634 1.2e-5,-15.384547 z"
id="rect854-6" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="41.337708mm"
height="41.270992mm"
viewBox="0 0 146.4722 146.23581"
id="svg7778"
version="1.1">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g818">
<rect
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="140.82561"
height="140.58922"
x="2.8232944"
y="2.8232942"
ry="8.9952965" />
<path
id="rect854"
d="m 83.222724,28.396005 44.297706,19.922227 -44.297706,19.922228 -0.26184,-0.117259 1.2e-5,-15.384667 -64.623254,1.071754 V 42.826296 l 64.623254,1.071634 -1.2e-5,-15.384547 z"
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.95391774;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
<path
id="rect854-6"
d="m 62.635193,77.995336 -44.297702,19.922228 44.297702,19.922226 0.261839,-0.11726 -1.1e-5,-15.38466 64.623249,1.07175 V 92.425628 l -64.623249,1.071634 1.1e-5,-15.384547 z"
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:1.95391762;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="72.756638mm"
height="29.311676mm"
viewBox="0 0 257.79912 103.86028"
id="svg7778"
version="1.1"
sodipodi:docname="generic-switch-l2-v2-colour-3d.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2017"
inkscape:window-height="1713"
id="namedview10"
showgrid="false"
inkscape:zoom="2.139659"
inkscape:cx="-25.807606"
inkscape:cy="63.537409"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg7778"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
transform="matrix(0.873861,-0.48617585,5.1650651e-8,1,0,0)"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:2.87683558;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1-5"
width="114.87411"
height="44.737236"
x="178.69905"
y="144.71878"
ry="1.058059" />
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:3.32662606;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3-1"
width="153.66602"
height="44.718945"
x="1.663313"
y="57.478024"
ry="0.92879671" />
<g
id="g4569"
transform="matrix(1.083679,0,-0.71270708,0.39467767,101.25302,9.4098496e-8)">
<rect
ry="3.6705956"
y="2.8232942"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3-6"
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<g
transform="matrix(0.94639625,0,0,1,3.3122542,4.7325938e-6)"
id="g1014">
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.34375024;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 103.11141,19.426017 c -3.662013,1.393109 -5.947484,3.725199 -7.338869,6.66687 l 15.624389,17.195437 -94.288336,-0.55481 c -1.354551,3.526045 -1.563357,7.166663 0,10.984498 L 111.41891,53.163202 95.618732,70.30737 c 1.289629,3.43447 3.835131,5.553303 7.291268,6.718139 l 26.45324,-28.707277 z"
id="rect864"
inkscape:connector-curvature="0" />
<path
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.34375024;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 43.360769,126.80978 c 3.66201,-1.39311 5.94749,-3.7252 7.33887,-6.66687 l -15.62439,-17.19544 94.288331,0.55481 c 1.35455,-3.526043 1.56336,-7.166662 0,-10.984501 l -94.310311,0.55481 15.80018,-17.14416 c -1.28963,-3.43448 -3.83513,-5.55331 -7.29127,-6.71814 l -26.45323,28.707275 z"
id="rect864-2"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 146.4722 146.23581"
height="41.270992mm"
width="41.337708mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="opacity:1;fill:#dddddd;fill-opacity:1;stroke:#0c3c86;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-2-3-3"
width="140.82561"
height="140.58922"
x="2.8232944"
y="2.8232942"
ry="8.9952965" />
<g
id="g1014"
transform="matrix(0.94639625,0,0,1,3.3122542,4.7325938e-6)">
<path
id="rect864"
d="m 103.11141,19.426017 c -3.662013,1.393109 -5.947484,3.725199 -7.338869,6.66687 l 15.624389,17.195437 -94.288336,-0.55481 c -1.354551,3.526045 -1.563357,7.166663 0,10.984498 L 111.41891,53.163202 95.618732,70.30737 c 1.289629,3.43447 3.835131,5.553303 7.291268,6.718139 l 26.45324,-28.707277 z"
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.34375024;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
<path
id="rect864-2"
d="m 43.360769,126.80978 c 3.66201,-1.39311 5.94749,-3.7252 7.33887,-6.66687 l -15.62439,-17.19544 94.288331,0.55481 c 1.35455,-3.526043 1.56336,-7.166662 0,-10.984501 l -94.310311,0.55481 15.80018,-17.14416 c -1.28963,-3.43448 -3.83513,-5.55331 -7.29127,-6.71814 l -26.45323,28.707275 z"
style="fill:#104ead;fill-opacity:1;stroke:#0c3c86;stroke-width:2.34375024;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg7778"
viewBox="0 0 146.47219 146.23581"
height="41.270992mm"
width="41.337704mm">
<defs
id="defs7780" />
<metadata
id="metadata7783">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g818">
<rect
ry="8.9952965"
y="2.8232942"
x="2.8232944"
height="140.58922"
width="140.82561"
id="rect4136-2-3-3"
style="opacity:1;fill:#fdfdfd;fill-opacity:1;stroke:#2c2c2c;stroke-width:5.6465888;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1" />
<path
id="rect864"
d="m 100.89651,19.426022 c -3.465719,1.393109 -5.628681,3.725199 -6.945482,6.66687 l 14.786862,17.195437 -89.234127,-0.55481 c -1.281942,3.526045 -1.479555,7.166663 0,10.984498 l 89.254927,-0.55481 -14.953226,17.144168 c 1.2205,3.43447 3.629553,5.553303 6.900426,6.718139 l 25.03525,-28.707277 z"
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:2.28006816;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
<path
id="rect864-2"
d="m 44.348723,126.80978 c 3.465713,-1.39311 5.628683,-3.7252 6.945479,-6.66687 l -14.786864,-17.19544 89.234122,0.55481 c 1.28194,-3.526038 1.47956,-7.166657 0,-10.984496 l -89.254923,0.55481 14.953231,-17.14416 c -1.220501,-3.43448 -3.629553,-5.55331 -6.900431,-6.71814 L 19.504099,97.917569 Z"
style="fill:#3a3a3a;fill-opacity:1;stroke:#2c2c2c;stroke-width:2.28006816;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Some files were not shown because too many files have changed in this diff Show more