mirror of
https://github.com/DarkflameUniverse/NexusDashboard.git
synced 2026-09-26 15:03:43 +00:00
Compare commits
145 Commits
v0.0.1
...
lxmfl-spli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e00544c98f | ||
|
|
7500be5d71 | ||
|
|
c9d329b1a1 | ||
|
|
ae0847aba9 | ||
|
|
0c18d03aa7 | ||
|
|
ded0e55501 | ||
|
|
3668ca8792 | ||
|
|
a07515cdb2 | ||
|
|
35ee0ed457 | ||
|
|
7b4e11d65b | ||
|
|
07136ef283 | ||
|
|
1dbe0ce980 | ||
|
|
1dbb637053 | ||
|
|
26111d0bcf | ||
|
|
4124d98853 | ||
|
|
ef312fc744 | ||
|
|
1790a8b060 | ||
|
|
02bb6bb0aa | ||
|
|
8dd4a2c80e | ||
|
|
6f4b2ef574 | ||
|
|
33d24745e1 | ||
|
|
8e58b0bc1f | ||
|
|
413a2c06a4 | ||
|
|
c32da45a16 | ||
|
|
8ad6249275 | ||
|
|
65ce84e090 | ||
|
|
d66bdee575 | ||
|
|
72be25f5b5 | ||
|
|
63d5d2da87 | ||
|
|
e73d1ecd53 | ||
|
|
5492ae6668 | ||
|
|
7b27cab25d | ||
|
|
b6cad4acae | ||
|
|
900a3cbf3c | ||
|
|
993e5dcfe4 | ||
|
|
2dc5437fc3 | ||
|
|
c94d1cc62c | ||
|
|
f73ce143a1 | ||
|
|
e5428dcd4d | ||
|
|
44689aad36 | ||
|
|
f1d79f1e90 | ||
|
|
1113295c5b | ||
|
|
e45fb3233a | ||
|
|
a3894c3450 | ||
|
|
6fa7ade6a3 | ||
|
|
70aed00d97 | ||
|
|
f8d5928c86 | ||
|
|
5ac384663b | ||
|
|
a79631be8e | ||
|
|
ef7da76629 | ||
|
|
520c5f2d40 | ||
|
|
27c4f1a7f2 | ||
|
|
7f992a5dfa | ||
|
|
560afa5e0d | ||
|
|
17c6d5bc1a | ||
|
|
be80d7b2ab | ||
|
|
521a10756e | ||
|
|
b6905c9235 | ||
|
|
faff08b160 | ||
|
|
c8b21ae6a6 | ||
|
|
179ea43cb8 | ||
|
|
1566df65cb | ||
|
|
4d62e70810 | ||
|
|
24e98190fe | ||
|
|
6606f1558e | ||
|
|
5b949e02d1 | ||
|
|
f0faa4c53b | ||
|
|
418c81944b | ||
|
|
8285f02ed8 | ||
|
|
ede3642efe | ||
|
|
3fed8cb02f | ||
|
|
7cb0b94972 | ||
|
|
1cb64f7594 | ||
|
|
33739456df | ||
|
|
5ce9ac85bc | ||
|
|
70742549b9 | ||
|
|
039b45c824 | ||
|
|
9452e8c801 | ||
|
|
09363fcd06 | ||
|
|
6303a86778 | ||
|
|
7f37343de4 | ||
|
|
78ab5b93bb | ||
|
|
76c862a2ba | ||
|
|
04f4e833a4 | ||
|
|
a66e4aaf80 | ||
|
|
e622c44042 | ||
|
|
819d51c68a | ||
|
|
94411568ce | ||
|
|
b829d666c9 | ||
|
|
2e82f94b9d | ||
|
|
b66d2748b4 | ||
|
|
78ed39905f | ||
|
|
d63dd807e3 | ||
|
|
e9aef0219e | ||
|
|
d77eaf16d1 | ||
|
|
24a398b89c | ||
|
|
26cc2d431c | ||
|
|
8a019c340e | ||
|
|
0dbe9d87fc | ||
|
|
d792596926 | ||
|
|
9cc3dbb4c4 | ||
|
|
de698f86fa | ||
|
|
2e8ce8ea19 | ||
|
|
91e6ed33e7 | ||
|
|
f0cbcee100 | ||
|
|
506d4ac090 | ||
|
|
121c407cdb | ||
|
|
d3c8c5d77b | ||
|
|
8aec4a45d2 | ||
|
|
87384c1b98 | ||
|
|
f0df357258 | ||
|
|
869bbdf7e6 | ||
|
|
8646c40943 | ||
|
|
ba0c4801df | ||
|
|
b0e76ce691 | ||
|
|
cf359e2c6b | ||
|
|
c6d624e154 | ||
|
|
0066e0ea2d | ||
|
|
3df9f143ed | ||
|
|
649c986345 | ||
|
|
172bbe5b60 | ||
|
|
7194a04c5d | ||
|
|
7bb0e46e56 | ||
|
|
2a5fad3c87 | ||
|
|
ac835de53b | ||
|
|
fba782fd96 | ||
|
|
59db1df604 | ||
|
|
aede0f4f1a | ||
|
|
1b5f002d6d | ||
|
|
256ea19bfc | ||
|
|
4bda98670b | ||
|
|
6deb02b11d | ||
|
|
d6d8bb085d | ||
|
|
0fa91c9cfa | ||
|
|
8cc630d420 | ||
|
|
7f7b854b8b | ||
|
|
81193499f4 | ||
|
|
dbe6da370a | ||
|
|
6197f630e9 | ||
|
|
683a1d6f7d | ||
|
|
ab25db182d | ||
|
|
c183d1ff5a | ||
|
|
de89647421 | ||
|
|
b2af6d967a | ||
|
|
1a5531eb19 |
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -1,4 +1,4 @@
|
|||||||
app/static/bootstrap-4.2.1/* linguist-vendored
|
app/static/bootstrap-4.2.1/* linguist-vendored=true
|
||||||
app/static/bootswatch-master/* linguist-vendored
|
app/static/datatables/* linguist-vendored=true
|
||||||
app/static/datatables/* linguist-vendored
|
app/static/font-awesome/* linguist-vendored=true
|
||||||
app/static/font-awesome/* linguist-vendored
|
app/static/chartjs/* linguist-vendored=true
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -13,3 +13,7 @@ app/static/ldddb/*
|
|||||||
locale.xml
|
locale.xml
|
||||||
app/luclient/*
|
app/luclient/*
|
||||||
app/cache/*
|
app/cache/*
|
||||||
|
property_files/*
|
||||||
|
*.log
|
||||||
|
app/settings.py
|
||||||
|
*.exe
|
||||||
|
|||||||
27
Jenkinsfile
vendored
27
Jenkinsfile
vendored
@@ -5,22 +5,25 @@ properties([
|
|||||||
branchFilter: 'origin/(.*)',
|
branchFilter: 'origin/(.*)',
|
||||||
defaultValue: 'origin/main',
|
defaultValue: 'origin/main',
|
||||||
description: '',
|
description: '',
|
||||||
name: 'BRANCH',
|
name: 'GIT_BRANCH',
|
||||||
quickFilterEnabled: false,
|
quickFilterEnabled: true,
|
||||||
selectedValue: 'DEFAULT',
|
selectedValue: 'DEFAULT',
|
||||||
sortMode: 'NONE',
|
sortMode: 'NONE',
|
||||||
tagFilter: '*',
|
tagFilter: '*',
|
||||||
useRepository: 'git@github.com:DarkflameUniverse/NexusDashboard.git',
|
useRepository: 'git@github.com:DarkflameUniverse/NexusDashboard.git',
|
||||||
type: 'PT_BRANCH'
|
type: 'PT_BRANCH',
|
||||||
|
listSize: "1"
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
|
||||||
node('worker'){
|
node('worker'){
|
||||||
|
currentBuild.setDescription(params.GIT_BRANCH)
|
||||||
|
|
||||||
stage('Clone Code'){
|
stage('Clone Code'){
|
||||||
checkout([
|
checkout([
|
||||||
$class: 'GitSCM',
|
$class: 'GitSCM',
|
||||||
branches: [[name: params.BRANCH]],
|
branches: [[name: params.GIT_BRANCH]],
|
||||||
extensions: [],
|
extensions: [],
|
||||||
userRemoteConfigs: [
|
userRemoteConfigs: [
|
||||||
[
|
[
|
||||||
@@ -31,19 +34,19 @@ node('worker'){
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
def tag = ''
|
def tag = ''
|
||||||
stage("Build Container"){
|
stage('Build Container'){
|
||||||
if (params.BRANCH.contains('main')){
|
if (params.GIT_BRANCH.contains('main')){
|
||||||
tag = 'latest'
|
tag = 'latest'
|
||||||
} else {
|
} else {
|
||||||
tag = params.BRANCH.replace('\\', '-')
|
tag = params.GIT_BRANCH.replace('\\', '-')
|
||||||
}
|
}
|
||||||
sh "docker build -t aronwk/nexus-dashboard:${tag} ."
|
sh "docker build -t aronwk/nexus-dashboard:$tag ."
|
||||||
}
|
}
|
||||||
stage("Push Container"){
|
stage('Push Container'){
|
||||||
withCredentials([usernamePassword(credentialsId: 'docker-hub-token', passwordVariable: 'password', usernameVariable: 'username')]) {
|
withCredentials([usernamePassword(credentialsId: 'docker-hub-token', passwordVariable: 'password', usernameVariable: 'username')]) {
|
||||||
sh "docker login -u ${username} -p ${password}"
|
sh "docker login -u $username -p $password"
|
||||||
sh "docker push aronwk/nexus-dashboard:${tag}"
|
sh "docker push aronwk/nexus-dashboard:$tag"
|
||||||
sh 'docker logout'
|
sh "docker logout"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
237
README.md
237
README.md
@@ -1,69 +1,226 @@
|
|||||||
# Nexus Dashboard
|
# Nexus Dashboard
|
||||||
|
|
||||||
**This is a WIP: For Advanced Users**
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="app/static/logo/logo.png" alt="Sublime's custom image"/>
|
<img src="app/static/logo/logo.png" alt="DLU logo"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Account Management:
|
||||||
|
* Ban, Lock, and Mute accounts (This Mute affects all Characters)
|
||||||
|
* Account Deletion
|
||||||
|
* Email ( all optional ):
|
||||||
|
* Require email verification
|
||||||
|
* Reset Password via Email
|
||||||
|
* Edit Email ( by Admin only )
|
||||||
|
* User Registration
|
||||||
|
* Invitations ( TODO: Implement this )
|
||||||
|
* Invitation Only Registration ( TODO: Implement this )
|
||||||
|
* Play Key Management:
|
||||||
|
* Create, Edit, and Add notes to play keys
|
||||||
|
* View accounts Tied to a play key
|
||||||
|
* Character Management:
|
||||||
|
* Rescue: Pull character to a previously visited world
|
||||||
|
* Restrict Trade: Toggle the character's ability to trade
|
||||||
|
* Restrict Mail: Toggle the character's ability to send mail
|
||||||
|
* Restrict Chat: Toggle the character's ability to send chat messages
|
||||||
|
* Inventory viewer
|
||||||
|
* View backpack contents, vault, models, and more!
|
||||||
|
* Stats Viewer
|
||||||
|
* Moderation:
|
||||||
|
* Character Names:
|
||||||
|
* Approve and mark as needs rename
|
||||||
|
* Pet Names:
|
||||||
|
* Auto-moderation of Pet names based on already moderated names
|
||||||
|
* This is a scheduled tack that runs in the background every hour
|
||||||
|
* Character Association, to see who has requested what name
|
||||||
|
* Name cleanup: remove names of deleted pets/characters
|
||||||
|
* Properties:
|
||||||
|
* Approve and Un-approve Properties
|
||||||
|
* Property/Model viewer
|
||||||
|
* Pre-built and UGC model rendering
|
||||||
|
* View Properties in full 360 in the browser!
|
||||||
|
* View in LOD0 (High), LOD1(Medium), or LOD2(Low) quality
|
||||||
|
* Download models
|
||||||
|
* Bug Reports:
|
||||||
|
* View and Resolve bug reports
|
||||||
|
* Logs:
|
||||||
|
* Command: View commands that have been run
|
||||||
|
* Activity: View character activity of entering and exiting worlds
|
||||||
|
* Audit:
|
||||||
|
* View moderation activity (characters, pets, properties)
|
||||||
|
* View GM Level changes
|
||||||
|
* View Send Mail usage
|
||||||
|
* System: View Extra logging of background activities of Nexus Dashboard
|
||||||
|
* Send Mail:
|
||||||
|
* Send Mail to characters
|
||||||
|
* Attach items to Mail
|
||||||
|
* Economy Reports:
|
||||||
|
* Reports are generated as a scheduled background task run every day at 2300 UTC
|
||||||
|
* Accounts with GM Level 3 and above are ignored
|
||||||
|
* Item reports:
|
||||||
|
* Reports numbers of items in existence
|
||||||
|
* Includes backpack and Vault items
|
||||||
|
* Currency:
|
||||||
|
* Reports how much currency that characters posses
|
||||||
|
* U-Score:
|
||||||
|
* Reports how much U-Score that characters posses
|
||||||
|
* Analytics:
|
||||||
|
* Provide reporting to Developers to help better solve issues
|
||||||
|
* Disabled by default. Set `ALLOW_ANALYTICS` to true to enable.
|
||||||
|
|
||||||
# Deployment
|
# Deployment
|
||||||
|
|
||||||
|
> **NOTE: This tutorial assumes you have a working DLU server instance and**
|
||||||
|
> **some knowledge of Linux**
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-e APP_SECRET_KEY='<secret_key>' \
|
-e APP_SECRET_KEY='<secret_key>' \
|
||||||
-e APP_DATABASE_URI='mysql+pymysql://<username>:<password>@<host>:<port>/<database>' \
|
-e APP_DATABASE_URI='mysql+pymysql://<username>:<password>@<host>:<port>/<database>' \
|
||||||
# you can include other optional Environment Variables from below like this
|
# you can include other optional Environment Variables from below like this
|
||||||
-e REQUIRE_PLAY_KEY=True
|
-e REQUIRE_PLAY_KEY=True
|
||||||
-p 8000:8000/tcp
|
-p 8000:8000/tcp
|
||||||
-v /path/to/unpacked/client:/app/luclient:rw \
|
-v /path/to/unpacked/client:/app/luclient:rw \
|
||||||
-v /path/to/cachedir:/app/cache:rw \ # optional for persistent cache for conversions
|
-v /path/to/cachedir:/app/cache:rw \
|
||||||
aronwk/nexus-dashboard:latest
|
aronwk/nexus-dashboard:latest
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* /app/luclient must be mapped to the location of an unpacked client
|
* `/app/luclient` must be mapped to the location of an unpacked client
|
||||||
* you only need `res/` and `locale/` from the client, but dropping the whole cleint in there won't hurt
|
* you only need `res/` and `locale/` from the client, but dropping the whole client in there won't hurt
|
||||||
* Use `fdb_to_sqlite.py` in lcdr's utilities on `res/cdclient.fdb` in the unpacked client to convert the client database to `cdclient.sqlite`
|
* Use `fdb_to_sqlite.py` in lcdr's utilities on `res/cdclient.fdb` in the unpacked client to convert the client database to `cdclient.sqlite`
|
||||||
* Put teh resulting `cdclient.sqlite` in the res folder: `res/cdclient.sqlite`
|
* Put the resulting `cdclient.sqlite` in the res folder: `res/cdclient.sqlite`
|
||||||
* unzip `res/brickdb.zip` in-place
|
|
||||||
* **Docker will do this for you**
|
|
||||||
* you should have new folders and files in the following places:
|
|
||||||
* `res/Assemblies/../..` with a bunch of sub folders
|
|
||||||
* `res/Primitives/../..` with a bunch of sub folders
|
|
||||||
* `res/info.xml`
|
|
||||||
* `res/Materials.xml`
|
|
||||||
|
|
||||||
### Environmental Variables
|
### Environmental Variables
|
||||||
* Required:
|
|
||||||
|
Please Reference `app/settings_exmaple.py` to see all the variables
|
||||||
|
|
||||||
|
* Required:
|
||||||
* APP_SECRET_KEY (Must be provided)
|
* APP_SECRET_KEY (Must be provided)
|
||||||
* APP_DATABASE_URI (Must be provided)
|
* APP_DATABASE_URI (Must be provided)
|
||||||
* Optional
|
* Everything else is optional and has defaults
|
||||||
* USER_ENABLE_REGISTER (Default: True)
|
|
||||||
* USER_ENABLE_EMAIL (Default: True, Needs Mail to be configured)
|
|
||||||
* USER_ENABLE_CONFIRM_EMAIL (Default: True)
|
|
||||||
* USER_ENABLE_INVITE_USER (Default: False)
|
|
||||||
* USER_REQUIRE_INVITATION (Default: False)
|
|
||||||
* REQUIRE_PLAY_KEY (Default: True)
|
|
||||||
* MAIL_SERVER (Default: smtp.gmail.com)
|
|
||||||
* MAIL_PORT (Default: 587)
|
|
||||||
* MAIL_USE_SSL (Default: False)
|
|
||||||
* MAIL_USE_TLS (Default: True)
|
|
||||||
* MAIL_USERNAME (Default: None)
|
|
||||||
* MAIL_PASSWORD (Default: None)
|
|
||||||
* USER_EMAIL_SENDER_NAME (Default: None)
|
|
||||||
* USER_EMAIL_SENDER_EMAIL (Default: None)
|
|
||||||
|
|
||||||
## Manual
|
## Manual
|
||||||
|
|
||||||
Don't, use Docker /s
|
Thanks to [HailStorm32](https://github.com/HailStorm32) for this manual install guide!
|
||||||
|
### Setting Up The Environment
|
||||||
|
First you will want to install the following packages by executing the following commands
|
||||||
|
`sudo apt-get update`
|
||||||
|
`sudo apt-get install -y python3 python3-pip sqlite3 git unzip libmagickwand-dev`
|
||||||
|
|
||||||
TODO: Make manual deployment easier to configure
|
> *Note: If you are having issues with installing `sqlite3`, change it to `sqlite`*
|
||||||
|
|
||||||
|
<br>
|
||||||
|
Next we will clone the repository. You can clone it anywhere, but for the purpose of this tutorial, we will be cloning it to the home directory.
|
||||||
|
|
||||||
|
`cd` *make sure you are in the home directory*
|
||||||
|
`git clone https://github.com/DarkflameUniverse/NexusDashboard.git`
|
||||||
|
|
||||||
|
You should now have a directory called `NexusDashboard`
|
||||||
|
|
||||||
|
### Setting up
|
||||||
|
|
||||||
|
Rename the example settings file
|
||||||
|
`cp ~/NexusDashboard/app/settings_example.py ~/NexusDashboard/app/settings.py`
|
||||||
|
|
||||||
|
Now let's open the settings file we just created and configure some of the settings
|
||||||
|
`vim ~/NexusDashboard/app/settings.py`
|
||||||
|
>*Feel free to use any text editor you are more comfortable with instead of vim*
|
||||||
|
|
||||||
|
<br>
|
||||||
|
Inside this file is where you can change certain settings like user registration, email support and other things. In this tutorial I will only be focusing on the bare minimum to get up and running, but feel free to adjust what you would like
|
||||||
|
>*Note: Enabling the email option will require further setup that is outside the scope of this tutorial*
|
||||||
|
|
||||||
|
The two important settings to configure are `APP_SECRET_KEY` and `APP_DATABASE_URI`
|
||||||
|
|
||||||
|
For `APP_SECRET_KEY`, fill in any random 32 character string
|
||||||
|
For `APP_DATABASE_URI`, fill in the respective fields
|
||||||
|
```
|
||||||
|
<username> --> database username
|
||||||
|
<password> --> database password
|
||||||
|
<host> --> database address
|
||||||
|
(this will most likely be localhost if you are running the database on the same machine
|
||||||
|
<port> --> port number of the database
|
||||||
|
(this can most likely be left out if you are running the database on the same machine)
|
||||||
|
<database> --> database name
|
||||||
|
```
|
||||||
|
>*If you are omitting `<port>`, make sure to also omit the `:`*
|
||||||
|
|
||||||
|
For a configuration where the database is running on the same machine, it would similar to this
|
||||||
|
```
|
||||||
|
APP_SECRET_KEY = "abcdefghijklmnopqrstuvwxyz123456"
|
||||||
|
APP_DATABASE_URI = "mysql+pymysql://DBusername:DBpassword@localhost/DBname"
|
||||||
|
```
|
||||||
|
The rest of the file is left at the default values
|
||||||
|
|
||||||
|
Once you are done making the changes, save and close the file
|
||||||
|
|
||||||
|
##### Client related files
|
||||||
|
|
||||||
|
We will need the following folders from the client
|
||||||
|
```
|
||||||
|
locale (all of the files inside)
|
||||||
|
|
||||||
|
res
|
||||||
|
|_BrickModels
|
||||||
|
|_brickprimitives
|
||||||
|
|_textures
|
||||||
|
|_ui
|
||||||
|
|_brickdb.zip
|
||||||
|
```
|
||||||
|
Put the two folders in `~/NexusDashboard/app/luclient`
|
||||||
|
|
||||||
|
Unzip the `brickdb.zip` in place
|
||||||
|
`unzip brickdb.zip`
|
||||||
|
|
||||||
|
Remove the `.zip` after you have unzipped it
|
||||||
|
`rm brickdb.zip`
|
||||||
|
|
||||||
|
In the `luclient` directory you should now have a file structure that looks like this
|
||||||
|
```
|
||||||
|
local
|
||||||
|
|_locale.xml
|
||||||
|
|
||||||
|
res
|
||||||
|
|_BrickModels
|
||||||
|
|_...
|
||||||
|
|_brickprimitives
|
||||||
|
|_...
|
||||||
|
|_textures
|
||||||
|
|_...
|
||||||
|
|_ui
|
||||||
|
|_...
|
||||||
|
|_Assemblies
|
||||||
|
|_...
|
||||||
|
|_Primitives
|
||||||
|
|_...
|
||||||
|
|_Materials.xml
|
||||||
|
|_info.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
We will also need to copy the `CDServer.sqlite` database file from the server to the `~/NexusDashboard/app/luclient/res` folder
|
||||||
|
|
||||||
|
Once the file is moved over, you will need to rename it to `cdclient.sqlite`
|
||||||
|
`mv ~/NexusDashboard/app/luclient/res/CDServer.sqlite ~/NexusDashboard/app/luclient/res/cdclient.sqlite`
|
||||||
|
|
||||||
|
|
||||||
|
##### Remaining Setup
|
||||||
|
Run the following commands one at a time
|
||||||
|
|
||||||
|
`cd ~/NexusDashboard`
|
||||||
|
`pip install -r requirements.txt`
|
||||||
|
`pip install gunicorn`
|
||||||
|
`flask db upgrade`
|
||||||
|
|
||||||
|
##### Running the site
|
||||||
|
You can run the site with
|
||||||
|
`gunicorn -b :8000 -w 4 wsgi:app`
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
|
|
||||||
Please use [Editor Config](https://editorconfig.org/)
|
Please use [Editor Config](https://editorconfig.org/)
|
||||||
|
|
||||||
* `flask run` to run local dev server
|
* `flask run` to run local dev server
|
||||||
|
|||||||
122
app/__init__.py
122
app/__init__.py
@@ -7,13 +7,26 @@ import time
|
|||||||
from app.models import db, migrate, PlayKey
|
from app.models import db, migrate, PlayKey
|
||||||
from app.schemas import ma
|
from app.schemas import ma
|
||||||
from app.forms import CustomUserManager
|
from app.forms import CustomUserManager
|
||||||
from flask_user import user_registered, current_user
|
from flask_user import user_registered, current_user, user_logged_in
|
||||||
from flask_wtf.csrf import CSRFProtect
|
from flask_wtf.csrf import CSRFProtect
|
||||||
from flask_apscheduler import APScheduler
|
from flask_apscheduler import APScheduler
|
||||||
from app.luclient import query_cdclient, register_luclient_jinja_helpers
|
from app.luclient import register_luclient_jinja_helpers
|
||||||
|
|
||||||
from app.commands import init_db, init_accounts
|
from app.commands import (
|
||||||
from app.models import Account, AccountInvitation
|
init_db,
|
||||||
|
init_accounts,
|
||||||
|
load_property,
|
||||||
|
gen_image_cache,
|
||||||
|
gen_model_cache,
|
||||||
|
fix_clone_ids,
|
||||||
|
split_ugc
|
||||||
|
)
|
||||||
|
from app.models import Account, AccountInvitation, AuditLog
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
# Instantiate Flask extensions
|
# Instantiate Flask extensions
|
||||||
csrf_protect = CSRFProtect()
|
csrf_protect = CSRFProtect()
|
||||||
@@ -22,7 +35,6 @@ scheduler = APScheduler()
|
|||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
||||||
# decrement uses on a play key after a successful registration
|
# decrement uses on a play key after a successful registration
|
||||||
@@ -33,14 +45,24 @@ def create_app():
|
|||||||
play_key_used = PlayKey.query.filter(PlayKey.id == user.play_key_id).first()
|
play_key_used = PlayKey.query.filter(PlayKey.id == user.play_key_id).first()
|
||||||
play_key_used.key_uses = play_key_used.key_uses - 1
|
play_key_used.key_uses = play_key_used.key_uses - 1
|
||||||
play_key_used.times_used = play_key_used.times_used + 1
|
play_key_used.times_used = play_key_used.times_used + 1
|
||||||
|
app.logger.info(
|
||||||
|
f"USERS::REGISTRATION User with ID {user.id} and name {user.username} Registered \
|
||||||
|
using Play Key ID {play_key_used.id} : {play_key_used.key_string}"
|
||||||
|
)
|
||||||
db.session.add(play_key_used)
|
db.session.add(play_key_used)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
app.logger.info(f"USERS::REGISTRATION User with ID {user.id} and name {user.username} Registered")
|
||||||
|
|
||||||
|
@user_logged_in.connect_via(app)
|
||||||
|
def _after_login_hook(sender, user, **extra):
|
||||||
|
app.logger.info(f"{user.username} Logged in")
|
||||||
|
|
||||||
# A bunch of jinja filters to make things easiers
|
# A bunch of jinja filters to make things easiers
|
||||||
@app.template_filter('ctime')
|
@app.template_filter('ctime')
|
||||||
def timectime(s):
|
def timectime(s):
|
||||||
if s:
|
if s:
|
||||||
return time.ctime(s) # or datetime.datetime.fromtimestamp(s)
|
return time.ctime(s) # or datetime.datetime.fromtimestamp(s)
|
||||||
else:
|
else:
|
||||||
return "Never"
|
return "Never"
|
||||||
|
|
||||||
@@ -51,6 +73,10 @@ def create_app():
|
|||||||
else:
|
else:
|
||||||
return 0 & (1 << bit)
|
return 0 & (1 << bit)
|
||||||
|
|
||||||
|
@app.template_filter('debug')
|
||||||
|
def debug(text):
|
||||||
|
print(text)
|
||||||
|
|
||||||
@app.teardown_appcontext
|
@app.teardown_appcontext
|
||||||
def close_connection(exception):
|
def close_connection(exception):
|
||||||
cdclient = getattr(g, '_cdclient', None)
|
cdclient = getattr(g, '_cdclient', None)
|
||||||
@@ -60,7 +86,13 @@ def create_app():
|
|||||||
# add the commands to flask cli
|
# add the commands to flask cli
|
||||||
app.cli.add_command(init_db)
|
app.cli.add_command(init_db)
|
||||||
app.cli.add_command(init_accounts)
|
app.cli.add_command(init_accounts)
|
||||||
|
app.cli.add_command(load_property)
|
||||||
|
app.cli.add_command(gen_image_cache)
|
||||||
|
app.cli.add_command(gen_model_cache)
|
||||||
|
app.cli.add_command(fix_clone_ids)
|
||||||
|
app.cli.add_command(split_ugc)
|
||||||
|
|
||||||
|
register_logging(app)
|
||||||
register_settings(app)
|
register_settings(app)
|
||||||
register_extensions(app)
|
register_extensions(app)
|
||||||
register_blueprints(app)
|
register_blueprints(app)
|
||||||
@@ -123,8 +155,18 @@ def register_blueprints(app):
|
|||||||
app.register_blueprint(luclient_blueprint, url_prefix='/luclient')
|
app.register_blueprint(luclient_blueprint, url_prefix='/luclient')
|
||||||
from .reports import reports_blueprint
|
from .reports import reports_blueprint
|
||||||
app.register_blueprint(reports_blueprint, url_prefix='/reports')
|
app.register_blueprint(reports_blueprint, url_prefix='/reports')
|
||||||
|
from .api import api_blueprint
|
||||||
|
app.register_blueprint(api_blueprint, url_prefix='/api')
|
||||||
|
|
||||||
|
|
||||||
|
def register_logging(app):
|
||||||
|
# file logger
|
||||||
|
file_handler = RotatingFileHandler('nexus_dashboard.log', maxBytes=1024 * 1024 * 100, backupCount=20)
|
||||||
|
file_handler.setLevel(logging.INFO)
|
||||||
|
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
app.logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
def register_settings(app):
|
def register_settings(app):
|
||||||
"""Register setting from setting and env
|
"""Register setting from setting and env
|
||||||
@@ -134,15 +176,26 @@ def register_settings(app):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Load common settings
|
# Load common settings
|
||||||
app.config.from_object('app.settings')
|
try:
|
||||||
|
app.config.from_object('app.settings')
|
||||||
|
except Exception:
|
||||||
|
app.logger.info("No settings.py, loading from example")
|
||||||
|
app.config.from_object('app.settings_example')
|
||||||
|
|
||||||
# Load environment specific settings
|
# Load environment specific settings
|
||||||
app.config['TESTING'] = False
|
app.config['TESTING'] = False
|
||||||
app.config['DEBUG'] = False
|
app.config['DEBUG'] = False
|
||||||
|
|
||||||
# always pull these two from the env
|
# always pull these two from the env
|
||||||
app.config['SECRET_KEY'] = os.getenv('APP_SECRET_KEY')
|
app.config['SECRET_KEY'] = os.getenv(
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('APP_DATABASE_URI')
|
'APP_SECRET_KEY',
|
||||||
|
app.config['APP_SECRET_KEY']
|
||||||
|
|
||||||
|
)
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv(
|
||||||
|
'APP_DATABASE_URI',
|
||||||
|
app.config['APP_DATABASE_URI']
|
||||||
|
)
|
||||||
|
|
||||||
# try to get overides, otherwise just use what we have already
|
# try to get overides, otherwise just use what we have already
|
||||||
app.config['USER_ENABLE_REGISTER'] = os.getenv(
|
app.config['USER_ENABLE_REGISTER'] = os.getenv(
|
||||||
@@ -169,6 +222,10 @@ def register_settings(app):
|
|||||||
'USER_REQUIRE_INVITATION',
|
'USER_REQUIRE_INVITATION',
|
||||||
app.config['USER_REQUIRE_INVITATION']
|
app.config['USER_REQUIRE_INVITATION']
|
||||||
)
|
)
|
||||||
|
app.config['ALLOW_ANALYTICS'] = os.getenv(
|
||||||
|
'ALLOW_ANALYTICS',
|
||||||
|
app.config['ALLOW_ANALYTICS']
|
||||||
|
)
|
||||||
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
||||||
"pool_pre_ping": True,
|
"pool_pre_ping": True,
|
||||||
"pool_size": 10,
|
"pool_size": 10,
|
||||||
@@ -177,14 +234,38 @@ def register_settings(app):
|
|||||||
"pool_pre_ping": True,
|
"pool_pre_ping": True,
|
||||||
"pool_use_lifo": True
|
"pool_use_lifo": True
|
||||||
}
|
}
|
||||||
app.config['MAIL_SERVER'] = os.getenv('MAIL_SERVER', 'smtp.gmail.com')
|
app.config['MAIL_SERVER'] = os.getenv(
|
||||||
app.config['MAIL_PORT'] = os.getenv('MAIL_USE_SSL', 587)
|
'MAIL_SERVER',
|
||||||
app.config['MAIL_USE_SSL'] = os.getenv('MAIL_USE_SSL', False)
|
app.config['MAIL_SERVER']
|
||||||
app.config['MAIL_USE_TLS'] = os.getenv('MAIL_USE_TLS', True)
|
)
|
||||||
app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME', None)
|
app.config['MAIL_PORT'] = os.getenv(
|
||||||
app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD', None)
|
'MAIL_USE_SSL',
|
||||||
app.config['USER_EMAIL_SENDER_NAME'] = os.getenv('USER_EMAIL_SENDER_NAME', None)
|
app.config['MAIL_PORT']
|
||||||
app.config['USER_EMAIL_SENDER_EMAIL'] = os.getenv('USER_EMAIL_SENDER_EMAIL', None)
|
)
|
||||||
|
app.config['MAIL_USE_SSL'] = os.getenv(
|
||||||
|
'MAIL_USE_SSL',
|
||||||
|
app.config['MAIL_USE_SSL']
|
||||||
|
)
|
||||||
|
app.config['MAIL_USE_TLS'] = os.getenv(
|
||||||
|
'MAIL_USE_TLS',
|
||||||
|
app.config['MAIL_USE_TLS']
|
||||||
|
)
|
||||||
|
app.config['MAIL_USERNAME'] = os.getenv(
|
||||||
|
'MAIL_USERNAME',
|
||||||
|
app.config['MAIL_USERNAME']
|
||||||
|
)
|
||||||
|
app.config['MAIL_PASSWORD'] = os.getenv(
|
||||||
|
'MAIL_PASSWORD',
|
||||||
|
app.config['MAIL_PASSWORD']
|
||||||
|
)
|
||||||
|
app.config['USER_EMAIL_SENDER_NAME'] = os.getenv(
|
||||||
|
'USER_EMAIL_SENDER_NAME',
|
||||||
|
app.config['USER_EMAIL_SENDER_NAME']
|
||||||
|
)
|
||||||
|
app.config['USER_EMAIL_SENDER_EMAIL'] = os.getenv(
|
||||||
|
'USER_EMAIL_SENDER_EMAIL',
|
||||||
|
app.config['USER_EMAIL_SENDER_EMAIL']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def gm_level(gm_level):
|
def gm_level(gm_level):
|
||||||
@@ -201,3 +282,10 @@ def gm_level(gm_level):
|
|||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def log_audit(message):
|
||||||
|
AuditLog(
|
||||||
|
account_id=current_user.id,
|
||||||
|
action=message
|
||||||
|
).save()
|
||||||
|
|||||||
123
app/accounts.py
123
app/accounts.py
@@ -1,18 +1,30 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, current_app, flash
|
from flask import render_template, Blueprint, redirect, url_for, request, current_app, flash
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required, current_user
|
||||||
import json
|
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
from app.models import (
|
||||||
from app.models import Account, AccountInvitation, db
|
Account,
|
||||||
|
CharacterInfo,
|
||||||
|
ActivityLog,
|
||||||
|
Leaderboard,
|
||||||
|
Mail,
|
||||||
|
Property,
|
||||||
|
PropertyContent,
|
||||||
|
UGC,
|
||||||
|
AuditLog,
|
||||||
|
BugReport,
|
||||||
|
AccountInvitation,
|
||||||
|
db
|
||||||
|
)
|
||||||
from app.schemas import AccountSchema
|
from app.schemas import AccountSchema
|
||||||
from app import gm_level
|
from app import gm_level, log_audit
|
||||||
from app.forms import EditGMLevelForm
|
from app.forms import EditGMLevelForm, EditEmailForm
|
||||||
|
|
||||||
accounts_blueprint = Blueprint('accounts', __name__)
|
accounts_blueprint = Blueprint('accounts', __name__)
|
||||||
|
|
||||||
account_schema = AccountSchema()
|
account_schema = AccountSchema()
|
||||||
|
|
||||||
|
|
||||||
@accounts_blueprint.route('/', methods=['GET'])
|
@accounts_blueprint.route('/', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
@@ -38,7 +50,7 @@ def edit_gm_level(id):
|
|||||||
if current_user.id == int(id):
|
if current_user.id == int(id):
|
||||||
flash("You cannot your own GM Level", "danger")
|
flash("You cannot your own GM Level", "danger")
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
account_data = Account.query.filter(Account.id==id).first()
|
account_data = Account.query.filter(Account.id == id).first()
|
||||||
if account_data.gm_level >= 8 and current_user.gm_level == 8:
|
if account_data.gm_level >= 8 and current_user.gm_level == 8:
|
||||||
flash("You cannot edit this user's GM Level", "warning")
|
flash("You cannot edit this user's GM Level", "warning")
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
@@ -46,8 +58,10 @@ def edit_gm_level(id):
|
|||||||
form = EditGMLevelForm()
|
form = EditGMLevelForm()
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
|
log_audit(f"Changed ({account_data.id}){account_data.username}'s GM Level from {account_data.gm_level} to {form.gm_level.data}")
|
||||||
account_data.gm_level = form.gm_level.data
|
account_data.gm_level = form.gm_level.data
|
||||||
account_data.save()
|
account_data.save()
|
||||||
|
|
||||||
return redirect(url_for('accounts.view', id=account_data.id))
|
return redirect(url_for('accounts.view', id=account_data.id))
|
||||||
|
|
||||||
form.gm_level.data = account_data.gm_level
|
form.gm_level.data = account_data.gm_level
|
||||||
@@ -55,17 +69,38 @@ def edit_gm_level(id):
|
|||||||
return render_template('accounts/edit_gm_level.html.j2', form=form, username=account_data.username)
|
return render_template('accounts/edit_gm_level.html.j2', form=form, username=account_data.username)
|
||||||
|
|
||||||
|
|
||||||
|
@accounts_blueprint.route('/edit_email/<id>', methods=('GET', 'POST'))
|
||||||
|
@login_required
|
||||||
|
@gm_level(8)
|
||||||
|
def edit_email(id):
|
||||||
|
account_data = Account.query.filter(Account.id == id).first()
|
||||||
|
form = EditEmailForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
log_audit(f"Changed ({account_data.id}){account_data.username}'s Email from {account_data.email} to {form.email.data}")
|
||||||
|
account_data.email = form.email.data
|
||||||
|
account_data.save()
|
||||||
|
return redirect(url_for('accounts.view', id=account_data.id))
|
||||||
|
|
||||||
|
form.email.data = account_data.email
|
||||||
|
return render_template('accounts/edit_email.html.j2', form=form, username=account_data.username)
|
||||||
|
|
||||||
|
|
||||||
@accounts_blueprint.route('/lock/<id>', methods=['GET'])
|
@accounts_blueprint.route('/lock/<id>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def lock(id):
|
def lock(id):
|
||||||
account = Account.query.filter(Account.id == id).first()
|
account = Account.query.filter(Account.id == id).first()
|
||||||
account.locked = not account.locked
|
if not account.locked:
|
||||||
account.save()
|
account.locked = True
|
||||||
if account.locked:
|
account.active = False
|
||||||
|
log_audit(f"Locked ({account.id}){account.username}")
|
||||||
flash("Locked Account", "danger")
|
flash("Locked Account", "danger")
|
||||||
else:
|
else:
|
||||||
|
account.locked = False
|
||||||
|
account.active = True
|
||||||
|
log_audit(f"Unlocked ({account.id}){account.username}")
|
||||||
flash("Unlocked account", "success")
|
flash("Unlocked account", "success")
|
||||||
|
account.save()
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
|
|
||||||
|
|
||||||
@@ -74,12 +109,17 @@ def lock(id):
|
|||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def ban(id):
|
def ban(id):
|
||||||
account = Account.query.filter(Account.id == id).first()
|
account = Account.query.filter(Account.id == id).first()
|
||||||
account.banned = not account.banned
|
if not account.banned:
|
||||||
account.save()
|
account.banned = True
|
||||||
if account.banned:
|
account.active = False
|
||||||
|
log_audit(f"Banned ({account.id}){account.username}")
|
||||||
flash("Banned Account", "danger")
|
flash("Banned Account", "danger")
|
||||||
else:
|
else:
|
||||||
|
account.banned = False
|
||||||
|
account.active = True
|
||||||
|
log_audit(f"Unbanned ({account.id}){account.username}")
|
||||||
flash("Unbanned account", "success")
|
flash("Unbanned account", "success")
|
||||||
|
account.save()
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
|
|
||||||
|
|
||||||
@@ -90,16 +130,60 @@ def mute(id, days=0):
|
|||||||
account = Account.query.filter(Account.id == id).first()
|
account = Account.query.filter(Account.id == id).first()
|
||||||
if days == "0":
|
if days == "0":
|
||||||
account.mute_expire = 0
|
account.mute_expire = 0
|
||||||
|
log_audit(f"Unmuted ({account.id}){account.username}")
|
||||||
flash("Unmuted Account", "success")
|
flash("Unmuted Account", "success")
|
||||||
else:
|
else:
|
||||||
muted_intil = datetime.datetime.now() + datetime.timedelta(days=int(days))
|
muted_intil = datetime.datetime.now() + datetime.timedelta(days=int(days))
|
||||||
account.mute_expire = muted_intil.timestamp()
|
account.mute_expire = muted_intil.timestamp()
|
||||||
|
log_audit(f"Muted ({account.id}){account.username} for {days} days")
|
||||||
flash(f"Muted account for {days} days", "danger")
|
flash(f"Muted account for {days} days", "danger")
|
||||||
account.save()
|
account.save()
|
||||||
|
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
|
|
||||||
|
|
||||||
|
@accounts_blueprint.route('/delete/<id>/', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(9)
|
||||||
|
def delete(id):
|
||||||
|
account = Account.query.filter(Account.id == id).first()
|
||||||
|
message = f"Deleted Account ({account.id}){account.username}"
|
||||||
|
chars = CharacterInfo.query.filter(CharacterInfo.account_id == id).all()
|
||||||
|
for char in chars:
|
||||||
|
activities = ActivityLog.query.filter(ActivityLog.character_id == char.id).all()
|
||||||
|
for activity in activities:
|
||||||
|
activity.delete()
|
||||||
|
lb_entries = Leaderboard.query.filter(Leaderboard.character_id == char.id).all()
|
||||||
|
for lb_entry in lb_entries:
|
||||||
|
lb_entry.delete()
|
||||||
|
mails = Mail.query.filter(Mail.receiver_id == char.id).all()
|
||||||
|
for mail in mails:
|
||||||
|
mail.delete()
|
||||||
|
props = Property.query.filter(Property.owner_id == char.id).all()
|
||||||
|
for prop in props:
|
||||||
|
prop_contents = PropertyContent.query.filter(PropertyContent.property_id == prop.id).all()
|
||||||
|
for prop_content in prop_contents:
|
||||||
|
if prop_content.lot == "14":
|
||||||
|
UGC.query.filter(UGC.id == prop.ugc_id).first().delete()
|
||||||
|
prop_content.delete()
|
||||||
|
prop.delete()
|
||||||
|
char.delete()
|
||||||
|
# This is for GM stuff, it will be permnently delete logs
|
||||||
|
bugs = BugReport.query.filter(BugReport.resolve_by_id == id).all()
|
||||||
|
for bug in bugs:
|
||||||
|
bug.delete()
|
||||||
|
audits = AuditLog.query.filter(AuditLog.account_id == id).all()
|
||||||
|
for audit in audits:
|
||||||
|
audit.delete()
|
||||||
|
invites = AccountInvitation.query.filter(AccountInvitation.invited_by_user_id == id).all()
|
||||||
|
for invite in invites:
|
||||||
|
invite.delete()
|
||||||
|
account.delete()
|
||||||
|
flash(message, "danger")
|
||||||
|
log_audit(message)
|
||||||
|
return redirect(url_for("main.index"))
|
||||||
|
|
||||||
|
|
||||||
@accounts_blueprint.route('/get', methods=['GET'])
|
@accounts_blueprint.route('/get', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
@@ -130,10 +214,10 @@ def get():
|
|||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
# <a role="button" class="btn btn-danger btn btn-block"
|
# <a role="button" class="btn btn-danger btn btn-block"
|
||||||
# href='{url_for('acounts.delete', id=account["0"])}'>
|
# href='{url_for('acounts.delete', id=account["0"])}'>
|
||||||
# Delete
|
# Delete
|
||||||
# </a>
|
# </a>
|
||||||
|
|
||||||
if account["4"]:
|
if account["4"]:
|
||||||
account["4"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
|
account["4"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
|
||||||
@@ -146,13 +230,13 @@ def get():
|
|||||||
account["5"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
account["5"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
||||||
|
|
||||||
if account["6"]:
|
if account["6"]:
|
||||||
account["6"] = f'''<h2 class="far fa-times-circle text-danger"></h2>'''
|
account["6"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
|
||||||
else:
|
else:
|
||||||
account["6"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
account["6"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
||||||
|
|
||||||
if current_app.config["USER_ENABLE_EMAIL"]:
|
if current_app.config["USER_ENABLE_EMAIL"]:
|
||||||
if account["8"]:
|
if account["8"]:
|
||||||
account["8"] = f'''<h2 class="far fa-check-square text-success"></h2>'''
|
account["8"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
||||||
else:
|
else:
|
||||||
account["8"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
|
account["8"] = '''<h2 class="far fa-times-circle text-danger"></h2>'''
|
||||||
else:
|
else:
|
||||||
@@ -167,4 +251,3 @@ def get():
|
|||||||
del account["8"]
|
del account["8"]
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
27
app/api.py
Normal file
27
app/api.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from flask import Blueprint, current_app, request
|
||||||
|
|
||||||
|
api_blueprint = Blueprint('api', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@api_blueprint.route('/web', methods=['GET', 'POST'])
|
||||||
|
def web():
|
||||||
|
current_app.logger.info(f"API::WEB [DATA] {request.data}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@api_blueprint.route('/game', methods=['GET', 'POST'])
|
||||||
|
def game():
|
||||||
|
current_app.logger.info(f"API::GAME [DATA] {request.data}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@api_blueprint.route('/game_content', methods=['GET', 'POST'])
|
||||||
|
def game_content():
|
||||||
|
current_app.logger.info(f"API::GAME CONTENT [DATA] {request.data}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@api_blueprint.route('/metrics_data_service', methods=['GET', 'POST'])
|
||||||
|
def metrics_data_service():
|
||||||
|
current_app.logger.info(f"API::METRICS DATA SERVICE [DATA] {request.data}")
|
||||||
|
return
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash
|
from flask import render_template, Blueprint, redirect, url_for, request, flash, redirect
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required, current_user
|
||||||
from app.models import db, BugReport, CharacterInfo
|
from app.models import db, BugReport, CharacterInfo
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
@@ -8,22 +8,28 @@ from app.luclient import translate_from_locale
|
|||||||
|
|
||||||
bug_report_blueprint = Blueprint('bug_reports', __name__)
|
bug_report_blueprint = Blueprint('bug_reports', __name__)
|
||||||
|
|
||||||
|
|
||||||
@bug_report_blueprint.route('/<status>', methods=['GET'])
|
@bug_report_blueprint.route('/<status>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
|
||||||
def index(status):
|
def index(status):
|
||||||
return render_template('bug_reports/index.html.j2', status=status)
|
return render_template('bug_reports/index.html.j2', status=status)
|
||||||
|
|
||||||
|
|
||||||
@bug_report_blueprint.route('/view/<id>', methods=['GET'])
|
@bug_report_blueprint.route('/view/<id>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
|
||||||
def view(id):
|
def view(id):
|
||||||
report = BugReport.query.filter(BugReport.id == id).first()
|
report = BugReport.query.filter(BugReport.id == id).first()
|
||||||
|
if current_user.gm_level < 3:
|
||||||
|
chars = CharacterInfo.query.with_entities(CharacterInfo.id).filter(CharacterInfo.account_id == current_user.id).all()
|
||||||
|
char_ids = []
|
||||||
|
for char in chars:
|
||||||
|
char_ids.append(char[0])
|
||||||
|
if report.reporter_id not in char_ids:
|
||||||
|
return redirect(url_for('bug_reports.index', status=all))
|
||||||
if report.resoleved_by:
|
if report.resoleved_by:
|
||||||
rb = report.resoleved_by.username
|
rb = report.resoleved_by.username
|
||||||
else:
|
else:
|
||||||
rb=""
|
rb = ""
|
||||||
return render_template('bug_reports/view.html.j2', report=report, resolved_by=rb)
|
return render_template('bug_reports/view.html.j2', report=report, resolved_by=rb)
|
||||||
|
|
||||||
|
|
||||||
@@ -49,27 +55,37 @@ def resolve(id):
|
|||||||
|
|
||||||
@bug_report_blueprint.route('/get/<status>', methods=['GET'])
|
@bug_report_blueprint.route('/get/<status>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
|
||||||
def get(status):
|
def get(status):
|
||||||
columns = [
|
columns = [
|
||||||
ColumnDT(BugReport.id), # 0
|
ColumnDT(BugReport.id), # 0
|
||||||
ColumnDT(BugReport.body), # 1
|
ColumnDT(BugReport.reporter_id), # 1
|
||||||
ColumnDT(BugReport.client_version), # 2
|
ColumnDT(BugReport.body), # 2
|
||||||
ColumnDT(BugReport.other_player_id), # 3
|
ColumnDT(BugReport.client_version), # 3
|
||||||
ColumnDT(BugReport.selection), # 4
|
ColumnDT(BugReport.other_player_id), # 4
|
||||||
ColumnDT(BugReport.submitted), # 5
|
ColumnDT(BugReport.selection), # 5
|
||||||
ColumnDT(BugReport.resolved_time), # 6
|
ColumnDT(BugReport.submitted), # 6
|
||||||
|
ColumnDT(BugReport.resolved_time), # 7
|
||||||
]
|
]
|
||||||
|
|
||||||
query = None
|
query = None
|
||||||
if status=="all":
|
if current_user.gm_level > 0:
|
||||||
query = db.session.query().select_from(BugReport)
|
if status == "resolved":
|
||||||
elif status=="resolved":
|
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time != None)
|
||||||
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time != None)
|
elif status == "unresolved":
|
||||||
elif status=="unresolved":
|
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time == None)
|
||||||
query = db.session.query().select_from(BugReport).filter(BugReport.resolved_time == None)
|
else:
|
||||||
|
query = db.session.query().select_from(BugReport)
|
||||||
else:
|
else:
|
||||||
raise Exception("Not a valid filter")
|
chars = CharacterInfo.query.with_entities(CharacterInfo.id).filter(CharacterInfo.account_id == current_user.id).all()
|
||||||
|
char_ids = []
|
||||||
|
for char in chars:
|
||||||
|
char_ids.append(char[0])
|
||||||
|
if status == "resolved":
|
||||||
|
query = db.session.query().select_from(BugReport).filter(BugReport.reporter_id.in_(char_ids)).filter(BugReport.resolved_time != None)
|
||||||
|
elif status == "unresolved":
|
||||||
|
query = db.session.query().select_from(BugReport).filter(BugReport.reporter_id.in_(char_ids)).filter(BugReport.resolved_time == None)
|
||||||
|
else:
|
||||||
|
query = db.session.query().select_from(BugReport).filter(BugReport.reporter_id.in_(char_ids))
|
||||||
|
|
||||||
params = request.args.to_dict()
|
params = request.args.to_dict()
|
||||||
|
|
||||||
@@ -85,31 +101,46 @@ def get(status):
|
|||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not report["6"]:
|
if report["7"] is None:
|
||||||
report["0"] += f"""
|
report["0"] += f"""
|
||||||
<a role="button" class="btn btn-danger btn btn-block"
|
<a role="button" class="btn btn-danger btn btn-block"
|
||||||
href='{url_for('bug_reports.resolve', id=id)}'>
|
href='{url_for('bug_reports.resolve', id=id)}'>
|
||||||
Resolve
|
Resolve
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
|
report["7"] = '''<h1 class="far fa-times-circle text-danger"></h1>'''
|
||||||
|
|
||||||
if report["3"] == "0":
|
if not report["1"]:
|
||||||
report["3"] = "None"
|
report["1"] = "None"
|
||||||
else:
|
else:
|
||||||
character = CharacterInfo.query.filter(CharacterInfo.id == int(report["3"]) & 0xFFFFFFFF).first()
|
character = CharacterInfo.query.filter(CharacterInfo.id == int(report["1"])).first()
|
||||||
if character:
|
if character:
|
||||||
report["3"] = f"""
|
report["1"] = f"""
|
||||||
<a role="button" class="btn btn-primary btn btn-block"
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
href='{url_for('characters.view', id=(int(report["3"]) & 0xFFFFFFFF))}'>
|
href='{url_for('characters.view', id=report['1'])}'>
|
||||||
{character.name}
|
{character.name}
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
else:
|
else:
|
||||||
report["3"] = "Player Deleted"
|
report["1"] = "Player Deleted"
|
||||||
|
|
||||||
report["4"] = translate_from_locale(report["4"][2:-1])
|
if report["4"] == "0":
|
||||||
|
report["4"] = "None"
|
||||||
|
else:
|
||||||
|
character = CharacterInfo.query.filter(CharacterInfo.id == int(report["4"]) & 0xFFFFFFFF).first()
|
||||||
|
if character:
|
||||||
|
if current_user.gm_level > 3:
|
||||||
|
report["4"] = f"""
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{url_for('characters.view', id=(int(report["4"]) & 0xFFFFFFFF))}'>
|
||||||
|
{character.name}
|
||||||
|
</a>
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
report["4"] = character.name
|
||||||
|
else:
|
||||||
|
report["4"] = "Player Deleted"
|
||||||
|
|
||||||
if not report["6"]:
|
report["5"] = translate_from_locale(report["5"][2:-1])
|
||||||
report["6"] = '''<h1 class="far fa-times-circle text-danger"></h1>'''
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash
|
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash, make_response
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required, current_user
|
||||||
import json
|
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
import datetime, time
|
import time
|
||||||
from app.models import CharacterInfo, CharacterXML, Account, db
|
from app.models import CharacterInfo, CharacterXML, Account, db
|
||||||
from app.schemas import CharacterInfoSchema
|
from app.schemas import CharacterInfoSchema
|
||||||
from app import gm_level
|
from app.forms import RescueForm
|
||||||
|
from app import gm_level, log_audit
|
||||||
|
from app.luclient import translate_from_locale
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
character_blueprint = Blueprint('characters', __name__)
|
character_blueprint = Blueprint('characters', __name__)
|
||||||
|
|
||||||
character_schema = CharacterInfoSchema()
|
character_schema = CharacterInfoSchema()
|
||||||
|
|
||||||
|
|
||||||
@character_blueprint.route('/', methods=['GET'])
|
@character_blueprint.route('/', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
@@ -23,21 +27,27 @@ def index():
|
|||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def approve_name(id, action):
|
def approve_name(id, action):
|
||||||
character = CharacterInfo.query.filter(CharacterInfo.id == id).first()
|
character = CharacterInfo.query.filter(CharacterInfo.id == id).first()
|
||||||
|
|
||||||
if action == "approve":
|
if action == "approve":
|
||||||
|
log_audit(f"Approved ({character.id}){character.pending_name} from {character.name}")
|
||||||
|
flash(
|
||||||
|
f"Approved ({character.id}){character.pending_name} from {character.name}",
|
||||||
|
"success"
|
||||||
|
)
|
||||||
if character.pending_name:
|
if character.pending_name:
|
||||||
character.name = character.pending_name
|
character.name = character.pending_name
|
||||||
character.pending_name = ""
|
character.pending_name = ""
|
||||||
character.needs_rename = False
|
character.needs_rename = False
|
||||||
flash(
|
|
||||||
f"Approved name {character.name}",
|
|
||||||
"success"
|
|
||||||
)
|
|
||||||
elif action == "rename":
|
elif action == "rename":
|
||||||
character.needs_rename = True
|
character.needs_rename = True
|
||||||
|
log_audit(
|
||||||
|
f"Marked character ({character.id}){character.name} \
|
||||||
|
(Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename")
|
||||||
flash(
|
flash(
|
||||||
f"Marked character {character.name} (Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename",
|
f"Marked character {character.name} \
|
||||||
|
(Pending Name: {character.pending_name if character.pending_name else 'None'}) as needing Rename",
|
||||||
"danger"
|
"danger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,11 +70,11 @@ def view(id):
|
|||||||
abort(403)
|
abort(403)
|
||||||
return
|
return
|
||||||
character_json = xmltodict.parse(
|
character_json = xmltodict.parse(
|
||||||
CharacterXML.query.filter(
|
CharacterXML.query.filter(
|
||||||
CharacterXML.id==id
|
CharacterXML.id == id
|
||||||
).first().xml_data,
|
).first().xml_data,
|
||||||
attr_prefix="attr_"
|
attr_prefix="attr_"
|
||||||
)
|
)
|
||||||
|
|
||||||
# print json for reference
|
# print json for reference
|
||||||
# with open("errorchar.json", "a") as file:
|
# with open("errorchar.json", "a") as file:
|
||||||
@@ -77,8 +87,7 @@ def view(id):
|
|||||||
# sort by items slot index
|
# sort by items slot index
|
||||||
for inv in character_json["obj"]["inv"]["holdings"]["in"]:
|
for inv in character_json["obj"]["inv"]["holdings"]["in"]:
|
||||||
if "i" in inv.keys() and type(inv["i"]) == list:
|
if "i" in inv.keys() and type(inv["i"]) == list:
|
||||||
inv["i"] = sorted(inv["i"], key = lambda i: int(i['attr_s']))
|
inv["i"] = sorted(inv["i"], key=lambda i: int(i['attr_s']))
|
||||||
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'character/view.html.j2',
|
'character/view.html.j2',
|
||||||
@@ -87,6 +96,58 @@ def view(id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@character_blueprint.route('/view_xml/<id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def view_xml(id):
|
||||||
|
|
||||||
|
character_data = CharacterInfo.query.filter(CharacterInfo.id == id).first()
|
||||||
|
|
||||||
|
if character_data == {}:
|
||||||
|
abort(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
if current_user.gm_level < 3:
|
||||||
|
if character_data.account_id and character_data.account_id != current_user.id:
|
||||||
|
abort(403)
|
||||||
|
return
|
||||||
|
|
||||||
|
character_xml = CharacterXML.query.filter(
|
||||||
|
CharacterXML.id == id
|
||||||
|
).first().xml_data
|
||||||
|
|
||||||
|
response = make_response(character_xml)
|
||||||
|
response.headers.set('Content-Type', 'text/xml')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@character_blueprint.route('/get_xml/<id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_xml(id):
|
||||||
|
|
||||||
|
character_data = CharacterInfo.query.filter(CharacterInfo.id == id).first()
|
||||||
|
|
||||||
|
if character_data == {}:
|
||||||
|
abort(404)
|
||||||
|
return
|
||||||
|
if current_user.gm_level < 3:
|
||||||
|
if character_data.account_id and character_data.account_id != current_user.id:
|
||||||
|
abort(403)
|
||||||
|
return
|
||||||
|
|
||||||
|
character_xml = CharacterXML.query.filter(
|
||||||
|
CharacterXML.id == id
|
||||||
|
).first().xml_data
|
||||||
|
|
||||||
|
response = make_response(character_xml)
|
||||||
|
response.headers.set('Content-Type', 'attachment/xml')
|
||||||
|
response.headers.set(
|
||||||
|
'Content-Disposition',
|
||||||
|
'attachment',
|
||||||
|
filename=f"{character_data.name}.xml"
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@character_blueprint.route('/restrict/<bit>/<id>', methods=['GET'])
|
@character_blueprint.route('/restrict/<bit>/<id>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
@@ -103,12 +164,52 @@ def restrict(id, bit):
|
|||||||
abort(404)
|
abort(404)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
log_audit(f"Updated ({character_data.id}){character_data.name}'s permission map to \
|
||||||
|
{character_data.permission_map ^ (1 << int(bit))} from {character_data.permission_map}")
|
||||||
|
|
||||||
character_data.permission_map ^= (1 << int(bit))
|
character_data.permission_map ^= (1 << int(bit))
|
||||||
character_data.save()
|
character_data.save()
|
||||||
|
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
|
|
||||||
|
|
||||||
|
@character_blueprint.route('/rescue/<id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def rescue(id):
|
||||||
|
|
||||||
|
form = RescueForm()
|
||||||
|
|
||||||
|
character_data = CharacterXML.query.filter(
|
||||||
|
CharacterXML.id == id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
character_xml = ET.XML(character_data.xml_data)
|
||||||
|
for zone in character_xml.findall('.//r'):
|
||||||
|
if int(zone.attrib["w"]) % 100 == 0:
|
||||||
|
form.save_world.choices.append(
|
||||||
|
(
|
||||||
|
zone.attrib["w"],
|
||||||
|
translate_from_locale(f"ZoneTable_{zone.attrib['w']}_DisplayDescription")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
new_zone = character_xml.find(f'.//r[@w="{form.save_world.data}"]')
|
||||||
|
char = character_xml.find(".//char")
|
||||||
|
char.attrib["lzx"] = new_zone.attrib["x"]
|
||||||
|
char.attrib["lzy"] = new_zone.attrib["y"]
|
||||||
|
char.attrib["lzz"] = new_zone.attrib["z"]
|
||||||
|
char.attrib["lwid"] = form.save_world.data
|
||||||
|
|
||||||
|
character_data.xml_data = ET.tostring(character_xml)
|
||||||
|
character_data.save()
|
||||||
|
|
||||||
|
return redirect(url_for('characters.view', id=id))
|
||||||
|
|
||||||
|
return render_template("character/rescue.html.j2", form=form)
|
||||||
|
|
||||||
|
|
||||||
@character_blueprint.route('/get/<status>', methods=['GET'])
|
@character_blueprint.route('/get/<status>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
@@ -124,14 +225,12 @@ def get(status):
|
|||||||
]
|
]
|
||||||
|
|
||||||
query = None
|
query = None
|
||||||
if status=="all":
|
if status == "approved":
|
||||||
query = db.session.query().select_from(CharacterInfo).join(Account)
|
|
||||||
elif status=="approved":
|
|
||||||
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name == "") & (CharacterInfo.needs_rename == False))
|
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name == "") & (CharacterInfo.needs_rename == False))
|
||||||
elif status=="unapproved":
|
elif status == "unapproved":
|
||||||
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name != "") | (CharacterInfo.needs_rename == True))
|
query = db.session.query().select_from(CharacterInfo).join(Account).filter((CharacterInfo.pending_name != "") | (CharacterInfo.needs_rename == True))
|
||||||
else:
|
else:
|
||||||
raise Exception("Not a valid filter")
|
query = db.session.query().select_from(CharacterInfo).join(Account)
|
||||||
|
|
||||||
params = request.args.to_dict()
|
params = request.args.to_dict()
|
||||||
|
|
||||||
@@ -190,6 +289,4 @@ def get(status):
|
|||||||
if perm_map & (1 << 6):
|
if perm_map & (1 << 6):
|
||||||
character["6"] += "Restricted Chat</br>"
|
character["6"] += "Restricted Chat</br>"
|
||||||
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
348
app/commands.py
348
app/commands.py
@@ -1,10 +1,23 @@
|
|||||||
import click
|
import click
|
||||||
import json
|
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
import random, string, datetime
|
import random
|
||||||
|
import string
|
||||||
|
import datetime
|
||||||
from flask_user import current_app
|
from flask_user import current_app
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Account, PlayKey
|
from app.models import Account, PlayKey, CharacterInfo, Property, PropertyContent, UGC, Mail
|
||||||
|
import pathlib
|
||||||
|
import zlib
|
||||||
|
from wand import image
|
||||||
|
from wand.exceptions import BlobError as BE
|
||||||
|
import app.pylddlib as ldd
|
||||||
|
from multiprocessing import Pool
|
||||||
|
from functools import partial
|
||||||
|
from sqlalchemy import func
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import xmltodict
|
||||||
|
|
||||||
|
|
||||||
@click.command("init_db")
|
@click.command("init_db")
|
||||||
@click.argument('drop_tables', nargs=1)
|
@click.argument('drop_tables', nargs=1)
|
||||||
@@ -29,16 +42,203 @@ def init_accounts():
|
|||||||
|
|
||||||
# Add accounts
|
# Add accounts
|
||||||
print('Creating Admin account.')
|
print('Creating Admin account.')
|
||||||
admin_account = find_or_create_account(
|
find_or_create_account(
|
||||||
'admin',
|
'admin',
|
||||||
'example@example.com',
|
'example@example.com',
|
||||||
'Nope',
|
'Nope',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("fix_clone_ids")
|
||||||
|
@with_appcontext
|
||||||
|
def fix_clone_ids():
|
||||||
|
"""
|
||||||
|
Fix incorrect prop_clone_id's
|
||||||
|
Remove duplicate properties
|
||||||
|
Either the one with most models or most recently claimed
|
||||||
|
Retuen Pre-built models via mail
|
||||||
|
(May have errors and need to be run multiple times)
|
||||||
|
"""
|
||||||
|
properties = Property.query.all()
|
||||||
|
count = 0
|
||||||
|
for prop in properties:
|
||||||
|
char = CharacterInfo.query.filter(CharacterInfo.id == prop.owner_id).first()
|
||||||
|
if char.prop_clone_id != prop.clone_id:
|
||||||
|
count += 1
|
||||||
|
prop.clone_id = char.prop_clone_id
|
||||||
|
prop.save()
|
||||||
|
|
||||||
|
print(f"Fixed {count} props where clone id did not match owner's clone id")
|
||||||
|
|
||||||
|
dupes = 0
|
||||||
|
characters = CharacterInfo.query.all()
|
||||||
|
for char in characters:
|
||||||
|
props = Property.query.with_entities(
|
||||||
|
Property.zone_id, func.count(Property.zone_id)
|
||||||
|
).group_by(Property.zone_id).filter(
|
||||||
|
Property.owner_id == char.id
|
||||||
|
).all()
|
||||||
|
for prop in props:
|
||||||
|
if prop[1] != 1:
|
||||||
|
dupes += 1
|
||||||
|
print(f"found dupe on {char.name}'s {prop[0]}")
|
||||||
|
dupe_props = Property.query.filter(
|
||||||
|
Property.owner_id == char.id
|
||||||
|
).filter(
|
||||||
|
Property.zone_id == prop[0]).all()
|
||||||
|
dupe_data = []
|
||||||
|
# id, content_count
|
||||||
|
for dprop in dupe_props:
|
||||||
|
dupe_data.append(
|
||||||
|
[
|
||||||
|
dprop.id,
|
||||||
|
PropertyContent.query.filter(PropertyContent.property_id == dprop.id).count(),
|
||||||
|
dprop.time_claimed
|
||||||
|
]
|
||||||
|
)
|
||||||
|
max_models = max(dupe_data, key=lambda x: x[1])
|
||||||
|
if max_models[1] == 0:
|
||||||
|
newest = max(dupe_data, key=lambda x: x[2])
|
||||||
|
for data in dupe_data:
|
||||||
|
if data[2] != newest[2]:
|
||||||
|
Property.query.filter(Property.id == data[0]).first().delete()
|
||||||
|
else:
|
||||||
|
for data in dupe_data:
|
||||||
|
if data[1] != max_models[1]:
|
||||||
|
contents = PropertyContent.query.filter(PropertyContent.property_id == dprop.id).all()
|
||||||
|
if contents:
|
||||||
|
for content in contents:
|
||||||
|
if content.lot == 14:
|
||||||
|
UGC.query.filter(content.ugc_id).first().delete()
|
||||||
|
content.delete()
|
||||||
|
else:
|
||||||
|
Mail(
|
||||||
|
sender_id=0,
|
||||||
|
sender_name="System",
|
||||||
|
receiver_id=char.id,
|
||||||
|
receiver_name=char.name,
|
||||||
|
time_sent=time.time(),
|
||||||
|
subject="Returned Model",
|
||||||
|
body="This model was returned to you from a property cleanup script",
|
||||||
|
attachment_id=0,
|
||||||
|
attachment_lot=content.lot,
|
||||||
|
attachment_count=1
|
||||||
|
).save()
|
||||||
|
content.delete()
|
||||||
|
time.sleep(1)
|
||||||
|
Property.query.filter(Property.id == data[0]).first().delete()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("load_property")
|
||||||
|
@click.argument('zone')
|
||||||
|
@click.argument('player')
|
||||||
|
@with_appcontext
|
||||||
|
def load_property(zone, player):
|
||||||
|
"""shoves property data into db"""
|
||||||
|
char = CharacterInfo.query.filter(CharacterInfo.name == player).first()
|
||||||
|
if not char:
|
||||||
|
print("Character not Found")
|
||||||
|
return 404
|
||||||
|
|
||||||
|
prop = Property.query.filter(Property.owner_id == char.id).filter(Property.zone_id == zone).first()
|
||||||
|
|
||||||
|
if not prop:
|
||||||
|
print(f"Property {zone} not claimed by Character: {char.name}")
|
||||||
|
return 404
|
||||||
|
|
||||||
|
prop_files = pathlib.Path('property_files/')
|
||||||
|
for i in prop_files.glob('**/*'):
|
||||||
|
if i.suffix == '.lxfml':
|
||||||
|
lxfml = ""
|
||||||
|
with open(i, "r") as file:
|
||||||
|
lxfml = file.read()
|
||||||
|
compressed_lxfml = zlib.compress(lxfml.encode())
|
||||||
|
|
||||||
|
new_ugc = UGC(
|
||||||
|
account_id=char.account_id,
|
||||||
|
character_id=char.id,
|
||||||
|
is_optimized=0,
|
||||||
|
lxfml=compressed_lxfml,
|
||||||
|
bake_ao=0,
|
||||||
|
filename=i.name
|
||||||
|
)
|
||||||
|
new_ugc.save()
|
||||||
|
|
||||||
|
new_prop_content = PropertyContent(
|
||||||
|
id=i.stem,
|
||||||
|
property_id=prop.id,
|
||||||
|
ugc_id=new_ugc.id,
|
||||||
|
lot=14,
|
||||||
|
x=0,
|
||||||
|
y=0,
|
||||||
|
z=0,
|
||||||
|
rx=0,
|
||||||
|
ry=0,
|
||||||
|
rz=0,
|
||||||
|
rw=1
|
||||||
|
)
|
||||||
|
new_prop_content.save()
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("gen_image_cache")
|
||||||
|
def gen_image_cache():
|
||||||
|
"""generates image cache"""
|
||||||
|
luclient = pathlib.Path('app/luclient/res')
|
||||||
|
files = [path for path in luclient.rglob("*.dds") if path.is_file()]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
cache = get_cache_file(file).with_suffix(".png")
|
||||||
|
if not cache.exists():
|
||||||
|
try:
|
||||||
|
print(f"Convert {file.as_posix()} to {cache}")
|
||||||
|
cache.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with image.Image(filename=str(file.as_posix())) as img:
|
||||||
|
img.compression = "no"
|
||||||
|
img.save(filename=str(cache.as_posix()))
|
||||||
|
except BE:
|
||||||
|
return print(f"Error on {file}")
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("gen_model_cache")
|
||||||
|
def gen_model_cache():
|
||||||
|
"""generate model obj cache"""
|
||||||
|
luclient = pathlib.Path('app/luclient/res')
|
||||||
|
files = [path for path in luclient.rglob("*.lxfml") if path.is_file()]
|
||||||
|
pool = Pool(processes=4)
|
||||||
|
pool.map(partial(convert_lxfml_to_obj, lod=0), files)
|
||||||
|
pool.map(partial(convert_lxfml_to_obj, lod=1), files)
|
||||||
|
pool.map(partial(convert_lxfml_to_obj, lod=2), files)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_lxfml_to_obj(file, lod):
|
||||||
|
mtl = get_cache_file(file).with_suffix(f".lod{lod}.mtl")
|
||||||
|
if not mtl.exists():
|
||||||
|
mtl.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
print(f"Convert LXFML {file.as_posix()} to obj and mtl @ {mtl}")
|
||||||
|
try:
|
||||||
|
ldd.main(str(file.as_posix()), str(mtl.with_suffix("").as_posix()), lod) # convert to OBJ
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR on {file}:\n {e}")
|
||||||
|
else:
|
||||||
|
# print(f"Already Exists: {file} with LOD {lod}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_file(path):
|
||||||
|
"""helper"""
|
||||||
|
# convert to list so that we can change elements
|
||||||
|
parts = list(path.parts)
|
||||||
|
|
||||||
|
# replace part that matches src with dst
|
||||||
|
parts[parts.index("luclient")] = "cache"
|
||||||
|
del parts[parts.index("res")]
|
||||||
|
|
||||||
|
return pathlib.Path(*parts)
|
||||||
|
|
||||||
|
|
||||||
def find_or_create_account(name, email, password, gm_level=9):
|
def find_or_create_account(name, email, password, gm_level=9):
|
||||||
""" Find existing account or create new account """
|
""" Find existing account or create new account """
|
||||||
account = Account.query.filter(Account.email == email).first()
|
account = Account.query.filter(Account.email == email).first()
|
||||||
@@ -56,15 +256,137 @@ def find_or_create_account(name, email, password, gm_level=9):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
play_key = PlayKey.query.filter(PlayKey.key_string == key).first()
|
play_key = PlayKey.query.filter(PlayKey.key_string == key).first()
|
||||||
account = Account(email=email,
|
account = Account(
|
||||||
username=name,
|
email=email,
|
||||||
password=current_app.user_manager.password_manager.hash_password(password),
|
username=name,
|
||||||
play_key_id=play_key.id,
|
password=current_app.user_manager.password_manager.hash_password(password),
|
||||||
email_confirmed_at=datetime.datetime.utcnow(),
|
play_key_id=play_key.id,
|
||||||
gm_level=gm_level
|
email_confirmed_at=datetime.datetime.utcnow(),
|
||||||
)
|
gm_level=gm_level
|
||||||
|
)
|
||||||
play_key.key_uses = 0
|
play_key.key_uses = 0
|
||||||
db.session.add(account)
|
db.session.add(account)
|
||||||
db.session.add(play_key)
|
db.session.add(play_key)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return # account
|
return # account
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("split_ugc")
|
||||||
|
@click.argument('path')
|
||||||
|
def split_ugc(path):
|
||||||
|
base = """<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
</Bricks>
|
||||||
|
<RigidSystems>
|
||||||
|
</RigidSystems>
|
||||||
|
<GroupSystems>
|
||||||
|
<GroupSystem>
|
||||||
|
</GroupSystem>
|
||||||
|
</GroupSystems>
|
||||||
|
</LXFML>
|
||||||
|
"""
|
||||||
|
# create element tree object
|
||||||
|
orig_lxfml = ET.parse(path)
|
||||||
|
|
||||||
|
rigidsystems = orig_lxfml.findall('.//RigidSystem')
|
||||||
|
# print(len(rigidsystems))
|
||||||
|
rigids_parts = {}
|
||||||
|
i = 0
|
||||||
|
for rigidsytem in rigidsystems:
|
||||||
|
rigids = rigidsytem.findall('.//Rigid')
|
||||||
|
rigids_parts[i] = []
|
||||||
|
for rigid in rigids:
|
||||||
|
rigids_parts[i].extend(rigid.attrib['boneRefs'].split(','))
|
||||||
|
i += 1
|
||||||
|
print(rigids_parts)
|
||||||
|
groups = orig_lxfml.findall('.//Group')
|
||||||
|
|
||||||
|
# print(len(groups))
|
||||||
|
groups_parts = []
|
||||||
|
for group in groups:
|
||||||
|
groups_parts.append(group.attrib['partRefs'].split(','))
|
||||||
|
print(groups_parts)
|
||||||
|
|
||||||
|
# our output
|
||||||
|
models = []
|
||||||
|
g = 0
|
||||||
|
# get things by group
|
||||||
|
if len(groups_parts) > 0:
|
||||||
|
for group in groups_parts:
|
||||||
|
tmp_model = {"bricks": [], "rigidsystems": [], "group": g}
|
||||||
|
|
||||||
|
for brick in group:
|
||||||
|
# find rigids
|
||||||
|
rigid = None
|
||||||
|
for k, v in rigids_parts.items():
|
||||||
|
if brick in v:
|
||||||
|
tmp_model["rigidsystems"].extend(str(k))
|
||||||
|
tmp_model["bricks"].extend(rigids_parts[k])
|
||||||
|
# delete the rigid so that we do dupe it in the next step
|
||||||
|
del rigids_parts[k]
|
||||||
|
break
|
||||||
|
models.append(tmp_model)
|
||||||
|
g += 1
|
||||||
|
# get stuff by rigids
|
||||||
|
if len(rigids_parts) > 0:
|
||||||
|
for k, v in rigids_parts.items():
|
||||||
|
tmp_model = {"bricks": [], "rigidsystems": []}
|
||||||
|
tmp_model["rigidsystems"].extend(str(k))
|
||||||
|
tmp_model["bricks"].extend(rigids_parts[k])
|
||||||
|
models.append(tmp_model)
|
||||||
|
# print(models)
|
||||||
|
|
||||||
|
# make out lxfml files
|
||||||
|
output_xmls = []
|
||||||
|
if len(models) > 0:
|
||||||
|
for model in models:
|
||||||
|
new_xml = ET.fromstring(base)
|
||||||
|
for key, v in model.items():
|
||||||
|
if key == "bricks":
|
||||||
|
bricks = new_xml.find(".//Bricks")
|
||||||
|
for brick in model[key]:
|
||||||
|
# get brick and then add it to our new xml
|
||||||
|
bricks.append(orig_lxfml.find(f".//Brick[@refID='{brick}']"))
|
||||||
|
if key == "rigidsystems":
|
||||||
|
new_rigidsystems = new_xml.find(".//RigidSystems")
|
||||||
|
for rigidsystem in model[key]:
|
||||||
|
new_rigidsystems.append(rigidsystems[int(rigidsystem)])
|
||||||
|
if key == "group":
|
||||||
|
new_groupsystem = new_xml.find(".//GroupSystem")
|
||||||
|
new_groupsystem.append(groups[v])
|
||||||
|
output_xmls.append(new_xml)
|
||||||
|
else:
|
||||||
|
raise Exception("WHY ARE THERE NO MODELS????")
|
||||||
|
|
||||||
|
filename = 0
|
||||||
|
for xml in output_xmls:
|
||||||
|
rigids_parts = {}
|
||||||
|
i = 0
|
||||||
|
rigids = xml.findall('.//Rigid')
|
||||||
|
bricks = xml.findall('.//Bone')
|
||||||
|
transformation = list(map(float, rigids[0].attrib['transformation'].split(',')))
|
||||||
|
print(f"x: {transformation[-3]}")
|
||||||
|
print(f"y: {transformation[-2]}")
|
||||||
|
print(f"z: {transformation[-1]}")
|
||||||
|
for brick in bricks:
|
||||||
|
old_transform = list(map(float, brick.attrib['transformation'].split(',')))
|
||||||
|
old_transform[-1] = old_transform[-1] - transformation[-1]
|
||||||
|
old_transform[-2] = old_transform[-2] - transformation[-2]
|
||||||
|
old_transform[-3] = old_transform[-3] - transformation[-3]
|
||||||
|
new_transform = ','.join(map(str, old_transform))
|
||||||
|
brick.set("transformation", new_transform)
|
||||||
|
f = open(f"{filename}.bin", "ab")
|
||||||
|
out = ET.tostring(xml).replace(b"\n", b"").replace(b" ", b"")
|
||||||
|
f.write(zlib.compress(out))
|
||||||
|
f.close()
|
||||||
|
f = open(f"{filename}.lxfml", "ab")
|
||||||
|
out = ET.tostring(xml).replace(b"\n", b"").replace(b" ", b"")
|
||||||
|
f.write(out)
|
||||||
|
f.close()
|
||||||
|
filename += 1
|
||||||
|
|||||||
48
app/forms.py
48
app/forms.py
@@ -16,13 +16,13 @@ from wtforms import (
|
|||||||
SubmitField,
|
SubmitField,
|
||||||
validators,
|
validators,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
StringField,
|
|
||||||
SelectField
|
SelectField
|
||||||
)
|
)
|
||||||
|
|
||||||
from wtforms.validators import DataRequired, Optional
|
from wtforms.validators import DataRequired, Optional
|
||||||
from app.models import PlayKey
|
from app.models import PlayKey
|
||||||
|
|
||||||
|
|
||||||
def validate_play_key(form, field):
|
def validate_play_key(form, field):
|
||||||
"""Validates a field for a valid phone number
|
"""Validates a field for a valid phone number
|
||||||
Args:
|
Args:
|
||||||
@@ -85,6 +85,7 @@ class CustomRegisterForm(FlaskForm):
|
|||||||
|
|
||||||
submit = SubmitField('Register')
|
submit = SubmitField('Register')
|
||||||
|
|
||||||
|
|
||||||
class CreatePlayKeyForm(FlaskForm):
|
class CreatePlayKeyForm(FlaskForm):
|
||||||
|
|
||||||
count = IntegerField(
|
count = IntegerField(
|
||||||
@@ -97,6 +98,7 @@ class CreatePlayKeyForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField('Create!')
|
submit = SubmitField('Create!')
|
||||||
|
|
||||||
|
|
||||||
class EditPlayKeyForm(FlaskForm):
|
class EditPlayKeyForm(FlaskForm):
|
||||||
|
|
||||||
active = BooleanField(
|
active = BooleanField(
|
||||||
@@ -119,12 +121,24 @@ class EditGMLevelForm(FlaskForm):
|
|||||||
|
|
||||||
gm_level = IntegerField(
|
gm_level = IntegerField(
|
||||||
'GM Level',
|
'GM Level',
|
||||||
widget=NumberInput(min = 0, max = 9)
|
widget=NumberInput(min=0, max=9)
|
||||||
)
|
)
|
||||||
|
|
||||||
submit = SubmitField('Submit')
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
|
class EditEmailForm(FlaskForm):
|
||||||
|
email = StringField(
|
||||||
|
'E-Mail',
|
||||||
|
validators=[
|
||||||
|
Optional(),
|
||||||
|
validators.Email('Invalid email address'),
|
||||||
|
unique_email_validator,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
class ResolveBugReportForm(FlaskForm):
|
class ResolveBugReportForm(FlaskForm):
|
||||||
|
|
||||||
resolution = StringField(
|
resolution = StringField(
|
||||||
@@ -142,8 +156,8 @@ class SendMailForm(FlaskForm):
|
|||||||
'Recipient: ',
|
'Recipient: ',
|
||||||
coerce=str,
|
coerce=str,
|
||||||
choices=[
|
choices=[
|
||||||
("",""),
|
("", ""),
|
||||||
("0","All Characters"),
|
("0", "All Characters"),
|
||||||
],
|
],
|
||||||
validators=[validators.DataRequired()]
|
validators=[validators.DataRequired()]
|
||||||
)
|
)
|
||||||
@@ -162,7 +176,7 @@ class SendMailForm(FlaskForm):
|
|||||||
attachment = SelectField(
|
attachment = SelectField(
|
||||||
"Attachment",
|
"Attachment",
|
||||||
coerce=str,
|
coerce=str,
|
||||||
choices=[(0,"No Attachment")]
|
choices=[(0, "No Attachment")]
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment_count = IntegerField(
|
attachment_count = IntegerField(
|
||||||
@@ -171,3 +185,27 @@ class SendMailForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
submit = SubmitField('Submit')
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
|
class RescueForm(FlaskForm):
|
||||||
|
|
||||||
|
save_world = SelectField(
|
||||||
|
'Move to:',
|
||||||
|
coerce=str,
|
||||||
|
choices=[
|
||||||
|
("", ""),
|
||||||
|
],
|
||||||
|
validators=[validators.DataRequired()]
|
||||||
|
)
|
||||||
|
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|
||||||
|
|
||||||
|
class RejectPropertyForm(FlaskForm):
|
||||||
|
rejection_reason = StringField(
|
||||||
|
'Rejection Reason',
|
||||||
|
widget=TextArea(),
|
||||||
|
validators=[validators.DataRequired()]
|
||||||
|
)
|
||||||
|
|
||||||
|
submit = SubmitField('Submit')
|
||||||
|
|||||||
65
app/log.py
65
app/log.py
@@ -1,12 +1,13 @@
|
|||||||
from flask import render_template, Blueprint, request, url_for
|
from flask import render_template, Blueprint, request, url_for
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required
|
||||||
from app.models import CommandLog, ActivityLog, db, Account, CharacterInfo
|
from app.models import CommandLog, ActivityLog, db, Account, CharacterInfo, AuditLog
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
import time
|
import time
|
||||||
from app import gm_level
|
from app import gm_level
|
||||||
|
|
||||||
log_blueprint = Blueprint('log', __name__)
|
log_blueprint = Blueprint('log', __name__)
|
||||||
|
|
||||||
|
|
||||||
@log_blueprint.route('/activities', methods=['GET'])
|
@log_blueprint.route('/activities', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(8)
|
@gm_level(8)
|
||||||
@@ -21,16 +22,30 @@ def command():
|
|||||||
return render_template('logs/command.html.j2')
|
return render_template('logs/command.html.j2')
|
||||||
|
|
||||||
|
|
||||||
|
@log_blueprint.route('/system')
|
||||||
|
@gm_level(8)
|
||||||
|
def system():
|
||||||
|
with open('nexus_dashboard.log', 'r') as file:
|
||||||
|
logs = '</br>'.join(file.read().split('\n')[-100:])
|
||||||
|
return render_template('logs/system.html.j2', logs=logs)
|
||||||
|
|
||||||
|
|
||||||
|
@log_blueprint.route('/audits')
|
||||||
|
@gm_level(8)
|
||||||
|
def audit():
|
||||||
|
return render_template('logs/audit.html.j2')
|
||||||
|
|
||||||
|
|
||||||
@log_blueprint.route('/get_activities', methods=['GET'])
|
@log_blueprint.route('/get_activities', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(8)
|
@gm_level(8)
|
||||||
def get_activities():
|
def get_activities():
|
||||||
columns = [
|
columns = [
|
||||||
ColumnDT(ActivityLog.id), # 0
|
ColumnDT(ActivityLog.id), # 0
|
||||||
ColumnDT(ActivityLog.character_id), # 1
|
ColumnDT(ActivityLog.character_id), # 1
|
||||||
ColumnDT(ActivityLog.activity), # 2
|
ColumnDT(ActivityLog.activity), # 2
|
||||||
ColumnDT(ActivityLog.time), # 3
|
ColumnDT(ActivityLog.time), # 3
|
||||||
ColumnDT(ActivityLog.map_id), # 4
|
ColumnDT(ActivityLog.map_id), # 4
|
||||||
]
|
]
|
||||||
|
|
||||||
query = db.session.query().select_from(ActivityLog)
|
query = db.session.query().select_from(ActivityLog)
|
||||||
@@ -68,9 +83,9 @@ def get_activities():
|
|||||||
@gm_level(8)
|
@gm_level(8)
|
||||||
def get_commands():
|
def get_commands():
|
||||||
columns = [
|
columns = [
|
||||||
ColumnDT(CommandLog.id), # 0
|
ColumnDT(CommandLog.id), # 0
|
||||||
ColumnDT(CommandLog.character_id), # 1
|
ColumnDT(CommandLog.character_id), # 1
|
||||||
ColumnDT(CommandLog.command), # 2
|
ColumnDT(CommandLog.command), # 2
|
||||||
]
|
]
|
||||||
|
|
||||||
query = db.session.query().select_from(CommandLog)
|
query = db.session.query().select_from(CommandLog)
|
||||||
@@ -96,3 +111,33 @@ def get_commands():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@log_blueprint.route('/get_audits', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(8)
|
||||||
|
def get_audits():
|
||||||
|
columns = [
|
||||||
|
ColumnDT(AuditLog.id), # 0
|
||||||
|
ColumnDT(AuditLog.account_id), # 1
|
||||||
|
ColumnDT(AuditLog.action), # 2
|
||||||
|
ColumnDT(AuditLog.date), # 2
|
||||||
|
]
|
||||||
|
|
||||||
|
query = db.session.query().select_from(AuditLog)
|
||||||
|
|
||||||
|
params = request.args.to_dict()
|
||||||
|
|
||||||
|
rowTable = DataTables(params, query, columns)
|
||||||
|
|
||||||
|
data = rowTable.output_result()
|
||||||
|
for audit in data["data"]:
|
||||||
|
char_id = audit["1"]
|
||||||
|
audit["1"] = f"""
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{url_for('accounts.view', id=char_id)}'>
|
||||||
|
{Account.query.filter(Account.id==audit['1']).first().username}
|
||||||
|
</a>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
175
app/luclient.py
175
app/luclient.py
@@ -3,7 +3,9 @@ from flask import (
|
|||||||
send_file,
|
send_file,
|
||||||
g,
|
g,
|
||||||
redirect,
|
redirect,
|
||||||
url_for
|
url_for,
|
||||||
|
make_response,
|
||||||
|
abort
|
||||||
)
|
)
|
||||||
from flask_user import login_required
|
from flask_user import login_required
|
||||||
from app.models import CharacterInfo
|
from app.models import CharacterInfo
|
||||||
@@ -11,6 +13,8 @@ import glob
|
|||||||
import os
|
import os
|
||||||
from wand import image
|
from wand import image
|
||||||
from wand.exceptions import BlobError as BE
|
from wand.exceptions import BlobError as BE
|
||||||
|
import pathlib
|
||||||
|
import json
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
@@ -18,6 +22,7 @@ import xml.etree.ElementTree as ET
|
|||||||
luclient_blueprint = Blueprint('luclient', __name__)
|
luclient_blueprint = Blueprint('luclient', __name__)
|
||||||
locale = {}
|
locale = {}
|
||||||
|
|
||||||
|
|
||||||
@luclient_blueprint.route('/get_dds_as_png/<filename>')
|
@luclient_blueprint.route('/get_dds_as_png/<filename>')
|
||||||
@login_required
|
@login_required
|
||||||
def get_dds_as_png(filename):
|
def get_dds_as_png(filename):
|
||||||
@@ -26,7 +31,7 @@ def get_dds_as_png(filename):
|
|||||||
|
|
||||||
cache = f'cache/{filename.split(".")[0]}.png'
|
cache = f'cache/{filename.split(".")[0]}.png'
|
||||||
|
|
||||||
if not os.path.exists("app/" + cache):
|
if not os.path.exists(cache):
|
||||||
root = 'app/luclient/res/'
|
root = 'app/luclient/res/'
|
||||||
|
|
||||||
path = glob.glob(
|
path = glob.glob(
|
||||||
@@ -36,7 +41,7 @@ def get_dds_as_png(filename):
|
|||||||
|
|
||||||
with image.Image(filename=path) as img:
|
with image.Image(filename=path) as img:
|
||||||
img.compression = "no"
|
img.compression = "no"
|
||||||
img.save(filename='app/cache/'+filename.split('.')[0] + '.png')
|
img.save(filename='app/cache/' + filename.split('.')[0] + '.png')
|
||||||
|
|
||||||
return send_file(cache)
|
return send_file(cache)
|
||||||
|
|
||||||
@@ -68,26 +73,30 @@ def get_icon_lot(id):
|
|||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
# find the asset from rendercomponent given the component id
|
# find the asset from rendercomponent given the component id
|
||||||
filename = query_cdclient('select icon_asset from RenderComponent where id = ?',
|
filename = query_cdclient(
|
||||||
|
'select icon_asset from RenderComponent where id = ?',
|
||||||
[render_component_id],
|
[render_component_id],
|
||||||
one=True
|
one=True
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
filename = filename.replace("..\\", "").replace("\\", "/")
|
if filename:
|
||||||
|
filename = filename.replace("..\\", "").replace("\\", "/")
|
||||||
|
else:
|
||||||
|
return redirect(url_for('luclient.unknown'))
|
||||||
|
|
||||||
cache = f'cache/{filename.split("/")[-1].split(".")[0]}.png'
|
cache = f'app/cache/{filename.split(".")[0]}.png'
|
||||||
|
|
||||||
if not os.path.exists("app/" + cache):
|
if not os.path.exists(cache):
|
||||||
root = 'app/luclient/res/'
|
root = 'app/luclient/res/'
|
||||||
try:
|
try:
|
||||||
|
pathlib.Path(os.path.dirname(cache)).resolve().mkdir(parents=True, exist_ok=True)
|
||||||
with image.Image(filename=f'{root}{filename}'.lower()) as img:
|
with image.Image(filename=f'{root}{filename}'.lower()) as img:
|
||||||
img.compression = "no"
|
img.compression = "no"
|
||||||
img.save(filename=f'app/cache/{filename.split("/")[-1].split(".")[0]}.png')
|
img.save(filename=cache)
|
||||||
except BE:
|
except BE:
|
||||||
return redirect(url_for('luclient.unknown'))
|
return redirect(url_for('luclient.unknown'))
|
||||||
|
|
||||||
return send_file(cache)
|
return send_file(pathlib.Path(cache).resolve())
|
||||||
|
|
||||||
|
|
||||||
@luclient_blueprint.route('/get_icon_iconid/<id>')
|
@luclient_blueprint.route('/get_icon_iconid/<id>')
|
||||||
@@ -102,36 +111,76 @@ def get_icon_iconid(id):
|
|||||||
|
|
||||||
filename = filename.replace("..\\", "").replace("\\", "/")
|
filename = filename.replace("..\\", "").replace("\\", "/")
|
||||||
|
|
||||||
cache = f'cache/{filename.split("/")[-1].split(".")[0]}.png'
|
cache = f'app/cache/{filename.split(".")[0]}.png'
|
||||||
|
|
||||||
if not os.path.exists("app/" + cache):
|
if not os.path.exists(cache):
|
||||||
root = 'app/luclient/res/'
|
root = 'app/luclient/res/'
|
||||||
try:
|
try:
|
||||||
|
pathlib.Path(os.path.dirname(cache)).resolve().mkdir(parents=True, exist_ok=True)
|
||||||
with image.Image(filename=f'{root}{filename}'.lower()) as img:
|
with image.Image(filename=f'{root}{filename}'.lower()) as img:
|
||||||
img.compression = "no"
|
img.compression = "no"
|
||||||
img.save(filename=f'app/cache/{filename.split("/")[-1].split(".")[0]}.png')
|
img.save(filename=cache)
|
||||||
except BE:
|
except BE:
|
||||||
return redirect(url_for('luclient.unknown'))
|
return redirect(url_for('luclient.unknown'))
|
||||||
|
|
||||||
return send_file(cache)
|
return send_file(pathlib.Path(cache).resolve())
|
||||||
|
|
||||||
|
|
||||||
|
@luclient_blueprint.route('/ldddb/')
|
||||||
|
@login_required
|
||||||
|
def brick_list():
|
||||||
|
brick_list = []
|
||||||
|
if len(brick_list) == 0:
|
||||||
|
suffixes = [".g", ".g1", ".g2", ".g3", ".xml"]
|
||||||
|
res = pathlib.Path('app/luclient/res/')
|
||||||
|
# Load g files
|
||||||
|
for path in res.rglob("*.*"):
|
||||||
|
if str(path.suffix) in suffixes:
|
||||||
|
brick_list.append(
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"name": str(path.as_posix()).replace("app/luclient/res/", "")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = make_response(json.dumps(brick_list))
|
||||||
|
response.headers.set('Content-Type', 'application/json')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@luclient_blueprint.route('/ldddb/', defaults={'req_path': ''})
|
||||||
|
@luclient_blueprint.route('/ldddb/<path:req_path>')
|
||||||
|
def dir_listing(req_path):
|
||||||
|
# Joining the base and the requested path
|
||||||
|
rel_path = pathlib.Path(str(pathlib.Path(f'app/luclient/res/{req_path}').resolve()))
|
||||||
|
# Return 404 if path doesn't exist
|
||||||
|
if not rel_path.exists():
|
||||||
|
return abort(404)
|
||||||
|
|
||||||
|
# Check if path is a file and serve
|
||||||
|
if rel_path.is_file():
|
||||||
|
return send_file(rel_path)
|
||||||
|
else:
|
||||||
|
return abort(404)
|
||||||
|
|
||||||
|
|
||||||
@luclient_blueprint.route('/unknown')
|
@luclient_blueprint.route('/unknown')
|
||||||
@login_required
|
@login_required
|
||||||
def unknown():
|
def unknown():
|
||||||
filename = "textures/ui/inventory/unknown.dds"
|
filename = "textures/ui/inventory/unknown.dds"
|
||||||
|
|
||||||
cache = f'cache/{filename.split("/")[-1].split(".")[0]}.png'
|
cache = f'app/cache/{filename.split(".")[0]}.png'
|
||||||
|
|
||||||
if not os.path.exists("app/" + cache):
|
if not os.path.exists(cache):
|
||||||
root = 'app/luclient/res/'
|
root = 'app/luclient/res/'
|
||||||
|
try:
|
||||||
|
pathlib.Path(os.path.dirname(cache)).resolve().mkdir(parents=True, exist_ok=True)
|
||||||
|
with image.Image(filename=f'{root}{filename}'.lower()) as img:
|
||||||
|
img.compression = "no"
|
||||||
|
img.save(filename=cache)
|
||||||
|
except BE:
|
||||||
|
return redirect(url_for('luclient.unknown'))
|
||||||
|
|
||||||
with image.Image(filename=f'{root}{filename}'.lower()) as img:
|
return send_file(pathlib.Path(cache).resolve())
|
||||||
img.compression = "no"
|
|
||||||
img.save(filename=f'app/cache/{filename.split("/")[-1].split(".")[0]}.png')
|
|
||||||
|
|
||||||
|
|
||||||
return send_file(cache)
|
|
||||||
|
|
||||||
|
|
||||||
def get_cdclient():
|
def get_cdclient():
|
||||||
@@ -192,23 +241,43 @@ def translate_from_locale(trans_string):
|
|||||||
else:
|
else:
|
||||||
return trans_string
|
return trans_string
|
||||||
|
|
||||||
|
|
||||||
|
def get_lot_name(lot_id):
|
||||||
|
if not lot_id:
|
||||||
|
return "Missing"
|
||||||
|
name = translate_from_locale(f'Objects_{lot_id}_name')
|
||||||
|
if name == f'Objects_{lot_id}_name':
|
||||||
|
intermed = query_cdclient(
|
||||||
|
'select * from Objects where id = ?',
|
||||||
|
[lot_id],
|
||||||
|
one=True
|
||||||
|
)
|
||||||
|
if intermed:
|
||||||
|
name = intermed[7] if (intermed[7] != "None" and intermed[7] != "" and intermed[7] is None) else intermed[1]
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
def register_luclient_jinja_helpers(app):
|
def register_luclient_jinja_helpers(app):
|
||||||
|
|
||||||
@app.template_filter('get_zone_name')
|
@app.template_filter('get_zone_name')
|
||||||
def get_zone_name(zone_id):
|
def get_zone_name(zone_id):
|
||||||
|
if not zone_id:
|
||||||
|
return "Missing"
|
||||||
return translate_from_locale(f'ZoneTable_{zone_id}_DisplayDescription')
|
return translate_from_locale(f'ZoneTable_{zone_id}_DisplayDescription')
|
||||||
|
|
||||||
@app.template_filter('get_skill_desc')
|
@app.template_filter('get_skill_desc')
|
||||||
def get_skill_desc(skill_id):
|
def get_skill_desc(skill_id):
|
||||||
return translate_from_locale(f'SkillBehavior_{skill_id}_descriptionUI').replace(
|
return translate_from_locale(
|
||||||
"%(DamageCombo)", "Damage Combo: "
|
f'SkillBehavior_{skill_id}_descriptionUI'
|
||||||
).replace(
|
).replace(
|
||||||
"%(AltCombo)", "<br/>Skeleton Combo: "
|
"%(DamageCombo)", "Damage Combo: "
|
||||||
).replace(
|
).replace(
|
||||||
"%(Description)", "<br/>"
|
"%(AltCombo)", "<br/>Skeleton Combo: "
|
||||||
).replace(
|
).replace(
|
||||||
"%(ChargeUp)", "<br/>Charge-up: "
|
"%(Description)", "<br/>"
|
||||||
)
|
).replace(
|
||||||
|
"%(ChargeUp)", "<br/>Charge-up: "
|
||||||
|
)
|
||||||
|
|
||||||
@app.template_filter('parse_lzid')
|
@app.template_filter('parse_lzid')
|
||||||
def parse_lzid(lzid):
|
def parse_lzid(lzid):
|
||||||
@@ -228,27 +297,21 @@ def register_luclient_jinja_helpers(app):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@app.template_filter('get_lot_name')
|
@app.template_filter('get_lot_name')
|
||||||
def get_lot_name(lot_id):
|
def jinja_get_lot_name(lot_id):
|
||||||
name = translate_from_locale(f'Objects_{lot_id}_name')
|
return get_lot_name(lot_id)
|
||||||
if name == translate_from_locale(f'Objects_{lot_id}_name'):
|
|
||||||
intermed = query_cdclient(
|
|
||||||
'select * from Objects where id = ?',
|
|
||||||
[lot_id],
|
|
||||||
one=True
|
|
||||||
)
|
|
||||||
name = intermed[7] if (intermed[7] != "None" and intermed[7] !="" and intermed[7] != None) else intermed[1]
|
|
||||||
return name
|
|
||||||
|
|
||||||
@app.template_filter('get_lot_rarity')
|
@app.template_filter('get_lot_rarity')
|
||||||
def get_lot_rarity(lot_id):
|
def get_lot_rarity(lot_id):
|
||||||
|
if not lot_id:
|
||||||
|
return "Missing"
|
||||||
render_component_id = query_cdclient(
|
render_component_id = query_cdclient(
|
||||||
'select component_id from ComponentsRegistry where component_type = 2 and id = ?',
|
'select component_id from ComponentsRegistry where component_type = 11 and id = ?',
|
||||||
[lot_id],
|
[lot_id],
|
||||||
one=True
|
one=True
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
rarity = query_cdclient('select rarity from ItemComponent where id = ?',
|
rarity = query_cdclient(
|
||||||
|
'select rarity from ItemComponent where id = ?',
|
||||||
[render_component_id],
|
[render_component_id],
|
||||||
one=True
|
one=True
|
||||||
)
|
)
|
||||||
@@ -258,19 +321,29 @@ def register_luclient_jinja_helpers(app):
|
|||||||
|
|
||||||
@app.template_filter('get_lot_desc')
|
@app.template_filter('get_lot_desc')
|
||||||
def get_lot_desc(lot_id):
|
def get_lot_desc(lot_id):
|
||||||
|
if not lot_id:
|
||||||
|
return "Missing"
|
||||||
desc = translate_from_locale(f'Objects_{lot_id}_description')
|
desc = translate_from_locale(f'Objects_{lot_id}_description')
|
||||||
if desc == f'Objects_{lot_id}_description':
|
if desc == f'Objects_{lot_id}_description':
|
||||||
desc = query_cdclient(
|
desc = query_cdclient(
|
||||||
'select description from Objects where id = ?',
|
'select description from Objects where id = ?',
|
||||||
[lot_id],
|
[lot_id],
|
||||||
one=True
|
one=True
|
||||||
)[0]
|
)
|
||||||
if desc in ("", None):
|
if desc in ("", None):
|
||||||
desc = None
|
desc = None
|
||||||
|
else:
|
||||||
|
desc = desc[0]
|
||||||
|
if desc in ("", None):
|
||||||
|
desc = None
|
||||||
|
if desc:
|
||||||
|
desc = desc.replace('"', "“")
|
||||||
return desc
|
return desc
|
||||||
|
|
||||||
@app.template_filter('get_item_set')
|
@app.template_filter('get_item_set')
|
||||||
def check_if_in_set(lot_id):
|
def check_if_in_set(lot_id):
|
||||||
|
if not lot_id:
|
||||||
|
return None
|
||||||
item_set = query_cdclient(
|
item_set = query_cdclient(
|
||||||
'select * from ItemSets where itemIDs like ? or itemIDs like ? or itemIDs like ?',
|
'select * from ItemSets where itemIDs like ? or itemIDs like ? or itemIDs like ?',
|
||||||
[f'{lot_id}%', f'%, {lot_id}%', f'%,{lot_id}%'],
|
[f'{lot_id}%', f'%, {lot_id}%', f'%,{lot_id}%'],
|
||||||
@@ -283,6 +356,8 @@ def register_luclient_jinja_helpers(app):
|
|||||||
|
|
||||||
@app.template_filter('get_lot_stats')
|
@app.template_filter('get_lot_stats')
|
||||||
def get_lot_stats(lot_id):
|
def get_lot_stats(lot_id):
|
||||||
|
if not lot_id:
|
||||||
|
return None
|
||||||
stats = query_cdclient(
|
stats = query_cdclient(
|
||||||
'SELECT imBonusUI, lifeBonusUI, armorBonusUI, skillID, skillIcon FROM SkillBehavior WHERE skillID IN (\
|
'SELECT imBonusUI, lifeBonusUI, armorBonusUI, skillID, skillIcon FROM SkillBehavior WHERE skillID IN (\
|
||||||
SELECT skillID FROM ObjectSkills WHERE objectTemplate=?\
|
SELECT skillID FROM ObjectSkills WHERE objectTemplate=?\
|
||||||
@@ -292,9 +367,10 @@ def register_luclient_jinja_helpers(app):
|
|||||||
|
|
||||||
return consolidate_stats(stats)
|
return consolidate_stats(stats)
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('get_set_stats')
|
@app.template_filter('get_set_stats')
|
||||||
def get_set_stats(lot_id):
|
def get_set_stats(lot_id):
|
||||||
|
if not lot_id:
|
||||||
|
return "Missing"
|
||||||
stats = query_cdclient(
|
stats = query_cdclient(
|
||||||
'SELECT imBonusUI, lifeBonusUI, armorBonusUI, skillID, skillIcon FROM SkillBehavior WHERE skillID IN (\
|
'SELECT imBonusUI, lifeBonusUI, armorBonusUI, skillID, skillIcon FROM SkillBehavior WHERE skillID IN (\
|
||||||
SELECT skillID FROM ItemSetSkills WHERE SkillSetID=?\
|
SELECT skillID FROM ItemSetSkills WHERE SkillSetID=?\
|
||||||
@@ -321,7 +397,7 @@ def register_luclient_jinja_helpers(app):
|
|||||||
def consolidate_stats(stats):
|
def consolidate_stats(stats):
|
||||||
|
|
||||||
if len(stats) > 1:
|
if len(stats) > 1:
|
||||||
consolidated_stats = {"im": 0,"life": 0,"armor": 0, "skill": []}
|
consolidated_stats = {"im": 0, "life": 0, "armor": 0, "skill": []}
|
||||||
for stat in stats:
|
for stat in stats:
|
||||||
if stat[0]:
|
if stat[0]:
|
||||||
consolidated_stats["im"] += stat[0]
|
consolidated_stats["im"] += stat[0]
|
||||||
@@ -330,8 +406,7 @@ def consolidate_stats(stats):
|
|||||||
if stat[2]:
|
if stat[2]:
|
||||||
consolidated_stats["armor"] += stat[2]
|
consolidated_stats["armor"] += stat[2]
|
||||||
if stat[3]:
|
if stat[3]:
|
||||||
consolidated_stats["skill"].append([stat[3],stat[4]])
|
consolidated_stats["skill"].append([stat[3], stat[4]])
|
||||||
|
|
||||||
|
|
||||||
stats = consolidated_stats
|
stats = consolidated_stats
|
||||||
elif len(stats) == 1:
|
elif len(stats) == 1:
|
||||||
|
|||||||
60
app/mail.py
60
app/mail.py
@@ -1,9 +1,8 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash, request
|
from flask import render_template, Blueprint, redirect, url_for, flash, request
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required, current_user
|
||||||
from app.models import db, Mail, CharacterInfo
|
from app.models import Mail, CharacterInfo
|
||||||
from datatables import ColumnDT, DataTables
|
|
||||||
from app.forms import SendMailForm
|
from app.forms import SendMailForm
|
||||||
from app import gm_level
|
from app import gm_level, log_audit
|
||||||
from app.luclient import translate_from_locale, query_cdclient
|
from app.luclient import translate_from_locale, query_cdclient
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -25,41 +24,46 @@ def send():
|
|||||||
form = SendMailForm()
|
form = SendMailForm()
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
# if form.validate_on_submit():
|
|
||||||
if form.attachment.data != "0" and form.attachment_count.data == 0:
|
if form.attachment.data != "0" and form.attachment_count.data == 0:
|
||||||
form.attachment_count.data = 1
|
form.attachment_count.data = 1
|
||||||
if form.recipient.data == "0":
|
if form.recipient.data == "0":
|
||||||
|
log_audit(f"Sending {form.subject.data}: {form.body.data} to All Characters with {form.attachment_count.data} of item {form.attachment.data}")
|
||||||
for character in CharacterInfo.query.all():
|
for character in CharacterInfo.query.all():
|
||||||
Mail(
|
Mail(
|
||||||
sender_id = 0,
|
sender_id=0,
|
||||||
sender_name = f"[GM] {current_user.username}",
|
sender_name=f"[GM] {current_user.username}",
|
||||||
receiver_id = character.id,
|
receiver_id=character.id,
|
||||||
receiver_name = character.name,
|
receiver_name=character.name,
|
||||||
time_sent = time.time(),
|
time_sent=time.time(),
|
||||||
subject = form.subject.data,
|
subject=form.subject.data,
|
||||||
body = form.body.data,
|
body=form.body.data,
|
||||||
attachment_id = 0,
|
attachment_id=0,
|
||||||
attachment_lot = form.attachment.data,
|
attachment_lot=form.attachment.data,
|
||||||
attachment_count = form.attachment_count.data
|
attachment_count=form.attachment_count.data
|
||||||
).save()
|
).save()
|
||||||
|
log_audit(f"Sent {form.subject.data}: \
|
||||||
|
{form.body.data} to ({character.id}){character.name} \
|
||||||
|
with {form.attachment_count.data} of item {form.attachment.data}")
|
||||||
else:
|
else:
|
||||||
Mail(
|
Mail(
|
||||||
sender_id = 0,
|
sender_id=0,
|
||||||
sender_name = f"[GM] {current_user.username}",
|
sender_name=f"[GM] {current_user.username}",
|
||||||
receiver_id = form.recipient.data,
|
receiver_id=form.recipient.data,
|
||||||
receiver_name = CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name,
|
receiver_name=CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name,
|
||||||
time_sent = time.time(),
|
time_sent=time.time(),
|
||||||
subject = form.subject.data,
|
subject=form.subject.data,
|
||||||
body = form.body.data,
|
body=form.body.data,
|
||||||
attachment_id = 0,
|
attachment_id=0,
|
||||||
attachment_lot = form.attachment.data,
|
attachment_lot=form.attachment.data,
|
||||||
attachment_count = form.attachment_count.data
|
attachment_count=form.attachment_count.data
|
||||||
).save()
|
).save()
|
||||||
|
log_audit(f"Sent {form.subject.data}: \
|
||||||
|
{form.body.data} to ({form.recipient.data}){CharacterInfo.query.filter(CharacterInfo.id == form.recipient.data).first().name} \
|
||||||
|
with {form.attachment_count.data} of item {form.attachment.data}")
|
||||||
|
|
||||||
flash("Sent Mail", "success")
|
flash("Sent Mail", "success")
|
||||||
return redirect(url_for('mail.send'))
|
return redirect(url_for('mail.send'))
|
||||||
|
|
||||||
|
|
||||||
recipients = CharacterInfo.query.all()
|
recipients = CharacterInfo.query.all()
|
||||||
for character in recipients:
|
for character in recipients:
|
||||||
form.recipient.choices.append((character.id, character.name))
|
form.recipient.choices.append((character.id, character.name))
|
||||||
@@ -72,7 +76,7 @@ def send():
|
|||||||
for item in items:
|
for item in items:
|
||||||
name = translate_from_locale(f'Objects_{item[0]}_name')
|
name = translate_from_locale(f'Objects_{item[0]}_name')
|
||||||
if name == f'Objects_{item[0]}_name':
|
if name == f'Objects_{item[0]}_name':
|
||||||
name = (item[2] if (item[2] != "None" and item[2] !="" and item[2] != None) else item[1])
|
name = (item[2] if (item[2] != "None" and item[2] != "" and item[2] is not None) else item[1])
|
||||||
form.attachment.choices.append(
|
form.attachment.choices.append(
|
||||||
(
|
(
|
||||||
item[0],
|
item[0],
|
||||||
@@ -80,6 +84,4 @@ def send():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
return render_template('mail/send.html.j2', form=form)
|
return render_template('mail/send.html.j2', form=form)
|
||||||
|
|
||||||
|
|||||||
28
app/main.py
28
app/main.py
@@ -1,22 +1,19 @@
|
|||||||
from flask import render_template, Blueprint, redirect, request, send_from_directory, make_response, send_file
|
from flask import render_template, Blueprint, send_from_directory
|
||||||
from flask_user import login_required, current_user
|
from flask_user import current_user, login_required
|
||||||
import json, glob, os
|
|
||||||
from wand import image
|
|
||||||
|
|
||||||
from app.models import Account, AccountInvitation, CharacterInfo
|
from app.models import Account, CharacterInfo, ActivityLog
|
||||||
from app.schemas import AccountSchema, CharacterInfoSchema
|
from app.schemas import AccountSchema, CharacterInfoSchema
|
||||||
from app.luclient import query_cdclient
|
|
||||||
|
|
||||||
main_blueprint = Blueprint('main', __name__)
|
main_blueprint = Blueprint('main', __name__)
|
||||||
|
|
||||||
account_schema = AccountSchema()
|
account_schema = AccountSchema()
|
||||||
char_info_schema = CharacterInfoSchema()
|
char_info_schema = CharacterInfoSchema()
|
||||||
|
|
||||||
|
|
||||||
@main_blueprint.route('/', methods=['GET'])
|
@main_blueprint.route('/', methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
"""Home/Index Page"""
|
"""Home/Index Page"""
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
|
|
||||||
account_data = Account.query.filter(Account.id == current_user.id).first()
|
account_data = Account.query.filter(Account.id == current_user.id).first()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
@@ -28,9 +25,24 @@ def index():
|
|||||||
|
|
||||||
|
|
||||||
@main_blueprint.route('/about')
|
@main_blueprint.route('/about')
|
||||||
|
@login_required
|
||||||
def about():
|
def about():
|
||||||
"""About Page"""
|
"""About Page"""
|
||||||
return render_template('main/about.html.j2')
|
mods = Account.query.filter(Account.gm_level > 0).order_by(Account.gm_level.desc()).all()
|
||||||
|
online = 0
|
||||||
|
chars = CharacterInfo.query.all()
|
||||||
|
for char in chars:
|
||||||
|
last_log = ActivityLog.query.with_entities(
|
||||||
|
ActivityLog.activity
|
||||||
|
).filter(
|
||||||
|
ActivityLog.character_id == char.id
|
||||||
|
).order_by(ActivityLog.id.desc()).first()
|
||||||
|
|
||||||
|
if last_log:
|
||||||
|
if last_log[0] == 0:
|
||||||
|
online += 1
|
||||||
|
|
||||||
|
return render_template('main/about.html.j2', mods=mods, online=online)
|
||||||
|
|
||||||
|
|
||||||
@main_blueprint.route('/favicon.ico')
|
@main_blueprint.route('/favicon.ico')
|
||||||
|
|||||||
111
app/models.py
111
app/models.py
@@ -7,10 +7,12 @@ import logging
|
|||||||
from flask_sqlalchemy import BaseQuery
|
from flask_sqlalchemy import BaseQuery
|
||||||
from sqlalchemy.dialects import mysql
|
from sqlalchemy.dialects import mysql
|
||||||
from sqlalchemy.exc import OperationalError, StatementError
|
from sqlalchemy.exc import OperationalError, StatementError
|
||||||
|
from sqlalchemy.types import JSON
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
|
||||||
# retrying query to work around python trash collector
|
# retrying query to work around python trash collector
|
||||||
# killing connections of other gunicorn workers
|
# killing connections of other gunicorn workers
|
||||||
class RetryingQuery(BaseQuery):
|
class RetryingQuery(BaseQuery):
|
||||||
@@ -45,9 +47,11 @@ class RetryingQuery(BaseQuery):
|
|||||||
raise
|
raise
|
||||||
self.session.rollback()
|
self.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
db = SQLAlchemy(query_class=RetryingQuery)
|
db = SQLAlchemy(query_class=RetryingQuery)
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
|
|
||||||
|
|
||||||
class PlayKey(db.Model):
|
class PlayKey(db.Model):
|
||||||
__tablename__ = 'play_keys'
|
__tablename__ = 'play_keys'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -110,6 +114,7 @@ class PlayKey(db.Model):
|
|||||||
)
|
)
|
||||||
db.session.add(new_key)
|
db.session.add(new_key)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
return key
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
@@ -120,6 +125,7 @@ class PlayKey(db.Model):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.refresh(self)
|
db.session.refresh(self)
|
||||||
|
|
||||||
|
|
||||||
class Account(db.Model, UserMixin):
|
class Account(db.Model, UserMixin):
|
||||||
__tablename__ = 'accounts'
|
__tablename__ = 'accounts'
|
||||||
id = db.Column(
|
id = db.Column(
|
||||||
@@ -199,7 +205,7 @@ class Account(db.Model, UserMixin):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_by_id(*, user_id=None):
|
def get_user_by_id(*, user_id=None):
|
||||||
return User.query.filter(user_id == User.id).first()
|
return Account.query.filter(user_id == Account.id).first()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
@@ -210,6 +216,7 @@ class Account(db.Model, UserMixin):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class AccountInvitation(db.Model):
|
class AccountInvitation(db.Model):
|
||||||
__tablename__ = 'account_invites'
|
__tablename__ = 'account_invites'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -244,14 +251,10 @@ class AccountInvitation(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_by_id(*, user_id=None):
|
def get_user_by_id(*, user_id=None):
|
||||||
return User.query.filter(user_id == User.id).first()
|
return Account.query.filter(user_id == Account.id).first()
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
db.session.delete(self)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# This table is cursed, see prop_clone_id
|
# This table is cursed, see prop_clone_id
|
||||||
class CharacterInfo(db.Model):
|
class CharacterInfo(db.Model):
|
||||||
@@ -326,6 +329,7 @@ class CharacterInfo(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class CharacterXML(db.Model):
|
class CharacterXML(db.Model):
|
||||||
__tablename__ = 'charxml'
|
__tablename__ = 'charxml'
|
||||||
id = db.Column(
|
id = db.Column(
|
||||||
@@ -347,6 +351,7 @@ class CharacterXML(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class CommandLog(db.Model):
|
class CommandLog(db.Model):
|
||||||
__tablename__ = 'command_log'
|
__tablename__ = 'command_log'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -363,7 +368,7 @@ class CommandLog(db.Model):
|
|||||||
passive_deletes=True
|
passive_deletes=True
|
||||||
)
|
)
|
||||||
|
|
||||||
command = db.Column(
|
command = db.Column(
|
||||||
mysql.VARCHAR(256),
|
mysql.VARCHAR(256),
|
||||||
nullable=False
|
nullable=False
|
||||||
)
|
)
|
||||||
@@ -377,6 +382,7 @@ class CommandLog(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class Friends(db.Model):
|
class Friends(db.Model):
|
||||||
__tablename__ = 'friends'
|
__tablename__ = 'friends'
|
||||||
player_id = db.Column(
|
player_id = db.Column(
|
||||||
@@ -422,6 +428,7 @@ class Friends(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class Leaderboard(db.Model):
|
class Leaderboard(db.Model):
|
||||||
__tablename__ = 'leaderboard'
|
__tablename__ = 'leaderboard'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -471,6 +478,7 @@ class Leaderboard(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class Mail(db.Model):
|
class Mail(db.Model):
|
||||||
__tablename__ = 'mail'
|
__tablename__ = 'mail'
|
||||||
id = db.Column(
|
id = db.Column(
|
||||||
@@ -559,6 +567,7 @@ class Mail(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class ObjectIDTracker(db.Model):
|
class ObjectIDTracker(db.Model):
|
||||||
__tablename__ = 'object_id_tracker'
|
__tablename__ = 'object_id_tracker'
|
||||||
last_object_id = db.Column(
|
last_object_id = db.Column(
|
||||||
@@ -568,6 +577,7 @@ class ObjectIDTracker(db.Model):
|
|||||||
server_default='0'
|
server_default='0'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PetNames(db.Model):
|
class PetNames(db.Model):
|
||||||
__tablename__ = 'pet_names'
|
__tablename__ = 'pet_names'
|
||||||
id = db.Column(mysql.BIGINT, primary_key=True)
|
id = db.Column(mysql.BIGINT, primary_key=True)
|
||||||
@@ -581,6 +591,11 @@ class PetNames(db.Model):
|
|||||||
server_default='0'
|
server_default='0'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
owner_id = db.Column(
|
||||||
|
mysql.BIGINT,
|
||||||
|
nullable=True
|
||||||
|
)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -599,7 +614,7 @@ class Property(db.Model):
|
|||||||
autoincrement=False
|
autoincrement=False
|
||||||
)
|
)
|
||||||
|
|
||||||
owner_id = db.Column(
|
owner_id = db.Column(
|
||||||
mysql.BIGINT,
|
mysql.BIGINT,
|
||||||
db.ForeignKey(CharacterInfo.id, ondelete='CASCADE'),
|
db.ForeignKey(CharacterInfo.id, ondelete='CASCADE'),
|
||||||
nullable=False
|
nullable=False
|
||||||
@@ -617,7 +632,7 @@ class Property(db.Model):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_id = db.Column(
|
clone_id = db.Column(
|
||||||
mysql.BIGINT(unsigned=True),
|
mysql.BIGINT(unsigned=True),
|
||||||
db.ForeignKey(CharacterInfo.prop_clone_id, ondelete='CASCADE'),
|
db.ForeignKey(CharacterInfo.prop_clone_id, ondelete='CASCADE'),
|
||||||
)
|
)
|
||||||
@@ -680,6 +695,15 @@ class Property(db.Model):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
performance_cost = db.Column(
|
||||||
|
mysql.DOUBLE(
|
||||||
|
precision=20,
|
||||||
|
scale=15,
|
||||||
|
asdecimal=False
|
||||||
|
),
|
||||||
|
server_default='0.0'
|
||||||
|
)
|
||||||
|
|
||||||
zone_id = db.Column(
|
zone_id = db.Column(
|
||||||
mysql.INTEGER,
|
mysql.INTEGER,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
@@ -757,6 +781,7 @@ class UGC(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class PropertyContent(db.Model):
|
class PropertyContent(db.Model):
|
||||||
__tablename__ = 'properties_contents'
|
__tablename__ = 'properties_contents'
|
||||||
id = db.Column(
|
id = db.Column(
|
||||||
@@ -837,6 +862,7 @@ class PropertyContent(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class ActivityLog(db.Model):
|
class ActivityLog(db.Model):
|
||||||
__tablename__ = 'activity_log'
|
__tablename__ = 'activity_log'
|
||||||
id = db.Column(mysql.INTEGER, primary_key=True)
|
id = db.Column(mysql.INTEGER, primary_key=True)
|
||||||
@@ -877,6 +903,7 @@ class ActivityLog(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class BugReport(db.Model):
|
class BugReport(db.Model):
|
||||||
__tablename__ = 'bug_reports'
|
__tablename__ = 'bug_reports'
|
||||||
id = db.Column(mysql.INTEGER, primary_key=True)
|
id = db.Column(mysql.INTEGER, primary_key=True)
|
||||||
@@ -929,6 +956,12 @@ class BugReport(db.Model):
|
|||||||
nullable=True
|
nullable=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reporter_id = db.Column(
|
||||||
|
mysql.INTEGER(),
|
||||||
|
nullable=False,
|
||||||
|
server_default='0'
|
||||||
|
)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
db.session.add(self)
|
db.session.add(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -938,6 +971,7 @@ class BugReport(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class Server(db.Model):
|
class Server(db.Model):
|
||||||
__tablename__ = 'servers'
|
__tablename__ = 'servers'
|
||||||
id = db.Column(
|
id = db.Column(
|
||||||
@@ -980,23 +1014,25 @@ class Server(db.Model):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class ItemReports(db.Model):
|
class Reports(db.Model):
|
||||||
__tablename__ = 'item_reports'
|
__tablename__ = 'reports'
|
||||||
|
|
||||||
item = db.Column(
|
data = db.Column(
|
||||||
db.Integer(),
|
JSON(),
|
||||||
primary_key=True,
|
|
||||||
nullable=False
|
nullable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
count = db.Column(
|
report_type = db.Column(
|
||||||
db.Integer(),
|
db.VARCHAR(35),
|
||||||
nullable=False
|
nullable=False,
|
||||||
|
primary_key=True,
|
||||||
|
autoincrement=False
|
||||||
)
|
)
|
||||||
|
|
||||||
date = db.Column(
|
date = db.Column(
|
||||||
db.Date(),
|
db.Date(),
|
||||||
primary_key=False,
|
primary_key=True,
|
||||||
|
autoincrement=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -1008,3 +1044,42 @@ class ItemReports(db.Model):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
class AuditLog(db.Model):
|
||||||
|
__tablename__ = 'audit_logs'
|
||||||
|
id = db.Column(
|
||||||
|
mysql.INTEGER,
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
account_id = db.Column(
|
||||||
|
db.Integer(),
|
||||||
|
db.ForeignKey(Account.id, ondelete='CASCADE'),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
account = db.relationship(
|
||||||
|
'Account',
|
||||||
|
backref="audit_logs",
|
||||||
|
passive_deletes=True
|
||||||
|
)
|
||||||
|
|
||||||
|
action = db.Column(
|
||||||
|
mysql.TEXT,
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
date = db.Column(
|
||||||
|
mysql.TIMESTAMP,
|
||||||
|
nullable=False,
|
||||||
|
server_default=db.func.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
db.session.add(self)
|
||||||
|
db.session.commit()
|
||||||
|
db.session.refresh(self)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
db.session.delete(self)
|
||||||
|
db.session.commit()
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash
|
from flask import render_template, Blueprint, redirect, url_for, request, flash, current_app
|
||||||
from flask_user import login_required
|
from flask_user import login_required
|
||||||
from app.models import PetNames, db
|
from app.models import PetNames, db, CharacterXML, CharacterInfo
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
|
from app import gm_level, log_audit, scheduler
|
||||||
from app import gm_level
|
|
||||||
|
|
||||||
moderation_blueprint = Blueprint('moderation', __name__)
|
moderation_blueprint = Blueprint('moderation', __name__)
|
||||||
|
|
||||||
@@ -20,10 +19,11 @@ def index(status):
|
|||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def approve_pet(id):
|
def approve_pet(id):
|
||||||
|
|
||||||
pet_data = PetNames.query.filter(PetNames.id == id).first()
|
pet_data = PetNames.query.filter(PetNames.id == id).first()
|
||||||
|
|
||||||
pet_data.approved = 2
|
pet_data.approved = 2
|
||||||
flash(f"Approved pet name {pet_data.pet_name}", "success")
|
log_audit(f"Approved pet name {pet_data.pet_name} from {pet_data.owner_id}")
|
||||||
|
flash(f"Approved pet name {pet_data.pet_name} from {pet_data.owner_id}", "success")
|
||||||
pet_data.save()
|
pet_data.save()
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
|
|
||||||
@@ -33,10 +33,11 @@ def approve_pet(id):
|
|||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def reject_pet(id):
|
def reject_pet(id):
|
||||||
|
|
||||||
pet_data = PetNames.query.filter(PetNames.id == id).first()
|
pet_data = PetNames.query.filter(PetNames.id == id).first()
|
||||||
|
|
||||||
pet_data.approved = 0
|
pet_data.approved = 0
|
||||||
flash(f"Rejected pet name {pet_data.pet_name}", "danger")
|
log_audit(f"Rejected pet name {pet_data.pet_name} from {pet_data.owner_id}")
|
||||||
|
flash(f"Rejected pet name {pet_data.pet_name} from {pet_data.owner_id}", "danger")
|
||||||
pet_data.save()
|
pet_data.save()
|
||||||
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
return redirect(request.referrer if request.referrer else url_for("main.index"))
|
||||||
|
|
||||||
@@ -49,18 +50,16 @@ def get_pets(status="all"):
|
|||||||
ColumnDT(PetNames.id),
|
ColumnDT(PetNames.id),
|
||||||
ColumnDT(PetNames.pet_name),
|
ColumnDT(PetNames.pet_name),
|
||||||
ColumnDT(PetNames.approved),
|
ColumnDT(PetNames.approved),
|
||||||
|
ColumnDT(PetNames.owner_id),
|
||||||
]
|
]
|
||||||
|
|
||||||
query = None
|
query = None
|
||||||
if status=="all":
|
if status == "approved":
|
||||||
query = db.session.query().select_from(PetNames)
|
query = db.session.query().select_from(PetNames).filter(PetNames.approved == 2)
|
||||||
elif status=="approved":
|
elif status == "unapproved":
|
||||||
query = db.session.query().select_from(PetNames).filter(PetNames.approved==2)
|
query = db.session.query().select_from(PetNames).filter(PetNames.approved == 1)
|
||||||
elif status=="unapproved":
|
|
||||||
query = db.session.query().select_from(PetNames).filter(PetNames.approved==1)
|
|
||||||
else:
|
else:
|
||||||
raise Exception("Not a valid filter")
|
query = db.session.query().select_from(PetNames)
|
||||||
|
|
||||||
|
|
||||||
params = request.args.to_dict()
|
params = request.args.to_dict()
|
||||||
|
|
||||||
@@ -105,4 +104,47 @@ def get_pets(status="all"):
|
|||||||
"""
|
"""
|
||||||
pet_data["2"] = "<span class='text-danger'>Rejected</span>"
|
pet_data["2"] = "<span class='text-danger'>Rejected</span>"
|
||||||
|
|
||||||
|
if pet_data["3"]:
|
||||||
|
try:
|
||||||
|
pet_data["3"] = f"""
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{url_for('characters.view', id=pet_data["3"])}'>
|
||||||
|
{CharacterInfo.query.filter(CharacterInfo.id==pet_data['3']).first().name}
|
||||||
|
</a>
|
||||||
|
"""
|
||||||
|
except Exception:
|
||||||
|
PetNames.query.filter(PetNames.id == id).first().delete()
|
||||||
|
pet_data["0"] = "<span class='text-danger'>Deleted Refresh to make go away</span>"
|
||||||
|
pet_data["3"] = "<span class='text-danger'>Character Deleted</span>"
|
||||||
|
else:
|
||||||
|
pet_data["3"] = "Pending Character Association"
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.task("cron", id="pet_name_maintenance", hour="*", timezone="UTC")
|
||||||
|
def pet_name_maintenance():
|
||||||
|
with scheduler.app.app_context():
|
||||||
|
# associate pet names to characters
|
||||||
|
# current_app.logger.info("Started Pet Name Maintenance")
|
||||||
|
unassociated_pets = PetNames.query.filter(PetNames.owner_id == None).all()
|
||||||
|
if unassociated_pets:
|
||||||
|
current_app.logger.info("Found un-associated pets")
|
||||||
|
for pet in unassociated_pets:
|
||||||
|
owner = CharacterXML.query.filter(CharacterXML.xml_data.like(f"%<p id=\"{pet.id}\" l=\"%")).first()
|
||||||
|
if owner:
|
||||||
|
pet.owner_id = owner.id
|
||||||
|
pet.save()
|
||||||
|
else:
|
||||||
|
pet.delete()
|
||||||
|
|
||||||
|
# auto-moderate based on already moderated names
|
||||||
|
unmoderated_pets = PetNames.query.filter(PetNames.approved == 1).all()
|
||||||
|
if unmoderated_pets:
|
||||||
|
current_app.logger.info("Found un-moderated Pets")
|
||||||
|
for pet in unmoderated_pets:
|
||||||
|
existing_pet = PetNames.query.filter(PetNames.approved.in_([0, 2])).filter(PetNames.pet_name == pet.pet_name).first()
|
||||||
|
if existing_pet:
|
||||||
|
pet.approved = existing_pet.approved
|
||||||
|
pet.save()
|
||||||
|
# current_app.logger.info("Finished Pet Name Maintenance")
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash
|
from flask import render_template, Blueprint, redirect, url_for, request, flash
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required
|
||||||
from app.models import Account, AccountInvitation, PlayKey, db
|
from app.models import Account, PlayKey, db
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
|
from app.forms import CreatePlayKeyForm, EditPlayKeyForm
|
||||||
from app import gm_level
|
from app import gm_level, log_audit
|
||||||
|
|
||||||
play_keys_blueprint = Blueprint('play_keys', __name__)
|
play_keys_blueprint = Blueprint('play_keys', __name__)
|
||||||
|
|
||||||
# Key creation page
|
|
||||||
|
|
||||||
|
# Key creation page
|
||||||
@play_keys_blueprint.route('/', methods=['GET'])
|
@play_keys_blueprint.route('/', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(9)
|
@gm_level(9)
|
||||||
@@ -20,8 +20,9 @@ def index():
|
|||||||
@login_required
|
@login_required
|
||||||
@gm_level(9)
|
@gm_level(9)
|
||||||
def create(count=1, uses=1):
|
def create(count=1, uses=1):
|
||||||
PlayKey.create(count=count, uses=uses)
|
key = PlayKey.create(count=count, uses=uses)
|
||||||
flash(f"Created {count} Play Key(s) with {uses} uses!", "success")
|
log_audit(f"Created {count} Play Key(s) with {uses} uses!")
|
||||||
|
flash(f"Created {count} Play Key(s) with {uses} uses! {key}", "success")
|
||||||
return redirect(url_for('play_keys.index'))
|
return redirect(url_for('play_keys.index'))
|
||||||
|
|
||||||
|
|
||||||
@@ -32,6 +33,8 @@ def bulk_create():
|
|||||||
form = CreatePlayKeyForm()
|
form = CreatePlayKeyForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
PlayKey.create(count=form.count.data, uses=form.uses.data)
|
PlayKey.create(count=form.count.data, uses=form.uses.data)
|
||||||
|
log_audit(f"Created {form.count.data} Play Key(s) with {form.uses.data} uses!")
|
||||||
|
flash(f"Created {form.count.data} Play Key(s) with {form.uses.data} uses!", "success")
|
||||||
return redirect(url_for('play_keys.index'))
|
return redirect(url_for('play_keys.index'))
|
||||||
|
|
||||||
return render_template('play_keys/bulk.html.j2', form=form)
|
return render_template('play_keys/bulk.html.j2', form=form)
|
||||||
@@ -42,7 +45,8 @@ def bulk_create():
|
|||||||
@gm_level(9)
|
@gm_level(9)
|
||||||
def delete(id):
|
def delete(id):
|
||||||
key = PlayKey.query.filter(PlayKey.id == id).first()
|
key = PlayKey.query.filter(PlayKey.id == id).first()
|
||||||
associated_accounts = Account.query.filter(Account.play_key_id==id).all()
|
# associated_accounts = Account.query.filter(Account.play_key_id==id).all()
|
||||||
|
log_audit(f"Deleted Play Key {key.key_string}")
|
||||||
flash(f"Deleted Play Key {key.key_string}", "danger")
|
flash(f"Deleted Play Key {key.key_string}", "danger")
|
||||||
key.delete()
|
key.delete()
|
||||||
return redirect(url_for('play_keys.index'))
|
return redirect(url_for('play_keys.index'))
|
||||||
@@ -52,14 +56,20 @@ def delete(id):
|
|||||||
@login_required
|
@login_required
|
||||||
@gm_level(9)
|
@gm_level(9)
|
||||||
def edit(id):
|
def edit(id):
|
||||||
key = PlayKey.query.filter(PlayKey.id==id).first()
|
key = PlayKey.query.filter(PlayKey.id == id).first()
|
||||||
form = EditPlayKeyForm()
|
form = EditPlayKeyForm()
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
|
log_audit(f"Updated Play key {key.id} \
|
||||||
|
Uses: {key.key_uses}:{form.uses.data} \
|
||||||
|
Active: {key.active}:{form.active.data} \
|
||||||
|
Notes: {key.notes}:{form.notes.data} \
|
||||||
|
")
|
||||||
key.key_uses = form.uses.data
|
key.key_uses = form.uses.data
|
||||||
key.active = form.active.data
|
key.active = form.active.data
|
||||||
key.notes = form.notes.data
|
key.notes = form.notes.data
|
||||||
key.save()
|
key.save()
|
||||||
|
|
||||||
return redirect(url_for('play_keys.index'))
|
return redirect(url_for('play_keys.index'))
|
||||||
|
|
||||||
form.uses.data = key.key_uses
|
form.uses.data = key.key_uses
|
||||||
@@ -74,7 +84,7 @@ def edit(id):
|
|||||||
@gm_level(9)
|
@gm_level(9)
|
||||||
def view(id):
|
def view(id):
|
||||||
key = PlayKey.query.filter(PlayKey.id == id).first()
|
key = PlayKey.query.filter(PlayKey.id == id).first()
|
||||||
accounts = Account.query.filter(Account.play_key_id==id).all()
|
accounts = Account.query.filter(Account.play_key_id == id).all()
|
||||||
return render_template('play_keys/view.html.j2', key=key, accounts=accounts)
|
return render_template('play_keys/view.html.j2', key=key, accounts=accounts)
|
||||||
|
|
||||||
|
|
||||||
@@ -115,7 +125,12 @@ def get():
|
|||||||
Delete
|
Delete
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="modal fade bd-example-modal-lg" id="delete-{play_key["1"]}-modal" tabindex="-1" role="dialog" aria-labelledby="delete-{play_key["1"]}-modalLabel" aria-hidden="true">
|
<div class="modal
|
||||||
|
fade bd-example-modal-lg"
|
||||||
|
id="delete-{play_key["1"]}-modal"
|
||||||
|
tabindex="-1" role="dialog"
|
||||||
|
aria-labelledby="delete-{play_key["1"]}-modalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content bg-dark border-primary">
|
<div class="modal-content bg-dark border-primary">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -5,29 +5,28 @@ from flask import (
|
|||||||
url_for,
|
url_for,
|
||||||
request,
|
request,
|
||||||
abort,
|
abort,
|
||||||
jsonify,
|
|
||||||
send_from_directory,
|
|
||||||
make_response,
|
make_response,
|
||||||
flash
|
flash,
|
||||||
|
current_app
|
||||||
)
|
)
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required, current_user
|
||||||
import json
|
|
||||||
from datatables import ColumnDT, DataTables
|
from datatables import ColumnDT, DataTables
|
||||||
import time
|
import time
|
||||||
from app.models import Property, db, UGC, CharacterInfo, PropertyContent, Account
|
from app.models import Property, db, UGC, CharacterInfo, PropertyContent, Account, Mail
|
||||||
from app.schemas import PropertySchema
|
from app.schemas import PropertySchema
|
||||||
from app import gm_level
|
from app import gm_level, log_audit
|
||||||
from app.luclient import query_cdclient
|
from app.luclient import query_cdclient
|
||||||
|
from app.forms import RejectPropertyForm
|
||||||
|
|
||||||
import zlib
|
import zlib
|
||||||
import xmltodict
|
|
||||||
import os
|
|
||||||
import app.pylddlib as ldd
|
import app.pylddlib as ldd
|
||||||
|
import pathlib
|
||||||
|
|
||||||
property_blueprint = Blueprint('properties', __name__)
|
property_blueprint = Blueprint('properties', __name__)
|
||||||
|
|
||||||
property_schema = PropertySchema()
|
property_schema = PropertySchema()
|
||||||
|
|
||||||
|
|
||||||
@property_blueprint.route('/', methods=['GET'])
|
@property_blueprint.route('/', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
@@ -40,7 +39,7 @@ def index():
|
|||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def approve(id):
|
def approve(id):
|
||||||
|
|
||||||
property_data = Property.query.filter(Property.id == id).first()
|
property_data = Property.query.filter(Property.id == id).first()
|
||||||
|
|
||||||
property_data.mod_approved = not property_data.mod_approved
|
property_data.mod_approved = not property_data.mod_approved
|
||||||
|
|
||||||
@@ -49,26 +48,30 @@ def approve(id):
|
|||||||
property_data.rejection_reason = ""
|
property_data.rejection_reason = ""
|
||||||
|
|
||||||
if property_data.mod_approved:
|
if property_data.mod_approved:
|
||||||
flash(
|
message = f"""Approved Property
|
||||||
f"""Approved Property
|
|
||||||
{property_data.name if property_data.name else query_cdclient(
|
{property_data.name if property_data.name else query_cdclient(
|
||||||
'select DisplayDescription from ZoneTable where zoneID = ?',
|
'select DisplayDescription from ZoneTable where zoneID = ?',
|
||||||
[property_data.zone_id],
|
[property_data.zone_id],
|
||||||
one=True
|
one=True
|
||||||
)[0]}
|
)[0]}
|
||||||
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}""",
|
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}"""
|
||||||
|
log_audit(message)
|
||||||
|
flash(
|
||||||
|
message,
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
flash(
|
message = f"""Unapproved Property
|
||||||
f"""Unapproved Property
|
|
||||||
{property_data.name if property_data.name else query_cdclient(
|
{property_data.name if property_data.name else query_cdclient(
|
||||||
'select DisplayDescription from ZoneTable where zoneID = ?',
|
'select DisplayDescription from ZoneTable where zoneID = ?',
|
||||||
[property_data.zone_id],
|
[property_data.zone_id],
|
||||||
one=True
|
one=True
|
||||||
)[0]}
|
)[0]}
|
||||||
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}""",
|
from {CharacterInfo.query.filter(CharacterInfo.id==property_data.owner_id).first().name}"""
|
||||||
"danger"
|
log_audit(message)
|
||||||
|
flash(
|
||||||
|
message,
|
||||||
|
"warning"
|
||||||
)
|
)
|
||||||
|
|
||||||
property_data.save()
|
property_data.save()
|
||||||
@@ -83,11 +86,73 @@ def approve(id):
|
|||||||
else:
|
else:
|
||||||
go_to = url_for('main.index')
|
go_to = url_for('main.index')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return redirect(go_to)
|
return redirect(go_to)
|
||||||
|
|
||||||
|
|
||||||
|
@property_blueprint.route('/reject/<id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def reject(id):
|
||||||
|
|
||||||
|
property_data = Property.query.filter(Property.id == id).first()
|
||||||
|
|
||||||
|
form = RejectPropertyForm()
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
char_name = CharacterInfo.query.filter(CharacterInfo.id == property_data.owner_id).first().name
|
||||||
|
zone_name = query_cdclient(
|
||||||
|
'select DisplayDescription from ZoneTable where zoneID = ?',
|
||||||
|
[property_data.zone_id],
|
||||||
|
one=True
|
||||||
|
)[0]
|
||||||
|
property_data.mod_approved = False
|
||||||
|
property_data.rejection_reason = form.rejection_reason.data
|
||||||
|
message = f"""Rejected Property
|
||||||
|
{property_data.name if property_data.name else zone_name}
|
||||||
|
from {char_name} with reason \"{form.rejection_reason.data}\""""
|
||||||
|
log_audit(message)
|
||||||
|
flash(
|
||||||
|
message,
|
||||||
|
"danger"
|
||||||
|
)
|
||||||
|
|
||||||
|
property_data.save()
|
||||||
|
|
||||||
|
# send rejection reason to their mailbox
|
||||||
|
# cause the game doesn't present it otherwise
|
||||||
|
mail_message = f"""Rejected Property
|
||||||
|
{property_data.name} on {zone_name}
|
||||||
|
with reason \"{form.rejection_reason.data}\""""
|
||||||
|
Mail(
|
||||||
|
sender_id=0,
|
||||||
|
sender_name=f"[GM] {current_user.username}",
|
||||||
|
receiver_id=property_data.owner_id,
|
||||||
|
receiver_name=char_name,
|
||||||
|
time_sent=time.time(),
|
||||||
|
subject="Property Rejected",
|
||||||
|
body=mail_message,
|
||||||
|
attachment_id=0,
|
||||||
|
attachment_lot=0,
|
||||||
|
attachment_count=0
|
||||||
|
).save()
|
||||||
|
|
||||||
|
go_to = ""
|
||||||
|
|
||||||
|
if request.referrer:
|
||||||
|
if "view_models" in request.referrer:
|
||||||
|
go_to = url_for('properties.view', id=id)
|
||||||
|
else:
|
||||||
|
go_to = url_for('properties.index')
|
||||||
|
else:
|
||||||
|
go_to = url_for('main.index')
|
||||||
|
|
||||||
|
return redirect(go_to)
|
||||||
|
|
||||||
|
form.rejection_reason.data = property_data.rejection_reason
|
||||||
|
|
||||||
|
return render_template('properties/reject.html.j2', property_data=property_data, form=form)
|
||||||
|
|
||||||
|
|
||||||
@property_blueprint.route('/view/<id>', methods=['GET'])
|
@property_blueprint.route('/view/<id>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def view(id):
|
def view(id):
|
||||||
@@ -123,26 +188,29 @@ def get(status="all"):
|
|||||||
ColumnDT(Property.time_claimed), # 9
|
ColumnDT(Property.time_claimed), # 9
|
||||||
ColumnDT(Property.rejection_reason), # 10
|
ColumnDT(Property.rejection_reason), # 10
|
||||||
ColumnDT(Property.reputation), # 11
|
ColumnDT(Property.reputation), # 11
|
||||||
ColumnDT(Property.zone_id), # 12
|
ColumnDT(Property.performance_cost), # 12
|
||||||
ColumnDT(Account.username) # 13
|
ColumnDT(Property.zone_id), # 13
|
||||||
|
ColumnDT(Account.username) # 14
|
||||||
]
|
]
|
||||||
|
|
||||||
query = None
|
query = None
|
||||||
if status=="all":
|
if status == "approved":
|
||||||
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id==Property.owner_id).join(Account)
|
query = db.session.query().select_from(Property).join(
|
||||||
elif status=="approved":
|
CharacterInfo, CharacterInfo.id == Property.owner_id
|
||||||
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id==Property.owner_id).join(Account).filter(Property.mod_approved==True)
|
).join(Account).filter(Property.mod_approved == True).filter(Property.privacy_option == 2) # noqa
|
||||||
elif status=="unapproved":
|
elif status == "unapproved":
|
||||||
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id==Property.owner_id).join(Account).filter(Property.mod_approved==False)
|
query = db.session.query().select_from(Property).join(
|
||||||
|
CharacterInfo, CharacterInfo.id == Property.owner_id
|
||||||
|
).join(Account).filter(Property.mod_approved == False).filter(Property.privacy_option == 2).filter(Property.rejection_reason == "") # noqa
|
||||||
else:
|
else:
|
||||||
raise Exception("Not a valid filter")
|
query = db.session.query().select_from(Property).join(CharacterInfo, CharacterInfo.id == Property.owner_id).join(Account)
|
||||||
|
|
||||||
|
|
||||||
params = request.args.to_dict()
|
params = request.args.to_dict()
|
||||||
|
|
||||||
rowTable = DataTables(params, query, columns)
|
rowTable = DataTables(params, query, columns)
|
||||||
|
|
||||||
data = rowTable.output_result()
|
data = rowTable.output_result()
|
||||||
|
print(data)
|
||||||
for property_data in data["data"]:
|
for property_data in data["data"]:
|
||||||
id = property_data["0"]
|
id = property_data["0"]
|
||||||
|
|
||||||
@@ -167,6 +235,13 @@ def get(status="all"):
|
|||||||
Unapprove
|
Unapprove
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
|
if not property_data["10"]:
|
||||||
|
property_data["0"] += f"""
|
||||||
|
<a role="button" class="btn btn-danger btn btn-block"
|
||||||
|
href='{url_for('properties.reject', id=id)}'>
|
||||||
|
Reject
|
||||||
|
</a>
|
||||||
|
"""
|
||||||
|
|
||||||
property_data["1"] = f"""
|
property_data["1"] = f"""
|
||||||
<a role="button" class="btn btn-primary btn btn-block"
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
@@ -177,10 +252,10 @@ def get(status="all"):
|
|||||||
|
|
||||||
if property_data["4"] == "":
|
if property_data["4"] == "":
|
||||||
property_data["4"] = query_cdclient(
|
property_data["4"] = query_cdclient(
|
||||||
'select DisplayDescription from ZoneTable where zoneID = ?',
|
'select DisplayDescription from ZoneTable where zoneID = ?',
|
||||||
[property_data["12"]],
|
[property_data["13"]],
|
||||||
one=True
|
one=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if property_data["6"] == 0:
|
if property_data["6"] == 0:
|
||||||
property_data["6"] = "Private"
|
property_data["6"] = "Private"
|
||||||
@@ -197,25 +272,25 @@ def get(status="all"):
|
|||||||
else:
|
else:
|
||||||
property_data["7"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
property_data["7"] = '''<h2 class="far fa-check-square text-success"></h2>'''
|
||||||
|
|
||||||
property_data["12"] = query_cdclient(
|
property_data["13"] = query_cdclient(
|
||||||
'select DisplayDescription from ZoneTable where zoneID = ?',
|
'select DisplayDescription from ZoneTable where zoneID = ?',
|
||||||
[property_data["12"]],
|
[property_data["13"]],
|
||||||
one=True
|
one=True
|
||||||
)
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@property_blueprint.route('/view_model/<id>', methods=['GET'])
|
@property_blueprint.route('/view_model/<id>/<lod>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def view_model(id):
|
def view_model(id, lod):
|
||||||
property_content_data = PropertyContent.query.filter(PropertyContent.id==id).all()
|
property_content_data = PropertyContent.query.filter(PropertyContent.id == id).all()
|
||||||
|
|
||||||
# TODO: Restrict somehow
|
# TODO: Restrict somehow
|
||||||
formatted_data = [
|
formatted_data = [
|
||||||
{
|
{
|
||||||
"obj": url_for('properties.get_model', id=property_content_data[0].id, file_format='obj'),
|
"obj": url_for('properties.get_model', id=property_content_data[0].id, file_format='obj', lod=lod),
|
||||||
"mtl": url_for('properties.get_model', id=property_content_data[0].id, file_format='mtl'),
|
"mtl": url_for('properties.get_model', id=property_content_data[0].id, file_format='mtl', lod=lod),
|
||||||
"lot": property_content_data[0].lot,
|
"lot": property_content_data[0].lot,
|
||||||
"id": property_content_data[0].id,
|
"id": property_content_data[0].id,
|
||||||
"pos": [{
|
"pos": [{
|
||||||
@@ -232,9 +307,11 @@ def view_model(id):
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'ldd/ldd.html.j2',
|
'ldd/ldd.html.j2',
|
||||||
content=formatted_data
|
content=formatted_data,
|
||||||
|
lod=lod
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
property_center = {
|
property_center = {
|
||||||
1150: "(-17, 432, -60)",
|
1150: "(-17, 432, -60)",
|
||||||
1151: "(0, 455, -110)",
|
1151: "(0, 455, -110)",
|
||||||
@@ -245,11 +322,11 @@ property_center = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@property_blueprint.route('/view_models/<id>', methods=['GET'])
|
@property_blueprint.route('/view_models/<id>/<lod>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def view_models(id):
|
def view_models(id, lod):
|
||||||
property_content_data = PropertyContent.query.filter(
|
property_content_data = PropertyContent.query.filter(
|
||||||
PropertyContent.property_id==id
|
PropertyContent.property_id == id
|
||||||
).order_by(PropertyContent.lot).all()
|
).order_by(PropertyContent.lot).all()
|
||||||
|
|
||||||
consolidated_list = []
|
consolidated_list = []
|
||||||
@@ -273,8 +350,8 @@ def view_models(id):
|
|||||||
# add new lot
|
# add new lot
|
||||||
consolidated_list.append(
|
consolidated_list.append(
|
||||||
{
|
{
|
||||||
"obj": url_for('properties.get_model', id=property_content_data[item].id, file_format='obj'),
|
"obj": url_for('properties.get_model', id=property_content_data[item].id, file_format='obj', lod=lod),
|
||||||
"mtl": url_for('properties.get_model', id=property_content_data[item].id, file_format='mtl'),
|
"mtl": url_for('properties.get_model', id=property_content_data[item].id, file_format='mtl', lod=lod),
|
||||||
"lot": property_content_data[item].lot,
|
"lot": property_content_data[item].lot,
|
||||||
"id": property_content_data[item].id,
|
"id": property_content_data[item].id,
|
||||||
"pos": [{
|
"pos": [{
|
||||||
@@ -288,37 +365,39 @@ def view_models(id):
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
property_data = Property.query.filter(Property.id==id).first()
|
property_data = Property.query.filter(Property.id == id).first()
|
||||||
return render_template(
|
return render_template(
|
||||||
'ldd/ldd.html.j2',
|
'ldd/ldd.html.j2',
|
||||||
property_data=property_data,
|
property_data=property_data,
|
||||||
content=consolidated_list,
|
content=consolidated_list,
|
||||||
center=property_center[property_data.zone_id]
|
center=property_center[property_data.zone_id],
|
||||||
|
lod=lod
|
||||||
)
|
)
|
||||||
|
|
||||||
@property_blueprint.route('/get_model/<id>/<file_format>', methods=['GET'])
|
|
||||||
@login_required
|
|
||||||
def get_model(id, file_format):
|
|
||||||
content = PropertyContent.query.filter(PropertyContent.id==id).first()
|
|
||||||
|
|
||||||
if content.lot == 14: # ugc model
|
@property_blueprint.route('/get_model/<id>/<file_format>/<lod>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_model(id, file_format, lod):
|
||||||
|
content = PropertyContent.query.filter(PropertyContent.id == id).first()
|
||||||
|
if not(0 <= int(lod) <= 2):
|
||||||
|
abort(404)
|
||||||
|
if content.lot == 14: # ugc model
|
||||||
response = ugc(content)[0]
|
response = ugc(content)[0]
|
||||||
else: # prebuild model
|
else: # prebuilt model
|
||||||
response = prebuilt(content, file_format)[0]
|
response = prebuilt(content, file_format, lod)[0]
|
||||||
|
|
||||||
response.headers.set('Content-Type', 'text/xml')
|
response.headers.set('Content-Type', 'text/xml')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property_blueprint.route('/download_model/<id>', methods=['GET'])
|
@property_blueprint.route('/download_model/<id>', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def download_model(id):
|
def download_model(id):
|
||||||
content = PropertyContent.query.filter(PropertyContent.id==id).first()
|
content = PropertyContent.query.filter(PropertyContent.id == id).first()
|
||||||
|
|
||||||
if content.lot == 14: # ugc model
|
if content.lot == 14: # ugc model
|
||||||
response, filename = ugc(content)
|
response, filename = ugc(content)
|
||||||
else: # prebuild model
|
else: # prebuilt model
|
||||||
response, filename = prebuilt(content, "lxfml")
|
response, filename = prebuilt(content, "lxfml")
|
||||||
|
|
||||||
response.headers.set('Content-Type', 'attachment/xml')
|
response.headers.set('Content-Type', 'attachment/xml')
|
||||||
@@ -331,13 +410,13 @@ def download_model(id):
|
|||||||
|
|
||||||
|
|
||||||
def ugc(content):
|
def ugc(content):
|
||||||
ugc_data = UGC.query.filter(UGC.id==content.ugc_id).first()
|
ugc_data = UGC.query.filter(UGC.id == content.ugc_id).first()
|
||||||
uncompressed_lxfml = zlib.decompress(ugc_data.lxfml)
|
uncompressed_lxfml = zlib.decompress(ugc_data.lxfml)
|
||||||
response = make_response(uncompressed_lxfml)
|
response = make_response(uncompressed_lxfml)
|
||||||
return response, ugc_data.filename
|
return response, ugc_data.filename
|
||||||
|
|
||||||
|
|
||||||
def prebuilt(content, file_format):
|
def prebuilt(content, file_format, lod):
|
||||||
# translate LOT to component id
|
# translate LOT to component id
|
||||||
# we need to get a type of 2 because reasons
|
# we need to get a type of 2 because reasons
|
||||||
render_component_id = query_cdclient(
|
render_component_id = query_cdclient(
|
||||||
@@ -346,40 +425,38 @@ def prebuilt(content, file_format):
|
|||||||
one=True
|
one=True
|
||||||
)[0]
|
)[0]
|
||||||
# find the asset from rendercomponent given the component id
|
# find the asset from rendercomponent given the component id
|
||||||
filename = query_cdclient('select render_asset from RenderComponent where id = ?',
|
filename = query_cdclient(
|
||||||
|
'select render_asset from RenderComponent where id = ?',
|
||||||
[render_component_id],
|
[render_component_id],
|
||||||
one=True
|
one=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
filename = filename[0].split("\\\\")[-1].lower().split(".")[0]
|
filename = filename[0].split("\\\\")[-1].lower().split(".")[0]
|
||||||
|
if "/" in filename:
|
||||||
|
filename = filename.split("/")[-1].lower()
|
||||||
else:
|
else:
|
||||||
return f"No filename for LOT {content.lot}"
|
return f"No filename for LOT {content.lot}"
|
||||||
|
|
||||||
|
lxfml = pathlib.Path(f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml')
|
||||||
if file_format == "lxfml":
|
if file_format == "lxfml":
|
||||||
lxfml = f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml'
|
|
||||||
with open(lxfml, 'r') as file:
|
with open(lxfml, 'r') as file:
|
||||||
lxfml_data = file.read()
|
lxfml_data = file.read()
|
||||||
# print(lxfml_data)
|
|
||||||
response = make_response(lxfml_data)
|
response = make_response(lxfml_data)
|
||||||
|
|
||||||
elif file_format in ["obj", "mtl"]:
|
elif file_format in ["obj", "mtl"]:
|
||||||
|
cache = pathlib.Path(f'app/cache/BrickModels/{filename}.lod{lod}.{file_format}')
|
||||||
|
if not cache.is_file():
|
||||||
|
cache.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
ldd.main(str(lxfml.as_posix()), str(cache.with_suffix("").as_posix()), lod) # convert to OBJ
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"ERROR on {cache}:\n {e}")
|
||||||
|
|
||||||
cache = f"app/cache/{filename}.{file_format}"
|
with open(str(cache.as_posix()), 'r') as file:
|
||||||
|
cache_data = file.read()
|
||||||
|
|
||||||
if os.path.exists(cache):
|
response = make_response(cache_data)
|
||||||
with open(cache, 'r') as file:
|
|
||||||
cache_data = file.read()
|
|
||||||
response = make_response(cache_data)
|
|
||||||
|
|
||||||
else:
|
|
||||||
lxfml = f'app/luclient/res/BrickModels/{filename.split(".")[0]}.lxfml'
|
|
||||||
ldd.main(lxfml, cache.split('.')[0]) # convert to OBJ
|
|
||||||
|
|
||||||
if os.path.exists(cache):
|
|
||||||
with open(cache, 'r') as file:
|
|
||||||
cache_data = file.read()
|
|
||||||
response = make_response(cache_data)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise(Exception("INVALID FILE FORMAT"))
|
raise(Exception("INVALID FILE FORMAT"))
|
||||||
|
|||||||
382
app/pylddlib.py
382
app/pylddlib.py
@@ -1,43 +1,23 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# pylddlib version 0.4.9.7
|
# pylddlib version 0.4.9.7
|
||||||
# based on pyldd2obj version 0.4.8 - Copyright (c) 2019 by jonnysp
|
# based on pyldd2obj version 0.4.8 - Copyright (c) 2019 by jonnysp
|
||||||
#
|
|
||||||
# Updates:
|
|
||||||
# 0.4.9.8 Make work with LEGO Universe brickdb
|
|
||||||
# 0.4.9.7 corrected bug of incorrectly parsing the primitive xml file, specifically with comments. Add support LDDLIFTREE envirnment variable to set location of db.lif.
|
|
||||||
# 0.4.9.6 preliminary Linux support
|
|
||||||
# 0.4.9.5 corrected bug of incorrectly Bounding / GeometryBounding parsing the primitive xml file.
|
|
||||||
# 0.4.9.4 improved lif.db checking for crucial files (because of the infamous botched 4.3.12 LDD Windows update).
|
|
||||||
# 0.4.9.3 improved Windows and Python 3 compatibility
|
|
||||||
# 0.4.9.2 changed handling of material = 0 for a part. Now a 0 will choose the 1st material (the base material of a part) and not the previous material of the subpart before. This will fix "Chicken Helmet Part 11262". It may break other parts and this change needs further regression.
|
|
||||||
# 0.4.9.1 improved custom2DField handling, fixed decorations bug, improved material assignments handling
|
|
||||||
# 0.4.9 updates to support reading extracted db.lif from db folder
|
|
||||||
#
|
|
||||||
# License: MIT License
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import sys
|
import sys
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
import zipfile
|
import zipfile
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
import time
|
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf-8')
|
|
||||||
|
|
||||||
PRIMITIVEPATH = '/Primitives/'
|
PRIMITIVEPATH = '/Primitives/'
|
||||||
GEOMETRIEPATH = PRIMITIVEPATH + 'LOD0/'
|
GEOMETRIEPATH = PRIMITIVEPATH
|
||||||
DECORATIONPATH = '/Decorations/'
|
DECORATIONPATH = '/Decorations/'
|
||||||
MATERIALNAMESPATH = '/MaterialNames/'
|
MATERIALNAMESPATH = '/MaterialNames/'
|
||||||
|
|
||||||
LOGOONSTUDSCONNTYPE = {"0:4", "0:4:1", "0:4:2", "0:4:33", "2:4:1", "2:4:34"}
|
LOGOONSTUDSCONNTYPE = {"0:4", "0:4:1", "0:4:2", "0:4:33", "2:4:1", "2:4:34"}
|
||||||
|
|
||||||
|
|
||||||
class Matrix3D:
|
class Matrix3D:
|
||||||
def __init__(self, n11=1,n12=0,n13=0,n14=0,n21=0,n22=1,n23=0,n24=0,n31=0,n32=0,n33=1,n34=0,n41=0,n42=0,n43=0,n44=1):
|
def __init__(self, n11=1, n12=0, n13=0, n14=0, n21=0, n22=1, n23=0, n24=0, n31=0, n32=0, n33=1, n34=0, n41=0, n42=0, n43=0, n44=1):
|
||||||
self.n11 = n11
|
self.n11 = n11
|
||||||
self.n12 = n12
|
self.n12 = n12
|
||||||
self.n13 = n13
|
self.n13 = n13
|
||||||
@@ -56,9 +36,26 @@ class Matrix3D:
|
|||||||
self.n44 = n44
|
self.n44 = n44
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15}]'.format(self.n11, self.n12, self.n13,self.n14,self.n21, self.n22, self.n23,self.n24,self.n31, self.n32, self.n33,self.n34,self.n41, self.n42, self.n43,self.n44)
|
return '[{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15}]'.format(
|
||||||
|
self.n11,
|
||||||
|
self.n12,
|
||||||
|
self.n13,
|
||||||
|
self.n14,
|
||||||
|
self.n21,
|
||||||
|
self.n22,
|
||||||
|
self.n23,
|
||||||
|
self.n24,
|
||||||
|
self.n31,
|
||||||
|
self.n32,
|
||||||
|
self.n33,
|
||||||
|
self.n34,
|
||||||
|
self.n41,
|
||||||
|
self.n42,
|
||||||
|
self.n43,
|
||||||
|
self.n44
|
||||||
|
)
|
||||||
|
|
||||||
def rotate(self,angle=0,axis=0):
|
def rotate(self, angle=0, axis=0):
|
||||||
c = math.cos(angle)
|
c = math.cos(angle)
|
||||||
s = math.sin(angle)
|
s = math.sin(angle)
|
||||||
t = 1 - c
|
t = 1 - c
|
||||||
@@ -109,21 +106,22 @@ class Matrix3D:
|
|||||||
self.n12 * other.n41 + self.n22 * other.n42 + self.n32 * other.n43 + self.n42 * other.n44,
|
self.n12 * other.n41 + self.n22 * other.n42 + self.n32 * other.n43 + self.n42 * other.n44,
|
||||||
self.n13 * other.n41 + self.n23 * other.n42 + self.n33 * other.n43 + self.n43 * other.n44,
|
self.n13 * other.n41 + self.n23 * other.n42 + self.n33 * other.n43 + self.n43 * other.n44,
|
||||||
self.n14 * other.n41 + self.n24 * other.n42 + self.n34 * other.n43 + self.n44 * other.n44
|
self.n14 * other.n41 + self.n24 * other.n42 + self.n34 * other.n43 + self.n44 * other.n44
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Point3D:
|
class Point3D:
|
||||||
def __init__(self, x=0,y=0,z=0):
|
def __init__(self, x=0, y=0, z=0):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.z = z
|
self.z = z
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[{0},{1},{2}]'.format(self.x, self.y,self.z)
|
return '[{0},{1},{2}]'.format(self.x, self.y, self.z)
|
||||||
|
|
||||||
def string(self,prefix = "v"):
|
def string(self, prefix="v"):
|
||||||
return '{0} {1:f} {2:f} {3:f}\n'.format(prefix ,self.x , self.y, self.z)
|
return '{0} {1:f} {2:f} {3:f}\n'.format(prefix, self.x, self.y, self.z)
|
||||||
|
|
||||||
def transformW(self,matrix):
|
def transformW(self, matrix):
|
||||||
x = matrix.n11 * self.x + matrix.n21 * self.y + matrix.n31 * self.z
|
x = matrix.n11 * self.x + matrix.n21 * self.y + matrix.n31 * self.z
|
||||||
y = matrix.n12 * self.x + matrix.n22 * self.y + matrix.n32 * self.z
|
y = matrix.n12 * self.x + matrix.n22 * self.y + matrix.n32 * self.z
|
||||||
z = matrix.n13 * self.x + matrix.n23 * self.y + matrix.n33 * self.z
|
z = matrix.n13 * self.x + matrix.n23 * self.y + matrix.n33 * self.z
|
||||||
@@ -131,7 +129,7 @@ class Point3D:
|
|||||||
self.y = y
|
self.y = y
|
||||||
self.z = z
|
self.z = z
|
||||||
|
|
||||||
def transform(self,matrix):
|
def transform(self, matrix):
|
||||||
x = matrix.n11 * self.x + matrix.n21 * self.y + matrix.n31 * self.z + matrix.n41
|
x = matrix.n11 * self.x + matrix.n21 * self.y + matrix.n31 * self.z + matrix.n41
|
||||||
y = matrix.n12 * self.x + matrix.n22 * self.y + matrix.n32 * self.z + matrix.n42
|
y = matrix.n12 * self.x + matrix.n22 * self.y + matrix.n32 * self.z + matrix.n42
|
||||||
z = matrix.n13 * self.x + matrix.n23 * self.y + matrix.n33 * self.z + matrix.n43
|
z = matrix.n13 * self.x + matrix.n23 * self.y + matrix.n33 * self.z + matrix.n43
|
||||||
@@ -140,41 +138,58 @@ class Point3D:
|
|||||||
self.z = z
|
self.z = z
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Point3D(x=self.x,y=self.y,z=self.z)
|
return Point3D(x=self.x, y=self.y, z=self.z)
|
||||||
|
|
||||||
|
|
||||||
class Point2D:
|
class Point2D:
|
||||||
def __init__(self, x=0,y=0):
|
def __init__(self, x=0, y=0):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[{0},{1}]'.format(self.x, self.y * -1)
|
return '[{0},{1}]'.format(self.x, self.y * -1)
|
||||||
def string(self,prefix="t"):
|
|
||||||
return '{0} {1:f} {2:f}\n'.format(prefix , self.x, self.y * -1 )
|
def string(self, prefix="t"):
|
||||||
|
return '{0} {1:f} {2:f}\n'.format(prefix, self.x, self.y * -1)
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Point2D(x=self.x,y=self.y)
|
return Point2D(x=self.x, y=self.y)
|
||||||
|
|
||||||
|
|
||||||
class Face:
|
class Face:
|
||||||
def __init__(self,a=0,b=0,c=0):
|
def __init__(self, a=0, b=0, c=0):
|
||||||
self.a = a
|
self.a = a
|
||||||
self.b = b
|
self.b = b
|
||||||
self.c = c
|
self.c = c
|
||||||
def string(self,prefix="f", indexOffset=0 ,textureoffset=0):
|
|
||||||
|
def string(self, prefix="f", indexOffset=0, textureoffset=0):
|
||||||
if textureoffset == 0:
|
if textureoffset == 0:
|
||||||
return prefix + ' {0}//{0} {1}//{1} {2}//{2}\n'.format(self.a + indexOffset, self.b + indexOffset, self.c + indexOffset)
|
return prefix + ' {0}//{0} {1}//{1} {2}//{2}\n'.format(self.a + indexOffset, self.b + indexOffset, self.c + indexOffset)
|
||||||
else:
|
else:
|
||||||
return prefix + ' {0}/{3}/{0} {1}/{4}/{1} {2}/{5}/{2}\n'.format(self.a + indexOffset, self.b + indexOffset, self.c + indexOffset,self.a + textureoffset, self.b + textureoffset, self.c + textureoffset)
|
return prefix + ' {0}/{3}/{0} {1}/{4}/{1} {2}/{5}/{2}\n'.format(
|
||||||
|
self.a + indexOffset,
|
||||||
|
self.b + indexOffset,
|
||||||
|
self.c + indexOffset,
|
||||||
|
self.a + textureoffset,
|
||||||
|
self.b + textureoffset,
|
||||||
|
self.c + textureoffset
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[{0},{1},{2}]'.format(self.a, self.b, self.c)
|
return '[{0},{1},{2}]'.format(self.a, self.b, self.c)
|
||||||
|
|
||||||
|
|
||||||
class Group:
|
class Group:
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
self.partRefs = node.getAttribute('partRefs').split(',')
|
self.partRefs = node.getAttribute('partRefs').split(',')
|
||||||
|
|
||||||
|
|
||||||
class Bone:
|
class Bone:
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
self.refID = node.getAttribute('refID')
|
self.refID = node.getAttribute('refID')
|
||||||
(a, b, c, d, e, f, g, h, i, x, y, z) = map(float, node.getAttribute('transformation').split(','))
|
(a, b, c, d, e, f, g, h, i, x, y, z) = map(float, node.getAttribute('transformation').split(','))
|
||||||
self.matrix = Matrix3D(n11=a,n12=b,n13=c,n14=0,n21=d,n22=e,n23=f,n24=0,n31=g,n32=h,n33=i,n34=0,n41=x,n42=y,n43=z,n44=1)
|
self.matrix = Matrix3D(n11=a, n12=b, n13=c, n14=0, n21=d, n22=e, n23=f, n24=0, n31=g, n32=h, n33=i, n34=0, n41=x, n42=y, n43=z, n44=1)
|
||||||
|
|
||||||
|
|
||||||
class Part:
|
class Part:
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
@@ -185,19 +200,17 @@ class Part:
|
|||||||
self.designID = node.getAttribute('designID')
|
self.designID = node.getAttribute('designID')
|
||||||
self.materials = list(map(str, node.getAttribute('materials').split(',')))
|
self.materials = list(map(str, node.getAttribute('materials').split(',')))
|
||||||
|
|
||||||
lastm = '0'
|
|
||||||
for i, m in enumerate(self.materials):
|
for i, m in enumerate(self.materials):
|
||||||
if (m == '0'):
|
if (m == '0'):
|
||||||
# self.materials[i] = lastm
|
# self.materials[i] = lastm
|
||||||
self.materials[i] = self.materials[0] #in case of 0 choose the 'base' material
|
self.materials[i] = self.materials[0] # in case of 0 choose the 'base' material
|
||||||
else:
|
|
||||||
lastm = m
|
|
||||||
if node.hasAttribute('decoration'):
|
if node.hasAttribute('decoration'):
|
||||||
self.decoration = list(map(str,node.getAttribute('decoration').split(',')))
|
self.decoration = list(map(str, node.getAttribute('decoration').split(',')))
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'Bone':
|
if childnode.nodeName == 'Bone':
|
||||||
self.Bones.append(Bone(node=childnode))
|
self.Bones.append(Bone(node=childnode))
|
||||||
|
|
||||||
|
|
||||||
class Brick:
|
class Brick:
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
self.refID = node.getAttribute('refID')
|
self.refID = node.getAttribute('refID')
|
||||||
@@ -207,14 +220,16 @@ class Brick:
|
|||||||
if childnode.nodeName == 'Part':
|
if childnode.nodeName == 'Part':
|
||||||
self.Parts.append(Part(node=childnode))
|
self.Parts.append(Part(node=childnode))
|
||||||
|
|
||||||
|
|
||||||
class SceneCamera:
|
class SceneCamera:
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
self.refID = node.getAttribute('refID')
|
self.refID = node.getAttribute('refID')
|
||||||
(a, b, c, d, e, f, g, h, i, x, y, z) = map(float, node.getAttribute('transformation').split(','))
|
(a, b, c, d, e, f, g, h, i, x, y, z) = map(float, node.getAttribute('transformation').split(','))
|
||||||
self.matrix = Matrix3D(n11=a,n12=b,n13=c,n14=0,n21=d,n22=e,n23=f,n24=0,n31=g,n32=h,n33=i,n34=0,n41=x,n42=y,n43=z,n44=1)
|
self.matrix = Matrix3D(n11=a, n12=b, n13=c, n14=0, n21=d, n22=e, n23=f, n24=0, n31=g, n32=h, n33=i, n34=0, n41=x, n42=y, n43=z, n44=1)
|
||||||
self.fieldOfView = float(node.getAttribute('fieldOfView'))
|
self.fieldOfView = float(node.getAttribute('fieldOfView'))
|
||||||
self.distance = float(node.getAttribute('distance'))
|
self.distance = float(node.getAttribute('distance'))
|
||||||
|
|
||||||
|
|
||||||
class Scene:
|
class Scene:
|
||||||
def __init__(self, file):
|
def __init__(self, file):
|
||||||
self.Bricks = []
|
self.Bricks = []
|
||||||
@@ -238,10 +253,10 @@ class Scene:
|
|||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'BrickSet':
|
if childnode.nodeName == 'BrickSet':
|
||||||
self.Version = str(childnode.getAttribute('version'))
|
self.Version = str(childnode.getAttribute('version'))
|
||||||
elif node.nodeName == 'Cameras':
|
# elif node.nodeName == 'Cameras':
|
||||||
for childnode in node.childNodes:
|
# for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'Camera':
|
# if childnode.nodeName == 'Camera':
|
||||||
self.Scenecamera.append(SceneCamera(node=childnode))
|
# self.Scenecamera.append(SceneCamera(node=childnode))
|
||||||
elif node.nodeName == 'Bricks':
|
elif node.nodeName == 'Bricks':
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'Brick':
|
if childnode.nodeName == 'Brick':
|
||||||
@@ -262,6 +277,7 @@ class Scene:
|
|||||||
|
|
||||||
# print('Scene "'+ self.Name + '" Brickversion: ' + str(self.Version))
|
# print('Scene "'+ self.Name + '" Brickversion: ' + str(self.Version))
|
||||||
|
|
||||||
|
|
||||||
class GeometryReader:
|
class GeometryReader:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
@@ -282,10 +298,10 @@ class GeometryReader:
|
|||||||
options = self.readInt()
|
options = self.readInt()
|
||||||
|
|
||||||
for i in range(0, self.valueCount):
|
for i in range(0, self.valueCount):
|
||||||
self.positions.append(Point3D(x=self.readFloat(),y= self.readFloat(),z=self.readFloat()))
|
self.positions.append(Point3D(x=self.readFloat(), y=self.readFloat(), z=self.readFloat()))
|
||||||
|
|
||||||
for i in range(0, self.valueCount):
|
for i in range(0, self.valueCount):
|
||||||
self.normals.append(Point3D(x=self.readFloat(),y= self.readFloat(),z=self.readFloat()))
|
self.normals.append(Point3D(x=self.readFloat(), y=self.readFloat(), z=self.readFloat()))
|
||||||
|
|
||||||
if (options & 3) == 3:
|
if (options & 3) == 3:
|
||||||
self.texCount = self.valueCount
|
self.texCount = self.valueCount
|
||||||
@@ -293,7 +309,7 @@ class GeometryReader:
|
|||||||
self.textures.append(Point2D(x=self.readFloat(), y=self.readFloat()))
|
self.textures.append(Point2D(x=self.readFloat(), y=self.readFloat()))
|
||||||
|
|
||||||
for i in range(0, self.faceCount):
|
for i in range(0, self.faceCount):
|
||||||
self.faces.append(Face(a=self.readInt(),b=self.readInt(),c=self.readInt()))
|
self.faces.append(Face(a=self.readInt(), b=self.readInt(), c=self.readInt()))
|
||||||
|
|
||||||
if (options & 48) == 48:
|
if (options & 48) == 48:
|
||||||
num = self.readInt()
|
num = self.readInt()
|
||||||
@@ -311,7 +327,7 @@ class GeometryReader:
|
|||||||
boneoffset = self.readInt() + 4
|
boneoffset = self.readInt() + 4
|
||||||
self.bonemap[i] = self.read_Int(datastart + boneoffset)
|
self.bonemap[i] = self.read_Int(datastart + boneoffset)
|
||||||
|
|
||||||
def read_Int(self,_offset):
|
def read_Int(self, _offset):
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
return int(struct.unpack_from('i', self.data, _offset)[0])
|
return int(struct.unpack_from('i', self.data, _offset)[0])
|
||||||
else:
|
else:
|
||||||
@@ -330,6 +346,7 @@ class GeometryReader:
|
|||||||
self.offset += 4
|
self.offset += 4
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class Geometry:
|
class Geometry:
|
||||||
def __init__(self, designID, database):
|
def __init__(self, designID, database):
|
||||||
self.designID = designID
|
self.designID = designID
|
||||||
@@ -337,21 +354,25 @@ class Geometry:
|
|||||||
self.maxGeoBounding = -1
|
self.maxGeoBounding = -1
|
||||||
self.studsFields2D = []
|
self.studsFields2D = []
|
||||||
|
|
||||||
GeometryLocation = '{0}{1}{2}'.format(GEOMETRIEPATH, designID,'.g')
|
GeometryLocation = '{0}{1}{2}'.format(GEOMETRIEPATH, designID, '.g')
|
||||||
GeometryCount = 0
|
GeometryCount = 0
|
||||||
while str(GeometryLocation) in database.filelist:
|
while str(GeometryLocation) in database.filelist:
|
||||||
self.Parts[GeometryCount] = GeometryReader(data=database.filelist[GeometryLocation].read())
|
self.Parts[GeometryCount] = GeometryReader(data=database.filelist[GeometryLocation].read())
|
||||||
GeometryCount += 1
|
GeometryCount += 1
|
||||||
GeometryLocation = '{0}{1}{2}{3}'.format(GEOMETRIEPATH, designID,'.g',GeometryCount)
|
GeometryLocation = '{0}{1}{2}{3}'.format(GEOMETRIEPATH, designID, '.g', GeometryCount)
|
||||||
|
|
||||||
primitive = Primitive(data = database.filelist[PRIMITIVEPATH + designID + '.xml'].read())
|
primitive = Primitive(data=database.filelist[PRIMITIVEPATH + designID + '.xml'].read())
|
||||||
self.Partname = primitive.Designname
|
self.Partname = primitive.Designname
|
||||||
self.studsFields2D = primitive.Fields2D
|
self.studsFields2D = primitive.Fields2D
|
||||||
try:
|
try:
|
||||||
geoBoundingList = [abs(float(primitive.Bounding['minX']) - float(primitive.Bounding['maxX'])), abs(float(primitive.Bounding['minY']) - float(primitive.Bounding['maxY'])), abs(float(primitive.Bounding['minZ']) - float(primitive.Bounding['maxZ']))]
|
geoBoundingList = [
|
||||||
|
abs(float(primitive.Bounding['minX']) - float(primitive.Bounding['maxX'])),
|
||||||
|
abs(float(primitive.Bounding['minY']) - float(primitive.Bounding['maxY'])),
|
||||||
|
abs(float(primitive.Bounding['minZ']) - float(primitive.Bounding['maxZ']))
|
||||||
|
]
|
||||||
geoBoundingList.sort()
|
geoBoundingList.sort()
|
||||||
self.maxGeoBounding = geoBoundingList[-1]
|
self.maxGeoBounding = geoBoundingList[-1]
|
||||||
except KeyError as e:
|
except KeyError:
|
||||||
# print('\nBounding errror in part {0}: {1}\n'.format(designID, e))
|
# print('\nBounding errror in part {0}: {1}\n'.format(designID, e))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -386,25 +407,33 @@ class Geometry:
|
|||||||
count += self.Parts[part].texCount
|
count += self.Parts[part].texCount
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
class Bone2:
|
class Bone2:
|
||||||
def __init__(self,boneId=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
|
def __init__(self, boneId=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
|
||||||
self.boneId = boneId
|
self.boneId = boneId
|
||||||
rotationMatrix = Matrix3D()
|
rotationMatrix = Matrix3D()
|
||||||
rotationMatrix.rotate(angle = -angle * math.pi / 180.0,axis = Point3D(x=ax,y=ay,z=az))
|
rotationMatrix.rotate(
|
||||||
p = Point3D(x=tx,y=ty,z=tz)
|
angle=(-angle * math.pi / 180.0),
|
||||||
|
axis=Point3D(x=ax, y=ay, z=az)
|
||||||
|
)
|
||||||
|
p = Point3D(x=tx, y=ty, z=tz)
|
||||||
p.transformW(rotationMatrix)
|
p.transformW(rotationMatrix)
|
||||||
rotationMatrix.n41 -= p.x
|
rotationMatrix.n41 -= p.x
|
||||||
rotationMatrix.n42 -= p.y
|
rotationMatrix.n42 -= p.y
|
||||||
rotationMatrix.n43 -= p.z
|
rotationMatrix.n43 -= p.z
|
||||||
self.matrix = rotationMatrix
|
self.matrix = rotationMatrix
|
||||||
|
|
||||||
|
|
||||||
class Field2D:
|
class Field2D:
|
||||||
def __init__(self, type=0, width=0, height=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0, field2DRawData='none'):
|
def __init__(self, type=0, width=0, height=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0, field2DRawData='none'):
|
||||||
self.type = type
|
self.type = type
|
||||||
self.field2DRawData = field2DRawData
|
self.field2DRawData = field2DRawData
|
||||||
rotationMatrix = Matrix3D()
|
rotationMatrix = Matrix3D()
|
||||||
rotationMatrix.rotate(angle = -angle * math.pi / 180.0, axis = Point3D(x=ax,y=ay,z=az))
|
rotationMatrix.rotate(
|
||||||
p = Point3D(x=tx,y=ty,z=tz)
|
angle=(-angle * math.pi / 180.0),
|
||||||
|
axis=Point3D(x=ax, y=ay, z=az)
|
||||||
|
)
|
||||||
|
p = Point3D(x=tx, y=ty, z=tz)
|
||||||
p.transformW(rotationMatrix)
|
p.transformW(rotationMatrix)
|
||||||
rotationMatrix.n41 -= p.x
|
rotationMatrix.n41 -= p.x
|
||||||
rotationMatrix.n42 -= p.y
|
rotationMatrix.n42 -= p.y
|
||||||
@@ -413,7 +442,7 @@ class Field2D:
|
|||||||
self.matrix = rotationMatrix
|
self.matrix = rotationMatrix
|
||||||
self.custom2DField = []
|
self.custom2DField = []
|
||||||
|
|
||||||
#The height and width are always double the number of studs. The contained text is a 2D array that is always height + 1 and width + 1.
|
# The height and width are always double the number of studs. The contained text is a 2D array that is always height + 1 and width + 1.
|
||||||
rows_count = height + 1
|
rows_count = height + 1
|
||||||
cols_count = width + 1
|
cols_count = width + 1
|
||||||
# creation looks reverse
|
# creation looks reverse
|
||||||
@@ -432,18 +461,23 @@ class Field2D:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[type="{0}" transform="{1}" custom2DField="{2}"]'.format(self.type, self.matrix, self.custom2DField)
|
return '[type="{0}" transform="{1}" custom2DField="{2}"]'.format(self.type, self.matrix, self.custom2DField)
|
||||||
|
|
||||||
|
|
||||||
class CollisionBox:
|
class CollisionBox:
|
||||||
def __init__(self, sX=0, sY=0, sZ=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
|
def __init__(self, sX=0, sY=0, sZ=0, angle=0, ax=0, ay=0, az=0, tx=0, ty=0, tz=0):
|
||||||
rotationMatrix = Matrix3D()
|
rotationMatrix = Matrix3D()
|
||||||
rotationMatrix.rotate(angle = -angle * math.pi / 180.0, axis = Point3D(x=ax,y=ay,z=az))
|
rotationMatrix.rotate(
|
||||||
p = Point3D(x=tx,y=ty,z=tz)
|
angle=(-angle * math.pi / 180.0),
|
||||||
|
axis=Point3D(x=ax, y=ay, z=az)
|
||||||
|
)
|
||||||
|
p = Point3D(x=tx, y=ty,
|
||||||
|
z=tz)
|
||||||
p.transformW(rotationMatrix)
|
p.transformW(rotationMatrix)
|
||||||
rotationMatrix.n41 -= p.x
|
rotationMatrix.n41 -= p.x
|
||||||
rotationMatrix.n42 -= p.y
|
rotationMatrix.n42 -= p.y
|
||||||
rotationMatrix.n43 -= p.z
|
rotationMatrix.n43 -= p.z
|
||||||
|
|
||||||
self.matrix = rotationMatrix
|
self.matrix = rotationMatrix
|
||||||
self.corner = Point3D(x=sX,y=sY,z=sZ)
|
self.corner = Point3D(x=sX, y=sY, z=sZ)
|
||||||
self.positions = []
|
self.positions = []
|
||||||
|
|
||||||
self.positions.append(Point3D(x=0, y=0, z=0))
|
self.positions.append(Point3D(x=0, y=0, z=0))
|
||||||
@@ -452,11 +486,14 @@ class CollisionBox:
|
|||||||
self.positions.append(Point3D(x=sX, y=sY, z=0))
|
self.positions.append(Point3D(x=sX, y=sY, z=0))
|
||||||
self.positions.append(Point3D(x=0, y=0, z=sZ))
|
self.positions.append(Point3D(x=0, y=0, z=sZ))
|
||||||
self.positions.append(Point3D(x=0, y=sY, z=sZ))
|
self.positions.append(Point3D(x=0, y=sY, z=sZ))
|
||||||
self.positions.append(Point3D(x=sX ,y=0, z=sZ))
|
self.positions.append(Point3D(x=sX, y=0, z=sZ))
|
||||||
self.positions.append(Point3D(x=sX ,y=sY, z=sZ))
|
self.positions.append(Point3D(x=sX, y=sY, z=sZ))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[0,0,0] [{0},0,0] [0,{1},0] [{0},{1},0] [0,0,{2}] [0,{1},{2}] [{0},0,{2}] [{0},{1},{2}]'.format(self.corner.x, self.corner.y, self.corner.z)
|
return '[0,0,0] [{0},0,0] [0,{1},0] [{0},{1},0] [0,0,{2}] [0,{1},{2}] [{0},0,{2}] [{0},{1},{2}]'.format(
|
||||||
|
self.corner.x, self.corner.y, self.corner.z
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Primitive:
|
class Primitive:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
@@ -475,7 +512,18 @@ class Primitive:
|
|||||||
if node.nodeName == 'Flex':
|
if node.nodeName == 'Flex':
|
||||||
for node in node.childNodes:
|
for node in node.childNodes:
|
||||||
if node.nodeName == 'Bone':
|
if node.nodeName == 'Bone':
|
||||||
self.Bones.append(Bone2(boneId=int(node.getAttribute('boneId')), angle=float(node.getAttribute('angle')), ax=float(node.getAttribute('ax')), ay=float(node.getAttribute('ay')), az=float(node.getAttribute('az')), tx=float(node.getAttribute('tx')), ty=float(node.getAttribute('ty')), tz=float(node.getAttribute('tz'))))
|
self.Bones.append(
|
||||||
|
Bone2(
|
||||||
|
boneId=int(node.getAttribute('boneId')),
|
||||||
|
angle=float(node.getAttribute('angle')),
|
||||||
|
ax=float(node.getAttribute('ax')),
|
||||||
|
ay=float(node.getAttribute('ay')),
|
||||||
|
az=float(node.getAttribute('az')),
|
||||||
|
tx=float(node.getAttribute('tx')),
|
||||||
|
ty=float(node.getAttribute('ty')),
|
||||||
|
tz=float(node.getAttribute('tz'))
|
||||||
|
)
|
||||||
|
)
|
||||||
elif node.nodeName == 'Annotations':
|
elif node.nodeName == 'Annotations':
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'Annotation' and childnode.hasAttribute('designname'):
|
if childnode.nodeName == 'Annotation' and childnode.hasAttribute('designname'):
|
||||||
@@ -483,23 +531,73 @@ class Primitive:
|
|||||||
elif node.nodeName == 'Collision':
|
elif node.nodeName == 'Collision':
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'Box':
|
if childnode.nodeName == 'Box':
|
||||||
self.CollisionBoxes.append(CollisionBox(sX=float(childnode.getAttribute('sX')), sY=float(childnode.getAttribute('sY')), sZ=float(childnode.getAttribute('sZ')), angle=float(childnode.getAttribute('angle')), ax=float(childnode.getAttribute('ax')), ay=float(childnode.getAttribute('ay')), az=float(childnode.getAttribute('az')), tx=float(childnode.getAttribute('tx')), ty=float(childnode.getAttribute('ty')), tz=float(childnode.getAttribute('tz'))))
|
self.CollisionBoxes.append(
|
||||||
|
CollisionBox(
|
||||||
|
sX=float(childnode.getAttribute('sX')),
|
||||||
|
sY=float(childnode.getAttribute('sY')),
|
||||||
|
sZ=float(childnode.getAttribute('sZ')),
|
||||||
|
angle=float(childnode.getAttribute('angle')),
|
||||||
|
ax=float(childnode.getAttribute('ax')),
|
||||||
|
ay=float(childnode.getAttribute('ay')),
|
||||||
|
az=float(childnode.getAttribute('az')),
|
||||||
|
tx=float(childnode.getAttribute('tx')),
|
||||||
|
ty=float(childnode.getAttribute('ty')),
|
||||||
|
tz=float(childnode.getAttribute('tz'))
|
||||||
|
)
|
||||||
|
)
|
||||||
elif node.nodeName == 'PhysicsAttributes':
|
elif node.nodeName == 'PhysicsAttributes':
|
||||||
self.PhysicsAttributes = {"inertiaTensor": node.getAttribute('inertiaTensor'),"centerOfMass": node.getAttribute('centerOfMass'),"mass": node.getAttribute('mass'),"frictionType": node.getAttribute('frictionType')}
|
self.PhysicsAttributes = {
|
||||||
|
"inertiaTensor": node.getAttribute('inertiaTensor'),
|
||||||
|
"centerOfMass": node.getAttribute('centerOfMass'),
|
||||||
|
"mass": node.getAttribute('mass'),
|
||||||
|
"frictionType": node.getAttribute('frictionType')
|
||||||
|
}
|
||||||
elif node.nodeName == 'Bounding':
|
elif node.nodeName == 'Bounding':
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'AABB':
|
if childnode.nodeName == 'AABB':
|
||||||
self.Bounding = {"minX": childnode.getAttribute('minX'), "minY": childnode.getAttribute('minY'), "minZ": childnode.getAttribute('minZ'), "maxX": childnode.getAttribute('maxX'), "maxY": childnode.getAttribute('maxY'), "maxZ": childnode.getAttribute('maxZ')}
|
self.Bounding = {
|
||||||
|
"minX": childnode.getAttribute('minX'),
|
||||||
|
"minY": childnode.getAttribute('minY'),
|
||||||
|
"minZ": childnode.getAttribute('minZ'),
|
||||||
|
"maxX": childnode.getAttribute('maxX'),
|
||||||
|
"maxY": childnode.getAttribute('maxY'),
|
||||||
|
"maxZ": childnode.getAttribute('maxZ')
|
||||||
|
}
|
||||||
elif node.nodeName == 'GeometryBounding':
|
elif node.nodeName == 'GeometryBounding':
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'AABB':
|
if childnode.nodeName == 'AABB':
|
||||||
self.GeometryBounding = {"minX": childnode.getAttribute('minX'), "minY": childnode.getAttribute('minY'), "minZ": childnode.getAttribute('minZ'), "maxX": childnode.getAttribute('maxX'), "maxY": childnode.getAttribute('maxY'), "maxZ": childnode.getAttribute('maxZ')}
|
self.GeometryBounding = {
|
||||||
|
"minX": childnode.getAttribute('minX'),
|
||||||
|
"minY": childnode.getAttribute('minY'),
|
||||||
|
"minZ": childnode.getAttribute('minZ'),
|
||||||
|
"maxX": childnode.getAttribute('maxX'),
|
||||||
|
"maxY": childnode.getAttribute('maxY'),
|
||||||
|
"maxZ": childnode.getAttribute('maxZ')
|
||||||
|
}
|
||||||
elif node.nodeName == 'Connectivity':
|
elif node.nodeName == 'Connectivity':
|
||||||
for childnode in node.childNodes:
|
for childnode in node.childNodes:
|
||||||
if childnode.nodeName == 'Custom2DField':
|
if childnode.nodeName == 'Custom2DField':
|
||||||
self.Fields2D.append(Field2D(type=int(childnode.getAttribute('type')), width=int(childnode.getAttribute('width')), height=int(childnode.getAttribute('height')), angle=float(childnode.getAttribute('angle')), ax=float(childnode.getAttribute('ax')), ay=float(childnode.getAttribute('ay')), az=float(childnode.getAttribute('az')), tx=float(childnode.getAttribute('tx')), ty=float(childnode.getAttribute('ty')), tz=float(childnode.getAttribute('tz')), field2DRawData=str(childnode.firstChild.data)))
|
self.Fields2D.append(
|
||||||
|
Field2D(
|
||||||
|
type=int(childnode.getAttribute('type')),
|
||||||
|
width=int(childnode.getAttribute('width')),
|
||||||
|
height=int(childnode.getAttribute('height')),
|
||||||
|
angle=float(childnode.getAttribute('angle')),
|
||||||
|
ax=float(childnode.getAttribute('ax')),
|
||||||
|
ay=float(childnode.getAttribute('ay')),
|
||||||
|
az=float(childnode.getAttribute('az')),
|
||||||
|
tx=float(childnode.getAttribute('tx')),
|
||||||
|
ty=float(childnode.getAttribute('ty')),
|
||||||
|
tz=float(childnode.getAttribute('tz')),
|
||||||
|
field2DRawData=str(childnode.firstChild.data)
|
||||||
|
)
|
||||||
|
)
|
||||||
elif node.nodeName == 'Decoration':
|
elif node.nodeName == 'Decoration':
|
||||||
self.Decoration = {"faces": node.getAttribute('faces'), "subMaterialRedirectLookupTable": node.getAttribute('subMaterialRedirectLookupTable')}
|
self.Decoration = {
|
||||||
|
"faces": node.getAttribute('faces'),
|
||||||
|
"subMaterialRedirectLookupTable": node.getAttribute('subMaterialRedirectLookupTable')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Materials:
|
class Materials:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
@@ -514,13 +612,14 @@ class Materials:
|
|||||||
b=int(node.getAttribute('Blue')),
|
b=int(node.getAttribute('Blue')),
|
||||||
a=int(node.getAttribute('Alpha')),
|
a=int(node.getAttribute('Alpha')),
|
||||||
mtype=str(node.getAttribute('MaterialType'))
|
mtype=str(node.getAttribute('MaterialType'))
|
||||||
)
|
)
|
||||||
|
|
||||||
def getMaterialbyId(self, mid):
|
def getMaterialbyId(self, mid):
|
||||||
return self.Materials[mid]
|
return self.Materials[mid]
|
||||||
|
|
||||||
|
|
||||||
class Material:
|
class Material:
|
||||||
def __init__(self,id, r, g, b, a, mtype):
|
def __init__(self, id, r, g, b, a, mtype):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = id
|
self.name = id
|
||||||
self.mattype = mtype
|
self.mattype = mtype
|
||||||
@@ -528,18 +627,25 @@ class Material:
|
|||||||
self.g = float(g)
|
self.g = float(g)
|
||||||
self.b = float(b)
|
self.b = float(b)
|
||||||
self.a = float(a)
|
self.a = float(a)
|
||||||
|
|
||||||
def string(self):
|
def string(self):
|
||||||
out = 'Kd {0} {1} {2}\nKa 1.600000 1.600000 1.600000\nKs 0.400000 0.400000 0.400000\nNs 3.482202\nTf 1 1 1\n'.format( self.r / 255, self.g / 255,self.b / 255)
|
out = 'Kd {0} {1} {2}\nKa 1.600000 1.600000 1.600000\nKs 0.400000 0.400000 0.400000\nNs 3.482202\nTf 1 1 1\n'.format(
|
||||||
|
self.r / 255,
|
||||||
|
self.g / 255,
|
||||||
|
self.b / 255
|
||||||
|
)
|
||||||
if self.a < 255:
|
if self.a < 255:
|
||||||
out += 'Ni 1.575\n' + 'd {0}'.format(0.05) + '\n' + 'Tr {0}\n'.format(0.05)
|
out += 'Ni 1.575\n' + 'd {0}'.format(0.05) + '\n' + 'Tr {0}\n'.format(0.05)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
class DBinfo:
|
class DBinfo:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
xml = minidom.parseString(data)
|
xml = minidom.parseString(data)
|
||||||
self.Version = xml.getElementsByTagName('Bricks')[0].attributes['version'].value
|
self.Version = xml.getElementsByTagName('Bricks')[0].attributes['version'].value
|
||||||
# print('DB Version: ' + str(self.Version))
|
# print('DB Version: ' + str(self.Version))
|
||||||
|
|
||||||
|
|
||||||
class DBFolderFile:
|
class DBFolderFile:
|
||||||
def __init__(self, name, handle):
|
def __init__(self, name, handle):
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
@@ -554,6 +660,7 @@ class DBFolderFile:
|
|||||||
finally:
|
finally:
|
||||||
reader.close()
|
reader.close()
|
||||||
|
|
||||||
|
|
||||||
class LIFFile:
|
class LIFFile:
|
||||||
def __init__(self, name, offset, size, handle):
|
def __init__(self, name, offset, size, handle):
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
@@ -565,6 +672,7 @@ class LIFFile:
|
|||||||
self.handle.seek(self.offset, 0)
|
self.handle.seek(self.offset, 0)
|
||||||
return self.handle.read(self.size)
|
return self.handle.read(self.size)
|
||||||
|
|
||||||
|
|
||||||
class DBFolderReader:
|
class DBFolderReader:
|
||||||
def __init__(self, folder):
|
def __init__(self, folder):
|
||||||
self.filelist = {}
|
self.filelist = {}
|
||||||
@@ -574,14 +682,14 @@ class DBFolderReader:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
os.path.isdir(self.location)
|
os.path.isdir(self.location)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.initok = False
|
self.initok = False
|
||||||
# print("db folder read FAIL")
|
# print("db folder read FAIL")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.parse()
|
self.parse()
|
||||||
if self.fileexist(os.path.join(self.location,'Materials.xml')) and self.fileexist(os.path.join(self.location, 'info.xml')):
|
if self.fileexist(os.path.join(self.location, 'Materials.xml')) and self.fileexist(os.path.join(self.location, 'info.xml')):
|
||||||
self.dbinfo = DBinfo(data=self.filelist[os.path.join(self.location,'info.xml')].read())
|
self.dbinfo = DBinfo(data=self.filelist[os.path.join(self.location, 'info.xml')].read())
|
||||||
# print("DB folder OK.")
|
# print("DB folder OK.")
|
||||||
self.initok = True
|
self.initok = True
|
||||||
else:
|
else:
|
||||||
@@ -593,7 +701,6 @@ class DBFolderReader:
|
|||||||
# print(MATERIALNAMESPATH)
|
# print(MATERIALNAMESPATH)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def fileexist(self, filename):
|
def fileexist(self, filename):
|
||||||
return filename in self.filelist
|
return filename in self.filelist
|
||||||
|
|
||||||
@@ -603,6 +710,7 @@ class DBFolderReader:
|
|||||||
entryName = os.path.join(path, name)
|
entryName = os.path.join(path, name)
|
||||||
self.filelist[entryName] = DBFolderFile(name=entryName, handle=entryName)
|
self.filelist[entryName] = DBFolderFile(name=entryName, handle=entryName)
|
||||||
|
|
||||||
|
|
||||||
class LIFReader:
|
class LIFReader:
|
||||||
def __init__(self, file):
|
def __init__(self, file):
|
||||||
self.packedFilesOffset = 84
|
self.packedFilesOffset = 84
|
||||||
@@ -614,7 +722,7 @@ class LIFReader:
|
|||||||
try:
|
try:
|
||||||
self.filehandle = open(self.location, "rb")
|
self.filehandle = open(self.location, "rb")
|
||||||
self.filehandle.seek(0, 0)
|
self.filehandle.seek(0, 0)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.initok = False
|
self.initok = False
|
||||||
# print("Database FAIL")
|
# print("Database FAIL")
|
||||||
return
|
return
|
||||||
@@ -632,7 +740,7 @@ class LIFReader:
|
|||||||
# print("Database FAIL")
|
# print("Database FAIL")
|
||||||
self.initok = False
|
self.initok = False
|
||||||
|
|
||||||
def fileexist(self,filename):
|
def fileexist(self, filename):
|
||||||
return filename in self.filelist
|
return filename in self.filelist
|
||||||
|
|
||||||
def parse(self, prefix='', offset=0):
|
def parse(self, prefix='', offset=0):
|
||||||
@@ -648,7 +756,7 @@ class LIFReader:
|
|||||||
entryType = self.readShort(offset=offset)
|
entryType = self.readShort(offset=offset)
|
||||||
offset += 6
|
offset += 6
|
||||||
|
|
||||||
entryName = '{0}{1}'.format(prefix,'/');
|
entryName = '{0}{1}'.format(prefix, '/')
|
||||||
self.filehandle.seek(offset + 1, 0)
|
self.filehandle.seek(offset + 1, 0)
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
t = ord(self.filehandle.read(1))
|
t = ord(self.filehandle.read(1))
|
||||||
@@ -656,7 +764,7 @@ class LIFReader:
|
|||||||
t = int.from_bytes(self.filehandle.read(1), byteorder='big')
|
t = int.from_bytes(self.filehandle.read(1), byteorder='big')
|
||||||
|
|
||||||
while not t == 0:
|
while not t == 0:
|
||||||
entryName ='{0}{1}'.format(entryName,chr(t))
|
entryName = '{0}{1}'.format(entryName, chr(t))
|
||||||
self.filehandle.seek(1, 1)
|
self.filehandle.seek(1, 1)
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
t = ord(self.filehandle.read(1))
|
t = ord(self.filehandle.read(1))
|
||||||
@@ -692,36 +800,37 @@ class LIFReader:
|
|||||||
else:
|
else:
|
||||||
return int.from_bytes(self.filehandle.read(2), byteorder='big')
|
return int.from_bytes(self.filehandle.read(2), byteorder='big')
|
||||||
|
|
||||||
|
|
||||||
class Converter:
|
class Converter:
|
||||||
def LoadDBFolder(self, dbfolderlocation):
|
def LoadDBFolder(self, dbfolderlocation):
|
||||||
self.database = DBFolderReader(folder=dbfolderlocation)
|
self.database = DBFolderReader(folder=dbfolderlocation)
|
||||||
if self.database.initok and self.database.fileexist(os.path.join(dbfolderlocation,'Materials.xml')):
|
if self.database.initok and self.database.fileexist(os.path.join(dbfolderlocation, 'Materials.xml')):
|
||||||
self.allMaterials = Materials(data=self.database.filelist[os.path.join(dbfolderlocation,'Materials.xml')].read());
|
self.allMaterials = Materials(data=self.database.filelist[os.path.join(dbfolderlocation, 'Materials.xml')].read())
|
||||||
|
|
||||||
def LoadDatabase(self,databaselocation):
|
def LoadDatabase(self, databaselocation):
|
||||||
self.database = LIFReader(file=databaselocation)
|
self.database = LIFReader(file=databaselocation)
|
||||||
|
|
||||||
if self.database.initok and self.database.fileexist('/Materials.xml'):
|
if self.database.initok and self.database.fileexist('/Materials.xml'):
|
||||||
self.allMaterials = Materials(data=self.database.filelist['/Materials.xml'].read());
|
self.allMaterials = Materials(data=self.database.filelist['/Materials.xml'].read())
|
||||||
|
|
||||||
def LoadScene(self,filename):
|
def LoadScene(self, filename):
|
||||||
if self.database.initok:
|
if self.database.initok:
|
||||||
self.scene = Scene(file=filename)
|
self.scene = Scene(file=filename)
|
||||||
|
|
||||||
def Export(self,filename):
|
def Export(self, filename):
|
||||||
invert = Matrix3D()
|
invert = Matrix3D()
|
||||||
#invert.n33 = -1 #uncomment to invert the Z-Axis
|
# invert.n33 = -1 #uncomment to invert the Z-Axis
|
||||||
|
|
||||||
indexOffset = 1
|
indexOffset = 1
|
||||||
textOffset = 1
|
textOffset = 1
|
||||||
usedmaterials = []
|
usedmaterials = []
|
||||||
geometriecache = {}
|
geometriecache = {}
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
out = open(filename + ".obj.tmp", "w+")
|
out = open(filename + ".obj.tmp", "w+")
|
||||||
|
out.truncate(0)
|
||||||
out.write("mtllib " + filename + ".mtl" + '\n\n')
|
out.write("mtllib " + filename + ".mtl" + '\n\n')
|
||||||
outtext = open(filename + ".mtl.tmp", "w+")
|
outtext = open(filename + ".mtl.tmp", "w+")
|
||||||
|
outtext.truncate(0)
|
||||||
|
|
||||||
total = len(self.scene.Bricks)
|
total = len(self.scene.Bricks)
|
||||||
current = 0
|
current = 0
|
||||||
@@ -733,12 +842,12 @@ class Converter:
|
|||||||
|
|
||||||
if pa.designID not in geometriecache:
|
if pa.designID not in geometriecache:
|
||||||
geo = Geometry(designID=pa.designID, database=self.database)
|
geo = Geometry(designID=pa.designID, database=self.database)
|
||||||
progress(current ,total , "(" + geo.designID + ") " + geo.Partname, ' ')
|
progress(current, total, "(" + geo.designID + ") " + geo.Partname, ' ')
|
||||||
geometriecache[pa.designID] = geo
|
geometriecache[pa.designID] = geo
|
||||||
else:
|
else:
|
||||||
geo = geometriecache[pa.designID]
|
geo = geometriecache[pa.designID]
|
||||||
|
|
||||||
progress(current ,total , "(" + geo.designID + ") " + geo.Partname ,'-')
|
progress(current, total, "(" + geo.designID + ") " + geo.Partname, '-')
|
||||||
|
|
||||||
out.write("o\n")
|
out.write("o\n")
|
||||||
|
|
||||||
@@ -750,11 +859,11 @@ class Converter:
|
|||||||
# positions
|
# positions
|
||||||
for j, p in enumerate(geo.Parts[part].outpositions):
|
for j, p in enumerate(geo.Parts[part].outpositions):
|
||||||
if (geo.Parts[part].bonemap[j] == i):
|
if (geo.Parts[part].bonemap[j] == i):
|
||||||
p.transform( invert * b.matrix)
|
p.transform(invert * b.matrix)
|
||||||
# normals
|
# normals
|
||||||
for k, n in enumerate(geo.Parts[part].outnormals):
|
for k, n in enumerate(geo.Parts[part].outnormals):
|
||||||
if (geo.Parts[part].bonemap[k] == i):
|
if (geo.Parts[part].bonemap[k] == i):
|
||||||
n.transformW( invert * b.matrix)
|
n.transformW(invert * b.matrix)
|
||||||
|
|
||||||
for point in geo.Parts[part].outpositions:
|
for point in geo.Parts[part].outpositions:
|
||||||
out.write(point.string("v"))
|
out.write(point.string("v"))
|
||||||
@@ -770,13 +879,12 @@ class Converter:
|
|||||||
last_color = 0
|
last_color = 0
|
||||||
for part in geo.Parts:
|
for part in geo.Parts:
|
||||||
|
|
||||||
|
# try catch here for possible problems in materials assignment of various g, g1, g2, .. files in lxf file
|
||||||
#try catch here for possible problems in materials assignment of various g, g1, g2, .. files in lxf file
|
|
||||||
try:
|
try:
|
||||||
materialCurrentPart = pa.materials[part]
|
materialCurrentPart = pa.materials[part]
|
||||||
last_color = pa.materials[part]
|
last_color = pa.materials[part]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# print('WARNING: {0}.g{1} has NO material assignment in lxf. Replaced with color {2}. Fix {0}.xml faces values.'.format(pa.designID, part, last_color))
|
|
||||||
materialCurrentPart = last_color
|
materialCurrentPart = last_color
|
||||||
|
|
||||||
lddmat = self.allMaterials.getMaterialbyId(materialCurrentPart)
|
lddmat = self.allMaterials.getMaterialbyId(materialCurrentPart)
|
||||||
@@ -784,7 +892,7 @@ class Converter:
|
|||||||
|
|
||||||
deco = '0'
|
deco = '0'
|
||||||
if hasattr(pa, 'decoration') and len(geo.Parts[part].textures) > 0:
|
if hasattr(pa, 'decoration') and len(geo.Parts[part].textures) > 0:
|
||||||
#if decoCount <= len(pa.decoration):
|
# if decoCount <= len(pa.decoration):
|
||||||
if decoCount < len(pa.decoration):
|
if decoCount < len(pa.decoration):
|
||||||
deco = pa.decoration[decoCount]
|
deco = pa.decoration[decoCount]
|
||||||
decoCount += 1
|
decoCount += 1
|
||||||
@@ -799,7 +907,7 @@ class Converter:
|
|||||||
f.write(self.database.filelist[decofilename].read())
|
f.write(self.database.filelist[decofilename].read())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
if not matname in usedmaterials:
|
if matname not in usedmaterials:
|
||||||
usedmaterials.append(matname)
|
usedmaterials.append(matname)
|
||||||
outtext.write("newmtl " + matname + '\n')
|
outtext.write("newmtl " + matname + '\n')
|
||||||
outtext.write(lddmat.string())
|
outtext.write(lddmat.string())
|
||||||
@@ -809,9 +917,9 @@ class Converter:
|
|||||||
out.write("usemtl " + matname + '\n')
|
out.write("usemtl " + matname + '\n')
|
||||||
for face in geo.Parts[part].faces:
|
for face in geo.Parts[part].faces:
|
||||||
if len(geo.Parts[part].textures) > 0:
|
if len(geo.Parts[part].textures) > 0:
|
||||||
out.write(face.string("f",indexOffset,textOffset))
|
out.write(face.string("f", indexOffset, textOffset))
|
||||||
else:
|
else:
|
||||||
out.write(face.string("f",indexOffset))
|
out.write(face.string("f", indexOffset))
|
||||||
|
|
||||||
indexOffset += len(geo.Parts[part].outpositions)
|
indexOffset += len(geo.Parts[part].outpositions)
|
||||||
textOffset += len(geo.Parts[part].textures)
|
textOffset += len(geo.Parts[part].textures)
|
||||||
@@ -823,55 +931,20 @@ class Converter:
|
|||||||
sys.stdout.write('%s\r' % (' '))
|
sys.stdout.write('%s\r' % (' '))
|
||||||
# print("--- %s seconds ---" % (time.time() - start_time))
|
# print("--- %s seconds ---" % (time.time() - start_time))
|
||||||
|
|
||||||
def setDBFolderVars(dbfolderlocation):
|
|
||||||
|
def setDBFolderVars(dbfolderlocation, lod):
|
||||||
global PRIMITIVEPATH
|
global PRIMITIVEPATH
|
||||||
global GEOMETRIEPATH
|
global GEOMETRIEPATH
|
||||||
global DECORATIONPATH
|
global DECORATIONPATH
|
||||||
global MATERIALNAMESPATH
|
global MATERIALNAMESPATH
|
||||||
PRIMITIVEPATH = os.path.join(dbfolderlocation, 'Primitives', '')
|
PRIMITIVEPATH = os.path.join(dbfolderlocation, 'Primitives', '')
|
||||||
GEOMETRIEPATH = os.path.join(dbfolderlocation, 'brickprimitives', 'lod0', '')
|
GEOMETRIEPATH = os.path.join(dbfolderlocation, 'brickprimitives', f'lod{lod}', '')
|
||||||
DECORATIONPATH = os.path.join(dbfolderlocation, 'Decorations', '')
|
DECORATIONPATH = os.path.join(dbfolderlocation, 'Decorations', '')
|
||||||
MATERIALNAMESPATH = os.path.join(dbfolderlocation, 'MaterialNames', '')
|
MATERIALNAMESPATH = os.path.join(dbfolderlocation, 'MaterialNames', '')
|
||||||
# print(MATERIALNAMESPATH)
|
# print(MATERIALNAMESPATH)
|
||||||
|
|
||||||
def FindDatabase():
|
|
||||||
lddliftree = os.getenv('LDDLIFTREE')
|
|
||||||
if lddliftree is not None:
|
|
||||||
if os.path.isdir(str(lddliftree)): #LDDLIFTREE points to folder
|
|
||||||
return str(lddliftree)
|
|
||||||
elif os.path.isfile(str(lddliftree)): #LDDLIFTREE points to file (should be db.lif)
|
|
||||||
return str(lddliftree)
|
|
||||||
|
|
||||||
else: #Env variable LDDLIFTREE not set. Check for default locations per different platform.
|
def progress(count, total, status='', suffix=''):
|
||||||
if platform.system() == 'Darwin':
|
|
||||||
if os.path.isdir(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db'))):
|
|
||||||
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db'))
|
|
||||||
elif os.path.isfile(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db.lif'))):
|
|
||||||
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'Library','Application Support','LEGO Company','LEGO Digital Designer','db.lif'))
|
|
||||||
else:
|
|
||||||
# print("no LDD database found please install LEGO-Digital-Designer")
|
|
||||||
os._exit()
|
|
||||||
elif platform.system() == 'Windows':
|
|
||||||
if os.path.isdir(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db'))):
|
|
||||||
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db'))
|
|
||||||
elif os.path.isfile(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db.lif'))):
|
|
||||||
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'AppData','Roaming','LEGO Company','LEGO Digital Designer','db.lif'))
|
|
||||||
else:
|
|
||||||
# print("no LDD database found please install LEGO-Digital-Designer")
|
|
||||||
os._exit()
|
|
||||||
elif platform.system() == 'Linux':
|
|
||||||
if os.path.isdir(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db'))):
|
|
||||||
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db'))
|
|
||||||
elif os.path.isfile(str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db.lif'))):
|
|
||||||
return str(os.path.join(str(os.getenv('USERPROFILE') or os.getenv('HOME')),'.wine','drive_c','users',os.getenv('USER'),'Application Data','LEGO Company','LEGO Digital Designer','db.lif'))
|
|
||||||
else:
|
|
||||||
# print("no LDD database found please install LEGO-Digital-Designer")
|
|
||||||
os._exit()
|
|
||||||
else:
|
|
||||||
# print('Your OS {0} is not supported yet.'.format(platform.system()))
|
|
||||||
os._exit()
|
|
||||||
|
|
||||||
def progress(count, total, status='', suffix = ''):
|
|
||||||
bar_len = 40
|
bar_len = 40
|
||||||
filled_len = int(round(bar_len * count / float(total)))
|
filled_len = int(round(bar_len * count / float(total)))
|
||||||
percents = round(100.0 * count / float(total), 1)
|
percents = round(100.0 * count / float(total), 1)
|
||||||
@@ -880,7 +953,8 @@ def progress(count, total, status='', suffix = ''):
|
|||||||
sys.stdout.write('Progress: [%s] %s%s %s %s\r' % (bar, percents, '%', suffix, status))
|
sys.stdout.write('Progress: [%s] %s%s %s %s\r' % (bar, percents, '%', suffix, status))
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
def main(lxf_filename, obj_filename):
|
|
||||||
|
def main(lxf_filename, obj_filename, lod="2"):
|
||||||
# print("- - - pylddlib - - -")
|
# print("- - - pylddlib - - -")
|
||||||
# print(" _ ")
|
# print(" _ ")
|
||||||
# print(" [_]")
|
# print(" [_]")
|
||||||
@@ -890,13 +964,15 @@ def main(lxf_filename, obj_filename):
|
|||||||
# print(" [=|=]")
|
# print(" [=|=]")
|
||||||
# print("")
|
# print("")
|
||||||
# print("- - - - - - - - - - - -")
|
# print("- - - - - - - - - - - -")
|
||||||
|
global GEOMETRIEPATH
|
||||||
|
GEOMETRIEPATH = GEOMETRIEPATH + f"LOD{lod}/"
|
||||||
converter = Converter()
|
converter = Converter()
|
||||||
# print("Found DB folder. Will use this instead of db.lif!")
|
# print("Found DB folder. Will use this instead of db.lif!")
|
||||||
setDBFolderVars(dbfolderlocation = "app/luclient/res/")
|
setDBFolderVars(dbfolderlocation="app/luclient/res/", lod=lod)
|
||||||
converter.LoadDBFolder(dbfolderlocation = "app/luclient/res/")
|
converter.LoadDBFolder(dbfolderlocation="app/luclient/res/")
|
||||||
converter.LoadScene(filename=lxf_filename)
|
converter.LoadScene(filename=lxf_filename)
|
||||||
converter.Export(filename=obj_filename)
|
converter.Export(filename=obj_filename)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
371
app/reports.py
371
app/reports.py
@@ -1,58 +1,349 @@
|
|||||||
from flask import render_template, Blueprint, redirect, url_for, request, abort, flash, request
|
from flask import render_template, Blueprint, current_app
|
||||||
from flask_user import login_required, current_user
|
from flask_user import login_required
|
||||||
from app.models import db, CharacterInfo, Account, CharacterXML, ItemReports
|
from app.models import CharacterInfo, Account, CharacterXML, Reports
|
||||||
|
from app.luclient import get_lot_name
|
||||||
from app import gm_level, scheduler
|
from app import gm_level, scheduler
|
||||||
import datetime, xmltodict
|
from sqlalchemy.orm import load_only
|
||||||
|
import datetime
|
||||||
|
import xmltodict
|
||||||
|
import random
|
||||||
|
|
||||||
reports_blueprint = Blueprint('reports', __name__)
|
reports_blueprint = Blueprint('reports', __name__)
|
||||||
|
|
||||||
|
|
||||||
@reports_blueprint.route('/', methods=['GET', 'POST'])
|
@reports_blueprint.route('/', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def index():
|
def index():
|
||||||
items = ItemReports.query.distinct(ItemReports.date).group_by(ItemReports.date).all()
|
reports_items = Reports.query.distinct(
|
||||||
|
Reports.date
|
||||||
|
).filter(
|
||||||
|
Reports.report_type == "items"
|
||||||
|
).group_by(Reports.date).options(load_only(Reports.date)).all()
|
||||||
|
|
||||||
|
reports_currency = Reports.query.distinct(
|
||||||
|
Reports.date
|
||||||
|
).filter(
|
||||||
|
Reports.report_type == "currency"
|
||||||
|
).group_by(Reports.date).options(load_only(Reports.date)).all()
|
||||||
|
|
||||||
|
reports_uscore = Reports.query.distinct(
|
||||||
|
Reports.date
|
||||||
|
).filter(
|
||||||
|
Reports.report_type == "uscore"
|
||||||
|
).group_by(Reports.date).options(load_only(Reports.date)).all()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'reports/index.html.j2',
|
||||||
|
reports_items=reports_items,
|
||||||
|
reports_currency=reports_currency,
|
||||||
|
reports_uscore=reports_uscore,
|
||||||
|
)
|
||||||
|
|
||||||
return render_template('reports/index.html.j2', items=items)
|
|
||||||
|
|
||||||
@reports_blueprint.route('/items/by_date/<date>', methods=['GET', 'POST'])
|
@reports_blueprint.route('/items/by_date/<date>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@gm_level(3)
|
@gm_level(3)
|
||||||
def items_by_date(date):
|
def items_by_date(date):
|
||||||
items = ItemReports.query.filter(ItemReports.date==date).order_by(ItemReports.count.desc()).all()
|
data = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "items").first().data
|
||||||
return render_template('reports/items/by_date.html.j2', items=items, date=date)
|
return render_template('reports/items/by_date.html.j2', data=data, date=date)
|
||||||
|
|
||||||
|
|
||||||
@scheduler.task("cron", id="gen_item_report", hour=3)
|
@reports_blueprint.route('/items/graph/<start>/<end>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def items_graph(start, end):
|
||||||
|
start_date = datetime.date.today() - datetime.timedelta(weeks=int(start))
|
||||||
|
end_date = datetime.date.today() - datetime.timedelta(weeks=int(end))
|
||||||
|
entries = Reports.query.filter(
|
||||||
|
Reports.report_type == "items"
|
||||||
|
).filter(Reports.date.between(start_date, end_date)).all()
|
||||||
|
# transform data for chartjs
|
||||||
|
labels = []
|
||||||
|
items = dict()
|
||||||
|
datasets = []
|
||||||
|
# get stuff ready
|
||||||
|
for entry in entries:
|
||||||
|
labels.append(entry.date.strftime("%m/%d/%Y"))
|
||||||
|
for key in entry.data:
|
||||||
|
items[key] = get_lot_name(key)
|
||||||
|
# make it
|
||||||
|
for key, value in items.items():
|
||||||
|
data = []
|
||||||
|
for entry in entries:
|
||||||
|
if key in entry.data.keys():
|
||||||
|
data.append(entry.data[key])
|
||||||
|
else:
|
||||||
|
data.append(0)
|
||||||
|
color = "#" + value.encode("utf-8").hex()[1:7]
|
||||||
|
if max(data) > 10:
|
||||||
|
datasets.append({
|
||||||
|
"label": value,
|
||||||
|
"data": data,
|
||||||
|
"backgroundColor": color,
|
||||||
|
"borderColor": color
|
||||||
|
})
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'reports/graph.html.j2',
|
||||||
|
labels=labels,
|
||||||
|
datasets=datasets,
|
||||||
|
name="Item",
|
||||||
|
start=start,
|
||||||
|
end=end,
|
||||||
|
data_type="items",
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@reports_blueprint.route('/currency/by_date/<date>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def currency_by_date(date):
|
||||||
|
data = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "currency").first().data
|
||||||
|
return render_template('reports/currency/by_date.html.j2', data=data, date=date)
|
||||||
|
|
||||||
|
|
||||||
|
@reports_blueprint.route('/currency/graph/<start>/<end>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def currency_graph(start, end):
|
||||||
|
start_date = datetime.date.today() - datetime.timedelta(weeks=int(start))
|
||||||
|
end_date = datetime.date.today() - datetime.timedelta(weeks=int(end))
|
||||||
|
entries = Reports.query.filter(
|
||||||
|
Reports.report_type == "currency"
|
||||||
|
).filter(Reports.date.between(start_date, end_date)).all()
|
||||||
|
characters = CharacterInfo.query.options(load_only(CharacterInfo.name)).all()
|
||||||
|
labels = []
|
||||||
|
datasets = []
|
||||||
|
# get stuff ready
|
||||||
|
for entry in entries:
|
||||||
|
labels.append(entry.date.strftime("%m/%d/%Y"))
|
||||||
|
for character in characters:
|
||||||
|
data = []
|
||||||
|
for entry in entries:
|
||||||
|
if character.name in entry.data.keys():
|
||||||
|
data.append(entry.data[character.name])
|
||||||
|
else:
|
||||||
|
data.append(0)
|
||||||
|
color = "#" + character.name.encode("utf-8").hex()[1:7]
|
||||||
|
if max(data) > 10000:
|
||||||
|
datasets.append({
|
||||||
|
"label": character.name,
|
||||||
|
"data": data,
|
||||||
|
"backgroundColor": color,
|
||||||
|
"borderColor": color
|
||||||
|
})
|
||||||
|
return render_template(
|
||||||
|
'reports/graph.html.j2',
|
||||||
|
labels=labels,
|
||||||
|
datasets=datasets,
|
||||||
|
name="Currency",
|
||||||
|
start=start,
|
||||||
|
end=end,
|
||||||
|
data_type="currency",
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@reports_blueprint.route('/uscore/by_date/<date>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def uscore_by_date(date):
|
||||||
|
data = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "uscore").first().data
|
||||||
|
return render_template('reports/uscore/by_date.html.j2', data=data, date=date)
|
||||||
|
|
||||||
|
|
||||||
|
@reports_blueprint.route('/uscore/graph/<start>/<end>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@gm_level(3)
|
||||||
|
def uscore_graph(start, end):
|
||||||
|
start_date = datetime.date.today() - datetime.timedelta(weeks=int(start))
|
||||||
|
end_date = datetime.date.today() - datetime.timedelta(weeks=int(end))
|
||||||
|
entries = Reports.query.filter(
|
||||||
|
Reports.report_type == "uscore"
|
||||||
|
).filter(Reports.date.between(start_date, end_date)).all()
|
||||||
|
characters = CharacterInfo.query.options(load_only(CharacterInfo.name)).all()
|
||||||
|
labels = []
|
||||||
|
datasets = []
|
||||||
|
# get stuff ready
|
||||||
|
for entry in entries:
|
||||||
|
labels.append(entry.date.strftime("%m/%d/%Y"))
|
||||||
|
for character in characters:
|
||||||
|
data = []
|
||||||
|
for entry in entries:
|
||||||
|
if character.name in entry.data.keys():
|
||||||
|
data.append(entry.data[character.name])
|
||||||
|
else:
|
||||||
|
data.append(0)
|
||||||
|
color = "#" + character.name.encode("utf-8").hex()[1:7]
|
||||||
|
if max(data) > 1000:
|
||||||
|
datasets.append({
|
||||||
|
"label": character.name,
|
||||||
|
"data": data,
|
||||||
|
"backgroundColor": color,
|
||||||
|
"borderColor": color
|
||||||
|
})
|
||||||
|
return render_template(
|
||||||
|
'reports/graph.html.j2',
|
||||||
|
labels=labels,
|
||||||
|
datasets=datasets,
|
||||||
|
name="U-Score",
|
||||||
|
start=start,
|
||||||
|
end=end,
|
||||||
|
data_type="uscore",
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.task("cron", id="gen_item_report", hour=23, timezone="UTC")
|
||||||
def gen_item_report():
|
def gen_item_report():
|
||||||
char_xmls = CharacterXML.query.join(
|
with scheduler.app.app_context():
|
||||||
CharacterInfo,
|
try:
|
||||||
CharacterInfo.id==CharacterXML.id
|
current_app.logger.info("Start Item Report Generation")
|
||||||
).join(
|
|
||||||
Account,
|
|
||||||
CharacterInfo.account_id==Account.id
|
|
||||||
).filter(Account.gm_level < 3).all()
|
|
||||||
date = datetime.date.today().strftime('%Y-%m-%d')
|
|
||||||
for char_xml in char_xmls:
|
|
||||||
character_json = xmltodict.parse(
|
|
||||||
char_xml.xml_data,
|
|
||||||
attr_prefix="attr_"
|
|
||||||
)
|
|
||||||
for inv in character_json["obj"]["inv"]["items"]["in"]:
|
|
||||||
if "i" in inv.keys() and type(inv["i"]) == list and (int(inv["attr_t"])==0 or int(inv["attr_t"])==0):
|
|
||||||
for item in inv["i"]:
|
|
||||||
entry = ItemReports.query.filter(
|
|
||||||
ItemReports.item == int(item["attr_l"]) and \
|
|
||||||
ItemReports.date == date
|
|
||||||
).first()
|
|
||||||
if entry:
|
|
||||||
entry.count = entry.count + int(item["attr_c"])
|
|
||||||
entry.save()
|
|
||||||
else:
|
|
||||||
new_entry = ItemReports(
|
|
||||||
item=int(item["attr_l"]),
|
|
||||||
count=int(item["attr_c"]),
|
|
||||||
date=date
|
|
||||||
)
|
|
||||||
new_entry.save()
|
|
||||||
|
|
||||||
return "Done"
|
date = datetime.date.today().strftime('%Y-%m-%d')
|
||||||
|
report = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "items").first()
|
||||||
|
|
||||||
|
# Only one report per day
|
||||||
|
if report is not None:
|
||||||
|
current_app.logger.info(f"Item Report Already Generated for {date}")
|
||||||
|
return
|
||||||
|
|
||||||
|
char_xmls = CharacterXML.query.join(
|
||||||
|
CharacterInfo,
|
||||||
|
CharacterInfo.id == CharacterXML.id
|
||||||
|
).join(
|
||||||
|
Account,
|
||||||
|
CharacterInfo.account_id == Account.id
|
||||||
|
).filter(Account.gm_level < 3).all()
|
||||||
|
|
||||||
|
report_data = {}
|
||||||
|
|
||||||
|
for char_xml in char_xmls:
|
||||||
|
try:
|
||||||
|
character_json = xmltodict.parse(
|
||||||
|
char_xml.xml_data,
|
||||||
|
attr_prefix="attr_"
|
||||||
|
)
|
||||||
|
for inv in character_json["obj"]["inv"]["items"]["in"]:
|
||||||
|
if "i" in inv.keys() and type(inv["i"]) == list and (int(inv["attr_t"]) == 0 or int(inv["attr_t"]) == 1):
|
||||||
|
for item in inv["i"]:
|
||||||
|
if item["attr_l"] in report_data:
|
||||||
|
report_data[item["attr_l"]] = report_data[item["attr_l"]] + int(item["attr_c"])
|
||||||
|
else:
|
||||||
|
report_data[item["attr_l"]] = int(item["attr_c"])
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"REPORT::ITEMS - ERROR PARSING CHARACTER {char_xml.id}")
|
||||||
|
current_app.logger.error(f"REPORT::ITEMS - {e}")
|
||||||
|
|
||||||
|
new_report = Reports(
|
||||||
|
data=report_data,
|
||||||
|
report_type="items",
|
||||||
|
date=date
|
||||||
|
)
|
||||||
|
|
||||||
|
new_report.save()
|
||||||
|
current_app.logger.info(f"Generated Item Report for {date}")
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.critical(f"REPORT::ITEMS - {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.task("cron", id="gen_currency_report", hour=23, timezone="UTC")
|
||||||
|
def gen_currency_report():
|
||||||
|
with scheduler.app.app_context():
|
||||||
|
try:
|
||||||
|
current_app.logger.info("Start Currency Report Generation")
|
||||||
|
|
||||||
|
date = datetime.date.today().strftime('%Y-%m-%d')
|
||||||
|
report = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "currency").first()
|
||||||
|
|
||||||
|
# Only one report per day
|
||||||
|
if report is not None:
|
||||||
|
current_app.logger.info(f"Currency Report Already Generated for {date}")
|
||||||
|
return
|
||||||
|
|
||||||
|
characters = CharacterXML.query.join(
|
||||||
|
CharacterInfo,
|
||||||
|
CharacterInfo.id == CharacterXML.id
|
||||||
|
).join(
|
||||||
|
Account,
|
||||||
|
CharacterInfo.account_id == Account.id
|
||||||
|
).filter(Account.gm_level < 3).all()
|
||||||
|
|
||||||
|
report_data = {}
|
||||||
|
|
||||||
|
for character in characters:
|
||||||
|
try:
|
||||||
|
character_json = xmltodict.parse(
|
||||||
|
character.xml_data,
|
||||||
|
attr_prefix="attr_"
|
||||||
|
)
|
||||||
|
report_data[CharacterInfo.query.filter(CharacterInfo.id == character.id).first().name] = int(character_json["obj"]["char"]["attr_cc"])
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"REPORT::CURRENCY - ERROR PARSING CHARACTER {character.id}")
|
||||||
|
current_app.logger.error(f"REPORT::CURRENCY - {e}")
|
||||||
|
|
||||||
|
new_report = Reports(
|
||||||
|
data=report_data,
|
||||||
|
report_type="currency",
|
||||||
|
date=date
|
||||||
|
)
|
||||||
|
|
||||||
|
new_report.save()
|
||||||
|
current_app.logger.info(f"Generated Currency Report for {date}")
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.critical(f"REPORT::CURRENCY - {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.task("cron", id="gen_uscore_report", hour=23, timezone="UTC")
|
||||||
|
def gen_uscore_report():
|
||||||
|
with scheduler.app.app_context():
|
||||||
|
try:
|
||||||
|
current_app.logger.info("Start U-Score Report Generation")
|
||||||
|
|
||||||
|
date = datetime.date.today().strftime('%Y-%m-%d')
|
||||||
|
report = Reports.query.filter(Reports.date == date).filter(Reports.report_type == "uscore").first()
|
||||||
|
|
||||||
|
# Only one report per day
|
||||||
|
if report is not None:
|
||||||
|
current_app.logger.info(f"U-Score Report Already Generated for {date}")
|
||||||
|
return
|
||||||
|
|
||||||
|
characters = CharacterXML.query.join(
|
||||||
|
CharacterInfo,
|
||||||
|
CharacterInfo.id == CharacterXML.id
|
||||||
|
).join(
|
||||||
|
Account,
|
||||||
|
CharacterInfo.account_id == Account.id
|
||||||
|
).filter(Account.gm_level < 3).all()
|
||||||
|
|
||||||
|
report_data = {}
|
||||||
|
|
||||||
|
for character in characters:
|
||||||
|
try:
|
||||||
|
character_json = xmltodict.parse(
|
||||||
|
character.xml_data,
|
||||||
|
attr_prefix="attr_"
|
||||||
|
)
|
||||||
|
report_data[CharacterInfo.query.filter(CharacterInfo.id == character.id).first().name] = int(character_json["obj"]["char"]["attr_ls"])
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"REPORT::U-SCORE - ERROR PARSING CHARACTER {character.id}")
|
||||||
|
current_app.logger.error(f"REPORT::U-SCORE - {e}")
|
||||||
|
|
||||||
|
new_report = Reports(
|
||||||
|
data=report_data,
|
||||||
|
report_type="uscore",
|
||||||
|
date=date
|
||||||
|
)
|
||||||
|
|
||||||
|
new_report.save()
|
||||||
|
current_app.logger.info(f"Generated U-Score Report for {date}")
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.critical(f"REPORT::U-SCORE - {e}")
|
||||||
|
return
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
from flask_marshmallow import Marshmallow
|
from flask_marshmallow import Marshmallow
|
||||||
from app.models import *
|
from app.models import (
|
||||||
|
PlayKey,
|
||||||
|
PetNames,
|
||||||
|
Mail,
|
||||||
|
UGC,
|
||||||
|
PropertyContent,
|
||||||
|
Property,
|
||||||
|
CharacterXML,
|
||||||
|
CharacterInfo,
|
||||||
|
Account,
|
||||||
|
AccountInvitation,
|
||||||
|
ActivityLog,
|
||||||
|
CommandLog
|
||||||
|
)
|
||||||
ma = Marshmallow()
|
ma = Marshmallow()
|
||||||
|
|
||||||
|
|
||||||
class PlayKeySchema(ma.SQLAlchemyAutoSchema):
|
class PlayKeySchema(ma.SQLAlchemyAutoSchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PlayKey
|
model = PlayKey
|
||||||
@@ -44,7 +58,6 @@ class PropertyContentSchema(ma.SQLAlchemyAutoSchema):
|
|||||||
ugc = ma.Nested(UGCSchema)
|
ugc = ma.Nested(UGCSchema)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PropertySchema(ma.SQLAlchemyAutoSchema):
|
class PropertySchema(ma.SQLAlchemyAutoSchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Property
|
model = Property
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
APP_NAME = "Nexus Dashboard"
|
APP_NAME = "Nexus Dashboard"
|
||||||
APP_SYSTEM_ERROR_SUBJECT_LINE = APP_NAME + " system error"
|
APP_SYSTEM_ERROR_SUBJECT_LINE = APP_NAME + " system error"
|
||||||
|
|
||||||
|
APP_SECRET_KEY = ""
|
||||||
|
APP_DATABASE_URI = "mysql+pymysql://<username>:<password>@<host>:<port>/<database>"
|
||||||
|
|
||||||
|
# Send Analytics for Developers to better fix issues
|
||||||
|
ALLOW_ANALYTICS = False
|
||||||
|
|
||||||
# Flask settings
|
# Flask settings
|
||||||
CSRF_ENABLED = True
|
CSRF_ENABLED = True
|
||||||
|
|
||||||
@@ -14,7 +20,7 @@ WTF_CSRF_TIME_LIMIT = 86400
|
|||||||
# Flask-User settings
|
# Flask-User settings
|
||||||
USER_APP_NAME = APP_NAME
|
USER_APP_NAME = APP_NAME
|
||||||
USER_ENABLE_CHANGE_PASSWORD = True # Allow users to change their password
|
USER_ENABLE_CHANGE_PASSWORD = True # Allow users to change their password
|
||||||
USER_ENABLE_CHANGE_USERNAME = True # Allow users to change their username
|
USER_ENABLE_CHANGE_USERNAME = False # Allow users to change their username
|
||||||
USER_ENABLE_REGISTER = False # Allow new users to register
|
USER_ENABLE_REGISTER = False # Allow new users to register
|
||||||
|
|
||||||
# Should alwyas be set to true
|
# Should alwyas be set to true
|
||||||
@@ -22,16 +28,25 @@ USER_REQUIRE_RETYPE_PASSWORD = True # Prompt for `retype password`
|
|||||||
USER_ENABLE_USERNAME = True # Register and Login with username
|
USER_ENABLE_USERNAME = True # Register and Login with username
|
||||||
|
|
||||||
# Email Related Settings
|
# Email Related Settings
|
||||||
USER_ENABLE_EMAIL = True # Register with Email WILL - DISABLE OTHER THINGS TOO
|
USER_ENABLE_EMAIL = False # Register with Email WILL - DISABLE OTHER THINGS TOO
|
||||||
USER_ENABLE_CONFIRM_EMAIL = True # Force users to confirm their email
|
USER_ENABLE_CONFIRM_EMAIL = True # Force users to confirm their email
|
||||||
USER_ENABLE_INVITE_USER = False # Allow users to be invited
|
USER_ENABLE_INVITE_USER = False # Allow users to be invited
|
||||||
USER_REQUIRE_INVITATION = False # Only invited users may - WILL DISABLE REGISTRATION
|
USER_REQUIRE_INVITATION = False # Only invited users may - WILL DISABLE REGISTRATION
|
||||||
USER_ENABLE_FORGOT_PASSWORD = True # Allow users to reset their passwords
|
USER_ENABLE_FORGOT_PASSWORD = True # Allow users to reset their passwords
|
||||||
|
|
||||||
|
MAIL_SERVER = 'smtp.gmail.com'
|
||||||
|
MAIL_PORT = 587
|
||||||
|
MAIL_USE_SSL = False
|
||||||
|
MAIL_USE_TLS = True
|
||||||
|
MAIL_USERNAME = None
|
||||||
|
MAIL_PASSWORD = None
|
||||||
|
USER_EMAIL_SENDER_NAME = None
|
||||||
|
USER_EMAIL_SENDER_EMAIL = None
|
||||||
|
|
||||||
# Require Play Key
|
# Require Play Key
|
||||||
REQUIRE_PLAY_KEY = True
|
REQUIRE_PLAY_KEY = True
|
||||||
|
|
||||||
# Password hashing settings
|
# Password hashing settings DO NOT CHANGE
|
||||||
USER_PASSLIB_CRYPTCONTEXT_SCHEMES = ['bcrypt'] # bcrypt for password hashing
|
USER_PASSLIB_CRYPTCONTEXT_SCHEMES = ['bcrypt'] # bcrypt for password hashing
|
||||||
|
|
||||||
# Flask-User routing settings
|
# Flask-User routing settings
|
||||||
13
app/static/chartjs/chart.min.js
vendored
Normal file
13
app/static/chartjs/chart.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ $theme-colors: (
|
|||||||
$link-color: #005ac2;
|
$link-color: #005ac2;
|
||||||
|
|
||||||
@import "../bootstrap-4.2.1/scss/bootstrap";
|
@import "../bootstrap-4.2.1/scss/bootstrap";
|
||||||
@import url(http://fonts.googleapis.com/css?family=Nunito:700);
|
@import url(https://fonts.googleapis.com/css?family=Nunito:700);
|
||||||
|
|
||||||
body { font-family:'Nunito', Helvetica, Arial, sans-serif; }
|
body { font-family:'Nunito', Helvetica, Arial, sans-serif; }
|
||||||
|
|
||||||
@@ -92,3 +92,13 @@ body { font-family:'Nunito', Helvetica, Arial, sans-serif; }
|
|||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
border-color: rgba(255, 255, 255, 0.5);
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dataTables_paginate {
|
||||||
|
float: right;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dataTables_filter{
|
||||||
|
float: right;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|||||||
21
app/templates/accounts/edit_email.html.j2
Normal file
21
app/templates/accounts/edit_email.html.j2
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Edit E-mail for User {{ username }}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
Edit E-mail for User {{ username }}
|
||||||
|
{% endblock content_before %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method=post>
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 20rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ helper.render_field(form.email) }}
|
||||||
|
{{ helper.render_submit_field(form.submit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<table class="table" id="accounts_table">
|
<table class="table table-dark table-striped table-bordered table-hover" id="accounts_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
@@ -41,7 +41,11 @@
|
|||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"ajax": "{{ url_for('accounts.get') }}",
|
"ajax": "{{ url_for('accounts.get') }}",
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{ "searchable": false, "targets": [0,7] },
|
{% if config.USER_ENABLE_EMAIL %}
|
||||||
|
{ "searchable": false, "targets": [0,7] },
|
||||||
|
{% else %}
|
||||||
|
{ "searchable": false, "targets": [0,6] },
|
||||||
|
{% endif %}
|
||||||
{ "orderable": false, "targets": [0] }
|
{ "orderable": false, "targets": [0] }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,7 +93,27 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
})
|
})
|
||||||
|
{% if config.ALLOW_ANALYTICS %}
|
||||||
|
// Matomo JS analytics
|
||||||
|
var _paq = window._paq = window._paq || [];
|
||||||
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
|
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="https://matomo.aronwk.com/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||||
|
_paq.push(['setSiteId', '3']);
|
||||||
|
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
|
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||||
|
})();
|
||||||
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
{% if config.ALLOW_ANALYTICS %}
|
||||||
|
<!-- Matomo no js analytics -->
|
||||||
|
<noscript><p><img src="https://matomo.aronwk.com/matomo.php?idsite=3&rec=1" style="border:0;" alt="" /></p></noscript>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
{{ status|capitalize }} Bug Reports
|
{{ status|capitalize }} Bug Reports
|
||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content_override %}
|
||||||
<table class="table" id="accounts_table">
|
<div class="mx-5">
|
||||||
<thead>
|
<table class="table table-dark table-striped table-bordered table-hover" id="accounts_table">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
|
<th>Reporter</th>
|
||||||
<th>Body</th>
|
<th>Body</th>
|
||||||
<th>Client Version</th>
|
<th>Client Version</th>
|
||||||
<th>Other Player</th>
|
<th>Other Player</th>
|
||||||
@@ -20,9 +22,10 @@
|
|||||||
<th>Submitted</th>
|
<th>Submitted</th>
|
||||||
<th>Resolved</th>
|
<th>Resolved</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
Resolve Report {{ report.id }}
|
Resolve Report {{ report.id }}
|
||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content_override %}
|
||||||
<form method=post>
|
<form method=post>
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 20rem;">
|
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 80vw;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-right">
|
<div class="col text-right">
|
||||||
@@ -75,4 +75,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content_override %}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
View Report {{ report.id }}
|
View Report {{ report.id }}
|
||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content_override %}
|
||||||
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 20rem;">
|
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 80vw;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -102,4 +102,4 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content_override %}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{% extends 'base.html.j2' %}
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Account Management
|
Character Management
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block content_before %}
|
{% block content_before %}
|
||||||
Account Management
|
Character Management
|
||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table" id="characters_table">
|
<table class="table table-dark table-striped table-bordered table-hover" id="characters_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
|
|||||||
21
app/templates/character/rescue.html.j2
Normal file
21
app/templates/character/rescue.html.j2
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Character Rescue
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
Character Rescue
|
||||||
|
{% endblock content_before %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method=post>
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 20rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ helper.render_field(form.save_world) }}
|
||||||
|
{{ helper.render_submit_field(form.submit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -46,42 +46,55 @@
|
|||||||
<a id='property-index' class='nav-link' href='{{ url_for('properties.index') }}'>Properties</a>
|
<a id='property-index' class='nav-link' href='{{ url_for('properties.index') }}'>Properties</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.is_authenticated and current_user.gm_level >= 2 %}
|
|
||||||
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Tools</a>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
|
|
||||||
<a class="dropdown-item" href='{{ url_for('mail.send') }}'>Send Mail</a>
|
|
||||||
<hr/>
|
|
||||||
<h6 class="text-center">Moderation</h6>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('moderation.index', status='unapproved') }}'>View Unapproved Items</a>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('moderation.index', status='approved') }}'>View Approved Items</a>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('moderation.index', status='all') }}'>View All Items</a>
|
|
||||||
<hr/>
|
|
||||||
<h6 class="text-center">Bug Reports</h6>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('bug_reports.index', status='unresolved') }}'>View Unresolved Reports</a>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('bug_reports.index', status='resolved') }}'>View Resolved Reports</a>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('bug_reports.index', status='all') }}'>View All Reports</a>
|
|
||||||
{% if current_user.is_authenticated and current_user.gm_level >= 8 %}
|
|
||||||
<hr/>
|
|
||||||
<h6 class="text-center">Logs</h6>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('log.activity') }}'>Command Log</a>
|
|
||||||
<a class="dropdown-item" href='{{ url_for('log.command') }}'>Activity Log</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% if current_user.is_authenticated and current_user.gm_level == 9 and config.REQUIRE_PLAY_KEY %}
|
{% if current_user.is_authenticated and current_user.gm_level == 9 and config.REQUIRE_PLAY_KEY %}
|
||||||
{# Play Keys #}
|
{# Play Keys #}
|
||||||
<a id='play_keys-index' class='nav-link' href='{{ url_for('play_keys.index') }}'>Play Keys</a>
|
<a id='play_keys-index' class='nav-link' href='{{ url_for('play_keys.index') }}'>Play Keys</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# About always right most #}
|
{% if current_user.is_authenticated and current_user.gm_level >= 2 %}
|
||||||
<a id='main-about' class='nav-link' href='{{ url_for('main.about') }}'>About</a>
|
<a id='report-index' class='nav-link' href='{{ url_for('reports.index') }}'>Reports</a>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Tools</a>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('mail.send') }}'>Send Mail</a>
|
||||||
|
<hr/>
|
||||||
|
<h3 class="text-center">Moderation</h3>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('moderation.index', status='unapproved') }}'>Unapproved Items</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('moderation.index', status='approved') }}'>Approved Items</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('moderation.index', status='all') }}'>All Items</a>
|
||||||
|
<hr/>
|
||||||
|
<h3 class="text-center">Bug Reports</h3>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('bug_reports.index', status='unresolved') }}'>Unresolved Reports</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('bug_reports.index', status='resolved') }}'>Resolved Reports</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('bug_reports.index', status='all') }}'>All Reports</a>
|
||||||
|
{% if current_user.is_authenticated and current_user.gm_level >= 8 %}
|
||||||
|
<hr/>
|
||||||
|
<h3 class="text-center">Logs</h3>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('log.activity') }}'>Command Log</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('log.command') }}'>Activity Log</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('log.audit') }}'>Audit Log</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('log.system') }}'>System Log</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
{% if current_user.gm_level == 0 %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Bug Reports</a>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('bug_reports.index', status='unresolved') }}'>Unresolved Reports</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('bug_reports.index', status='resolved') }}'>Resolved Reports</a>
|
||||||
|
<a class="dropdown-item text-center" href='{{ url_for('bug_reports.index', status='all') }}'>All Reports</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{# About always right most #}
|
||||||
|
<a id='main-about' class='nav-link' href='{{ url_for('main.about') }}'>About</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{# Only show logout if unauthenticated #}
|
{# Only show logout if unauthenticated #}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
Credit to sttng
|
Credit to sttng
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
{% if property_data %}
|
{% if (property_data and current_user.gm_level >= 3) %}
|
||||||
<a role="button" class="btn text-{% if property_data.mod_approved %}danger{% else %}success{% endif %} btn-block"
|
<a role="button" class="btn text-{% if property_data.mod_approved %}danger{% else %}success{% endif %} btn-block"
|
||||||
href='{{url_for('properties.approve', id=property_data.id)}}'>
|
href='{{url_for('properties.approve', id=property_data.id)}}'>
|
||||||
{% if property_data.mod_approved %} Unapprove {% else %} Approve {% endif %}
|
{% if property_data.mod_approved %} Unapprove {% else %} Approve {% endif %}
|
||||||
@@ -29,16 +29,38 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.5.0/jszip.min.js"></script>
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.116.0/build/three.min.js"></script>
|
||||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip-utils/0.1.0/jszip-utils.min.js"></script>
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.116.0/examples/js/controls/OrbitControls.js"></script>
|
||||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/super-three@0.116.0/build/three.min.js"></script>
|
|
||||||
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/super-three@0.116.0/examples/js/controls/OrbitControls.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='lddviewer/base64-binary.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='lddviewer/base64-binary.js') }}"></script>
|
||||||
|
|
||||||
|
{% if config.ALLOW_ANALYTICS %}
|
||||||
|
<script>
|
||||||
|
// Matomo JS analytics
|
||||||
|
var _paq = window._paq = window._paq || [];
|
||||||
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
|
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="https://matomo.aronwk.com/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||||
|
_paq.push(['setSiteId', '3']);
|
||||||
|
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
|
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if config.ALLOW_ANALYTICS %}
|
||||||
|
<!-- Matomo no js analytics -->
|
||||||
|
<noscript><p><img src="https://matomo.aronwk.com/matomo.php?idsite=3&rec=1" style="border:0;" alt="" /></p></noscript>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<script type='module'>
|
<script type='module'>
|
||||||
import {MTLLoader} from 'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/jsm/loaders/MTLLoader.js'
|
import {MTLLoader} from 'https://cdn.jsdelivr.net/npm/three@0.116.0/examples/jsm/loaders/MTLLoader.js'
|
||||||
import {OBJLoader} from 'https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/jsm/loaders/OBJLoader.js'
|
import {OBJLoader} from 'https://cdn.jsdelivr.net/npm/three@0.116.0/examples/jsm/loaders/OBJLoader.js'
|
||||||
//Three.js stuff
|
//Three.js stuff
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
let cammatr = new THREE.Matrix4();
|
let cammatr = new THREE.Matrix4();
|
||||||
@@ -270,7 +292,6 @@
|
|||||||
|
|
||||||
|
|
||||||
class Scene{
|
class Scene{
|
||||||
//partially done - need zip file handling
|
|
||||||
constructor(){
|
constructor(){
|
||||||
this.Bricks = []
|
this.Bricks = []
|
||||||
this.Scenecamera = []
|
this.Scenecamera = []
|
||||||
@@ -446,16 +467,16 @@
|
|||||||
constructor(designID, database){
|
constructor(designID, database){
|
||||||
this.designID = designID
|
this.designID = designID
|
||||||
this.Parts = []
|
this.Parts = []
|
||||||
|
|
||||||
this.studsFields2D = []
|
this.studsFields2D = []
|
||||||
let GeometryLocation = `${designID}.g`
|
let lod = {{ lod }}
|
||||||
let PrimitiveLocation = `${designID}.xml`
|
let GeometryLocation = `brickprimitives/lod${lod}/${designID}.g`
|
||||||
|
let PrimitiveLocation = `Primitives/${designID}.xml`
|
||||||
|
|
||||||
let GeometryCount = 0
|
let GeometryCount = 0
|
||||||
while (GeometryLocation in database.filelist) {
|
while (GeometryLocation in database.filelist) {
|
||||||
this.Parts[GeometryCount] = new GeometryReader(database.filelist[GeometryLocation].read())
|
this.Parts[GeometryCount] = new GeometryReader(database.filelist[GeometryLocation].read())
|
||||||
GeometryCount = GeometryCount + 1
|
GeometryCount = GeometryCount + 1
|
||||||
GeometryLocation = `${designID}.g${GeometryCount}`
|
GeometryLocation = `brickprimitives/lod${lod}/${designID}.g${GeometryCount}`
|
||||||
}
|
}
|
||||||
let primitive = new Primitive(database.filelist[PrimitiveLocation].read())
|
let primitive = new Primitive(database.filelist[PrimitiveLocation].read())
|
||||||
this.Partname = primitive.Designname
|
this.Partname = primitive.Designname
|
||||||
@@ -528,7 +549,7 @@
|
|||||||
|
|
||||||
LoadDBURL(dbURLlocation){
|
LoadDBURL(dbURLlocation){
|
||||||
this.database = new DBURLReader(dbURLlocation)
|
this.database = new DBURLReader(dbURLlocation)
|
||||||
if(this.database.initok && this.database.fileexist('Materials.xml') && this.database.fileexist('localizedStrings.loc')){
|
if(this.database.initok && this.database.fileexist('Materials.xml')){
|
||||||
this.allMaterials = new Materials(this.database.filelist['Materials.xml'].read())
|
this.allMaterials = new Materials(this.database.filelist['Materials.xml'].read())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,13 +589,15 @@
|
|||||||
for (const bri of this.scene.Bricks){
|
for (const bri of this.scene.Bricks){
|
||||||
current += 1
|
current += 1
|
||||||
for (const pa of bri.Parts){
|
for (const pa of bri.Parts){
|
||||||
let geo = new Geometry(pa.designID, this.database)
|
let geo = 0
|
||||||
if (!(pa.designID in geometriecache)) {
|
if (geometriecache.hasOwnProperty(pa.designID)) {
|
||||||
let geo = new Geometry(pa.designID, this.database)
|
// console.log(`Re-use brick ${pa.designID}`)
|
||||||
geometriecache[pa.designID] = geo
|
geo = geometriecache[pa.designID]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
geo = geometriecache[pa.designID]
|
// console.log(`New brick ${pa.designID}`)
|
||||||
|
geo = new Geometry(pa.designID, this.database)
|
||||||
|
geometriecache[pa.designID] = geo
|
||||||
}
|
}
|
||||||
|
|
||||||
let ind = 0
|
let ind = 0
|
||||||
@@ -675,7 +698,7 @@
|
|||||||
console.log('partindex: ' + partindex)
|
console.log('partindex: ' + partindex)
|
||||||
console.log(pa.materials)
|
console.log(pa.materials)
|
||||||
|
|
||||||
let lddmat = allMaterials.getMaterialbyId(21)
|
lddmat = allMaterials.getMaterialbyId(21)
|
||||||
}
|
}
|
||||||
|
|
||||||
let deco = '0'
|
let deco = '0'
|
||||||
@@ -724,11 +747,8 @@
|
|||||||
// let vnh = new VertexNormalsHelper( mesh, 5 );
|
// let vnh = new VertexNormalsHelper( mesh, 5 );
|
||||||
// scene.add( vnh );
|
// scene.add( vnh );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -861,7 +881,6 @@
|
|||||||
|
|
||||||
|
|
||||||
class Materials {
|
class Materials {
|
||||||
//done
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.Materials = {}
|
this.Materials = {}
|
||||||
let parser = new DOMParser();
|
let parser = new DOMParser();
|
||||||
@@ -908,13 +927,11 @@
|
|||||||
|
|
||||||
|
|
||||||
function FindDBURL(){
|
function FindDBURL(){
|
||||||
let dburl = 'https://json.aronwk.com/LDD-DB/'
|
let dburl = '/luclient/ldddb/'
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', dburl, false); // `false` makes the request synchronous
|
xhr.open('GET', dburl, false); // `false` makes the request synchronous
|
||||||
|
|
||||||
// request state change event
|
// request state change event
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
|
|
||||||
// request completed?
|
// request completed?
|
||||||
if (xhr.readyState !== 4) {//return;
|
if (xhr.readyState !== 4) {//return;
|
||||||
dburl = false;
|
dburl = false;
|
||||||
@@ -930,7 +947,6 @@
|
|||||||
console.log('HTTP error in FindDBURL:', xhr.status, xhr.statusText);
|
console.log('HTTP error in FindDBURL:', xhr.status, xhr.statusText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// start request
|
// start request
|
||||||
xhr.send();
|
xhr.send();
|
||||||
return dburl
|
return dburl
|
||||||
@@ -999,7 +1015,7 @@
|
|||||||
return self.filelist[filename];
|
return self.filelist[filename];
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(dburl) {
|
parse(dburl, folder="") {
|
||||||
let self = this;
|
let self = this;
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', dburl, false);
|
xhr.open('GET', dburl, false);
|
||||||
@@ -1018,7 +1034,7 @@
|
|||||||
let obj = data[i];
|
let obj = data[i];
|
||||||
if (obj.type == 'directory'){
|
if (obj.type == 'directory'){
|
||||||
// parse subdirs
|
// parse subdirs
|
||||||
self.parse(dburl + obj.name + '/')
|
self.parse(dburl + obj.name + '/', obj.name)
|
||||||
}
|
}
|
||||||
else if (obj.type == 'file'){
|
else if (obj.type == 'file'){
|
||||||
self.filelist[obj.name] = new DBURLFile(dburl + obj.name, obj.name)
|
self.filelist[obj.name] = new DBURLFile(dburl + obj.name, obj.name)
|
||||||
@@ -1043,7 +1059,7 @@
|
|||||||
let lxfml_file_list = [
|
let lxfml_file_list = [
|
||||||
{% for model in content %}
|
{% for model in content %}
|
||||||
{% if model.lot == 14 %}
|
{% if model.lot == 14 %}
|
||||||
"{{url_for('properties.get_model', id=model.id, file_format='lxfml')}}"{{ ", " if not loop.last else "" }}
|
"{{url_for('properties.get_model', id=model.id, file_format='lxfml', lod=lod)}}"{{ ", " if not loop.last else "" }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table" id="command_table">
|
<table class="table table-dark table-striped table-bordered table-hover" id="command_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
|||||||
46
app/templates/logs/audit.html.j2
Normal file
46
app/templates/logs/audit.html.j2
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Audit Log
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
Audit Log
|
||||||
|
{% endblock content_before %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if message %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col text-center">
|
||||||
|
<h3>{{ message }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
{% endif %}
|
||||||
|
<table class="table table-dark table-striped table-bordered table-hover" id="audit_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Account</th>
|
||||||
|
<th>Command</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ super () }}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
let audit_table = $('#audit_table').DataTable({
|
||||||
|
"order": [[0, "desc"]],
|
||||||
|
"processing": true,
|
||||||
|
"serverSide": true,
|
||||||
|
"ajax": "{{ url_for('log.get_audits') }}",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table" id="activity_table">
|
<table class="table table-dark table-striped table-bordered table-hover" id="activity_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
|||||||
15
app/templates/logs/system.html.j2
Normal file
15
app/templates/logs/system.html.j2
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}LOGS{% endblock %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
LOGS - {{ config.APP_NAME }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_override %}
|
||||||
|
<div class="bg-white mx-5 p-3 rounded">
|
||||||
|
<code>
|
||||||
|
{{ logs }}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -3,58 +3,26 @@
|
|||||||
{% block title %}About{% endblock %}
|
{% block title %}About{% endblock %}
|
||||||
|
|
||||||
{% block content_before %}
|
{% block content_before %}
|
||||||
About {{ config.APP_NAME }}
|
Online Players: {{ online }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class='card mx-auto mt-5 shadow-sm bg-dark border-primary'>
|
<div class='card mx-auto mt-5 shadow-sm bg-dark border-primary'>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="text-center">Contributors</h4>
|
<h4 class="text-center">Staff</h4>
|
||||||
|
|
||||||
<div class="row">
|
{% for mod in mods %}
|
||||||
<div class="col text-right">
|
<div class="row">
|
||||||
Developer:
|
<div class="col text-right">
|
||||||
|
{{ mod.username }}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{% with gm_level=mod.gm_level %}
|
||||||
|
{% include 'partials/_gm_level.html.j2' %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
{% endfor %}
|
||||||
Aronwk (Aaron Kimbrell)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-right">
|
|
||||||
Developer:
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
Wincent
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-right">
|
|
||||||
Logo Designer:
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
BlasterBuilder
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-right">
|
|
||||||
LDD/LXFML Rendering:
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
m2m/sttng
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-right">
|
|
||||||
TODO: add more Contributors
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
Add more
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -62,7 +30,7 @@
|
|||||||
Source
|
Source
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<a href="https://github.com/DarkflameUniverse/AccountManager">
|
<a href="https://github.com/DarkflameUniverse/NexusDashboard">
|
||||||
Github
|
Github
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,59 +8,63 @@
|
|||||||
Moderation of {{ status|capitalize }} Items
|
Moderation of {{ status|capitalize }} Items
|
||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content_override %}
|
||||||
<h4> Characters </h4>
|
<div class="mx-5">
|
||||||
<hr class="bg-primary"/>
|
<h4> Characters </h4>
|
||||||
<table class="table" id="character_table">
|
<hr class="bg-primary"/>
|
||||||
<thead>
|
<table class="table table-dark table-striped table-bordered table-hover" id="character_table">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Actions</th>
|
<tr>
|
||||||
<th>Account</th>
|
<th>Actions</th>
|
||||||
<th>Name</th>
|
<th>Account</th>
|
||||||
<th>Pending Name</th>
|
<th>Name</th>
|
||||||
<th>Needs Rename</th>
|
<th>Pending Name</th>
|
||||||
<th>Last Login</th>
|
<th>Needs Rename</th>
|
||||||
<th>Permission Map</th>
|
<th>Last Login</th>
|
||||||
</tr>
|
<th>Permission Map</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody></tbody>
|
</thead>
|
||||||
</table>
|
<tbody></tbody>
|
||||||
<br/>
|
</table>
|
||||||
<h4> Pets </h4>
|
<br/>
|
||||||
<hr class="bg-primary"/>
|
<h4> Pets </h4>
|
||||||
<table class="table" id="pet_table">
|
<hr class="bg-primary"/>
|
||||||
<thead>
|
<table class="table table-dark table-striped table-bordered table-hover" id="pet_table">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Actions</th>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Actions</th>
|
||||||
<th>Status</th>
|
<th>Name</th>
|
||||||
</tr>
|
<th>Status</th>
|
||||||
</thead>
|
<th>Owner</th>
|
||||||
<tbody></tbody>
|
</tr>
|
||||||
</table>
|
</thead>
|
||||||
<br/>
|
<tbody></tbody>
|
||||||
<h4> Properties </h4>
|
</table>
|
||||||
<hr class="bg-primary"/>
|
<br/>
|
||||||
<table class="table" id="property_table">
|
<h4> Properties </h4>
|
||||||
<thead>
|
<hr class="bg-primary"/>
|
||||||
<tr>
|
<table class="table table-dark table-striped table-bordered table-hover" id="property_table">
|
||||||
<th>Actions</th>
|
<thead>
|
||||||
<th>Owner</th>
|
<tr>
|
||||||
<th>Template ID</th>
|
<th>Actions</th>
|
||||||
<th>Clone ID</th>
|
<th>Owner</th>
|
||||||
<th>Name</th>
|
<th>Template ID</th>
|
||||||
<th>Description</th>
|
<th>Clone ID</th>
|
||||||
<th>Privacy</th>
|
<th>Name</th>
|
||||||
<th>Approved</th>
|
<th>Description</th>
|
||||||
<th>Updated</th>
|
<th>Privacy</th>
|
||||||
<th>Claimed</th>
|
<th>Approved</th>
|
||||||
<th>Rejection Reason</th>
|
<th>Updated</th>
|
||||||
<th>Reputation</th>
|
<th>Claimed</th>
|
||||||
<th>Location</th>
|
<th>Rejection Reason</th>
|
||||||
</tr>
|
<th>Reputation</th>
|
||||||
</thead>
|
<th>Performance Cost</th>
|
||||||
<tbody></tbody>
|
<th>Location</th>
|
||||||
</table>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{ account_data.email }}
|
{{ account_data.email }}
|
||||||
|
{% if current_user.gm_level >= 8 and not(current_user.gm_level == 8 and account_data.gm_level == 8)%}
|
||||||
|
<a role="button" class="btn btn-primary"
|
||||||
|
href='{{ url_for('accounts.edit_email', id=account_data.id) }}'>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -141,9 +147,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.id != account_data.id and current_user.gm_level > 3 %}
|
{% if current_user.id != account_data.id and current_user.gm_level > 3 %}
|
||||||
<hr class="bg-primary"/>
|
<hr class="bg-primary"/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
@@ -191,6 +197,42 @@
|
|||||||
Mute for 1 year
|
Mute for 1 year
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if current_user.gm_level == 9 %}
|
||||||
|
<button type="button" class="btn btn-danger btn-block" data-toggle="modal" data-target="#deleteModal">
|
||||||
|
Delete Account
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if current_user.gm_level == 9 %}
|
||||||
|
{# delete Modal #}
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content bg-dark">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title" id="deleteModalLabel">
|
||||||
|
Permanently Delete this Account?
|
||||||
|
</h1>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h2> This can NOT be undone! </h2>
|
||||||
|
<br/>
|
||||||
|
This user is a GM {{account_data.gm_level}} !!!<br/>
|
||||||
|
This will delete their everything, including but not limited to:<br/>
|
||||||
|
Properties, Audit Logs, Bug Reports, and Invitations!
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a role="button" class="btn btn-danger btn-block"
|
||||||
|
href='{{ url_for('accounts.delete', id=account_data.id) }}'>
|
||||||
|
Permanently Delete
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|||||||
@@ -52,17 +52,21 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<br/>
|
<br/>
|
||||||
<div class="row">
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
<div class="col text-center">
|
href='{{ url_for('characters.view_xml', id=character.id) }}'>
|
||||||
<a role="button" class="btn btn-primary btn-block"
|
View XML
|
||||||
href='{{ url_for('accounts.view', id=character.account_id) }}'>
|
</a>
|
||||||
View Account: {{character.account.username}}
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
</a>
|
href='{{ url_for('characters.get_xml', id=character.id) }}'>
|
||||||
</div>
|
Download XML
|
||||||
</div>
|
</a>
|
||||||
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
|
href='{{ url_for('accounts.view', id=character.account_id) }}'>
|
||||||
|
View Account: {{character.account.username}}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.id != character.account_id and current_user.gm_level > 2 %}
|
{% if current_user.gm_level > 2 %}
|
||||||
<hr class="bg-primary"/>
|
<hr class="bg-primary"/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
@@ -83,6 +87,10 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<a role="button" class="btn btn-warning btn-block"
|
||||||
|
href='{{ url_for('characters.rescue', id=character.id) }}'>
|
||||||
|
Rescue
|
||||||
|
</a>
|
||||||
<a role="button" class="btn btn-primary btn-block"
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
href='{{ url_for('characters.restrict', id=character.id, bit=4) }}'>
|
href='{{ url_for('characters.restrict', id=character.id, bit=4) }}'>
|
||||||
{% if character.permission_map|check_perm_map(4) %}Unrestrict{% else %}Restrict{% endif %} Trade
|
{% if character.permission_map|check_perm_map(4) %}Unrestrict{% else %}Restrict{% endif %} Trade
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h4>
|
<h4>
|
||||||
Chatacter Data
|
Character Data
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
{% set parsed_lzid = character_json.obj.char.attr_lzid|parse_lzid %}
|
{% set parsed_lzid = character_json.obj.char.attr_lzid|parse_lzid %}
|
||||||
Zone: {{ parsed_lzid[0]|get_zone_name }}<br>
|
Zone: {{ character_json.obj.char.attr_lwid|get_zone_name }}<br>
|
||||||
Zone Instance: {{ parsed_lzid[1] }}<br>
|
Zone Instance: {{ parsed_lzid[1] }}<br>
|
||||||
Zone Clone: {{ parsed_lzid[2] }}<br>
|
Zone Clone: {{ parsed_lzid[2] }}<br>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
{% if gm_level==0 %}
|
{% if gm_level==0 %}
|
||||||
Player
|
Player
|
||||||
{% elif gm_level==1 %}
|
{% elif gm_level==1 %}
|
||||||
Key Distrubuter
|
Key Distributor
|
||||||
{% elif gm_level==2 %}
|
{% elif gm_level==2 %}
|
||||||
Junior Moderator
|
Junior Moderator
|
||||||
{% elif gm_level==3 %}
|
{% elif gm_level==3 %}
|
||||||
|
|||||||
@@ -69,6 +69,14 @@
|
|||||||
{{ property.reputation }}
|
{{ property.reputation }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col text-right">
|
||||||
|
Performance Cost:
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{{ property.performance_cost }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% if request.endpoint != "properties.view" %}
|
{% if request.endpoint != "properties.view" %}
|
||||||
<br/>
|
<br/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -81,11 +89,24 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<br/>
|
<br/>
|
||||||
|
<h5 class="text-center">Render Quality</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<a role="button" class="btn btn-primary btn-block"
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
href='{{ url_for('properties.view_models', id=property.id) }}'>
|
href='{{ url_for('properties.view_models', id=property.id, lod=0) }}'>
|
||||||
Render Property
|
High (0)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col text-center">
|
||||||
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
|
href='{{ url_for('properties.view_models', id=property.id, lod=1) }}'>
|
||||||
|
Med (1)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col text-center">
|
||||||
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
|
href='{{ url_for('properties.view_models', id=property.id, lod=2) }}'>
|
||||||
|
Low (2)
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,6 +117,13 @@
|
|||||||
href='{{url_for('properties.approve', id=property.id)}}'>
|
href='{{url_for('properties.approve', id=property.id)}}'>
|
||||||
{% if property.mod_approved %} Unapprove {% else %} Approve {% endif %}
|
{% if property.mod_approved %} Unapprove {% else %} Approve {% endif %}
|
||||||
</a>
|
</a>
|
||||||
|
{% if not property.rejection_reason %}
|
||||||
|
<br/>
|
||||||
|
<a role="button" class="btn btn-danger btn-block"
|
||||||
|
href='{{url_for('properties.reject', id=property.id)}}'>
|
||||||
|
Reject
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,21 +44,12 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<a role="button" class="btn btn-primary btn-block"
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
href='{{ url_for('properties.get_model', id=item.id, file_format="lxfml") }}'>
|
href='{{ url_for('properties.get_model', id=item.id, file_format="lxfml", lod=0) }}'>
|
||||||
View Model XML
|
View Model XML
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="row">
|
|
||||||
<div class="col text-center">
|
|
||||||
<a role="button" class="btn btn-primary btn-block"
|
|
||||||
href='{{ url_for('properties.view_model', id=item.id) }}'>
|
|
||||||
Render Model
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<a role="button" class="btn btn-primary btn-block"
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
@@ -67,5 +58,27 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br/>
|
||||||
|
<h5 class="text-center">Render Quality</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col text-center">
|
||||||
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
|
href='{{ url_for('properties.view_model', id=item.id, lod=0) }}'>
|
||||||
|
High (0)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col text-center">
|
||||||
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
|
href='{{ url_for('properties.view_model', id=item.id, lod=1) }}'>
|
||||||
|
Med (1)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col text-center">
|
||||||
|
<a role="button" class="btn btn-primary btn-block"
|
||||||
|
href='{{ url_for('properties.view_model', id=item.id, lod=2) }}'>
|
||||||
|
Low (2)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
<br/>
|
<br/>
|
||||||
--------------------------------
|
--------------------------------
|
||||||
<br/>
|
<br/>
|
||||||
{{ ("ItemSets_" ~ item_set[0] ~ "_kitName")|lu_translate }}: Rank {{ item_set[4] }}<br/>
|
|
||||||
|
{{ ("ItemSets_" ~ item_set[0] ~ "_kitName")|lu_translate }}{% if item_set[4]|int > 0%}: Rank {{ item_set[4] }}{% endif %}<br/>
|
||||||
{% if item_set[5] %}
|
{% if item_set[5] %}
|
||||||
<img src='/luclient/get_icon_iconid/{{item_set[5]}}'
|
<img src='/luclient/get_icon_iconid/{{item_set[5]}}'
|
||||||
alt='Kit Image'
|
alt='Kit Image'
|
||||||
|
|||||||
@@ -27,10 +27,12 @@
|
|||||||
{% set skill_desc = skill[0]|get_skill_desc %}
|
{% set skill_desc = skill[0]|get_skill_desc %}
|
||||||
{% if "IP" not in skill_desc and "AP" not in skill_desc and "LP" not in skill_desc and skill[0]|string not in skill_desc %}
|
{% if "IP" not in skill_desc and "AP" not in skill_desc and "LP" not in skill_desc and skill[0]|string not in skill_desc %}
|
||||||
<br/>
|
<br/>
|
||||||
<img src='{{ url_for('luclient.get_icon_iconid', id=skill[1]) }}'
|
{% if skill[1]%}
|
||||||
alt='Skill: '
|
<img src='{{ url_for('luclient.get_icon_iconid', id=skill[1]) }}'
|
||||||
width='32'
|
alt='Skill: '
|
||||||
height='32'>
|
width='32'
|
||||||
|
height='32'>
|
||||||
|
{% endif %}
|
||||||
{{ skill[0]|get_skill_desc }}
|
{{ skill[0]|get_skill_desc }}
|
||||||
<br/>
|
<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
Bulk Create Play Keys
|
Bulk Create Play Keys
|
||||||
</a>
|
</a>
|
||||||
<hr class="bg-primary"/>
|
<hr class="bg-primary"/>
|
||||||
<table class="table" id="play_key_table">
|
<table class="table table-dark table-striped table-bordered table-hover" id="play_key_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
|
|||||||
@@ -28,11 +28,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-right">
|
<div class="col text-right">
|
||||||
Uses Left:
|
Times Used:
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{ key.key_uses}}
|
{{ key.times_used}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -8,35 +8,38 @@
|
|||||||
Property Management
|
Property Management
|
||||||
{% endblock content_before %}
|
{% endblock content_before %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content_override %}
|
||||||
{% if message %}
|
<div class="mx-5">
|
||||||
<div class="row">
|
{% if message %}
|
||||||
<div class="col text-center">
|
<div class="row">
|
||||||
<h3>{{ message }}</h3>
|
<div class="col text-center">
|
||||||
|
<h3>{{ message }}</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br/>
|
||||||
<br/>
|
{% endif %}
|
||||||
{% endif %}
|
<table class="table table-dark table-striped table-bordered table-hover" id="properties_table">
|
||||||
<table class="table" id="properties_table">
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th>Actions</th>
|
||||||
<th>Actions</th>
|
<th>Owner</th>
|
||||||
<th>Owner</th>
|
<th>Template ID</th>
|
||||||
<th>Template ID</th>
|
<th>Clone ID</th>
|
||||||
<th>Clone ID</th>
|
<th>Name</th>
|
||||||
<th>Name</th>
|
<th>Description</th>
|
||||||
<th>Description</th>
|
<th>Privacy</th>
|
||||||
<th>Privacy</th>
|
<th>Approved</th>
|
||||||
<th>Approved</th>
|
<th>Updated</th>
|
||||||
<th>Updated</th>
|
<th>Claimed</th>
|
||||||
<th>Claimed</th>
|
<th>Rejection Reason</th>
|
||||||
<th>Rejection Reason</th>
|
<th>Reputation</th>
|
||||||
<th>Reputation</th>
|
<th>Performance Cost</th>
|
||||||
<th>Location</th>
|
<th>Location</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
32
app/templates/properties/reject.html.j2
Normal file
32
app/templates/properties/reject.html.j2
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Viewing {{property_data.owner.name}}'s
|
||||||
|
{% if property_data.name %}
|
||||||
|
{{ property_data.name }}
|
||||||
|
{% else %}
|
||||||
|
{{ property_data.zone_id|get_zone_name }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
Viewing {{property_data.owner.name}}'s
|
||||||
|
{% if property_data.name %}
|
||||||
|
{{ property_data.name }}
|
||||||
|
{% else %}
|
||||||
|
{{ property_data.zone_id|get_zone_name }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method=post>
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div class="card shadow-sm mx-auto pb-3 bg-dark border-primary" style="width: 20rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
{{ helper.render_field(form.rejection_reason) }}
|
||||||
|
{{ helper.render_submit_field(form.submit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
51
app/templates/reports/currency/by_date.html.j2
Normal file
51
app/templates/reports/currency/by_date.html.j2
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Currency on {{ date }}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
Currency on {{ date }}
|
||||||
|
{% endblock content_before %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class='table-responsive'>
|
||||||
|
<table class="table table-dark table-striped table-bordered table-hover"
|
||||||
|
id="currency_by_date"
|
||||||
|
data-order='[[ 1, "desc" ]]'>
|
||||||
|
<thead>
|
||||||
|
<th scope="col">
|
||||||
|
Character
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
Currency
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for name, currency in data.items() %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ currency }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ super () }}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
let currency_by_date = $('#currency_by_date').DataTable({
|
||||||
|
"processing": false,
|
||||||
|
"serverSide": false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
104
app/templates/reports/graph.html.j2
Normal file
104
app/templates/reports/graph.html.j2
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ name }} History for {{start_date}} to {{end_date}}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
{{ name }} History for {{start_date}} to {{end_date}}
|
||||||
|
{% endblock content_before %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class ="row">
|
||||||
|
{% if data_type == "items" %}
|
||||||
|
<div class="col">
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.items_graph', start=(start|int+1), end=(end|int+1))}}'>
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if end|int > 0 %}
|
||||||
|
<div class="col">
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.items_graph', start=(start|int-1), end=(end|int-1))}}'>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% elif data_type == "currency" %}
|
||||||
|
<div class="col">
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.currency_graph', start=(start|int+1), end=(end|int+1))}}'>
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if end|int > 0 %}
|
||||||
|
<div class="col">
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.currency_graph', start=(start|int-1), end=(end|int-1))}}'>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% elif data_type == "uscore" %}
|
||||||
|
<div class="col">
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.uscore_graph', start=(start|int+1), end=(end|int+1))}}'>
|
||||||
|
Previous
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% if end|int > 0 %}
|
||||||
|
<div class="col">
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.uscore_graph', start=(start|int-1), end=(end|int-1))}}'>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<h1> INVALID DATA TYPE </h1>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<canvas id="item_chart"></canvas>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ super () }}
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='chartjs/chart.min.js') }}"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
let config = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: {{labels}},
|
||||||
|
datasets: {{datasets}},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: { legend: { display: false }, },
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: '{{ name }}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let items_chart = new Chart(
|
||||||
|
document.getElementById('item_chart'),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -11,23 +11,43 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
Items: <br/>
|
Items:
|
||||||
{% for item in items %}
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.items_graph', start=1, end=0)}}'>
|
||||||
|
Graph
|
||||||
|
</a><br/>
|
||||||
|
{% for report in reports_items|reverse %}
|
||||||
<a role="button" class="btn btn-primary btn btn-block"
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
href='{{url_for('reports.items_by_date', date=item.date)}}'>
|
href='{{url_for('reports.items_by_date', date=report.date)}}'>
|
||||||
{{item.date}}
|
{{report.date}}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
Currency:
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.currency_graph', start=1, end=0)}}'>
|
||||||
|
Graph
|
||||||
|
</a><br/>
|
||||||
|
{% for report in reports_currency|reverse %}
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.currency_by_date', date=report.date)}}'>
|
||||||
|
{{report.date}}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
U-Score:
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.uscore_graph', start=1, end=0)}}'>
|
||||||
|
Graph
|
||||||
|
</a><br/>
|
||||||
|
{% for report in reports_uscore|reverse %}
|
||||||
|
<a role="button" class="btn btn-primary btn btn-block"
|
||||||
|
href='{{url_for('reports.uscore_by_date', date=report.date)}}'>
|
||||||
|
{{report.date}}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block js %}
|
|
||||||
{{ super () }}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -11,9 +11,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class='table-responsive'>
|
<div class='table-responsive'>
|
||||||
<table class="table table-dark table-striped table-bordered table-hover"
|
<table class="table table-dark table-striped table-bordered table-hover"
|
||||||
id="two_weeks"
|
id="items_by_date"
|
||||||
data-order='[[ 1, "asc" ]]'
|
data-order='[[ 1, "desc" ]]'>
|
||||||
data-page-length='25'>
|
|
||||||
<thead>
|
<thead>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
Item
|
Item
|
||||||
@@ -26,16 +25,16 @@
|
|||||||
</th>
|
</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for item in items %}
|
{% for lot, count in data.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{ item.item|get_lot_name }}
|
{{ lot|get_lot_name }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.count }}
|
{{ count }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.item|get_lot_rarity }}
|
{{ lot|get_lot_rarity }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -44,3 +43,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ super () }}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
let items_by_date = $('#items_by_date').DataTable({
|
||||||
|
"processing": false,
|
||||||
|
"serverSide": false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
51
app/templates/reports/uscore/by_date.html.j2
Normal file
51
app/templates/reports/uscore/by_date.html.j2
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
U-Score on {{ date }}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
U-Score on {{ date }}
|
||||||
|
{% endblock content_before %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class='table-responsive'>
|
||||||
|
<table class="table table-dark table-striped table-bordered table-hover"
|
||||||
|
id="uscore_by_date"
|
||||||
|
data-order='[[ 1, "desc" ]]'>
|
||||||
|
<thead>
|
||||||
|
<th scope="col">
|
||||||
|
Character
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
U-Score
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for name, uscore in data.items() %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ uscore }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ super () }}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
let uscore_by_date = $('#uscore_by_date').DataTable({
|
||||||
|
"processing": false,
|
||||||
|
"serverSide": false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
18
app/templates/status_codes/500.html.j2
Normal file
18
app/templates/status_codes/500.html.j2
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html.j2' %}
|
||||||
|
|
||||||
|
{% block title %}ERROR{% endblock %}
|
||||||
|
|
||||||
|
{% block content_before %}
|
||||||
|
ERROR - {{ config.APP_NAME }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if current_user.gm_level == 9 %}
|
||||||
|
<code>
|
||||||
|
{{ exception }}
|
||||||
|
</code>
|
||||||
|
{% else %}
|
||||||
|
<h2 class="text-center">An Error has Occurred!!!</h2>
|
||||||
|
<div>Please Report this to an Admin</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -3,5 +3,11 @@
|
|||||||
# unzip brickdb from client to the right places
|
# unzip brickdb from client to the right places
|
||||||
unzip -n -q /app/luclient/res/brickdb.zip -d app/luclient/res/
|
unzip -n -q /app/luclient/res/brickdb.zip -d app/luclient/res/
|
||||||
|
|
||||||
|
# TODO: preconvert images options
|
||||||
|
# TODO: preconvery models options
|
||||||
|
|
||||||
|
# update the DB
|
||||||
flask db upgrade
|
flask db upgrade
|
||||||
|
|
||||||
|
# RUNNNNNNNNNNNNN
|
||||||
gunicorn -b :8000 -w 4 wsgi:app
|
gunicorn -b :8000 -w 4 wsgi:app
|
||||||
|
|||||||
38
migrations/versions/3132aaef7413_fix_nullables.py
Normal file
38
migrations/versions/3132aaef7413_fix_nullables.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""fix nullables
|
||||||
|
|
||||||
|
Revision ID: 3132aaef7413
|
||||||
|
Revises: bd908969d8fe
|
||||||
|
Create Date: 2022-02-11 21:51:58.479066
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3132aaef7413'
|
||||||
|
down_revision = 'bd908969d8fe'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('audit_logs', 'account_id',
|
||||||
|
existing_type=mysql.INTEGER(display_width=11),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('audit_logs', 'action',
|
||||||
|
existing_type=mysql.TEXT(),
|
||||||
|
nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('audit_logs', 'action',
|
||||||
|
existing_type=mysql.TEXT(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('audit_logs', 'account_id',
|
||||||
|
existing_type=mysql.INTEGER(display_width=11),
|
||||||
|
nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
51
migrations/versions/8e52b5c7568a_reporter_id.py
Normal file
51
migrations/versions/8e52b5c7568a_reporter_id.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"""reporter_id
|
||||||
|
|
||||||
|
Revision ID: 8e52b5c7568a
|
||||||
|
Revises: fa97b0d0c351
|
||||||
|
Create Date: 2022-04-02 17:35:54.814007
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy.engine import reflection
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '8e52b5c7568a'
|
||||||
|
down_revision = 'fa97b0d0c351'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
if not _table_has_column('bug_reports', 'reporter_id'):
|
||||||
|
op.add_column(
|
||||||
|
'bug_reports',
|
||||||
|
sa.Column(
|
||||||
|
'reporter_id',
|
||||||
|
mysql.INTEGER(),
|
||||||
|
server_default='0',
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('bug_reports', 'reporter_id')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
def _table_has_column(table, column):
|
||||||
|
config = op.get_context().config
|
||||||
|
engine = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section), prefix='sqlalchemy.')
|
||||||
|
insp = reflection.Inspector.from_engine(engine)
|
||||||
|
has_column = False
|
||||||
|
for col in insp.get_columns(table):
|
||||||
|
if column not in col['name']:
|
||||||
|
continue
|
||||||
|
has_column = True
|
||||||
|
return has_column
|
||||||
33
migrations/versions/aee4c6c24811_reports.py
Normal file
33
migrations/versions/aee4c6c24811_reports.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""reports
|
||||||
|
|
||||||
|
Revision ID: aee4c6c24811
|
||||||
|
Revises: 8a2966b9f7ee
|
||||||
|
Create Date: 2022-01-16 20:12:39.816567
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'aee4c6c24811'
|
||||||
|
down_revision = '8a2966b9f7ee'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('reports',
|
||||||
|
sa.Column('data', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('report_type', sa.VARCHAR(length=35), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('date', sa.Date(), autoincrement=False, nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('report_type', 'date')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('reports')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""make pet owner not a forein key
|
||||||
|
|
||||||
|
Revision ID: b470795db8e1
|
||||||
|
Revises: e3e8e05f27ee
|
||||||
|
Create Date: 2022-02-12 20:51:12.318782
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b470795db8e1'
|
||||||
|
down_revision = 'e3e8e05f27ee'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint('pet_names_ibfk_1', 'pet_names', type_='foreignkey')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_foreign_key('pet_names_ibfk_1', 'pet_names', 'charinfo', ['owner_id'], ['id'], ondelete='CASCADE')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"""itemreport table
|
|
||||||
|
|
||||||
Revision ID: b89c21b5112f
|
|
||||||
Revises: 8a2966b9f7ee
|
|
||||||
Create Date: 2022-01-15 19:21:44.544653
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import mysql
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'b89c21b5112f'
|
|
||||||
down_revision = '8a2966b9f7ee'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('item_reports',
|
|
||||||
sa.Column('item', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('count', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('date', sa.Date(), nullable=False),
|
|
||||||
sa.PrimaryKeyConstraint('item', 'date')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('item_reports')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
35
migrations/versions/bd908969d8fe_add_audit_log_table.py
Normal file
35
migrations/versions/bd908969d8fe_add_audit_log_table.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"""Add audit_log table
|
||||||
|
|
||||||
|
Revision ID: bd908969d8fe
|
||||||
|
Revises: aee4c6c24811
|
||||||
|
Create Date: 2022-02-11 21:48:03.798474
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'bd908969d8fe'
|
||||||
|
down_revision = 'aee4c6c24811'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('audit_logs',
|
||||||
|
sa.Column('id', mysql.INTEGER(), nullable=False),
|
||||||
|
sa.Column('account_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('action', mysql.TEXT(), nullable=True),
|
||||||
|
sa.Column('date', mysql.TIMESTAMP(), server_default=sa.text('now()'), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('audit_logs')
|
||||||
|
# ### end Alembic commands ###
|
||||||
30
migrations/versions/e3e8e05f27ee_pet_owners.py
Normal file
30
migrations/versions/e3e8e05f27ee_pet_owners.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""pet owners
|
||||||
|
|
||||||
|
Revision ID: e3e8e05f27ee
|
||||||
|
Revises: 3132aaef7413
|
||||||
|
Create Date: 2022-02-11 23:18:20.978203
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e3e8e05f27ee'
|
||||||
|
down_revision = '3132aaef7413'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('pet_names', sa.Column('owner_id', mysql.BIGINT(), nullable=True))
|
||||||
|
op.create_foreign_key(None, 'pet_names', 'charinfo', ['owner_id'], ['id'], ondelete='CASCADE')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'pet_names', type_='foreignkey')
|
||||||
|
op.drop_column('pet_names', 'owner_id')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
"""property performance index
|
||||||
|
|
||||||
|
Revision ID: fa97b0d0c351
|
||||||
|
Revises: b470795db8e1
|
||||||
|
Create Date: 2022-03-31 10:38:06.367277
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy.engine import reflection
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'fa97b0d0c351'
|
||||||
|
down_revision = 'b470795db8e1'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
if not _table_has_column('properties', 'performance_cost'):
|
||||||
|
op.add_column(
|
||||||
|
'properties',
|
||||||
|
sa.Column(
|
||||||
|
'performance_cost',
|
||||||
|
mysql.DOUBLE(
|
||||||
|
precision=20,
|
||||||
|
scale=15,
|
||||||
|
asdecimal=False
|
||||||
|
),
|
||||||
|
server_default='0.0',
|
||||||
|
nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('properties', 'performance_cost')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def _table_has_column(table, column):
|
||||||
|
config = op.get_context().config
|
||||||
|
engine = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section), prefix='sqlalchemy.')
|
||||||
|
insp = reflection.Inspector.from_engine(engine)
|
||||||
|
has_column = False
|
||||||
|
for col in insp.get_columns(table):
|
||||||
|
if column not in col['name']:
|
||||||
|
continue
|
||||||
|
has_column = True
|
||||||
|
return has_column
|
||||||
0
property_files/.gitkeep
Normal file
0
property_files/.gitkeep
Normal file
6
pylama.ini
Normal file
6
pylama.ini
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[pylama]
|
||||||
|
ignore = D203, D212, D213, D406, D407, D408, D409, D100, D104, D401, F722
|
||||||
|
max_line_length = 160
|
||||||
|
|
||||||
|
[pylama:mccabe]
|
||||||
|
max-complexity = 35
|
||||||
80
weedeater.lxfml
Normal file
80
weedeater.lxfml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="3002">
|
||||||
|
<Part refID="0" designID="3002" materials="23">
|
||||||
|
<Bone refID="0" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,433.91998291015625,-65.199996948242188">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="1" designID="3002">
|
||||||
|
<Part refID="1" designID="3002" materials="23">
|
||||||
|
<Bone refID="1" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,432.95999145507812,-65.199996948242188">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="2" designID="3002">
|
||||||
|
<Part refID="2" designID="3002" materials="23">
|
||||||
|
<Bone refID="2" transformation="0,0,1,0,1,0,-1,0,0,-38.799999237060547,433.91998291015625,-61.199996948242188">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="3" designID="3002">
|
||||||
|
<Part refID="3" designID="3002" materials="23">
|
||||||
|
<Bone refID="3" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,432.95999145507812,-61.199996948242188">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="4" designID="3002">
|
||||||
|
<Part refID="4" designID="3002" materials="23">
|
||||||
|
<Bone refID="4" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,432.95999145507812,-68.399993896484375">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="5" designID="3002">
|
||||||
|
<Part refID="5" designID="3002" materials="23">
|
||||||
|
<Bone refID="5" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,432.95999145507812,-69.999992370605469">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="6" designID="3002">
|
||||||
|
<Part refID="6" designID="3002" materials="23">
|
||||||
|
<Bone refID="6" transformation="1,0,0,0,1,0,0,0,1,-34.799999237060547,433.91998291015625,-69.999992370605469">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="7" designID="3002">
|
||||||
|
<Part refID="7" designID="3002" materials="23">
|
||||||
|
<Bone refID="7" transformation="1,0,0,0,1,0,0,0,1,-34.799999237060547,432.95999145507812,-70.799995422363281">
|
||||||
|
</Bone>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
<RigidSystems>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="0" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,433.91998291015625,-65.199996948242188" boneRefs="0,1"/>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="1" transformation="0,0,1,0,1,0,-1,0,0,-38.799999237060547,433.91998291015625,-61.199996948242188" boneRefs="2,3"/>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="2" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,432.95999145507812,-68.399993896484375" boneRefs="4"/>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="3" transformation="1,0,0,0,1,0,0,0,1,-39.599998474121094,432.95999145507812,-69.999992370605469" boneRefs="5"/>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="4" transformation="1,0,0,0,1,0,0,0,1,-34.799999237060547,433.91998291015625,-69.999992370605469" boneRefs="6,7"/>
|
||||||
|
</RigidSystem>
|
||||||
|
</RigidSystems>
|
||||||
|
<GroupSystems>
|
||||||
|
<GroupSystem>
|
||||||
|
<Group transformation="1,0,0,0,1,0,0,0,1,0,0,0" pivot="0,0,0" partRefs="1,0,3"/>
|
||||||
|
</GroupSystem>
|
||||||
|
</GroupSystems>
|
||||||
|
</LXFML>
|
||||||
11
wsgi.py
11
wsgi.py
@@ -13,4 +13,13 @@ def make_shell_context():
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
app.run(host='0.0.0.0')
|
app.run(host='0.0.0.0')
|
||||||
|
else:
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
gunicorn_logger = logging.getLogger('gunicorn.error')
|
||||||
|
app.logger.handlers = gunicorn_logger.handlers
|
||||||
|
file_handler = RotatingFileHandler('nexus_dashboard.log', maxBytes=1024 * 1024 * 100, backupCount=20)
|
||||||
|
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
app.logger.addHandler(file_handler)
|
||||||
|
app.logger.setLevel(gunicorn_logger.level)
|
||||||
|
|||||||
Reference in New Issue
Block a user