[add] nav [add] session management [add] pages

This commit is contained in:
Fenris Wolf 2024-09-19 10:17:43 +02:00
parent 03f29fae11
commit 2738299c1b
12 changed files with 5696 additions and 3823 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -22,5 +22,13 @@ document.addEventListener(
</script>
</head>
<body>
<header>
<nav>
<ul>
</ul>
</nav>
</header>
<main>
</main>
</body>
</html>

View file

@ -5,7 +5,48 @@ namespace _zeitbild.frontend_web.backend
/**
*/
var _session_key : (null | string) = null;
var _data_chest : (
null
|
lib_plankton.storage.type_chest<string, string, void, string, string>
) = null;
/**
*/
export async function init(
) : Promise<void>
{
_data_chest = lib_plankton.storage.localstorage.implementation_chest(
{
"corner": "zeitbild",
}
);
return Promise.resolve<void>(undefined);
}
/**
*/
async function get_session_key(
) : Promise<(null | string)>
{
try {
return (await _data_chest.read("session_key"));
}
catch (error) {
return null;
}
}
/**
*/
export async function is_logged_in(
) : Promise<boolean>
{
return ((await get_session_key()) !== null);
}
/**
@ -23,6 +64,7 @@ namespace _zeitbild.frontend_web.backend
lib_plankton.http.enum_method.patch,
].includes(method)
);
const session_key : (null | string) = await get_session_key();
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": (
@ -64,11 +106,11 @@ namespace _zeitbild.frontend_web.backend
{"Content-Type": "application/json"}
),
(
(_session_key === null)
(session_key === null)
?
{}
:
{"X-Session-Key": _session_key}
{"X-Session-Key": session_key}
)
),
"body": (
@ -80,8 +122,19 @@ namespace _zeitbild.frontend_web.backend
),
};
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(http_request);
const output : any = lib_plankton.json.decode(http_response.body.toString());
return Promise.resolve<any>(output);
if (
! (
(http_response.status_code >= 200)
&&
(http_response.status_code < 300)
)
) {
return Promise.reject<any>(http_response.body.toString());
}
else {
const output : any = lib_plankton.json.decode(http_response.body.toString());
return Promise.resolve<any>(output);
}
}
@ -92,7 +145,7 @@ namespace _zeitbild.frontend_web.backend
password : string
) : Promise<void>
{
_session_key = await call(
const session_key : string = await call(
lib_plankton.http.enum_method.post,
"/session/begin",
{
@ -100,6 +153,7 @@ namespace _zeitbild.frontend_web.backend
"password": password,
}
);
await _data_chest.write("session_key", session_key);
return Promise.resolve<void>(undefined);
}
@ -109,11 +163,13 @@ namespace _zeitbild.frontend_web.backend
export async function session_end(
) : Promise<void>
{
return call(
await call(
lib_plankton.http.enum_method.delete,
"/session/end",
null
);
await _data_chest.delete("session_key");
return Promise.resolve<void>(undefined);
}
@ -121,9 +177,9 @@ namespace _zeitbild.frontend_web.backend
*/
export async function calendar_list(
) : Promise<
Array<
Array<
{
key : type_calendar_id;
key : _zeitbild.frontend_web.type.calendar_id;
preview : {
name : string;
}
@ -146,14 +202,14 @@ namespace _zeitbild.frontend_web.backend
from_pit : _zeitbild.frontend_web.helpers.type_pit,
to_pit : _zeitbild.frontend_web.helpers.type_pit,
options : {
calendar_ids ?: (null | Array<type_calendar_id>);
calendar_ids ?: (null | Array<_zeitbild.frontend_web.type.calendar_id>);
} = {}
) : Promise<
Array<
{
calendar_id : type_calendar_id;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
calendar_name : string;
event : type_event;
event : _zeitbild.frontend_web.type.event_object;
}
>
>

View file

@ -1,218 +0,0 @@
/**
*/
async function main(
args_raw : Array<string>
) : Promise<void>
{
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
"data_path": lib_plankton.args.class_argument.volatile({
"indicators_long": ["data-path"],
"indicators_short": ["d"],
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "data.json",
// "info": null,
"name": "data-path",
}),
"timezone_shift": lib_plankton.args.class_argument.volatile({
"indicators_long": ["timezone-shift"],
"indicators_short": ["t"],
"type": lib_plankton.args.enum_type.integer,
"mode": lib_plankton.args.enum_mode.replace,
"default": 0,
// "info": null,
"name": "timezone-shift",
}),
"calendar_id": lib_plankton.args.class_argument.volatile({
"indicators_long": ["calendar"],
"indicators_short": ["c"],
"type": lib_plankton.args.enum_type.integer,
"mode": lib_plankton.args.enum_mode.replace,
"default": 1,
// "info": null,
"name": "calendar_id",
}),
"view_mode": lib_plankton.args.class_argument.volatile({
"indicators_long": ["view-moode"],
"indicators_short": ["m"],
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "table",
// "info": null,
"name": "view_mode",
}),
"help": lib_plankton.args.class_argument.volatile({
"indicators_long": ["help"],
"indicators_short": ["h"],
"type": lib_plankton.args.enum_type.boolean,
"mode": lib_plankton.args.enum_mode.replace,
"default": false,
// "info": null,
"name": "help",
}),
});
const args : Record<string, any> = arg_handler.read(lib_plankton.args.enum_environment.cli, args_raw.join(" "));
// init
lib_plankton.log.conf_push(
[
// lib_plankton.log.channel_make({"kind": "file", "data": {"threshold": "debug", "path": "/tmp/kalender.log"}}),
lib_plankton.log.channel_make({"kind": "file", "data": {"threshold": "info", "path": "/dev/stderr"}}),
// lib_plankton.log.channel_make({"kind": "stdout", "data": {"threshold": "info"}}),
]
);
// exec
if (args["help"]) {
process.stdout.write(
arg_handler.generate_help(
{
"programname": "kalender",
"description": "Kalender",
"executable": "kalender",
}
)
+
"\n"
);
}
else {
const data : type_datamodel = lib_plankton.call.convey(
await lib_plankton.file.read(args["data_path"]),
[
lib_plankton.json.decode,
(data_raw : any) => (
({
"users": data_raw["users"],
"calendars": (
data_raw["calendars"]
.map(
(calendar_entry_raw : any) => ({
"id": calendar_entry_raw["id"],
"object": (
((calendar_object_raw) => {
switch (calendar_object_raw["kind"]) {
default: {
return calendar_object_raw;
break;
}
case "concrete": {
return {
"kind": "concrete",
"data": {
"name": calendar_object_raw["data"]["name"],
"users": calendar_object_raw["data"]["users"],
"events": (
calendar_object_raw["data"]["events"]
.map(
(event_raw : any) => ({
"name": event_raw["name"],
"begin": event_raw["begin"],
"end": (
(event_raw["end"] === null)
?
null
:
event_raw["end"]
),
"description": event_raw["description"],
})
)
),
},
};
break;
}
}
}) (calendar_entry_raw["object"])
),
})
)
),
}) as type_datamodel
),
]
);
let content : string;
switch (args["view_mode"]) {
default: {
content = "";
throw (new Error("invalid view mode"));
break;
}
case "table": {
content = await calendar_view_table_html(
data,
args.calendar_id,
{
"timezone_shift": args["timezone_shift"],
}
);
break;
}
case "list": {
content = await calendar_view_list_html(
data,
args.calendar_id,
{
"timezone_shift": args["timezone_shift"],
}
);
break;
}
}
const output : string = template_coin(
"main",
{
"content": content,
}
);
process.stdout.write(output);
}
return Promise.resolve<void>(undefined);
}
/*
process.stderr.write(
JSON.stringify(
{
"x1": datetime_from_date_object(
new Date(Date.now()),
{
"timezone_shift": 2,
}
),
"x2": datetime_from_date_object(
new Date(Date.now()),
{
"timezone_shift": 0,
}
),
"x3": pit_from_year_and_week(
2024,
37,
{
"timezone_shift": 0,
}
),
},
undefined,
"\t"
)
+
"\n"
);
*/
(
main(process.argv.slice(2))
.then(
() => {}
)
/*
.catch(
(error) => {process.stderr.write(String(error) + "\n");}
)
*/
);

View file

@ -8,7 +8,7 @@ namespace _zeitbild.frontend_web.helpers
*/
var _template_cache : Record<string, string> = {};
/**
* @todo caching
*/
@ -44,8 +44,8 @@ namespace _zeitbild.frontend_web.helpers
)
);
}
/**
* @todo outsource
*/

View file

@ -4,130 +4,133 @@
namespace _zeitbild.frontend_web
{
/**
*/
type type_conf = {
view_mode : string;
calendar_ids : Array<int>;
timezone_shift : int;
};
/**
*/
async function render(
) : Promise<void>
{
const target : HTMLElement = (document.querySelector("body") as HTMLBodyElement);
const view_type : string = "table";
switch (view_type) {
default: {
throw (new Error("invalid view mode"));
break;
}
case "table": {
const content : string = await _zeitbild.frontend_web.view.calendar_view_table_html(
{
"calendar_ids": null,
"from": {
"year": 2024,
"week": 37
},
"to": {
"year": 2024,
"week": 43
},
"timezone_shift": /*conf.timezone_shift*/0,
}
);
target.innerHTML = content;
/*
document.querySelectorAll(".tableview-sources-entry").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
const element_ : HTMLElement = (event.target as HTMLElement);
const calendar_id : type_calendar_id = parseInt(element_.getAttribute("rel") as string);
const active : boolean = element_.classList.toggle("tableview-sources-entry-active");
render(
conf,
lib_plankton.call.convey(
calendar_ids,
[
(x : Array<int>) => (
active
?
calendar_ids.concat([calendar_id])
:
calendar_ids.filter(y => (y !== calendar_id))
),
]
)
);
}
);
}
);
*/
break;
}
case "list": {
const content : string = await _zeitbild.frontend_web.view.calendar_view_list_html(
null,
{
"timezone_shift": /*conf.timezone_shift*/0,
}
);
target.innerHTML = content;
break;
}
}
return Promise.resolve<void>(undefined);
}
/**
*/
export async function main(
) : Promise<void>
{
// init
lib_plankton.log.conf_push(
[
lib_plankton.log.channel_make({"kind": "console", "data": {"threshold": "info"}}),
]
);
// conf
await _zeitbild.frontend_web.conf.init(("conf.json"));
await _zeitbild.frontend_web.conf.init("conf.json");
// setup
await _zeitbild.frontend_web.backend.session_begin(
"alice",
"alice"
// init
await _zeitbild.frontend_web.backend.init();
lib_plankton.zoo_page.init(
document.querySelector("main"),
{
"pool": {
"login": async (parameters, target_element) => {
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"login",
{
"form": "",
}
);
const form : lib_plankton.zoo_form.class_form<
{name : string; password : string;},
{name : string; password : string;}
> = new lib_plankton.zoo_form.class_form<
{name : string; password : string;},
{name : string; password : string;}
>(
x => x,
x => x,
new lib_plankton.zoo_input.class_input_group<
{name : string; password : string;}
>(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": "Name", // TODO: translate
},
{
"name": "password",
"input": new lib_plankton.zoo_input.class_input_password(),
"label": "Kennwort", // TODO: translate
},
]
),
[
{
"label": "Anmelden", // TODO: translate
"target": "submit",
"procedure": async (get_value, get_representation) => {
const value : any = await get_value();
try {
await _zeitbild.frontend_web.backend.session_begin(
value.name,
value.password
);
lib_plankton.zoo_page.set(
{
"name": "events",
"parameters": {}
}
);
}
catch (error) {
lib_plankton.zoo_page.set(
{
"name": "login",
"parameters": {
"name": value.name,
}
}
);
}
}
},
]
);
await form.setup(document.querySelector("#login"));
await form.input_write(
{
"name": (parameters.name ?? ""),
"password": "",
}
);
},
"logout": async (parameters, target_element) => {
await _zeitbild.frontend_web.backend.session_end(
);
lib_plankton.zoo_page.set(
{
"name": "login",
"parameters": {
}
}
);
},
"events": async (parameters, target_element) => {
const content = await _zeitbild.frontend_web.view.calendar_view_table_html(
{
"calendar_ids": null,
"from": {
"year": 2024,
"week": 37
},
"to": {
"year": 2024,
"week": 43
},
"timezone_shift": /*conf.timezone_shift*/0,
}
);
target_element.innerHTML = content;
},
},
"fallback": {
"name": "login",
"parameters": {}
}
}
);
// args
/*
const url : URL = new URL(window.location.toString());
const calendar_ids : Array<type_calendar_id> = (
(url.searchParams.get("ids") !== null)
?
lib_plankton.call.convey(
url.searchParams.get("ids"),
[
(x : string) => lib_plankton.string.split(x, ","),
(x : Array<string>) => x.map(y => parseInt(y)),
]
)
:
(await _zeitbild.frontend_web.backend.calendar_list()).map(x => x.key)
);
*/
lib_plankton.zoo_page.add_nav_entry({"name": "login", "parameters": {}});
lib_plankton.zoo_page.add_nav_entry({"name": "events", "parameters": {}});
lib_plankton.zoo_page.add_nav_entry({"name": "logout", "parameters": {}});
// exec
await render();
lib_plankton.zoo_page.start();
return Promise.resolve<void>(undefined);
}

View file

@ -1,12 +1,14 @@
/**
*/
namespace _zeitbild.frontend_web
namespace _zeitbild.frontend_web.type
{
/**
*/
type type_role = (
export type role = (
"admin"
|
"editor"
|
"viewer"
@ -15,19 +17,24 @@ namespace _zeitbild.frontend_web
/**
*/
type type_user_id = int;
export type user_id = int;
/**
*/
type type_user_object = {
export type user_object = {
name : string;
email_address : (
null
|
string
);
};
/**
*/
export type type_event = {
export type event_object = {
name : string;
begin : _zeitbild.frontend_web.helpers.type_datetime;
end : (
@ -50,54 +57,48 @@ namespace _zeitbild.frontend_web
/**
*/
export type type_calendar_id = int;
export type resource_id = int;
/**
*/
export type type_calendar_object = (
export type resource_object = (
{
kind : "concrete";
kind : "local";
data : {
name : string;
private : boolean;
users : Array<
{
id : type_user_id;
role : type_role;
}
events : Array<
event_object
>;
events : Array<type_event>;
};
}
|
{
kind : "caldav";
data : {
name : string;
private : boolean;
read_only : boolean;
source_url : string;
}
url : string;
};
}
);
/**
*/
export type type_datamodel = {
users : Array<
export type calendar_id = int;
/**
*/
export type calendar_object = {
name : string;
public : boolean;
members : Array<
{
id : type_user_id;
object : type_user_object;
}
>;
calendars : Array<
{
id : type_calendar_id;
object : type_calendar_object;
user_id : user_id;
role : role;
}
>;
resource_id : resource_id;
};
}

View file

@ -8,7 +8,7 @@ namespace _zeitbild.frontend_web.view
*/
function event_generate_tooltip(
calendar_name : string,
event : type_event
event : _zeitbild.frontend_web.type.event_object
) : string
{
return (
@ -137,7 +137,7 @@ namespace _zeitbild.frontend_web.view
calendar_ids : (
null
|
Array<type_calendar_id>
Array<_zeitbild.frontend_web.type.calendar_id>
),
from : {
year : int;
@ -151,7 +151,7 @@ namespace _zeitbild.frontend_web.view
) : Promise<
{
sources : lib_plankton.map.type_map<
type_calendar_id,
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
}
@ -164,8 +164,8 @@ namespace _zeitbild.frontend_web.view
pit : _zeitbild.frontend_web.helpers.type_pit;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
@ -177,12 +177,6 @@ namespace _zeitbild.frontend_web.view
>
{
const now_pit : _zeitbild.frontend_web.helpers.type_pit = _zeitbild.frontend_web.helpers.pit_now();
/*
const calendar_object : type_calendar_object = calendar_read(
data,
calendar_id
);
*/
const from_pit : _zeitbild.frontend_web.helpers.type_pit = _zeitbild.frontend_web.helpers.pit_from_year_and_week(
(from as {year : int; week : int}).year,
(from as {year : int; week : int}).week,
@ -201,9 +195,9 @@ namespace _zeitbild.frontend_web.view
// prepare
const entries : Array<
{
calendar_id : type_calendar_id;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
calendar_name : string;
event : type_event;
event : _zeitbild.frontend_web.type.event_object;
}
> = await _zeitbild.frontend_web.backend.events(
from_pit,
@ -214,7 +208,7 @@ namespace _zeitbild.frontend_web.view
);
let result : {
sources : lib_plankton.map.type_map<
type_calendar_id,
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
}
@ -227,8 +221,8 @@ namespace _zeitbild.frontend_web.view
pit : _zeitbild.frontend_web.helpers.type_pit;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
@ -264,8 +258,8 @@ namespace _zeitbild.frontend_web.view
pit : _zeitbild.frontend_web.helpers.type_pit;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
@ -373,7 +367,7 @@ namespace _zeitbild.frontend_web.view
calendar_ids ?: (
null
|
Array<type_calendar_id>
Array<_zeitbild.frontend_web.type.calendar_id>
);
from ?: {
year : int;
@ -419,7 +413,7 @@ namespace _zeitbild.frontend_web.view
);
const stuff : {
sources : lib_plankton.map.type_map<
type_calendar_id,
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
}
@ -432,8 +426,8 @@ namespace _zeitbild.frontend_web.view
pit : _zeitbild.frontend_web.helpers.type_pit;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
@ -448,7 +442,7 @@ namespace _zeitbild.frontend_web.view
options.timezone_shift
);
const sources : lib_plankton.map.type_map<
type_calendar_id,
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
color : lib_plankton.color.type_color;
@ -588,7 +582,7 @@ namespace _zeitbild.frontend_web.view
/**
*/
async function calendar_view_list_data(
calendar_ids : Array<type_calendar_id>,
calendar_ids : Array<_zeitbild.frontend_web.type.calendar_id>,
options : {
from ?: _zeitbild.frontend_web.helpers.type_pit;
to ?: _zeitbild.frontend_web.helpers.type_pit;
@ -597,8 +591,8 @@ namespace _zeitbild.frontend_web.view
) : Promise<
Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
>
>
@ -625,8 +619,8 @@ namespace _zeitbild.frontend_web.view
const entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
> = await _zeitbild.frontend_web.backend.events(
(options.from as _zeitbild.frontend_web.helpers.type_pit),
@ -651,7 +645,7 @@ namespace _zeitbild.frontend_web.view
/**
*/
export async function calendar_view_list_html(
calendar_ids : Array<type_calendar_id>,
calendar_ids : Array<_zeitbild.frontend_web.type.calendar_id>,
options : {
from ?: _zeitbild.frontend_web.helpers.type_pit;
to ?: _zeitbild.frontend_web.helpers.type_pit;
@ -661,8 +655,8 @@ namespace _zeitbild.frontend_web.view
{
const stuff : Array<
{
calendar_id : type_calendar_id;
event : type_event;
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event : _zeitbild.frontend_web.type.event_object;
}
> = await calendar_view_list_data(
calendar_ids,

View file

@ -4,6 +4,29 @@ html {
font-family: sans-serif;
}
header {
border-bottom: 2px solid #888;
padding-bottom: 16px;
margin-bottom: 16px;
}
nav > ul {
list-style-type: none;
margin: 0;
padding: 0;
}
nav > ul > li {
display: inline-block;
margin: 16px;
padding: 8px;
}
a {
text-decoration: none;
color: hsl(0, 0%, 100%);
}
.calendar {
display: flex;
flex-direction: row;
@ -82,3 +105,13 @@ html {
font-weight: bold;
cursor: pointer;
}
.plankton_input_group_field {
margin-bottom: 8px;
}
.plankton_input_group_field_label {
display: block;
font-weight: bold;
font-size: 0.8em;
}

View file

@ -0,0 +1,2 @@
<div id="login">
</div>

View file

@ -6,20 +6,22 @@ dir=lib/plankton
modules=""
modules="${modules} base"
modules="${modules} conf"
modules="${modules} call"
modules="${modules} storage"
modules="${modules} file"
modules="${modules} json"
modules="${modules} args"
modules="${modules} string"
modules="${modules} color"
modules="${modules} xml"
modules="${modules} map"
# modules="${modules} ical"
modules="${modules} http"
modules="${modules} log"
modules="${modules} url"
modules="${modules} conf"
modules="${modules} www_form"
modules="${modules} zoo-page"
modules="${modules} zoo-form"
modules="${modules} zoo-input"
## exec