Compare commits

...

16 Commits

Author SHA1 Message Date
R. Miles McCain
6afea91c5f Bump version 2020-11-26 21:03:52 +00:00
R. Miles McCain
7a4c892804 Remove background from favicon 2020-11-26 21:03:18 +00:00
imgbot[bot]
9b50b1ea42 [ImgBot] Optimize images (#87)
*Total -- 765.10kb -> 502.59kb (34.31%)

/images/service.png -- 366.95kb -> 239.64kb (34.69%)
/images/homepage.png -- 398.15kb -> 262.95kb (33.96%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2020-11-26 16:00:51 -05:00
R. Miles McCain
52a18d21f1 Small visual improvements 2020-11-26 20:59:03 +00:00
R. Miles McCain
8aaf312c67 Bump version 2020-11-26 20:10:28 +00:00
R. Miles McCain
ede06900e5 Format code 2020-11-26 20:09:55 +00:00
R. Miles McCain
a42455c9dc Add browser icon 2020-11-26 20:09:34 +00:00
R. Miles McCain
547a84f2fc Update screenshots 2020-11-26 20:04:48 +00:00
R. Miles McCain
f56ea99dc2 Add demo data command 2020-11-26 19:44:07 +00:00
R. Miles McCain
4cea5d2310 Add additional icons 2020-11-26 19:43:44 +00:00
R. Miles McCain
e4f09b4e68 Ensure times are always correct 2020-11-26 19:43:25 +00:00
R. Miles McCain
cc094fe04e Add icons to dashboard 2020-11-26 18:09:42 +00:00
R. Miles McCain
ac5c743390 Update dependencies 2020-11-26 18:00:19 +00:00
R. Miles McCain
963db18642 Bump version 2020-10-17 19:11:25 +00:00
R. Miles McCain
748fb76eaf Bump version 2020-10-17 19:03:37 +00:00
R. Miles McCain
d93a698e87 Update django-redis-cache 2020-10-17 19:03:18 +00:00
21 changed files with 414 additions and 92 deletions

View File

@@ -18,7 +18,7 @@ rules = "~=2.2"
gunicorn = "~=20.0.4" gunicorn = "~=20.0.4"
psycopg2-binary = "~=2.8.5" psycopg2-binary = "~=2.8.5"
redis = "~=3.5.3" redis = "~=3.5.3"
django-redis-cache = "~=2.1.1" django-redis-cache = "~=3.0.0"
pycountry = "~=19.8.18" pycountry = "~=19.8.18"
html2text = "~=2020.1.16" html2text = "~=2020.1.16"
django-health-check = "~=3.12.1" django-health-check = "~=3.12.1"

60
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f9ab7bf92fe2342e3d221ab34ac957bf217638062da0554b2328502650515741" "sha256": "73fbc4a0251ccae805550aa48b70ec9f668bee6fa4ff9503e023b3854a06bce8"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@@ -23,10 +23,10 @@
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
], ],
"version": "==3.2.10" "version": "==3.3.1"
}, },
"billiard": { "billiard": {
"hashes": [ "hashes": [
@@ -45,10 +45,10 @@
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
], ],
"version": "==2020.6.20" "version": "==2020.11.8"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
@@ -66,11 +66,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc", "sha256:14a4b7cd77297fba516fc0d92444cc2e2e388aa9de32d7a68d4a83d58f5a4927",
"sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4" "sha256:14b87775ffedab2ef6299b73343d1b4b41e5d4e2aa58c6581f114dbec01e3f8f"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.1.2" "version": "==3.1.3"
}, },
"django-allauth": { "django-allauth": {
"hashes": [ "hashes": [
@@ -103,19 +103,18 @@
}, },
"django-redis-cache": { "django-redis-cache": {
"hashes": [ "hashes": [
"sha256:9b2c45a1bc0f295bccd56c2542d937665ae98f3325f20b3d82fc620e14395d52", "sha256:9a2eebef421d996a82098a19d17ff6b321265cd73178fa398913019764e8394a"
"sha256:e72691539be99c0b2dd64ac380e26f4d2be5c53c1b2d26845dd279ae38b47477"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.1.3" "version": "==3.0.0"
}, },
"emoji-country-flag": { "emoji-country-flag": {
"hashes": [ "hashes": [
"sha256:67c0cb6a3765fb53f31b34160d6b1c8a5f44b297bc278d1835c6f2e5b0a9a592", "sha256:256e47d30fb43bf154f370cc3c9e767f003aeb8653e31ba7b87151669c608e19",
"sha256:ae7edb38077b0840210fa9e37673f481f2b9c032446e13ad6dab2b1108cd7ad6" "sha256:3f6c32699c19489383497865b208260b1d55b8424d66e08049187b13db2f0b8a"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.2.1" "version": "==1.2.3"
}, },
"geoip2": { "geoip2": {
"hashes": [ "hashes": [
@@ -173,9 +172,11 @@
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
"sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6",
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
"sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056",
"sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
@@ -223,21 +224,23 @@
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
], ],
"version": "==2020.1" "version": "==2020.4"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
@@ -255,10 +258,10 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
], ],
"version": "==2.24.0" "version": "==2.25.0"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
@@ -274,13 +277,6 @@
"index": "pypi", "index": "pypi",
"version": "==2.2" "version": "==2.2"
}, },
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.15.0"
},
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
@@ -298,10 +294,10 @@
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
], ],
"version": "==1.25.10" "version": "==1.26.2"
}, },
"user-agents": { "user-agents": {
"hashes": [ "hashes": [

View File

@@ -70,3 +70,6 @@ SHOW_SHYNET_VERSION=True
# that you have a separate queue consumer running somewhere via `celeryworker.sh`. # that you have a separate queue consumer running somewhere via `celeryworker.sh`.
# CELERY_TASK_ALWAYS_EAGER=False # CELERY_TASK_ALWAYS_EAGER=False
# CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1 # CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1
# Should Shynet show third-party icons in the dashboard?
SHOW_THIRD_PARTY_ICONS=True

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 240 KiB

148
package-lock.json generated
View File

@@ -3,21 +3,72 @@
"requires": true, "requires": true,
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
"@babel/helper-module-imports": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz",
"integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==",
"requires": {
"@babel/types": "^7.12.5"
}
},
"@babel/helper-validator-identifier": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
},
"@babel/types": {
"version": "7.12.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz",
"integrity": "sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==",
"requires": {
"@babel/helper-validator-identifier": "^7.10.4",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
}
},
"@fortawesome/fontawesome-free": { "@fortawesome/fontawesome-free": {
"version": "5.15.1", "version": "5.15.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.1.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.1.tgz",
"integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ==" "integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ=="
}, },
"@rollup/plugin-babel": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.1.tgz",
"integrity": "sha512-Jd7oqFR2dzZJ3NWANDyBjwTtX/lYbZpVcmkHrfQcpvawHs9E4c0nYk5U2mfZ6I/DZcIvy506KZJi54XK/jxH7A==",
"requires": {
"@babel/helper-module-imports": "^7.10.4",
"@rollup/pluginutils": "^3.1.0"
}
},
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
},
"a17t": { "a17t": {
"version": "0.2.9", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/a17t/-/a17t-0.2.9.tgz", "resolved": "https://registry.npmjs.org/a17t/-/a17t-0.3.0.tgz",
"integrity": "sha512-PdkeIVJYTfKCrYkx64c8HEPvbiVo2Prx8NWMCsiXHbsvPLbai64FwydXnNSzq/hRBQ3Toi5qU8DNCxeX1AXBCw==" "integrity": "sha512-zDQs0Xw4q+jwSALsdPxDqalW24mIuUSLxua3pdT9bukyfeqfN5g3lIxYuh7DRo0iU1GAW8fDQRR9Ow5OdDD4AQ==",
"requires": {
"autoprefixer": "^10.0.2"
}
}, },
"apexcharts": { "apexcharts": {
"version": "3.22.0", "version": "3.22.2",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.0.tgz", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.2.tgz",
"integrity": "sha512-DDh2eXnAEA8GoKU/hdicOaS2jzGehXwv8Bj1djYYudkeQzEdglFoWsVyIxff+Ds7+aUtVAJzd/9ythZuyyIbXQ==", "integrity": "sha512-pR+cmApk7dhfYILBpe8RVb+FdLfVCt/RDWvAJO1F5feeSQ8lKDgFkRuVu9KOeEarHVXjUpnhLqHNMx7YaprK8A==",
"requires": { "requires": {
"@rollup/plugin-babel": "^5.2.1",
"svg.draggable.js": "^2.2.2", "svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0", "svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2", "svg.filter.js": "^2.0.2",
@@ -26,6 +77,56 @@
"svg.select.js": "^3.0.1" "svg.select.js": "^3.0.1"
} }
}, },
"autoprefixer": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.0.2.tgz",
"integrity": "sha512-okBmu9OMdt6DNEcZmnl0IYVv8Xl/xYWRSnc2OJ9UJEOt1u30opG1B8aLsViqKryBaYv1SKB4f85fOGZs5zYxHQ==",
"requires": {
"browserslist": "^4.14.7",
"caniuse-lite": "^1.0.30001157",
"colorette": "^1.2.1",
"normalize-range": "^0.1.2",
"num2fraction": "^1.2.2",
"postcss-value-parser": "^4.1.0"
}
},
"browserslist": {
"version": "4.14.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz",
"integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==",
"requires": {
"caniuse-lite": "^1.0.30001157",
"colorette": "^1.2.1",
"electron-to-chromium": "^1.3.591",
"escalade": "^3.1.1",
"node-releases": "^1.1.66"
}
},
"caniuse-lite": {
"version": "1.0.30001161",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001161.tgz",
"integrity": "sha512-JharrCDxOqPLBULF9/SPa6yMcBRTjZARJ6sc3cuKrPfyIk64JN6kuMINWqA99Xc8uElMFcROliwtz0n9pYej+g=="
},
"colorette": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz",
"integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw=="
},
"electron-to-chromium": {
"version": "1.3.607",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.607.tgz",
"integrity": "sha512-h2SYNaBnlplGS0YyXl8oJWokfcNxVjJANQfMCsQefG6OSuAuNIeW+A8yGT/ci+xRoBb3k2zq1FrOvkgoKBol8g=="
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
},
"inter-ui": { "inter-ui": {
"version": "3.15.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/inter-ui/-/inter-ui-3.15.0.tgz", "resolved": "https://registry.npmjs.org/inter-ui/-/inter-ui-3.15.0.tgz",
@@ -36,6 +137,36 @@
"resolved": "https://registry.npmjs.org/litepicker/-/litepicker-1.5.7.tgz", "resolved": "https://registry.npmjs.org/litepicker/-/litepicker-1.5.7.tgz",
"integrity": "sha512-4L2ZcF8iqCE4A/qGWS3PbdFplZR1g751x5SsZ87zCRZ4LQN1Fgezarnvqi0eHk/kDWK7Qx0HZ9Y4bNznJMF1xA==" "integrity": "sha512-4L2ZcF8iqCE4A/qGWS3PbdFplZR1g751x5SsZ87zCRZ4LQN1Fgezarnvqi0eHk/kDWK7Qx0HZ9Y4bNznJMF1xA=="
}, },
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"node-releases": {
"version": "1.1.67",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz",
"integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg=="
},
"normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
},
"num2fraction": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
"integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4="
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
},
"postcss-value-parser": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
},
"svg.draggable.js": { "svg.draggable.js": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
@@ -100,6 +231,11 @@
"svg.js": "^2.6.5" "svg.js": "^2.6.5"
} }
}, },
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"turbolinks": { "turbolinks": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/turbolinks/-/turbolinks-5.2.0.tgz", "resolved": "https://registry.npmjs.org/turbolinks/-/turbolinks-5.2.0.tgz",

View File

@@ -18,8 +18,8 @@
"homepage": "https://github.com/milesmcc/shynet#readme", "homepage": "https://github.com/milesmcc/shynet#readme",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1", "@fortawesome/fontawesome-free": "^5.15.1",
"a17t": "^0.2.9", "a17t": "^0.3.0",
"apexcharts": "^3.22.0", "apexcharts": "^3.22.2",
"inter-ui": "^3.15.0", "inter-ui": "^3.15.0",
"litepicker": "^1.5.7", "litepicker": "^1.5.7",
"turbolinks": "^5.2.0" "turbolinks": "^5.2.0"

View File

@@ -20,8 +20,8 @@ class Session(models.Model):
identifier = models.TextField(blank=True, db_index=True) identifier = models.TextField(blank=True, db_index=True)
# Time # Time
start_time = models.DateTimeField(auto_now_add=True, db_index=True) start_time = models.DateTimeField(default=timezone.now, db_index=True)
last_seen = models.DateTimeField(auto_now_add=True) last_seen = models.DateTimeField(default=timezone.now)
# Core request information # Core request information
user_agent = models.TextField() user_agent = models.TextField()
@@ -78,8 +78,8 @@ class Hit(models.Model):
initial = models.BooleanField(default=True, db_index=True) initial = models.BooleanField(default=True, db_index=True)
# Base request information # Base request information
start_time = models.DateTimeField(auto_now_add=True, db_index=True) start_time = models.DateTimeField(default=timezone.now, db_index=True)
last_seen = models.DateTimeField(auto_now_add=True) last_seen = models.DateTimeField(default=timezone.now)
heartbeats = models.IntegerField(default=0) heartbeats = models.IntegerField(default=0)
tracker = models.TextField( tracker = models.TextField(
choices=[("JS", "JavaScript"), ("PIXEL", "Pixel (noscript)")] choices=[("JS", "JavaScript"), ("PIXEL", "Pixel (noscript)")]

View File

@@ -9,7 +9,6 @@ from celery import shared_task
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Q from django.db.models import Q
from django.utils import timezone
from core.models import Service from core.models import Service
@@ -123,6 +122,8 @@ def ingress_request(
browser=ua.browser.family or "", browser=ua.browser.family or "",
device=ua.device.family or ua.device.model or "", device=ua.device.family or ua.device.model or "",
device_type=device_type, device_type=device_type,
start_time=time,
last_seen=time,
os=ua.os.family or "", os=ua.os.family or "",
asn=ip_data.get("asn") or "", asn=ip_data.get("asn") or "",
country=ip_data.get("country") or "", country=ip_data.get("country") or "",
@@ -139,7 +140,7 @@ def ingress_request(
log.debug("Updating old session with new data...") log.debug("Updating old session with new data...")
# Update last seen time # Update last seen time
session.last_seen = timezone.now() session.last_seen = time
if session.identifier == "" and identifier.strip() != "": if session.identifier == "" and identifier.strip() != "":
session.identifier = identifier.strip() session.identifier = identifier.strip()
session.save() session.save()
@@ -160,7 +161,7 @@ def ingress_request(
# this is a heartbeat. # this is a heartbeat.
log.debug("Hit is a heartbeat; updating old hit with new data...") log.debug("Hit is a heartbeat; updating old hit with new data...")
hit.heartbeats += 1 hit.heartbeats += 1
hit.last_seen = timezone.now() hit.last_seen = time
hit.save() hit.save()
if hit is None: if hit is None:
@@ -176,6 +177,8 @@ def ingress_request(
location=payload.get("location", location), location=payload.get("location", location),
referrer=payload.get("referrer", ""), referrer=payload.get("referrer", ""),
load_time=payload.get("loadTime"), load_time=payload.get("loadTime"),
start_time=time,
last_seen=time,
) )
# Set idempotency (if applicable) # Set idempotency (if applicable)
if idempotency is not None: if idempotency is not None:

View File

@@ -5,7 +5,12 @@ from urllib.parse import urlparse
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
)
from django.shortcuts import render, reverse from django.shortcuts import render, reverse
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@@ -53,7 +58,10 @@ class ValidateServiceOriginsMixin:
if origins != "*": if origins != "*":
remote_origin = request.META.get("HTTP_ORIGIN") remote_origin = request.META.get("HTTP_ORIGIN")
if remote_origin is None and request.META.get("HTTP_REFERER") is not None: if (
remote_origin is None
and request.META.get("HTTP_REFERER") is not None
):
parsed = urlparse(request.META.get("HTTP_REFERER")) parsed = urlparse(request.META.get("HTTP_REFERER"))
remote_origin = f"{parsed.scheme}://{parsed.netloc}".lower() remote_origin = f"{parsed.scheme}://{parsed.netloc}".lower()
origins = [origin.strip().lower() for origin in origins.split(",")] origins = [origin.strip().lower() for origin in origins.split(",")]

View File

@@ -0,0 +1,114 @@
import traceback
from django.utils.timezone import now
from django.utils.timezone import timedelta
import random
import uuid
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand, CommandError
from django.utils.crypto import get_random_string
import user_agents
from logging import info
from core.models import User, Service
from analytics.models import Session, Hit
from analytics.tasks import ingress_request
LOCATIONS = [
"/",
"/post/{rand}",
"/login",
"/me",
]
REFERRERS = [
"https://news.ycombinator.com/item?id=11116274",
"https://news.ycombinator.com/item?id=24872911",
"https://reddit.com",
"https://facebook.com",
"https://twitter.com/milesmccain",
"https://twitter.com",
"https://stanford.edu/~mccain/",
"https://tiktok.com",
"https://io.stanford.edu",
"https://en.wikipedia.org",
"https://stackoverflow.com",
"",
"",
"",
"",
]
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/43.4",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko)",
"Version/10.0 Mobile/14E304 Safari/602.1",
]
class Command(BaseCommand):
help = "Configures a Shynet demo service"
def add_arguments(self, parser):
parser.add_argument(
"name", type=str,
)
parser.add_argument("owner_email", type=str)
parser.add_argument(
"avg", type=int,
)
parser.add_argument("deviation", type=float, default=0.4)
parser.add_argument(
"days", type=int,
)
parser.add_argument("load_time", type=float, default=1000)
def handle(self, *args, **options):
owner = User.objects.get(email=options.get("owner_email"))
service = Service.objects.create(name=options.get("name"), owner=owner)
print(
f"Created demo service `{service.name}` (uuid: `{service.uuid}`, owner: {owner})"
)
# Go through each day requested, creating sessions and hits
for days in range(options.get("days")):
day = (now() - timedelta(days=days)).replace(hour=0, minute=0, second=0)
print(f"Populating info for {day}...")
avg = options.get("avg")
deviation = options.get("deviation")
ips = [
".".join(map(str, (random.randint(0, 255) for _ in range(4))))
for _ in range(avg)
]
n = avg + random.randrange(-1 * deviation * avg, deviation * avg)
for _ in range(n):
time = day + timedelta(
hours=random.randrange(0, 23),
minutes=random.randrange(0, 59),
seconds=random.randrange(0, 59),
)
ip = random.choice(ips)
load_time = random.normalvariate(options.get("load_time"), 500)
referrer = random.choice(REFERRERS)
location = "https://example.com" + random.choice(LOCATIONS).replace(
"{rand}", str(random.randint(0, n))
)
user_agent = random.choice(USER_AGENTS)
ingress_request(
service.uuid,
"JS",
time,
{"loadTime": load_time, "referrer": referrer},
ip,
location,
user_agent,
)
print(f"Created {n} demo hits on {day}!")
self.stdout.write(self.style.SUCCESS(f"Successfully created demo data!"))

View File

@@ -6,18 +6,20 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0007_service_ignore_robots'), ("core", "0007_service_ignore_robots"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='service', model_name="service",
name='script_inject', name="script_inject",
field=models.TextField(blank=True, default=''), field=models.TextField(blank=True, default=""),
), ),
migrations.AlterField( migrations.AlterField(
model_name='user', model_name="user",
name='first_name', name="first_name",
field=models.CharField(blank=True, max_length=150, verbose_name='first name'), field=models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
), ),
] ]

View File

@@ -18,7 +18,7 @@ class ServiceForm(forms.ModelForm):
"hide_referrer_regex", "hide_referrer_regex",
"origins", "origins",
"collaborators", "collaborators",
"script_inject" "script_inject",
] ]
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
@@ -28,7 +28,7 @@ class ServiceForm(forms.ModelForm):
"collect_ips": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]), "collect_ips": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"ignore_robots": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]), "ignore_robots": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"hide_referrer_regex": forms.TextInput(), "hide_referrer_regex": forms.TextInput(),
"script_inject": forms.Textarea(attrs={'class':'font-mono', 'rows': 5}) "script_inject": forms.Textarea(attrs={"class": "font-mono", "rows": 5}),
} }
labels = { labels = {
"origins": "Allowed origins", "origins": "Allowed origins",
@@ -60,7 +60,9 @@ class ServiceForm(forms.ModelForm):
def clean_collaborators(self): def clean_collaborators(self):
collaborators = [] collaborators = []
users_to_emails = {} # maps users to the email they are listed under as a collaborator users_to_emails = (
{}
) # maps users to the email they are listed under as a collaborator
for collaborator_email in self.cleaned_data["collaborators"].split(","): for collaborator_email in self.cleaned_data["collaborators"].split(","):
email = collaborator_email.strip() email = collaborator_email.strip()
if email == "": if email == "":
@@ -72,7 +74,9 @@ class ServiceForm(forms.ModelForm):
raise forms.ValidationError(f"Email '{email}' is not registered") raise forms.ValidationError(f"Email '{email}' is not registered")
user = collaborator_email_linked.user user = collaborator_email_linked.user
if user in collaborators: if user in collaborators:
raise forms.ValidationError(f"The emails '{email}' and '{users_to_emails[user]}' both correspond to the same user") raise forms.ValidationError(
f"The emails '{email}' and '{users_to_emails[user]}' both correspond to the same user"
)
users_to_emails[user] = email users_to_emails[user] = email
collaborators.append(collaborator_email_linked.user) collaborators.append(collaborator_email_linked.user)
return collaborators return collaborators

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -8,6 +8,7 @@
<meta name="robots" content="noindex"> <meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
{% include 'a17t/includes/head.html' %} {% include 'a17t/includes/head.html' %}
<link rel="icon" type="image/png" href="{% static 'dashboard/images/icon.png' %}">
<script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script> <script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script>
<script src="{% static 'litepicker/dist/js/main.js' %}"></script> <script src="{% static 'litepicker/dist/js/main.js' %}"></script>
<script src="{% static 'turbolinks/dist/turbolinks.js' %}"></script> <script src="{% static 'turbolinks/dist/turbolinks.js' %}"></script>
@@ -21,7 +22,8 @@
{% block body %} {% block body %}
<section class="max-w-screen-xl mx-auto px-4 py-4 md:py-12 md:flex"> <section class="max-w-screen-xl mx-auto px-4 py-4 md:py-12 md:flex">
<aside class="mb-8 md:w-2/12 md:pr-6 relative flex flex-wrap md:block justify-between items-center overflow-x-hidden"> <aside
class="mb-8 md:w-2/12 md:pr-6 relative flex flex-wrap md:block justify-between items-center overflow-x-hidden">
<a class="icon ~urge ml-2 md:ml-6 md:mb-8 md:mt-3" href="{% url 'dashboard:dashboard' %}"> <a class="icon ~urge ml-2 md:ml-6 md:mb-8 md:mt-3" href="{% url 'dashboard:dashboard' %}">
<i class="fas fa-binoculars fa-3x text-urge-600 hidden md:block"></i> <i class="fas fa-binoculars fa-3x text-urge-600 hidden md:block"></i>
<i class="fas fa-binoculars fa-2x text-urge-600 md:hidden"></i> <i class="fas fa-binoculars fa-2x text-urge-600 md:hidden"></i>
@@ -41,7 +43,7 @@
{% for service in user.owning_services.all %} {% for service in user.owning_services.all %}
{% url 'dashboard:service' service.uuid as url %} {% url 'dashboard:service' service.uuid as url %}
{% include 'dashboard/includes/sidebar_portal.html' with label=service.name|truncatechars:20 url=url %} {% include 'dashboard/includes/sidebar_portal.html' with label=service.name|truncatechars:16 url=url icon=service.link|iconify %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}

View File

@@ -4,8 +4,9 @@
{% with stats=object.stats %} {% with stats=object.stats %}
<div class="p-4 md:flex justify-between"> <div class="p-4 md:flex justify-between">
<div class="flex items-center mb-4 md:mb-0"> <div class="flex items-center mb-4 md:mb-0">
<h3 class="heading text-xl md:text-2xl mr-2 mb-1 text-urge-600"> <h3 class="heading text-xl md:text-2xl mr-2 mb-1 text-urge-600 flex items-center">
{{object.name}} {{object.link|iconify}}
<span>{{object.name}}</span>
</h3> </h3>
{% include 'dashboard/includes/stats_status_chip.html' %} {% include 'dashboard/includes/stats_status_chip.html' %}
</div> </div>

View File

@@ -1,6 +1,6 @@
{% load helpers %} {% load helpers %}
<div> <div>
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-neutral-100{% endif %}" <a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-neutral-100{% endif %} flex items-center"
{% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{label}}</a> {% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{icon}} {{label}}</a>
</div> </div>

View File

@@ -166,7 +166,7 @@
<tbody> <tbody>
{% for os in stats.operating_systems %} {% for os in stats.operating_systems %}
<tr> <tr>
<td>{{os.os|default:"Unknown"}}</td> <td class="flex items-center">{{os.os|iconify}}<span>{{os.os|default:"Unknown"}}</span></td>
<td class="rf">{{os.count|intcomma}}</td> <td class="rf">{{os.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -188,7 +188,8 @@
<tbody> <tbody>
{% for browser in stats.browsers %} {% for browser in stats.browsers %}
<tr> <tr>
<td>{{browser.browser|default:"Unknown"}}</td> <td class="flex items-center">
{{browser.browser|iconify}}<span>{{browser.browser|default:"Unknown"}}</span></td>
<td class="rf">{{browser.count|intcomma}}</td> <td class="rf">{{browser.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -225,7 +226,8 @@
<div class="card ~neutral !low limited-height py-2"> <div class="card ~neutral !low limited-height py-2">
{% include 'dashboard/includes/session_list.html' %} {% include 'dashboard/includes/session_list.html' %}
<hr class="sep h-8"> <hr class="sep h-8">
<a href="{% url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more sessions <a href="{% url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more
sessions
&rarr;</a> &rarr;</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -8,6 +8,7 @@
<div class="md:flex justify-between items-center" id="heading"> <div class="md:flex justify-between items-center" id="heading">
<a class="flex items-center mb-4 md:mb-0" href="{% url 'dashboard:service' object.uuid %}"> <a class="flex items-center mb-4 md:mb-0" href="{% url 'dashboard:service' object.uuid %}">
<h3 class="heading leading-none mr-4"> <h3 class="heading leading-none mr-4">
{{object.link|iconify}}
{{object.name}} {{object.name}}
</h3> </h3>
<div class='text-3xl'> <div class='text-3xl'>

View File

@@ -84,8 +84,7 @@ def percent_change_display(start, end):
@register.inclusion_tag("dashboard/includes/sidebar_footer.html") @register.inclusion_tag("dashboard/includes/sidebar_footer.html")
def sidebar_footer(): def sidebar_footer():
return {"version": settings.VERSION if settings.SHOW_SHYNET_VERSION return {"version": settings.VERSION if settings.SHOW_SHYNET_VERSION else ""}
else ""}
@register.inclusion_tag("dashboard/includes/stat_comparison.html") @register.inclusion_tag("dashboard/includes/stat_comparison.html")
@@ -116,12 +115,60 @@ def startswith(text, starts):
return False return False
@register.filter
def iconify(text):
if not settings.SHOW_THIRD_PARTY_ICONS:
return ""
text = text.lower()
icons = {
"chrome": "chrome.com",
"safari": "www.apple.com",
"windows": "windows.com",
"edge": "microsoft.com",
"firefox": "firefox.com",
"opera": "opera.com",
"unknown": "example.com",
"linux": "kernel.org",
"ios": "www.apple.com",
"mac": "www.apple.com",
"macos": "www.apple.com",
"mac os x": "www.apple.com",
"android": "android.com",
"chrome os": "chrome.com",
"ubuntu": "ubuntu.com",
"fedora": "getfedora.org",
"mobile safari": "www.apple.com",
"chrome mobile ios": "chrome.com",
"chrome mobile": "chrome.com",
"samsung internet": "samsung.com",
"google": "google.com",
"chrome mobile webview": "chrome.com",
"firefox mobile": "firefox.com",
"edge mobile": "microsoft.com",
"chromium": "chromium.org",
}
domain = None
if text.startswith("http"):
domain = urlparse(text).netloc
elif text in icons:
domain = icons[text]
else:
# This fallback works better than you'd think!
domain = text + ".com"
return SafeString(
f'<span class="icon mr-1"><img src="https://icons.duckduckgo.com/ip3/{domain}.ico"></span>'
)
@register.filter @register.filter
def urldisplay(url): def urldisplay(url):
if url.startswith("http"): if url.startswith("http"):
display_url = url.replace("http://", "").replace("https://", "") display_url = url.replace("http://", "").replace("https://", "")
return SafeString( return SafeString(
f"<a href='{url}' title='{url}' rel='nofollow'>{escape(display_url if len(display_url) < 40 else display_url[:40] + '...')}</a>" f"<a href='{url}' title='{url}' rel='nofollow' class='flex items-center'>{iconify(url)} {escape(display_url if len(display_url) < 40 else display_url[:40] + '...')}</a>"
) )
else: else:
return url return url

View File

@@ -18,7 +18,7 @@ import urllib.parse as urlparse
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
# Increment on new releases # Increment on new releases
VERSION = "v0.6.8" VERSION = "v0.7.1"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -318,3 +318,6 @@ SESSION_MEMORY_TIMEOUT = int(os.getenv("SESSION_MEMORY_TIMEOUT", "1800"))
# Should the Shynet version information be displayed? # Should the Shynet version information be displayed?
SHOW_SHYNET_VERSION = os.getenv("SHOW_SHYNET_VERSION", "True") == "True" SHOW_SHYNET_VERSION = os.getenv("SHOW_SHYNET_VERSION", "True") == "True"
# Should Shynet show third-party icons in the dashboard?
SHOW_THIRD_PARTY_ICONS = os.getenv("SHOW_THIRD_PARTY_ICONS", "True") == "True"