Merge branch 'develop' of github.com:thecodingmachine/workadventure into oidcProfile
5
.github/workflows/build-and-deploy.yml
vendored
@ -149,6 +149,10 @@ jobs:
|
||||
# Create a slugified value of the branch
|
||||
- uses: rlespinasse/github-slug-action@3.1.0
|
||||
|
||||
- name: Set ADMIN_URL if "deploy-connect-to-admin" label is set
|
||||
run: echo "ADMIN_API_URL=https://${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}.test.workadventu.re" >> $GITHUB_ENV
|
||||
if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy-connect-to-admin') }}
|
||||
|
||||
- name: Write certificate
|
||||
run: echo "${CERTS_PRIVATE_KEY}" > secret.key && chmod 0600 secret.key
|
||||
env:
|
||||
@ -185,6 +189,7 @@ jobs:
|
||||
env:
|
||||
KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }}
|
||||
ADMIN_API_TOKEN: ${{ secrets.ADMIN_API_TOKEN }}
|
||||
ADMIN_SOCKETS_TOKEN: ${{ secrets.ADMIN_SOCKETS_TOKEN }}
|
||||
JITSI_ISS: ${{ secrets.JITSI_ISS }}
|
||||
JITSI_URL: ${{ secrets.JITSI_URL }}
|
||||
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
|
||||
|
9
.github/workflows/continuous_integration.yml
vendored
@ -56,6 +56,14 @@ jobs:
|
||||
ADMIN_URL: "//localhost:80"
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Build iframe-api"
|
||||
run: yarn run build-iframe-api
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Typecheck"
|
||||
run: yarn run typecheck
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Svelte check"
|
||||
run: yarn run svelte-check
|
||||
working-directory: "front"
|
||||
@ -165,4 +173,3 @@ jobs:
|
||||
- name: "Prettier"
|
||||
run: yarn run pretty-check
|
||||
working-directory: "back"
|
||||
|
||||
|
134
.github/workflows/end_to_end_tests.yml
vendored
@ -1,6 +1,4 @@
|
||||
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
|
||||
|
||||
name: "End to end tests"
|
||||
name: 'End to end tests'
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -10,117 +8,31 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
start-runner:
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
||||
name: Start self-hosted EC2 runner
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
label: ${{ steps.start-ec2-runner.outputs.label }}
|
||||
ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
- name: Start EC2 runner
|
||||
id: start-ec2-runner
|
||||
uses: machulav/ec2-github-runner@v2
|
||||
with:
|
||||
mode: start
|
||||
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
ec2-image-id: ami-094dbcc53250a2480
|
||||
ec2-instance-type: m5.2xlarge
|
||||
subnet-id: subnet-0ac40025f559df1bc
|
||||
security-group-id: sg-0e36e96e3b8ed2d64
|
||||
#iam-role-name: my-role-name # optional, requires additional permissions
|
||||
#aws-resource-tags: > # optional, requires additional permissions
|
||||
# [
|
||||
# {"Key": "Name", "Value": "ec2-github-runner"},
|
||||
# {"Key": "GitHubRepository", "Value": "${{ github.repository }}"}
|
||||
# ]
|
||||
|
||||
|
||||
end-to-end-tests:
|
||||
name: "End-to-end testcafe tests"
|
||||
|
||||
needs: start-runner # required to start the main job when the runner is ready
|
||||
runs-on: ${{ needs.start-runner.outputs.label }} # run the job on the newly created runner
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2.0.0"
|
||||
|
||||
- name: "Setup NodeJS"
|
||||
uses: actions/setup-node@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: npm install
|
||||
working-directory: "tests"
|
||||
|
||||
- name: "Setup .env file"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
working-directory: tests
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps
|
||||
- name: 'Setup .env file'
|
||||
run: cp .env.template .env
|
||||
|
||||
- name: "Edit ownership of file for test cases"
|
||||
run: sudo chown 1000:1000 -R .
|
||||
|
||||
- name: "Start environment"
|
||||
run: LIVE_RELOAD=0 docker-compose up -d
|
||||
|
||||
- name: "Wait for environment to build (and downloading testcafe image)"
|
||||
run: (docker-compose -f docker-compose.testcafe.yml build &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully"
|
||||
|
||||
# - name: "temp debug: display logs"
|
||||
# run: docker-compose logs
|
||||
#
|
||||
# - name: "Wait for back start"
|
||||
# run: docker-compose logs -f back | grep -q "WorkAdventure HTTP API starting on port"
|
||||
#
|
||||
# - name: "Wait for pusher start"
|
||||
# run: docker-compose logs -f pusher | grep -q "WorkAdventure starting on port"
|
||||
|
||||
- name: "Run tests"
|
||||
run: PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
|
||||
|
||||
- name: Upload failed tests
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Start WorkAdventure
|
||||
run: docker-compose -f docker-compose.yaml -f docker-compose.e2e.yml up -d --build
|
||||
- name: Wait for environment to Start
|
||||
run: sleep 60
|
||||
- name: Run Playwright tests
|
||||
run: npm run test-prod-like
|
||||
working-directory: tests
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: my-artifact
|
||||
path: './tests/screenshots/'
|
||||
|
||||
- name: Display state
|
||||
if: ${{ failure() }}
|
||||
run: docker-compose ps
|
||||
|
||||
- name: Display logs
|
||||
if: ${{ failure() }}
|
||||
run: docker-compose logs
|
||||
|
||||
stop-runner:
|
||||
name: Stop self-hosted EC2 runner
|
||||
needs:
|
||||
- start-runner # required to get output from the start-runner job
|
||||
- end-to-end-tests # required to wait when the main job is done
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
- name: Stop EC2 runner
|
||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|
||||
uses: machulav/ec2-github-runner@v2
|
||||
with:
|
||||
mode: stop
|
||||
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||
label: ${{ needs.start-runner.outputs.label }}
|
||||
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
|
||||
name: playwright-report
|
||||
path: tests/playwright-report/
|
||||
retention-days: 30
|
||||
|
28
.github/workflows/push-to-npm.yml
vendored
@ -22,31 +22,10 @@ jobs:
|
||||
run: cat package.json
|
||||
working-directory: "front/packages/iframe-api-typings"
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v1
|
||||
with:
|
||||
version: '3.x'
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: yarn install
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Install messages dependencies"
|
||||
run: yarn install
|
||||
working-directory: "messages"
|
||||
|
||||
- name: "Build proto messages"
|
||||
run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front
|
||||
working-directory: "messages"
|
||||
|
||||
- name: "Create index.html"
|
||||
run: ./templater.sh
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Generate i18n files"
|
||||
run: yarn run typesafe-i18n
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Build"
|
||||
run: yarn run build-typings
|
||||
env:
|
||||
@ -54,13 +33,6 @@ jobs:
|
||||
ADMIN_URL: "//localhost:80"
|
||||
working-directory: "front"
|
||||
|
||||
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
|
||||
- name: Copy typings to package dir
|
||||
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
|
||||
|
||||
- name: Copy typings to package dir (2)
|
||||
run: cp -R front/dist/src/Api front/packages/iframe-api-typings/Api
|
||||
|
||||
- name: Install dependencies in package
|
||||
run: yarn install
|
||||
working-directory: "front/packages/iframe-api-typings"
|
||||
|
@ -67,46 +67,20 @@ $ docker-compose exec back yarn run pretty
|
||||
|
||||
WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test.
|
||||
|
||||
Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine), or an end-to-end test (we use Testcafe).
|
||||
Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine), or an end-to-end test (we use Playwright).
|
||||
|
||||
If you are providing a new feature, you should setup a test map in the `maps/tests` directory. The test map should contain
|
||||
some description text describing how to test the feature.
|
||||
|
||||
* if the features is meant to be manually tested, you should modify the `maps/tests/index.html` file to add a reference
|
||||
to your newly created test map
|
||||
* if the features can be automatically tested, please provide a testcafe test
|
||||
* if the features can be automatically tested, please provide an end-to-end test
|
||||
|
||||
#### Running testcafe tests
|
||||
#### Running end-to-end tests
|
||||
|
||||
End-to-end tests are available in the "/tests" directory.
|
||||
|
||||
To run these tests locally:
|
||||
|
||||
```console
|
||||
$ LIVE_RELOAD=0 docker-compose up -d
|
||||
$ cd tests
|
||||
$ npm install
|
||||
$ npm run test
|
||||
```
|
||||
|
||||
Note: If your tests fail on a Javascript error in "sockjs", this is due to the
|
||||
Webpack live reload. The Webpack live reload feature is conflicting with testcafe. This is why we recommend starting
|
||||
WorkAdventure with the `LIVE_RELOAD=0` environment variable.
|
||||
|
||||
End-to-end tests can take a while to run. To run only one test, use:
|
||||
|
||||
```console
|
||||
$ npm run test -- tests/[name of the test file].ts
|
||||
```
|
||||
|
||||
You can also run the tests inside a container (but you will not have visual feedbacks on your test, so we recommend using
|
||||
the local tests).
|
||||
|
||||
```console
|
||||
$ LIVE_RELOAD=0 docker-compose up -d
|
||||
# Wait 2-3 minutes for the environment to start, then:
|
||||
$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up
|
||||
```
|
||||
More information on running end-to-end tests can be found in the [`/tests/README`](/tests/README.md).
|
||||
|
||||
### A bad wording or a missing language
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
# protobuf build
|
||||
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
|
||||
WORKDIR /usr/src
|
||||
COPY messages/yarn.lock messages/package.json ./
|
||||
RUN yarn install
|
||||
COPY messages .
|
||||
RUN yarn install && yarn proto
|
||||
RUN yarn proto
|
||||
|
||||
# typescript build
|
||||
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2
|
||||
@ -18,9 +20,9 @@ RUN yarn run tsc
|
||||
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d
|
||||
WORKDIR /usr/src
|
||||
COPY back/yarn.lock back/package.json ./
|
||||
COPY --from=builder2 /usr/src/dist /usr/src/dist
|
||||
ENV NODE_ENV=production
|
||||
RUN yarn install --production
|
||||
COPY --from=builder2 /usr/src/dist /usr/src/dist
|
||||
|
||||
USER node
|
||||
CMD ["yarn", "run", "runprod"]
|
||||
|
@ -13,7 +13,7 @@ DOMAIN=workadventure.localhost
|
||||
|
||||
# Subdomains
|
||||
# MUST match the DOMAIN variable above
|
||||
FRONT_HOST=front.workadventure.localhost
|
||||
FRONT_HOST=play.workadventure.localhost
|
||||
PUSHER_HOST=pusher.workadventure.localhost
|
||||
BACK_HOST=api.workadventure.localhost
|
||||
MAPS_HOST=maps.workadventure.localhost
|
||||
|
@ -27,10 +27,7 @@ services:
|
||||
|
||||
|
||||
front:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: front/Dockerfile
|
||||
#image: thecodingmachine/workadventure-front:${VERSION}
|
||||
image: thecodingmachine/workadventure-front:${VERSION}
|
||||
environment:
|
||||
- DEBUG_MODE
|
||||
- JITSI_URL
|
||||
@ -60,10 +57,7 @@ services:
|
||||
restart: ${RESTART_POLICY}
|
||||
|
||||
pusher:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: pusher/Dockerfile
|
||||
#image: thecodingmachine/workadventure-pusher:${VERSION}
|
||||
image: thecodingmachine/workadventure-pusher:${VERSION}
|
||||
command: yarn run runprod
|
||||
environment:
|
||||
- SECRET_JITSI_KEY
|
||||
@ -77,7 +71,7 @@ services:
|
||||
- "traefik.http.routers.pusher.rule=Host(`${PUSHER_HOST}`)"
|
||||
- "traefik.http.routers.pusher.entryPoints=web"
|
||||
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.pusher-ssl.rule=Host(${PUSHER_HOST}`)"
|
||||
- "traefik.http.routers.pusher-ssl.rule=Host(`${PUSHER_HOST}`)"
|
||||
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.pusher-ssl.service=pusher"
|
||||
- "traefik.http.routers.pusher-ssl.tls=true"
|
||||
@ -85,10 +79,7 @@ services:
|
||||
restart: ${RESTART_POLICY}
|
||||
|
||||
back:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: back/Dockerfile
|
||||
#image: thecodingmachine/workadventure-back:${VERSION}
|
||||
image: thecodingmachine/workadventure-back:${VERSION}
|
||||
command: yarn run runprod
|
||||
environment:
|
||||
- SECRET_JITSI_KEY
|
||||
@ -119,7 +110,7 @@ services:
|
||||
image: matthiasluedtke/iconserver:v3.13.0
|
||||
labels:
|
||||
- "traefik.http.routers.icon.rule=Host(`${ICON_HOST}`)"
|
||||
- "traefik.http.routers.icon.entryPoints=web,traefik"
|
||||
- "traefik.http.routers.icon.entryPoints=web"
|
||||
- "traefik.http.services.icon.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.icon-ssl.rule=Host(`${ICON_HOST}`)"
|
||||
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
|
||||
|
@ -4,7 +4,7 @@
|
||||
local tag = namespace,
|
||||
local url = namespace+".test.workadventu.re",
|
||||
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
|
||||
local adminUrl = if std.startsWith(namespace, "admin") then "https://"+url else null,
|
||||
local adminUrl = if std.objectHas(env, 'ADMIN_API_URL') then env.ADMIN_API_URL else null,
|
||||
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
|
||||
"version": "1.0",
|
||||
"containers": {
|
||||
|
38
docker-compose.e2e.yml
Normal file
@ -0,0 +1,38 @@
|
||||
version: '3.5'
|
||||
services:
|
||||
# overrides for e2e tests to be closer to production
|
||||
# use with command:
|
||||
# docker-compose -f docker-compose.yaml -f docker-compose.e2e.yml up -d --build
|
||||
|
||||
front:
|
||||
image: 'wa-front-e2e'
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: front/Dockerfile
|
||||
command: /bin/sh -c "/templater.sh && envsubst < /usr/share/nginx/html/env-config.template.js > /usr/share/nginx/html/env-config.js && exec nginx -g 'daemon off;'"
|
||||
volumes: []
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)"
|
||||
- "traefik.http.routers.front.entryPoints=web"
|
||||
- "traefik.http.services.front.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.front-ssl.rule=Host(`play.workadventure.localhost`)"
|
||||
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.front-ssl.tls=true"
|
||||
- "traefik.http.routers.front-ssl.service=front"
|
||||
|
||||
pusher:
|
||||
image: 'wa-pusher-e2e'
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: pusher/Dockerfile
|
||||
command: yarn run runprod
|
||||
volumes: []
|
||||
|
||||
back:
|
||||
image: 'wa-back-e2e'
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: back/Dockerfile
|
||||
command: yarn run runprod
|
||||
volumes: []
|
@ -1,19 +0,0 @@
|
||||
version: "3.5"
|
||||
services:
|
||||
testcafe:
|
||||
build: tests/
|
||||
working_dir: /project/tests
|
||||
command:
|
||||
- --dev
|
||||
# Run as root to have the right to access /var/run/docker.sock
|
||||
user: root
|
||||
environment:
|
||||
BROWSER: "chromium --use-fake-device-for-media-stream"
|
||||
PROJECT_DIR: ${PROJECT_DIR}
|
||||
ADMIN_API_TOKEN: ${ADMIN_API_TOKEN}
|
||||
volumes:
|
||||
- ./:/project
|
||||
- ./maps:/maps
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
@ -52,7 +52,6 @@ services:
|
||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
|
||||
LIVE_RELOAD: "$LIVE_RELOAD:-true"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
- ./front:/usr/src/app
|
||||
|
@ -9,9 +9,11 @@ On your map, you can define special zones (meeting rooms) that will trigger the
|
||||
|
||||
In order to create Jitsi meet zones:
|
||||
|
||||
* You must create a specific layer.
|
||||
* In layer properties, you MUST add a "`jitsiRoom`" property (of type "`string`"). The value of the property is the name of the room in Jitsi. Note: the name of the room will be "slugified" and prepended with the name of the instance of the map (so that different instances of the map have different rooms)
|
||||
* You may also use "jitsiWidth" property (of type "number" between 0 and 100) to control the width of the iframe containing the meeting room.
|
||||
* You must create a specific layer.
|
||||
* In layer properties, you MUST add a "`jitsiRoom`" property (of type "`string`"). The value of the property is the name of the room in Jitsi. Note: the name of the room will be "slugified" and prepended with the name of the instance of the map (so that different instances of the map have different rooms)
|
||||
* You may also use "jitsiWidth" property (of type "number" between 0 and 100) to control the width of the iframe containing the meeting room.
|
||||
|
||||
You can have this layer (i.e. your meeting area) to be selectable as the precise location for your meeting using the [Google Calendar integration for Work Adventure](/integrations/google-calendar). To do so, you must set the `meetingRoomLabel` property. You can provide any name that you would like your meeting room to have (as a string).
|
||||
|
||||
## Triggering of the "Jitsi meet" action
|
||||
|
||||
|
11
front/.gitignore
vendored
@ -1,14 +1,7 @@
|
||||
/node_modules/
|
||||
/dist/*.js
|
||||
/dist/*.js.map
|
||||
/dist/*.js.LICENSE.txt
|
||||
/dist/main.*.css
|
||||
/dist/main.*.css.map
|
||||
/dist/tests/
|
||||
dist/
|
||||
/yarn-error.log
|
||||
/package-lock.json
|
||||
/dist/webpack.config.js
|
||||
/dist/webpack.config.js.map
|
||||
/dist/src
|
||||
*.sh
|
||||
!templater.sh
|
||||
/public/iframe_api.js
|
||||
|
@ -1,35 +1,28 @@
|
||||
# protobuf build
|
||||
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
|
||||
|
||||
WORKDIR /usr/src/messages
|
||||
WORKDIR /usr/src
|
||||
COPY messages/yarn.lock messages/package.json ./
|
||||
RUN yarn install
|
||||
COPY messages .
|
||||
RUN yarn install && yarn ts-proto
|
||||
RUN yarn ts-proto
|
||||
|
||||
WORKDIR /usr/src/front
|
||||
# typescript build
|
||||
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2
|
||||
WORKDIR /usr/src
|
||||
COPY front/yarn.lock front/package.json ./
|
||||
RUN yarn install
|
||||
COPY front .
|
||||
|
||||
# move messages to front
|
||||
RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generated
|
||||
COPY --from=builder /usr/src/ts-proto-generated/protos src/Messages/ts-proto-generated
|
||||
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts
|
||||
RUN cp -r ../messages/JsonMessages/* src/Messages/JsonMessages
|
||||
COPY --from=builder /usr/src/JsonMessages src/Messages/JsonMessages
|
||||
RUN yarn run typesafe-i18n && yarn run build-iframe-api && yarn build
|
||||
|
||||
RUN yarn install && yarn run typesafe-i18n && yarn build
|
||||
# final production image
|
||||
FROM nginx:1.21.6-alpine
|
||||
|
||||
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
||||
# iframe.html is only in dev mode to circumvent a limitation
|
||||
RUN rm dist/iframe.html
|
||||
COPY front/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY front/templater.sh /
|
||||
COPY --from=builder2 /usr/src/dist /usr/share/nginx/html
|
||||
|
||||
FROM thecodingmachine/nodejs:14-apache
|
||||
|
||||
COPY --from=builder --chown=docker:docker /usr/src/front/dist dist
|
||||
COPY front/templater.sh .
|
||||
|
||||
USER root
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
|
||||
&& apt-get install -y \
|
||||
gettext-base \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
USER docker
|
||||
|
||||
ENV STARTUP_COMMAND_0="./templater.sh"
|
||||
ENV STARTUP_COMMAND_1="envsubst < dist/env-config.template.js > dist/env-config.js"
|
||||
ENV APACHE_DOCUMENT_ROOT=dist/
|
||||
EXPOSE 80
|
||||
CMD ["/bin/sh", "-c", "/templater.sh && envsubst < /usr/share/nginx/html/env-config.template.js > /usr/share/nginx/html/env-config.js && exec nginx -g 'daemon off;'"]
|
||||
|
6
front/dist/.gitignore
vendored
@ -1,6 +0,0 @@
|
||||
index.html
|
||||
/js/
|
||||
/fonts/
|
||||
style.*.css
|
||||
!env-config.template.js
|
||||
*.png
|
27
front/dist/env-config.template.js
vendored
@ -1,27 +0,0 @@
|
||||
window.env = {
|
||||
SKIP_RENDER_OPTIMIZATIONS: '${SKIP_RENDER_OPTIMIZATIONS}',
|
||||
DISABLE_NOTIFICATIONS: '${DISABLE_NOTIFICATIONS}',
|
||||
PUSHER_URL: '${PUSHER_URL}',
|
||||
UPLOADER_URL: '${UPLOADER_URL}',
|
||||
ADMIN_URL: '${ADMIN_URL}',
|
||||
CONTACT_URL: '${CONTACT_URL}',
|
||||
PROFILE_URL: '${PROFILE_URL}',
|
||||
ICON_URL: '${ICON_URL}',
|
||||
DEBUG_MODE: '${DEBUG_MODE}',
|
||||
STUN_SERVER: '${STUN_SERVER}',
|
||||
TURN_SERVER: '${TURN_SERVER}',
|
||||
TURN_USER: '${TURN_USER}',
|
||||
TURN_PASSWORD: '${TURN_PASSWORD}',
|
||||
JITSI_URL: '${JITSI_URL}',
|
||||
JITSI_PRIVATE_MODE: '${JITSI_PRIVATE_MODE}',
|
||||
START_ROOM_URL: '${START_ROOM_URL}',
|
||||
MAX_USERNAME_LENGTH: '${MAX_USERNAME_LENGTH}',
|
||||
MAX_PER_GROUP: '${MAX_PER_GROUP}',
|
||||
DISPLAY_TERMS_OF_USE: '${DISPLAY_TERMS_OF_USE}',
|
||||
POSTHOG_API_KEY: '${POSTHOG_API_KEY}',
|
||||
POSTHOG_URL: '${POSTHOG_URL}',
|
||||
NODE_ENV: '${NODE_ENV}',
|
||||
DISABLE_ANONYMOUS: '${DISABLE_ANONYMOUS}',
|
||||
OPID_LOGIN_SCREEN_PROVIDER: '${OPID_LOGIN_SCREEN_PROVIDER}',
|
||||
FALLBACK_LOCALE: '${FALLBACK_LOCALE}',
|
||||
};
|
21
front/dist/iframe.html
vendored
@ -1,21 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/iframe_api.js" ></script>
|
||||
<script>
|
||||
// Note: this is a huge XSS flow as we allow anyone to load a Javascript file in our domain.
|
||||
// This file must ABSOLUTELY be removed from the Docker images/deployments and is only here
|
||||
// for development purpose (because dynamically generated iframes are not working with
|
||||
// webpack hot reload due to an issue with rights)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const scriptUrl = urlParams.get('script');
|
||||
const script = document.createElement('script');
|
||||
script.src = scriptUrl;
|
||||
|
||||
if (urlParams.get('moduleMode') === 'true') {
|
||||
script.type = "module";
|
||||
}
|
||||
document.head.append(script);
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
5
front/dist/resources/fonts/fonts.css
vendored
@ -1,5 +0,0 @@
|
||||
/*This file is a workaround to allow phaser to load directly this font */
|
||||
@font-face {
|
||||
font-family: "Press Start 2P";
|
||||
src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2');
|
||||
}
|
49
front/dist/resources/logos/meet.svg
vendored
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="452.388px" height="452.388px" viewBox="0 0 452.388 452.388" style="enable-background:new 0 0 452.388 452.388;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g id="Layer_8_38_">
|
||||
<path d="M441.677,43.643H10.687C4.785,43.643,0,48.427,0,54.329v297.425c0,5.898,4.785,10.676,10.687,10.676h162.069v25.631
|
||||
c0,0.38,0.074,0.722,0.112,1.089h-23.257c-5.407,0-9.796,4.389-9.796,9.795c0,5.408,4.389,9.801,9.796,9.801h158.506
|
||||
c5.406,0,9.795-4.389,9.795-9.801c0-5.406-4.389-9.795-9.795-9.795h-23.256c0.032-0.355,0.115-0.709,0.115-1.089V362.43H441.7
|
||||
c5.898,0,10.688-4.782,10.688-10.676V54.329C452.37,48.427,447.589,43.643,441.677,43.643z M422.089,305.133
|
||||
c0,5.903-4.784,10.687-10.683,10.687H40.96c-5.898,0-10.684-4.783-10.684-10.687V79.615c0-5.898,4.786-10.684,10.684-10.684
|
||||
h370.446c5.898,0,10.683,4.785,10.683,10.684V305.133z M303.942,290.648H154.025c0-29.872,17.472-55.661,42.753-67.706
|
||||
c-15.987-10.501-26.546-28.571-26.546-49.13c0-32.449,26.306-58.755,58.755-58.755c32.448,0,58.753,26.307,58.753,58.755
|
||||
c0,20.553-10.562,38.629-26.545,49.13C286.475,234.987,303.942,260.781,303.942,290.648z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
16
front/iframe-api.vite.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { resolve } from "path";
|
||||
|
||||
export default defineConfig({
|
||||
publicDir: false,
|
||||
build: {
|
||||
outDir: "public",
|
||||
emptyOutDir: false,
|
||||
lib: {
|
||||
entry: resolve(__dirname, "src/iframe_api.ts"),
|
||||
name: "iframe_api",
|
||||
formats: ["cjs"],
|
||||
fileName: () => "iframe_api.js",
|
||||
},
|
||||
},
|
||||
});
|
@ -27,8 +27,8 @@
|
||||
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#000000">
|
||||
|
||||
<script src="/env-config.js"></script>
|
||||
<base href="/">
|
||||
<style>/*hide cowebsite container before scss is loaded*/#cowebsite { visibility: collapse };</style>
|
||||
|
||||
<title>WorkAdventure</title>
|
||||
</head>
|
||||
@ -63,5 +63,6 @@
|
||||
</div>
|
||||
<div id="cowebsite-buffer"></div>
|
||||
</div>
|
||||
<script type="module" src="/src/index.ts"></script>
|
||||
</body>
|
||||
</html>
|
51
front/nginx.conf
Normal file
@ -0,0 +1,51 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
access_log off;
|
||||
|
||||
gzip on;
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1000;
|
||||
gzip_proxied any;
|
||||
gzip_disable "msie6";
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/geo+json
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/ld+json
|
||||
application/manifest+json
|
||||
application/rdf+xml
|
||||
application/rss+xml
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/eot
|
||||
font/otf
|
||||
font/ttf
|
||||
image/svg+xml
|
||||
text/css
|
||||
text/javascript
|
||||
text/plain
|
||||
text/xml;
|
||||
|
||||
# serve static assets (that have a cache busting hash) with an efficient cache policy
|
||||
location /assets {
|
||||
root /usr/share/nginx/html;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
rewrite ^/register/ /index.html break;
|
||||
rewrite ^/login /index.html break;
|
||||
rewrite ^/jwt /index.html break;
|
||||
}
|
||||
|
||||
location ~ ^/[@_]/ {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
@ -4,42 +4,32 @@
|
||||
"main": "index.js",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"devDependencies": {
|
||||
"@geprog/vite-plugin-env-config": "^4.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||
"@tsconfig/svelte": "^1.0.10",
|
||||
"@types/google-protobuf": "^3.7.3",
|
||||
"@types/jasmine": "^3.5.10",
|
||||
"@types/mini-css-extract-plugin": "^1.4.3",
|
||||
"@types/node": "^15.3.0",
|
||||
"@types/quill": "^1.3.7",
|
||||
"@types/uuidv4": "^5.0.0",
|
||||
"@types/webpack-dev-server": "^3.11.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"css-loader": "^5.2.4",
|
||||
"css-minimizer-webpack-plugin": "^3.3.1",
|
||||
"eslint": "^8.4.1",
|
||||
"eslint-plugin-svelte3": "^3.2.1",
|
||||
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"jasmine": "^3.5.0",
|
||||
"lint-staged": "^11.0.0",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.3.1",
|
||||
"prettier-plugin-svelte": "^2.5.0",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.1.0",
|
||||
"svelte": "^3.38.2",
|
||||
"sass": "^1.49.7",
|
||||
"svelte": "^3.46.3",
|
||||
"svelte-check": "^2.1.0",
|
||||
"svelte-loader": "^3.1.1",
|
||||
"svelte-preprocess": "^4.7.3",
|
||||
"ts-loader": "^9.2.6",
|
||||
"svelte-preprocess": "^4.10.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"typescript": "^4.5.3",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
"vite": "^2.7.13",
|
||||
"vite-plugin-rewrite-all": "^0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@16bits/nes.css": "^2.3.2",
|
||||
@ -48,11 +38,12 @@
|
||||
"@types/simple-peer": "^9.11.1",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"axios": "^0.21.2",
|
||||
"buffer": "^6.0.3",
|
||||
"cancelable-promise": "^4.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"deep-copy-ts": "^0.5.0",
|
||||
"easystarjs": "^0.4.4",
|
||||
"generic-type-guard": "^3.2.0",
|
||||
"generic-type-guard": "^3.4.2",
|
||||
"google-protobuf": "^3.13.0",
|
||||
"phaser": "^3.54.0",
|
||||
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
|
||||
@ -72,11 +63,13 @@
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "run-p templater serve svelte-check-watch typesafe-i18n-watch",
|
||||
"start": "run-p templater serve watch-iframe-api svelte-check-watch typesafe-i18n-watch",
|
||||
"templater": "cross-env ./templater.sh",
|
||||
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||
"build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack",
|
||||
"serve": "cross-env vite --host",
|
||||
"build": "cross-env vite build",
|
||||
"build-iframe-api": "vite --config iframe-api.vite.config.ts build",
|
||||
"watch-iframe-api": "yarn run build-iframe-api --watch",
|
||||
"build-typings": "cross-env tsc --project tsconfig-for-iframe-api-typings.json",
|
||||
"test": "cross-env TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||
"lint": "node_modules/.bin/eslint src/ tests/ --ext .ts,.svelte",
|
||||
"fix": "node_modules/.bin/eslint --fix src/ tests/ --ext .ts,.svelte",
|
||||
@ -85,6 +78,7 @@
|
||||
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"",
|
||||
"pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'",
|
||||
"pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||
"typesafe-i18n-watch": "typesafe-i18n"
|
||||
},
|
||||
|
2
front/packages/iframe-api-typings/.gitignore
vendored
@ -1 +1 @@
|
||||
iframe_api.d.ts
|
||||
*.d.ts
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |