Better frequent questions, tiny css improvements

master
Alice 5 years ago
parent 5cf77af233
commit 2fc3a0274c

@ -40,13 +40,16 @@ INSTALLED_APPS = (
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_countries',
'lambdainst',
'payments',
'tickets',
'downloads',
'kb',
'constance',
'constance.backends.database',
'tinymce',
)
MIDDLEWARE = (
@ -274,6 +277,7 @@ CONSTANCE_CONFIG = {
'TRIAL_PERIOD_MAX': (84, "Maximum number of trial periods to give (84*2h=1w)"),
'NOTIFY_DAYS_BEFORE': ("3, 1", "When to send account expiration notifications. In number of days before, separated y commas",
'integer_list'),
'KB_MAX_TOP_ENTRIES': (10, "Maximum number of categories to show in the short list"),
}
CONSTANCE_ADDITIONAL_FIELDS = {
@ -282,6 +286,22 @@ CONSTANCE_ADDITIONAL_FIELDS = {
}],
}
TINYMCE_DEFAULT_CONFIG = {
'selector': 'textarea',
'theme': 'modern',
'plugins': 'link image preview codesample table code lists paste',
'toolbar1': 'bold italic underline | alignleft aligncenter alignright alignjustify '
'| bullist numlist | outdent indent | table | link image | codesample | preview code',
'contextmenu': 'formats | link image',
'menubar': False,
'inline': False,
'statusbar': True,
'height': 360,
'width': 'auto',
'paste_as_text': True,
'browser_spellcheck': True,
}
# Local settings
try:

@ -1,6 +1,7 @@
from django.urls import path, include
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.views.generic.base import RedirectView
from lambdainst import views as account_views
@ -18,6 +19,7 @@ urlpatterns = [
path('ca.crt', account_views.ca_crt),
path('setlang', views.set_lang, name='set_lang'),
path('chat', views.chat, name='chat'),
path('page/faq', RedirectView.as_view(url='/kb/')),
path('page/<name>', views.page, name='page'),
path('status', account_views.status),
@ -30,8 +32,10 @@ urlpatterns = [
path('account/reset/done/', auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'),
path('tinymce/', include('tinymce.urls')),
path('account/', include('lambdainst.urls', namespace='account')),
path('payments/', include('payments.urls', namespace='payments')),
path('tickets/', include('tickets.urls', namespace='tickets')),
path('kb/', include('kb.urls', namespace='kb')),
]

@ -0,0 +1,48 @@
from django.contrib import admin
from django_admin_listfilter_dropdown.filters import DropdownFilter
from .models import KbCategory, KbCategoryName, KbCategoryDescription
from .models import KbEntry, KbEntryAnswer, KbEntryQuestion
def custom_titled_filter(title, c=admin.FieldListFilter):
class Wrapper(c):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title = title
return Wrapper
class KbCategoryNameInline(admin.TabularInline):
model = KbCategoryName
extra = 0
class KbCategoryDescriptionInline(admin.TabularInline):
model = KbCategoryDescription
extra = 0
class KbCategoryAdmin(admin.ModelAdmin):
list_display = ('slug', 'name', 'entries')
inlines = (KbCategoryNameInline, KbCategoryDescriptionInline)
def entries(self, instance):
return instance.entry_set.count()
class KbEntryQuestionInline(admin.TabularInline):
model = KbEntryQuestion
extra = 0
class KbEntryAnswerInline(admin.StackedInline):
model = KbEntryAnswer
extra = 0
class KbEntryAdmin(admin.ModelAdmin):
list_display = ('category', 'title', 'question', 'score', 'internal')
list_filter = ('category', 'internal')
search_fields = ('category', 'title', 'question', 'answer')
inlines = (KbEntryQuestionInline, KbEntryAnswerInline)
def score(self, instance):
return "%d (%d votes)" % (instance.get_score(), instance.get_vote_count())
admin.site.register(KbCategory, KbCategoryAdmin)
admin.site.register(KbEntry, KbEntryAdmin)

@ -0,0 +1,5 @@
from django.apps import AppConfig
class HelpConfig(AppConfig):
name = 'help'

@ -0,0 +1,93 @@
# Generated by Django 2.2.1 on 2019-10-22 13:15
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import tinymce.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='KbCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField()),
('internal_name', models.CharField(max_length=100, verbose_name='Internal name')),
],
options={
'verbose_name': 'Knowledgebase Category',
'verbose_name_plural': 'Knowledgebase Categories',
},
),
migrations.CreateModel(
name='KbEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_date', models.DateTimeField(auto_now_add=True)),
('title', models.CharField(max_length=50)),
('internal', models.BooleanField(default=False)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='entry_set', to='kb.KbCategory')),
],
options={
'verbose_name': 'Knowledgebase Entry',
'verbose_name_plural': 'Knowledgebase Entries',
},
),
migrations.CreateModel(
name='KbEntryVote',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_date', models.DateTimeField(auto_now_add=True)),
('vote', models.IntegerField()),
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vote_set', to='kb.KbEntry')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Knowledgebase Entry Vote',
},
),
migrations.CreateModel(
name='KbEntryQuestion',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('language', models.CharField(max_length=2)),
('question', models.CharField(max_length=200)),
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='question_set', to='kb.KbEntry')),
],
),
migrations.CreateModel(
name='KbEntryAnswer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('language', models.CharField(max_length=2)),
('answer', tinymce.models.HTMLField()),
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answer_set', to='kb.KbEntry')),
],
),
migrations.CreateModel(
name='KbCategoryName',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('language', models.CharField(max_length=2)),
('name', models.CharField(max_length=100)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='name_set', to='kb.KbCategory')),
],
),
migrations.CreateModel(
name='KbCategoryDescription',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('language', models.CharField(max_length=2)),
('description', models.TextField()),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='description_set', to='kb.KbCategory')),
],
),
]

@ -0,0 +1,136 @@
from django.db import models
from django.db.models.aggregates import Sum
from django.conf import settings
from django.urls import reverse
from django.template.loader import render_to_string
from django.dispatch import receiver
from django.utils.translation import get_language
from constance import config
from tinymce import HTMLField
def get_internationalized(obj_set, prop_name):
obj = obj_set.filter(language=get_language()).first()
if obj:
return getattr(obj, prop_name)
return ""
class KbCategory(models.Model):
slug = models.SlugField()
internal_name = models.CharField("Internal name", max_length=100)
def get_absolute_url(self):
return reverse('help:kb_category', kwargs=dict(category=self.slug))
def get_top_entries(self, n=None):
n = n or config.KB_MAX_TOP_ENTRIES
return KbEntry.objects.get_top(n, category=self)
def get_entry_count(self):
return self.entry_set.filter(internal=False).count()
def __str__(self):
return self.name
@property
def name(self):
return get_internationalized(self.name_set, 'name')
@property
def description(self):
return get_internationalized(self.description_set, 'description')
class Meta:
verbose_name = "Knowledgebase Category"
verbose_name_plural = "Knowledgebase Categories"
class KbCategoryName(models.Model):
category = models.ForeignKey(KbCategory, on_delete=models.CASCADE,
related_name='name_set')
language = models.CharField(max_length=2)
name = models.CharField(max_length=100)
class KbCategoryDescription(models.Model):
category = models.ForeignKey(KbCategory, on_delete=models.CASCADE,
related_name='description_set')
language = models.CharField(max_length=2)
description = models.TextField()
class KbEntry(models.Model):
create_date = models.DateTimeField(auto_now_add=True, editable=False)
category = models.ForeignKey(KbCategory, on_delete=models.CASCADE,
related_name='entry_set')
title = models.CharField(max_length=50)
internal = models.BooleanField(default=False)
def vote(self, user, v):
vote = self.vote_set.filter(user=user).first()
if vote:
if vote.vote == v:
return
vote.vote = v
vote.save()
else:
v = KbEntryVote(entry=self, user=user, vote=v)
v.save()
def get_score(self):
return self.vote_set.aggregate(Sum('vote'))['vote__sum'] or 0
def get_vote_count(self):
return self.vote_set.count()
def get_absolute_url(self):
return reverse('help:kb_entry', kwargs=dict(category=self.category.slug, entry=self.id))
@property
def question(self):
return self.question_set.filter(language=get_language()).first().question
@property
def answer(self):
return self.answer_set.filter(language=get_language()).first().answer
def __str__(self):
return self.question
class Meta:
verbose_name = "Knowledgebase Entry"
verbose_name_plural = "Knowledgebase Entries"
class KbEntryManager(models.Manager):
def get_top(self, n, **kwargs):
return (self.filter(internal=False, **kwargs)
.annotate(Sum('vote_set__vote'))
.order_by('-vote_set__vote__sum')
[:n])
objects = KbEntryManager()
class KbEntryQuestion(models.Model):
entry = models.ForeignKey(KbEntry, on_delete=models.CASCADE,
related_name='question_set')
language = models.CharField(max_length=2)
question = models.CharField(max_length=200)
class KbEntryAnswer(models.Model):
entry = models.ForeignKey(KbEntry, on_delete=models.CASCADE,
related_name='answer_set')
language = models.CharField(max_length=2)
answer = HTMLField()
class KbEntryVote(models.Model):
entry = models.ForeignKey(KbEntry, on_delete=models.CASCADE,
related_name='vote_set')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
null=True)
create_date = models.DateTimeField(auto_now_add=True, editable=False)
vote = models.IntegerField()
def __str__(self):
return 'Vote %+d on %s by %s' % (self.vote, self.entry, self.user)
class Meta:
verbose_name = "Knowledgebase Entry Vote"

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,13 @@
from django.urls import path
from . import views
app_name = 'help'
urlpatterns = [
path('', views.kb_index, name='kb_index'),
path('<slug:category>/', views.kb_category, name='kb_category'),
path('<slug:category>/<int:entry>/', views.kb_entry, name='kb_entry'),
path('<slug:category>/<int:entry>/feedback/', views.kb_feedback, name='kb_feedback'),
]

@ -0,0 +1,42 @@
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import Http404
from django import forms
from django.forms import inlineformset_factory
from django.utils.translation import get_language
from .models import KbCategory, KbEntry
def kb_index(request):
return render(request, 'kb/kb_index.html', {
'categories': KbCategory.objects.all(),
})
def kb_category(request, category):
category = get_object_or_404(KbCategory, slug=category)
return render(request, 'kb/kb_category.html', {
'category': category,
'items': list(KbEntry.objects.filter(category=category)),
})
def kb_entry(request, category, entry):
entry = get_object_or_404(KbEntry, category__slug=category, pk=entry, internal=False)
return render(request, 'kb/kb_item.html', {
'item': entry,
})
@login_required
def kb_feedback(request, category, entry):
entry = get_object_or_404(KbEntry, category__slug=category, pk=entry, internal=False)
arg = request.GET.get('vote')
if arg == 'up':
entry.vote(request.user, 1)
elif arg == 'down':
entry.vote(request.user, -1)
return redirect(entry.get_absolute_url())

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-07 21:02+0000\n"
"POT-Creation-Date: 2019-10-22 19:16+0000\n"
"PO-Revision-Date: 2016-04-07 01:32+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
@ -27,7 +27,7 @@ msgstr ""
msgid "hash"
msgstr ""
#: ccvpn/views.py:40 templates/tickets/layout.html:11
#: ccvpn/views.py:37
msgid "Live Chat"
msgstr "Chat Live"
@ -130,11 +130,11 @@ msgstr "Utilisateur de codes"
msgid "Gift Code Users"
msgstr "Utilisateurs de codes"
#: lambdainst/openvpn.py:10 templates/ccvpn/page.html:17
#: lambdainst/openvpn.py:10 templates/base_help.html:42
msgid "Windows"
msgstr "Windows"
#: lambdainst/openvpn.py:11 templates/ccvpn/page.html:16
#: lambdainst/openvpn.py:11 templates/base_help.html:38
msgid "Android"
msgstr "Android"
@ -142,7 +142,7 @@ msgstr "Android"
msgid "Ubuntu"
msgstr "Ubuntu"
#: lambdainst/openvpn.py:13 templates/ccvpn/page.html:19
#: lambdainst/openvpn.py:13 templates/base_help.html:50
msgid "OS X"
msgstr "OS X"
@ -150,7 +150,7 @@ msgstr "OS X"
msgid "iOS"
msgstr "iOS"
#: lambdainst/openvpn.py:15 templates/ccvpn/page.html:20
#: lambdainst/openvpn.py:15 templates/base_help.html:54
msgid "Chrome OS"
msgstr "Chrome OS"
@ -334,21 +334,15 @@ msgid "Confirming transaction"
msgstr "Confirmation de la transaction"
#: payments/backends/coinpayments.py:169
#, fuzzy
#| msgid "Payments"
msgid "CoinPayments"
msgstr "Paiements"
msgstr "CoinPayments"
#: payments/backends/coinpayments.py:198
#, fuzzy
#| msgid ""
#| "Waiting for PayPal to confirm the transaction... It can take up to a few "
#| "minutes..."
msgid ""
"Waiting for CoinPayments to confirm the transaction... It can take up to a "
"few minutes..."
msgstr ""
"En attente de la confirmation par Paypal... Cette étape peut durer quelques "
"En attente de la confirmation par CoinPayments... Cette étape peut durer quelques "
"minutes..."
#: payments/backends/paypal.py:14 payments/backends/paypal.py:15
@ -472,6 +466,43 @@ msgstr ""
msgid "Unknown content"
msgstr ""
#: templates/base_help.html:8
msgid "Help"
msgstr "Aide"
#: templates/base_help.html:13 templates/layout.html:55
msgid "Guides"
msgstr "Guides"
#: templates/base_help.html:19 templates/kb/kb_category.html:4
#: templates/kb/kb_index.html:4
msgid "Knowledge Base"
msgstr "F.A.Q."
#: templates/base_help.html:25
msgid "Self-Diagnosis"
msgstr "Auto-Diagnostic"
#: templates/base_help.html:30
msgid "Privacy"
msgstr "Vie Privée"
#: templates/base_help.html:34
msgid "Installation"
msgstr "Installation"
#: templates/base_help.html:46
msgid "GNU/Linux"
msgstr "GNU/Linux"
#: templates/base_help.html:58
msgid "Advanced"
msgstr "Avancé"
#: templates/base_help.html:62
msgid "Tor"
msgstr "Tor"
#: templates/ccvpn/chat.html:9
#, python-format
msgid ""
@ -593,26 +624,6 @@ msgstr ""
"<b>Profitez de l'IPv6</b> même sur les réseaux qui ne supportent pas ou mal "
"l'IPv6."
#: templates/ccvpn/page.html:8
msgid "Help"
msgstr "Aide"
#: templates/ccvpn/page.html:10
msgid "Frequently Asked Questions"
msgstr "Questions fréquemment posées"
#: templates/ccvpn/page.html:11
msgid "Self-Diagnosis"
msgstr "Auto-Diagnostic"
#: templates/ccvpn/page.html:14
msgid "Installation"
msgstr "Installation"
#: templates/ccvpn/page.html:18
msgid "GNU/Linux"
msgstr "GNU/Linux"
#: templates/ccvpn/require_email.html:8
msgid "E-mail Confirmation"
msgstr "Confirmation de l'adresse e-mail"
@ -664,6 +675,22 @@ msgstr "Mot de passe oublié ?"
msgid "Need help?"
msgstr "Besoin d'aide ?"
#: templates/kb/kb_index.html:7
msgid ""
"Please check to see if any of these answers address your problem prior to "
"opening a support ticket."
msgstr ""
"Veuillez vérifier si une des questions suivantes résolvent votre problème "
"avant d'ouvrir un ticket."
#: templates/kb/kb_index.html:20
msgid "View all questions"
msgstr "Voir toutes les questions"
#: templates/kb/kb_item.html:13
msgid "Did you find this answer useful?"
msgstr "Avez-vous trouvé cette réponse utile ?"
#: templates/lambdainst/account.html:23
#, python-format
msgid ""
@ -984,10 +1011,6 @@ msgstr "Se connecter"
msgid "VPN"
msgstr "VPN"
#: templates/layout.html:55
msgid "Guides"
msgstr "Guides"
#: templates/layout.html:57 templates/tickets/index.html:6
#: templates/tickets/layout.html:8 tickets/models.py:13
msgid "Support"
@ -1130,19 +1153,23 @@ msgstr "Sujet"
msgid "New Ticket"
msgstr "Nouveau ticket"
#: templates/tickets/layout.html:14
#: templates/tickets/layout.html:19
msgid "Open Tickets"
msgstr "Tickets ouverts"
#: templates/tickets/layout.html:16
#: templates/tickets/layout.html:26
msgid "Closed Tickets"
msgstr "Tickets fermés"
#: templates/tickets/layout.html:18
#: templates/tickets/layout.html:31
msgid "Staff"
msgstr ""
#: templates/tickets/layout.html:36
msgid "All Open"
msgstr "Tous ouverts"
#: templates/tickets/layout.html:20
#: templates/tickets/layout.html:43
msgid "All Closed"
msgstr "Tous fermés"
@ -1266,18 +1293,3 @@ msgstr "Peut envoyer des messages privés"
#: tickets/models.py:55
msgid "Waiting for staff"
msgstr "En attente du support"
#~ msgid ""
#~ "Error subscribing. It usually means you don't have enough money available."
#~ msgstr ""
#~ "Il y a une erreur à l'inscription. C'est le plus souvent un manque de "
#~ "fonds sur la carte."
#~ msgid "Subscribed!"
#~ msgstr "Abonné!"
#~ msgid "You can cancel it from PayPal account."
#~ msgstr "Vous pouvez annuler depuis votre compte PayPal."
#~ msgid "Do you really want to cancel your subscription?"
#~ msgstr "Voulez-vous vraimer annuler votre abonnement ?"

33
poetry.lock generated

@ -49,6 +49,14 @@ version = "2.2.1"
pytz = "*"
sqlparse = "*"
[[package]]
category = "main"
description = "Use dropdowns in Django admin list filter"
name = "django-admin-list-filter-dropdown"
optional = false
python-versions = "*"
version = "1.0.3"
[[package]]
category = "main"
description = "Django live settings with pluggable backends, including Redis."
@ -91,6 +99,18 @@ version = "2.0"
[package.dependencies]
Django = ">=1.11"
[[package]]
category = "main"
description = "A Django application that provides a fully functional TinyMCE 4 editor widget for models and forms."
name = "django-tinymce4-lite"
optional = false
python-versions = "*"
version = "1.7.5"
[package.dependencies]
Django = ">=1.8.0"
jsmin = "*"
[[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
@ -107,6 +127,14 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "4.3.19"
[[package]]
category = "main"
description = "JavaScript minifier."
name = "jsmin"
optional = false
python-versions = "*"
version = "2.2.2"
[[package]]
category = "dev"
description = "A fast and thorough lazy object proxy."
@ -293,7 +321,7 @@ python-versions = "*"
version = "1.11.1"
[metadata]
content-hash = "5a877ca66b221fad6e7837be828b4474efe123d30feb664c6b11bb38edc4ce07"
content-hash = "c00450bd56aa5a669d833686086c1839e5f67dd1ed271fa7a1898d88d6a9ae10"
python-versions = "^3.5"
[metadata.hashes]
@ -302,12 +330,15 @@ certifi = ["59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", "
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
django = ["6fcc3cbd55b16f9a01f37de8bcbe286e0ea22e87096557f1511051780338eaea", "bb407d0bb46395ca1241f829f5bd03f7e482f97f7d1936e26e98dacb201ed4ec"]
django-admin-list-filter-dropdown = ["07cd37b6a9be1b08f11d4a92957c69b67bc70b1f87a2a7d4ae886c93ea51eb53", "bf1b48bab9772dad79db71efef17e78782d4f2421444d5e49bb10e0da71cd6bb"]
django-constance = ["19ff1ae8295aeffc2917ab67da4b310bfaf8c42f34d570f89e289fd54c4217b0", "417f9866a4fcd93c198acd16d5bc22b68e491eaabb18efea4c70d183d42daa45"]
django-countries = ["5307a61172eee5740720e44ea08721858b7d8bf8509ec7701ccd7a8d21120b9a", "e4eaaec9bddb9365365109f833d1fd0ecc0cfee3348bf5441c0ccefb2d6917cd"]
django-jsonfield = ["af7938ebf0eb945d29648d80bad4845973f1ab1d9fea67f65a963753e913e271", "f357e196565daa503e914b6bdbeeaa104288a5e3b3f982a928a8de46f54e55ea"]
django-picklefield = ["9052f2dcf4882c683ce87b4356f29b4d014c0dad645b6906baf9f09571f52bc8", "f1733a8db1b6046c0d7d738e785f9875aa3c198215de11993463a9339aa4ea24"]
django-tinymce4-lite = ["e6776bc5b2c7237705fea18668574bc1c4dff36babc90c99a2bb7b5d636eb5e8", "f0958117ddacc72596e80746729e02a727264413ab54b799f3b697a44e054e87"]
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
isort = ["49293e2ff590cc8d48bc1f51970548b5b102bf038439ca1af77f352164725628", "ba69a4be8474be11720636bc2f0cf66f7054d417d4c1dbc1dfe504bb8e739541"]
jsmin = ["b6df99b2cd1c75d9d342e4335b535789b8da9107ec748212706ef7bbe5c2553b"]
lazy-object-proxy = ["159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661", "23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f", "3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13", "3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821", "4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71", "4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e", "64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea", "6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229", "7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4", "7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e", "8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20", "a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16", "acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b", "be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7", "bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c", "c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a", "dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e", "e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1"]
lcoreapi = []
markdown = ["fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250", "fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680"]

@ -20,6 +20,8 @@ lcoreapi = {git = "https://git.packetimpact.net/lvpn/lcoreapi.git"}
pygments = "^2.3"
psycopg2-binary = "^2.8"
python-frontmatter = "^0.4.5"
django-tinymce4-lite = "^1.7"
django-admin-list-filter-dropdown = "^1.0"
[tool.poetry.dev-dependencies]
pylint = "^2.3"

@ -19,11 +19,10 @@ html, button, input, select, textarea,
}
a{
color: #1c619a;
color: #0b2d4f;
}
em {
color: #9A691C;
font-style: normal;
font-weight: bold;
}
@ -175,8 +174,8 @@ header nav a{
font-weight: 600;
}
.left-menu ul {
list-style-type: disc;
padding: 0.5em 1em 0.5em 30px;
list-style-type: none;
padding: 0.5em 1em 0.5em 1em;
margin: 0;
}
.left-menu li {
@ -186,7 +185,7 @@ header nav a{
text-decoration: none;
}
.left-menu + .content {
.left-menu + .content{
margin-left: 200px;
}
@ -217,6 +216,83 @@ header nav a{
padding-left: 2em;
}
.flex-page {
display: flex;
}
.flex-page .left-menu {
display: block;
float: none;
width: 200px;
position: relative;
margin-left: 1em;
flex-shrink: 0;
}
.content-box {
margin: 1em;
border: 1px solid #bbb;
padding: 2em 2em;
background: #fff;
overflow: auto;
flex-grow: 2;
-webkit-box-shadow: 1px 1px 2px 1px rgba(0,0,0,0.21);
-moz-box-shadow: 1px 1px 2px 1px rgba(0,0,0,0.21);
box-shadow: 1px 1px 2px 1px rgba(0,0,0,0.21);
}
.content-box h2 {
margin-top: 0;
font-size: 1.5em;
font-weight: bold;
color: #1c619a;
}
.content-box h3 {
font-weight: bold;
font-size: 1.2em;
margin: 0.5em 0 0.25em 0;
}
.content-box h4 {
font-weight: bold;
font-size: 1.0em;
margin: 1em 0 0.25em 2em;
}
.content-box ul {
margin: 0.25em 0 0.5em 0;
}
.content-box ul {
list-style-type: circle;
}
.content-box hr {
height: 0;
border: 0;
border-top: 1px solid #ccc;
padding: 0;
margin: 1.5em 0;
}
/*.kb-question-list {
list-style-type: circle;
}
.kb-question-list a {
font-size: 1.2em;
}*/
.kb-question-meta {
}
.kb-question-meta .kb-vote-buttons {
padding-left: 0.5em;
}
.kb-vote-buttons button {
padding: 0 0.4em 0.2em 0.4em;
border: 1px solid #ccc;
border-radius: 3px;
}
.kb-question-meta p {
margin: 0 0;
}
footer{
padding-top: 4em;

@ -0,0 +1,73 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load staticfiles %}
{% block content %}
<div class="flex-page">
<div class="left-menu">
<p class="menu-title">{% trans 'Help' %}</p>
<ul>
<li class="pure-menu-item">
<a href="{% url 'page' 'help' %}">
<i class="fa fa-life-ring fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Guides' %}
</a>
</li>
<li class="pure-menu-item">
<a href="{% url 'kb:kb_index' %}">
<i class="fa fa-book fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Knowledge Base' %}
</a>
</li>
<li class="pure-menu-item">
<a href="/page/self-diagnosis">
<i class="fa fa-wrench fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Self-Diagnosis' %}
</a>
</li>
<li><a href="/page/privacy">
<i class="fa fa-shield fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Privacy' %}
</a></li>
</ul>
<p class="menu-title">{% trans 'Installation' %}</p>
<ul>
<li><a href="/page/install-android">
<i class="fa fa-android fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Android' %}
</a></li>
<li><a href="/page/install-windows">
<i class="fa fa-windows fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Windows' %}
</a></li>
<li><a href="/page/install-gnulinux">
<i class="fa fa-linux fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'GNU/Linux' %}
</a></li>
<li><a href="/page/install-osx">
<i class="fa fa-apple fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'OS X' %}
</a></li>
<li><a href="/page/install-chromeos">
<i class="fa fa-chrome fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Chrome OS' %}
</a></li>
</ul>
<p class="menu-title">{% trans 'Advanced' %}</p>
<ul>
<li><a href="/page/advanced-tor">
<i class="fa fa-user-secret fa-fw" aria-hidden="true"></i>&nbsp;
{% trans 'Tor' %}
</a></li>
</ul>
</div>
<div class="content-box">
{% if title %}
<h2>{{ title }}</h2>
{% endif %}
{% block helpdesk_body %}{% endblock %}
</div>
</div>
{% endblock %}

@ -1,36 +1,7 @@
{% extends 'layout.html' %}
{% extends 'base_help.html' %}
{% load i18n %}
{% load staticfiles %}
{% block content %}
<div class="page">
<div class="left-menu">
<p class="menu-title">{% trans 'Help' %}</p>
<ul>
<li><a href="/page/faq">{% trans 'Frequently Asked Questions' %}</a></li>
<li><a href="/page/self-diagnosis">{% trans 'Self-Diagnosis' %}</a></li>
</ul>
<p class="menu-title">{% trans 'Installation' %}</p>
<ul>
<li><a href="/page/install-android">{% trans 'Android' %}</a></li>
<li><a href="/page/install-windows">{% trans 'Windows' %}</a></li>
<li><a href="/page/install-gnulinux">{% trans 'GNU/Linux' %}</a></li>
<li><a href="/page/install-osx">{% trans 'OS X' %}</a></li>
<li><a href="/page/install-chromeos">{% trans 'Chrome OS' %}</a></li>
</ul>
<p class="menu-title">{% trans 'Advanced' %}</p>
<ul>
<li><a href="/page/advanced-tor">{% trans 'Tor' %}</a></li>
</ul>
</div>
<div class="content">
{% if title %}
<h1>{{ title }}</h1>
{% endif %}
{% block helpdesk_body %}
{{ content|safe }}
</div>
</section>
{% endblock %}
{% endblock %}

@ -0,0 +1,19 @@
{% extends "base_help.html" %}{% load i18n %}{% load i18n humanize %}
{% block helpdesk_body %}
<h2>{% trans 'Knowledge Base' %}: {{ category.name }}</h2>
<div class="block padded-block">
<h3>{{ category.name }}</h3>
{% if category.description %}
<p>{{ category.description }}</p>
{% endif %}
<ul class="kb-question-list">
{% for item in items %}
<li><a href="{{ item.get_absolute_url }}">{{ item.question }}</a></li>
{% endfor %}
</ul>
</div>
{% endblock %}

@ -0,0 +1,28 @@
{% extends "base_help.html" %}{% load i18n %}
{% block helpdesk_body %}
<h2>{% trans "Knowledge Base" %}</h2>
<div class="block padded-block">
<p>{% trans "Please check to see if any of these answers address your problem prior to opening a support ticket." %}</p>
{% for category in categories %}
{% with category.get_entry_count as entry_count %}
{% if entry_count >= 1 %}
<h3>{{ category.name }}</h3>
<ul class="kb-cat-top-questions">
{% for item in category.get_top_entries %}
<li><a href="{{ item.get_absolute_url }}">{{ item.question }}</a></li>
{% endfor %}
{% if entry_count > config.KB_MAX_TOP_ENTRIES %}
<li class="more">
<a href='{{ category.get_absolute_url }}'><b>{% trans 'View all questions' %}</b></a>
</li>
{% endif %}
</ul>
{% endif %}
{% endwith %}
{% endfor %}
</div>
{% endblock %}

@ -0,0 +1,24 @@
{% extends "base_help.html" %}{% load i18n humanize %}
{% block helpdesk_body %}
<h2>{{ item.question }}</h2>
{{ item.answer|safe }}
{% if request.user.is_authenticated %}
<hr />
<div class="block block-note kb-question-meta">
<p>
{% trans "Did you find this answer useful?" %}
<span class="kb-vote-buttons">
<a href='feedback/?vote=up'><button type="button"><i class="fa fa-thumbs-up fa-lg"></i></button></a>
<a href='feedback/?vote=down'><button type="button"><i class="fa fa-thumbs-down fa-lg"></i></button></a>
</span>
({{ item.get_score }} with {{ item.get_vote_count }} votes)
</p>
</div>
{% endif %}
{% endblock %}

@ -14,7 +14,7 @@ This page will go over the methods and their uses and issues.
[TOC]
## a. Tor through the VPN
### a. Tor through the VPN
Much better for anonymity, but requires careful use of end-to-end encryption (HTTPS, SSH, ...)
@ -32,9 +32,10 @@ Much better for anonymity, but requires careful use of end-to-end encryption (HT
1. Install the VPN.
2. Install tor or the Tor Browser Bundle.
3. By using the Tor SOCKS proxy or the Tor Browser, you will effectively be using Tor over the VPN.
## b. VPN through Tor
### b. VPN through Tor
Harder to keep anonymous but will provide a clean and secure connection even over Tor.
@ -46,24 +47,28 @@ Harder to keep anonymous but will provide a clean and secure connection even ove
#### Cons:
- this anonymity is lessed depending on the payment method used for the VPN.
- your anonimity depends on the payment method used for the VPN.
- the bandwidth will be limited by Tor and its network.
- VPN servers aren't as anonymous as most Tor exit nodes. please don't get us into any trouble.
#### Installation:
1. Install Tor. The exact method will be highly OS/distribution specific.
2. Set up the VPN to use Tor:
Edit your VPN configuration file to append the following config (or add it in the "additional config" in CCVPNGUI):
socks-proxy-retry
socks-proxy 127.0.0.1 9050
Replace 9050 by 9150 when using the Tor Browser Bundle.
3. (Re)connect to the VPN to start using it through Tor.
**Do not** use the Tor Browser Bundle or SOCKS proxy directly if you want to go through the VPN too.
1. We will assume using a separate host or virtual machine for Tor.
This configuration is implemented by Whonix and Qubes, and we recommend it for strong
anonymity. It also greatly simplifies routing and avoids some simple failure modes.
2. Set up the VPN on the "Workstation" (your host or VM behind the Tor gateway)
* You will need a TCP configuration to go over Tor
* Whonix Workstation: you will need to loosen the local firewall to be able to use a VPN:
`sudo iptables -I OUTPUT -j ACCEPT`
It shouldn't have serious security implications, providing that your physical network (or virtual network between VM)
is properly isolated.
* Whonix Workstation: change the default DNS server to use the VPN's server:
`echo "nameserver 10.99.0.20" | sudo tee /etc/resolv.conf`
* Whonix Workstation: using the default browser will always use Tor through a proxy.
The easiest workaround is to install and use a regular version of Firefox:
`sudo apt install firefox-esr`
3. Your trafic should be sent to the VPN, over Tor.
Your current IP address as seen from a website visited on your workstation VM should be linked to the VPN.

@ -1,139 +0,0 @@
---
Title: Frequently Asked Questions
---
[TOC]
General
-------
### What is a VPN? How does it work?
A Virtual Private Network is a virtual network that provides a secure
and reliable connection between two points over the Internet.
Our VPN forwards your traffic through our VPN servers to prevent it
from being analyzed, filtered, or modified between you and the server
and hides its original source.
Instead of seeing your own IP address and finding your ISP and approximate
geolocation, websites will only see the IP address of the VPN server.
It lets you pick the country and blend in as anyone behind the VPN server.
### How does it compare to tor?
Tor, although free and decentralized, is not designed for the same usage.
It provides better anonymity than a VPN, at the cost of much reduced performances
and in some cases security.
While communications are very secure between two tor nodes,
to communicate to the existing web ("clearnet") it needs *exit nodes*
that will see and handle all your traffic, and some of those have been
seen to abuse that trust. Our servers don't do that.
A VPN can be used for everyday browsing, online gaming, and you can
pick a server close to you for best performances.
For anonymity, tor can be used over the VPN to hide your tor usage to
your ISP or school, as it could be used against you.
### Do you propose an affiliate program?
Yes, sare your affiliate link and earn 2 weeks for each referral that pays.
Invite 24 people and you get one year of free VPN!
### Is P2P allowed?
On some servers. You can see it in CCVPNGUI and on the configuration download page.
### Can I have a dedicated IP address?
Not at the moment.
### Do you limit bandwidth usage?
Every user of a server share its connection without hard limit.
We try to always have enough bandwidth (at least 20Mbps) available
for each connection.
### Do you censor some websites or protocols?
No. BitTorrent is forbidden on some servers.
### Which protocols are supported?
We only support OpenVPN for now.
### Which payment methods are supported?
We support Paypal, credit cards (via Stripe) and many cryptocurrencies (BTC, XMR, LTC, ETH, ...).
Feel free to ask [the support](/page/help) if you need any other method.
### Is it free software?
Yes! Our VPN is based on OpenVPN and we try to release most of our own work as free software.
* [Our Windows OpenVPN GUI](https://github.com/PacketImpact/lvpngui/)
* [This website](https://github.com/CCrypto/ccvpn3/)
### Are my data kept secure?
Yes, the VPN traffic is strongly encrypted and we do not keep any data on the
VPN servers.
The website and database are on a different server, in a
different datacenter.
### Will there be more servers/countries available?
Yes, but we first need money to pay the servers.
If you would like to have a server somewhere, know a good provider or would
like to host one, please contact us.
Account
-------
### Can I have a trial account?
Yes, you just have to [sign up](/account/signup) and click on the dedicated button
to get a repeatable free 2-hour test period. The limit is a full week.
### Can I use my account on multiple machines?
Yes, you can! Up to 10 at the same time!
### How can I delete my account?
Contact [the support](/page/help).
Technical
---------
### Encryption used
Authentication uses a 4096 bits RSA key. (3072 bits on oldest servers)
The current recommended key size considered safe until 2030 is 2048 bits.
VPN trafic encryption is performed with the best cipher available to OpenVPN
(with a recent version AES 256 GCM)
using a random 256 bits key re-generated regularly, and unique to a VPN connection.
Key Exchange uses a 3072 bits Diffie-Hellman parameters.
A 2048 bits key is considered safe until 2030.
### Do you support IPv6?
Yes, most of our servers are dual stack - they perfectly support IPv4 and IPv6
at the same time.
Some are IPv4 only (but we're working with our providers to fix it) and will
block all IPv6 traffic to make sure your IPv6 address is not leaked.
### Do you support PPTP?
No, PPTP is not supported and will not be supported.
PPTP is considered insecure and should never be used.
Legal
-----
### What do you log?
See our [privacy policy](/page/privacy) page.
Your traffic through the VPN is never logged, but VPN connections are.
### Is it really anonymous?
We will not ask your name and you can pay with bitcoins.
The VPN will hide your identity from people over the Internet,
and can help you achieve anonymity by not leaking information through your IP address.
It will however not make you untraceable.
### Will you log traffic or send user data to authorities?
We won't log your traffic under any condition.
We may give the little we know about you to authorities
only if required by the law to keep the service running.
In this case, we'll try to contact you before doing anything if possible.

@ -1,148 +0,0 @@
---
Title: Questions fréquemment posées
---
[TOC]
Géneral
-------
### Qu'est-ce qu'un VPN ? Comment ça marche ?
Un VPN (Réseau Privé Virtuel) est un réseau virtual permettant
d'établir une connexion sécurisée entre deux points d'Internet.
Notre VPN redirige votre traffic vers nos serveurs VPN à travers le tunnel
sécurisé, le protégeant contre la lecture et l'interception par un tiers,
et cachant son origine.
Au lieu de voir votre adresse IP and d'en déduire une localisation approximative,
les sites web ne verront que l'adresse du VPN.
Cela vous donne le choix du pays depuis lequel apparaitre, et vous permet
de vous fondre dans la masse des utilisateurs de ce serveur.
### Quelles sont les différences avec tor ?
Tor, bien que gratuit et décentralisé, n'est pas fait pour le même usage.
Il fournit un meilleur anonymat qu'un VPN, mais au coût de performances
très réduits et dans certains cas la sécurité.
La communication entre deux noeuds tor est bien sécurisée, mais pour accéder
au reste du web ("clearnet") le réseau tor passe par des *exit nodes*
qui vont avoir accès à votre traffic, et certains ont déjà été vu abuser de
cette position. Nos serveurs ne font pas ça.
Un VPN peut être utilisé pour la navigation de tous les jours,
les jeux en ligne, et vous pouvez choisir un serveur proche de vous
pour les meilleures performances.
Pour l'anonymat, tor peut être utilisé en plus du VPN, pour cacher votre
utilisation de tor à votre FAI ou école qui pourrait utiliser ça contre vous.
### Avez-vous un programme d'affiliation ?
Oui, vous pouvez partager un lien associé à votre compte, qui vous
fera gagner 2 semaines de VPN pour chaque client l'ayant suivi.
Inviter 24 personnes vous donne donc 1 an de VPN gratuit !
### Est-ce que le P2P est autorisé ?
Sur certains serveurs. C'est indiqué dans CCVPNGUI et sur la page
de téléchargement de config.
### Puis-je avoir une adresse dédiée ?
Non, pas pour le moment.
### Y a-t-il une limite de bande passante ?
Non, tous les utilisateurs partagent équitablement la connexion des serveurs.
Nous faisons en sorte qu'il y ait toujours un minimum de 20Mbps disponible
pour chaque client.
### Censurez-vous certains sites ou protocoles ?
Non. Le BitTorrent est interdit sur certains serveurs.
### Avec quels protocoles fonctionne le VPN ?
Notre VPN est fait avec OpenVPN.
### Quelles méthodes de payement sont disponibles ?
Vous pouvez payer par Paypal, carte (via Stripe), et plusieurs cryptomonnaies.
Vous pouvez [nous contacter](/page/help) si vous avez besoin d'un autre moyen
de payement.
### Est-ce Libre ?
Oui ! Notre VPN fonctionne avec OpenVPN, et nous essayons de distribuer
une grande partie de notre travail comme logiciel libre.
* [Notre logiciel client pour Windows](https://github.com/PacketImpact/lvpngui/)
* [Ce site](https://github.com/CCrypto/ccvpn3/)
### Est-ce vraiment sécurisé ?
Oui, le VPN utilise différents algorithmes de chiffrement fiables et nous ne
gardons aucune données sensible sur les serveurs du VPN.
Les comptes clients et historiques de connexions sont uniquement gardés sur des
serveurs séparés.
### Y aura-t-il plus de serveurs ou dans d'autres pays ?
Oui, nous ajoutons des serveurs en fonction de la demande et de nos moyens.
Si vous voudriez héberger un serveur, recommander un bon hébergeur, ou
seriez simplement intéressé par une certain pays, contactez nous.
Comptes
-------
### Puis-je avoir un compte de test gratuit ?
Oui, vous n'avez qu'à [créer un compte](/account/signup) et cliquer sur
le bouton dédié pour recevoir une période d'essai gratuite de 2h répétable.
La limite est d'une semaine entière.
### Puis-je utiliser mon compte sur plusieurs machines ?
Oui, vous pouvez utiliser votre compte avec un maximum de 10 connexions
simultannées.
### Comment supprimer mon compte ?
[Contactez nous](/page/help).
Technique
---------
### Chiffrement
L'authentification utilise une clé RSA de 4096 bits. (3072 sur les serveurs plus anciens)
Les clés de 2048 bits ou plus sont considérées sûres jusqu'à 2030.
Le traffic est chiffré avec le meilleur algorithme disponible pour OpenVPN
(AES 256 GCM dans les versions récentes),
en utilisant une clé aléatoire de 256 bits re-générée régulièrement
et unique pour chaque connexion au VPN.
L'échange de clés (Diffie-Hellman) utilise un groupe de 3072 bits.
2048 bits ou plus est considéré suffisant jusqu'en 2030.
### Est-ce que l'IPv6 est supporté ?
Oui, la plupart des serveurs fonctionnent en IPv4 et IPv6 (dual stack).
Quelques-uns ne fonctionnent qu'en IPv4 et bloquent entièrement l'IPv6 pour
éviter de laisser passer votre addresse IPv6.
### Est-ce que le PPTP est supporté ?
Non, le PPTP n'est plus considéré sécurisé et ne doit plus être utilisé.
Légal
-----
### Quelles informations gardez-vous ?
Voyez notre [politique de confidentialité](/page/privacy).
Nous n'enregistrons jamais votre traffic dans le VPN,
mais les connexions au VPN sont loggées.
### Est-ce réellement anonyme ?
Nous ne vous demanderons pas votre nom et nous acceptons les paiements anonymes.
Le VPN cachera votre identité lors de communications sur Internet,
et peut vous aider à atteindre un niveau d'anonymat en évitant de laisser
filtrer informations par l'adresse IP.
Cela ne vous rendra pas pour autant intraçable et ne permet pas d'achapper à la loi.
### Donnez vous des informations aux autorités ?
Nous ne vous espionnerons jamais.
Le peu de données enregistrées peuvent être transmises aux autorités si requis
par la loi.
Dans ce cas, nous essaierons de contacter les clients concernés avant tout,
si possible.

@ -3,7 +3,7 @@ Title: Guides
---
## Installation
### Installation
<ul class="install-guides">
<li><a href="/page/install-android"><i class="fa fa-android fa-5x"></i> Android</a></li>
@ -13,9 +13,10 @@ Title: Guides
<li><a href="/page/install-chromeos"><i class="fa fa-chrome fa-5x"></i> Chromebook</a></li>
</ul>
## Support
### Support
- [**Frequently Asked Questions**](/page/faq)
- [**Privacy**](/page/privacy)
- **[Self-Diagnosis](/page/self-diagnosis)**: Before asking for help, check here if you find the solution to your problem.
- [**Knowledge Base**](/kb/)
- [**Self-Diagnosis**](/page/self-diagnosis): Before asking for help, check here if you find the solution to your problem.
- [**Tickets**](/tickets/)
- E-mail: **support at ccrypto.org**

@ -2,7 +2,7 @@
Title: Guides
---
## Installation
### Installation
<ul class="install-guides">
<li><a href="/page/install-android"><i class="fa fa-android fa-5x"></i> Android</a></li>
@ -12,9 +12,9 @@ Title: Guides
<li><a href="/page/install-chromeos"><i class="fa fa-chrome fa-5x"></i> Chromebook</a></li>
</ul>
## Support
### Support
- [**Questions fréquemment posées**](/page/faq)
- [**Informations personnelles et vie privée**](/page/privacy)
- [**F.A.Q.**](/kb/)
- [**Auto-Diagnostic**](/page/self-diagnosis) : Avant de demander de l'aide, vérifiez si vous trouvez la solution à votre problème ici.
- [**Tickets**](/tickets/)
- E-mail: **support at ccrypto.org**

@ -2,8 +2,7 @@
Title: Install on GNU/Linux
---
With NetworkManager
-------------------
### With NetworkManager
1. Download and install OpenVPN and the NetworkManager plugin with your package manager.
@ -35,8 +34,7 @@ With NetworkManager