V0.1
This commit is contained in:
parent
8dfafa9404
commit
fb4d566007
@ -10,11 +10,13 @@ For the full list of settings and their values, see
|
|||||||
https://docs.djangoproject.com/en/5.1/ref/settings/
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from django.utils.module_loading import import_module
|
from django.utils.module_loading import import_module
|
||||||
|
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
@ -46,23 +48,6 @@ INSTALLED_APPS = [
|
|||||||
"items",
|
"items",
|
||||||
]
|
]
|
||||||
|
|
||||||
BOWER_COMPONENT_ROOT = os.path.join(BASE_DIR, "components")
|
|
||||||
BOWER_INSTALLED_APPS = [
|
|
||||||
"https://code.jquery.com/jquery-3.7.1.min.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/sweetalert2@11",
|
|
||||||
"https://cdn.jsdelivr.net/npm/vue-resource@1.5.3",
|
|
||||||
"https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js",
|
|
||||||
"https://unpkg.com/vue-router@3/dist/vue-router.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/lux/bootstrap.min.css",
|
|
||||||
"https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css",
|
|
||||||
"https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css",
|
|
||||||
"https://fonts.cdnfonts.com/css/jetbrains-mono",
|
|
||||||
]
|
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static_source"),)
|
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static_source"),)
|
||||||
@ -78,11 +63,29 @@ STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
|||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"staticfiles": {
|
"staticfiles": {
|
||||||
# "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
"BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
|
# "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOWER_COMPONENT_ROOT = os.path.join(BASE_DIR, "components")
|
||||||
|
BOWER_INSTALLED_APPS = [
|
||||||
|
"https://code.jquery.com/jquery-3.7.1.min.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/sweetalert2",
|
||||||
|
"https://cdn.jsdelivr.net/npm/vue-resource",
|
||||||
|
"https://unpkg.com/vuex@3.6.2/dist/vuex.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js",
|
||||||
|
"https://unpkg.com/vue-router@3/dist/vue-router.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
|
||||||
|
"bootswatch.min.css=https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/lux/bootstrap.min.css",
|
||||||
|
"https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css",
|
||||||
|
"https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css",
|
||||||
|
"https://fonts.cdnfonts.com/css/jetbrains-mono",
|
||||||
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
@ -161,6 +164,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|||||||
|
|
||||||
from app.settingsLocal import *
|
from app.settingsLocal import *
|
||||||
|
|
||||||
|
|
||||||
for extra_app in EXTRA_APPS:
|
for extra_app in EXTRA_APPS:
|
||||||
INSTALLED_APPS.append(extra_app)
|
INSTALLED_APPS.append(extra_app)
|
||||||
|
|
||||||
|
|||||||
16
k356/app/utils/helpers.py
Normal file
16
k356/app/utils/helpers.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
def recursive_getattr(obj, attr, *args, delimiter="__"):
|
||||||
|
"""Recursive getattr on a object, based on the attr name, delimited with the delimiter."""
|
||||||
|
|
||||||
|
def _getattr(obj, attr):
|
||||||
|
rg = getattr(obj, attr, *args)
|
||||||
|
|
||||||
|
# Usefull for getting attribute like `datetime__date` => will result in datetime.date()
|
||||||
|
if callable(rg):
|
||||||
|
return rg()
|
||||||
|
|
||||||
|
return rg
|
||||||
|
|
||||||
|
return reduce(_getattr, [obj] + attr.split(delimiter))
|
||||||
@ -1,9 +1,14 @@
|
|||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
|
|
||||||
|
|
||||||
|
from app.utils.helpers import recursive_getattr
|
||||||
|
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
@ -17,9 +22,9 @@ class BaseQuerySet(models.QuerySet):
|
|||||||
"text": field.verbose_name.capitalize(),
|
"text": field.verbose_name.capitalize(),
|
||||||
"align": "",
|
"align": "",
|
||||||
"encrypted": field.name in self.model.Encryption.fields,
|
"encrypted": field.name in self.model.Encryption.fields,
|
||||||
"editable": field.name
|
"editable": field.name not in self.model.Serialization.excluded_fields_edit,
|
||||||
not in self.model.Serialization.excluded_fields_edit,
|
|
||||||
"field_widget": "v-textarea",
|
"field_widget": "v-textarea",
|
||||||
|
"choices": None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +46,19 @@ class BaseQuerySet(models.QuerySet):
|
|||||||
field_widget="v-select",
|
field_widget="v-select",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if field.choices:
|
||||||
|
ret[field.name].update(
|
||||||
|
text="",
|
||||||
|
field_widget="v-select",
|
||||||
|
choices=[
|
||||||
|
{
|
||||||
|
"value": c[0],
|
||||||
|
"text": c[1],
|
||||||
|
}
|
||||||
|
for c in field.choices
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
fields = {}
|
fields = {}
|
||||||
@ -78,12 +96,25 @@ class BaseModel(models.Model):
|
|||||||
class Encryption:
|
class Encryption:
|
||||||
fields = ["name", "description", "custom_identifier"]
|
fields = ["name", "description", "custom_identifier"]
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
ret = {}
|
||||||
|
|
||||||
|
for field_name in self._meta.model.objects.headers().keys():
|
||||||
|
value = recursive_getattr(self, field_name)
|
||||||
|
|
||||||
|
if isinstance(value, BaseModel):
|
||||||
|
value = value.id
|
||||||
|
|
||||||
|
ret[field_name] = value
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
|
|
||||||
name = models.TextField(max_length=2048)
|
name = models.TextField(max_length=2048, blank=True, null=True)
|
||||||
description = models.TextField(max_length=2048)
|
description = models.TextField(max_length=2048, blank=True, null=True)
|
||||||
|
|
||||||
custom_identifier = models.TextField(max_length=2048, null=True)
|
custom_identifier = models.TextField(max_length=2048, null=True, blank=True)
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
last_modified_at = models.DateTimeField(auto_now=True)
|
last_modified_at = models.DateTimeField(auto_now=True)
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-09-28 07:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("items", "0002_item_custom_identifier_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="item",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="itemrelation",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="itemrelation",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="itemrelation",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="itemtype",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="itemtype",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="itemtype",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="linkedproperty",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="linkedproperty",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="linkedproperty",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="relationproperty",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="relationproperty",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="relationproperty",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
35
k356/items/migrations/0004_alter_property_type.py
Normal file
35
k356/items/migrations/0004_alter_property_type.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-09-28 11:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("items", "0003_alter_item_custom_identifier_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="property",
|
||||||
|
name="type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("text", "Text"),
|
||||||
|
("date", "Date"),
|
||||||
|
("datetime", "Date & time"),
|
||||||
|
("time", "Time"),
|
||||||
|
("duration", "Duration"),
|
||||||
|
("uuid", "UUID"),
|
||||||
|
("number", "Number"),
|
||||||
|
("float", "Float"),
|
||||||
|
("boolean", "Boolean"),
|
||||||
|
("email", "Email"),
|
||||||
|
("ip", "IP address"),
|
||||||
|
("json", "JSON"),
|
||||||
|
],
|
||||||
|
default="text",
|
||||||
|
max_length=32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,6 +1,8 @@
|
|||||||
from app.utils.models import BaseModel
|
|
||||||
from django.contrib.auth.forms import gettext as _
|
from django.contrib.auth.forms import gettext as _
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
from app.utils.models import BaseModel
|
||||||
from users.models import UserSettings
|
from users.models import UserSettings
|
||||||
|
|
||||||
|
|
||||||
@ -16,21 +18,14 @@ class ItemBase(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ItemType(ItemBase):
|
class ItemType(ItemBase):
|
||||||
name = models.TextField(max_length=2048)
|
pass
|
||||||
description = models.TextField(max_length=2048)
|
|
||||||
|
|
||||||
|
|
||||||
class ItemRelation(ItemBase):
|
class ItemRelation(ItemBase):
|
||||||
parent = models.ForeignKey(
|
parent = models.ForeignKey("items.Item", on_delete=models.CASCADE, related_name="children")
|
||||||
"items.Item", on_delete=models.CASCADE, related_name="children"
|
child = models.ForeignKey("items.Item", on_delete=models.CASCADE, related_name="parents")
|
||||||
)
|
|
||||||
child = models.ForeignKey(
|
|
||||||
"items.Item", on_delete=models.CASCADE, related_name="parents"
|
|
||||||
)
|
|
||||||
|
|
||||||
properties = models.ManyToManyField(
|
properties = models.ManyToManyField("items.Property", through="items.RelationProperty", related_name="relations")
|
||||||
"items.Property", through="items.RelationProperty", related_name="relations"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Item(ItemBase):
|
class Item(ItemBase):
|
||||||
@ -54,13 +49,21 @@ class PropertyType(models.TextChoices):
|
|||||||
TEXT = "text", _("Text")
|
TEXT = "text", _("Text")
|
||||||
DATE = "date", _("Date")
|
DATE = "date", _("Date")
|
||||||
DATETIME = "datetime", _("Date & time")
|
DATETIME = "datetime", _("Date & time")
|
||||||
|
TIME = "time", _("Time")
|
||||||
|
DURATION = "duration", _("Duration")
|
||||||
|
UUID = "uuid", _("UUID")
|
||||||
|
NUMBER = "number", _("Number")
|
||||||
|
FLOAT = "float", _("Float")
|
||||||
|
BOOLEAN = "boolean", _("Boolean")
|
||||||
|
EMAIL = "email", _("Email")
|
||||||
|
IP = "ip", _("IP address")
|
||||||
|
JSON = "json", _("JSON")
|
||||||
|
|
||||||
# TODO: Add more property types (location, etc)
|
# TODO: Add more property types (location, etc)
|
||||||
|
|
||||||
|
|
||||||
class Property(ItemBase):
|
class Property(ItemBase):
|
||||||
type = models.CharField(
|
type = models.CharField(max_length=32, choices=PropertyType.choices, default=PropertyType.TEXT)
|
||||||
max_length=32, choices=PropertyType.choices, default=PropertyType.TEXT
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseLinkedProperty(ItemBase):
|
class BaseLinkedProperty(ItemBase):
|
||||||
@ -74,9 +77,7 @@ class BaseLinkedProperty(ItemBase):
|
|||||||
|
|
||||||
|
|
||||||
class LinkedProperty(BaseLinkedProperty):
|
class LinkedProperty(BaseLinkedProperty):
|
||||||
item = models.ForeignKey(
|
item = models.ForeignKey(Item, on_delete=models.CASCADE, null=True, related_name="linked_properties")
|
||||||
Item, on_delete=models.CASCADE, null=True, related_name="linked_properties"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RelationProperty(BaseLinkedProperty):
|
class RelationProperty(BaseLinkedProperty):
|
||||||
|
|||||||
51
k356/items/templates/components/ItemDetail/template.html
Normal file
51
k356/items/templates/components/ItemDetail/template.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="card mt-4 pt-2 ps-lg-2">
|
||||||
|
<h5 class="card-header">{% trans "Properties" %} [[ this.$route.params.id ]]</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<PropertyList
|
||||||
|
:items="properties"
|
||||||
|
:items_headers="properties_headers"
|
||||||
|
:items_relations="{}"
|
||||||
|
:hidden_fields="[]"
|
||||||
|
group_by="type"
|
||||||
|
@deleteItem="deleteItem"
|
||||||
|
@createItem="createItem"
|
||||||
|
@editItem="editItem"
|
||||||
|
></PropertyList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4 pt-2 ps-lg-2">
|
||||||
|
<h5 class="card-header">{% trans "Children" %}</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<ItemRelationList
|
||||||
|
:items="children"
|
||||||
|
:items_headers="children_headers"
|
||||||
|
:items_relations="{}"
|
||||||
|
:hidden_fields="['parent__name']"
|
||||||
|
group_by="type__name"
|
||||||
|
@deleteItem="deleteItem"
|
||||||
|
@createItem="createItem"
|
||||||
|
@editItem="editItem"
|
||||||
|
></ItemRelationList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4 pt-2 ps-lg-2">
|
||||||
|
<h5 class="card-header">{% trans "Parents" %}</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<ItemRelationList
|
||||||
|
:items="parents"
|
||||||
|
:items_headers="parents_headers"
|
||||||
|
:items_relations="{}"
|
||||||
|
:hidden_fields="['child__name']"
|
||||||
|
group_by="type__name"
|
||||||
|
@deleteItem="deleteItem"
|
||||||
|
@createItem="createItem"
|
||||||
|
@editItem="editItem"
|
||||||
|
></ItemRelationList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
97
k356/items/templates/components/ItemDetail/vue.js
Normal file
97
k356/items/templates/components/ItemDetail/vue.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
ItemDetail = {
|
||||||
|
template: "#ItemDetail",
|
||||||
|
router_path: "/ItemDetail/:id",
|
||||||
|
delimiters: ["[[", "]]"],
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
properties: [],
|
||||||
|
linked_properties: [],
|
||||||
|
children: [],
|
||||||
|
parents: [],
|
||||||
|
// TODO: Also remove this tedious things
|
||||||
|
properties_headers: [],
|
||||||
|
linked_properties_headers: [],
|
||||||
|
children_headers: [],
|
||||||
|
parents_headers: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
// TODO: Remove this by a generic things at some points, this become tedious and repetitive
|
||||||
|
properties_efields: function() {
|
||||||
|
return this.properties_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
linked_properties_efields: function() {
|
||||||
|
return this.linked_properties_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
children_efields: function() {
|
||||||
|
return this.children_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
parents_efields: function() {
|
||||||
|
return this.parents_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function() {
|
||||||
|
|
||||||
|
this.reload()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
async reload () {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const response = await this.$http.get(Urls["items:details"](this.$route.params.id))
|
||||||
|
|
||||||
|
this.properties_headers = response.data.properties_headers
|
||||||
|
this.linked_properties_headers = response.data.linked_properties_headers
|
||||||
|
this.children_headers = response.data.children_headers
|
||||||
|
this.parents_headers = response.data.parents_headers
|
||||||
|
|
||||||
|
// TODO: TEDIOUUUUUS
|
||||||
|
response.data.parents.forEach(async e => {
|
||||||
|
this.parents.push(await this.decryptObject(this.parents_efields, e))
|
||||||
|
})
|
||||||
|
|
||||||
|
response.data.children.forEach(async e => {
|
||||||
|
this.children.push(await this.decryptObject(this.children_efields, e))
|
||||||
|
})
|
||||||
|
|
||||||
|
response.data.properties.forEach(async e => {
|
||||||
|
this.properties.push(await this.decryptObject(this.properties_efields, e))
|
||||||
|
})
|
||||||
|
|
||||||
|
response.data.linked_properties.forEach(async e => {
|
||||||
|
this.linked_properties.push(await this.decryptObject(this.linked_properties_efields, e))
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
Swal.fire({title: "{{_('Error during loading of items') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
|
throw err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteItem () {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async createItem () {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async editItem() {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,5 +3,6 @@
|
|||||||
|
|
||||||
{% block component %}
|
{% block component %}
|
||||||
{% define 'items' 'items' %}
|
{% define 'items' 'items' %}
|
||||||
|
{% define 'show_url' 'ItemDetail' %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
<div>[[ this.$route.params.id ]]</div>
|
||||||
26
k356/items/templates/components/ItemRelationDetail/vue.js
Normal file
26
k356/items/templates/components/ItemRelationDetail/vue.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
ItemRelationDetail = {
|
||||||
|
template: "#ItemRelationDetail",
|
||||||
|
router_path: "/ItemRelationDetail/:id",
|
||||||
|
delimiters: ["[[", "]]"],
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function() {
|
||||||
|
|
||||||
|
this.reload()
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
async reload () {
|
||||||
|
|
||||||
|
const response = await this.$http.get(Urls["items:relation.details"](this.$route.params.id))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% extends "base_components/glist/template.html" %}
|
||||||
9
k356/items/templates/components/ItemRelationList/vue.js
Normal file
9
k356/items/templates/components/ItemRelationList/vue.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "base_components/glist/vue.js" %}
|
||||||
|
{% load main %}
|
||||||
|
|
||||||
|
{% block component %}
|
||||||
|
{% define 'items' 'items' %}
|
||||||
|
{% define 'default_hidden_fields' 'name' 'description' %}
|
||||||
|
{% define 'show_url' 'ItemRelationDetail' %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
@ -6,10 +6,10 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<ItemList
|
<ItemList
|
||||||
:crypto_key="crypto_key"
|
|
||||||
:items="items"
|
:items="items"
|
||||||
:items_headers="items_headers"
|
:items_headers="items_headers"
|
||||||
:items_relations="{'type': types}"
|
:items_relations="{'type': types}"
|
||||||
|
:hidden_fields="[]"
|
||||||
group_by="type__name"
|
group_by="type__name"
|
||||||
@deleteItem="deleteItem"
|
@deleteItem="deleteItem"
|
||||||
@createItem="createItem"
|
@createItem="createItem"
|
||||||
@ -24,10 +24,10 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<ItemList
|
<ItemList
|
||||||
:crypto_key="crypto_key"
|
|
||||||
:items="types"
|
:items="types"
|
||||||
:items_headers="types_headers"
|
:items_headers="types_headers"
|
||||||
:items_relations="{}"
|
:items_relations="{}"
|
||||||
|
:hidden_fields="[]"
|
||||||
group_by="[]"
|
group_by="[]"
|
||||||
@deleteItem="deleteType"
|
@deleteItem="deleteType"
|
||||||
@createItem="createType"
|
@createItem="createType"
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
ItemView = {
|
ItemView = {
|
||||||
template: "#ItemView",
|
template: "#ItemView",
|
||||||
|
router_path: "/ItemView",
|
||||||
delimiters: ["[[", "]]"],
|
delimiters: ["[[", "]]"],
|
||||||
props: ["crypto_key"],
|
props: [],
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
search: "",
|
|
||||||
items: [],
|
items: [],
|
||||||
items_headers: [],
|
items_headers: [],
|
||||||
items_relations: [],
|
|
||||||
types: [],
|
types: [],
|
||||||
types_headers: [],
|
types_headers: [],
|
||||||
types_relations: [],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -20,158 +18,67 @@ ItemView = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
items_encrypted_fields: function() {
|
items_efields: function() {
|
||||||
return this.items_headers.filter(e => e.encrypted).map(e => e.value)
|
return this.items_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
},
|
},
|
||||||
|
|
||||||
types_encrypted_fields: function() {
|
types_efields: function() {
|
||||||
return this.types_headers.filter(e => e.encrypted).map(e => e.value)
|
return this.types_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
reload () {
|
async reload () {
|
||||||
|
|
||||||
var self = this
|
try {
|
||||||
|
const response = await this.$http.get(Urls["items:list"]())
|
||||||
|
|
||||||
this.$http.get(Urls["items:list"]()).then(response => {
|
this.items_headers = response.data.result.items_headers
|
||||||
|
this.types_headers = response.data.result.types_headers
|
||||||
|
|
||||||
Object.keys(response.data.result).forEach(name => {
|
// Decrypt all item the push
|
||||||
self.$set(self, name, response.data.result[name])
|
response.data.result.items.forEach(async item => {
|
||||||
|
const new_item = await this.decryptObject(this.items_efields, item)
|
||||||
|
|
||||||
|
this.items.push(new_item)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.items.forEach(item => {
|
// Decrypt all type the push
|
||||||
self.decryptObject(self.items_encrypted_fields, item)
|
response.data.result.types.forEach(async type => {
|
||||||
|
const new_type = await this.decryptObject(this.types_efields, type)
|
||||||
|
|
||||||
|
this.types.push(new_type)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.types.forEach(type => {
|
} catch (err) {
|
||||||
self.decryptObject(self.types_encrypted_fields, type)
|
|
||||||
})
|
|
||||||
|
|
||||||
}).catch(err => {
|
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Error during loading of items') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Error during loading of items') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
async aDecryptObject (encrypted_fields, obj) {
|
|
||||||
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
|
|
||||||
let promises = encrypted_fields.map(field => {
|
|
||||||
// Encrypt all necessary fields
|
|
||||||
if (obj[field] == null) {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
return decryptWithKey(self.crypto_key, obj[field]).then(dec => {
|
|
||||||
resolve({field: field, value: dec})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}).filter(e => e != null)
|
|
||||||
|
|
||||||
Promise.all(promises).then(values => {
|
|
||||||
|
|
||||||
values.forEach(value => {
|
|
||||||
obj[value.field] = value.value
|
|
||||||
})
|
|
||||||
|
|
||||||
resolve(obj)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
decryptObject (encrypted_fields, obj) {
|
async object_edit(url_edit, url_create, encrypted_fields, method, obj) {
|
||||||
|
|
||||||
var self = this
|
let url = null
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
|
|
||||||
let promises = encrypted_fields.map(field => {
|
|
||||||
// Encrypt all necessary fields
|
|
||||||
if (obj[field] == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
return decryptWithKey(self.crypto_key, obj[field]).then(dec => {
|
|
||||||
resolve({field: field, value: dec})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}).filter(e => e != null)
|
|
||||||
|
|
||||||
Promise.all(promises).then(values => {
|
|
||||||
|
|
||||||
values.forEach(value => {
|
|
||||||
obj[value.field] = value.value
|
|
||||||
})
|
|
||||||
|
|
||||||
resolve(obj)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
object_edition (url_edit, url_create, encrypted_fields, method, obj) {
|
|
||||||
// Return a Promise
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
|
|
||||||
let url = Urls[url_edit](obj.id)
|
|
||||||
|
|
||||||
if (obj.id == undefined || obj.id == null) {
|
if (obj.id == undefined || obj.id == null) {
|
||||||
url = Urls[url_create]()
|
url = Urls[url_create]()
|
||||||
}
|
|
||||||
|
|
||||||
let promises = encrypted_fields.map(field => {
|
|
||||||
// Encrypt all necessary fields
|
|
||||||
if (obj[field] == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
return encryptWithKey(self.crypto_key, obj[field]).then(enc => {
|
|
||||||
resolve({field: field, value: enc})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}).filter(e => e != null)
|
|
||||||
|
|
||||||
Promise.all(promises).then(values => {
|
|
||||||
|
|
||||||
values.forEach(value => {
|
|
||||||
obj[value.field] = value.value
|
|
||||||
})
|
|
||||||
|
|
||||||
self.$http[method](url, obj).then(response => {
|
|
||||||
|
|
||||||
if (method == "delete") {
|
|
||||||
|
|
||||||
resolve()
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
url = Urls[url_edit](obj.id)
|
||||||
self.decryptObject(encrypted_fields, response.data.object).then(new_obj => {
|
|
||||||
resolve(new_obj)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}).catch(err => {
|
try {
|
||||||
|
|
||||||
|
const newobj = await this.encryptObject(encrypted_fields, obj)
|
||||||
|
const response = await this.$http[method](url, newobj)
|
||||||
|
|
||||||
|
if (method != "delete") {
|
||||||
|
return await this.decryptObject(encrypted_fields, response.data.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
let msg = "{{_('Error during edition') | escapejs}}"
|
let msg = "{{_('Error during edition') | escapejs}}"
|
||||||
if (method == "delete") {
|
if (method == "delete") {
|
||||||
@ -180,102 +87,130 @@ ItemView = {
|
|||||||
|
|
||||||
Swal.fire({title: msg, icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: msg, icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
throw err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
item_edition (method, item) {
|
item_edition (method, item) {
|
||||||
return this.object_edition("items:edit", "items:create", this.items_encrypted_fields, method, item)
|
return this.object_edit("items:edit", "items:create", this.items_efields, method, item)
|
||||||
},
|
},
|
||||||
|
|
||||||
type_edition (method, item) {
|
type_edition (method, item) {
|
||||||
return this.object_edition("items:type.edit", "items:type.create", this.items_encrypted_fields, method, item)
|
return this.object_edit("items:type.edit", "items:type.create", this.types_efields, method, item)
|
||||||
},
|
},
|
||||||
|
|
||||||
createItem (item) {
|
async createItem (item) {
|
||||||
|
|
||||||
var self = this
|
try {
|
||||||
|
|
||||||
this.item_edition("post", item).then(new_item => {
|
const new_item = await this.item_edition("post", item)
|
||||||
|
this.items.push(new_item)
|
||||||
self.items.push(new_item)
|
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
} catch (err) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
editItem (index, item) {
|
async editItem (index, item) {
|
||||||
|
|
||||||
var self = this
|
try {
|
||||||
|
|
||||||
this.item_edition("post", item).then(new_item => {
|
// Remove the item
|
||||||
|
this.items.splice(index, 1)
|
||||||
|
|
||||||
// Remove the 'current' (non edited) item from the list
|
const new_item = await this.item_edition("post", item)
|
||||||
self.items.splice(index, 1)
|
|
||||||
self.items.push(new_item)
|
// Add the new item
|
||||||
|
this.items.push(new_item)
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
} catch (err) {
|
||||||
|
|
||||||
|
this.items.push(item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteItem (index) {
|
async deleteItem (index) {
|
||||||
|
|
||||||
var self = this
|
|
||||||
var item = this.items[index]
|
var item = this.items[index]
|
||||||
|
|
||||||
this.item_edition("delete", item).then(() => {
|
try {
|
||||||
self.items.splice(this.items.indexOf(item), 1)
|
|
||||||
|
// Remove the item
|
||||||
|
this.items.splice(index, 1)
|
||||||
|
await this.item_edition("delete", item)
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
} catch (err) {
|
||||||
|
|
||||||
|
this.items.push(item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createType (type) {
|
async createType (type) {
|
||||||
|
|
||||||
var self = this
|
try {
|
||||||
|
|
||||||
this.type_edition("post", type).then(new_type => {
|
const new_type = await this.type_edition("post", type)
|
||||||
|
this.types.push(new_type)
|
||||||
self.types.push(new_type)
|
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Type successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Type successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
} catch (err) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
editType (index, type) {
|
async editType (index, type) {
|
||||||
|
|
||||||
var self = this
|
try {
|
||||||
|
|
||||||
this.type_edition("post", type).then(new_type => {
|
this.types.splice(index, 1)
|
||||||
|
|
||||||
// Remove the 'current' (non edited) item from the list
|
const new_type = await this.type_edition("post", type)
|
||||||
self.types.splice(index, 1)
|
|
||||||
self.types.push(new_type)
|
this.types.push(new_type)
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Type successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Type successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
} catch (err) {
|
||||||
|
|
||||||
|
this.types.push(type)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteType (index) {
|
async deleteType (index) {
|
||||||
|
|
||||||
var self = this
|
|
||||||
var type = this.types[index]
|
var type = this.types[index]
|
||||||
|
|
||||||
this.type_edition("delete", type).then(() => {
|
try {
|
||||||
self.types.splice(this.types.indexOf(type), 1)
|
|
||||||
|
// Remove the type
|
||||||
|
this.types.splice(index, 1)
|
||||||
|
|
||||||
|
await this.type_edition("delete", type)
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Type successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
Swal.fire({title: "{{_('Type successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
})
|
} catch (err) {
|
||||||
|
|
||||||
|
this.types.push(type)
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
<div>PropertyDetail</div>
|
||||||
4
k356/items/templates/components/PropertyDetail/vue.js
Normal file
4
k356/items/templates/components/PropertyDetail/vue.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
PropertyDetail = {
|
||||||
|
template: "#PropertyDetail",
|
||||||
|
router_path: "/PropertyDetail/:id",
|
||||||
|
},
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{% extends "base_components/glist/template.html" %}
|
||||||
8
k356/items/templates/components/PropertyList/vue.js
Normal file
8
k356/items/templates/components/PropertyList/vue.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends "base_components/glist/vue.js" %}
|
||||||
|
{% load main %}
|
||||||
|
|
||||||
|
{% block component %}
|
||||||
|
{% define 'items' 'items' %}
|
||||||
|
{% define 'show_url' 'PropertyDetail' %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
22
k356/items/templates/components/PropertyView/template.html
Normal file
22
k356/items/templates/components/PropertyView/template.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="card mt-4 pt-2 ps-lg-2">
|
||||||
|
<h5 class="card-header">{% trans "Your properties" %}</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<PropertyList
|
||||||
|
:items="properties"
|
||||||
|
:items_headers="properties_headers"
|
||||||
|
:items_relations="{}"
|
||||||
|
:hidden_fields="[]"
|
||||||
|
group_by="type"
|
||||||
|
@deleteItem="deleteItem"
|
||||||
|
@createItem="createItem"
|
||||||
|
@editItem="editItem"
|
||||||
|
></PropertyList>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
183
k356/items/templates/components/PropertyView/vue.js
Normal file
183
k356/items/templates/components/PropertyView/vue.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
PropertyView = {
|
||||||
|
template: "#PropertyView",
|
||||||
|
router_path: "/PropertyView",
|
||||||
|
delimiters: ["[[", "]]"],
|
||||||
|
props: [],
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
properties: [],
|
||||||
|
properties_headers: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function() {
|
||||||
|
this.reload()
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
properties_encrypted_fields: function() {
|
||||||
|
return this.properties_headers.filter(e => e.encrypted).map(e => e.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
properties_relations: function() {
|
||||||
|
return this.properties_headers.filter(e => e.choices != null)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
async reload () {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.$http.get(Urls["items:property.list"]())
|
||||||
|
|
||||||
|
this.properties_headers = response.data.result.properties_headers
|
||||||
|
|
||||||
|
// Decrypt all item the push
|
||||||
|
response.data.result.properties.forEach(async item => {
|
||||||
|
const new_item = await this.decryptObject(this.properties_encrypted_fields, item)
|
||||||
|
|
||||||
|
this.properties.push(new_item)
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
Swal.fire({title: "{{_('Error during loading of properties') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
|
throw err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async decryptObject (encrypted_fields, obj) {
|
||||||
|
// Decrypt all fields and return a new object
|
||||||
|
|
||||||
|
var newobj = {}
|
||||||
|
await Promise.all(Object.keys(obj).map(async field => {
|
||||||
|
if (encrypted_fields.includes(field) && obj[field] != null) {
|
||||||
|
newobj[field] = await this.decrypt(obj[field])
|
||||||
|
} else {
|
||||||
|
newobj[field] = obj[field]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return newobj
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async encryptObject (encrypted_fields, obj) {
|
||||||
|
// Encrypt all fields and return a new object
|
||||||
|
|
||||||
|
var newobj = {}
|
||||||
|
|
||||||
|
await Promise.all(Object.keys(obj).map(async field => {
|
||||||
|
if (encrypted_fields.includes(field) && obj[field] != null) {
|
||||||
|
newobj[field] = await this.encrypt(obj[field])
|
||||||
|
} else {
|
||||||
|
newobj[field] = obj[field]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return newobj
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async object_edit(url_edit, url_create, encrypted_fields, method, obj) {
|
||||||
|
|
||||||
|
let url = null
|
||||||
|
|
||||||
|
if (obj.id == undefined || obj.id == null) {
|
||||||
|
url = Urls[url_create]()
|
||||||
|
} else {
|
||||||
|
url = Urls[url_edit](obj.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const newobj = await this.encryptObject(encrypted_fields, obj)
|
||||||
|
const response = await this.$http[method](url, newobj)
|
||||||
|
|
||||||
|
if (method != "delete") {
|
||||||
|
return await this.decryptObject(encrypted_fields, response.data.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
let msg = "{{_('Error during edition') | escapejs}}"
|
||||||
|
if (method == "delete") {
|
||||||
|
msg = "{{_('Error during deletion') | escapejs}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
Swal.fire({title: msg, icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
|
throw err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
property_edition (method, item) {
|
||||||
|
return this.object_edit("items:property.edit", "items:property.create", this.properties_encrypted_fields, method, item)
|
||||||
|
},
|
||||||
|
|
||||||
|
async createItem (item) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const new_item = await this.property_edition("post", item)
|
||||||
|
this.properties.push(new_item)
|
||||||
|
|
||||||
|
Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async editItem (index, item) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Remove the item
|
||||||
|
this.properties.splice(index, 1)
|
||||||
|
|
||||||
|
const new_item = await this.property_edition("post", item)
|
||||||
|
|
||||||
|
// Add the new item
|
||||||
|
this.properties.push(new_item)
|
||||||
|
|
||||||
|
Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
this.properties.push(item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteItem (index) {
|
||||||
|
|
||||||
|
var item = this.properties[index]
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Remove the item
|
||||||
|
this.properties.splice(index, 1)
|
||||||
|
await this.property_edition("delete", item)
|
||||||
|
|
||||||
|
Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
this.properties.push(item)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,12 +1,27 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from items.views import item_view, type_view
|
|
||||||
|
|
||||||
|
from items.views import item_view, property_view, relation_view, type_view
|
||||||
|
|
||||||
|
|
||||||
app_name = "items"
|
app_name = "items"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", item_view.item_list, name="list"),
|
# Item
|
||||||
|
path("list", item_view.item_list, name="list"),
|
||||||
path("<uuid:id>", item_view.item_edit, name="edit"),
|
path("<uuid:id>", item_view.item_edit, name="edit"),
|
||||||
|
path("<uuid:id>/details", item_view.item_details, name="details"),
|
||||||
path("create", item_view.item_edit, {"id": None}, name="create"),
|
path("create", item_view.item_edit, {"id": None}, name="create"),
|
||||||
|
# Type
|
||||||
path("type/<uuid:id>", type_view.type_edit, name="type.edit"),
|
path("type/<uuid:id>", type_view.type_edit, name="type.edit"),
|
||||||
path("type/create", type_view.type_edit, {"id": None}, name="type.create"),
|
path("type/create", type_view.type_edit, {"id": None}, name="type.create"),
|
||||||
|
# Property
|
||||||
|
path("property/list", property_view.property_list, name="property.list"),
|
||||||
|
path("property/<uuid:id>", property_view.property_edit, name="property.edit"),
|
||||||
|
path("property/create", property_view.property_edit, {"id": None}, name="property.create"),
|
||||||
|
# Relations
|
||||||
|
# path("relation/list", relation_view.relation_list, name="relation.list"),
|
||||||
|
# path("relation/<uuid:id>", relation_view.relation_edit, name="relation.edit"),
|
||||||
|
path("relation/<uuid:id>/details", relation_view.relation_details, name="relation.details"),
|
||||||
|
# path("relation/create", relation_view.relation_edit, {"id": None}, name="relation.create"),
|
||||||
]
|
]
|
||||||
|
|||||||
65
k356/items/views/base.py
Normal file
65
k356/items/views/base.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.db.models.fields.related import RelatedField
|
||||||
|
from django.http.response import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def generic_edit(model, request, id=None):
|
||||||
|
"""Create/edit generic object view."""
|
||||||
|
|
||||||
|
if id:
|
||||||
|
item = model.objects.filter(id=id, author=request.user.setting).first()
|
||||||
|
|
||||||
|
else:
|
||||||
|
item = model(author=request.user.setting)
|
||||||
|
|
||||||
|
if not item:
|
||||||
|
return JsonResponse({}, status=404)
|
||||||
|
|
||||||
|
if request.method == "DELETE":
|
||||||
|
try:
|
||||||
|
item.delete()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"error": "INVALID_DELETE"}, status=401)
|
||||||
|
|
||||||
|
return JsonResponse({})
|
||||||
|
|
||||||
|
if request.method != "POST":
|
||||||
|
return JsonResponse({}, status=405)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"error": "INVALID_DATA"}, status=401)
|
||||||
|
|
||||||
|
for field in item._meta.fields:
|
||||||
|
if field.name in item.Serialization.excluded_fields_edit:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(field, RelatedField):
|
||||||
|
# For now, disregard related field (fk, m2m, 1-1)
|
||||||
|
if isinstance(field, models.ForeignKey):
|
||||||
|
setattr(item, f"{field.name}_id", data[field.name])
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
if field.name not in data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
setattr(item, field.name, data[field.name])
|
||||||
|
|
||||||
|
try:
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"error": "DATA_INVALID"}, status=401)
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"object": item.serialize(),
|
||||||
|
}
|
||||||
|
)
|
||||||
@ -1,11 +1,10 @@
|
|||||||
import json
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
from app.utils.api.api_list import header_for_table
|
from app.utils.api.api_list import header_for_table
|
||||||
from django.contrib.auth.decorators import login_required
|
from items.models import Item, ItemRelation, ItemType, LinkedProperty, Property
|
||||||
from django.db import models
|
from items.views.base import generic_edit
|
||||||
from django.db.models.fields.related import RelatedField
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from items.models import Item, ItemType
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -31,57 +30,27 @@ def item_list(request):
|
|||||||
def item_edit(request, id=None):
|
def item_edit(request, id=None):
|
||||||
"""Create/edit item view."""
|
"""Create/edit item view."""
|
||||||
|
|
||||||
if id:
|
return generic_edit(Item, request, id)
|
||||||
item = Item.objects.filter(id=id, author=request.user.setting).first()
|
|
||||||
|
|
||||||
else:
|
|
||||||
item = Item(author=request.user.setting)
|
@login_required
|
||||||
|
def item_details(request, id):
|
||||||
|
|
||||||
|
item = Item.objects.filter(author=request.user.setting, id=id).first()
|
||||||
|
|
||||||
if not item:
|
if not item:
|
||||||
return JsonResponse({}, status=404)
|
return JsonResponse({}, status=404)
|
||||||
|
|
||||||
if request.method == "DELETE":
|
|
||||||
try:
|
|
||||||
item.delete()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return JsonResponse({"error": "INVALID_DELETE"}, status=401)
|
|
||||||
|
|
||||||
return JsonResponse({})
|
|
||||||
|
|
||||||
if request.method != "POST":
|
|
||||||
return JsonResponse({}, status=405)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = json.loads(request.body)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return JsonResponse({"error": "INVALID_DATA"}, status=401)
|
|
||||||
|
|
||||||
for field in item._meta.fields:
|
|
||||||
if field.name in item.Serialization.excluded_fields_edit:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(field, RelatedField):
|
|
||||||
# For now, disregard related field (fk, m2m, 1-1)
|
|
||||||
if isinstance(field, models.ForeignKey):
|
|
||||||
setattr(item, f"{field.name}_id", data[field.name])
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if field.name not in data:
|
|
||||||
continue
|
|
||||||
|
|
||||||
setattr(item, field.name, data[field.name])
|
|
||||||
|
|
||||||
try:
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return JsonResponse({"error": "DATA_INVALID"}, status=401)
|
|
||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"object": Item.objects.filter(id=item.id).serialize().first(),
|
"object": item.serialize(),
|
||||||
|
"parents": list(item.parents.serialize()),
|
||||||
|
"parents_headers": header_for_table(ItemRelation),
|
||||||
|
"children": list(item.children.serialize()),
|
||||||
|
"children_headers": header_for_table(ItemRelation),
|
||||||
|
"properties": list(item.properties.serialize()),
|
||||||
|
"properties_headers": header_for_table(Property),
|
||||||
|
"linked_properties": list(item.linked_properties.serialize()),
|
||||||
|
"linked_properties_headers": header_for_table(LinkedProperty),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
30
k356/items/views/property_view.py
Normal file
30
k356/items/views/property_view.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
|
from app.utils.api.api_list import header_for_table
|
||||||
|
from items.models import Property
|
||||||
|
from items.views.base import generic_edit
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def property_list(request):
|
||||||
|
|
||||||
|
items = Property.objects.filter(author=request.user.setting)
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"properties": list(items.serialize()),
|
||||||
|
"properties_headers": header_for_table(Property),
|
||||||
|
},
|
||||||
|
"count": items.count(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def property_edit(request, id=None):
|
||||||
|
"""Create/edit property view."""
|
||||||
|
|
||||||
|
return generic_edit(Property, request, id)
|
||||||
29
k356/items/views/relation_view.py
Normal file
29
k356/items/views/relation_view.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import JsonResponse
|
||||||
|
|
||||||
|
|
||||||
|
from app.utils.api.api_list import header_for_table
|
||||||
|
from items.models import Item, ItemRelation, Property, RelationProperty
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def relation_details(request, id):
|
||||||
|
|
||||||
|
relation = ItemRelation.objects.filter(author=request.user.setting, id=id).first()
|
||||||
|
|
||||||
|
if not relation:
|
||||||
|
return JsonResponse({}, status=404)
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"object": relation.serialize(),
|
||||||
|
"parent": relation.parent.serialize(),
|
||||||
|
"parent_headers": header_for_table(Item),
|
||||||
|
"child": relation.child.serialize(),
|
||||||
|
"child_headers": header_for_table(Item),
|
||||||
|
"properties": list(relation.properties.serialize()),
|
||||||
|
"properties_headers": header_for_table(Property),
|
||||||
|
"relation_properties": list(relation.relation_properties.serialize()),
|
||||||
|
"relation_properties_headers": header_for_table(RelationProperty),
|
||||||
|
}
|
||||||
|
)
|
||||||
@ -1,67 +1,12 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db import models
|
|
||||||
from django.db.models.fields.related import RelatedField
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from items.models import ItemType
|
from items.models import ItemType
|
||||||
|
from items.views.base import generic_edit
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def type_edit(request, id=None):
|
def type_edit(request, id=None):
|
||||||
"""Create/edit type view."""
|
"""Create/edit type view."""
|
||||||
|
|
||||||
if id:
|
return generic_edit(ItemType, request, id)
|
||||||
item = ItemType.objects.filter(id=id, author=request.user.setting).first()
|
|
||||||
|
|
||||||
else:
|
|
||||||
item = ItemType(author=request.user.setting)
|
|
||||||
|
|
||||||
if not item:
|
|
||||||
return JsonResponse({}, status=404)
|
|
||||||
|
|
||||||
if request.method == "DELETE":
|
|
||||||
try:
|
|
||||||
item.delete()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return JsonResponse({"error": "INVALID_DELETE"}, status=401)
|
|
||||||
|
|
||||||
return JsonResponse({})
|
|
||||||
|
|
||||||
if request.method != "POST":
|
|
||||||
return JsonResponse({}, status=405)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = json.loads(request.body)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return JsonResponse({"error": "INVALID_DATA"}, status=401)
|
|
||||||
|
|
||||||
for field in item._meta.fields:
|
|
||||||
if field.name in item.Serialization.excluded_fields_edit:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(field, RelatedField):
|
|
||||||
# For now, disregard related field (fk, m2m, 1-1)
|
|
||||||
if isinstance(field, models.ForeignKey):
|
|
||||||
setattr(item, f"{field.name}_id", data[field.name])
|
|
||||||
|
|
||||||
continue
|
|
||||||
|
|
||||||
if field.name not in data:
|
|
||||||
continue
|
|
||||||
|
|
||||||
setattr(item, field.name, data[field.name])
|
|
||||||
|
|
||||||
try:
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
return JsonResponse({"error": "DATA_INVALID"}, status=401)
|
|
||||||
|
|
||||||
return JsonResponse(
|
|
||||||
{
|
|
||||||
"object": ItemType.objects.filter(id=item.id).serialize().first(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
<h5 class="card-header">{% trans "Encryption testing" %}</h5>
|
<h5 class="card-header">{% trans "Encryption testing" %}</h5>
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<p class="card-text">{% trans "The text will be automatically copied to your clipboard." %}</p>
|
<p class="card-text">{% trans "The text will be automatically copied to your clipboard." %}</p>
|
||||||
<textarea class="form-control m-2" cols="50" rows="4" v-model="text" @keyup="encrypt(text)"></textarea>
|
<textarea class="form-control m-2" cols="50" rows="4" v-model="text" @keyup="encrypt_text(text)"></textarea>
|
||||||
|
<p class="card-text text-danger" v-if="encryption_error">[[ encryption_error ]]</p>
|
||||||
<textarea class="form-control m-2" cols="50" rows="4" v-model="encrypted" disabled></textarea>
|
<textarea class="form-control m-2" cols="50" rows="4" v-model="encrypted" disabled></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -14,7 +15,8 @@
|
|||||||
<h5 class="card-header">{% trans "Decryption testing" %}</h5>
|
<h5 class="card-header">{% trans "Decryption testing" %}</h5>
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<p class="card-text">{% trans "The text will be automatically copied to your clipboard." %}</p>
|
<p class="card-text">{% trans "The text will be automatically copied to your clipboard." %}</p>
|
||||||
<textarea class="form-control m-2" cols="50" rows="4" v-model="encrypted_text" @keyup="decrypt(encrypted_text)"></textarea>
|
<textarea class="form-control m-2" cols="50" rows="4" v-model="encrypted_text" @keyup="decrypt_text(encrypted_text)"></textarea>
|
||||||
|
<p class="card-text text-danger" v-if="decryption_error">[[ decryption_error ]]</p>
|
||||||
<textarea class="form-control m-2" cols="50" rows="4" v-model="decrypted" disabled></textarea>
|
<textarea class="form-control m-2" cols="50" rows="4" v-model="decrypted" disabled></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,34 +1,36 @@
|
|||||||
EncryptionTesting = {
|
EncryptionTesting = {
|
||||||
template: "#EncryptionTesting",
|
template: "#EncryptionTesting",
|
||||||
props: ["crypto_key"],
|
router_path: "/EncryptionTesting",
|
||||||
|
delimiters: ["[[", "]]"],
|
||||||
|
props: [],
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
text: '',
|
text: '',
|
||||||
encrypted: '',
|
encrypted: '',
|
||||||
encrypted_text: '',
|
encrypted_text: '',
|
||||||
decrypted: '',
|
decrypted: '',
|
||||||
|
decryption_error: '',
|
||||||
|
encryption_error: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
encrypt: function(data) {
|
async encrypt_text (data) {
|
||||||
var self = this;
|
try {
|
||||||
|
this.encrypted = await this.encrypt(data)
|
||||||
encryptWithKey(this.crypto_key, data).then(e => {
|
this.encryption_error = ""
|
||||||
self.encrypted = e;
|
} catch (err) {
|
||||||
|
this.encryption_error = "{{_('Error while encryption of message.') | escapejs}}"
|
||||||
navigator.clipboard.writeText(self.encrypted);
|
}
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
decrypt: function(data) {
|
async decrypt_text (data) {
|
||||||
var self = this;
|
try {
|
||||||
|
this.decrypted = await this.decrypt(data)
|
||||||
decryptWithKey(this.crypto_key, data).then(e => {
|
this.decryption_error = ""
|
||||||
self.decrypted = e;
|
} catch (err) {
|
||||||
|
this.decryption_error = "{{_('Error while decryption of message.') | escapejs}}"
|
||||||
navigator.clipboard.writeText(self.encrypted);
|
}
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,10 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<div class="card bg-warning mt-4 pt-2 ps-lg-2">
|
<div class="card bg-warning mt-4 pt-2 ps-lg-2">
|
||||||
{% if user_settings.k356_key %}
|
|
||||||
<h5 class="card-header">{% trans "K356 is locked" %}</h5>
|
<h5 class="card-header">{% trans "K356 is locked" %}</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{% trans "K356 needs an unlock..." %}</h5>
|
<h5 class="card-title">{% trans "K356 needs an unlock..." %}</h5>
|
||||||
<p class="card-text">{% trans "Enter your personnal password in order to unlock your K356." %}</p>
|
<p class="card-text">{% trans "Enter your personnal password in order to unlock your K356." %}</p>
|
||||||
<input class="form-control" type="password" v-model="password" @keyup.enter="generate_import_key(password)" autofocus>
|
<input class="form-control" type="password" v-model="password" @keyup.enter="generate_aes_key(password)" autofocus>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
<h5 class="card-header">{% trans "K356 creation" %}</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="card-text">{% trans "Enter your personnal password in order to create your K356." %}</p>
|
|
||||||
<input class="form-control" type="password" v-model="password" @keyup.enter="generate_import_key(password)" autofocus>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,108 +1,25 @@
|
|||||||
const rvalidate = Vue.resource(Urls["users:k356.validate"]);
|
|
||||||
|
|
||||||
Loading = {
|
Loading = {
|
||||||
template: "#Loading",
|
template: "#Loading",
|
||||||
|
router_path: "/",
|
||||||
|
|
||||||
props: ["crypto_key"],
|
props: [],
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
password: '',
|
password: '',
|
||||||
k356_fingerprint: "{{ user_settings.k356_fingerprint }}",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted: function() {},
|
mounted: function() {
|
||||||
|
// FIX: Remove this, this is the key for debugging
|
||||||
|
this.generate_aes_key('asd')
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
generate_import_key: function(password) {
|
async generate_aes_key (password) {
|
||||||
|
const key = await this.deriveKeyFromPassphrase(password, "{{ user_setting.id }}--aes")
|
||||||
if (password != null && password != "") {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
window.crypto.subtle.importKey(
|
|
||||||
"raw",
|
|
||||||
(new TextEncoder()).encode(password),
|
|
||||||
"PBKDF2",
|
|
||||||
true,
|
|
||||||
["deriveKey"]
|
|
||||||
).then(pkey => {
|
|
||||||
|
|
||||||
window.crypto.subtle.deriveKey(
|
|
||||||
{
|
|
||||||
name: "PBKDF2",
|
|
||||||
salt: stringToArrayBuffer("salt"),
|
|
||||||
iterations: 250000,
|
|
||||||
hash: "SHA-256",
|
|
||||||
},
|
|
||||||
pkey,
|
|
||||||
{ name: "AES-GCM", length: 256 },
|
|
||||||
true,
|
|
||||||
["encrypt", "decrypt"]
|
|
||||||
).then(key => {
|
|
||||||
|
|
||||||
self.set_key(key);
|
|
||||||
|
|
||||||
}).catch(function(err) {
|
|
||||||
|
|
||||||
self.set_key(null);
|
|
||||||
|
|
||||||
});
|
|
||||||
}).catch(function(err) {
|
|
||||||
|
|
||||||
self.set_key(null);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
this.set_key(null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
set_key: function(key) {
|
|
||||||
|
|
||||||
if (key) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
encryptWithKey({key: key, uuid: this.crypto_key.uuid}, this.crypto_key.uuid).then(efinger => {
|
|
||||||
|
|
||||||
self.$http.post(Urls["users:k356.validate"](), {fingerprint: efinger}).then(response => {
|
|
||||||
|
|
||||||
if (!response.data.ok) {
|
|
||||||
|
|
||||||
Swal.fire({title: response.data.error, icon: "error", showConfirmButton: false, toast: false});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
self.$emit("update_key", key);
|
|
||||||
|
|
||||||
Swal.fire({title: "Successfully loaded K356!", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
|
|
||||||
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Error while verifying encryption check') | escapejs}}", icon: "error", showConfirmButton: false, toast: false});
|
|
||||||
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
|
|
||||||
Swal.fire({title: "{{_('Error while encrypting verification') | escapejs}}", icon: "error", showConfirmButton: false, toast: false});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == null) {
|
|
||||||
Swal.fire({title: "Error while loading K356!", icon: "error", showConfirmButton: false});
|
|
||||||
|
|
||||||
this.$emit("update_key", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.password = "";
|
|
||||||
|
|
||||||
|
this.$emit("update_key", key)
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
from django import template
|
from django import template
|
||||||
|
from django.utils.html import mark_safe
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def define(context, key, value):
|
def define(context, key, *args):
|
||||||
context.dicts[0][key] = value
|
if len(args) == 1:
|
||||||
|
context.dicts[0][key] = args[0]
|
||||||
|
|
||||||
|
else:
|
||||||
|
context.dicts[0][key] = [mark_safe(arg) for arg in args]
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
19
k356/node_modules/.package-lock.json
generated
vendored
Normal file
19
k356/node_modules/.package-lock.json
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "k356",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"node_modules/@jamescoyle/svg-icon": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jamescoyle/svg-icon/-/svg-icon-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-ZGth9/uM02L7hxKEPmpVYheviVM1R0P6pxQp4M96xmtqTcFVe22UQIGTfyGWrkFMJtztTgMq2K12++Zv5aXZIw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@mdi/js": {
|
||||||
|
"version": "7.4.47",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
|
||||||
|
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
k356/node_modules/@jamescoyle/svg-icon/.github/FUNDING.yml
generated
vendored
Normal file
12
k356/node_modules/@jamescoyle/svg-icon/.github/FUNDING.yml
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [JamesCoyle] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
otechie: # Replace with a single Otechie username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
21
k356/node_modules/@jamescoyle/svg-icon/LICENSE
generated
vendored
Normal file
21
k356/node_modules/@jamescoyle/svg-icon/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 James Coyle
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
42
k356/node_modules/@jamescoyle/svg-icon/README.md
generated
vendored
Normal file
42
k356/node_modules/@jamescoyle/svg-icon/README.md
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# SVG Icon WebComponent
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/@jamescoyle/svg-icon)
|
||||||
|
[](https://www.npmjs.com/package/@jamescoyle/svg-icon)
|
||||||
|
|
||||||
|
A basic webcomponent for rendering a single path SVG icon. This component makes it easy to use SVG path based icon packs such as [MaterialDesignIcons](https://materialdesignicons.com/) and [SimpleIcons](https://simpleicons.org/).
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
1. Install the package from NPM
|
||||||
|
```
|
||||||
|
npm install @jamescoyle/svg-icon
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Import the component into your application
|
||||||
|
```
|
||||||
|
import '@jamescoyle/svg-icon'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Use the icon in your markup
|
||||||
|
```
|
||||||
|
<svg-icon type="mdi" path="M...z"></svg-icon>
|
||||||
|
```
|
||||||
|
|
||||||
|
# Attributes
|
||||||
|
|
||||||
|
| Name | Default | Description |
|
||||||
|
| ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| type | null | This sets the size and viewbox to match the recommended size for the icon pack specified. |
|
||||||
|
| path | null | Required. An SVG path to render as an icon |
|
||||||
|
| size | 24 | The width and height of the SVG element |
|
||||||
|
| viewbox | "0 0 24 24" | The `viewBox` of the SVG element |
|
||||||
|
| flip | null | One of "horizontal", "vertical", or "both". Flips the icon in the specified direction(s). |
|
||||||
|
| rotate | 0deg | Rotates the icon by the specified value. Can be any valid [CSS angle](https://developer.mozilla.org/en-US/docs/Web/CSS/angle) value. |
|
||||||
|
|
||||||
|
# Styling
|
||||||
|
|
||||||
|
By default the icon will inherit the current font color of the container it is placed within. You can easily provide a specific color using an inline style on the element (`style="color: red"`) or can target the tag as normal with CSS rules.
|
||||||
|
|
||||||
|
# Accessibility
|
||||||
|
|
||||||
|
You should make use of aria attributes to improve accessibility for users that use screen reading technology. You can use `aria-labelledby` to create a link between an icon and its label. A descriptive `aria-label` can be used to allow screen readers to announce an icon if there is no visual label to accompany it.
|
||||||
29
k356/node_modules/@jamescoyle/svg-icon/package.json
generated
vendored
Normal file
29
k356/node_modules/@jamescoyle/svg-icon/package.json
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "@jamescoyle/svg-icon",
|
||||||
|
"version": "0.2.1",
|
||||||
|
"description": "Icon webcomponent",
|
||||||
|
"main": "lib/svg-icon.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "tests"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/JamesCoyle/svg-icon.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"webcomponents",
|
||||||
|
"icon",
|
||||||
|
"mdi",
|
||||||
|
"pictogrammers"
|
||||||
|
],
|
||||||
|
"author": "Pictogrammers, James Coyle",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/JamesCoyle/svg-icon/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/JamesCoyle/svg-icon#readme"
|
||||||
|
}
|
||||||
15
k356/node_modules/@jamescoyle/svg-icon/tests/index.html
generated
vendored
Normal file
15
k356/node_modules/@jamescoyle/svg-icon/tests/index.html
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import '../lib/svg-icon.js'
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<svg-icon type="mdi" path="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12M5,13.28L7.45,14.77L6.8,11.96L9,10.08L6.11,9.83L5,7.19L3.87,9.83L1,10.08L3.18,11.96L2.5,14.77L5,13.28Z"></svg-icon>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
k356/node_modules/@mdi/js/LICENSE
generated
vendored
Normal file
20
k356/node_modules/@mdi/js/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Pictogrammers Free License
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
This icon collection is released as free, open source, and GPL friendly by
|
||||||
|
the [Pictogrammers](http://pictogrammers.com/) icon group. You may use it
|
||||||
|
for commercial projects, open source projects, or anything really.
|
||||||
|
|
||||||
|
# Icons: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
Some of the icons are redistributed under the Apache 2.0 license. All other
|
||||||
|
icons are either redistributed under their respective licenses or are
|
||||||
|
distributed under the Apache 2.0 license.
|
||||||
|
|
||||||
|
# Fonts: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
All web and desktop fonts are distributed under the Apache 2.0 license. Web
|
||||||
|
and desktop fonts contain some icons that are redistributed under the Apache
|
||||||
|
2.0 license. All other icons are either redistributed under their respective
|
||||||
|
licenses or are distributed under the Apache 2.0 license.
|
||||||
|
|
||||||
|
# Code: MIT (https://opensource.org/licenses/MIT)
|
||||||
|
The MIT license applies to all non-font and non-icon files.
|
||||||
34
k356/node_modules/@mdi/js/README.md
generated
vendored
Normal file
34
k356/node_modules/@mdi/js/README.md
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
> *Note:* Please use the main [MaterialDesign](https://github.com/Templarian/MaterialDesign/issues) repo to report issues. This repo is for distribution of the JavaScript files only.
|
||||||
|
|
||||||
|
# JavaScript/TypeScript - Material Design Icons
|
||||||
|
|
||||||
|
JavaScript and TypeScript distribution for the [Material Design Icons](https://materialdesignicons.com). This module contains all the path data for all icons.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install @mdi/js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { mdiAccount } from '@mdi/js'
|
||||||
|
|
||||||
|
console.log(mdiAccount); // "M...Z"
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: [WebPack](https://webpack.js.org) 4 or [Rollup](https://rollupjs.org) will tree shake unused icons.
|
||||||
|
|
||||||
|
## Related Packages
|
||||||
|
|
||||||
|
[NPM @MDI Organization](https://npmjs.com/org/mdi)
|
||||||
|
|
||||||
|
- React: [MaterialDesign-React](https://github.com/Templarian/MaterialDesign-React)
|
||||||
|
- SVG: [MaterialDesign-SVG](https://github.com/Templarian/MaterialDesign-SVG)
|
||||||
|
- Webfont: [MaterialDesign-Webfont](https://github.com/Templarian/MaterialDesign-Webfont)
|
||||||
|
- Font-Build: [MaterialDesign-Font-Build](https://github.com/Templarian/MaterialDesign-Font-Build)
|
||||||
|
- Desktop Font: [MaterialDesign-Font](https://github.com/Templarian/MaterialDesign-Font)
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
- [MaterialDesignIcons.com](https://materialdesignicons.com)
|
||||||
|
- https://github.com/Templarian/MaterialDesign
|
||||||
18
k356/node_modules/@mdi/js/build.js
generated
vendored
Normal file
18
k356/node_modules/@mdi/js/build.js
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const util = require('@mdi/util');
|
||||||
|
|
||||||
|
const meta = util.getMeta(true);
|
||||||
|
|
||||||
|
const find = /(\-\w)/g;
|
||||||
|
const convert = function(matches){
|
||||||
|
return matches[1].toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const lines = meta.map(icon => {
|
||||||
|
let name = icon.name.replace(find, convert);
|
||||||
|
name = `${name[0].toUpperCase()}${name.slice(1)}`;
|
||||||
|
return `export const mdi${name}: string = "${icon.path}";`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = `// Material Design Icons v${util.getVersion()}\n${lines.join('\n')}`;
|
||||||
|
|
||||||
|
util.write("mdi.ts", output);
|
||||||
7447
k356/node_modules/@mdi/js/commonjs/mdi.d.ts
generated
vendored
Normal file
7447
k356/node_modules/@mdi/js/commonjs/mdi.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7599
k356/node_modules/@mdi/js/commonjs/mdi.js
generated
vendored
Normal file
7599
k356/node_modules/@mdi/js/commonjs/mdi.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7447
k356/node_modules/@mdi/js/mdi.d.ts
generated
vendored
Normal file
7447
k356/node_modules/@mdi/js/mdi.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7448
k356/node_modules/@mdi/js/mdi.js
generated
vendored
Normal file
7448
k356/node_modules/@mdi/js/mdi.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
37
k356/node_modules/@mdi/js/package.json
generated
vendored
Normal file
37
k356/node_modules/@mdi/js/package.json
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "@mdi/js",
|
||||||
|
"version": "7.4.47",
|
||||||
|
"description": "Dist for Material Design Icons for JS/TypeScript",
|
||||||
|
"main": "commonjs/mdi.js",
|
||||||
|
"module": "mdi.js",
|
||||||
|
"types": "mdi.d.ts",
|
||||||
|
"sideEffects": false,
|
||||||
|
"scripts": {
|
||||||
|
"build": "npm update && npm install && npm run buildjs && npm run es5 && npm run commonjs",
|
||||||
|
"buildjs": "node build.js",
|
||||||
|
"es5": "tsc -d mdi.ts --target es5 --module es2015",
|
||||||
|
"commonjs": "tsc -d mdi.ts --outDir commonjs",
|
||||||
|
"umd": "tsc -d mdi.ts --module UMD --outDir umd",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Templarian/MaterialDesign-JS.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Material",
|
||||||
|
"Design",
|
||||||
|
"Icons",
|
||||||
|
"mdi"
|
||||||
|
],
|
||||||
|
"author": "Austin Andrews",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Templarian/MaterialDesign-JS/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Templarian/MaterialDesign-JS#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@mdi/svg": "^7.4.47",
|
||||||
|
"@mdi/util": "^0.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
k356/package-lock.json
generated
Normal file
25
k356/package-lock.json
generated
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "k356",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@jamescoyle/svg-icon": "^0.2.1",
|
||||||
|
"@mdi/js": "^7.4.47"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jamescoyle/svg-icon": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jamescoyle/svg-icon/-/svg-icon-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-ZGth9/uM02L7hxKEPmpVYheviVM1R0P6pxQp4M96xmtqTcFVe22UQIGTfyGWrkFMJtztTgMq2K12++Zv5aXZIw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@mdi/js": {
|
||||||
|
"version": "7.4.47",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
|
||||||
|
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
k356/package.json
Normal file
6
k356/package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@jamescoyle/svg-icon": "^0.2.1",
|
||||||
|
"@mdi/js": "^7.4.47"
|
||||||
|
}
|
||||||
|
}
|
||||||
391
k356/poetry.lock
generated
Normal file
391
k356/poetry.lock
generated
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asgiref"
|
||||||
|
version = "3.8.1"
|
||||||
|
description = "ASGI specs, helper code, and adapters"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
|
||||||
|
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "24.8.0"
|
||||||
|
description = "The uncompromising code formatter."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||||
|
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||||
|
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
|
||||||
|
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
|
||||||
|
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
|
||||||
|
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
|
||||||
|
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
|
||||||
|
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
|
||||||
|
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
|
||||||
|
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
|
||||||
|
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
|
||||||
|
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
|
||||||
|
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
|
||||||
|
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
|
||||||
|
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
|
||||||
|
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
|
||||||
|
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
|
||||||
|
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
|
||||||
|
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
|
||||||
|
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
|
||||||
|
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
|
||||||
|
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=8.0.0"
|
||||||
|
mypy-extensions = ">=0.4.3"
|
||||||
|
packaging = ">=22.0"
|
||||||
|
pathspec = ">=0.9.0"
|
||||||
|
platformdirs = ">=2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colorama = ["colorama (>=0.4.3)"]
|
||||||
|
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
||||||
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfgv"
|
||||||
|
version = "3.4.0"
|
||||||
|
description = "Validate configuration and produce human readable error messages."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
|
||||||
|
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.1.7"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||||
|
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "distlib"
|
||||||
|
version = "0.3.8"
|
||||||
|
description = "Distribution utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
|
||||||
|
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django"
|
||||||
|
version = "5.1.1"
|
||||||
|
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
files = [
|
||||||
|
{file = "Django-5.1.1-py3-none-any.whl", hash = "sha256:71603f27dac22a6533fb38d83072eea9ddb4017fead6f67f2562a40402d61c3f"},
|
||||||
|
{file = "Django-5.1.1.tar.gz", hash = "sha256:021ffb7fdab3d2d388bc8c7c2434eb9c1f6f4d09e6119010bbb1694dda286bc2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
asgiref = ">=3.8.1,<4"
|
||||||
|
sqlparse = ">=0.3.1"
|
||||||
|
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
|
bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-bower"
|
||||||
|
version = "5.2.1-rc2"
|
||||||
|
description = "Integrate django with bower"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = []
|
||||||
|
develop = false
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
django = "*"
|
||||||
|
six = "*"
|
||||||
|
|
||||||
|
[package.source]
|
||||||
|
type = "git"
|
||||||
|
url = "https://github.com/ArcaniteSolutions/django-bower.git"
|
||||||
|
reference = "HEAD"
|
||||||
|
resolved_reference = "3f85ca87da022f73a2f10356659d8178a71f1752"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filelock"
|
||||||
|
version = "3.16.1"
|
||||||
|
description = "A platform independent file lock."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
|
||||||
|
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
|
||||||
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
|
||||||
|
typing = ["typing-extensions (>=4.12.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "identify"
|
||||||
|
version = "2.6.1"
|
||||||
|
description = "File identification library for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"},
|
||||||
|
{file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
license = ["ukkonen"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "isort"
|
||||||
|
version = "5.13.2"
|
||||||
|
description = "A Python utility / library to sort Python imports."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.0"
|
||||||
|
files = [
|
||||||
|
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||||
|
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colors = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Type system extensions for programs checked with the mypy type checker."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||||
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nodeenv"
|
||||||
|
version = "1.9.1"
|
||||||
|
description = "Node.js virtual environment builder"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
files = [
|
||||||
|
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
|
||||||
|
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "24.1"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||||
|
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathspec"
|
||||||
|
version = "0.12.1"
|
||||||
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||||
|
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.3.6"
|
||||||
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||||
|
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
|
||||||
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
|
||||||
|
type = ["mypy (>=1.11.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pre-commit"
|
||||||
|
version = "3.8.0"
|
||||||
|
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"},
|
||||||
|
{file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cfgv = ">=2.0.0"
|
||||||
|
identify = ">=1.0.0"
|
||||||
|
nodeenv = ">=0.11.1"
|
||||||
|
pyyaml = ">=5.1"
|
||||||
|
virtualenv = ">=20.10.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0.2"
|
||||||
|
description = "YAML parser and emitter for Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
|
||||||
|
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
|
||||||
|
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
|
||||||
|
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
|
||||||
|
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
|
||||||
|
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
|
||||||
|
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
files = [
|
||||||
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlparse"
|
||||||
|
version = "0.5.1"
|
||||||
|
description = "A non-validating SQL parser."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
|
||||||
|
{file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["build", "hatch"]
|
||||||
|
doc = ["sphinx"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2024.2"
|
||||||
|
description = "Provider of IANA time zone data"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2"
|
||||||
|
files = [
|
||||||
|
{file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
|
||||||
|
{file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtualenv"
|
||||||
|
version = "20.26.6"
|
||||||
|
description = "Virtual Python Environment builder"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"},
|
||||||
|
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
distlib = ">=0.3.7,<1"
|
||||||
|
filelock = ">=3.12.2,<4"
|
||||||
|
platformdirs = ">=3.9.1,<5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||||
|
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.0"
|
||||||
|
python-versions = "^3.11"
|
||||||
|
content-hash = "60769459dbf139704b035dd2ea3e1de8dc0fe36bc730e452db7676ea9d5344b3"
|
||||||
109
k356/pyproject.toml
Normal file
109
k356/pyproject.toml
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
[tool.black]
|
||||||
|
line-length = 150
|
||||||
|
extend-exclude = ""
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
# When imports are broken into multi-line, use the "Vertical Hanging Indent" layout.
|
||||||
|
multi_line_output = 3
|
||||||
|
|
||||||
|
# Always add a trailing comma to import lists (default: False).
|
||||||
|
include_trailing_comma = true
|
||||||
|
|
||||||
|
# Always put imports lists into vertical mode (0 = none allowed on first line)
|
||||||
|
force_grid_wrap = 0
|
||||||
|
|
||||||
|
# When multi-lining imports, use parentheses for line-continuation instead of default \.
|
||||||
|
use_parentheses = true
|
||||||
|
|
||||||
|
# Max import line length.
|
||||||
|
line_length = 150
|
||||||
|
|
||||||
|
# Put the django package into its own named section so we can rearrange it.
|
||||||
|
known_django = "django"
|
||||||
|
|
||||||
|
# All apps from this project
|
||||||
|
known_first_party = [
|
||||||
|
"main",
|
||||||
|
"items",
|
||||||
|
"users",
|
||||||
|
]
|
||||||
|
|
||||||
|
# projects have Django as the first imports.
|
||||||
|
sections = ["FUTURE","DJANGO","FIRSTPARTY","LOCALFOLDER","STDLIB","THIRDPARTY"]
|
||||||
|
|
||||||
|
# projects treat STDLIB the same as THIRDPARTY, so remove the blank line between them.
|
||||||
|
# projects want FIRSTPARTY and LOCALFOLDER in the same section.
|
||||||
|
no_lines_before = ["THIRDPARTY", "LOCALFOLDER"]
|
||||||
|
|
||||||
|
# Regardless of what follows the imports, force 2 blank lines after the import list
|
||||||
|
lines_after_imports = 2
|
||||||
|
|
||||||
|
# Insert 2 blank lines between each section
|
||||||
|
lines_between_sections = 2
|
||||||
|
|
||||||
|
# Alphabetical sort in sections (inside a line or in ())
|
||||||
|
force_alphabetical_sort_within_sections = true
|
||||||
|
|
||||||
|
# Sort by lexicographical
|
||||||
|
lexicographical = true
|
||||||
|
|
||||||
|
# Put all from before import
|
||||||
|
from_first = true
|
||||||
|
|
||||||
|
ensure_newline_before_comments = true
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
name = "k356"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = ""
|
||||||
|
authors = []
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.11"
|
||||||
|
Django = "^5.0"
|
||||||
|
django-bower = {git = "https://github.com/ArcaniteSolutions/django-bower.git"}
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
black = "^24.4.0"
|
||||||
|
isort = "^5.10.1"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
pre-commit = "^3.8.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
include = "*/k356/*"
|
||||||
|
omit = [
|
||||||
|
"*env*",
|
||||||
|
"*migrations*",
|
||||||
|
"*test*",
|
||||||
|
"admin.py",
|
||||||
|
"*settings*",
|
||||||
|
"*wsgi.py",
|
||||||
|
"*manage.py"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
# Regexes for lines to exclude from consideration
|
||||||
|
exclude_lines = [
|
||||||
|
# Have to re-enable the standard pragma
|
||||||
|
"pragma: no cover",
|
||||||
|
|
||||||
|
# Don't complain about missing debug-only code:
|
||||||
|
"def __repr__",
|
||||||
|
"if self.debug",
|
||||||
|
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
|
"raise AssertionError",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
|
||||||
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
"if 0:",
|
||||||
|
"if __name__ == .__main__.:"
|
||||||
|
]
|
||||||
|
|
||||||
@ -8,25 +8,26 @@
|
|||||||
|
|
||||||
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}">
|
<link rel="shortcut icon" href="{% static 'img/favicon.png' %}">
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
<script src="{% static "vue/index.js" %}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
|
<script src="{% static "sweetalert2/index" %}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="{% static "vue-resource/index" %}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.5.3"></script>
|
<script src="{% static "js.cookie.min/index.js" %}"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
|
<script src="{% static "vue-router/index.js" %}"></script>
|
||||||
<script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>
|
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
<link href="{% static "bootstrap.min/index.css" %}" rel="stylesheet">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
<script src="{% static "bootstrap.bundle.min/index.js" %}"></script>
|
||||||
|
<link href="{% static "bootswatch.min.css/index.css" %}" rel="stylesheet">
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/lux/bootstrap.min.css " rel="stylesheet">
|
|
||||||
<script src="{% url 'reverse_js' %}" type="text/javascript"></script>
|
<script src="{% url 'reverse_js' %}" type="text/javascript"></script>
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
|
<link href="{% static "vuetify.min/index.css" %}" rel="stylesheet">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
|
<script src="{% static "vuetify/index.js" %}"></script>
|
||||||
|
|
||||||
|
<script src="{% static "vuex/index.js" %}"></script>
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet">
|
||||||
|
|
||||||
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
|
<link href="{% static "jetbrains-mono/index" %}">
|
||||||
<style>
|
<style>
|
||||||
.font {
|
.font {
|
||||||
font-family: 'JetBrains Mono', sans-serif;
|
font-family: 'JetBrains Mono', sans-serif;
|
||||||
@ -49,7 +50,7 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarColor01">
|
<div class="collapse navbar-collapse" id="navbarColor01">
|
||||||
<ul class="navbar-nav me-auto">
|
<ul class="navbar-nav me-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<router-link href="#" to="{% url 'main:home' %}" class="nav-link active" to="{% url 'main:home' %}">
|
<router-link class="nav-link active" to="{% url 'main:home' %}">
|
||||||
{% trans "K356" %}
|
{% trans "K356" %}
|
||||||
<span class="visually-hidden"></span>
|
<span class="visually-hidden"></span>
|
||||||
</router-link>
|
</router-link>
|
||||||
@ -58,11 +59,12 @@
|
|||||||
<router-link class="nav-link" to="/ItemView">{% trans "Items" %}</router-link>
|
<router-link class="nav-link" to="/ItemView">{% trans "Items" %}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#">{% trans "Properties" %}</a>
|
<router-link class="nav-link" to="/PropertyView">{% trans "Properties" %}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<router-link class="nav-link" to="/EncryptionTesting">{% trans "Encryption" %}</router-link>
|
<router-link class="nav-link" to="/EncryptionTesting">{% trans "Encryption" %}</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{% trans "More" %}</a>
|
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{% trans "More" %}</a>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
@ -73,6 +75,7 @@
|
|||||||
<a class="dropdown-item" href="/admin/">{% trans "Admin" %}</a>
|
<a class="dropdown-item" href="/admin/">{% trans "Admin" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<form class="d-flex">
|
<form class="d-flex">
|
||||||
<input class="form-control me-sm-2" type="search" placeholder="Search">
|
<input class="form-control me-sm-2" type="search" placeholder="Search">
|
||||||
@ -83,9 +86,9 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="app" class="container">
|
<div id="app" class="container">
|
||||||
<Loading :crypto_key="key" @update_key="update_key" v-if="locked"></Loading>
|
<Loading @update_key="update_key" v-if="locked"></Loading>
|
||||||
<template v-if="!locked">
|
<template v-if="!locked">
|
||||||
<router-view :crypto_key="key"></router-view>
|
<router-view></router-view>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -103,56 +106,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
Vue.use(VueRouter);
|
{% include "vue/index.js" %}
|
||||||
|
|
||||||
Vue.config.delimiters = ["[[", "]]"];
|
|
||||||
|
|
||||||
{% for name, path in components.items %}
|
|
||||||
{% include path %}
|
|
||||||
Vue.component("{{ name }}", {{ name }});
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{ path: '/', component: null },
|
|
||||||
{% for name, path in components.items %}
|
|
||||||
{
|
|
||||||
path: "/{{ name }}",
|
|
||||||
component: {{ name }},
|
|
||||||
},
|
|
||||||
{% endfor %}
|
|
||||||
];
|
|
||||||
|
|
||||||
const router = new VueRouter({routes});
|
|
||||||
|
|
||||||
const approuter = new Vue({
|
|
||||||
router,
|
|
||||||
vuetify: new Vuetify(),
|
|
||||||
el: "#main",
|
|
||||||
data: {
|
|
||||||
key: {
|
|
||||||
key: null,
|
|
||||||
uuid: "{{ user_settings.id }}",
|
|
||||||
},
|
|
||||||
locked: true,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
update_key: function(key) {
|
|
||||||
this.key.key = key;
|
|
||||||
this.locked = key == null;
|
|
||||||
},
|
|
||||||
|
|
||||||
lock_me: function() {
|
|
||||||
this.locked = true;
|
|
||||||
this.key.key = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
// Prevent from routing if key is not present.
|
|
||||||
next(approuter.key.key != null);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -164,16 +118,5 @@
|
|||||||
refresh_csrftoken();
|
refresh_csrftoken();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import { useDate } from './vuetify.js'
|
|
||||||
|
|
||||||
const date = useDate()
|
|
||||||
|
|
||||||
const formatted = date.format('2010-04-13', 'fullDateWithWeekday')
|
|
||||||
|
|
||||||
console.log(formatted) // Tuesday, April 13, 2010
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block component %}
|
{% block component %}
|
||||||
<v-data-table
|
<v-data-table
|
||||||
:headers="items_headers"
|
:headers="citems_headers"
|
||||||
:items="items"
|
:items="items"
|
||||||
:items-per-page="50"
|
:items-per-page="50"
|
||||||
:search="search"
|
:search="search"
|
||||||
@ -34,6 +34,17 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<template v-for="field in editable_fields">
|
<template v-for="field in editable_fields">
|
||||||
<template v-if="field.field_widget == 'v-select'">
|
<template v-if="field.field_widget == 'v-select'">
|
||||||
|
<template v-if="field.choices">
|
||||||
|
<v-select
|
||||||
|
v-model="editedItem[field.value]"
|
||||||
|
:items="field.choices"
|
||||||
|
:label="field.text"
|
||||||
|
item-text="text"
|
||||||
|
item-value="value"
|
||||||
|
persistent-hint>
|
||||||
|
</v-select>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<v-select
|
<v-select
|
||||||
v-model="editedItem[field.value]"
|
v-model="editedItem[field.value]"
|
||||||
:items="items_relations[field.value]"
|
:items="items_relations[field.value]"
|
||||||
@ -46,6 +57,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-select>
|
</v-select>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<component :is="field.field_widget" v-model="editedItem[field.value]" :label="field.text"></component>
|
<component :is="field.field_widget" v-model="editedItem[field.value]" :label="field.text"></component>
|
||||||
</template>
|
</template>
|
||||||
@ -91,7 +103,23 @@
|
|||||||
<template v-slot:item.id="{ item }">
|
<template v-slot:item.id="{ item }">
|
||||||
[[ item.id.slice(0, 8) ]]...
|
[[ item.id.slice(0, 8) ]]...
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:item.description="{ item }">
|
||||||
|
<template v-if="item.description && item.description.length > 15">
|
||||||
|
[[ item.description.slice(0, 15) ]]...
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
[[ item.description ]]
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.last_modified_at="{ item }">
|
||||||
|
[[ formatDate(item.last_modified_at) ]]
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.created_at="{ item }">
|
||||||
|
[[ formatDate(item.created_at) ]]
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-slot:item.actions="{ item }">
|
<template v-slot:item.actions="{ item }">
|
||||||
|
<v-icon v-if="show_url" small class="mr-2" @click="showItem(item)">mdi-eye</v-icon>
|
||||||
<v-icon small class="mr-2" @click="editItem(item)">mdi-pencil</v-icon>
|
<v-icon small class="mr-2" @click="editItem(item)">mdi-pencil</v-icon>
|
||||||
<v-icon small @click="deleteItem(item)">mdi-delete</v-icon>
|
<v-icon small @click="deleteItem(item)">mdi-delete</v-icon>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
{% block component %}
|
{% block component %}
|
||||||
{{ name }} = {
|
{{ name }} = {
|
||||||
template: "#{{ name }}",
|
template: "#{{ name }}",
|
||||||
|
router_path: "/{{ name }}",
|
||||||
delimiters: ["[[", "]]"],
|
delimiters: ["[[", "]]"],
|
||||||
props: ["crypto_key", "items", "items_headers", "items_relations", "group_by"],
|
props: ["crypto_key", "items", "items_headers", "items_relations", "group_by", "hidden_fields"],
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
@ -12,6 +13,8 @@
|
|||||||
defaultItem: {},
|
defaultItem: {},
|
||||||
editedItem: {},
|
editedItem: {},
|
||||||
search: null,
|
search: null,
|
||||||
|
show_url: "{{ show_url|default:'' }}",
|
||||||
|
default_hidden_fields: [{% for field in default_hidden_fields %}"{{ field }}",{% endfor %}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -19,6 +22,10 @@
|
|||||||
editable_fields: function() {
|
editable_fields: function() {
|
||||||
return this.items_headers.filter(e => e.editable)
|
return this.items_headers.filter(e => e.editable)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
citems_headers: function() {
|
||||||
|
return this.items_headers.filter(e => !this.hidden_fields.includes(e.value) && !this.default_hidden_fields.includes(e.value))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@ -32,6 +39,14 @@
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
formatDate (date) {
|
||||||
|
return formatDate(date)
|
||||||
|
},
|
||||||
|
|
||||||
|
showItem (item) {
|
||||||
|
this.$router.replace({ name: this.show_url, params: { id: item.id }})
|
||||||
|
},
|
||||||
|
|
||||||
editItem (item) {
|
editItem (item) {
|
||||||
this.editedIndex = this.items.indexOf(item)
|
this.editedIndex = this.items.indexOf(item)
|
||||||
this.editedItem = Object.assign({}, item)
|
this.editedItem = Object.assign({}, item)
|
||||||
@ -45,9 +60,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
deleteItemConfirm () {
|
deleteItemConfirm () {
|
||||||
var self = this
|
|
||||||
var item = this.items[this.editedIndex]
|
|
||||||
|
|
||||||
this.$emit("deleteItem", this.editedIndex)
|
this.$emit("deleteItem", this.editedIndex)
|
||||||
this.closeDelete()
|
this.closeDelete()
|
||||||
|
|
||||||
@ -74,7 +86,6 @@
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
console.log('createItem emit', this.editedItem)
|
|
||||||
this.$emit("createItem", this.editedItem)
|
this.$emit("createItem", this.editedItem)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,51 @@
|
|||||||
|
async function init() {
|
||||||
|
|
||||||
|
const operations = crypto.subtle
|
||||||
|
|
||||||
|
const KEY_LEN = 256
|
||||||
|
|
||||||
|
const ENCRYPTION_ALGO = "AES-GCM"
|
||||||
|
const ENCRYPTION_USAGES = ["encrypt", "decrypt"]
|
||||||
|
const ENCRYPTION_PARAMS = { name: ENCRYPTION_ALGO, length: KEY_LEN }
|
||||||
|
|
||||||
|
const WRAPPING_ALGO = "AES-KW"
|
||||||
|
const WRAPPING_USAGES = ["wrapKey", "unwrapKey"]
|
||||||
|
|
||||||
|
const DERIVATION_ALGO = "ECDH"
|
||||||
|
|
||||||
|
const { publicKey, privateKey } = await operations.generateKey(
|
||||||
|
{
|
||||||
|
name: DERIVATION_ALGO,
|
||||||
|
namedCurve: "P-384",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
["derivekey"]
|
||||||
|
)
|
||||||
|
|
||||||
|
const encryptionKey = await operations.deriveKey(
|
||||||
|
{
|
||||||
|
name: DERIVATION_ALGO,
|
||||||
|
public: publicKey,
|
||||||
|
},
|
||||||
|
privateKey,
|
||||||
|
ENCRYPTION_PARAMS,
|
||||||
|
false,
|
||||||
|
["encrypt"]
|
||||||
|
)
|
||||||
|
|
||||||
|
const decryptionKey = await operations.deriveKey(
|
||||||
|
{ name: DERIVATION_ALGO, public: publicKey },
|
||||||
|
privateKey,
|
||||||
|
ENCRYPTION_PARAMS,
|
||||||
|
false,
|
||||||
|
['decrypt']
|
||||||
|
)
|
||||||
|
|
||||||
|
return { encryptionKey, decryptionKey }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function stringToArrayBuffer(str) {
|
function stringToArrayBuffer(str) {
|
||||||
var buf = new ArrayBuffer(str.length);
|
var buf = new ArrayBuffer(str.length);
|
||||||
var bufView = new Uint8Array(buf);
|
var bufView = new Uint8Array(buf);
|
||||||
@ -18,6 +66,24 @@ function arrayBufferToString(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function aEncryptWithKey(key, data) {
|
||||||
|
// Encrypt data with key. Return a Promise
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
window.crypto.subtle.encrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv: stringToArrayBuffer(key.uuid),
|
||||||
|
},
|
||||||
|
key.key,
|
||||||
|
stringToArrayBuffer(data)
|
||||||
|
).then(text => {
|
||||||
|
|
||||||
|
resolve(btoa(arrayBufferToString(text)));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function encryptWithKey(key, data) {
|
function encryptWithKey(key, data) {
|
||||||
// Encrypt data with key. Return a Promise
|
// Encrypt data with key. Return a Promise
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|||||||
117
k356/templates/vue/index.js
Normal file
117
k356/templates/vue/index.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
|
||||||
|
{% include "vue/plugins.js" %}
|
||||||
|
|
||||||
|
Vue.use(VueRouter)
|
||||||
|
Vue.use(Vuex)
|
||||||
|
Vue.use(EncryptionPlugin)
|
||||||
|
|
||||||
|
Vue.config.delimiters = ["[[", "]]"];
|
||||||
|
|
||||||
|
{% for name, path in components.items %}
|
||||||
|
{% include path %}
|
||||||
|
Vue.component("{{ name }}", {{ name }})
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ path: '/', component: null },
|
||||||
|
{% for name, path in components.items %}
|
||||||
|
{
|
||||||
|
path: {{ name }}.router_path,
|
||||||
|
name: "{{ name }}",
|
||||||
|
component: {{ name }},
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const encryptionStore = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
aes_key: null,
|
||||||
|
keyPair: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
mutations: {
|
||||||
|
update_aes_key (state, key) {
|
||||||
|
state.aes_key = key
|
||||||
|
},
|
||||||
|
|
||||||
|
update_keyPair (state, keyPair) {
|
||||||
|
state.keyPair = keyPair
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = new VueRouter({routes})
|
||||||
|
const approuter = new Vue({
|
||||||
|
router,
|
||||||
|
vuetify: new Vuetify(),
|
||||||
|
store: encryptionStore,
|
||||||
|
el: "#main",
|
||||||
|
data: {
|
||||||
|
uuid: "{{ user_settings.id }}",
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
locked: function() {
|
||||||
|
return this.$store.state.aes_key == null || this.$store.state.keyPair?.privateKey == null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function() {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async load_keys (aes_key) {
|
||||||
|
const response = await this.$http.get(Urls["users:keys"]())
|
||||||
|
|
||||||
|
const iv_private = `${this.uuid}--private`
|
||||||
|
const iv_public = `${this.uuid}--public`
|
||||||
|
|
||||||
|
if (response.data.privateKey != null) {
|
||||||
|
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: await this.unwrapKey(aes_key, response.data.privateKey, iv_private, ["decrypt"]),
|
||||||
|
publicKey: await this.unwrapKey(aes_key, response.data.publicKey, iv_public, ["encrypt"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.commit('update_keyPair', keyPair)
|
||||||
|
|
||||||
|
Swal.fire({title: "Successfully loaded K356!", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const keyPair = await this.generateKeyPair()
|
||||||
|
|
||||||
|
await this.$http.post(
|
||||||
|
Urls["users:keys"](),
|
||||||
|
{
|
||||||
|
privateKey: await this.wrapKey(this.keyPair.privateKey, aes_key, iv_private),
|
||||||
|
publicKey: await this.wrapKey(this.keyPair.publicKey, aes_key, iv_public),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.$store.commit('update_keyPair', keyPair)
|
||||||
|
|
||||||
|
Swal.fire({title: "Successfully created K356!", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
update_key: function(key) {
|
||||||
|
this.$store.commit('update_aes_key', key)
|
||||||
|
|
||||||
|
this.load_keys(key)
|
||||||
|
},
|
||||||
|
|
||||||
|
lock_me: function() {
|
||||||
|
this.$store.commit('update_keyPair', null)
|
||||||
|
this.$store.commit('update_aes_key', null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
// Prevent from routing if key is not present.
|
||||||
|
next(!approuter.locked)
|
||||||
|
})
|
||||||
160
k356/templates/vue/plugins.js
Normal file
160
k356/templates/vue/plugins.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
const operations = crypto.subtle
|
||||||
|
const pbkdf2_iterations = 250000
|
||||||
|
|
||||||
|
|
||||||
|
function stringToArrayBuffer(str) {
|
||||||
|
var buf = new ArrayBuffer(str.length);
|
||||||
|
var bufView = new Uint8Array(buf);
|
||||||
|
for (var i = 0, strLen = str.length; i < strLen; i++) {
|
||||||
|
bufView[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function arrayBufferToString(str) {
|
||||||
|
var byteArray = new Uint8Array(str);
|
||||||
|
var byteString = '';
|
||||||
|
for (var i = 0; i < byteArray.byteLength; i++) {
|
||||||
|
byteString += String.fromCodePoint(byteArray[i]);
|
||||||
|
}
|
||||||
|
return byteString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function formatDate (date) {
|
||||||
|
const d = new Date(date)
|
||||||
|
const hours = d.getHours().toString().padStart(2, '0')
|
||||||
|
const minutes = d.getMinutes().toString().padStart(2, '0')
|
||||||
|
const formattedTime = `${hours}:${minutes}`
|
||||||
|
return `${d.toLocaleDateString()} ${formattedTime}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const EncryptionPlugin = {
|
||||||
|
install(Vue, options) {
|
||||||
|
Vue.prototype.deriveKeyFromPassphrase = async (passphrase, salt) => {
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const keyFromPassword = await operations.importKey(
|
||||||
|
"raw",
|
||||||
|
encoder.encode(passphrase),
|
||||||
|
"PBKDF2",
|
||||||
|
false,
|
||||||
|
["deriveKey"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return await operations.deriveKey(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: stringToArrayBuffer(salt),
|
||||||
|
iterations: pbkdf2_iterations,
|
||||||
|
hash: "SHA-256",
|
||||||
|
},
|
||||||
|
keyFromPassword,
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
length: 256
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["wrapKey", "unwrapKey"]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.generateKeyPair = async () => {
|
||||||
|
return await operations.generateKey(
|
||||||
|
{
|
||||||
|
name: "RSA-OAEP",
|
||||||
|
modulusLength: 4096,
|
||||||
|
publicExponent: new Uint8Array([1, 0, 1]),
|
||||||
|
hash: "SHA-256"
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.wrapKey = async (key, wrappingKey, iv) => {
|
||||||
|
|
||||||
|
return btoa(arrayBufferToString(await operations.wrapKey(
|
||||||
|
"jwk",
|
||||||
|
key,
|
||||||
|
wrappingKey,
|
||||||
|
{name: "AES-GCM", iv: stringToArrayBuffer(iv)}
|
||||||
|
)))
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.unwrapKey = async (unwrappingKey, armored_jwk_data, iv, args) => {
|
||||||
|
|
||||||
|
return await operations.unwrapKey(
|
||||||
|
"jwk",
|
||||||
|
stringToArrayBuffer(atob(armored_jwk_data)),
|
||||||
|
unwrappingKey,
|
||||||
|
{name: "AES-GCM", iv: stringToArrayBuffer(iv)},
|
||||||
|
{
|
||||||
|
name: "RSA-OAEP",
|
||||||
|
hash: "SHA-256",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.encrypt = async function(data) {
|
||||||
|
|
||||||
|
return btoa(arrayBufferToString(await operations.encrypt(
|
||||||
|
{ name: "RSA-OAEP" },
|
||||||
|
this.$store.state.keyPair.publicKey,
|
||||||
|
stringToArrayBuffer(data),
|
||||||
|
)))
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.decrypt = async function(armored_data) {
|
||||||
|
|
||||||
|
return arrayBufferToString(await operations.decrypt(
|
||||||
|
{ name: "RSA-OAEP" },
|
||||||
|
this.$store.state.keyPair.privateKey,
|
||||||
|
stringToArrayBuffer(atob(armored_data))
|
||||||
|
))
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.decryptObject = async function(efields, obj) {
|
||||||
|
// Decrypt all fields and return a new object
|
||||||
|
|
||||||
|
var newobj = {}
|
||||||
|
|
||||||
|
await Promise.all(Object.keys(obj).map(async field => {
|
||||||
|
if (efields.includes(field) && obj[field] != null && obj[field] != "") {
|
||||||
|
// TODO: Catch error
|
||||||
|
newobj[field] = await this.decrypt(obj[field])
|
||||||
|
} else {
|
||||||
|
newobj[field] = obj[field]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return newobj
|
||||||
|
},
|
||||||
|
|
||||||
|
Vue.prototype.encryptObject = async function(efields, obj) {
|
||||||
|
// Encrypt all fields and return a new object
|
||||||
|
|
||||||
|
var newobj = {}
|
||||||
|
|
||||||
|
await Promise.all(Object.keys(obj).map(async field => {
|
||||||
|
if (efields.includes(field) && obj[field] != null) {
|
||||||
|
// TODO: Catch error
|
||||||
|
newobj[field] = await this.encrypt(obj[field])
|
||||||
|
} else {
|
||||||
|
newobj[field] = obj[field]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return newobj
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -6,11 +6,12 @@ from users.models import UserSettings
|
|||||||
|
|
||||||
@admin.action(description="Remove the key from the users")
|
@admin.action(description="Remove the key from the users")
|
||||||
def remove_key(modeladmin, request, queryset):
|
def remove_key(modeladmin, request, queryset):
|
||||||
queryset.update(k356_key=False, k356_key_fingerprint=None)
|
# FIX: This is only for debugging first, this should *never* be used in production
|
||||||
|
queryset.update(public_key=None, private_key=None)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(UserSettings)
|
@admin.register(UserSettings)
|
||||||
class UserSettingsAdmin(admin.ModelAdmin):
|
class UserSettingsAdmin(admin.ModelAdmin):
|
||||||
list_display = ("user", "k356_key")
|
list_display = ("user",)
|
||||||
|
|
||||||
actions = [remove_key]
|
actions = [remove_key]
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-09-28 04:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0002_usersettings_custom_identifier"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="private_key",
|
||||||
|
field=models.TextField(max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="public_key",
|
||||||
|
field=models.TextField(max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-09-28 06:34
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0003_usersettings_private_key_usersettings_public_key"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="k356_key",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="k356_key_fingerprint",
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-09-28 07:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0004_remove_usersettings_k356_key_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="custom_identifier",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="usersettings",
|
||||||
|
name="name",
|
||||||
|
field=models.TextField(blank=True, max_length=2048, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,6 +1,7 @@
|
|||||||
from uuid import uuid4
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
from app.utils.models import BaseModel
|
from app.utils.models import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@ -10,5 +11,6 @@ User = get_user_model()
|
|||||||
class UserSettings(BaseModel):
|
class UserSettings(BaseModel):
|
||||||
user = models.OneToOneField(User, on_delete=models.PROTECT, related_name="setting")
|
user = models.OneToOneField(User, on_delete=models.PROTECT, related_name="setting")
|
||||||
|
|
||||||
k356_key = models.BooleanField(default=False)
|
# The private and public key are wrapped with the AES key from the front-end
|
||||||
k356_key_fingerprint = models.CharField(null=True)
|
public_key = models.TextField(max_length=2048, null=True)
|
||||||
|
private_key = models.TextField(max_length=2048, null=True)
|
||||||
|
|||||||
@ -3,8 +3,10 @@ from django.urls import path
|
|||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
app_name = "users"
|
app_name = "users"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("keys", views.keys, name="keys"),
|
||||||
path("k356/validate", views.k356_validate, name="k356.validate"),
|
path("k356/validate", views.k356_validate, name="k356.validate"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import json
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
login_required()
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
def k356_validate(request):
|
def k356_validate(request):
|
||||||
|
|
||||||
us = request.user.setting
|
us = request.user.setting
|
||||||
@ -36,7 +38,7 @@ def k356_validate(request):
|
|||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"ok": True
|
"ok": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,3 +48,30 @@ def k356_validate(request):
|
|||||||
"fingerprint": us.k356_key_fingerprint,
|
"fingerprint": us.k356_key_fingerprint,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def keys(request):
|
||||||
|
|
||||||
|
us = request.user.setting
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({"error": "INVALID_DATA"}, status=401)
|
||||||
|
|
||||||
|
if us.private_key and not request.GET("force", "false") == "true":
|
||||||
|
return JsonResponse({"error": "KEY_EXISTS"}, status=401)
|
||||||
|
|
||||||
|
us.private_key = data.get("privateKey", None)
|
||||||
|
us.public_key = data.get("publicKey", None)
|
||||||
|
us.save()
|
||||||
|
|
||||||
|
return JsonResponse(
|
||||||
|
{
|
||||||
|
"privateKey": us.private_key,
|
||||||
|
"publicKey": us.public_key,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
109
k356/wrap.js
Normal file
109
k356/wrap.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const operations = crypto.subtle
|
||||||
|
var iv = crypto.getRandomValues(new Uint8Array(24))
|
||||||
|
|
||||||
|
function stringToArrayBuffer(str) {
|
||||||
|
var buf = new ArrayBuffer(str.length);
|
||||||
|
var bufView = new Uint8Array(buf);
|
||||||
|
for (var i = 0, strLen = str.length; i < strLen; i++) {
|
||||||
|
bufView[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function arrayBufferToString(str) {
|
||||||
|
var byteArray = new Uint8Array(str);
|
||||||
|
var byteString = '';
|
||||||
|
for (var i = 0; i < byteArray.byteLength; i++) {
|
||||||
|
byteString += String.fromCodePoint(byteArray[i]);
|
||||||
|
}
|
||||||
|
return byteString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const passwordAsKeyData = encoder.encode('superSecretPassword');
|
||||||
|
|
||||||
|
const keyFromPassword = await operations.importKey(
|
||||||
|
"raw",
|
||||||
|
passwordAsKeyData,
|
||||||
|
"PBKDF2",
|
||||||
|
false,
|
||||||
|
["deriveKey"]
|
||||||
|
)
|
||||||
|
|
||||||
|
const aes = await operations.deriveKey(
|
||||||
|
{
|
||||||
|
name: "PBKDF2",
|
||||||
|
salt: stringToArrayBuffer("salt"),
|
||||||
|
iterations: 250000,
|
||||||
|
hash: "SHA-256",
|
||||||
|
},
|
||||||
|
keyFromPassword,
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
length: 256
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["wrapKey", "unwrapKey"]
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyPair = await crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: "RSA-OAEP",
|
||||||
|
modulusLength: 4096,
|
||||||
|
publicExponent: new Uint8Array([1, 0, 1]),
|
||||||
|
hash: "SHA-256",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
)
|
||||||
|
|
||||||
|
var wrappedRSAKey = await crypto.subtle.wrapKey(
|
||||||
|
"jwk",
|
||||||
|
keyPair.privateKey,
|
||||||
|
aes,
|
||||||
|
{name: "AES-GCM", iv: iv}
|
||||||
|
)
|
||||||
|
|
||||||
|
const kk = btoa(arrayBufferToString(wrappedRSAKey))
|
||||||
|
const siv = btoa(arrayBufferToString(iv))
|
||||||
|
|
||||||
|
console.log(wrappedRSAKey)
|
||||||
|
console.log('wrapped key:', kk)
|
||||||
|
console.log('iv:', siv)
|
||||||
|
|
||||||
|
console.log("=================================")
|
||||||
|
console.log("=================================")
|
||||||
|
console.log("=================================")
|
||||||
|
|
||||||
|
var unwrapped = await crypto.subtle.unwrapKey(
|
||||||
|
"jwk",
|
||||||
|
stringToArrayBuffer(atob(kk)),
|
||||||
|
aes,
|
||||||
|
{name: "AES-GCM", iv: stringToArrayBuffer(atob(siv))},
|
||||||
|
{
|
||||||
|
name: "RSA-OAEP",
|
||||||
|
hash: "SHA-256",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["decrypt"]
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(unwrapped)
|
||||||
|
|
||||||
|
const enc = await operations.encrypt(
|
||||||
|
{ name: "RSA-OAEP" },
|
||||||
|
keyPair.publicKey,
|
||||||
|
stringToArrayBuffer("asd"),
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(btoa(arrayBufferToString(enc)))
|
||||||
|
|
||||||
|
const dec = await operations.decrypt(
|
||||||
|
{ name: "RSA-OAEP" },
|
||||||
|
unwrapped,
|
||||||
|
enc,
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(arrayBufferToString(dec))
|
||||||
Loading…
Reference in New Issue
Block a user