lighthouse
This commit is contained in:
@@ -139,6 +139,7 @@ jobs:
|
|||||||
docker logs $container_id || true
|
docker logs $container_id || true
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
done
|
done
|
||||||
|
|
||||||
#- name: Wait for Live Server
|
#- name: Wait for Live Server
|
||||||
# run: |
|
# run: |
|
||||||
# attempts=0
|
# attempts=0
|
||||||
@@ -186,16 +187,13 @@ jobs:
|
|||||||
yarn add lighthouse
|
yarn add lighthouse
|
||||||
npx lighthouse http://live-server:80 --output json --output-path /tmp/lighthouse-report.json --chrome-flags="--headless --no-sandbox --disable-dev-shm-usage"
|
npx lighthouse http://live-server:80 --output json --output-path /tmp/lighthouse-report.json --chrome-flags="--headless --no-sandbox --disable-dev-shm-usage"
|
||||||
|
|
||||||
# Notify-Lighthouse Step
|
- name: upload-to-nextcloud
|
||||||
- name: Notify Lighthouse
|
|
||||||
run: |
|
run: |
|
||||||
docker run --rm \
|
sudo apt-get update && sudo apt-get install -y curl bash findutils
|
||||||
-e PLUGIN_FROM=noreply@gitbase.de \
|
export datetime=`date +%Y-%m-%d_%H-%M-%S`
|
||||||
-e PLUGIN_HOST=smtp.basehosts.de \
|
mkdir -p /tmp/cloudsend/${GITHUB_REF_NAME}/$${datetime}
|
||||||
-e PLUGIN_RECIPIENT=binkrassdufass@gmail.com \
|
mv /tmp/lighthouse-report.json /tmp/cloudsend/${GITHUB_REF_NAME}/$${datetime}/
|
||||||
-e PLUGIN_SUBJECT="Lighthouse Report" \
|
./scripts/cloudsend.sh /tmp/cloudsend/ https://www.basiswolke.de/index.php/s/xHGsypbqiifnGH5
|
||||||
-v ${{ github.workspace }}/tmp:/lighthouse-reports \
|
|
||||||
drillster/drone-email /tmp/lighthouse-report.json
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: deploy
|
name: deploy
|
||||||
@@ -242,24 +240,6 @@ jobs:
|
|||||||
- name: setup node 18
|
- name: setup node 18
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
|
|
||||||
- name: container within a step
|
|
||||||
uses: docker://node:20-alpine3.19
|
|
||||||
with:
|
|
||||||
entrypoint: echo
|
|
||||||
args: "dastest"
|
|
||||||
- name: check docker networking infos
|
|
||||||
run: |
|
|
||||||
echo "${{ toJson(job) }}"
|
|
||||||
echo "${{ github }}"
|
|
||||||
docker network ls
|
|
||||||
docker network inspect bridge
|
|
||||||
docker network inspect host
|
|
||||||
docker network inspect none
|
|
||||||
docker network inspect container:${{ job.services.tibi-server.id }}
|
|
||||||
docker network inspect container:${{ job.services.live-server.id }}
|
|
||||||
docker network inspect container:${{ job.services.mongo.id }}
|
|
||||||
docker network inspect container:${{ job.services.maildev.id }}
|
|
||||||
|
|
||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: "true"
|
FORCE_COLOR: "true"
|
||||||
@@ -310,58 +290,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn build:legacy
|
yarn build:legacy
|
||||||
|
|
||||||
- name: Wait for Live Server
|
|
||||||
run: |
|
|
||||||
attempts=0
|
|
||||||
max_attempts=2
|
|
||||||
while ! curl --output /dev/null --silent --head --fail http://live-server:8081; do
|
|
||||||
if [ $attempts -eq $max_attempts ]; then
|
|
||||||
echo "Live server not ready after $max_attempts attempts"
|
|
||||||
echo "${{ toJson(job) }}"
|
|
||||||
curl -v http://live-server:8081
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
attempts=$((attempts+1))
|
|
||||||
echo "Waiting for live-server to be ready... attempt $attempts"
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Test HTTP Request
|
|
||||||
run: |
|
|
||||||
echo "Live server not ready after $max_attempts attempts"
|
|
||||||
echo "${{ toJson(job) }}"
|
|
||||||
echo "${{ job.services.live-server.id }}"
|
|
||||||
echo "${{ job.services.tibi-server.id }}"
|
|
||||||
echo "${{ job.services.mongo.id }}"
|
|
||||||
docker logs "${{ job.services.tibi-server.id }}"
|
|
||||||
docker logs "${{ job.services.live-server.id }}"
|
|
||||||
curl -v http://live-server:8081
|
|
||||||
|
|
||||||
- name: Install Chrome
|
|
||||||
run: |
|
|
||||||
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
|
||||||
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
|
|
||||||
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y google-chrome-stable
|
|
||||||
|
|
||||||
# Lighthouse Analysis Step
|
|
||||||
- name: Lighthouse Analysis
|
|
||||||
run: |
|
|
||||||
yarn add lighthouse
|
|
||||||
npx lighthouse http://127.0.0.1:8081 --output json --output-path /tmp/lighthouse-report.json --chrome-flags="--headless --no-sandbox --disable-dev-shm-usage"
|
|
||||||
|
|
||||||
# Notify-Lighthouse Step
|
|
||||||
- name: Notify Lighthouse
|
|
||||||
run: |
|
|
||||||
docker run --rm \
|
|
||||||
-e PLUGIN_FROM=noreply@gitbase.de \
|
|
||||||
-e PLUGIN_HOST=smtp.basehosts.de \
|
|
||||||
-e PLUGIN_RECIPIENT=recipient@example.com \
|
|
||||||
-e PLUGIN_SUBJECT="Lighthouse Report" \
|
|
||||||
-v ${{ github.workspace }}/tmp:/lighthouse-reports \
|
|
||||||
drillster/drone-email /tmp/lighthouse-report.json
|
|
||||||
|
|
||||||
- name: deploy
|
- name: deploy
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
env:
|
env:
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -46,6 +46,22 @@ meta:
|
|||||||
subFields:
|
subFields:
|
||||||
- source: aktiv
|
- source: aktiv
|
||||||
- source: meta
|
- source: meta
|
||||||
|
subNavigation:
|
||||||
|
- name: modalForeign
|
||||||
|
defaultSort:
|
||||||
|
field: "path"
|
||||||
|
order: "ASC"
|
||||||
|
views:
|
||||||
|
- type: table
|
||||||
|
columns:
|
||||||
|
- path
|
||||||
|
defaultCallback:
|
||||||
|
eval: |
|
||||||
|
//js
|
||||||
|
(entry) => {
|
||||||
|
parent.selectEntry(entry)
|
||||||
|
}
|
||||||
|
//!js
|
||||||
|
|
||||||
imageFilter:
|
imageFilter:
|
||||||
xs:
|
xs:
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: lighthouseSubpath
|
||||||
|
|
||||||
|
meta:
|
||||||
|
label: Lighthouse Subpaths
|
||||||
|
muiIcon: web
|
||||||
|
views:
|
||||||
|
- type: table
|
||||||
|
columns:
|
||||||
|
- source: lighthouseSubpath
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
public:
|
||||||
|
methods:
|
||||||
|
get: false
|
||||||
|
post: false
|
||||||
|
put: false
|
||||||
|
delete: false
|
||||||
|
user:
|
||||||
|
methods:
|
||||||
|
get: true
|
||||||
|
post: true
|
||||||
|
put: true
|
||||||
|
delete: true
|
||||||
|
|
||||||
|
fields:
|
||||||
|
- type: string
|
||||||
|
name: lighthouseSubpath
|
||||||
|
meta:
|
||||||
|
label: PagespeedPaths
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
name: lighthouse
|
||||||
|
|
||||||
|
meta:
|
||||||
|
label: Lighthouse
|
||||||
|
muiIcon: web
|
||||||
|
views:
|
||||||
|
- type: table
|
||||||
|
mediaQuery: "(min-width: 600px)"
|
||||||
|
columns:
|
||||||
|
- source: insertTime
|
||||||
|
filter: true
|
||||||
|
- source: perfomance
|
||||||
|
filter: true
|
||||||
|
- source: accessibility
|
||||||
|
filter: true
|
||||||
|
- source: bestPractices
|
||||||
|
filter: true
|
||||||
|
- source: seo
|
||||||
|
filter: true
|
||||||
|
- type: simpleList
|
||||||
|
mediaQuery: "(max-width: 599px)"
|
||||||
|
primaryText: insertTime
|
||||||
|
secondaryText: performance
|
||||||
|
tertiaryText: accessibility
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
public:
|
||||||
|
methods:
|
||||||
|
get: false
|
||||||
|
post: false
|
||||||
|
put: false
|
||||||
|
delete: false
|
||||||
|
user:
|
||||||
|
methods:
|
||||||
|
get: true
|
||||||
|
post: true
|
||||||
|
put: true
|
||||||
|
delete: true
|
||||||
|
|
||||||
|
projections:
|
||||||
|
dashboard:
|
||||||
|
|
||||||
|
hooks:
|
||||||
|
post:
|
||||||
|
create:
|
||||||
|
type: javascript
|
||||||
|
file: hooks/lighthouse/post_create.js
|
||||||
|
|
||||||
|
fields:
|
||||||
|
- name: analyzedPaths
|
||||||
|
type: string[]
|
||||||
|
meta:
|
||||||
|
label: Analyzed Paths
|
||||||
|
- name: performance
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Performance
|
||||||
|
- name: accessibility
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Accessibility
|
||||||
|
- name: bestPractices
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Best Practices
|
||||||
|
- name: seo
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: SEO
|
||||||
|
- name: lighthouseMetrics
|
||||||
|
|
||||||
|
type: object
|
||||||
|
meta:
|
||||||
|
label: Lighthouse Metrics
|
||||||
|
subFields:
|
||||||
|
- name: FCPS
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: First Contentful Paint Score
|
||||||
|
- name: FCPV
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: First Contentful Paint Value
|
||||||
|
- name: FMPV
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: First Meaningful Paint Value
|
||||||
|
|
||||||
|
- name: FMPS
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: First Meaningful Paint Score
|
||||||
|
|
||||||
|
- name: SIS
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Speed Index Score
|
||||||
|
|
||||||
|
- name: SIV
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Speed Index Value
|
||||||
|
- name: TTIS
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Time to Interactive Score
|
||||||
|
|
||||||
|
- name: TTIV
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: Time to Interactive Value
|
||||||
|
- name: FPIDS
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: First Potential Input Delay Score
|
||||||
|
|
||||||
|
- name: FPIDV
|
||||||
|
type: number
|
||||||
|
meta:
|
||||||
|
label: First Potential Input Delay Value
|
||||||
+265
-8
@@ -5,15 +5,250 @@ meta:
|
|||||||
servers:
|
servers:
|
||||||
- url: https://tibi-admin-server.code.testversion.online/api/v1/_/demo
|
- url: https://tibi-admin-server.code.testversion.online/api/v1/_/demo
|
||||||
description: code-server
|
description: code-server
|
||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
majorItems:
|
majorItems:
|
||||||
- collection: banner # Sammlung, aus der die Daten für das nächste Element stammen
|
- type: "sectionTitle"
|
||||||
type: reference # Art des Elements, hier ein Referenz-Element
|
title: { de: "Website Perfomance", en: "Website Perfomance" }
|
||||||
style: # Stil des Elements
|
appendix:
|
||||||
upper: rgba(3, 50, 59, 0.7) # Farbe des oberen Teils
|
collection: lighthouse
|
||||||
lower: rgba(3, 50, 59) # Farbe des unteren Teils
|
eval: |
|
||||||
|
(function(){
|
||||||
|
return " " + new Date($date).toLocaleDateString() + ""
|
||||||
|
})()
|
||||||
|
- type: graph
|
||||||
|
filter: false
|
||||||
|
graphType: radialBar
|
||||||
|
until: "lastYear"
|
||||||
|
value: total
|
||||||
|
containerProps:
|
||||||
|
#optional class prop
|
||||||
|
layout:
|
||||||
|
breakBefore: false
|
||||||
|
breakAfter: false
|
||||||
|
size:
|
||||||
|
default: "col-6"
|
||||||
|
small: "col-12"
|
||||||
|
large: "col-3"
|
||||||
|
options:
|
||||||
|
{
|
||||||
|
property: plotOptions,
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
radialBar:
|
||||||
|
{
|
||||||
|
hollow: { margin: 0, size: "70%" },
|
||||||
|
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
|
||||||
|
dataLabels:
|
||||||
|
{
|
||||||
|
name: { offsetY: -10, color: "#000", fontSize: "13px" },
|
||||||
|
value: { color: "#000", fontSize: "30px", show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
graphs:
|
||||||
|
- collection: lighthouse
|
||||||
|
field: performance
|
||||||
|
yAxis: latestValue
|
||||||
|
graphName: { de: "Perfomance Score", en: "Perfomance Score" }
|
||||||
|
dateTimeField: insertTime
|
||||||
|
|
||||||
- collection: content # Wiederholung der vorherigen Elemente
|
- type: graph
|
||||||
|
filter: false
|
||||||
|
graphType: radialBar
|
||||||
|
until: "lastYear"
|
||||||
|
value: total
|
||||||
|
containerProps:
|
||||||
|
#optional class prop
|
||||||
|
layout:
|
||||||
|
breakBefore: false
|
||||||
|
breakAfter: false
|
||||||
|
size:
|
||||||
|
default: "col-6"
|
||||||
|
small: "col-12"
|
||||||
|
large: "col-3"
|
||||||
|
options:
|
||||||
|
{
|
||||||
|
property: plotOptions,
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
radialBar:
|
||||||
|
{
|
||||||
|
hollow: { margin: 0, size: "70%" },
|
||||||
|
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
|
||||||
|
dataLabels:
|
||||||
|
{
|
||||||
|
name: { offsetY: -10, color: "#000", fontSize: "13px" },
|
||||||
|
value: { color: "#000", fontSize: "30px", show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
graphs:
|
||||||
|
- collection: lighthouse
|
||||||
|
field: accessibility
|
||||||
|
yAxis: latestValue
|
||||||
|
graphName: { de: "Accessibility Score", en: "Accessibility Score" }
|
||||||
|
dateTimeField: insertTime
|
||||||
|
|
||||||
|
- type: graph
|
||||||
|
filter: false
|
||||||
|
graphType: radialBar
|
||||||
|
until: "lastYear"
|
||||||
|
value: total
|
||||||
|
containerProps:
|
||||||
|
#optional class prop
|
||||||
|
layout:
|
||||||
|
breakBefore: false
|
||||||
|
breakAfter: false
|
||||||
|
size:
|
||||||
|
default: "col-6"
|
||||||
|
small: "col-12"
|
||||||
|
large: "col-3"
|
||||||
|
options:
|
||||||
|
{
|
||||||
|
property: plotOptions,
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
radialBar:
|
||||||
|
{
|
||||||
|
hollow: { margin: 0, size: "70%" },
|
||||||
|
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
|
||||||
|
dataLabels:
|
||||||
|
{
|
||||||
|
name: { offsetY: -10, color: "#000", fontSize: "13px" },
|
||||||
|
value: { color: "#000", fontSize: "30px", show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
graphs:
|
||||||
|
- collection: lighthouse
|
||||||
|
field: bestPractices
|
||||||
|
yAxis: latestValue
|
||||||
|
graphName: { de: "Best Practices Score", en: "Best Practices Score" }
|
||||||
|
dateTimeField: insertTime
|
||||||
|
|
||||||
|
- type: graph
|
||||||
|
filter: false
|
||||||
|
graphType: radialBar
|
||||||
|
until: "lastYear"
|
||||||
|
value: total
|
||||||
|
containerProps:
|
||||||
|
#optional class prop
|
||||||
|
layout:
|
||||||
|
breakBefore: false
|
||||||
|
breakAfter: false
|
||||||
|
size:
|
||||||
|
default: "col-6"
|
||||||
|
small: "col-12"
|
||||||
|
large: "col-3"
|
||||||
|
options:
|
||||||
|
{
|
||||||
|
property: plotOptions,
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
radialBar:
|
||||||
|
{
|
||||||
|
hollow: { margin: 0, size: "70%" },
|
||||||
|
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
|
||||||
|
dataLabels:
|
||||||
|
{
|
||||||
|
name: { offsetY: -10, color: "#000", fontSize: "13px" },
|
||||||
|
value: { color: "#000", fontSize: "30px", show: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
graphs:
|
||||||
|
- collection: lighthouse
|
||||||
|
field: seo
|
||||||
|
yAxis: latestValue
|
||||||
|
graphName: { de: "SEO Score", en: "SEO Score" }
|
||||||
|
dateTimeField: insertTime
|
||||||
|
|
||||||
|
- type: swiper # Art des Elements, hier ein Swiper
|
||||||
|
containerProps:
|
||||||
|
#optional class prop
|
||||||
|
layout:
|
||||||
|
breakBefore: false
|
||||||
|
breakAfter: false
|
||||||
|
size:
|
||||||
|
default: "col-12"
|
||||||
|
small: "col-12"
|
||||||
|
large: "col-6 row-2-4"
|
||||||
|
|
||||||
|
elements: # Liste der Elemente in diesem Swiper
|
||||||
|
- type: graph
|
||||||
|
title:
|
||||||
|
value: { de: "Ladezeit (Score)", en: "Loadtime (Score)" }
|
||||||
|
xAxis: manual
|
||||||
|
until: "lastYear"
|
||||||
|
filter: false #deaktiviert die Filter möglichkeit für den Nutzer beim diagramm, normalerweise aktiviert. Hierbei sind alle kombinationen x >= until möglich
|
||||||
|
columns:
|
||||||
|
- name: { de: '["Erstes sichtbares", "Element"]', en: '["First Contentful", "Paint"]' }
|
||||||
|
field: lighthouseMetrics.FCPS
|
||||||
|
|
||||||
|
- name: { de: '["Erstes bedeutsames", "Element"]', en: '["First Meaningful", "Paint"]' }
|
||||||
|
field: lighthouseMetrics.FMPS
|
||||||
|
|
||||||
|
- name:
|
||||||
|
{
|
||||||
|
de: '["Maximale potenzielle", "erste", "ingabeverzögerung"]',
|
||||||
|
en: '["Max Potential", "First Input", "Delay"]',
|
||||||
|
}
|
||||||
|
field: lighthouseMetrics.FPIDS
|
||||||
|
|
||||||
|
- name: { de: '["Zeit bis", "zur", "Interaktivität"]', en: '["Time to", "Interactive"]' }
|
||||||
|
field: lighthouseMetrics.TTIS
|
||||||
|
|
||||||
|
- name: { de: '["Geschwindigkeitsindex"]', en: '["Speed Index"]' }
|
||||||
|
field: lighthouseMetrics.SIS
|
||||||
|
|
||||||
|
graphType: "bar"
|
||||||
|
graphs:
|
||||||
|
- graphName: { de: "Lighthouse Metriken", en: "Lighthouse Metrics" }
|
||||||
|
yAxis: latestValue
|
||||||
|
collection: lighthouse
|
||||||
|
dateTimeField: insertTime
|
||||||
|
- type: graph
|
||||||
|
title:
|
||||||
|
value: { de: "Ladezeit (Sekunden)", en: "Loadtime (seconds)" }
|
||||||
|
xAxis: manual
|
||||||
|
until: "lastYear"
|
||||||
|
filter: false #deaktiviert die Filter möglichkeit für den Nutzer beim diagramm, normalerweise aktiviert. Hierbei sind alle kombinationen x >= until möglich
|
||||||
|
columns:
|
||||||
|
- name: { de: '["Erstes sichtbares", "Element"]', en: '["First Contentful", "Paint"]' }
|
||||||
|
field: lighthouseMetrics.FCPV
|
||||||
|
|
||||||
|
- name: { de: '["Erstes bedeutsames", "Element"]', en: '["First Meaningful", "Paint"]' }
|
||||||
|
field: lighthouseMetrics.FMPV
|
||||||
|
|
||||||
|
- name:
|
||||||
|
{
|
||||||
|
de: '["Maximale potenzielle", "erste", "ingabeverzögerung"]',
|
||||||
|
en: '["Max Potential", "First Input", "Delay"]',
|
||||||
|
}
|
||||||
|
field: lighthouseMetrics.FPIDV
|
||||||
|
|
||||||
|
- name: { de: '["Zeit bis", "zur", "Interaktivität"]', en: '["Time to", "Interactive"]' }
|
||||||
|
field: lighthouseMetrics.TTIV
|
||||||
|
|
||||||
|
- name: { de: '["Geschwindigkeitsindex"]', en: '["Speed Index"]' }
|
||||||
|
field: lighthouseMetrics.SIV
|
||||||
|
|
||||||
|
graphType: "bar"
|
||||||
|
graphs:
|
||||||
|
- graphName: { de: "Lighthouse Metriken", en: "Lighthouse Metrics" }
|
||||||
|
yAxis: latestValue
|
||||||
|
collection: lighthouse
|
||||||
|
dateTimeField: insertTime
|
||||||
|
|
||||||
|
- type: "sectionTitle"
|
||||||
|
title: { de: "Seiteninhalte", en: "Page content" }
|
||||||
|
|
||||||
|
- collection: content
|
||||||
type: reference
|
type: reference
|
||||||
style:
|
style:
|
||||||
upper: rgba(3, 50, 59, 0.7)
|
upper: rgba(3, 50, 59, 0.7)
|
||||||
@@ -30,6 +265,26 @@ meta:
|
|||||||
style:
|
style:
|
||||||
upper: rgba(3, 50, 59, 0.7)
|
upper: rgba(3, 50, 59, 0.7)
|
||||||
lower: rgba(3, 50, 59)
|
lower: rgba(3, 50, 59)
|
||||||
|
|
||||||
|
- type: "sectionTitle"
|
||||||
|
title: { de: "Aktionen", en: "Actions" }
|
||||||
|
|
||||||
|
- collection: lighthouse
|
||||||
|
type: action
|
||||||
|
action: "Lighthouse Durchlauf starten"
|
||||||
|
backgroundAction: true
|
||||||
|
modalText:
|
||||||
|
{
|
||||||
|
de: "Zur Analyse der Website werden einige Zeitintensive prozesse gestartet, daher wird dies im Hintergrund ausgeführt. Es kann einige Minuten dauern bis das Dashboard aktuallisiert wird, bitte haben Sie etwas Geduld.",
|
||||||
|
en: "To analyze the website, some time-intensive processes are started, so this is done in the background. It may take a few minutes for the dashboard to be updated, please be patient.",
|
||||||
|
}
|
||||||
|
properties:
|
||||||
|
url: https://allkids-erfurt.de
|
||||||
|
type: post
|
||||||
|
style:
|
||||||
|
upper: rgba(3, 50, 59, 0.7)
|
||||||
|
lower: rgba(3, 50, 59)
|
||||||
|
|
||||||
minorItems: []
|
minorItems: []
|
||||||
|
|
||||||
collections:
|
collections:
|
||||||
@@ -39,8 +294,10 @@ collections:
|
|||||||
- !include collections/forms.yml
|
- !include collections/forms.yml
|
||||||
- !include collections/backups.yml
|
- !include collections/backups.yml
|
||||||
- !include collections/ssr.yml
|
- !include collections/ssr.yml
|
||||||
|
- !include collections/lighthouse.yml
|
||||||
|
- !include collections/lighthouse-subpaths.yml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- cron: "0 * * * *"
|
- cron: "* * * * "
|
||||||
type: javascript
|
type: javascript
|
||||||
file: jobs/requestTemperature.js
|
file: jobs/lighthouse.js
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ module.exports = {
|
|||||||
return -1
|
return -1
|
||||||
},
|
},
|
||||||
ssrPublishCheckCollections: ["content"],
|
ssrPublishCheckCollections: ["content"],
|
||||||
|
LIGHTHOUSE_TOKEN: "AIzaSyC0UxHp3-MpJiDL3ws7pEV6lj57bfIc7GQ",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function obj2str(obj) {
|
|||||||
|
|
||||||
if (obj) return obj
|
if (obj) return obj
|
||||||
}
|
}
|
||||||
|
var { LIGHTHOUSE_TOKEN } = require("../config")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clear SSR cache
|
* clear SSR cache
|
||||||
@@ -46,8 +47,103 @@ function clearSSRCache() {
|
|||||||
context.response.header("X-SSR-Cleared", info.removed)
|
context.response.header("X-SSR-Cleared", info.removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateAverageDynamically(dbObjs) {
|
||||||
|
const sumObj = {}
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
dbObjs.forEach((obj) => {
|
||||||
|
accumulate(obj, sumObj)
|
||||||
|
count++
|
||||||
|
})
|
||||||
|
|
||||||
|
function accumulate(sourceObj, targetObj) {
|
||||||
|
for (const key in sourceObj) {
|
||||||
|
if (typeof sourceObj[key] === "number") {
|
||||||
|
targetObj[key] = (targetObj[key] || 0) + sourceObj[key]
|
||||||
|
} else if (typeof sourceObj[key] === "object" && sourceObj[key] !== null) {
|
||||||
|
targetObj[key] = targetObj[key] || {}
|
||||||
|
accumulate(sourceObj[key], targetObj[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function average(targetObj) {
|
||||||
|
for (const key in targetObj) {
|
||||||
|
if (typeof targetObj[key] === "number") {
|
||||||
|
targetObj[key] = targetObj[key] / count
|
||||||
|
} else if (typeof targetObj[key] === "object") {
|
||||||
|
average(targetObj[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
average(sumObj)
|
||||||
|
return sumObj
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(url) {
|
||||||
|
const response = context.http
|
||||||
|
.fetch(url, {
|
||||||
|
timeout: 300,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.body.json()
|
||||||
|
// needs enough traffic to be collected
|
||||||
|
const cruxMetrics = {
|
||||||
|
"First Contentful Paint": response?.loadingExperience?.metrics?.FIRST_CONTENTFUL_PAINT_MS?.category,
|
||||||
|
"First Input Delay": response?.loadingExperience?.metrics?.FIRST_INPUT_DELAY_MS?.category,
|
||||||
|
}
|
||||||
|
const lighthouse = response.lighthouseResult
|
||||||
|
const lighthouseMetrics = {
|
||||||
|
FCPS: lighthouse.audits["first-contentful-paint"].score * 100,
|
||||||
|
FCPV: lighthouse.audits["first-contentful-paint"].numericValue / 1000,
|
||||||
|
FMPS: lighthouse.audits["first-meaningful-paint"].score * 100,
|
||||||
|
FMPV: lighthouse.audits["first-meaningful-paint"].numericValue / 1000,
|
||||||
|
|
||||||
|
SIS: lighthouse.audits["speed-index"].score * 100,
|
||||||
|
SIV: lighthouse.audits["speed-index"].numericValue / 1000,
|
||||||
|
TTIS: lighthouse.audits["interactive"].score * 100,
|
||||||
|
TTIV: lighthouse.audits["interactive"].numericValue / 1000,
|
||||||
|
|
||||||
|
FPIDS: lighthouse.audits["max-potential-fid"].score * 100,
|
||||||
|
FPIDV: lighthouse.audits["max-potential-fid"].numericValue / 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbObject = {
|
||||||
|
cruxMetrics,
|
||||||
|
lighthouseMetrics,
|
||||||
|
performance: lighthouse.categories.performance.score * 100,
|
||||||
|
accessibility: lighthouse.categories.accessibility.score * 100,
|
||||||
|
bestPractices: lighthouse.categories["best-practices"].score * 100,
|
||||||
|
seo: lighthouse.categories.seo.score * 100,
|
||||||
|
}
|
||||||
|
return dbObject
|
||||||
|
}
|
||||||
|
function setUpQuery(subPath = "/") {
|
||||||
|
const api = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
|
||||||
|
let params = `category=performance&category=accessibility&category=best-practices&category=seo`
|
||||||
|
|
||||||
|
const parameters = {
|
||||||
|
url: encodeURIComponent(`https://allkids-erfurt.de/${subPath}`),
|
||||||
|
key: LIGHTHOUSE_TOKEN,
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = `${api}?`
|
||||||
|
for (let key in parameters) {
|
||||||
|
query += `${key}=${parameters[key]}&`
|
||||||
|
}
|
||||||
|
query += params // Append other parameters without URL encoding
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
log,
|
log,
|
||||||
clearSSRCache,
|
clearSSRCache,
|
||||||
obj2str,
|
obj2str,
|
||||||
|
run,
|
||||||
|
setUpQuery,
|
||||||
|
calculateAverageDynamically,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
var { setUpQuery, calculateAverageDynamically, run } = require("../lib/utils")
|
||||||
|
;(function () {
|
||||||
|
let subPaths = context.db.find("lighthouseSubpath")
|
||||||
|
let urls = []
|
||||||
|
for (let i = 0; i < subPaths.length; i++) {
|
||||||
|
urls.push(setUpQuery(subPaths[i].lighthouseSubpath))
|
||||||
|
}
|
||||||
|
let dbObjs = []
|
||||||
|
urls.forEach((url) => {
|
||||||
|
console.log("URL:", url)
|
||||||
|
dbObjs.push(run(url))
|
||||||
|
})
|
||||||
|
let dbObject = calculateAverageDynamically(dbObjs)
|
||||||
|
dbObject.analyzedPaths = [...subPaths].map((subPath) => subPath.lighthouseSubpath)
|
||||||
|
return { data: dbObject }
|
||||||
|
})()
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
var { setUpQuery, calculateAverageDynamically, run } = require("../hooks/lib/utils")
|
||||||
|
;(function () {
|
||||||
|
console.log("Running lighthouse job")
|
||||||
|
let subPaths = context.db.find("lighthouseSubpath")
|
||||||
|
let urls = []
|
||||||
|
for (let i = 0; i < subPaths.length; i++) {
|
||||||
|
urls.push(setUpQuery(subPaths[i].lighthouseSubpath))
|
||||||
|
}
|
||||||
|
let dbObjs = []
|
||||||
|
urls.forEach((url) => {
|
||||||
|
console.log("URL:", url)
|
||||||
|
dbObjs.push(run(url))
|
||||||
|
})
|
||||||
|
let dbObject = calculateAverageDynamically(dbObjs)
|
||||||
|
dbObject.analyzedPaths = [...subPaths].map((subPath) => subPath.lighthouseSubpath)
|
||||||
|
context.db.create("lighthouse", dbObject)
|
||||||
|
})()
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
function requestTemperature() {
|
|
||||||
let response = context.http.fetch(
|
|
||||||
"https://api.openweathermap.org/data/2.5/weather?lat=50.98&lon=11.03&appid=21fa3d5930956682000f7a2db5f357c0"
|
|
||||||
)
|
|
||||||
let data = response.body.json()
|
|
||||||
let temperatureEntries = context.db.find("temperature")
|
|
||||||
context.db.update("temperature", temperatureEntries[0].id, { temperature: data.main.temp - 273.15 })
|
|
||||||
}
|
|
||||||
requestTemperature()
|
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
"core-js": "3.32.2",
|
"core-js": "3.32.2",
|
||||||
"cssnano": "^6.0.1",
|
"cssnano": "^6.0.1",
|
||||||
"external-svg-loader": "^1.6.10",
|
"external-svg-loader": "^1.6.10",
|
||||||
"lighthouse": "^11.3.0",
|
|
||||||
"live-server": "1.2.1",
|
"live-server": "1.2.1",
|
||||||
"postcss-import": "^15.1.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
|
|||||||
@@ -2171,9 +2171,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@puppeteer/browsers@npm:1.8.0":
|
"@puppeteer/browsers@npm:1.9.0":
|
||||||
version: 1.8.0
|
version: 1.9.0
|
||||||
resolution: "@puppeteer/browsers@npm:1.8.0"
|
resolution: "@puppeteer/browsers@npm:1.9.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
extract-zip: 2.0.1
|
extract-zip: 2.0.1
|
||||||
@@ -2184,7 +2184,7 @@ __metadata:
|
|||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
bin:
|
bin:
|
||||||
browsers: lib/cjs/main-cli.js
|
browsers: lib/cjs/main-cli.js
|
||||||
checksum: 94bd9ba2c9b33bf41a5d7ae7c4c7384afd09c54aa5356b6e2e2ed67d7f3f2d76941e3cb431f270baa9e572701f12289ae58bcb63a46b251deaff4030c64f2426
|
checksum: 9a1cad8b760333a46f6ea69f07b1049b9b5da3aef3354da76089176998018aa10c4eeb5adb965405c9a899da81205cf60e845beca643bfe5a4a63410328fe131
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -3221,15 +3221,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"chromium-bidi@npm:0.4.33":
|
"chromium-bidi@npm:0.5.1":
|
||||||
version: 0.4.33
|
version: 0.5.1
|
||||||
resolution: "chromium-bidi@npm:0.4.33"
|
resolution: "chromium-bidi@npm:0.5.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
mitt: 3.0.1
|
mitt: 3.0.1
|
||||||
urlpattern-polyfill: 9.0.0
|
urlpattern-polyfill: 9.0.0
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
devtools-protocol: "*"
|
devtools-protocol: "*"
|
||||||
checksum: a7c8191a541a97a01fb7781b4258a2cf6d046bfa572065db60c97d59510670a2b9b62cf6adb7f78de9b8ff5abbc5f8a9086adec9aee1da3994576e5cd92dd07d
|
checksum: be7f695857210869dbd1cfa869285367c7982ae9a0acda2cc5b20265eb83748d98a641477bf3405da72e62dc39dd71b18829cc09d04d9fb06c9cd1c971e30046
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -5562,9 +5562,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lighthouse@npm:^11.3.0":
|
"lighthouse@npm:^11.4.0":
|
||||||
version: 11.3.0
|
version: 11.4.0
|
||||||
resolution: "lighthouse@npm:11.3.0"
|
resolution: "lighthouse@npm:11.4.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sentry/node": ^6.17.4
|
"@sentry/node": ^6.17.4
|
||||||
axe-core: ^4.8.1
|
axe-core: ^4.8.1
|
||||||
@@ -5585,11 +5585,12 @@ __metadata:
|
|||||||
open: ^8.4.0
|
open: ^8.4.0
|
||||||
parse-cache-control: 1.0.1
|
parse-cache-control: 1.0.1
|
||||||
ps-list: ^8.0.0
|
ps-list: ^8.0.0
|
||||||
puppeteer-core: ^21.5.0
|
puppeteer-core: ^21.5.2
|
||||||
robots-parser: ^3.0.1
|
robots-parser: ^3.0.1
|
||||||
semver: ^5.3.0
|
semver: ^5.3.0
|
||||||
speedline-core: ^1.4.3
|
speedline-core: ^1.4.3
|
||||||
third-party-web: ^0.24.0
|
third-party-web: ^0.24.1
|
||||||
|
tldts-icann: ^6.1.0
|
||||||
ws: ^7.0.0
|
ws: ^7.0.0
|
||||||
yargs: ^17.3.1
|
yargs: ^17.3.1
|
||||||
yargs-parser: ^21.0.0
|
yargs-parser: ^21.0.0
|
||||||
@@ -5597,7 +5598,7 @@ __metadata:
|
|||||||
chrome-debug: core/scripts/manual-chrome-launcher.js
|
chrome-debug: core/scripts/manual-chrome-launcher.js
|
||||||
lighthouse: cli/index.js
|
lighthouse: cli/index.js
|
||||||
smokehouse: cli/test/smokehouse/frontends/smokehouse-bin.js
|
smokehouse: cli/test/smokehouse/frontends/smokehouse-bin.js
|
||||||
checksum: a6297c09e66d57efa8bb85fcf4af007e04ce1922cb7e2e328ae04e93df82d03ec35cf43b9e87a504253cb1c80b1229f249a7f7f71ed40bd38b78ce91c24067af
|
checksum: 403e9a9cd572cbbb965f419474c13b22db1cdd03d1e297d5edd8cde4510ccd5c6de4dcb8fd2324924085599c17b9d380d9d2a8bd564748eb2b64f1e46589a4aa
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -7451,17 +7452,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"puppeteer-core@npm:^21.5.0":
|
"puppeteer-core@npm:^21.5.2":
|
||||||
version: 21.5.2
|
version: 21.6.1
|
||||||
resolution: "puppeteer-core@npm:21.5.2"
|
resolution: "puppeteer-core@npm:21.6.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@puppeteer/browsers": 1.8.0
|
"@puppeteer/browsers": 1.9.0
|
||||||
chromium-bidi: 0.4.33
|
chromium-bidi: 0.5.1
|
||||||
cross-fetch: 4.0.0
|
cross-fetch: 4.0.0
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
devtools-protocol: 0.0.1203626
|
devtools-protocol: 0.0.1203626
|
||||||
ws: 8.14.2
|
ws: 8.15.1
|
||||||
checksum: 62d3ef8e8a04b027da298f480db8c42bbb5d2ab06fee864749b65da389ca3376bb65d1b87aed8cd1e49da2470c877c38620cad87f48bd7309122721f427689ae
|
checksum: 954c5afd4daaea8333cc17ebfdea6fe898d1977b1e93e526ff80a1fe1c6ad970511ad90720c03de6198961906e4c2f7ba9b593f41a252504303eeaff3f883b4c
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -8627,7 +8628,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"third-party-web@npm:^0.24.0":
|
"third-party-web@npm:^0.24.1":
|
||||||
version: 0.24.1
|
version: 0.24.1
|
||||||
resolution: "third-party-web@npm:0.24.1"
|
resolution: "third-party-web@npm:0.24.1"
|
||||||
checksum: 21363a1506c8c5186ef9ca81c32a25c20ab3b4976010ca6976066f05e6cd56c42bd121e385e071a18299a28032ad4f593ee367bf3a46c459424adbc0c3182b37
|
checksum: 21363a1506c8c5186ef9ca81c32a25c20ab3b4976010ca6976066f05e6cd56c42bd121e385e071a18299a28032ad4f593ee367bf3a46c459424adbc0c3182b37
|
||||||
@@ -8656,6 +8657,7 @@ __metadata:
|
|||||||
autoprefixer: ^10.4.15
|
autoprefixer: ^10.4.15
|
||||||
browser-sync: ^2.29.3
|
browser-sync: ^2.29.3
|
||||||
chokidar: ^3.5.3
|
chokidar: ^3.5.3
|
||||||
|
chrome-launcher: ^1.1.0
|
||||||
connect-history-api-fallback: ^2.0.0
|
connect-history-api-fallback: ^2.0.0
|
||||||
core-js: 3.32.2
|
core-js: 3.32.2
|
||||||
cssnano: ^6.0.1
|
cssnano: ^6.0.1
|
||||||
@@ -8665,7 +8667,7 @@ __metadata:
|
|||||||
fluent-svelte: ^1.6.0
|
fluent-svelte: ^1.6.0
|
||||||
http-proxy-middleware: ^2.0.6
|
http-proxy-middleware: ^2.0.6
|
||||||
less: ^4.2.0
|
less: ^4.2.0
|
||||||
lighthouse: ^11.3.0
|
lighthouse: ^11.4.0
|
||||||
live-server: 1.2.1
|
live-server: 1.2.1
|
||||||
morgan: ^1.10.0
|
morgan: ^1.10.0
|
||||||
node-fetch: ^3.3.2
|
node-fetch: ^3.3.2
|
||||||
@@ -8691,6 +8693,22 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"tldts-core@npm:^6.1.1":
|
||||||
|
version: 6.1.1
|
||||||
|
resolution: "tldts-core@npm:6.1.1"
|
||||||
|
checksum: 0ce2ec0d8ec30153d257cf885da68c0c2b2e29dacd23525b52a9f023ae02cb833d7602e5abe1a00427dc89cf4ba6cfb228acd269bc28b38a555c20b9cb3314e3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"tldts-icann@npm:^6.1.0":
|
||||||
|
version: 6.1.1
|
||||||
|
resolution: "tldts-icann@npm:6.1.1"
|
||||||
|
dependencies:
|
||||||
|
tldts-core: ^6.1.1
|
||||||
|
checksum: b5a123fef9f207e82b9c2ffa02013318160ebb080e2b5a56deec6a4a6dd6538fb889c54e95de05061f2aae4e117c60fa9caba46586619daac25d6f214d0a93d0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"to-fast-properties@npm:^2.0.0":
|
"to-fast-properties@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "to-fast-properties@npm:2.0.0"
|
resolution: "to-fast-properties@npm:2.0.0"
|
||||||
@@ -9098,9 +9116,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ws@npm:8.14.2":
|
"ws@npm:8.15.1":
|
||||||
version: 8.14.2
|
version: 8.15.1
|
||||||
resolution: "ws@npm:8.14.2"
|
resolution: "ws@npm:8.15.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
bufferutil: ^4.0.1
|
bufferutil: ^4.0.1
|
||||||
utf-8-validate: ">=5.0.2"
|
utf-8-validate: ">=5.0.2"
|
||||||
@@ -9109,7 +9127,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 3ca0dad26e8cc6515ff392b622a1467430814c463b3368b0258e33696b1d4bed7510bc7030f7b72838b9fdeb8dbd8839cbf808367d6aae2e1d668ce741d4308b
|
checksum: 8c67365f6e6134278ad635d558bfce466d7ef7543a043baea333aaa430429f0af8a130c0c36e7dd78f918d68167a659ba9b5067330b77c4b279e91533395952b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user