Merge branch 'main' into patterned-territory

This commit is contained in:
Aotumuri
2025-05-17 16:19:00 +09:00
committed by GitHub
773 changed files with 15719 additions and 12321 deletions
+1 -1
View File
@@ -12,4 +12,4 @@ helm-charts
.env
.editorconfig
.idea
coverage*
coverage*
+3
View File
@@ -7,3 +7,6 @@ end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
[*.sh]
indent_size = 4
@@ -0,0 +1,67 @@
---
name: "📝 API/DB Feature Request"
about: Suggest a new backend API or database feature
title: "[Feature] <short description here>"
labels: [feature, backend]
assignees: ""
---
## ✨ API/Database Feature Request
### Summary
Describe the feature being requested. What functionality does it add to the backend or database? What problem does it solve?
## 📘 Use Case
Explain the use case behind this feature. Who is it for, and why is it needed now?
## 📥 Example API Request
```
POST /api/example-endpoint
Content-Type: application/json
Authorization: Bearer <token>
{
"exampleField": "value",
"anotherField": 123
}
```
## 📤 Example API Response
```
// JSON response
{
"id": "abc123",
"status": "success",
"data": {
"result": true,
"timestamp": "2025-04-30T18:00:00Z"
}
}
```
## 📦 Database Considerations
- **New Tables**: Yes/No
If yes, describe the schema or include a rough layout.
- **Modified Tables**: Yes/No
Describe what changes are needed and why.
- **Migrations Required**: Yes/No
- **Indexes Required**: Yes/No
Include any thoughts on performance or query efficiency.
## 🔐 Security & Access Control
- Does the endpoint require authentication? Yes/No
- Should it be limited to specific roles (e.g. admin, worker, user)?
- Any sensitive data in the request or response?
## 📎 Additional Context
Add any screenshots, designs, relevant discussion links, or references to prior issues.
+21 -7
View File
@@ -1,7 +1,11 @@
name: CI
name: 🧪 CI
on: [push, pull_request]
permissions: {}
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -12,13 +16,23 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup npm
run: npm install
- name: Run tests
run: npm test
- name: Build
run: npm run build-prod
- run: npm ci
- run: npm run build-prod
- uses: actions/upload-artifact@v4
with:
path: out/index.html
retention-days: 1
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: false
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
+141 -39
View File
@@ -1,77 +1,179 @@
name: 🚀 Deploy
on:
# Allow contributors to schedule manual deployments.
# Permission to deploy can be restricted by requiring approval in environment configuration.
workflow_dispatch:
inputs:
target_environment:
description: "Deployment Environment"
target_domain:
description: "Deployment Domain"
required: true
default: "staging"
default: "openfront.dev"
type: choice
options:
- prod
- staging
- openfront.io
- openfront.dev
target_host:
description: "Deployment Host"
required: true
default: "staging"
type: choice
options:
- eu
- us
- masters
- nbg1
- staging
target_subdomain:
description: "Deployment Subdomain"
required: false
default: ""
type: string
# Automatic deployment on push
# See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore
push:
branches:
- main
- "*"
permissions: {}
jobs:
deploy:
# Don't deploy on push if this is a fork
if: ${{ github.event_name == 'workflow_dispatch' || github.repository == 'openfrontio/OpenFrontIO' }}
# Use different logic based on event type
name: Deploy to ${{ github.event_name == 'workflow_dispatch' && inputs.target_environment || 'staging' }}
name: ${{
github.event_name == 'push'
&& (github.ref_name == 'main' && 'openfront.dev'
|| format('{0}.openfront.dev', github.ref_name))
|| inputs.target_subdomain && format('{0}.{1}', inputs.target_subdomain, inputs.target_domain)
|| inputs.target_domain
|| 'openfront.dev'
}}
runs-on: ubuntu-latest
environment: ${{ github.event_name == 'workflow_dispatch' && inputs.target_environment || 'staging' }}
environment: ${{ inputs.target_domain == 'openfront.io' && 'prod' || '' }}
env:
DOMAIN: ${{ inputs.target_domain || 'openfront.dev' }}
SUBDOMAIN: ${{ github.event_name == 'push' && github.ref_name || inputs.target_subdomain || 'main' }}
steps:
- uses: actions/checkout@v4
- name: Log in to Docker Hub
- name: 📝 Update job summary
env:
FQDN: ${{ env.SUBDOMAIN && format('{0}.{1}', env.SUBDOMAIN, env.DOMAIN) || env.DOMAIN || 'openfront.dev' }}
run: |
echo "FQDN=$FQDN" >> $GITHUB_ENV
cat <<EOF >> $GITHUB_STEP_SUMMARY
### In progress :ship:
Deploying from $GITHUB_REF to $FQDN
EOF
- uses: actions/create-github-app-token@v2
id: generate-token
if: ${{ github.repository == 'openfrontio/OpenFrontIO' }}
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Export the token
if: ${{ github.repository == 'openfrontio/OpenFrontIO' }}
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
echo "GH_TOKEN=$GH_TOKEN" >> $GITHUB_ENV
gh api octocat
- name: 📝 Create deployment
uses: chrnorm/deployment-action@v2
id: deployment
with:
token: ${{ steps.generate-token.outputs.token }}
environment-url: https://${{ env.FQDN }}
environment: ${{ env.FQDN }}
- name: 🔗 Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: |
- name: 🔑 Create SSH private key
env:
SERVER_HOST_MASTERS: ${{ secrets.SERVER_HOST_MASTERS }}
SERVER_HOST_NBG1: ${{ secrets.SERVER_HOST_NBG1 }}
SERVER_HOST_STAGING: ${{ secrets.SERVER_HOST_STAGING }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
set -euxo pipefail
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
test -n "$SERVER_HOST_MASTERS" && ssh-keyscan -H "$SERVER_HOST_MASTERS" >> ~/.ssh/known_hosts
test -n "$SERVER_HOST_NBG1" && ssh-keyscan -H "$SERVER_HOST_NBG1" >> ~/.ssh/known_hosts
test -n "$SERVER_HOST_STAGING" && ssh-keyscan -H "$SERVER_HOST_STAGING" >> ~/.ssh/known_hosts
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.SERVER_HOST_STAGING }} >> ~/.ssh/known_hosts
- name: 🚢 Deploy
env:
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
DOCKER_REPO: ${{ vars.DOCKERHUB_REPO }}
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
ENV: ${{ inputs.target_domain == 'openfront.io' && 'prod' || 'staging' }}
HOST: ${{ github.event_name == 'workflow_dispatch' && inputs.target_host || 'staging' }}
MON_PASSWORD: ${{ secrets.MON_PASSWORD }}
MON_USERNAME: ${{ secrets.MON_USERNAME }}
OTEL_ENDPOINT: ${{ secrets.OTEL_ENDPOINT }}
OTEL_PASSWORD: ${{ secrets.OTEL_PASSWORD }}
OTEL_USERNAME: ${{ secrets.OTEL_USERNAME }}
R2_ACCESS_KEY: ${{ secrets.R2_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
SERVER_HOST_MASTERS: ${{ secrets.SERVER_HOST_MASTERS }}
SERVER_HOST_NBG1: ${{ secrets.SERVER_HOST_NBG1 }}
SERVER_HOST_STAGING: ${{ secrets.SERVER_HOST_STAGING }}
SSH_KEY: ~/.ssh/id_rsa
VERSION_TAG: latest
run: |
echo "::group::deploy.sh"
./deploy.sh "$ENV" "$HOST" "$SUBDOMAIN"
echo "Deployment created in ${SECONDS} seconds" >> $GITHUB_STEP_SUMMARY
echo "::endgroup::"
- name: ⏳ Wait for deployment to start
run: |
echo "::group::Wait for deployment to start"
set -euxo pipefail
while [ "$(curl -s https://${FQDN}/commit.txt)" != "${GITHUB_SHA}" ]; do
if [ "$SECONDS" -ge 300 ]; then
echo "Timeout: deployment did not start within 5 minutes"
exit 1
fi
sleep 10
done
echo "Deployment started in ${SECONDS} seconds" >> $GITHUB_STEP_SUMMARY
echo "::endgroup::"
- name: ✅ Update deployment status
if: success()
uses: chrnorm/deployment-status@v2
with:
token: ${{ steps.generate-token.outputs.token }}
environment-url: https://${{ env.FQDN }}
state: success
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: ✅ Update job summary
if: success()
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
### Success! :rocket:
# Determine environment based on trigger type
TARGET_ENV="${{ github.event_name == 'workflow_dispatch' && inputs.target_environment || 'staging' }}"
TARGET_HOST="${{ github.event_name == 'workflow_dispatch' && inputs.target_host || 'staging' }}"
TARGET_SUBDOMAIN="${{ github.event_name == 'workflow_dispatch' && inputs.target_subdomain || 'main' }}"
cat >.env.$TARGET_ENV <<EOF
ADMIN_TOKEN=${{ secrets.ADMIN_TOKEN }}
CF_ACCOUNT_ID=${{ secrets.CF_ACCOUNT_ID }}
CF_API_TOKEN=${{ secrets.CF_API_TOKEN }}
DOCKER_REPO=${{ vars.DOCKERHUB_REPO }}
DOCKER_USERNAME=${{ vars.DOCKERHUB_USERNAME }}
DOMAIN=${{ vars.DOMAIN }}
MON_PASSWORD=${{ secrets.MON_PASSWORD }}
MON_USERNAME=${{ secrets.MON_USERNAME }}
R2_ACCESS_KEY=${{ secrets.R2_ACCESS_KEY }}
R2_BUCKET=${{ secrets.R2_BUCKET }}
R2_SECRET_KEY=${{ secrets.R2_SECRET_KEY }}
SERVER_HOST_STAGING=${{ secrets.SERVER_HOST_STAGING }}
SERVER_HOST_US=${{ secrets.SERVER_HOST_US }}
SERVER_HOST_EU=${{ secrets.SERVER_HOST_EU }}
SSH_KEY=~/.ssh/id_rsa
VERSION_TAG="latest"
Deployed from $GITHUB_REF to $FQDN
EOF
- name: ❌ Update deployment status
if: failure()
uses: chrnorm/deployment-status@v2
with:
token: ${{ steps.generate-token.outputs.token }}
environment-url: https://${{ env.FQDN }}
state: failure
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: ❌ Update job summary
if: failure()
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
### Failure! :fire:
./deploy.sh $TARGET_ENV $TARGET_HOST $TARGET_SUBDOMAIN
echo "Deployed to $TARGET_ENV environment on $TARGET_HOST host with subdomain $TARGET_SUBDOMAIN"
Unable to deploy from $GITHUB_REF to $FQDN
EOF
+4 -1
View File
@@ -1,12 +1,15 @@
name: ESLint Check
name: 🔍 ESLint
on:
pull_request:
push:
branches: [main]
permissions: {}
jobs:
eslint:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+45
View File
@@ -0,0 +1,45 @@
name: 🧼 PR Description
on:
pull_request:
types: [opened, edited, synchronize]
permissions: {}
jobs:
validate-description:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || '';
const errors = [];
// Check for ## Description section
const descMatch = body.match(/^## Description:\s*\n((?:(?!^#).*\n?)*)/m);
if (!descMatch || descMatch[1].trim().length < 20) {
errors.push('❌ Missing or short `## Description:` section.');
}
// Check all three boxes are checked
const requiredBoxes = [
/- \[x\] I have added screenshots for all UI updates/i,
/- \[x\] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced/i,
/- \[x\] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors/i
];
for (const box of requiredBoxes) {
if (!box.test(body)) {
errors.push('❌ One or more checklist items are not checked.');
break;
}
}
if (errors.length > 0) {
core.setFailed(errors.join('\n'));
} else {
console.log('✅ PR description and checklist look good.');
}
+4 -1
View File
@@ -1,12 +1,15 @@
name: Prettier Check
name: 🎨 Prettier
on:
pull_request:
push:
branches: [main]
permissions: {}
jobs:
prettier:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+1 -1
View File
@@ -6,4 +6,4 @@ TODO.txt
resources/images/.DS_Store
resources/.DS_Store
.env*
.DS_Store
.DS_Store
+1 -1
View File
@@ -5,4 +5,4 @@
export PATH="/usr/local/bin:$HOME/.npm-global/bin:$HOME/.nvm/versions/node/$(node -v)/bin:$PATH"
# Then run lint-staged if tests pass
npx lint-staged
npx lint-staged
+15 -1
View File
@@ -1,3 +1,17 @@
{
"plugins": ["prettier-plugin-organize-imports"]
"overrides": [
{
"files": ".husky/**",
"options": {
"plugins": []
}
},
{
"files": "Dockerfile",
"options": {
"plugins": []
}
}
],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-sh"]
}
+19
View File
@@ -9,6 +9,25 @@
"runtimeArgs": ["run-script", "test"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"type": "node",
"request": "launch",
"name": "Debug Server",
"runtimeExecutable": "node",
"runtimeArgs": [
"--loader",
"ts-node/esm",
"--experimental-specifier-resolution=node",
"${workspaceFolder}/src/server/Server.ts"
],
"env": {
"GAME_ENV": "dev"
},
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"sourceMaps": true,
"restart": true
}
]
}
+4
View File
@@ -0,0 +1,4 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
* @openfrontio/core-contributor
resources/lang @openfrontio/translation-approver
resources/lang/en.json
+19 -13
View File
@@ -1,9 +1,8 @@
# Use an official Node runtime as the base image
FROM node:18
ARG GIT_COMMIT=unknown
ENV GIT_COMMIT=$GIT_COMMIT
FROM node:18 AS base
# Install Nginx, Supervisor, Git, jq, curl, and Node Exporter dependencies
# Create dependency layer
FROM base AS dependencies
RUN apt-get update && apt-get install -y \
nginx \
supervisor \
@@ -11,19 +10,22 @@ RUN apt-get update && apt-get install -y \
curl \
jq \
wget \
apache2-utils \
&& rm -rf /var/lib/apt/lists/*
# Install Node Exporter
RUN mkdir -p /opt/node_exporter && \
wget -qO- https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz | \
tar xvz --strip-components=1 -C /opt/node_exporter && \
ln -s /opt/node_exporter/node_exporter /usr/local/bin/
# Install cloudflared
RUN curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb > cloudflared.deb \
&& dpkg -i cloudflared.deb \
&& rm cloudflared.deb
# Final image
FROM base
# Copy installed packages from dependencies stage
COPY --from=dependencies / /
ARG GIT_COMMIT=unknown
ENV GIT_COMMIT=$GIT_COMMIT
# Set the working directory in the container
WORKDIR /usr/src/app
@@ -31,7 +33,7 @@ WORKDIR /usr/src/app
COPY package*.json ./
# Install dependencies while bypassing Husky hooks
ENV HUSKY=0
ENV HUSKY=0
ENV NPM_CONFIG_IGNORE_SCRIPTS=1
RUN mkdir -p .git && npm install
@@ -41,6 +43,10 @@ COPY . .
# Build the client-side application
RUN npm run build-prod
# So we can see which commit was used to build the container
# https://openfront.io/commit.txt
RUN echo $GIT_COMMIT > static/commit.txt
# Copy Nginx configuration and ensure it's used instead of the default
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -f /etc/nginx/sites-enabled/default
@@ -54,4 +60,4 @@ COPY startup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/startup.sh
# Use the startup script as the entrypoint
ENTRYPOINT ["/usr/local/bin/startup.sh"]
ENTRYPOINT ["/usr/local/bin/startup.sh"]
+9 -7
View File
@@ -116,21 +116,23 @@ This project is licensed under the terms found in the [LICENSE](LICENSE) file.
Contributions are welcome! Please feel free to submit a Pull Request.
1. Request to join the development [Discord](https://discord.gg/K9zernJB5z).
1. Fork the repository
2. Create your feature branch (`git checkout -b amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin amazing-feature`)
5. Open a Pull Request
1. Create your feature branch (`git checkout -b amazing-feature`)
1. Commit your changes (`git commit -m 'Add some amazing feature'`)
1. Push to the branch (`git push origin amazing-feature`)
1. Open a Pull Request
## 🌐 Translation
Translators are welcome! Please feel free to help translate into your language.
How to help?
1. Request to join the translation [Discord](https://discord.gg/rUukAnz4Ww)
1. Go to the project's Crowdin translation page: [https://crowdin.com/project/openfront-mls](https://crowdin.com/project/openfront-mls)
2. Login if you already have an account/ Sign up if you don't have one
3. Select the language you want to translate in/ If your language isn't on the list, click the "Request New Language" button and enter the language you want added there.
4. Translate the strings
1. Login if you already have an account/ Sign up if you don't have one
1. Select the language you want to translate in/ If your language isn't on the list, click the "Request New Language" button and enter the language you want added there.
1. Translate the strings
### Project Governance
+71 -30
View File
@@ -5,26 +5,47 @@
# 2. Copies the update script to Hetzner server
# 3. Executes the update script on the Hetzner server
set -e # Exit immediately if a command exits with a non-zero status
set -e # Exit immediately if a command exits with a non-zero status
# Initialize variables
ENABLE_BASIC_AUTH=false
# Parse command line arguments
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
--enable_basic_auth)
ENABLE_BASIC_AUTH=true
shift
;;
*)
POSITIONAL_ARGS+=("$1")
shift
;;
esac
done
# Restore positional parameters
set -- "${POSITIONAL_ARGS[@]}"
# Check command line arguments
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "Error: Please specify environment and host, with optional subdomain"
echo "Usage: $0 [prod|staging] [eu|us|staging] [subdomain]"
echo "Usage: $0 [prod|staging] [eu|nbg1|staging|masters] [subdomain] [--enable_basic_auth]"
exit 1
fi
# Validate first argument (environment)
if [ "$1" != "prod" ] && [ "$1" != "staging" ]; then
echo "Error: First argument must be either 'prod' or 'staging'"
echo "Usage: $0 [prod|staging] [eu|us|staging] [subdomain]"
echo "Usage: $0 [prod|staging] [eu|nbg1|staging|masters] [subdomain] [--enable_basic_auth]"
exit 1
fi
# Validate second argument (host)
if [ "$2" != "eu" ] && [ "$2" != "us" ] && [ "$2" != "staging" ]; then
echo "Error: Second argument must be either 'eu', 'us', or 'staging'"
echo "Usage: $0 [prod|staging] [eu|us|staging] [subdomain]"
if [ "$2" != "eu" ] && [ "$2" != "nbg1" ] && [ "$2" != "staging" ] && [ "$2" != "masters" ]; then
echo "Error: Second argument must be either 'eu', 'nbg1', 'staging', or 'masters'"
echo "Usage: $0 [prod|staging] [eu|nbg1|staging|masters] [subdomain] [--enable_basic_auth]"
exit 1
fi
@@ -37,7 +58,7 @@ print_header() {
ENV=$1
HOST=$2
SUBDOMAIN=$3 # Optional third argument for custom subdomain
SUBDOMAIN=$3 # Optional third argument for custom subdomain
# Set subdomain - use the custom subdomain if provided, otherwise use REGION
if [ -n "$SUBDOMAIN" ]; then
@@ -57,17 +78,17 @@ fi
if [ -f .env.$ENV ]; then
echo "Loading $ENV-specific configuration from .env.$ENV file..."
export $(grep -v '^#' .env.$ENV | xargs)
else
echo "Error: Environment file .env.$ENV not found"
exit 1
fi
if [ "$HOST" == "staging" ]; then
print_header "DEPLOYING TO STAGING HOST"
SERVER_HOST=$SERVER_HOST_STAGING
elif [ "$HOST" == "us" ]; then
print_header "DEPLOYING TO US HOST"
SERVER_HOST=$SERVER_HOST_US
elif [ "$HOST" == "nbg1" ]; then
print_header "DEPLOYING TO NBG1 HOST"
SERVER_HOST=$SERVER_HOST_NBG1
elif [ "$HOST" == "masters" ]; then
print_header "DEPLOYING TO MASTERS HOST"
SERVER_HOST=$SERVER_HOST_MASTERS
else
print_header "DEPLOYING TO EU HOST"
SERVER_HOST=$SERVER_HOST_EU
@@ -79,14 +100,29 @@ if [ -z "$SERVER_HOST" ]; then
exit 1
fi
# Configuration
UPDATE_SCRIPT="./update.sh" # Path to your update script
REMOTE_USER="openfront"
REMOTE_UPDATE_PATH="/home/$REMOTE_USER"
REMOTE_UPDATE_SCRIPT="$REMOTE_UPDATE_PATH/update-openfront.sh" # Where to place the script on server
# Check if basic auth is enabled and credentials are available
if [ "$ENABLE_BASIC_AUTH" = true ]; then
print_header "BASIC AUTH ENABLED"
if [ -z "$BASIC_AUTH_USER" ] || [ -z "$BASIC_AUTH_PASS" ]; then
echo "Error: Basic Auth is enabled but BASIC_AUTH_USER or BASIC_AUTH_PASS not defined in .env file or environment"
exit 1
fi
echo "Basic Authentication will be enabled with user: $BASIC_AUTH_USER"
else
# If basic auth is not enabled, set the variables to empty to ensure they don't get used
BASIC_AUTH_USER=""
BASIC_AUTH_PASS=""
echo "Basic Authentication is disabled"
fi
IMAGE_NAME="${DOCKER_USERNAME}/${DOCKER_REPO}"
DOCKER_IMAGE="${IMAGE_NAME}:${VERSION_TAG}"
# Configuration
UPDATE_SCRIPT="./update.sh" # Path to your update script
REMOTE_USER="openfront"
REMOTE_UPDATE_PATH="/home/$REMOTE_USER"
REMOTE_UPDATE_SCRIPT="$REMOTE_UPDATE_PATH/update-openfront.sh" # Where to place the script on server
VERSION_TAG=$(date +"%Y%m%d-%H%M%S")
DOCKER_IMAGE="${DOCKER_USERNAME}/${DOCKER_REPO}:${VERSION_TAG}"
# Check if update script exists
if [ ! -f "$UPDATE_SCRIPT" ]; then
@@ -103,15 +139,15 @@ echo "Using version tag: $VERSION_TAG"
echo "Docker repository: $DOCKER_REPO"
# Get Git commit for build info
GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
GIT_COMMIT=$(git rev-parse HEAD 2> /dev/null || echo "unknown")
echo "Git commit: $GIT_COMMIT"
docker buildx build \
--platform linux/amd64 \
--build-arg GIT_COMMIT=$GIT_COMMIT \
-t $DOCKER_USERNAME/$DOCKER_REPO:$VERSION_TAG \
--push \
.
--platform linux/amd64 \
--build-arg GIT_COMMIT=$GIT_COMMIT \
-t $DOCKER_IMAGE \
--push \
.
if [ $? -ne 0 ]; then
echo "❌ Docker build failed. Stopping deployment."
@@ -140,7 +176,6 @@ cat > $REMOTE_UPDATE_PATH/.env << 'EOL'
GAME_ENV=$ENV
ENV=$ENV
HOST=$HOST
SUBDOMAIN=$SUBDOMAIN
DOCKER_IMAGE=$DOCKER_IMAGE
DOCKER_TOKEN=$DOCKER_TOKEN
ADMIN_TOKEN=$ADMIN_TOKEN
@@ -151,8 +186,11 @@ R2_BUCKET=$R2_BUCKET
CF_API_TOKEN=$CF_API_TOKEN
DOMAIN=$DOMAIN
SUBDOMAIN=$SUBDOMAIN
MON_USERNAME=$MON_USERNAME
MON_PASSWORD=$MON_PASSWORD
OTEL_USERNAME=$OTEL_USERNAME
OTEL_PASSWORD=$OTEL_PASSWORD
OTEL_ENDPOINT=$OTEL_ENDPOINT
BASIC_AUTH_USER=$BASIC_AUTH_USER
BASIC_AUTH_PASS=$BASIC_AUTH_PASS
EOL
chmod 600 $REMOTE_UPDATE_PATH/.env && \
$REMOTE_UPDATE_SCRIPT"
@@ -164,5 +202,8 @@ fi
print_header "DEPLOYMENT COMPLETED SUCCESSFULLY"
echo "✅ New version deployed to ${ENV} environment in ${HOST} with subdomain ${SUBDOMAIN}!"
if [ "$ENABLE_BASIC_AUTH" = true ]; then
echo "🔒 Basic authentication enabled with user: $BASIC_AUTH_USER"
fi
echo "🌐 Check your server to verify the deployment."
echo "======================================================="
echo "======================================================="
+6
View File
@@ -31,4 +31,10 @@ export default [
"no-useless-escape": "off",
},
},
{
rules: {
// Enable rules
eqeqeq: "error",
},
},
];
+1 -1
View File
@@ -29,4 +29,4 @@ MON_USERNAME=monitor_username
MON_PASSWORD=monitor_password
# Version
VERSION_TAG="latest"
VERSION_TAG="latest"
-103
View File
@@ -1,103 +0,0 @@
#!/bin/bash
# Metric Collector for Prometheus Pushgateway
# This script collects metrics from Node Exporter and application sources
# and pushes them to a Prometheus Pushgateway with custom labels.
# Configuration
NODE_EXPORTER_URL="http://localhost:9100/metrics"
APP_METRICS_URL="http://localhost:9090/metrics"
PUSHGATEWAY_BASE_URL="https://mon.openfront.io/pushgateway/metrics"
AUTH=$MON_USERNAME:$MON_PASSWORD
INTERVAL=15 # seconds
# Function to fetch metrics from Node Exporter
fetch_node_exporter_metrics() {
curl -s --connect-timeout 5 --max-time 10 "$NODE_EXPORTER_URL" ||
echo "# Error fetching Node Exporter metrics"
}
# Function to fetch metrics from your application
fetch_app_metrics() {
curl -s --connect-timeout 5 --max-time 10 "$APP_METRICS_URL" ||
echo "# Error fetching application metrics"
}
# Function to push metrics to Pushgateway
push_metrics() {
local metrics=$1
local job_name=$2
echo "Pushing $job_name metrics to Pushgateway..."
# Create a temporary file for the metrics
TEMP_FILE=$(mktemp)
echo "$metrics" > "$TEMP_FILE"
# Push to Pushgateway with instance label
curl -s -u "$AUTH" --data-binary @"$TEMP_FILE" \
"$PUSHGATEWAY_BASE_URL/job/$job_name/instance/$HOST"
# Check if push was successful
if [ $? -eq 0 ]; then
echo "$job_name metrics pushed successfully"
else
echo "Error pushing $job_name metrics"
fi
# Remove temporary file
rm "$TEMP_FILE"
}
# Function to add labels to metrics
add_labels() {
local metrics=$1
# First, handle metrics with existing labels
metrics=$(echo "$metrics" | sed -E 's/(\{[^}]*)\}/\1,env="'$ENV'",host="'$HOST'",subdomain="'$SUBDOMAIN'"}/g')
# Then, handle metrics with no existing labels
metrics=$(echo "$metrics" | sed -E 's/^([a-zA-Z0-9_:]+)[ \t]+([0-9.e+-]+)$/\1{env="'$ENV'",host="'$HOST'",subdomain="'$SUBDOMAIN'"} \2/g')
echo "$metrics"
}
# Main function to collect and push metrics
collect_and_push_metrics() {
echo "Starting metrics collection cycle at $(date)"
# Get metrics from both sources
NODE_METRICS=$(fetch_node_exporter_metrics)
APP_METRICS=$(fetch_app_metrics)
# Clean up metrics (remove headers etc.)
NODE_METRICS=$(echo "$NODE_METRICS" | grep -v "^Fetching")
APP_METRICS=$(echo "$APP_METRICS" | grep -v "^Fetching")
# Add labels to metrics
NODE_METRICS=$(add_labels "$NODE_METRICS")
APP_METRICS=$(add_labels "$APP_METRICS")
# Push to Pushgateway separately
push_metrics "$NODE_METRICS" "node_exporter"
push_metrics "$APP_METRICS" "app_metrics"
echo "Metrics collection cycle completed at $(date)"
}
# Main execution
echo "===== Starting metrics collector ====="
echo "Environment: $ENV, HOST: $HOST, Subdomain: $SUBDOMAIN"
echo "Collecting and pushing metrics every $INTERVAL seconds"
echo "Node Exporter URL: $NODE_EXPORTER_URL"
echo "App Metrics URL: $APP_METRICS_URL"
echo "Pushgateway URL: $PUSHGATEWAY_BASE_URL"
# Wait for app to be ready.
sleep 30
# Then set up interval loop
while true; do
sleep $INTERVAL
collect_and_push_metrics
done
+52
View File
@@ -15,6 +15,32 @@ map $uri $port {
~^/w12/ 3013;
~^/w13/ 3014;
~^/w14/ 3015;
~^/w15/ 3016;
~^/w16/ 3017;
~^/w17/ 3018;
~^/w18/ 3019;
~^/w19/ 3020;
~^/w20/ 3021;
~^/w21/ 3022;
~^/w22/ 3023;
~^/w23/ 3024;
~^/w24/ 3025;
~^/w25/ 3026;
~^/w26/ 3027;
~^/w27/ 3028;
~^/w28/ 3029;
~^/w29/ 3030;
~^/w30/ 3031;
~^/w31/ 3032;
~^/w32/ 3033;
~^/w33/ 3034;
~^/w34/ 3035;
~^/w35/ 3036;
~^/w36/ 3037;
~^/w37/ 3038;
~^/w38/ 3039;
~^/w39/ 3040;
~^/w40/ 3041;
default 3000;
}
@@ -235,6 +261,32 @@ server {
if ($worker = "12") { set $worker_port 3013; }
if ($worker = "13") { set $worker_port 3014; }
if ($worker = "14") { set $worker_port 3015; }
if ($worker = "15") { set $worker_port 3016; }
if ($worker = "16") { set $worker_port 3017; }
if ($worker = "17") { set $worker_port 3018; }
if ($worker = "18") { set $worker_port 3019; }
if ($worker = "19") { set $worker_port 3020; }
if ($worker = "20") { set $worker_port 3021; }
if ($worker = "21") { set $worker_port 3022; }
if ($worker = "22") { set $worker_port 3023; }
if ($worker = "23") { set $worker_port 3024; }
if ($worker = "24") { set $worker_port 3025; }
if ($worker = "25") { set $worker_port 3026; }
if ($worker = "26") { set $worker_port 3027; }
if ($worker = "27") { set $worker_port 3028; }
if ($worker = "28") { set $worker_port 3029; }
if ($worker = "29") { set $worker_port 3030; }
if ($worker = "30") { set $worker_port 3031; }
if ($worker = "31") { set $worker_port 3032; }
if ($worker = "32") { set $worker_port 3033; }
if ($worker = "33") { set $worker_port 3034; }
if ($worker = "34") { set $worker_port 3035; }
if ($worker = "35") { set $worker_port 3036; }
if ($worker = "36") { set $worker_port 3037; }
if ($worker = "37") { set $worker_port 3038; }
if ($worker = "38") { set $worker_port 3039; }
if ($worker = "39") { set $worker_port 3040; }
if ($worker = "40") { set $worker_port 3041; }
proxy_pass http://127.0.0.1:$worker_port$2;
proxy_http_version 1.1;
+1640 -42
View File
File diff suppressed because it is too large Load Diff
+17 -2
View File
@@ -61,6 +61,7 @@
"postcss-loader": "^8.1.1",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-sh": "^0.17.4",
"raw-loader": "^4.0.2",
"sinon": "^18.0.0",
"sinon-chai": "^4.0.0",
@@ -80,6 +81,18 @@
"@aws-sdk/client-s3": "^3.758.0",
"@datastructures-js/priority-queue": "^6.3.1",
"@google-cloud/secret-manager": "^5.6.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.200.0",
"@opentelemetry/auto-instrumentations-node": "^0.58.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
"@opentelemetry/host-metrics": "^0.36.0",
"@opentelemetry/resources": "^2.0.0",
"@opentelemetry/sdk-logs": "^0.200.0",
"@opentelemetry/sdk-metrics": "^2.0.0",
"@opentelemetry/sdk-node": "^0.200.0",
"@opentelemetry/semantic-conventions": "^1.32.0",
"@opentelemetry/winston-transport": "^0.11.0",
"@types/dompurify": "^3.0.5",
"@types/express": "^4.17.21",
"@types/google-protobuf": "^3.15.12",
@@ -95,7 +108,7 @@
"d3": "^7.9.0",
"discord.js": "^14.16.3",
"dompurify": "^3.1.7",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"express": "^4.21.1",
"express-rate-limit": "^7.5.0",
"google-auth-library": "^9.14.0",
@@ -104,13 +117,14 @@
"html-webpack-plugin": "^5.6.3",
"ip-anonymize": "^0.1.0",
"jimp": "^0.22.12",
"jose": "^6.0.10",
"lit": "^3.2.1",
"msgpack5": "^6.0.2",
"nanoid": "^3.3.6",
"node-addon-api": "^8.1.0",
"node-gyp": "^10.2.0",
"obscenity": "^0.4.3",
"page": "^1.11.6",
"page": "^1.3.7",
"pg": "^8.13.3",
"priority-queue-typescript": "^1.0.1",
"prom-client": "^15.1.3",
@@ -127,6 +141,7 @@
"webpack-dev-server": "^5.0.4",
"wheelnav": "^1.7.1",
"winston": "^3.17.0",
"winston-transport": "^4.9.0",
"ws": "^8.18.0",
"zod": "^3.23.8"
},
+182
View File
@@ -0,0 +1,182 @@
{
"help": [
{
"key": "troops",
"requiresPlayer": false
},
{
"key": "gold",
"requiresPlayer": false
},
{
"key": "no_attack",
"requiresPlayer": false
},
{
"key": "sorry_attack",
"requiresPlayer": false
},
{
"key": "alliance",
"requiresPlayer": false
},
{
"key": "help_defend",
"requiresPlayer": true
},
{
"key": "team_up",
"requiresPlayer": true
}
],
"attack": [
{
"key": "attack",
"requiresPlayer": true
},
{
"key": "mirv",
"requiresPlayer": true
},
{
"key": "focus",
"requiresPlayer": true
},
{
"key": "finish",
"requiresPlayer": true
}
],
"defend": [
{
"key": "defend",
"requiresPlayer": true
},
{
"key": "dont_attack",
"requiresPlayer": true
},
{
"key": "ally",
"requiresPlayer": true
}
],
"greet": [
{
"key": "hello",
"requiresPlayer": false
},
{
"key": "good_luck",
"requiresPlayer": false
},
{
"key": "have_fun",
"requiresPlayer": false
},
{
"key": "gg",
"requiresPlayer": false
},
{
"key": "nice_to_meet",
"requiresPlayer": false
},
{
"key": "well_played",
"requiresPlayer": false
},
{
"key": "hi_again",
"requiresPlayer": false
},
{
"key": "bye",
"requiresPlayer": false
},
{
"key": "thanks",
"requiresPlayer": false
},
{
"key": "oops",
"requiresPlayer": false
},
{
"key": "trust_me",
"requiresPlayer": false
},
{
"key": "trust_broken",
"requiresPlayer": false
}
],
"misc": [
{
"key": "go",
"requiresPlayer": false
},
{
"key": "strategy",
"requiresPlayer": false
},
{
"key": "fun",
"requiresPlayer": false
},
{
"key": "pr",
"requiresPlayer": false
}
],
"warnings": [
{
"key": "strong",
"requiresPlayer": true
},
{
"key": "weak",
"requiresPlayer": true
},
{
"key": "mirv_soon",
"requiresPlayer": true
},
{
"key": "number1_warning",
"requiresPlayer": false
},
{
"key": "stalemate",
"requiresPlayer": false
},
{
"key": "has_allies",
"requiresPlayer": true
},
{
"key": "no_allies",
"requiresPlayer": true
},
{
"key": "betrayed",
"requiresPlayer": true
},
{
"key": "getting_big",
"requiresPlayer": true
},
{
"key": "danger_base",
"requiresPlayer": true
},
{
"key": "saving_for_mirv",
"requiresPlayer": true
},
{
"key": "mirv_ready",
"requiresPlayer": true
}
]
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 70 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 100 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 113 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 7.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 80 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 600 B

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
<image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAADICAYAAACZBDirAAAAAXNSR0IArs4c6QAADSRJREFUeF7t3E+MXWUZx/Hv/TPTOy1tpdM2FIrSYlRMCcbEoCSEnYsSjDsJGHXhAnVjwoaFBsNGNiSuDFEXEkVwZSIpCzeGhUEWGDUY1ISKoWgtbYGWdqYz949950+saDmn5Tyd5/R+T9K06bz3uc/5PKe/vOee6XT4z9EDrgPuAu4BbgduBPoXrPGPCiigQJsEhsBrwAvAM8BzwFFgVE6is3YmJeT2n//CA2vhtxuYWwu/9TVtOml7VUABBYrABCghuAAcWwvBx4HD5e9LuJWd383AQ8DdwE6gq50CCihwlQmMgePAIeBR4JUSgDcADwL3A7su2BVeZefu6SiggAIrO8I3gCeBx0oA3nd+x/cIsM+dn5eHAgpMgUDZCZZb4IdLAD4FHAS2TcGJe4oKKKBAETgFPFsCsCThXmBGFwUUUGBKBJaBIyUAyx/KgxCf9k7J5D1NBRRY+SxwVEKv/MFDAQUUmDqBywrA8qJNnQ6b6TDb6TBDh577x6m7eDxhBTZKYDQpt64TliYTzjLh3GRyWTu5Sw7Acq883+1xW3+WT88M+FC3x55en62drt88uFFXg++rwBQJlEe4pydj/jka8vfxiN8uL/KH4RInxqPV/95xCUftAFzf9X2o2+fzgy0cnN3M7m6XWTr0O52VDxE9FFBAgSshUIJuOJmwxIRj4zGHls7yi8UzvDYeXtJusHYAllveT/Y38bW5rXyiP8sHuj13fFdi0r6HAgq8p0DZEb45HvHicIkfLpzmd8NzKyFY56gVgGXRrm6Pr85t5d7BNVzb6frIuI6uaxRQ4IoIlLgrIfj0uTP8aOE0b4xHtT4TrBWAg06HO2cGfGvLtezr9d35XZGR+iYKKHApAiv/vWO0zHfOvMXzy4u1doG1AnBHp8s3Nm/nvsGWlYcdHgoooEBGgVOTMT9bPMP3z77NyUmJxPc+agXgnm6P716zgztnB2zy5rfK1K8roMAGCSxOJvx6aYFvn3mTo+PqZ8K1AvCDvT4/2LqTj/dnvf3doMH6tgooUC1QIu+Py0t8/fTxlSfCVUetALyp1+cn23avfP7noYACCmQWODwa8qVTx3h11GAA/nTbbkoQeiiggAKZBUrwfdEAzDwie1NAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSgBAzBK1roKKJBewABMPyIbVECBKAEDMErWugookF7AAEw/IhtUQIEoAQMwSta6CiiQXsAATD8iG1RAgSiBxgNwb7fH97bOs7fbj+rZugoooEAjAkfGQ755+gRHxqPKeh1gUrVq0OnwkV6fAd2qpX5dAQUU2FCBRcb8dTRkcVIZbdQKwLKoRF/53UMBBRTILFBib1xnZ7eWadUxmfls7U0BBRS4TIFaO8DLrO3LFFBAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dicAgpEChiAkbrWVkCB1AIGYOrx2JwCCkQKGICRutZWQIHUAgZg6vHYnAIKRAoYgJG61lZAgdQCBmDq8dic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" x="7.500" y="32.813" width="135.000" height="84.375" />
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 99 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 87 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 94 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 78 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 441 KiB

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
<image href="data:image/png;base64,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" x="7.500" y="32.813" width="135.000" height="84.375" />
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
<image href="data:image/png;base64,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" x="7.500" y="32.813" width="135.000" height="84.375" />
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 28 KiB

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