mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Adds Auth Docs + Starting quizroom + stylise
This commit is contained in:
parent
8b518b9742
commit
115d3785ca
8 changed files with 487 additions and 4 deletions
2
documentation/dev.py
Normal file
2
documentation/dev.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
from plantuml import PlantUML
|
||||||
|
server = PlantUML(url='http://localhost:8080/plantuml')
|
||||||
288
documentation/docs/developpeur/backend/auth.md
Normal file
288
documentation/docs/developpeur/backend/auth.md
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
# Authentification
|
||||||
|
|
||||||
|
Le but du module d'authentification est de pouvoir facilement faire des blocks de code permettant une authentification personalisée. Il est possible de le faire grâce a cette architecture.
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
package Backend {
|
||||||
|
class AuthManager{
|
||||||
|
+IAuthModule[] auths
|
||||||
|
#userInfos
|
||||||
|
|
||||||
|
-load()
|
||||||
|
-registerAuths()
|
||||||
|
+showAuths()
|
||||||
|
|
||||||
|
+authStatus()
|
||||||
|
+logIn(UserInfos)
|
||||||
|
+register(UserInfos)
|
||||||
|
+logOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAuthModule{
|
||||||
|
+registerAuth()
|
||||||
|
+authenticate()
|
||||||
|
+register()
|
||||||
|
+showAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleFormAuthModule{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PassportAuthModule{
|
||||||
|
IPassportProviderDefinition[] providers
|
||||||
|
}
|
||||||
|
|
||||||
|
Interface IPassportProviderDefinition{
|
||||||
|
+name
|
||||||
|
+type
|
||||||
|
}
|
||||||
|
|
||||||
|
class OAuthPassportProvider{
|
||||||
|
+clientId
|
||||||
|
+clientSecret
|
||||||
|
+configUrl
|
||||||
|
+authorizeUrl
|
||||||
|
+tokenUrl
|
||||||
|
+userinfoUrl
|
||||||
|
+logoutUrl
|
||||||
|
+JWKSUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
IAuthModule <|-- SimpleFormAuthModule
|
||||||
|
IAuthModule <|-- PassportAuthModule
|
||||||
|
IPassportProviderDefinition <|-- OAuthPassportProvider
|
||||||
|
|
||||||
|
AuthManager -> IAuthModule
|
||||||
|
PassportAuthModule -> IPassportProviderDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
package Frontend{
|
||||||
|
class AuthDrawer{
|
||||||
|
+IAuthVisual[] getAuthsVisual()
|
||||||
|
+drawAuths()
|
||||||
|
}
|
||||||
|
|
||||||
|
Interface IAuthVisual{
|
||||||
|
+draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
class FormVisual{
|
||||||
|
+FormInput[] formInputs
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormInput{
|
||||||
|
+name
|
||||||
|
+label
|
||||||
|
+type
|
||||||
|
+value
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthDrawer -> IAuthVisual
|
||||||
|
IAuthVisual <|-- FormVisual
|
||||||
|
FormVisual -> FormInput
|
||||||
|
}
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Le fonctionnement peut être expliqué avec les diagrammes suivants :
|
||||||
|
|
||||||
|
|
||||||
|
## Module : Passport Js
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
box "Frontend"
|
||||||
|
participant User
|
||||||
|
Participant App
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "Backend"
|
||||||
|
participant PassportAuthModule
|
||||||
|
participant Db
|
||||||
|
participant AuthManager
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "Auth Server"
|
||||||
|
participant AuthServer
|
||||||
|
end box
|
||||||
|
|
||||||
|
User -> App : Get auth page
|
||||||
|
App -> User : auth page
|
||||||
|
|
||||||
|
User -> App : click OAuth button
|
||||||
|
App -> User : redirect to OAuth
|
||||||
|
|
||||||
|
User -> AuthServer: Login
|
||||||
|
AuthServer -> User: Redirect to Auth endpoint with token
|
||||||
|
|
||||||
|
User -> PassportAuthModule: Authenticate with token
|
||||||
|
|
||||||
|
PassportAuthModule -> AuthServer: get user info
|
||||||
|
AuthServer -> PassportAuthModule: userInfo
|
||||||
|
|
||||||
|
alt login
|
||||||
|
PassportAuthModule -> Db : fetch local userInfo
|
||||||
|
Db->PassportAuthModule: userInfo
|
||||||
|
PassportAuthModule -> PassportAuthModule: Merge userInfo definition
|
||||||
|
PassportAuthModule -> Db : update user profile
|
||||||
|
Db->PassportAuthModule: userInfo
|
||||||
|
end
|
||||||
|
|
||||||
|
alt register
|
||||||
|
PassportAuthModule -> Db : fetch local userInfo
|
||||||
|
Db->PassportAuthModule: null
|
||||||
|
PassportAuthModule -> Db : create user profile
|
||||||
|
Db->PassportAuthModule: userInfo
|
||||||
|
end
|
||||||
|
|
||||||
|
PassportAuthModule -> AuthManager : login(userInfos)
|
||||||
|
|
||||||
|
AuthManager -> User: Give refresh token + Redirect to page
|
||||||
|
User -> App: get /
|
||||||
|
App -> User: Show Authenticated /
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module : SimpleAuth
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
box "Frontend"
|
||||||
|
participant User
|
||||||
|
Participant App
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "Backend"
|
||||||
|
participant SimpleAuthModule
|
||||||
|
participant Db
|
||||||
|
participant AuthManager
|
||||||
|
end box
|
||||||
|
|
||||||
|
User -> App : Get auth page
|
||||||
|
App -> User : auth page
|
||||||
|
|
||||||
|
|
||||||
|
alt Login
|
||||||
|
User -> App : Send Login/Pass
|
||||||
|
|
||||||
|
App -> SimpleAuthModule: Send login/pass
|
||||||
|
|
||||||
|
SimpleAuthModule -> Db: get user info
|
||||||
|
Db->SimpleAuthModule: user info
|
||||||
|
SimpleAuthModule -> SimpleAuthModule: Validate Hash
|
||||||
|
end
|
||||||
|
|
||||||
|
alt register
|
||||||
|
User -> App : Send Username + Password + Email
|
||||||
|
|
||||||
|
App -> SimpleAuthModule: Send Username + Password + Email
|
||||||
|
|
||||||
|
SimpleAuthModule -> Db: get user info
|
||||||
|
Db -> SimpleAuthModule : null
|
||||||
|
|
||||||
|
SimpleAuthModule -> Db: put user info
|
||||||
|
end
|
||||||
|
|
||||||
|
SimpleAuthModule -> AuthManager: userInfo
|
||||||
|
AuthManager -> User: Give refresh token + Redirect to page
|
||||||
|
User -> App: get /
|
||||||
|
App -> User: Show Authenticated /
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comment les boutons sont affichés
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
box "FrontEnd"
|
||||||
|
participant User
|
||||||
|
Participant FrontEnd
|
||||||
|
Participant AuthDrawer
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "BackEnd"
|
||||||
|
participant API
|
||||||
|
participant AuthManager
|
||||||
|
participant Db
|
||||||
|
participant IAuthModule
|
||||||
|
end box
|
||||||
|
|
||||||
|
API -> API : load global configurations
|
||||||
|
|
||||||
|
create AuthManager
|
||||||
|
API -> AuthManager : instanciate with auth configurations
|
||||||
|
|
||||||
|
|
||||||
|
create IAuthModule
|
||||||
|
AuthManager -> IAuthModule : instanciate array
|
||||||
|
|
||||||
|
loop For each auth in auths
|
||||||
|
AuthManager -> IAuthModule : register
|
||||||
|
IAuthModule -> API : register routes
|
||||||
|
API -> IAuthModule : route registration confirmation
|
||||||
|
IAuthModule -> AuthManager : module registration confirmation
|
||||||
|
end
|
||||||
|
|
||||||
|
User -> FrontEnd : get login page
|
||||||
|
|
||||||
|
alt already logged in
|
||||||
|
FrontEnd -> User: redirected to authenticated page
|
||||||
|
end
|
||||||
|
|
||||||
|
FrontEnd -> AuthDrawer : get auth visual
|
||||||
|
AuthDrawer -> API : get auth form data
|
||||||
|
|
||||||
|
API -> AuthManager : get auth form data
|
||||||
|
|
||||||
|
|
||||||
|
loop For each auth in auths
|
||||||
|
AuthManager -> IAuthModule : get form data
|
||||||
|
IAuthModule -> AuthManager : form data
|
||||||
|
end
|
||||||
|
|
||||||
|
AuthManager -> API : auth fom data
|
||||||
|
API -> AuthDrawer : auth form data
|
||||||
|
|
||||||
|
AuthDrawer -> AuthDrawer : make auth html
|
||||||
|
AuthDrawer -> FrontEnd : auth HTML
|
||||||
|
FrontEnd -> User : show auth page
|
||||||
|
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comment les sessions sont conservées
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
box "Frontend"
|
||||||
|
participant User
|
||||||
|
Participant App
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "Backend"
|
||||||
|
participant AuthManager
|
||||||
|
participant IAuthModules
|
||||||
|
end box
|
||||||
|
|
||||||
|
App -> AuthManager : send refresh token
|
||||||
|
|
||||||
|
AuthManager -> IAuthModules: ForEach check if logged
|
||||||
|
IAuthModules -> AuthManager: is authenticated ?
|
||||||
|
|
||||||
|
alt one logged in
|
||||||
|
AuthManager -> App : send new token
|
||||||
|
end
|
||||||
|
|
||||||
|
alt all logged out
|
||||||
|
AuthManager -> App : send error
|
||||||
|
App -> App : destroy token
|
||||||
|
App -> User : redirect to login page
|
||||||
|
end
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
146
documentation/docs/developpeur/backend/salle-de-quiz.md
Normal file
146
documentation/docs/developpeur/backend/salle-de-quiz.md
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Salles de Quiz
|
||||||
|
|
||||||
|
Les salles de quiz ont été extraites dans leur propre container afin de limiter les dégats liés soit a une surutilisation d'une salle ou une attaque sur le logiciel.
|
||||||
|
|
||||||
|
En éffet, le découplement permet a un quiz de:
|
||||||
|
|
||||||
|
- Survivre même si le backend est non-fonctionnel
|
||||||
|
- Mourir sans entrainer toute l'application avec elle
|
||||||
|
- Créer/Supprimer des salles automatiquement dépendant de la demande
|
||||||
|
|
||||||
|
Pour éffectuer ceci il faut éffectuer une petite gymnastique. Il y a une route dans l'api servant à gerer les salles. Lorsqu'un utilisateur demande le socket d'une salle : "/api/rooms/{id}/socket", la requette rebondit sur le proxy Nginx. Celui-ci contacte le backend afin d'obtenir l'addresse de l'ordinateur auquel envoyer la requette et redirige le socket vers ce pc.
|
||||||
|
|
||||||
|
|
||||||
|
## Diagramme de séquence
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
actor Teacher
|
||||||
|
actor Student
|
||||||
|
entity Nginx
|
||||||
|
entity Frontend
|
||||||
|
entity Api
|
||||||
|
entity Docker
|
||||||
|
entity Database
|
||||||
|
|
||||||
|
group Quiz Creation
|
||||||
|
Teacher -> Frontend : Create a quizroom
|
||||||
|
Frontend -> Api : Create a quizroom
|
||||||
|
Api -> Docker : Create a quizroom
|
||||||
|
Docker -> QuizRoom **
|
||||||
|
QuizRoom -> Docker : creation successful
|
||||||
|
Docker -> Api : Creation Successful
|
||||||
|
|
||||||
|
loop every seconds until healthy or 30s:
|
||||||
|
Api -> QuizRoom : Checking Health via /health
|
||||||
|
QuizRoom -> Api : Doesn't answer, answer healthy or unhealthy
|
||||||
|
end
|
||||||
|
|
||||||
|
Api -> Database : Create Room
|
||||||
|
Database -> Api : Room created
|
||||||
|
Api -> Teacher : Route to room socket
|
||||||
|
end
|
||||||
|
|
||||||
|
group Quiz Joining:
|
||||||
|
Teacher -> Nginx : Join Room
|
||||||
|
Nginx -> Api : Get room infos from id
|
||||||
|
Api -> Nginx : Ip:port of room
|
||||||
|
Nginx -> QuizRoom: Give teacher's connexion
|
||||||
|
|
||||||
|
Student -> Frontend: Join Room X
|
||||||
|
Frontend -> Nginx : Join Room X
|
||||||
|
Nginx -> Api : Get room infos from id
|
||||||
|
Api -> Nginx : Ip:port of room
|
||||||
|
Nginx -> QuizRoom: Give student's connexion
|
||||||
|
|
||||||
|
QuizRoom -> QuizRoom : Give Quiz ... (Multiple actions)
|
||||||
|
|
||||||
|
Student -> QuizRoom: Disconnect
|
||||||
|
Teacher -> QuizRoom: Disconect
|
||||||
|
end
|
||||||
|
|
||||||
|
group QuizManagement (Every 10 seconds)
|
||||||
|
Api -> QuizRoom : Checking number of people in the room
|
||||||
|
QuizRoom -> Api : Number of people (0) or Unhealthy
|
||||||
|
Api -> Database : Mark room to deletion
|
||||||
|
end
|
||||||
|
|
||||||
|
group Quiz Deletion (Every 30 seconds)
|
||||||
|
Api -> Database : Give all rooms marked for deletion
|
||||||
|
Database -> Api : rooms
|
||||||
|
Api -> Docker : delete rooms
|
||||||
|
Docker -> QuizRoom : delete
|
||||||
|
Docker -> Api : Deleted
|
||||||
|
end
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Classes touchant cette fonctionalitée
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
class Room{
|
||||||
|
+id
|
||||||
|
+name
|
||||||
|
+host
|
||||||
|
+nbStudents
|
||||||
|
+mustBeCleaned
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomRepository {
|
||||||
|
+get(id)
|
||||||
|
+create(room)
|
||||||
|
+delete(id)
|
||||||
|
+update(room,id)
|
||||||
|
+getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomController {
|
||||||
|
+setupRoom(options)
|
||||||
|
+deleteRoom(roomId)
|
||||||
|
+listRooms()
|
||||||
|
+getRoomStatus(roomId)
|
||||||
|
+updateRoom(room,roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomRouter{
|
||||||
|
+ / : GET
|
||||||
|
+ /:id : GET
|
||||||
|
+ / : POST
|
||||||
|
+ /:id : PUT
|
||||||
|
+ /:id : DELETE
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseRoomProvider {
|
||||||
|
+createRoom(roomid,options)
|
||||||
|
+deleteRoom(roomId)
|
||||||
|
+getRoomInfo(roomId)
|
||||||
|
+getRoomStatus(roomId)
|
||||||
|
+listRooms()
|
||||||
|
-cleanup()
|
||||||
|
-syncInstantiatedRooms()
|
||||||
|
#updateRoomInfos()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DockerRoomProvider
|
||||||
|
circle Dockerode
|
||||||
|
|
||||||
|
|
||||||
|
Room - RoomRepository
|
||||||
|
BaseRoomProvider o-- RoomRepository
|
||||||
|
DockerRoomProvider --|> BaseRoomProvider
|
||||||
|
DockerRoomProvider - Dockerode
|
||||||
|
Dockerode o-- QuizRoom
|
||||||
|
RoomController o-- BaseRoomProvider
|
||||||
|
RoomRouter o-- RoomController
|
||||||
|
|
||||||
|
class QuizRoom{
|
||||||
|
+create-room()
|
||||||
|
+join-room()
|
||||||
|
+next-question()
|
||||||
|
+launch-student-mode()
|
||||||
|
+end-quiz()
|
||||||
|
+submit-answers()
|
||||||
|
-disconnect()
|
||||||
|
}
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
@ -5,7 +5,7 @@ Nous avons choisi d'exécuter les composantes de cette application avec Docker,
|
||||||
|
|
||||||
Voici un diagramme de déploiement expliquant la relation des composantes et comment les images Docker sont créées et déployées dans un serveur.
|
Voici un diagramme de déploiement expliquant la relation des composantes et comment les images Docker sont créées et déployées dans un serveur.
|
||||||
|
|
||||||
```puml
|
```plantuml
|
||||||
@startuml
|
@startuml
|
||||||
[Navigateur moderne (Windows/Android)] as Navigateur
|
[Navigateur moderne (Windows/Android)] as Navigateur
|
||||||
[MongoDB] as MongoDB
|
[MongoDB] as MongoDB
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
Pour lancer la documentation, il faut installer python et entrer dans le dossier documentation.
|
Pour lancer la documentation, il faut installer python et entrer dans le dossier documentation.
|
||||||
Il faut ensuite installer les dépendances avec `pip install -r requirements.txt`.
|
Il faut ensuite installer les dépendances avec `pip install -r requirements.txt`.
|
||||||
Pour lancer le mode développement il faut executer `python -m mkdocs serve`
|
Pour lancer le mode développement il faut executer `python -m mkdocs serve`
|
||||||
|
Afin d'accellerer le déploiement et ne pas être touché par des érreurs de "rate-limiting", il est préférable d'utiliser une image docker de plantuml. Pour cela, il faut utiliser la commande suivante : `docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat`
|
||||||
|
|
||||||
## Deploiement
|
## Deploiement
|
||||||
Le code est automatiquement déployé par la github-action `create-docs.yaml`
|
Le code est automatiquement déployé par la github-action `create-docs.yaml`
|
||||||
|
|
|
||||||
13
documentation/docs/utilisateur/deploiment.md
Normal file
13
documentation/docs/utilisateur/deploiment.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Déploiement
|
||||||
|
|
||||||
|
Les méthodes recommandées de déploiement sont via Ansible et Opentofu.
|
||||||
|
Ansible est utilisés afin de faire un déploiement sur un serveur local, opentofu sur le cloud.
|
||||||
|
|
||||||
|
## Ansible
|
||||||
|
|
||||||
|
Le déploiement avec ansible est un déploiement simplifié.
|
||||||
|
Il vous suffis d'avoir un ordinateur linux/mac ou pouvant faire exécuter [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) dans le cas de windows. Il faut ensuite utiliser le gestionnaire de paquet (souvent apt) afin d'installer le paquet `ansible-core`, d'autres méthodes sont indiquées dans la [documentation officielle de ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html). Une fois le tout fais, vous pouvez telécharger [les fichiers nécéssaire](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/ansible) et lancer la commande `ansible-playbook -i inventory.ini deploy.yml`
|
||||||
|
|
||||||
|
## OpenTofu
|
||||||
|
Le déploiement avec OpenTofu est un peu plus complexe mais il permet d'héberger la solution sur votre cloud préféré.
|
||||||
|
Il suffit [d'installer OpenTofu](https://opentofu.org/docs/intro/install/) et de téléchgarger [les fichiers nécéssaires](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/opentofu). Un Readme est inclus afin d'organiser votre grape de serveurs.
|
||||||
|
|
@ -1,12 +1,38 @@
|
||||||
site_name: EvalueTonSavoir
|
site_name: EvalueTonSavoir
|
||||||
|
repo_url: https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir
|
||||||
|
edit_uri: edit/main/documentation/docs
|
||||||
|
|
||||||
theme:
|
theme:
|
||||||
|
language: fr
|
||||||
|
icon:
|
||||||
|
repo: fontawesome/brands/github
|
||||||
name: material
|
name: material
|
||||||
|
palette:
|
||||||
|
# Palette toggle for light mode
|
||||||
|
- media: "(prefers-color-scheme: light)"
|
||||||
|
scheme: default
|
||||||
|
primary: deep purple
|
||||||
|
accent: purple
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-7
|
||||||
|
name: Mode sombre
|
||||||
|
|
||||||
|
# Palette toggle for dark mode
|
||||||
|
- media: "(prefers-color-scheme: dark)"
|
||||||
|
scheme: slate
|
||||||
|
primary: deep purple
|
||||||
|
accent: purple
|
||||||
|
toggle:
|
||||||
|
icon: material/brightness-4
|
||||||
|
name: Mode clair
|
||||||
features:
|
features:
|
||||||
- content.code.copy
|
- content.code.copy
|
||||||
- content.code.select
|
- content.code.select
|
||||||
- content.code.annotate
|
- content.code.annotate
|
||||||
locale: fr
|
- navigation.instant
|
||||||
|
- navigation.instant.progress
|
||||||
|
- navigation.tracking
|
||||||
|
- content.action.edit
|
||||||
highlightjs: true
|
highlightjs: true
|
||||||
hljs_languages:
|
hljs_languages:
|
||||||
- javascript
|
- javascript
|
||||||
|
|
@ -22,8 +48,14 @@ use_directory_urls: false
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
|
- offline
|
||||||
- plantuml:
|
- plantuml:
|
||||||
puml_url: https://www.plantuml.com/plantuml/
|
puml_url: http://localhost:8080/plantuml # dev
|
||||||
|
puml_url: https://www.plantuml.com/plantuml/ # default
|
||||||
|
puml_keyword: plantuml
|
||||||
|
theme:
|
||||||
|
light: material/deep-purple-light
|
||||||
|
dark: material/deep-purple-dark
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- pymdownx.highlight:
|
- pymdownx.highlight:
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,5 @@ mkdocs[i18n]
|
||||||
mkdocs_puml
|
mkdocs_puml
|
||||||
mkdocs-material
|
mkdocs-material
|
||||||
Pygments
|
Pygments
|
||||||
ghp-import
|
ghp-import
|
||||||
|
plantuml
|
||||||
Loading…
Reference in a new issue