initial commit and v1?
2
.gitignore
vendored
|
@ -10,3 +10,5 @@ Cargo.lock
|
|||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
/config.toml
|
||||
/db/sncf.sqlite
|
||||
|
|
23
Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "sncf"
|
||||
version = "1.0.0"
|
||||
authors = ["Association 42l <contact@noreply.example.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
actix-web = "3.0.0-beta.3"
|
||||
actix-files = "0.3.0-beta.1"
|
||||
diesel = { version = "1.4", features = ["sqlite", "r2d2", "chrono"] }
|
||||
diesel_migrations = "1.4"
|
||||
url = "2.0"
|
||||
toml = "0.5"
|
||||
lazy_static = "1.4"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
regex = "1.3"
|
||||
base64 = "0.12"
|
||||
rand = "0.7"
|
||||
askama = "0.10"
|
141
adj-list.txt
Normal file
|
@ -0,0 +1,141 @@
|
|||
Adorable
|
||||
Adventurous
|
||||
Aggressive
|
||||
Agreeable
|
||||
Ambitious
|
||||
Amused
|
||||
Annoying
|
||||
Ashy
|
||||
Attractive
|
||||
Beautiful
|
||||
Better
|
||||
Black
|
||||
Blue
|
||||
Blushing
|
||||
Brave
|
||||
Bright
|
||||
Busy
|
||||
Calm
|
||||
Careful
|
||||
Cautious
|
||||
Charming
|
||||
Cheerful
|
||||
Clever
|
||||
Clumsy
|
||||
Colorful
|
||||
Combative
|
||||
Confident
|
||||
Cooperative
|
||||
Courageous
|
||||
Crazy
|
||||
Creative
|
||||
Creepy
|
||||
Cruel
|
||||
Curious
|
||||
Cute
|
||||
Dangerous
|
||||
Dark
|
||||
Dazzling
|
||||
Delightful
|
||||
Determined
|
||||
Different
|
||||
Distinct
|
||||
Dizzy
|
||||
Eager
|
||||
Elegant
|
||||
Embarrassed
|
||||
Enchanting
|
||||
Encouraging
|
||||
Energetic
|
||||
Enthusiastic
|
||||
Evil
|
||||
Excited
|
||||
Fair
|
||||
Faithful
|
||||
Famous
|
||||
Fancy
|
||||
Fantastic
|
||||
Fierce
|
||||
Fine
|
||||
Friendly
|
||||
Funny
|
||||
Gentle
|
||||
Gifted
|
||||
Glad
|
||||
Glamorous
|
||||
Gleaming
|
||||
Glorious
|
||||
Good
|
||||
Gorgeous
|
||||
Graceful
|
||||
Gray
|
||||
Green
|
||||
Grumpy
|
||||
Handsome
|
||||
Happy
|
||||
Hardy
|
||||
Healthy
|
||||
Helpful
|
||||
Hilarious
|
||||
Homely
|
||||
Hungry
|
||||
Icy
|
||||
Important
|
||||
Innocent
|
||||
Inquisitive
|
||||
Jolly
|
||||
Joyful
|
||||
Joyous
|
||||
Kind
|
||||
Lazy
|
||||
Light
|
||||
Lively
|
||||
Lovely
|
||||
Loving
|
||||
Lucky
|
||||
Magnificent
|
||||
Marvelous
|
||||
Misty
|
||||
Modern
|
||||
Muscular
|
||||
Mysterious
|
||||
Nice
|
||||
Obedient
|
||||
Optimistic
|
||||
Orange
|
||||
Outstanding
|
||||
Perfect
|
||||
Pleasant
|
||||
Polite
|
||||
Powerful
|
||||
Precious
|
||||
Proud
|
||||
Purple
|
||||
Red
|
||||
Scary
|
||||
Shiny
|
||||
Shy
|
||||
Silly
|
||||
Skinny
|
||||
Sleepy
|
||||
Smiling
|
||||
Sparkling
|
||||
Splendid
|
||||
Spotless
|
||||
Stormy
|
||||
Strange
|
||||
Super
|
||||
Talented
|
||||
Tenacious
|
||||
Thankful
|
||||
Thoughtful
|
||||
Tired
|
||||
Tough
|
||||
Unusual
|
||||
Victorious
|
||||
Vivacious
|
||||
White
|
||||
Wild
|
||||
Witty
|
||||
Wonderful
|
||||
Yellow
|
26
config.toml.sample
Normal file
|
@ -0,0 +1,26 @@
|
|||
# The address and port sncf will listen
|
||||
listening_address = "0.0.0.0"
|
||||
listening_port = 8000
|
||||
|
||||
# Public-facing domain for sncf.
|
||||
# includes protocol, FQDN and port, without the trailing slash.
|
||||
sncf_url = "http://localhost:8000"
|
||||
|
||||
# path to the SQLite DB
|
||||
database_path = "./db/sncf.sqlite"
|
||||
|
||||
# IP address of the Nextcloud instance, including protocol and port
|
||||
nextcloud_url = "http://10.0.0.0"
|
||||
|
||||
# Nextcloud admin account
|
||||
admin_username = "adminusername"
|
||||
admin_password = "adminverylongandsecurepassword"
|
||||
|
||||
# How many days of inactivity for an admin token before deleting NC accounts
|
||||
prune_days = 150
|
||||
|
||||
# Displays route names and a lot of information
|
||||
debug_mode = true
|
||||
|
||||
# Don't touch this unless you know what you're doing
|
||||
config_version = 1
|
0
db/.gitkeep
Normal file
0
db/db.sqlite
Normal file
322
lang.json
Normal file
|
@ -0,0 +1,322 @@
|
|||
{
|
||||
"lang_code": {
|
||||
"en": "en",
|
||||
"fr": "fr"
|
||||
},
|
||||
"lang_full": {
|
||||
"en": "English",
|
||||
"fr": "Français"
|
||||
},
|
||||
"meta_description": {
|
||||
"en": "42l Forms : create forms for free, without registration while protecting your privacy",
|
||||
"fr": "42l Formulaires (Forms) : créez des formulaires ou questionnaires gratuitement, sans inscription et dans le respect de votre vie privée"
|
||||
},
|
||||
"index_title": {
|
||||
"en": "42l Forms",
|
||||
"fr": "42l Formulaires"
|
||||
},
|
||||
"index_description": {
|
||||
"en": "Create forms without registration",
|
||||
"fr": "Créez des questionnaires sans inscription"
|
||||
},
|
||||
"index_beta_tag": {
|
||||
"en": "BETA",
|
||||
"fr": "BETA"
|
||||
},
|
||||
"index_createform_button": {
|
||||
"en": "Create a form",
|
||||
"fr": "Créer un formulaire"
|
||||
},
|
||||
"index_beta_banner_title": {
|
||||
"en": "Warning: Service in beta.",
|
||||
"fr": "Attention : Service en bêta."
|
||||
},
|
||||
"index_beta_banner_desc1": {
|
||||
"en": "This service is currently under development and might behave in an unexpected way.",
|
||||
"fr": "Ce service est en cours de développement et pourrait se comporter de manière inattendue."
|
||||
},
|
||||
"index_beta_banner_desc2": {
|
||||
"en": "Feel free to send feedbacks on our ",
|
||||
"fr": "Vous pouvez nous envoyer vos retours sur "
|
||||
},
|
||||
"index_beta_banner_desc_link": {
|
||||
"en": "our contact page",
|
||||
"fr": "notre page de contact"
|
||||
},
|
||||
"index_disclaimer1": {
|
||||
"en": "This service is maintained for free, without subscription nor advertising nor tracking or selling of your personal data, on a server hosted in France.",
|
||||
"fr": "Ce service vous est fourni gratuitement, sans inscription, sans publicités, sans pistage ou revente de vos données personnelles, sur un serveur hébergé en France."
|
||||
},
|
||||
"index_disclaimer2": {
|
||||
"en": "If you appreciate our work, please consider donating to ",
|
||||
"fr": "Si vous appréciez notre travail, merci d'envisager de faire un don à "
|
||||
},
|
||||
"index_disclaimer2_link_org": {
|
||||
"en": "the 42l association",
|
||||
"fr": "l'association 42l"
|
||||
},
|
||||
"index_disclaimer2_or": {
|
||||
"en": " or ",
|
||||
"fr": " ou à "
|
||||
},
|
||||
"index_disclaimer2_nc": {
|
||||
"en": "Nextcloud",
|
||||
"fr": "Nextcloud"
|
||||
},
|
||||
"index_panel1_title": {
|
||||
"en": "Responsive and intuitive interface",
|
||||
"fr": "Interface intuitive et compatible mobile"
|
||||
},
|
||||
"index_panel1_desc1": {
|
||||
"en": "Are you searching for a privacy-friendly alternative to Google Forms while keeping its ease of use?",
|
||||
"fr": "Cherchez-vous une alternative éthique à Google Forms qui reste simple d'utilisation ?"
|
||||
},
|
||||
"index_panel1_desc2": {
|
||||
"en": "You've just found it.",
|
||||
"fr": "Vous venez de la trouver."
|
||||
},
|
||||
"index_panel2_title": {
|
||||
"en": "Choose and order your fields",
|
||||
"fr": "Choisissez et ordonnez vos champs"
|
||||
},
|
||||
"index_panel2_desc1": {
|
||||
"en": "The software currently supports five field types.",
|
||||
"fr": "Pour le moment, le logiciel supporte cinq types de champs."
|
||||
},
|
||||
"index_panel2_desc2": {
|
||||
"en": "New field types are ",
|
||||
"fr": "De nouveaux types de champs sont "
|
||||
},
|
||||
"index_panel2_desc2_link": {
|
||||
"en": "currently in the works",
|
||||
"fr": "en cours d'élaboration"
|
||||
},
|
||||
"index_panel3_title": {
|
||||
"en": "Analyze the answers",
|
||||
"fr": "Analysez les réponses"
|
||||
},
|
||||
"index_panel3_desc1": {
|
||||
"en": "See detailed graphs of the answers to your form.",
|
||||
"fr": "Visualisez les réponses à votre formulaire avec un graphique."
|
||||
},
|
||||
"index_panel4_title": {
|
||||
"en": "Export the answers",
|
||||
"fr": "Exportez les réponses"
|
||||
},
|
||||
"index_panel4_desc1": {
|
||||
"en": "Export the raw data of your form in CSV format to integrate the answers in other software (e.g. LibreOffice Calc or Microsoft Excel).",
|
||||
"fr": "Exportez les données brutes de votre formulaire en format CSV pour intégrer les réponses dans d'autres logiciels (ex. LibreOffice Calc ou Microsoft Excel)."
|
||||
},
|
||||
"index_panel5_title": {
|
||||
"en": "Edit your form's settings",
|
||||
"fr": "Paramétrez vos formulaires"
|
||||
},
|
||||
"index_panel5_desc1": {
|
||||
"en": "Use the share link to send your form to other people.",
|
||||
"fr": "Utilisez le lien de partage pour envoyer votre formulaire à d'autres personnes."
|
||||
},
|
||||
"index_panel5_desc2": {
|
||||
"en": "You can also define an expiration date for your form.",
|
||||
"fr": "Vous pouvez également définir une date d'expiration pour votre formulaire."
|
||||
},
|
||||
"index_panel6_title": {
|
||||
"en": "All your forms in one place",
|
||||
"fr": "Tous vos formulaires au même endroit"
|
||||
},
|
||||
"index_panel6_desc1": {
|
||||
"en": "Find all your forms in the same panel.",
|
||||
"fr": "Retrouvez tous vos formulaires sur un même panel."
|
||||
},
|
||||
"index_bottom_docs": {
|
||||
"en": "Documentation",
|
||||
"fr": "Documentation"
|
||||
},
|
||||
"index_bottom_source": {
|
||||
"en": "Source code",
|
||||
"fr": "Code source"
|
||||
},
|
||||
"index_bottom_lic": {
|
||||
"en": "License",
|
||||
"fr": "Licence"
|
||||
},
|
||||
"index_credits_title": {
|
||||
"en": "Credits",
|
||||
"fr": "Crédits"
|
||||
},
|
||||
"index_credits_desc1": {
|
||||
"en": "The Nextcloud software suite and the Nextcloud Forms application has been developed by ",
|
||||
"fr": "La suite logicielle Nextcloud et l'application Nextcloud Forms a été développée par "
|
||||
},
|
||||
"index_credits_desc1_link": {
|
||||
"en": "the Nextcloud team",
|
||||
"fr": "l'équipe Nextcloud"
|
||||
},
|
||||
"index_credits_desc1_a": {
|
||||
"en": " and its contributors.",
|
||||
"fr": " et ses contributeur·ices."
|
||||
},
|
||||
"index_credits_desc2": {
|
||||
"en": "The Simple Nextcloud Forms software, which simplifies the form creation process, has been developed by ",
|
||||
"fr": "Le logiciel Simple Nextcloud Forms, qui simplifie la création de formulaires, a été développé par "
|
||||
},
|
||||
"index_credits_desc2_for": {
|
||||
"en": " for ",
|
||||
"fr": " pour "
|
||||
},
|
||||
"index_credits_desc2_org": {
|
||||
"en": "the 42l association",
|
||||
"fr": "l'association 42l"
|
||||
},
|
||||
"index_credits_desc3": {
|
||||
"en": "(sources available soon)",
|
||||
"fr": "(sources bientôt disponibles)"
|
||||
},
|
||||
"link_title": {
|
||||
"en": "Link created",
|
||||
"fr": "Lien créé"
|
||||
},
|
||||
"link_desc1": {
|
||||
"en": "Here's an <b>administration link</b>, which will allow you to access all your forms and check your answers.",
|
||||
"fr": "Voici un <b>lien d'administration</b>, qui vous permettra d'accéder à tous vos formulaires et de consulter vos réponses."
|
||||
},
|
||||
"link_desc2": {
|
||||
"en": "<b>Keep it</b> carefully and don't give it away (it'd be the same as giving out your password!).",
|
||||
"fr": "<b>Conservez-le</B> bien précieusement et ne le donnez pas (cela reviendrait à donner un mot de passe !)."
|
||||
},
|
||||
"link_desc3": {
|
||||
"en": "Once your link copied, click on the button below to start editing your forms.",
|
||||
"fr": "Une fois votre lien copié, cliquez sur le bouton ci-dessous pour commencer à éditer vos formulaires."
|
||||
},
|
||||
"link_access_btn": {
|
||||
"en": "Access the forms",
|
||||
"fr": "Accéder aux formulaires"
|
||||
},
|
||||
"link_note": {
|
||||
"en": "Note: If you don't use your administration link during more than ",
|
||||
"fr": "Note : Si vous n'utilisez pas votre lien d'administration pendant plus de "
|
||||
},
|
||||
"link_note2": {
|
||||
"en": " days, your forms will be automatically deleted.",
|
||||
"fr": " jours, vos formulaires seront automatiquement supprimés."
|
||||
},
|
||||
"link_copy": {
|
||||
"en": "Copy link",
|
||||
"fr": "Copier le lien"
|
||||
},
|
||||
"link_copied": {
|
||||
"en": "Link copied!",
|
||||
"fr": "Lien copié !"
|
||||
},
|
||||
"error_title": {
|
||||
"en": "Oops!...",
|
||||
"fr": "Oups !..."
|
||||
},
|
||||
"error_description": {
|
||||
"en": "The application encountered a problem:",
|
||||
"fr": "L'application a rencontré un problème :"
|
||||
},
|
||||
"error_back": {
|
||||
"en": "Back to the main page",
|
||||
"fr": "Retour à la page principale"
|
||||
},
|
||||
"error_note1": {
|
||||
"en": "We are (probably) aware of this bug, but feel free to contact us if you need assistance.",
|
||||
"fr": "Nous sommes (probablement) au courant, mais n'hésitez pas à nous contacter si vous avez besoin d'aide."
|
||||
},
|
||||
"error_note2": {
|
||||
"en": "Sorry for the inconvenience.",
|
||||
"fr": "Désolés pour les désagréments occasionnés."
|
||||
},
|
||||
"error_forward_req": {
|
||||
"en": "Error while connecting to the Nextcloud instance.",
|
||||
"fr": "Erreur lors de la connexion à l'instance Nextcloud."
|
||||
},
|
||||
"error_forward_resp": {
|
||||
"en": "Error while reading Nextcloud instance's response.",
|
||||
"fr": "Erreur lors de la lecture de la réponse de l'instance Nextcloud."
|
||||
},
|
||||
"error_forward_isanon": {
|
||||
"en": "Couldn't set the form's isAnonymous value.",
|
||||
"fr": "Échec lors de la définition de la valeur isAnonymous du formulaire."
|
||||
},
|
||||
"error_forwardlogin_db": {
|
||||
"en": "Couldn't connect to the local database.",
|
||||
"fr": "Échec lors de la connexion à la base de données locale."
|
||||
},
|
||||
"error_forwardlogin_db_get": {
|
||||
"en": "Error during information retrieval from the local database.",
|
||||
"fr": "Erreur lors de la récupération des informations dans la base de données locale."
|
||||
},
|
||||
"error_login_get": {
|
||||
"en": "The account creation request (GET) to Nextcloud has failed.",
|
||||
"fr": "La requête de création de compte (GET) vers l'instance Nextcloud a échoué."
|
||||
},
|
||||
"error_login_get_body": {
|
||||
"en": "Reading response from the account creation request to Nextcloud has failed.",
|
||||
"fr": "La lecture de la réponse à la requête de création de compte vers l'instance Nextcloud a échoué."
|
||||
},
|
||||
"error_login_post": {
|
||||
"en": "The account creation request (POST) to Nextcloud has failed.",
|
||||
"fr": "La requête de création de compte (POST) vers l'instance Nextcloud a échoué."
|
||||
},
|
||||
"error_login_redir": {
|
||||
"en": "Redirection to Nextcloud accout failed.",
|
||||
"fr": "La redirection vers le compte Nextcloud a échoué."
|
||||
},
|
||||
"error_createaccount_post": {
|
||||
"en": "Account creation: connection to the Nextcloud API failed.",
|
||||
"fr": "Création de compte : la connexion à l'API Nextcloud a échoué."
|
||||
},
|
||||
"error_createaccount_post_body": {
|
||||
"en": "Account creation: reading the answer from the Nextcloud API failed.",
|
||||
"fr": "Création de compte : le traitement de la réponse de l'API Nextcloud a échoué."
|
||||
},
|
||||
"error_createaccount_status": {
|
||||
"en": "The Nextcloud instance responded with an unexpected status code.",
|
||||
"fr": "L'instance Nextcloud a répondu avec un code de statut inattendu."
|
||||
},
|
||||
"error_createaccount_ncstatus": {
|
||||
"en": "The Nextcloud API responded with an unexpected status code.",
|
||||
"fr": "L'API Nextcloud a répondu avec un code de statut inattendu."
|
||||
},
|
||||
"error_createaccount_ncstatus_parse": {
|
||||
"en": "Error parsing Nextcloud API's status code.",
|
||||
"fr": "Erreur lors de la lecture du code de statut de l'API Nextcloud."
|
||||
},
|
||||
"error_forwardregister_pool": {
|
||||
"en": "Error while connecting to the local database.",
|
||||
"fr": "Erreur lors de la connexion à la base de données locale."
|
||||
},
|
||||
"error_forwardregister_db": {
|
||||
"en": "Failed adding the Nextcloud account in the local database.",
|
||||
"fr": "L'ajout du compte Nextcloud dans la base de données locale a échoué."
|
||||
},
|
||||
"error_login_cookiepair": {
|
||||
"en": "Couldn't read cookies.",
|
||||
"fr": "Échec lors de la lecture de cookies."
|
||||
},
|
||||
"error_login_regex": {
|
||||
"en": "Couldn't read the CSRF token.",
|
||||
"fr": "Échec lors de la lecture du token CSRF."
|
||||
},
|
||||
"error_login_setcookie": {
|
||||
"en": "Error during cookies transfer.",
|
||||
"fr": "Erreur lors du transfert de cookies."
|
||||
},
|
||||
"error_form_insert": {
|
||||
"en": "The local database couldn't be reached",
|
||||
"fr": "Échec de la connexion avec la base de données locale"
|
||||
},
|
||||
"error_createaccount": {
|
||||
"en": "The Nextcloud API returned an unexpected result",
|
||||
"fr": "L'API de Nextcloud a retourné un résultat inattendu"
|
||||
},
|
||||
"error_dirtyhacker": {
|
||||
"en": "Attempt to access an unauthorized resource.",
|
||||
"fr": "Tentative d'accès à une ressource non autorisée."
|
||||
},
|
||||
"error_tplrender": {
|
||||
"en": "Template rendering failed.",
|
||||
"fr": "Le rendu du template a échoué."
|
||||
}
|
||||
}
|
1
migrations/20200809180000_create_form/down.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DELETE TABLE form;
|
8
migrations/20200809180000_create_form/up.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE form (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
lastvisit_at TIMESTAMP NOT NULL,
|
||||
token VARCHAR UNIQUE NOT NULL,
|
||||
nc_username VARCHAR UNIQUE NOT NULL,
|
||||
nc_password VARCHAR NOT NULL
|
||||
);
|
880
name-list.txt
Normal file
|
@ -0,0 +1,880 @@
|
|||
Abomasnow
|
||||
Abra
|
||||
Absol
|
||||
Accelgor
|
||||
Aegislash
|
||||
Aerodactyl
|
||||
Aggron
|
||||
Aipom
|
||||
Alakazam
|
||||
Alcremie
|
||||
Alomomola
|
||||
Altaria
|
||||
Amaura
|
||||
Ambipom
|
||||
Amoonguss
|
||||
Ampharos
|
||||
Anorith
|
||||
Appletun
|
||||
Applin
|
||||
Araquanid
|
||||
Arbok
|
||||
Arcanine
|
||||
Arceus
|
||||
Archen
|
||||
Archeops
|
||||
Arctovish
|
||||
Arctozolt
|
||||
Ariados
|
||||
Armaldo
|
||||
Aromatisse
|
||||
Aron
|
||||
Arrokuda
|
||||
Articuno
|
||||
Audino
|
||||
Aurorus
|
||||
Avalugg
|
||||
Axew
|
||||
Azelf
|
||||
Azumarill
|
||||
Azurill
|
||||
Bagon
|
||||
Baltoy
|
||||
Banette
|
||||
Barbaracle
|
||||
Barboach
|
||||
Barraskewda
|
||||
Basculin
|
||||
Bastiodon
|
||||
Bayleef
|
||||
Beartic
|
||||
Beautifly
|
||||
Beedrill
|
||||
Beheeyem
|
||||
Beldum
|
||||
Bellossom
|
||||
Bellsprout
|
||||
Bergmite
|
||||
Bewear
|
||||
Bibarel
|
||||
Bidoof
|
||||
Binacle
|
||||
Bisharp
|
||||
Blacephalon
|
||||
Blastoise
|
||||
Blaziken
|
||||
Blipbug
|
||||
Blissey
|
||||
Blitzle
|
||||
Boldore
|
||||
Boltund
|
||||
Bonsly
|
||||
Bouffalant
|
||||
Bounsweet
|
||||
Braixen
|
||||
Braviary
|
||||
Breloom
|
||||
Brionne
|
||||
Bronzong
|
||||
Bronzor
|
||||
Bruxish
|
||||
Budew
|
||||
Buizel
|
||||
Bulbasaur
|
||||
Buneary
|
||||
Bunnelby
|
||||
Burmy
|
||||
Butterfree
|
||||
Buzzwole
|
||||
Cacnea
|
||||
Cacturne
|
||||
Calyrex
|
||||
Camerupt
|
||||
Carbink
|
||||
Carkol
|
||||
Carnivine
|
||||
Carracosta
|
||||
Carvanha
|
||||
Cascoon
|
||||
Castform
|
||||
Caterpie
|
||||
Celebi
|
||||
Celesteela
|
||||
Centiskorch
|
||||
Chandelure
|
||||
Chansey
|
||||
Charizard
|
||||
Charjabug
|
||||
Charmander
|
||||
Charmeleon
|
||||
Chatot
|
||||
Cherrim
|
||||
Cherubi
|
||||
Chesnaught
|
||||
Chespin
|
||||
Chewtle
|
||||
Chikorita
|
||||
Chimchar
|
||||
Chimecho
|
||||
Chinchou
|
||||
Chingling
|
||||
Cinccino
|
||||
Cinderace
|
||||
Clamperl
|
||||
Clauncher
|
||||
Clawitzer
|
||||
Claydol
|
||||
Clefable
|
||||
Clefairy
|
||||
Cleffa
|
||||
Clobbopus
|
||||
Cloyster
|
||||
Coalossal
|
||||
Cobalion
|
||||
Cofagrigus
|
||||
Combee
|
||||
Combusken
|
||||
Comfey
|
||||
Conkeldurr
|
||||
Copperajah
|
||||
Corphish
|
||||
Corsola
|
||||
Corviknight
|
||||
Corvisquire
|
||||
Cosmoem
|
||||
Cosmog
|
||||
Cottonee
|
||||
Crabominable
|
||||
Crabrawler
|
||||
Cradily
|
||||
Cramorant
|
||||
Cranidos
|
||||
Crawdaunt
|
||||
Cresselia
|
||||
Croagunk
|
||||
Crobat
|
||||
Croconaw
|
||||
Crustle
|
||||
Cryogonal
|
||||
Cubchoo
|
||||
Cubone
|
||||
Cufant
|
||||
Cursola
|
||||
Cutiefly
|
||||
Cyndaquil
|
||||
Darkrai
|
||||
Darmanitan
|
||||
Dartrix
|
||||
Darumaka
|
||||
Decidueye
|
||||
Dedenne
|
||||
Deerling
|
||||
Deino
|
||||
Delcatty
|
||||
Delibird
|
||||
Delphox
|
||||
Deoxys
|
||||
Dewgong
|
||||
Dewott
|
||||
Dewpider
|
||||
Dhelmise
|
||||
Dialga
|
||||
Diancie
|
||||
Diggersby
|
||||
Diglett
|
||||
Ditto
|
||||
Dodrio
|
||||
Doduo
|
||||
Donphan
|
||||
Dottler
|
||||
Doublade
|
||||
Dracovish
|
||||
Dracozolt
|
||||
Dragalge
|
||||
Dragapult
|
||||
Dragonair
|
||||
Dragonite
|
||||
Drakloak
|
||||
Drampa
|
||||
Drapion
|
||||
Dratini
|
||||
Drednaw
|
||||
Dreepy
|
||||
Drifblim
|
||||
Drifloon
|
||||
Drilbur
|
||||
Drizzile
|
||||
Drowzee
|
||||
Druddigon
|
||||
Dubwool
|
||||
Ducklett
|
||||
Dugtrio
|
||||
Dunsparce
|
||||
Duosion
|
||||
Duraludon
|
||||
Durant
|
||||
Dusclops
|
||||
Dusknoir
|
||||
Duskull
|
||||
Dustox
|
||||
Dwebble
|
||||
Eelektrik
|
||||
Eelektross
|
||||
Eevee
|
||||
Eiscue
|
||||
Ekans
|
||||
Eldegoss
|
||||
Electabuzz
|
||||
Electivire
|
||||
Electrike
|
||||
Electrode
|
||||
Elekid
|
||||
Elgyem
|
||||
Emboar
|
||||
Emolga
|
||||
Empoleon
|
||||
Entei
|
||||
Escavalier
|
||||
Espeon
|
||||
Espurr
|
||||
Eternatus
|
||||
Excadrill
|
||||
Exeggcute
|
||||
Exeggutor
|
||||
Exploud
|
||||
Falinks
|
||||
Farfetch’d
|
||||
Fearow
|
||||
Feebas
|
||||
Fennekin
|
||||
Feraligatr
|
||||
Ferroseed
|
||||
Ferrothorn
|
||||
Finneon
|
||||
Flaaffy
|
||||
Flabebe
|
||||
Flapple
|
||||
Flareon
|
||||
Fletchinder
|
||||
Fletchling
|
||||
Floatzel
|
||||
Floette
|
||||
Florges
|
||||
Flygon
|
||||
Fomantis
|
||||
Foongus
|
||||
Forretress
|
||||
Fraxure
|
||||
Frillish
|
||||
Froakie
|
||||
Frogadier
|
||||
Froslass
|
||||
Frosmoth
|
||||
Furfrou
|
||||
Furret
|
||||
Gabite
|
||||
Gallade
|
||||
Galvantula
|
||||
Garbodor
|
||||
Garchomp
|
||||
Gardevoir
|
||||
Gastly
|
||||
Gastrodon
|
||||
Genesect
|
||||
Gengar
|
||||
Geodude
|
||||
Gible
|
||||
Gigalith
|
||||
Girafarig
|
||||
Giratina
|
||||
Glaceon
|
||||
Glalie
|
||||
Glameow
|
||||
Gligar
|
||||
Gliscor
|
||||
Gloom
|
||||
Gogoat
|
||||
Golbat
|
||||
Goldeen
|
||||
Golduck
|
||||
Golem
|
||||
Golett
|
||||
Golisopod
|
||||
Golurk
|
||||
Goodra
|
||||
Goomy
|
||||
Gorebyss
|
||||
Gossifleur
|
||||
Gothita
|
||||
Gothitelle
|
||||
Gothorita
|
||||
Gourgeist
|
||||
Granbull
|
||||
Grapploct
|
||||
Graveler
|
||||
Greedent
|
||||
Greninja
|
||||
Grimer
|
||||
Grimmsnarl
|
||||
Grookey
|
||||
Grotle
|
||||
Groudon
|
||||
Grovyle
|
||||
Growlithe
|
||||
Grubbin
|
||||
Grumpig
|
||||
Gulpin
|
||||
Gumshoos
|
||||
Gurdurr
|
||||
Guzzlord
|
||||
Gyarados
|
||||
Happiny
|
||||
Hariyama
|
||||
Hatenna
|
||||
Hatterene
|
||||
Hattrem
|
||||
Haunter
|
||||
Hawlucha
|
||||
Haxorus
|
||||
Heatmor
|
||||
Heatran
|
||||
Heliolisk
|
||||
Helioptile
|
||||
Heracross
|
||||
Herdier
|
||||
Hippopotas
|
||||
Hippowdon
|
||||
Hitmonchan
|
||||
Hitmonlee
|
||||
Hitmontop
|
||||
Honchkrow
|
||||
Honedge
|
||||
Hoopa
|
||||
Hoothoot
|
||||
Hoppip
|
||||
Horsea
|
||||
Houndoom
|
||||
Houndour
|
||||
Huntail
|
||||
Hydreigon
|
||||
Hypno
|
||||
Igglybuff
|
||||
Illumise
|
||||
Impidimp
|
||||
Incineroar
|
||||
Indeedee
|
||||
Infernape
|
||||
Inkay
|
||||
Inteleon
|
||||
Ivysaur
|
||||
Jellicent
|
||||
Jigglypuff
|
||||
Jirachi
|
||||
Jolteon
|
||||
Joltik
|
||||
Jumpluff
|
||||
Jynx
|
||||
Kabuto
|
||||
Kabutops
|
||||
Kadabra
|
||||
Kakuna
|
||||
Kangaskhan
|
||||
Karrablast
|
||||
Kartana
|
||||
Kecleon
|
||||
Keldeo
|
||||
Kingdra
|
||||
Kingler
|
||||
Kirlia
|
||||
Klang
|
||||
Klefki
|
||||
Klink
|
||||
Klinklang
|
||||
Koffing
|
||||
Komala
|
||||
Krabby
|
||||
Kricketot
|
||||
Kricketune
|
||||
Krokorok
|
||||
Krookodile
|
||||
Kubfu
|
||||
Kyogre
|
||||
Kyurem
|
||||
Lairon
|
||||
Lampent
|
||||
Landorus
|
||||
Lanturn
|
||||
Lapras
|
||||
Larvesta
|
||||
Larvitar
|
||||
Latias
|
||||
Latios
|
||||
Leafeon
|
||||
Leavanny
|
||||
Ledian
|
||||
Ledyba
|
||||
Lickilicky
|
||||
Lickitung
|
||||
Liepard
|
||||
Lileep
|
||||
Lilligant
|
||||
Lillipup
|
||||
Linoone
|
||||
Litleo
|
||||
Litten
|
||||
Litwick
|
||||
Lombre
|
||||
Lopunny
|
||||
Lotad
|
||||
Loudred
|
||||
Lucario
|
||||
Ludicolo
|
||||
Lugia
|
||||
Lumineon
|
||||
Lunala
|
||||
Lunatone
|
||||
Lurantis
|
||||
Luvdisc
|
||||
Luxio
|
||||
Luxray
|
||||
Lycanroc
|
||||
Machamp
|
||||
Machoke
|
||||
Machop
|
||||
Magby
|
||||
Magcargo
|
||||
Magearna
|
||||
Magikarp
|
||||
Magmar
|
||||
Magmortar
|
||||
Magnemite
|
||||
Magneton
|
||||
Magnezone
|
||||
Makuhita
|
||||
Malamar
|
||||
Mamoswine
|
||||
Manaphy
|
||||
Mandibuzz
|
||||
Manectric
|
||||
Mankey
|
||||
Mantine
|
||||
Mantyke
|
||||
Maractus
|
||||
Mareanie
|
||||
Mareep
|
||||
Marill
|
||||
Marowak
|
||||
Marshadow
|
||||
Marshtomp
|
||||
Masquerain
|
||||
Mawile
|
||||
Medicham
|
||||
Meditite
|
||||
Meganium
|
||||
Melmetal
|
||||
Meloetta
|
||||
Meltan
|
||||
Meowstic
|
||||
Meowth
|
||||
Mesprit
|
||||
Metagross
|
||||
Metang
|
||||
Metapod
|
||||
Mew
|
||||
Mewtwo
|
||||
Mienfoo
|
||||
Mienshao
|
||||
Mightyena
|
||||
Milcery
|
||||
Milotic
|
||||
Miltank
|
||||
Mimikyu
|
||||
Minccino
|
||||
Minior
|
||||
Minun
|
||||
Misdreavus
|
||||
Mismagius
|
||||
Moltres
|
||||
Monferno
|
||||
Morelull
|
||||
Morgrem
|
||||
Morpeko
|
||||
Mothim
|
||||
Mudbray
|
||||
Mudkip
|
||||
Mudsdale
|
||||
Muk
|
||||
Munchlax
|
||||
Munna
|
||||
Murkrow
|
||||
Musharna
|
||||
Naganadel
|
||||
Natu
|
||||
Necrozma
|
||||
Nickit
|
||||
Nidoking
|
||||
Nidoqueen
|
||||
Nidoran
|
||||
Nidorina
|
||||
Nidorino
|
||||
Nihilego
|
||||
Nincada
|
||||
Ninetales
|
||||
Ninjask
|
||||
Noctowl
|
||||
Noibat
|
||||
Noivern
|
||||
Nosepass
|
||||
Numel
|
||||
Nuzleaf
|
||||
Obstagoon
|
||||
Octillery
|
||||
Oddish
|
||||
Omanyte
|
||||
Omastar
|
||||
Onix
|
||||
Oranguru
|
||||
Orbeetle
|
||||
Oricorio
|
||||
Oshawott
|
||||
Pachirisu
|
||||
Palkia
|
||||
Palossand
|
||||
Palpitoad
|
||||
Pancham
|
||||
Pangoro
|
||||
Panpour
|
||||
Pansage
|
||||
Pansear
|
||||
Paras
|
||||
Parasect
|
||||
Passimian
|
||||
Patrat
|
||||
Pawniard
|
||||
Pelipper
|
||||
Perrserker
|
||||
Persian
|
||||
Petilil
|
||||
Phanpy
|
||||
Phantump
|
||||
Pheromosa
|
||||
Phione
|
||||
Pichu
|
||||
Pidgeot
|
||||
Pidgeotto
|
||||
Pidgey
|
||||
Pidove
|
||||
Pignite
|
||||
Pikachu
|
||||
Pikipek
|
||||
Piloswine
|
||||
Pincurchin
|
||||
Pineco
|
||||
Pinsir
|
||||
Piplup
|
||||
Plusle
|
||||
Poipole
|
||||
Politoed
|
||||
Poliwag
|
||||
Poliwhirl
|
||||
Poliwrath
|
||||
Polteageist
|
||||
Ponyta
|
||||
Poochyena
|
||||
Popplio
|
||||
Porygon
|
||||
Primarina
|
||||
Primeape
|
||||
Prinplup
|
||||
Probopass
|
||||
Psyduck
|
||||
Pumpkaboo
|
||||
Pupitar
|
||||
Purrloin
|
||||
Purugly
|
||||
Pyroar
|
||||
Pyukumuku
|
||||
Quagsire
|
||||
Quilava
|
||||
Quilladin
|
||||
Qwilfish
|
||||
Raboot
|
||||
Raichu
|
||||
Raikou
|
||||
Ralts
|
||||
Rampardos
|
||||
Rapidash
|
||||
Raticate
|
||||
Rattata
|
||||
Rayquaza
|
||||
Regice
|
||||
Regidrago
|
||||
Regieleki
|
||||
Regigigas
|
||||
Regirock
|
||||
Registeel
|
||||
Relicanth
|
||||
Remoraid
|
||||
Reshiram
|
||||
Reuniclus
|
||||
Rhydon
|
||||
Rhyhorn
|
||||
Rhyperior
|
||||
Ribombee
|
||||
Rillaboom
|
||||
Riolu
|
||||
Rockruff
|
||||
Roggenrola
|
||||
Rolycoly
|
||||
Rookidee
|
||||
Roselia
|
||||
Roserade
|
||||
Rotom
|
||||
Rowlet
|
||||
Rufflet
|
||||
Runerigus
|
||||
Sableye
|
||||
Salamence
|
||||
Salandit
|
||||
Salazzle
|
||||
Samurott
|
||||
Sandaconda
|
||||
Sandile
|
||||
Sandshrew
|
||||
Sandslash
|
||||
Sandygast
|
||||
Sawk
|
||||
Sawsbuck
|
||||
Scatterbug
|
||||
Sceptile
|
||||
Scizor
|
||||
Scolipede
|
||||
Scorbunny
|
||||
Scrafty
|
||||
Scraggy
|
||||
Scyther
|
||||
Seadra
|
||||
Seaking
|
||||
Sealeo
|
||||
Seedot
|
||||
Seel
|
||||
Seismitoad
|
||||
Sentret
|
||||
Serperior
|
||||
Servine
|
||||
Seviper
|
||||
Sewaddle
|
||||
Sharpedo
|
||||
Shaymin
|
||||
Shedinja
|
||||
Shelgon
|
||||
Shellder
|
||||
Shellos
|
||||
Shelmet
|
||||
Shieldon
|
||||
Shiftry
|
||||
Shiinotic
|
||||
Shinx
|
||||
Shroomish
|
||||
Shuckle
|
||||
Shuppet
|
||||
Sigilyph
|
||||
Silcoon
|
||||
Silicobra
|
||||
Silvally
|
||||
Simipour
|
||||
Simisage
|
||||
Simisear
|
||||
Sinistea
|
||||
Sizzlipede
|
||||
Skarmory
|
||||
Skiddo
|
||||
Skiploom
|
||||
Skitty
|
||||
Skorupi
|
||||
Skrelp
|
||||
Skuntank
|
||||
Skwovet
|
||||
Slaking
|
||||
Slakoth
|
||||
Sliggoo
|
||||
Slowbro
|
||||
Slowking
|
||||
Slowpoke
|
||||
Slugma
|
||||
Slurpuff
|
||||
Smeargle
|
||||
Smoochum
|
||||
Sneasel
|
||||
Snivy
|
||||
Snom
|
||||
Snorlax
|
||||
Snorunt
|
||||
Snover
|
||||
Snubbull
|
||||
Sobble
|
||||
Solgaleo
|
||||
Solosis
|
||||
Solrock
|
||||
Spearow
|
||||
Spewpa
|
||||
Spheal
|
||||
Spinarak
|
||||
Spinda
|
||||
Spiritomb
|
||||
Spoink
|
||||
Spritzee
|
||||
Squirtle
|
||||
Stakataka
|
||||
Stantler
|
||||
Staraptor
|
||||
Staravia
|
||||
Starly
|
||||
Starmie
|
||||
Staryu
|
||||
Steelix
|
||||
Steenee
|
||||
Stonjourner
|
||||
Stoutland
|
||||
Stufful
|
||||
Stunfisk
|
||||
Stunky
|
||||
Sudowoodo
|
||||
Suicune
|
||||
Sunflora
|
||||
Sunkern
|
||||
Surskit
|
||||
Swablu
|
||||
Swadloon
|
||||
Swalot
|
||||
Swampert
|
||||
Swanna
|
||||
Swellow
|
||||
Swinub
|
||||
Swirlix
|
||||
Swoobat
|
||||
Sylveon
|
||||
Taillow
|
||||
Talonflame
|
||||
Tangela
|
||||
Tangrowth
|
||||
Tauros
|
||||
Teddiursa
|
||||
Tentacool
|
||||
Tentacruel
|
||||
Tepig
|
||||
Terrakion
|
||||
Thievul
|
||||
Throh
|
||||
Thundurus
|
||||
Thwackey
|
||||
Timburr
|
||||
Tirtouga
|
||||
Togedemaru
|
||||
Togekiss
|
||||
Togepi
|
||||
Togetic
|
||||
Torchic
|
||||
Torkoal
|
||||
Tornadus
|
||||
Torracat
|
||||
Torterra
|
||||
Totodile
|
||||
Toucannon
|
||||
Toxapex
|
||||
Toxel
|
||||
Toxicroak
|
||||
Toxtricity
|
||||
Tranquill
|
||||
Trapinch
|
||||
Treecko
|
||||
Trevenant
|
||||
Tropius
|
||||
Trubbish
|
||||
Trumbeak
|
||||
Tsareena
|
||||
Turtonator
|
||||
Turtwig
|
||||
Tympole
|
||||
Tynamo
|
||||
Typhlosion
|
||||
Tyranitar
|
||||
Tyrantrum
|
||||
Tyrogue
|
||||
Tyrunt
|
||||
Umbreon
|
||||
Unfezant
|
||||
Unown
|
||||
Ursaring
|
||||
Urshifu
|
||||
Uxie
|
||||
Vanillish
|
||||
Vanillite
|
||||
Vanilluxe
|
||||
Vaporeon
|
||||
Venipede
|
||||
Venomoth
|
||||
Venonat
|
||||
Venusaur
|
||||
Vespiquen
|
||||
Vibrava
|
||||
Victini
|
||||
Victreebel
|
||||
Vigoroth
|
||||
Vikavolt
|
||||
Vileplume
|
||||
Virizion
|
||||
Vivillon
|
||||
Volbeat
|
||||
Volcanion
|
||||
Volcarona
|
||||
Voltorb
|
||||
Vullaby
|
||||
Vulpix
|
||||
Wailmer
|
||||
Wailord
|
||||
Walrein
|
||||
Wartortle
|
||||
Watchog
|
||||
Weavile
|
||||
Weedle
|
||||
Weepinbell
|
||||
Weezing
|
||||
Whimsicott
|
||||
Whirlipede
|
||||
Whiscash
|
||||
Whismur
|
||||
Wigglytuff
|
||||
Wimpod
|
||||
Wingull
|
||||
Wishiwashi
|
||||
Wobbuffet
|
||||
Woobat
|
||||
Wooloo
|
||||
Wooper
|
||||
Wormadam
|
||||
Wurmple
|
||||
Wynaut
|
||||
Xatu
|
||||
Xerneas
|
||||
Xurkitree
|
||||
Yamask
|
||||
Yampur
|
||||
Yanma
|
||||
Yanmega
|
||||
Yungoos
|
||||
Yveltal
|
||||
Zacian
|
||||
Zamazenta
|
||||
Zangoose
|
||||
Zapdos
|
||||
Zarude
|
||||
Zebstrika
|
||||
Zekrom
|
||||
Zeraora
|
||||
Zigzagoon
|
||||
Zoroark
|
||||
Zorua
|
||||
Zubat
|
||||
Zweilous
|
||||
Zygarde
|
243
src/account.rs
Normal file
|
@ -0,0 +1,243 @@
|
|||
use actix_web::client::Client;
|
||||
use actix_web::{http, web, HttpRequest, HttpResponse};
|
||||
use base64::URL_SAFE_NO_PAD;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::Rng;
|
||||
use rand::RngCore;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::templates::get_lang;
|
||||
use crate::config::{ADJ_LIST, NAME_LIST};
|
||||
use crate::errors::{crash, TrainCrash};
|
||||
use crate::debug;
|
||||
use crate::CONFIG;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NCLoginForm<'a> {
|
||||
pub user: &'a str,
|
||||
pub password: &'a str,
|
||||
pub timezone: &'a str,
|
||||
pub timezone_offset: &'a str,
|
||||
pub requesttoken: &'a str,
|
||||
}
|
||||
|
||||
// check if the user is connected to Nextcloud
|
||||
// returns Some(cookie_raw_value) if connected
|
||||
// returns None if disconnected
|
||||
pub fn is_logged_in(req: &HttpRequest) -> Option<&str> {
|
||||
let c = req.headers().get("Cookie")?.to_str().ok()?;
|
||||
if c.contains("nc_username") {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_admintoken(req: &HttpRequest) -> Option<&str> {
|
||||
let c = req.headers().get("Cookie")?.to_str().ok()?;
|
||||
if c.contains("sncf_admin_token") {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// attempts to create the account from Nextcloud's API
|
||||
// returns the newly created username.
|
||||
// if it fails (bad return code), returns None.
|
||||
pub async fn create_account(
|
||||
client: &web::Data<Client>,
|
||||
user: &str,
|
||||
password: &str,
|
||||
lang: String,
|
||||
) -> Result<String, TrainCrash> {
|
||||
let mut register_query = client
|
||||
.post(format!(
|
||||
"{}/{}",
|
||||
CONFIG.nextcloud_url, "ocs/v1.php/cloud/users"
|
||||
))
|
||||
.basic_auth(&CONFIG.admin_username, Some(&CONFIG.admin_password))
|
||||
.header(
|
||||
http::header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header("OCS-APIRequest", "true")
|
||||
.send_form(&NCCreateAccountForm {
|
||||
userid: user,
|
||||
password,
|
||||
quota: "0B",
|
||||
language: &lang,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("error_createaccount_post: {}", e);
|
||||
crash(lang.clone(), "error_createaccount_post")
|
||||
})?;
|
||||
|
||||
// only 200 http status code is allowed
|
||||
if register_query.status() != 200 {
|
||||
eprintln!("error_createaccount_status: {}", register_query.status());
|
||||
return Err(crash(lang.clone(), "error_createaccount_status"));
|
||||
}
|
||||
|
||||
// extract response body
|
||||
let response_body = register_query.body().await.map_err(|e| {
|
||||
eprintln!("error_createaccount_post_body: {}", e);
|
||||
crash(lang.clone(), "error_createaccount_post_body")
|
||||
})?;
|
||||
let response_body = String::from_utf8_lossy(&response_body);
|
||||
// grasp NC status code
|
||||
let status_start = response_body.find("<statuscode>").ok_or_else(|| {
|
||||
eprintln!("error_createaccount_ncstatus_parse: start missing");
|
||||
crash(lang.clone(), "error_createaccount_ncstatus_parse")
|
||||
})? + 12;
|
||||
let status_end = response_body.find("</statuscode>").ok_or_else(|| {
|
||||
eprintln!("error_createaccount_ncstatus_parse: end missing");
|
||||
crash(lang.clone(), "error_createaccount_ncstatus_parse")
|
||||
})?;
|
||||
let code = &response_body[status_start..status_end];
|
||||
match code.parse::<u16>() {
|
||||
Ok(100) => Ok(String::from(user)), // success
|
||||
Ok(r) => {
|
||||
eprintln!("error_createaccount_ncstatus: {}", r);
|
||||
Err(crash(lang.clone(), "error_createaccount_ncstatus"))
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("error_createaccount_ncstatus_parse: {}", e);
|
||||
Err(crash(lang.clone(), "error_createaccount_ncstatus_parse"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NCCreateAccountForm<'a> {
|
||||
pub userid: &'a str,
|
||||
pub password: &'a str,
|
||||
pub quota: &'a str,
|
||||
pub language: &'a str,
|
||||
}
|
||||
|
||||
pub async fn login(
|
||||
client: &web::Data<Client>,
|
||||
req: &HttpRequest,
|
||||
user: &str,
|
||||
password: &str,
|
||||
) -> Result<HttpResponse, TrainCrash> {
|
||||
debug(&format!("Sending forged login for user {}", user));
|
||||
|
||||
// 1. GET /login
|
||||
let mut login_get = client
|
||||
.get(format!("{}/{}", CONFIG.nextcloud_url, "login"))
|
||||
.header("User-Agent", "Actix-web")
|
||||
.send()
|
||||
.await.map_err(|e| {
|
||||
eprintln!("error_login_get: {}", e);
|
||||
crash(get_lang(&req), "error_login_get")
|
||||
})?;
|
||||
|
||||
// rewrite cookie headers from GET to POST
|
||||
let mut str_cookiepair = String::new();
|
||||
for h_value in login_get.headers().get_all("set-cookie") {
|
||||
str_cookiepair = format!("{}; {}", str_cookiepair, h_value.clone().to_str().map_err(|e| {
|
||||
eprintln!("error_login_cookiepair: {}", e);
|
||||
crash(get_lang(&req), "error_login_cookiepair")
|
||||
})?);
|
||||
}
|
||||
|
||||
// load requesttoken regex
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r#"requesttoken="(?P<token>.*)""#).expect("Error while parsing the requesttoken regex");
|
||||
}
|
||||
|
||||
let post_body = login_get.body().await.map_err(|e| {
|
||||
eprintln!("error_login_get_body: {}", e);
|
||||
crash(get_lang(&req), "error_login_get_body")
|
||||
})?;
|
||||
let post_body_str = String::from_utf8_lossy(&post_body);
|
||||
|
||||
// save requesttoken (CSRF) for POST
|
||||
let requesttoken = RE
|
||||
.captures(&post_body_str)
|
||||
.ok_or_else(|| {
|
||||
eprintln!("error_login_regex (no capture)");
|
||||
crash(get_lang(&req), "error_login_regex")
|
||||
})?
|
||||
.name("token")
|
||||
.ok_or_else(|| {
|
||||
eprintln!("error_login_regex (no capture named token)");
|
||||
crash(get_lang(&req), "error_login_regex")
|
||||
})?
|
||||
.as_str();
|
||||
|
||||
// 2. POST /login
|
||||
let mut login_post = client
|
||||
.post(format!("{}/{}", CONFIG.nextcloud_url, "login"))
|
||||
.header("User-Agent", "Actix-web");
|
||||
|
||||
// include all NC cookies in one cookie (cookie pair)
|
||||
login_post = login_post.header("Cookie", str_cookiepair);
|
||||
|
||||
// send the same POST data as you'd log in from a web browser
|
||||
let response_post = login_post
|
||||
.send_form(&NCLoginForm {
|
||||
user,
|
||||
password,
|
||||
timezone: "UTC",
|
||||
timezone_offset: "2",
|
||||
requesttoken,
|
||||
})
|
||||
.await.map_err(|e| {
|
||||
eprintln!("error_login_post: {}", e);
|
||||
crash(get_lang(&req), "error_login_post")
|
||||
})?;
|
||||
|
||||
// 3. set the same cookies in the user's browser
|
||||
let mut user_response = HttpResponse::SeeOther();
|
||||
for item in response_post.headers().clone().get_all("set-cookie") {
|
||||
user_response.header("Set-Cookie", item.to_str().map_err(|e| {
|
||||
eprintln!("error_login_setcookie: {}", e);
|
||||
crash(get_lang(&req), "error_login_setcookie")
|
||||
})?);
|
||||
}
|
||||
|
||||
// redirect to forms!
|
||||
Ok(user_response
|
||||
.header(http::header::LOCATION, "/apps/forms")
|
||||
.finish()
|
||||
.await.map_err(|e| {
|
||||
eprintln!("error_login_redir: {}", e);
|
||||
crash(get_lang(&req), "error_login_redir")
|
||||
})?)
|
||||
}
|
||||
|
||||
// checks if the token seems valid before asking the db.
|
||||
// The token must be 45 bytes long and base64-encoded.
|
||||
// returns true if the token is valid
|
||||
pub fn check_token(token: &str) -> bool {
|
||||
let token_dec = base64::decode_config(token, URL_SAFE_NO_PAD);
|
||||
if let Ok(token_bytes) = token_dec {
|
||||
token_bytes.len() == 45
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// generates a new token
|
||||
pub fn gen_token() -> String {
|
||||
// Using /dev/random to generate random bytes
|
||||
let mut r = OsRng;
|
||||
|
||||
let mut my_secure_bytes = vec![0u8; 45];
|
||||
r.fill_bytes(&mut my_secure_bytes);
|
||||
base64::encode_config(my_secure_bytes, URL_SAFE_NO_PAD)
|
||||
}
|
||||
|
||||
pub fn gen_name() -> String {
|
||||
format!("{}{}", list_rand(&ADJ_LIST), list_rand(&NAME_LIST))
|
||||
}
|
||||
|
||||
pub fn list_rand(list: &Vec<String>) -> &String {
|
||||
let mut rng = rand::thread_rng();
|
||||
let roll = rng.gen_range(0, list.len() - 1);
|
||||
&list[roll]
|
||||
}
|
71
src/config.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
use serde_json::Value;
|
||||
|
||||
// payload limit set to 5MiB
|
||||
pub const PAYLOAD_LIMIT: usize = 50_000_000;
|
||||
|
||||
pub const CONFIG_FILE: &str = "./config.toml";
|
||||
pub const CONFIG_VERSION: u8 = 1;
|
||||
|
||||
pub const ADJ_LIST_FILE: &str = "./adj-list.txt";
|
||||
pub const NAME_LIST_FILE: &str = "./name-list.txt";
|
||||
pub const LOC_FILE: &str = "./lang.json";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Config = Config::init();
|
||||
pub static ref ADJ_LIST: Vec<String> =
|
||||
lines_from_file(ADJ_LIST_FILE).expect("Failed to load adjectives list");
|
||||
pub static ref NAME_LIST: Vec<String> =
|
||||
lines_from_file(NAME_LIST_FILE).expect("Failed to load names list");
|
||||
pub static ref LOC: Value = init_lang();
|
||||
}
|
||||
|
||||
fn init_lang() -> Value {
|
||||
let mut file = File::open(LOC_FILE).expect("init_lang: Can't open translations file");
|
||||
let mut data = String::new();
|
||||
file.read_to_string(&mut data)
|
||||
.expect("init_lang: Can't read translations file");
|
||||
serde_json::from_str(&data).expect("init_lang(): Can't parse translations file")
|
||||
}
|
||||
|
||||
fn lines_from_file(filename: impl AsRef<Path>) -> io::Result<Vec<String>> {
|
||||
BufReader::new(File::open(filename)?).lines().collect()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub listening_address: String,
|
||||
pub listening_port: u16,
|
||||
pub sncf_url: String,
|
||||
pub database_path: String,
|
||||
pub nextcloud_url: String,
|
||||
pub admin_username: String,
|
||||
pub admin_password: String,
|
||||
pub prune_days: u16,
|
||||
pub debug_mode: bool,
|
||||
pub config_version: u8,
|
||||
}
|
||||
|
||||
// totally not copypasted from rs-short
|
||||
impl Config {
|
||||
pub fn init() -> Self {
|
||||
let mut conffile = File::open(CONFIG_FILE).expect(
|
||||
r#"Config file config.toml not found.
|
||||
Please create it using config.toml.sample."#,
|
||||
);
|
||||
let mut confstr = String::new();
|
||||
conffile
|
||||
.read_to_string(&mut confstr)
|
||||
.expect("Couldn't read config to string");
|
||||
toml::from_str(&confstr).expect("Couldn't deserialize the config")
|
||||
}
|
||||
pub fn check_version(&self) {
|
||||
if self.config_version != CONFIG_VERSION {
|
||||
eprintln!("Your configuration file is obsolete! Please update it with config.toml.sample and update its version to {}.", CONFIG_VERSION);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
50
src/database/methods.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use chrono::NaiveDateTime;
|
||||
use chrono::Utc;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::database::schema::form::dsl::*;
|
||||
use crate::database::structs::Form;
|
||||
use crate::database::schema::form;
|
||||
use crate::SqliteConnection;
|
||||
|
||||
#[table_name = "form"]
|
||||
#[derive(Serialize, Insertable)]
|
||||
pub struct InsertableForm<'b> {
|
||||
pub created_at: NaiveDateTime,
|
||||
pub lastvisit_at: NaiveDateTime,
|
||||
pub token: &'b str,
|
||||
pub nc_username: &'b str,
|
||||
pub nc_password: &'b str,
|
||||
}
|
||||
|
||||
impl Form {
|
||||
// gets a Form from a token.
|
||||
// also updates lastvisit_at.
|
||||
pub fn get_from_token(
|
||||
i_token: &str,
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<Option<Form>, diesel::result::Error> {
|
||||
if let Some(formdata) = form.filter(token.eq(i_token)).first::<Form>(conn).optional()? {
|
||||
match formdata.update_lastvisit(conn) {
|
||||
Ok(_) => Ok(Some(formdata)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_lastvisit(&self, conn: &SqliteConnection) -> Result<usize, diesel::result::Error> {
|
||||
diesel::update(form.find(self.id))
|
||||
.set(lastvisit_at.eq(Utc::now().naive_utc()))
|
||||
.execute(conn)
|
||||
}
|
||||
|
||||
pub fn insert<'b>(i_form: InsertableForm<'b>, conn: &SqliteConnection
|
||||
) -> Result<InsertableForm<'b>, diesel::result::Error> {
|
||||
match diesel::insert_into(form).values(&i_form).execute(conn) {
|
||||
Ok(_) => Ok(i_form),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
3
src/database/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod schema;
|
||||
pub mod structs;
|
||||
pub mod methods;
|
10
src/database/schema.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
table! {
|
||||
form (id) {
|
||||
id -> Integer,
|
||||
created_at -> Timestamp,
|
||||
lastvisit_at -> Timestamp,
|
||||
token -> Text,
|
||||
nc_username -> Text,
|
||||
nc_password -> Text,
|
||||
}
|
||||
}
|
16
src/database/structs.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
//use diesel::{self, prelude::*};
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use crate::database::schema::form;
|
||||
//use crate::config::CONFIG;
|
||||
|
||||
#[table_name = "form"]
|
||||
#[derive(Serialize, Queryable, Insertable, Debug, Clone)]
|
||||
pub struct Form {
|
||||
pub id: i32,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub lastvisit_at: NaiveDateTime,
|
||||
pub token: String,
|
||||
pub nc_username: String,
|
||||
pub nc_password: String,
|
||||
}
|
45
src/errors.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use crate::templates::TplError;
|
||||
|
||||
use std::fmt;
|
||||
use actix_web::dev::HttpResponseBuilder;
|
||||
use actix_web::{error, http::header, http::StatusCode, HttpResponse};
|
||||
use askama::Template;
|
||||
|
||||
pub fn crash(lang: String, error_msg: &'static str) -> TrainCrash {
|
||||
TrainCrash { lang, error_msg }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrainCrash {
|
||||
pub error_msg: &'static str,
|
||||
pub lang: String,
|
||||
}
|
||||
|
||||
// gonna avoid using failure crate
|
||||
// by implementing display
|
||||
impl fmt::Display for TrainCrash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.error_msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::ResponseError for TrainCrash {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
eprintln!("Error reached: {}", self.error_msg);
|
||||
HttpResponseBuilder::new(self.status_code())
|
||||
.set_header(header::CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.body(
|
||||
TplError {
|
||||
lang: &self.lang,
|
||||
error_msg: self.error_msg,
|
||||
}
|
||||
.render()
|
||||
.expect("error_tplrender (TplError). Empty page sent to client."))
|
||||
|
||||
}
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
265
src/forward.rs
Normal file
|
@ -0,0 +1,265 @@
|
|||
use actix_web::client::{Client, ClientRequest};
|
||||
use actix_web::{http, web, HttpRequest, HttpResponse};
|
||||
use askama::Template;
|
||||
use chrono::Utc;
|
||||
use url::Url;
|
||||
|
||||
use crate::account::*;
|
||||
use crate::config::PAYLOAD_LIMIT;
|
||||
use crate::database::methods::InsertableForm;
|
||||
use crate::database::structs::Form;
|
||||
use crate::debug;
|
||||
use crate::sniff::*;
|
||||
use crate::templates::*;
|
||||
use crate::DbPool;
|
||||
use crate::CONFIG;
|
||||
use crate::errors::{TrainCrash, crash};
|
||||
|
||||
pub async fn forward(
|
||||
req: HttpRequest,
|
||||
body: web::Bytes,
|
||||
url: web::Data<Url>,
|
||||
client: web::Data<Client>,
|
||||
) -> Result<HttpResponse, TrainCrash> {
|
||||
let route = req.uri().path();
|
||||
|
||||
// if check_route returns true,
|
||||
// the user supposedly tried to access a restricted page.
|
||||
// They get redirected to the main page.
|
||||
if check_route(route) {
|
||||
debug(&format!("Restricted route blocked: {}", route));
|
||||
return Ok(web_redir("/"));
|
||||
}
|
||||
|
||||
let forwarded_req = forge_from(route, &req, &url, &client);
|
||||
|
||||
// check the request before sending it
|
||||
// (prevents the user from sending some specific POST requests)
|
||||
if check_request(route, &body) {
|
||||
debug(&format!(
|
||||
"Restricted request: {}",
|
||||
String::from_utf8_lossy(&body)
|
||||
));
|
||||
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||
}
|
||||
|
||||
// send the request to the Nextcloud instance
|
||||
let mut res = forwarded_req.send_body(body).await.map_err(|e| {
|
||||
eprintln!("error_forward_resp: {}", e);
|
||||
crash(get_lang(&req), "error_forward_req")
|
||||
})?;
|
||||
|
||||
let mut client_resp = HttpResponse::build(res.status());
|
||||
// remove connection as per the spec
|
||||
// and content-encoding since we have to decompress the traffic to edit it
|
||||
for (header_name, header_value) in res
|
||||
.headers()
|
||||
.iter()
|
||||
.filter(|(h, _)| *h != "connection" && *h != "content-encoding")
|
||||
{
|
||||
client_resp.header(header_name.clone(), header_value.clone());
|
||||
}
|
||||
|
||||
// retreive the body from the request result
|
||||
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
|
||||
eprintln!("error_forward_resp: {}", e);
|
||||
crash(get_lang(&req), "error_forward_resp")
|
||||
})?;
|
||||
|
||||
// if a new form is created, automatically set some fields.
|
||||
// this is very hackish but it works! for now.
|
||||
let form_id = check_new_form(route, &response_body);
|
||||
if form_id > 0 {
|
||||
debug(&format!(
|
||||
"New form. Forging request to set isAnonymous for id {}",
|
||||
form_id
|
||||
));
|
||||
|
||||
let forged_body = format!(
|
||||
r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
|
||||
form_id
|
||||
);
|
||||
let update_req = forge_from("/apps/forms/api/v1/form/update", &req, &url, &client)
|
||||
.set_header("content-length", forged_body.len())
|
||||
.set_header("content-type", "application/json;charset=utf-8");
|
||||
|
||||
let res = update_req
|
||||
.send_body(forged_body)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("error_forward_isanon: {}", e);
|
||||
crash(get_lang(&req), "error_forward_isanon")
|
||||
})?;
|
||||
debug(&format!("(new_form) Request returned {}", res.status()));
|
||||
}
|
||||
|
||||
// check the response before returning it
|
||||
if check_response(route, &response_body) {
|
||||
return Ok(web_redir("/"));
|
||||
}
|
||||
Ok(client_resp.body(response_body))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginToken {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
pub async fn forward_login(
|
||||
req: HttpRequest,
|
||||
params: web::Path<LoginToken>,
|
||||
client: web::Data<Client>,
|
||||
dbpool: web::Data<DbPool>,
|
||||
) -> Result<HttpResponse, TrainCrash> {
|
||||
// if the user is already logged in, redirect to the Forms app
|
||||
if is_logged_in(&req).is_some() {
|
||||
return Ok(web_redir("/apps/forms/"));
|
||||
}
|
||||
|
||||
// check if the provided token seems valid. If not, early return.
|
||||
if !check_token(¶ms.token) {
|
||||
debug("Incorrect admin token given.");
|
||||
return Err(crash(get_lang(&req), "error_dirtyhacker"));
|
||||
}
|
||||
|
||||
let conn = dbpool
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
eprintln!("error_forwardlogin_db: {}", e);
|
||||
crash(get_lang(&req), "error_forwardlogin_db")
|
||||
})?;
|
||||
|
||||
// check if the link exists in DB. if it does, update lastvisit_at.
|
||||
let formdata = Form::get_from_token(¶ms.token, &conn).map_err(|e| {
|
||||
eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
|
||||
crash(get_lang(&req), "error_forwardlogin_db_get")
|
||||
})?.ok_or_else(|| {
|
||||
eprintln!("error_forwardlogin_db_get (none error)");
|
||||
crash(get_lang(&req), "error_forwardlogin_db_get")
|
||||
})?;
|
||||
|
||||
// else, try to log the user in with DB data, then redirect.
|
||||
login(&client, &req, &formdata.nc_username, &formdata.nc_password).await
|
||||
}
|
||||
|
||||
// creates a NC account using a random name and password.
|
||||
// the account gets associated with a token in sqlite DB.
|
||||
pub async fn forward_register(
|
||||
req: HttpRequest,
|
||||
client: web::Data<Client>,
|
||||
dbpool: web::Data<DbPool>,
|
||||
) -> Result<HttpResponse, TrainCrash> {
|
||||
let lang = get_lang(&req);
|
||||
|
||||
// if the user is already logged in, redirect to the Forms app
|
||||
if is_logged_in(&req).is_some() {
|
||||
return Ok(web_redir("/apps/forms/"));
|
||||
}
|
||||
|
||||
// if the user has already generated an admin token, redirect too
|
||||
if let Some(token) = has_admintoken(&req) {
|
||||
let admin_token =
|
||||
token.splitn(2, ';').collect::<Vec<&str>>()[0].replace("sncf_admin_token=", "");
|
||||
// sanitize the token beforehand, cookies are unsafe
|
||||
if check_token(&admin_token) {
|
||||
return Ok(web_redir(&format!(
|
||||
"{}/admin/{}",
|
||||
CONFIG.sncf_url, &admin_token
|
||||
)));
|
||||
} else {
|
||||
debug("Incorrect admin token given.");
|
||||
return Err(crash(lang, "error_dirtyhacker"));
|
||||
}
|
||||
}
|
||||
|
||||
let nc_username = gen_name();
|
||||
let nc_password = gen_token();
|
||||
// attempts to create the account
|
||||
create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
|
||||
|
||||
debug(&format!("Created user {}", nc_username));
|
||||
|
||||
let conn = dbpool
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
eprintln!("error_forwardregister_pool: {}", e);
|
||||
crash(lang.clone(), "error_forwardregister_pool")
|
||||
})?;
|
||||
|
||||
let token = gen_token();
|
||||
|
||||
// store the result in DB
|
||||
let form_result = Form::insert(
|
||||
InsertableForm {
|
||||
created_at: Utc::now().naive_utc(),
|
||||
lastvisit_at: Utc::now().naive_utc(),
|
||||
token: &token,
|
||||
nc_username: &nc_username,
|
||||
nc_password: &nc_password,
|
||||
},
|
||||
&conn,
|
||||
);
|
||||
|
||||
if form_result.is_err() {
|
||||
return Err(crash(lang, "error_forwardregister_db"))
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.set_header(
|
||||
"Set-Cookie",
|
||||
format!("sncf_admin_token={}; HttpOnly; SameSite=Strict", &token),
|
||||
)
|
||||
.body(
|
||||
TplLink {
|
||||
lang: &lang,
|
||||
admin_token: &token,
|
||||
config: &CONFIG,
|
||||
}
|
||||
.render()
|
||||
.map_err(|e| {
|
||||
eprintln!("error_tplrender (TplLink): {}", e);
|
||||
crash(lang, "error_tplrender")
|
||||
})?,
|
||||
))
|
||||
}
|
||||
|
||||
// create a new query destined to the nextcloud instance
|
||||
// needed to forward any query
|
||||
fn forge_from(
|
||||
route: &str,
|
||||
req: &HttpRequest,
|
||||
url: &web::Data<Url>,
|
||||
client: &web::Data<Client>,
|
||||
) -> ClientRequest {
|
||||
let mut new_url = url.get_ref().clone();
|
||||
new_url.set_path(route);
|
||||
new_url.set_query(req.uri().query());
|
||||
|
||||
// insert forwarded header if we can
|
||||
let forwarded_req = client.request_from(new_url.as_str(), req.head());
|
||||
if let Some(addr) = req.head().peer_addr {
|
||||
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
|
||||
} else {
|
||||
forwarded_req
|
||||
}
|
||||
}
|
||||
|
||||
fn web_redir(location: &str) -> HttpResponse {
|
||||
HttpResponse::SeeOther()
|
||||
.header(http::header::LOCATION, location)
|
||||
.finish()
|
||||
}
|
||||
|
||||
pub async fn index(req: HttpRequest) -> Result<HttpResponse, TrainCrash> {
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(
|
||||
TplIndex {
|
||||
lang: &get_lang(&req),
|
||||
}
|
||||
.render()
|
||||
.map_err(|e| {
|
||||
eprintln!("error_tplrender (TplIndex): {}", e);
|
||||
crash(get_lang(&req), "error_tplrender")
|
||||
})?,
|
||||
))
|
||||
}
|
92
src/main.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
use actix_web::client::Client;
|
||||
use actix_web::{middleware, web, App, FromRequest, HttpServer};
|
||||
use actix_files::Files;
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{self, ConnectionManager};
|
||||
use url::Url;
|
||||
|
||||
use crate::config::CONFIG;
|
||||
use crate::config::PAYLOAD_LIMIT;
|
||||
use crate::forward::*;
|
||||
|
||||
mod config;
|
||||
mod database;
|
||||
mod forward;
|
||||
mod sniff;
|
||||
mod account;
|
||||
mod templates;
|
||||
mod errors;
|
||||
|
||||
type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
|
||||
|
||||
embed_migrations!();
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
||||
/* std::env::set_var("RUST_LOG", "actix_web=debug");
|
||||
env_logger::init();*/
|
||||
|
||||
println!("ta ta tala ~ SNCF init");
|
||||
|
||||
println!("Checking configuration file...");
|
||||
CONFIG.check_version();
|
||||
|
||||
println!("Opening database {}", CONFIG.database_path);
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(&CONFIG.database_path);
|
||||
let pool = r2d2::Pool::builder()
|
||||
.build(manager)
|
||||
.expect("ERROR: main: Failed to create the database pool.");
|
||||
|
||||
let conn = pool.get().expect("ERROR: main: DB connection failed");
|
||||
|
||||
println!("Running migrations...");
|
||||
embedded_migrations::run(&*conn).expect("ERROR: main: Failed to run database migrations");
|
||||
|
||||
let forward_url = Url::parse(&CONFIG.nextcloud_url).expect("Couldn't parse the forward url from config");
|
||||
|
||||
println!(
|
||||
"Now listening at {}:{}",
|
||||
CONFIG.listening_address, CONFIG.listening_port
|
||||
);
|
||||
|
||||
// starting the http server
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.data(pool.clone())
|
||||
.data(Client::new())
|
||||
.data(forward_url.clone())
|
||||
/*.route("/mimolette", web::get().to(login))*/
|
||||
/*.route("/login", web::post().to(forward))*/
|
||||
.wrap(middleware::Compress::default())
|
||||
.service(Files::new("/assets/", "./templates/assets/").index_file("index.html"))
|
||||
.route("/", web::get().to(index))
|
||||
.route("/link", web::get().to(forward_register))
|
||||
.route("/admin/{token}", web::get().to(forward_login))
|
||||
.default_service(web::route().to(forward)).data(String::configure(|cfg| {
|
||||
cfg.limit(PAYLOAD_LIMIT)
|
||||
}))
|
||||
.app_data(actix_web::web::Bytes::configure(|cfg| {
|
||||
cfg.limit(PAYLOAD_LIMIT)
|
||||
}))
|
||||
})
|
||||
.bind((CONFIG.listening_address.as_str(), CONFIG.listening_port))?
|
||||
.system_exit()
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn debug(text: &str) {
|
||||
if CONFIG.debug_mode {
|
||||
println!("{}", text);
|
||||
}
|
||||
}
|
86
src/sniff.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use actix_web::web;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::debug;
|
||||
|
||||
// checks to be done on user requests
|
||||
// if it returns true, cancels the request
|
||||
pub fn check_request(route: &str, body: &web::Bytes) -> bool {
|
||||
match route {
|
||||
"/apps/forms/api/v1/form/update" => rq_form_update(body),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// prevents the user from doing anything other than link sharing.
|
||||
fn rq_form_update(body: &web::Bytes) -> bool {
|
||||
let req = String::from_utf8_lossy(body);
|
||||
|
||||
// try to serialize the body.
|
||||
// If the parsing fails, drop the request
|
||||
let v: Value = serde_json::from_str(&req).unwrap_or_else(|e| {
|
||||
eprintln!("check_request: failed to parse JSON: {}", e);
|
||||
Value::Null
|
||||
});
|
||||
// if the type or isAnonymous is set (isn't null),
|
||||
// drop the request.
|
||||
// Also drop if v is null because of parsing fail.
|
||||
v == Value::Null
|
||||
|| v["keyValuePairs"]["isAnonymous"] != Value::Null
|
||||
|| v["keyValuePairs"]["access"]["type"] != Value::Null
|
||||
}
|
||||
|
||||
// checks to be done on responses from the Nextcloud instance
|
||||
// if it returns true, cancels the request
|
||||
pub fn check_response(_route: &str, _body: &web::Bytes) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// checks if a form has been created.
|
||||
// if it's the case, sets some parameters.
|
||||
// this part may need code quality improvements
|
||||
pub fn check_new_form(route: &str, body: &web::Bytes) -> u64 {
|
||||
let req = String::from_utf8_lossy(body);
|
||||
|
||||
let new_form_route = "/apps/forms/api/v1/form";
|
||||
|
||||
if route != new_form_route {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// finds the form ID
|
||||
let v: Value = serde_json::from_str(&req).unwrap_or_else(|e| {
|
||||
eprintln!("check_new_form: failed to parse JSON: {}", e);
|
||||
Value::Null
|
||||
});
|
||||
|
||||
if v != Value::Null && v["id"] != Value::Null && v["isAnonymous"] == Value::Null {
|
||||
v["id"].as_u64().unwrap_or_else(|| {
|
||||
eprintln!("check_new_form: failed to parse formid: {}", v);
|
||||
0
|
||||
})
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
const BLOCKED_ROUTES: &'static [&'static str] = &[
|
||||
"/apps/settings",
|
||||
"/login",
|
||||
"/settings",
|
||||
"/ocs/v",
|
||||
"/remote.php",
|
||||
];
|
||||
|
||||
// checks if the accessed route is allowed for the user.
|
||||
// if it returns true, redirects elsewhere
|
||||
pub fn check_route(route: &str) -> bool {
|
||||
debug(route);
|
||||
|
||||
for r in BLOCKED_ROUTES {
|
||||
if route.starts_with(r) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
62
src/templates.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use actix_web::HttpRequest;
|
||||
use askama::Template;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct TplIndex<'a> {
|
||||
pub lang: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
pub struct TplError<'a> {
|
||||
pub lang: &'a str,
|
||||
pub error_msg: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "link.html")]
|
||||
pub struct TplLink<'a> {
|
||||
pub lang: &'a str,
|
||||
pub admin_token: &'a str,
|
||||
pub config: &'a Config,
|
||||
}
|
||||
|
||||
pub fn get_lang(req: &HttpRequest) -> String {
|
||||
// getting language from client header
|
||||
// taking the two first characters of the Accept-Language header,
|
||||
// in lowercase, then parsing it.
|
||||
// if it fails, returns "en"
|
||||
if let Some(l) = req.headers().get("Accept-Language") {
|
||||
if let Ok(s) = l.to_str() {
|
||||
return s.to_lowercase()[..2].to_string();
|
||||
}
|
||||
}
|
||||
String::from("en")
|
||||
}
|
||||
|
||||
mod filters {
|
||||
use crate::config::LOC;
|
||||
|
||||
pub fn tr(key: &str, lang: &str) -> askama::Result<String> {
|
||||
Ok(String::from(
|
||||
LOC.get(key)
|
||||
.ok_or_else(|| {
|
||||
eprintln!("tr filter: couldn't find the key {}", key);
|
||||
askama::Error::from(std::fmt::Error)
|
||||
})?
|
||||
.get(lang)
|
||||
.ok_or_else(|| {
|
||||
eprintln!("tr filter: couldn't find the lang {} in key {}", lang, key);
|
||||
askama::Error::from(std::fmt::Error)
|
||||
})?
|
||||
.as_str()
|
||||
.ok_or_else(|| {
|
||||
eprintln!("tr filter: lang {} in key {} is not str", lang, key);
|
||||
askama::Error::from(std::fmt::Error)
|
||||
})?,
|
||||
))
|
||||
}
|
||||
}
|
BIN
templates/assets/Ubuntu-R.ttf
Normal file
140
templates/assets/cloud.css
Normal file
|
@ -0,0 +1,140 @@
|
|||
.has-text-centered > * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.c-subelem, .c-fullwidth > * {
|
||||
color: #2c2c2c;
|
||||
}
|
||||
|
||||
.c-blue {
|
||||
}
|
||||
|
||||
.c-blue > a {
|
||||
color: white;
|
||||
background: #1f58c6;
|
||||
}
|
||||
|
||||
.c-flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
@media screen and (min-width:1280px) {
|
||||
.c-flex.c-flex-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.c-jumbo {
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
.c-subelem {
|
||||
padding: 0;
|
||||
max-width: 40vw;
|
||||
margin: auto 0;
|
||||
}
|
||||
}
|
||||
|
||||
.c-jumbo.c-jumbo-big {
|
||||
min-height: 25rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.c-jumbo.c-jumbo-medium {
|
||||
min-height: 18rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.c-jumbo.c-jumbo-small {
|
||||
min-height: 10rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.c-button {
|
||||
display: block;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
transition: all .2s ease-in-out;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
padding: 1rem;
|
||||
width: max-content;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.c-button:only-child {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.c-button.c-big {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.c-subelem {
|
||||
margin: auto 2rem;
|
||||
padding: 1rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c-img-shadow {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.c-img-center {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.c-fullwidth {
|
||||
width: 100%;
|
||||
margin: auto 2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width:1279px) {
|
||||
.c-no-margin-mobile {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.c-jumbo {
|
||||
padding: .5rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c-fade-left {
|
||||
opacity: 0;
|
||||
transform: translateX(-100px);
|
||||
animation: fadeInLeft 2s ease-in-out both;
|
||||
}
|
||||
|
||||
.c-fade-right {
|
||||
opacity: 0;
|
||||
transform: translateX(100px);
|
||||
animation: fadeInRight 2s ease-in-out both;
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-100px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
BIN
templates/assets/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
templates/assets/index-background.png
Normal file
After Width: | Height: | Size: 428 KiB |
230
templates/assets/index.css
Normal file
|
@ -0,0 +1,230 @@
|
|||
@font-face {
|
||||
font-family: 'Ubuntu-R';
|
||||
src: url('/assets/Ubuntu-R.ttf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: Ubuntu,"Ubuntu-R",sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #2359fb;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fullheight {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.fullheight-nav {
|
||||
min-height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: white;
|
||||
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4vw;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.25vw;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.75vw;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.25vw;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #f47606;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
padding: 0.3rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.beta-banner a {
|
||||
color: #ffeb7f;
|
||||
}
|
||||
|
||||
.beta-banner {
|
||||
background: repeating-linear-gradient( 45deg, #d56009, #d56009 10px, #c44c05 10px, #c44c05 20px );
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 10vw;
|
||||
margin-right: 2vw;
|
||||
}
|
||||
|
||||
.page-heading {
|
||||
background-image: url("/assets/index-background.png"), linear-gradient(0deg, #1f58c6 0%, #1c66f2 100%);
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.page-heading-text {
|
||||
width: auto;
|
||||
margin: auto;
|
||||
|
||||
}
|
||||
|
||||
.page-heading > p {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-heading > p > a {
|
||||
color: #c3cce8;
|
||||
}
|
||||
|
||||
.page-heading.error {
|
||||
background: url("/assets/index-background.png"), linear-gradient(0deg, #790000 0%, #a40000 100%)
|
||||
}
|
||||
|
||||
.ncstyle-button.error {
|
||||
background: #ee4040;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ncstyle-button {
|
||||
color: #FFF;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||
border-radius: 1vw;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
|
||||
white-space: nowrap;
|
||||
height: max-content;
|
||||
line-height: 2.25rem;
|
||||
padding: 2vh 3vw;
|
||||
background: #2a87ff;
|
||||
font-size: 1.5vw;
|
||||
min-width: 18vw;
|
||||
display: block;
|
||||
transition: all .25s ease-in-out;
|
||||
}
|
||||
|
||||
.margin-bottom {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ncstyle-button:hover {
|
||||
background: #2478e3;
|
||||
}
|
||||
|
||||
.ncstyle-input {
|
||||
margin: auto;
|
||||
padding: 7px 6px;
|
||||
font-size: 16px;
|
||||
background-color: white;
|
||||
color: #454545;
|
||||
border: 1px solid #dbdbdb;
|
||||
outline: none;
|
||||
border-radius: 3px;
|
||||
cursor: text;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
#script-copy {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1080px) {
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 20vw;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ncstyle-button {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1080px), screen and (max-height: 600px) {
|
||||
.scroll-down-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-down-arrow {
|
||||
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2hldnJvbl90aGluX2Rvd24iIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDIwIDIwIiBmaWxsPSJ3aGl0ZSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTE3LjQxOCw2LjEwOWMwLjI3Mi0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwYzAuMjcsMC4yNjgsMC4yNzEsMC43MDEsMCwwLjk2OWwtNy45MDgsNy44M2MtMC4yNywwLjI2OC0wLjcwNywwLjI2OC0wLjk3OSwwbC03LjkwOC03LjgzYy0wLjI3LTAuMjY4LTAuMjctMC43MDEsMC0wLjk2OWMwLjI3MS0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwTDEwLDEzLjI1TDE3LjQxOCw2LjEwOXoiLz48L3N2Zz4=);
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.scroll-down-link {
|
||||
cursor:pointer;
|
||||
height: 60px;
|
||||
width: 80px;
|
||||
margin: 0px 0 0 -40px;
|
||||
line-height: 60px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 10px;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
font-size: 70px;
|
||||
z-index: 100;
|
||||
text-decoration: none;
|
||||
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.4);
|
||||
animation: fade_move_down 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/*animated scroll arrow animation*/
|
||||
@keyframes fade_move_down {
|
||||
0% { transform:translate(0,-20px); opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100% { transform:translate(0,20px); opacity: 0; }
|
||||
}
|
51
templates/assets/logo.svg
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg enable-background="new 0 0 1866.187 1632.163" space="preserve" version="1.1" viewBox="0 0 1661.3 1444.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<polygon points="1565.4 562.79 1588.7 361.49 1588.8 361.49 1813.4 554.74" fill="#e4b50a"/>
|
||||
<polygon points="1565.4 562.79 1532.6 845.46 1258.8 580.05 1588.7 360.86 1588.7 361.49" fill="#e5de00"/>
|
||||
<polygon points="932.04 759.04 1532.6 845.46 1011.8 1051.4 1011.4 1051.5 1011.3 1051.3 932.6 759.59" fill="#6dc9ee"/>
|
||||
<polygon points="931.31 758.41 850.93 184.55 1258.8 580.05 1532.6 845.46 932.04 759.04" fill="#80be7b"/>
|
||||
<polygon points="520.6 1596.9 520.11 1596.4 831.9 961.52 931.31 758.96 931.39 758.96 932.04 759.04 932.6 759.59 1011.3 1051.3" fill="#eb76a7"/>
|
||||
<polygon points="931.31 758.41 932.04 759.04 931.39 758.96"/>
|
||||
<polygon points="931.31 758.96 831.9 961.52 158.98 411.87 554.38 411.87 555.11 411.16 931.31 758.41 931.39 758.96" fill="#8d5197"/>
|
||||
<polygon points="555.11 411.16 284.68 161.53 850.93 184.55 931.31 758.41" fill="#0476b8"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m933.04 758.02 76.812 288.6-2.885 0.773s-70.529-140.11-100.98-88.575l-384.7 634.52z" fill="#e9609b"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m931.15 757.63-96.492 198.39s45.029-163.52 16.082-182.82c0 0-141.52-176.9-344.15-294.3 0 0-61.111-53.07-348.46-67.544h396.71z" fill="#764382"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m1533.7 843.44 0.129 0.257-522.21 206.75-78.287-291.15c75.328 158.63 104.6 191.44 114.12 198.19 0.515 0.965 1.351 1.801 2.316 2.316l2.251 2.123c21.679 24.381 481.69-118.49 481.69-118.49z" fill="#00a3e2"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m1526.1 843.06-594.9-82.725-78.737-565.44c49.082 160.05 123.64 462.58 158.7 492.75 2.38 3.345 5.404 6.176 9.135 8.363 48.31 37.76 344.47 102.22 505.81 147.05z" fill="#00a57a"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m851.09 184.45 78.851 572.66s-157.19-452.29-192.97-481.05c0 0 0-0.064-0.064-0.064-20.662-43.471-453.86-114.75-453.86-114.75z" fill="#1c63a9"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m1813.5 553.97-247.66 8.555 22.515-200.51s2.637 147.82 24.444 156.51c14.731 19.363 200.7 35.445 200.7 35.445z" fill="#d79617"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m1587.3 360.98-53.585 484.58-274.36-264.77c5.854 3.795 200.19 129.49 228.11 119.14h0.643c0.45 0 0.836-0.128 1.158-0.257 0.772-0.128 1.608-0.321 2.444-0.708 28.175-3.024 95.591-337.98 95.591-337.98z" fill="#d7bb00"/>
|
||||
</g>
|
||||
<g transform="translate(-154.86 -156.52)">
|
||||
<path d="m1263.1 582.98c29.089-19.338 58.178-38.677 87.267-58.015l159.88-106.29c26.636-17.708 53.272-35.415 79.908-53.123 4.006-2.663 0.255-9.162-3.785-6.476l-247.15 164.3c-26.636 17.708-53.272 35.415-79.908 53.122-4.007 2.665-0.256 9.164 3.785 6.478z" fill="#fff"/>
|
||||
<path d="m1585.8 365.01c31.895 27.341 63.79 54.683 95.684 82.024 40.752 34.935 81.505 69.87 122.26 104.8 1.973 1.691 3.945 3.382 5.918 5.073 3.651 3.129 8.981-2.151 5.304-5.304-31.895-27.341-63.79-54.683-95.684-82.024-40.752-34.935-81.505-69.87-122.26-104.8-1.973-1.691-3.945-3.382-5.918-5.073-3.651-3.129-8.982 2.151-5.304 5.304z" fill="#fff"/>
|
||||
<path d="m1812.3 551.17c-46.01 1.282-92.019 2.564-138.03 3.846-36.051 1.004-72.102 2.009-108.15 3.014-4.826 0.134-4.839 7.635 0 7.5 46.01-1.282 92.019-2.564 138.03-3.846 36.051-1.004 72.102-2.009 108.15-3.014 4.826-0.134 4.839-7.634 0-7.5z" fill="#fff"/>
|
||||
<path d="m1584.2 363.15-10.359 89.256c-7.257 62.528-14.515 125.06-21.772 187.58-6.408 55.211-12.816 110.42-19.225 165.63-1.506 12.98-3.013 25.96-4.52 38.941-0.557 4.799 6.949 4.746 7.5 0l10.359-89.256c7.257-62.528 14.515-125.06 21.772-187.58 6.408-55.211 12.816-110.42 19.225-165.63 1.506-12.98 3.013-25.96 4.52-38.941 0.557-4.799-6.95-4.745-7.5 0z" fill="#fff"/>
|
||||
<path d="m1534.1 841.72-36.633-35.523c-31.571-30.616-63.143-61.231-94.714-91.847-43.339-42.027-86.679-84.054-130.02-126.08-47.515-46.077-95.03-92.153-142.54-138.23-44.099-42.764-88.197-85.527-132.3-128.29-33.09-32.088-66.179-64.176-99.269-96.264-14.489-14.05-28.977-28.1-43.466-42.15-0.133-0.129-0.267-0.259-0.401-0.389-3.472-3.367-8.78 1.932-5.303 5.304 12.211 11.841 24.422 23.682 36.632 35.523 31.571 30.616 63.143 61.231 94.714 91.846 43.339 42.027 86.679 84.055 130.02 126.08 47.515 46.076 95.03 92.153 142.54 138.23 44.099 42.764 88.197 85.527 132.3 128.29 33.09 32.088 66.179 64.176 99.269 96.264 14.489 14.05 28.977 28.1 43.466 42.15 0.134 0.129 0.267 0.259 0.4 0.389 3.473 3.367 8.782-1.931 5.305-5.303z" fill="#fff"/>
|
||||
<path d="m852.12 181.84c-26.327-1.174-52.654-2.349-78.981-3.523-59.471-2.653-118.94-5.306-178.41-7.959-63.349-2.826-126.7-5.652-190.05-8.479-37.96-1.693-75.92-3.387-113.88-5.08-2.101-0.094-4.201-0.188-6.302-0.281-4.837-0.216-4.816 7.285 0 7.5 26.327 1.174 52.654 2.349 78.981 3.523 59.471 2.653 118.94 5.306 178.41 7.959 63.349 2.826 126.7 5.652 190.05 8.479 37.96 1.693 75.92 3.387 113.88 5.08 2.101 0.094 4.201 0.188 6.302 0.281 4.838 0.216 4.817-7.285 0-7.5z" fill="#fff"/>
|
||||
<path d="m281.84 162.92c13.316 12.318 26.632 24.637 39.948 36.956 33.969 31.424 67.938 62.848 101.91 94.272 45.652 42.232 91.304 84.463 136.96 126.7l145.1 134.23c42.109 38.954 84.217 77.908 126.33 116.86 26.882 24.869 53.765 49.738 80.648 74.606 5.09 4.709 10.181 9.418 15.271 14.127 3.544 3.278 8.861-2.013 5.304-5.304-13.316-12.318-26.633-24.637-39.949-36.956-33.969-31.424-67.938-62.848-101.91-94.272-45.652-42.232-91.303-84.463-136.96-126.7l-145.1-134.23c-42.108-38.954-84.217-77.908-126.33-116.86-26.883-24.869-53.766-49.738-80.648-74.606-5.09-4.709-10.181-9.418-15.271-14.127-3.544-3.278-8.861 2.013-5.303 5.304z" fill="#fff"/>
|
||||
<path d="m935.59 757.54c-3.589-25.746-7.178-51.491-10.766-77.237-8.161-58.546-16.322-117.09-24.482-175.64-8.825-63.313-17.65-126.62-26.476-189.94-5.582-40.044-11.164-80.087-16.745-120.13-0.465-3.333-0.929-6.665-1.394-9.998-0.666-4.773-7.891-2.734-7.232 1.994 3.589 25.746 7.178 51.491 10.766 77.236 8.161 58.547 16.322 117.09 24.483 175.64 8.825 63.313 17.65 126.62 26.475 189.94 5.582 40.043 11.164 80.087 16.745 120.13 0.465 3.333 0.929 6.665 1.394 9.998 0.665 4.774 7.891 2.735 7.232-1.993z" fill="#fff"/>
|
||||
<path d="m930.75 762.13c24.773 3.578 49.547 7.157 74.321 10.735 57.265 8.271 114.53 16.543 171.79 24.815 64.172 9.27 128.34 18.539 192.52 27.809 45.496 6.572 90.992 13.144 136.49 19.715l23.388 3.378c4.729 0.683 6.766-6.543 1.994-7.232-24.773-3.578-49.547-7.157-74.32-10.735-57.265-8.271-114.53-16.543-171.79-24.815-64.172-9.27-128.34-18.539-192.52-27.809-45.496-6.572-90.992-13.144-136.49-19.715l-23.388-3.378c-4.73-0.683-6.767 6.543-1.995 7.232z" fill="#fff"/>
|
||||
<path d="m929.24 756.82c-7.599 15.446-15.198 30.892-22.797 46.337l-58.8 119.52c-26.806 54.484-53.611 108.97-80.417 163.45-29.215 59.382-58.43 118.76-87.646 178.15-26.83 54.533-53.659 109.07-80.488 163.6-19.648 39.936-39.296 79.873-58.944 119.81l-23.013 46.776h-1e-3c-2.126 4.322 4.341 8.125 6.477 3.785 7.599-15.445 15.198-30.892 22.797-46.337l58.8-119.52c26.806-54.485 53.611-108.97 80.417-163.45 29.215-59.382 58.43-118.76 87.646-178.15 26.83-54.533 53.659-109.07 80.488-163.6 19.648-39.937 39.296-79.873 58.944-119.81l23.013-46.776s0-1e-3 1e-3 -1e-3c2.126-4.322-4.342-8.125-6.477-3.785z" fill="#fff"/>
|
||||
<path d="m523.83 1599.8c14.242-15.838 28.484-31.677 42.726-47.516 34.868-38.777 69.736-77.554 104.6-116.33 43.708-48.608 87.416-97.216 131.12-145.82 40.761-45.331 81.522-90.662 122.28-135.99 26.028-28.946 52.057-57.893 78.085-86.839 3.806-4.232 7.612-8.465 11.418-12.698 3.224-3.585-2.063-8.907-5.304-5.304-14.242 15.839-28.483 31.677-42.726 47.516-34.868 38.777-69.736 77.554-104.6 116.33-43.708 48.608-87.416 97.216-131.12 145.82-40.761 45.331-81.522 90.663-122.28 135.99-26.028 28.946-52.057 57.893-78.085 86.839-3.806 4.232-7.612 8.465-11.418 12.698-3.223 3.585 2.064 8.906 5.304 5.302z" fill="#fff"/>
|
||||
<path d="m1014.8 1051.5c-10.611-39.859-21.221-79.719-31.832-119.58-14.401-54.101-28.803-108.2-43.204-162.3-1.153-4.33-2.305-8.66-3.458-12.989-1.243-4.669-8.478-2.686-7.232 1.994 10.611 39.859 21.221 79.719 31.832 119.58 14.401 54.101 28.803 108.2 43.204 162.3 1.153 4.33 2.305 8.66 3.458 12.989 1.243 4.669 8.478 2.685 7.232-1.994z" fill="#fff"/>
|
||||
<path d="m1013.9 1055.5c24.812-9.907 49.625-19.813 74.438-29.72 55.685-22.232 111.37-44.464 167.06-66.697 58.428-23.327 116.86-46.654 175.28-69.981 33.041-13.192 66.082-26.383 99.122-39.575 1.087-0.434 2.174-0.868 3.261-1.302 4.435-1.771 2.502-9.027-1.994-7.232-24.812 9.907-49.625 19.813-74.438 29.72-55.685 22.232-111.37 44.464-167.06 66.697-58.428 23.327-116.86 46.654-175.28 69.981-33.041 13.192-66.082 26.383-99.122 39.575-1.087 0.434-2.174 0.868-3.261 1.302-4.434 1.771-2.502 9.027 1.994 7.232z" fill="#fff"/>
|
||||
<path d="m553.86 406.73h-395.09c-4.836 0-4.836 7.5 0 7.5h395.09c4.837 0 4.837-7.5 0-7.5z" fill="#fff"/>
|
||||
<path d="m156.13 413.13c14.181 11.624 28.363 23.248 42.544 34.872 36.089 29.581 72.178 59.162 108.27 88.744 48.314 39.601 96.628 79.203 144.94 118.8 50.856 41.685 101.71 83.369 152.57 125.05 43.715 35.831 87.43 71.663 131.14 107.49 26.89 22.041 53.781 44.082 80.671 66.124l12.36 10.131c3.707 3.039 9.046-2.235 5.303-5.304-14.181-11.624-28.363-23.248-42.544-34.872-36.089-29.581-72.178-59.163-108.27-88.744-48.314-39.601-96.628-79.203-144.94-118.8-50.856-41.685-101.71-83.37-152.57-125.06-43.715-35.831-87.43-71.663-131.14-107.49-26.89-22.041-53.781-44.082-80.671-66.124l-12.36-10.131c-3.707-3.037-9.047 2.236-5.303 5.305z" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
BIN
templates/assets/screen-fields.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
templates/assets/screen-formslist.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
templates/assets/screen-params.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
templates/assets/screen-question.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
templates/assets/screen-responses-export.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
templates/assets/screen-responses.png
Normal file
After Width: | Height: | Size: 35 KiB |
28
templates/error.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<!doctype html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<title>{{ "error_title"|tr(lang) }}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
|
||||
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||
<body>
|
||||
<div class="flex page-heading error fullheight">
|
||||
<div class="flex page-heading-text">
|
||||
<div>
|
||||
<h1 class="title">{{ "error_title"|tr(lang) }}</h1>
|
||||
<h2 class="title">{{ "error_description"|tr(lang) }}</h2>
|
||||
<h3 class="title">{{ error_msg|tr(lang) }}</h3>
|
||||
<p class="title">{{ "error_note1"|tr(lang) }}</h3>
|
||||
<p class="title">{{ "error_note2"|tr(lang) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullwidth flex">
|
||||
<a class="ncstyle-button error margin-bottom" href="/">{{ "error_back"|tr(lang) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
118
templates/index.html
Normal file
|
@ -0,0 +1,118 @@
|
|||
<!doctype html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<title>{{ "index_title"|tr(lang) }} – {{ "index_description"|tr(lang) }}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
|
||||
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||
<body>
|
||||
<div class="flex page-heading fullheight">
|
||||
<div class="flex page-heading-text">
|
||||
<div class="flex">
|
||||
<a class="flex" href="https://42l.fr"><img class="logo" src="/assets/logo.svg" /></a>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="title">{{ "index_title"|tr(lang) }}<sup class="beta-tag">{{ "index_beta_tag"|tr(lang) }}</sup></h1>
|
||||
<h2 class="title">{{ "index_description"|tr(lang) }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fullwidth flex">
|
||||
<a class="ncstyle-button margin-bottom" href="/link">{{ "index_createform_button"|tr(lang) }}</a>
|
||||
</div>
|
||||
<a class="scroll-down-link scroll-down-arrow"></a>
|
||||
</div>
|
||||
<div class="has-text-centered beta-banner">
|
||||
<h3>{{ "index_beta_banner_desc1"|tr(lang) }}</h3>
|
||||
<p>{{ "index_beta_banner_desc1"|tr(lang) }}</p>
|
||||
<p>{{ "index_beta_banner_desc2"|tr(lang) }}<a href="https://42l.fr/Contact">{{ "index_beta_banner_desc_link"|tr(lang) }}</a>.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="c-flex c-jumbo c-color-inverted c-color-mailred">
|
||||
<div class="c-fullwidth">
|
||||
<div class="has-text-centered">
|
||||
<br />
|
||||
<br />
|
||||
<p>{{ "index_disclaimer1"|tr(lang) }}</p>
|
||||
<p>{{ "index_disclaimer2"|tr(lang) }}<a href="https://42l.fr/Faire-un-don">{{ "index_disclaimer2_link_org"|tr(lang) }}</a>{{ "index_disclaimer2_or"|tr(lang) }}<a href="https://www.bountysource.com/teams/nextcloud">{{ "index_disclaimer2_nc"|tr(lang) }}</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="c-flex c-jumbo">
|
||||
<div class="c-subelem">
|
||||
<a target="_blank" href="/assets/screen-question.png"><img class="c-img-shadow" alt="" src="/assets/screen-question.png" /></a>
|
||||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel1_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel1_desc1"|tr(lang) }}</p>
|
||||
<p>{{ "index_panel1_desc2"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-flex c-flex-reverse c-jumbo">
|
||||
<div class="c-subelem">
|
||||
<a target="_blank" href="/assets/screen-fields.png"><img class="c-img-shadow" alt="" src="/assets/screen-fields.png" /></a>
|
||||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel2_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel2_desc1"|tr(lang) }}</p>
|
||||
<p>{{ "index_panel2_desc2"|tr(lang) }}<a href="https://github.com/nextcloud/forms/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature%3A+%E2%9D%93+question+types%22">{{ "index_panel2_desc2_link"|tr(lang) }}</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-flex c-jumbo">
|
||||
<div class="c-subelem">
|
||||
<a target="_blank" href="/assets/screen-responses.png"><img class="c-img-shadow" alt="" src="/assets/screen-responses.png" /></a>
|
||||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel3_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel3_desc1"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-flex c-flex-reverse c-jumbo">
|
||||
<div class="c-subelem">
|
||||
<a target="_blank" href="/assets/screen-responses-export.png"><img class="c-img-shadow" alt="" src="/assets/screen-responses-export.png" /></a>
|
||||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel4_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel4_desc1"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-flex c-jumbo">
|
||||
<div class="c-subelem">
|
||||
<a target="_blank" href="/assets/screen-params.png"><img class="c-img-shadow" alt="" src="/assets/screen-params.png" /></a>
|
||||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel5_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
|
||||
<p>{{ "index_panel5_desc2"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-flex c-flex-reverse c-jumbo">
|
||||
<div class="c-subelem">
|
||||
<a target="_blank" href="/assets/screen-formslist.png"><img class="c-img-shadow" alt="" src="/assets/screen-formslist.png" /></a>
|
||||
</div>
|
||||
<div class="c-subelem">
|
||||
<h3>{{ "index_panel6_title"|tr(lang) }}</h3>
|
||||
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="c-flex c-jumbo c-blue">
|
||||
<a href="https://42l.fr/Rapport-technique" class="c-button" target="_blank">{{ "index_bottom_docs"|tr(lang) }}</a>
|
||||
<a href="https://github.com/nextcloud/forms" class="c-button" target="_blank">{{ "index_bottom_source"|tr(lang) }}</a>
|
||||
<a href="https://github.com/nextcloud/forms/blob/master/LICENSE" class="c-button" target="_blank">{{ "index_bottom_lic"|tr(lang) }}</a>
|
||||
</div>
|
||||
<br />
|
||||
<div class="has-text-centered page-heading">
|
||||
<br />
|
||||
<h3 class="title">Crédits</h3>
|
||||
<p>{{ "index_credits_desc1"|tr(lang) }}<a href="https://nextcloud.com/">{{ "index_credits_desc1_link"|tr(lang) }}</a>{{ "index_credits_desc1_a"|tr(lang) }}</p>
|
||||
<p>{{ "index_credits_desc2"|tr(lang) }}<a href="https://shelter.moe/@Neil">Neil</a>{{ "index_credits_desc2_for"|tr(lang) }}<a href="https://42l.fr">{{ "index_credits_desc2_org"|tr(lang) }}</a> {{"index_credits_desc3"|tr(lang) }}.</p>
|
||||
<br />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
64
templates/link.html
Normal file
|
@ -0,0 +1,64 @@
|
|||
<!doctype html>
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<title>{{ "link_title"|tr(lang) }} – {{ "index_title"|tr(lang) }}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
|
||||
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
|
||||
<script type="text/javascript">
|
||||
window.onload = function () {
|
||||
// show link copy button if javascript is enabled
|
||||
document.getElementById("script-copy").style.display = "unset";
|
||||
let btn = document.getElementById("script-copy-btn");
|
||||
btn.style.cursor = "pointer";
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var copyText = document.getElementById("link");
|
||||
/* Select the text field */
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999);
|
||||
|
||||
document.execCommand("copy");
|
||||
btn.innerHTML = '{{ "link_copied"|tr(lang) }}';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<div class="navbar has-text-centered page-heading"></div>
|
||||
<div class="fullheight-nav">
|
||||
<div class="c-flex c-jumbo">
|
||||
<div class="c-fullwidth">
|
||||
<div class="has-text-centered">
|
||||
<br />
|
||||
<h2>{{ "link_title"|tr(lang) }}</h2>
|
||||
<p>{{ "link_desc1"|tr(lang)|safe }}</p>
|
||||
<p>{{ "link_desc2"|tr(lang)|safe }}</p>
|
||||
<br />
|
||||
<div class="c-flex">
|
||||
<input id="link" class="ncstyle-input" type="text" readonly value="{{ config.sncf_url }}/admin/{{ admin_token }}" />
|
||||
</div>
|
||||
<br />
|
||||
<div id="script-copy">
|
||||
<br />
|
||||
<div class="c-flex">
|
||||
<a id="script-copy-btn" class="ncstyle-button margin-bottom">{{ "link_copy"|tr(lang) }}</a>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
<p>{{ "link_desc3"|tr(lang) }}</p>
|
||||
<br />
|
||||
<div class="c-flex">
|
||||
<a id="forms-btn" class="ncstyle-button margin-bottom" href="{{ config.sncf_url }}/admin/{{ admin_token }}">{{ "link_access_btn"|tr(lang) }}</a>
|
||||
</div>
|
||||
<br />
|
||||
<p>{{ "link_note"|tr(lang) }}{{ config.prune_days }}{{ "link_note2"|tr(lang) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|