User management with native gui and further improvements

This commit is contained in:
2026-08-17 10:37:19 +02:00
parent 41556cfe73
commit a3c8b4083d
24 changed files with 1004 additions and 159 deletions
+9 -2
View File
@@ -15,6 +15,7 @@ Yacht Management Software (YMS) Installation
- Python GTK3
- python3-gi
- gir1.2-gtk-3.0
- gir-rsvg-2.0
1.3 Web GUI
- Webserver
@@ -69,7 +70,7 @@ Customize path and access rights to your needs
To grant access for the local gui the directory has to be writeable by a special
group (here as example 'yms'). The user which runs the local gui (here
'guiuser') has also to be a member of that group as well as the webserver user
''www-data'.
'www-data'.
groupadd yms
usermod -aG yms www-data
@@ -107,14 +108,20 @@ Configfile should be readable by webserver but not writeable.
Create a local database user with same name as your computer user and allow
database acces with current unix credentials:
mysql> CREATE USER 'ymsgui'@'localhost' IDENTIFIED VIA unix_socket;
mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON yms.* TO 'ymsgui'@'localhost';
Copy ymsgui.py to /usr/local/bin
Copy directory yms to /usr/local/share
Start ymsgui.py, default configuration file is automatically created
7. Start using YMS
Use webgui:
Start your browser and login to YMS with the default username/password:
Start your browser and login to YMS with the initial username/password:
captain/captain
Use local client:
Start ymsgui.py
Change the password using the local GUI before normal use.
+70 -30
View File
@@ -3,6 +3,12 @@
/* MariaDB Scheme
Version: 1 */
/* TODO
- check all values if unsigned can be used or not
- implement foreign keys where possible
- rename provisions to provision(?)
*/
/* User
userid is signed because nevative values are required for settings
The user table must not be writeable by the webgui user!
@@ -58,6 +64,28 @@ CREATE TABLE dropdown (
PRIMARY KEY (ddid, ddval)
) ENGINE=InnoDB;
CREATE TABLE company (
compid smallint(6) NOT NULL AUTO_INCREMENT,
compname varchar(60) NOT NULL,
comptype tinyint(3) UNSIGNED NOT NULL DEFAULT 1,
comptype2 tinyint(3) UNSIGNED DEFAULT NULL,
shortname varchar(20) DEFAULT NULL,
street varchar(40),
zip varchar(10),
city varchar(30),
country varchar(30),
contact varchar(30),
phone varchar(30),
email varchar(50),
web varchar(40),
customerno varchar(20),
contractno varchar(20),
remarks varchar(150) DEFAULT NULL,
flags set('deleted','historic') DEFAULT NULL,
PRIMARY KEY (compid),
UNIQUE INDEX ix_compname (compname, comptype)
) ENGINE=InnoDB;
CREATE TABLE vessel (
vid smallint(6) NOT NULL AUTO_INCREMENT,
vtype enum('mono','cat','tri') NOT NULL DEFAULT 'mono',
@@ -76,14 +104,16 @@ CREATE TABLE vessel (
shapefile varchar(40) DEFAULT NULL,
sid_default smallint(6) UNSIGNED DEFAULT NULL, -- default storage
remarks varchar(150) DEFAULT NULL,
PRIMARY KEY (vid)
PRIMARY KEY (vid),
FOREIGN KEY fk_vessel_shipyard (shipyard)
REFERENCES company(compid)
) ENGINE=InnoDB;
CREATE TABLE storage (
sid smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT,
vid smallint(6) NOT NULL DEFAULT 1,
sname varchar(40) NOT NULL,
stype tinyint(3) NOT NULL DEFAULT 0,
stype tinyint(3) NOT NULL DEFAULT 0, -- ddid=3
capacity smallint(6) DEFAULT NULL,
capaunit enum('kg','l') DEFAULT NULL,
x smallint(6) UNSIGNED NOT NULL DEFAULT 0,
@@ -148,6 +178,16 @@ CREATE TABLE equipment (
PRIMARY KEY(eid)
) ENGINE=InnoDB;
/*
convert to
sid SMALLINT UNSIGNED NULL,
boxid SMALLINT UNSIGNED NULL,
CHECK (
(sid IS NOT NULL AND boxid IS NULL)
OR
(sid IS NULL AND boxid IS NOT NULL)
)
*/
CREATE TABLE inventory (
invid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
invname varchar(80) NOT NULL,
@@ -161,6 +201,7 @@ CREATE TABLE inventory (
purchdate date DEFAULT NULL,
invcond enum('unknown','excellent','good','fair','bad','repairable','defect') NOT NULL default 'unknown',
remarks varchar(150) DEFAULT NULL,
flags set('ordered','removed','deleted') DEFAULT NULL,
PRIMARY KEY (invid),
INDEX ix_invname (invname)
) ENGINE=InnoDB;
@@ -199,6 +240,7 @@ CREATE table fuse (
UNIQUE INDEX ix_fusenumber (vid, fnumber)
) ENGINE=InnoDB;
-- change vals to decimal(12,4)?
CREATE TABLE measurement (
mid smallint(6) NOT NULL AUTO_INCREMENT,
mname varchar(80) NOT NULL,
@@ -226,8 +268,10 @@ CREATE TABLE provisions (
weight float(5,2) UNSIGNED DEFAULT NULL,
weight_net float(5,2) UNSIGNED DEFAULT NULL,
unit tinyint(3) DEFAULT NULL,
calories smallint(6) UNSIGNED DEFAULT NULL,
storedate date DEFAULT CURRENT_DATE,
shelflife date DEFAULT NULL,
price decimal(12,2) DEFAULT NULL,
remarks varchar(150) DEFAULT NULL,
PRIMARY KEY (provid)
) ENGINE=InnoDB;
@@ -259,38 +303,19 @@ CREATE TABLE docref (
drid int(10) NOT NULL AUTO_INCREMENT,
reftype enum('vessel','box','equipment','project','task','user','company') NOT NULL DEFAULT 'vessel',
docid smallint(6) NOT NULL,
refid smallint(6) NOT NULL,
refid int(10) NOT NULL,
PRIMARY KEY (drid),
UNIQUE INDEX ix_docref (docid, refid),
FOREIGN KEY fx_docref_doc (docid)
UNIQUE INDEX ix_docref (docid, refid, reftype),
FOREIGN KEY fk_docref_doc (docid)
REFERENCES document (docid)
ON DELETE RESTRICT
ON UPDATE CASCADE
) ENGINE=InnoDB;
CREATE TABLE company (
compid smallint(6) NOT NULL AUTO_INCREMENT,
compname varchar(60) NOT NULL,
comptype tinyint(3) UNSIGNED NOT NULL DEFAULT 1,
comptype2 tinyint(3) UNSIGNED DEFAULT NULL,
shortname varchar(20) DEFAULT NULL,
street varchar(40),
zip varchar(10),
city varchar(30),
country varchar(30),
contact varchar(30),
phone varchar(30),
email varchar(50),
web varchar(40),
customerno varchar(20),
contractno varchar(20),
remarks varchar(150) DEFAULT NULL,
flags set('deleted','historic') DEFAULT NULL,
PRIMARY KEY (compid),
UNIQUE INDEX ix_compname (compname, comptype)
) ENGINE=InnoDB;
/* TODO optional field: plan date, target date, completion date */
/* TODO optional fields: plan date, target date, completion date
interval_hours, last_hours
signalk: operating hours current vs maint every n operating hours in separate
*/
CREATE TABLE maintenance (
maintid int(10) NOT NULL AUTO_INCREMENT,
vid smallint(6) NOT NULL DEFAULT 1,
@@ -332,14 +357,16 @@ CREATE TABLE task (
finished datetime DEFAULT NULL,
responsible smallint(6) NOT NULL,
/* TODO executed_by : company or user? or both? */
PRIMARY KEY (taskid)
PRIMARY KEY (taskid),
FOREIGN KEY fk_task_user (responsible)
REFERENCES user(userid)
) ENGINE=InnoDB;
/* Notes can be assigned to tasks,maintenances and equipment */
CREATE TABLE note (
noteid int(10) NOT NULL AUTO_INCREMENT,
notetype enum('task','maint','equip') NOT NULL DEFAULT 'task',
refid smallint(6) NOT NULL,
refid int(10) NOT NULL,
annotation text NOT NULL,
PRIMARY KEY (noteid)
) ENGINE=InnoDB;
@@ -372,3 +399,16 @@ CREATE TABLE checkgroup (
title varchar(30) NOT NULL,
PRIMARY KEY (groupid)
) ENGINE=InnoDB;
/* Expansion for future use: signalk mapping (just an idea) */
CREATE TABLE signalk (
skid int(10) NOT NULL AUTO_INCREMENT,
vid smallint(6) NOT NULL,
objtype enum('equipment','storage') NOT NULL,
objid int(10) NOT NULL,
datatype enum('engine_hours','tank_level') NOT NULL,
skpath varchar(255) NOT NULL,
PRIMARY KEY (skid),
UNIQUE KEY ix_signalk
(vid, objtype, objid, datatype)
) ENGINE=InnoDB;
+2 -1
View File
@@ -76,4 +76,5 @@ INSERT INTO dropdown (ddid, ddval, ddtext, flags) VALUES
INSERT INTO dropdown (ddid, ddval, ddtext, flags) VALUES
(3, 0, 'Stauraum allgemein', 'fixed'),
(3, 1, 'Tank', 'fixed');
(3, 1, 'Tank', 'fixed'),
(3, 2, 'Extern', 'fixed'); -- not on board
+1 -1
View File
@@ -17,7 +17,7 @@ define('APP_ILLUSTRATION', 'sailboat.svg');
// database connection
$g_db_host = 'localhost';
$g_db_username = 'yms';
$g_db_username = 'ymsweb';
$g_db_password = 'changeme!';
$g_db_schema = 'yms';
+1
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -57,6 +57,7 @@ switch ($submit = form_get_action()) {
// WIP New standalone document
// debug $g_message->Add(print_r($_FILES, true));
// check for possible errors
// TODO use "noscale" option for images
if (!isset($_FILES['files']['error']) || is_array($_FILES['files']['error'])) {
$g_error->Add(_("Invalid file upload parameters."));
$action = ACT_ADD;
@@ -434,7 +435,7 @@ echo '<h2>', _('Add Document'), "</h2>\n";
<label for="files" class="form-label"><?=_('Document')?></label>
<input class="form-control" type="file" id="files" name="files" onchange="preview()">
<img id="frame" src="" class="img-fluid">
<button type="button" class="btn btn-secondary" onclick="clearimg();return false;"><?=_('Clear selection')?></button>
<button type="button" class="btn btn-secondary mt-2" onclick="clearimg();return false;"><?=_('Clear selection')?></button>
<script>
function preview() {
var images = ["image/png", "image/jpeg", "image/svg+xml"];
@@ -451,6 +452,9 @@ echo '<h2>', _('Add Document'), "</h2>\n";
}
</script>
</div>
<?php
form_create_check('noscale', _('Do not scale image'), FALSE);
?>
<div class="container-fluid px-0 my-3">
<button type="submit" name="submit[insert]" class="btn btn-primary"><?=_('Save')?></button>
<a href="<?=$g_scriptname?>" class="btn btn-secondary"><?=_('Back')?></a>
+1 -1
View File
@@ -420,7 +420,7 @@ $flt = db_get_filter($user->id, 200);
<input type="text" class="form-control" id="ename" name="ename">
</div>
<?php
form_create_select('category', _('Category'), [-1 => '--- none ---'] + $opt_ecat, $flt->cat);
form_create_select('category', _('Category'), $g_opt_none + $opt_ecat, $flt->cat);
form_create_select('manufacturer', _('Manufacturer'), $opt_manufacturer, $flt->manuf);
?>
<div class="mb-3">
+24 -2
View File
@@ -904,7 +904,8 @@ function db_get_opt_user($userid, $default=NULL) {
return $list;
}
function db_get_opt_proj($vid, $default=NULL) {
function db_get_opt_proj($vid, $exclude=[], $default=NULL) {
// exclude is a list of project states
global $pdo;
global $g_error;
if (isset($default)) {
@@ -912,7 +913,15 @@ function db_get_opt_proj($vid, $default=NULL) {
} else {
$list = array();
}
$sql = "SELECT projid, projname FROM project WHERE vid=? ORDER BY projname";
$sql = "SELECT projid, projname FROM project WHERE vid=?";
$where = [];
foreach ($exclude as $excl) {
$where[] = "NOT FIND_IN_SET('$excl',projstate)";
}
if (! empty($where)) {
$sql .= ' AND '. implode(' AND ', $where);
}
$sql .= " ORDER BY projname";
$sth = $pdo->prepare($sql);
try {
$sth->execute([$vid]);
@@ -1047,6 +1056,18 @@ function form_create_text($fieldname, $label, $value) {
echo "</div>\n";
}
function form_create_check($fieldname, $label, $checked) {
// create single checkbox with assigned label
echo '<div class="mb-3">', "\n";
echo '<input type="checkbox" class="form-check-input" name="', $fieldname, '" id="', $fieldname, '"';
if ($checked) {
echo ' checked';
}
echo ">\n";
echo '<label for="', $fieldname, '">', $label, "</label>\n";
echo "</div>\n";
}
function form_create_select($fieldname, $label, $optlist, $selval=NULL, $multiple=FALSE) {
echo '<div class="mb-3">', "\n";
echo '<label for="', $fieldname, '">', $label, "</label>\n";
@@ -1063,6 +1084,7 @@ function form_create_select($fieldname, $label, $optlist, $selval=NULL, $multipl
}
function form_create_checks($fieldname, $label, $optlist, $selected = []) {
// create multiple combined checkboxes with title
echo '<fieldset class="mb-3">';
echo '<legend class="fs-6 mb-1 border-bottom pb-1">', $label, "</legend>\n";
echo '<div class="d-flex flex-wrap gap-3">';
+2
View File
File diff suppressed because one or more lines are too long
+167
View File
@@ -0,0 +1,167 @@
let db;
const request = indexedDB.open("shopping", 1);
request.onupgradeneeded = function(event) {
db = event.target.result;
const store = db.createObjectStore(
"items",
{ keyPath: "provid" }
);
};
request.onsuccess = function(event) {
db = event.target.result;
checkDatabase();
};
function checkDatabase()
{
let tx = db.transaction(
"items",
"readonly"
);
let store = tx.objectStore("items");
let request = store.count();
request.onsuccess = function(){
if (request.result > 0) {
renderLists();
} else {
importList(initialList);
}
};
}
function importList(list)
{
let tx = db.transaction(
"items",
"readwrite"
);
let store = tx.objectStore("items");
list.forEach(item => {
item.done = false;
store.put(item);
});
tx.oncomplete = function(){
renderLists();
};
}
function renderLists()
{
let tx = db.transaction(
"items",
"readonly"
);
let store = tx.objectStore("items");
let request = store.getAll();
request.onsuccess = function(){
let pending = "";
let completed = "";
request.result.forEach(item => {
let html = `
<div class="item">
<span class="name">
${item.provname}
</span>
<span>
${item.buy} ${item.unit}
</span>
<button onclick="completeItem(${item.provid})">
</button>
</div>
`;
if (item.completed) {
completed += html;
} else {
pending += html;
}
});
document.getElementById("shoppingList").innerHTML = pending || "No items";
document.getElementById("completedList").innerHTML = completed || "No completed items";
};
}
function plus(id)
{
let tx = db.transaction(
"items",
"readwrite"
);
let store = tx.objectStore("items");
let req = store.get(id);
req.onsuccess = function(){
let item = req.result;
item.buy++;
store.put(item);
renderLists();
};
}
function minus(id)
{
let tx = db.transaction(
"items",
"readwrite"
);
let store = tx.objectStore("items");
let req = store.get(id);
req.onsuccess = function(){
let item = req.result;
item.buy--;
store.put(item);
renderLists();
};
}
function completeItem(id)
{
let tx = db.transaction(
"items",
"readwrite"
);
let store = tx.objectStore("items");
let request = store.get(id);
request.onsuccess = function(){
let item = request.result;
item.completed = true;
store.put(item);
renderLists();
};
}
function undo(id)
{
let tx = db.transaction(
"items",
"readwrite"
);
let store = tx.objectStore("items");
let req = store.get(id);
req.onsuccess = function(){
let item = req.result;
item.done = false;
store.put(item);
showList();
};
}
function done(id)
{
let tx = db.transaction(
"items",
"readwrite"
);
let store = tx.objectStore("items");
let req = store.get(id);
req.onsuccess = function(){
let item = req.result;
item.done = true;
store.put(item);
showList();
};
}
+58 -58
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: YMS 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-27 08:02+0200\n"
"POT-Creation-Date: 2026-08-14 08:33+0200\n"
"PO-Revision-Date: 2024-03-15 13:50+0100\n"
"Last-Translator: Thomas Hooge <thomas@hoogi.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: box.php:19 index.php:353 globals.inc:1536
#: box.php:19 index.php:357 globals.inc:1536
msgid "Boxes"
msgstr "Kisten"
@@ -48,7 +48,7 @@ msgid "Content"
msgstr "Inhalt"
#: box.php:98 box.php:183 box.php:248 cable.php:211 equipment.php:464
#: index.php:361 index.php:377 inventory.php:381 provisions.php:284
#: index.php:365 index.php:381 inventory.php:384 provisions.php:284
msgid "Weight"
msgstr "Gewicht"
@@ -64,7 +64,7 @@ msgid "View"
msgstr "Ansehen"
#: box.php:115 cable.php:151 company.php:231 documents.php:407 dropdown.php:162
#: equipment.php:399 equipment_js.php:247 fuse.php:176 index.php:318
#: equipment.php:399 equipment_js.php:247 fuse.php:176 index.php:322
#: inventory.php:284 maintenance.php:197 maintenance.php:318
#: measurement.php:235 projects.php:219 projects.php:264 projects.php:351
#: provisions.php:219 storage.php:143 storage.php:182 tag.php:136 task.php:256
@@ -77,7 +77,7 @@ msgid "Add Box"
msgstr "Kiste hinzufügen"
#: box.php:138 box.php:184 box.php:311 inventory.php:208 inventory.php:313
#: inventory.php:320 inventory.php:374 inventory.php:424 provisions.php:260
#: inventory.php:320 inventory.php:374 inventory.php:427 provisions.php:260
#: provisions.php:290 provisions.php:344 storage.php:12 storage.php:225
#: globals.inc:564 globals.inc:1532
msgid "Storage"
@@ -87,7 +87,7 @@ msgstr "Stauraum"
#: company.php:206 company.php:295 company.php:420 documents.php:430
#: documents.php:538 documents.php:778 equipment.php:360 equipment.php:431
#: equipment.php:469 equipment.php:684 equipment_js.php:69 fuse.php:237
#: index.php:445 index.php:555 inventory.php:385 inventory.php:452
#: index.php:449 index.php:559 inventory.php:388 inventory.php:455
#: maintenance.php:180 maintenance.php:280 maintenance.php:350 projects.php:176
#: projects.php:200 projects.php:290 projects.php:317 projects.php:403
#: provisions.php:291 provisions.php:347 storage.php:122 storage.php:234
@@ -97,7 +97,7 @@ msgstr "Bemerkungen"
#: box.php:156 company.php:255 company.php:427 documents.php:455
#: documents.php:781 equipment.php:435 equipment.php:691 equipment_js.php:116
#: equipment_js.php:283 index.php:449 inventory.php:324 maintenance.php:238
#: equipment_js.php:283 index.php:453 inventory.php:324 maintenance.php:238
#: measurement.php:284 note.php:158 projects.php:293 projects.php:406
#: provisions.php:262 settings.php:146 storage.php:208 storage.php:363
#: task.php:286 globals.inc:1112 globals.inc:1119
@@ -107,7 +107,7 @@ msgstr "Speichern"
#: box.php:157 company.php:256 company.php:428 company.php:454
#: documents.php:456 documents.php:782 dropdown.php:214 dropdown.php:281
#: equipment.php:436 equipment.php:692 equipment_js.php:117
#: equipment_js.php:251 equipment_js.php:284 index.php:450 inventory.php:325
#: equipment_js.php:251 equipment_js.php:284 index.php:454 inventory.php:325
#: maintenance.php:239 measurement.php:285 note.php:159 projects.php:294
#: projects.php:407 provisions.php:263 provisions.php:385 search.php:36
#: settings.php:147 storage.php:209 storage.php:364 task.php:287
@@ -139,12 +139,12 @@ msgstr "Keine verknüpften Bilder"
msgid "Inventory in box"
msgstr "Inventar in der Kiste"
#: box.php:246 index.php:375 inventory.php:12 search.php:80 globals.inc:1500
#: box.php:246 index.php:379 inventory.php:12 search.php:80 globals.inc:1500
msgid "Inventory"
msgstr "Inventar"
#: box.php:247 dropdown.php:150 inventory.php:261 inventory.php:307
#: inventory.php:380 inventory.php:414 provisions.php:183 provisions.php:248
#: inventory.php:383 inventory.php:417 provisions.php:183 provisions.php:248
#: provisions.php:282 provisions.php:316
msgid "Number"
msgstr "Anzahl"
@@ -162,13 +162,13 @@ msgstr "&horbar; keine &horbar;"
msgid "Edit Box"
msgstr "Kiste bearbeiten"
#: box.php:308 equipment.php:668 inventory.php:418 provisions.php:324
#: box.php:308 equipment.php:668 inventory.php:421 provisions.php:324
msgid "Weight, kg"
msgstr "Gewicht, kg"
#: box.php:327 cable.php:298 checklist.php:170 company.php:465
#: documents.php:815 dropdown.php:287 equipment.php:718 equipment_js.php:293
#: fuse.php:290 index.php:566 inventory.php:480 maintenance.php:371
#: fuse.php:290 index.php:570 inventory.php:483 maintenance.php:371
#: measurement.php:369 note.php:226 projects.php:419 provisions.php:391
#: settings.php:153 storage.php:390 tag.php:294 task.php:415
msgid "Unknown function call: Please report to system development!"
@@ -222,8 +222,8 @@ msgstr "Kabel"
#: cable.php:127 cable.php:169 cable.php:235 checklist.php:153 company.php:204
#: company.php:251 company.php:274 company.php:355 dropdown.php:185
#: dropdown.php:233 equipment.php:419 equipment.php:651 equipment_js.php:101
#: equipment_js.php:268 index.php:437 inventory.php:259 inventory.php:303
#: inventory.php:410 measurement.php:256 measurement.php:303
#: equipment_js.php:268 index.php:441 inventory.php:259 inventory.php:303
#: inventory.php:413 measurement.php:256 measurement.php:303
#: measurement.php:333 projects.php:169 projects.php:196 projects.php:245
#: projects.php:283 projects.php:310 projects.php:377 provisions.php:244
#: provisions.php:312 storage.php:120 storage.php:202 storage.php:316
@@ -244,7 +244,7 @@ msgid "Cross section"
msgstr "Querschnitt"
#: cable.php:132 cable.php:213 cable.php:266 inventory.php:222
#: inventory.php:262 inventory.php:384 inventory.php:436
#: inventory.php:262 inventory.php:387 inventory.php:439
msgid "Condition"
msgstr "Zustand"
@@ -429,7 +429,7 @@ msgstr "Stadt"
msgid "Delete Company"
msgstr "Firma löschen"
#: company.php:441 documents.php:802 equipment.php:707 inventory.php:470
#: company.php:441 documents.php:802 equipment.php:707 inventory.php:473
#: note.php:217 storage.php:379
#, php-format
msgid "Record no. %d"
@@ -455,7 +455,7 @@ msgstr "Neuer GD Imagestream kann nicht initialisiert werden"
msgid "Resource not found!"
msgstr "Resource nicht gefunden!"
#: documents.php:21 index.php:381 task.php:343 globals.inc:1516
#: documents.php:21 index.php:385 task.php:343 globals.inc:1516
msgid "Documents"
msgstr "Dokumente"
@@ -515,7 +515,7 @@ msgstr "SQL-Fehler: %s"
msgid "Removed document reference"
msgstr "Dokumentreferenz wurde entfernt"
#: documents.php:345 note.php:103
#: documents.php:345 note.php:103 note.php:178
msgid "Reference"
msgstr "Referenz"
@@ -537,7 +537,7 @@ msgstr "Dokument"
#: documents.php:437
msgid "Clear selection"
msgstr "Auswah leeren"
msgstr "Auswahl leeren"
#: documents.php:473
msgid "View Document"
@@ -597,14 +597,14 @@ msgid "Add new reference"
msgstr "Neue Referenz hinzufügen"
#: documents.php:654 equipment.php:13 equipment_js.php:14 fuse.php:236
#: index.php:359 inventory.php:450 maintenance.php:95 maintenance.php:179
#: index.php:363 inventory.php:453 maintenance.php:95 maintenance.php:179
#: maintenance.php:225 maintenance.php:274 maintenance.php:348
#: measurement.php:281 measurement.php:312 measurement.php:358 search.php:52
#: globals.inc:532 globals.inc:1496
msgid "Equipment"
msgstr "Ausrüstung"
#: documents.php:700 inventory.php:314 inventory.php:321 inventory.php:425
#: documents.php:700 inventory.php:314 inventory.php:321 inventory.php:428
#: storage.php:155 globals.inc:522
msgid "Box"
msgstr "Kiste"
@@ -750,7 +750,7 @@ msgstr ""
#: equipment.php:357 equipment.php:427 equipment.php:462 equipment.php:660
#: equipment_js.php:67 equipment_js.php:109 equipment_js.php:138
#: equipment_js.php:276 index.php:441 index.php:505
#: equipment_js.php:276 index.php:445 index.php:509
msgid "Model"
msgstr "Modell"
@@ -785,11 +785,11 @@ msgstr "Entfernt"
msgid "Add Equipment"
msgstr "Ausrüstung hinzufügen"
#: equipment.php:465 inventory.php:382
#: equipment.php:465 inventory.php:385
msgid "Price"
msgstr "Preis"
#: equipment.php:466 equipment.php:676 inventory.php:383 inventory.php:432
#: equipment.php:466 equipment.php:676 inventory.php:386 inventory.php:435
msgid "Purchase date"
msgstr "Kaufdatum"
@@ -841,7 +841,7 @@ msgstr "Keine Ersatzteile gefunden."
msgid "Edit Equipment"
msgstr "Ausrüstung bearbeiten"
#: equipment.php:672 inventory.php:428
#: equipment.php:672 inventory.php:431
#, php-format
msgid "Price, %s"
msgstr "Preis, %s"
@@ -968,99 +968,99 @@ msgstr "Eigenbau"
msgid "Built by %s"
msgstr "Gebaut von %s"
#: index.php:303
#: index.php:307
msgid "Boat data"
msgstr "Bootsdaten"
#: index.php:305
#: index.php:309
msgid "Loa"
msgstr "LüA"
#: index.php:306
#: index.php:310
msgid "Lwl"
msgstr "LWL"
#: index.php:307 index.php:517
#: index.php:311 index.php:521
msgid "Beam"
msgstr "Breite"
#: index.php:308 index.php:521
#: index.php:312 index.php:525
msgid "Draught"
msgstr "Tiefgang"
#: index.php:309 index.php:525
#: index.php:313 index.php:529
msgid "Draught, min."
msgstr "Tiefgang, min."
#: index.php:310 index.php:529
#: index.php:314 index.php:533
msgid "Displacement"
msgstr "Verdrängung"
#: index.php:311 index.php:533
#: index.php:315 index.php:537
msgid "Ballast"
msgstr "Ballast"
#: index.php:312 index.php:537
#: index.php:316 index.php:541
msgid "Belt position aft"
msgstr "Gurtposition achtern"
#: index.php:313 index.php:541
#: index.php:317 index.php:545
msgid "Belt position bow"
msgstr "Gurtposition vorne"
#: index.php:329
#: index.php:333
msgid "Statistics"
msgstr "Statistik"
#: index.php:345
#: index.php:349
msgid "Storage type"
msgstr "Stauraumart"
#: index.php:360 index.php:376 index.php:382
#: index.php:364 index.php:380 index.php:386
msgid "Count"
msgstr "Anzahl"
#: index.php:397
#: index.php:401
msgid "Base data"
msgstr "Stammdaten"
#: index.php:409
#: index.php:413
msgid "Additional modules"
msgstr "Zusatzmodule"
#: index.php:432
#: index.php:436
msgid "Add additional yacht"
msgstr "Zusätzliche Yacht hinzufügen"
#: index.php:481
#: index.php:485
msgid "&horbar; Custom-built &horbar;"
msgstr "&horbar; Eigenbau &horbar;"
#: index.php:497
#: index.php:501
msgid "Edit Yacht"
msgstr "Yacht bearbeiten"
#: index.php:501
#: index.php:505
msgid "Vesselname"
msgstr "Yachtname"
#: index.php:509
#: index.php:513
msgid "LoA"
msgstr "LüA"
#: index.php:513
#: index.php:517
msgid "LwL"
msgstr "LWL"
#: index.php:549
#: index.php:553
msgid "Shape file"
msgstr "Rumpfform-Datei"
#: index.php:550
#: index.php:554
msgid "Shipyard"
msgstr "Werft"
#: index.php:551
#: index.php:555
msgid "Default Storage"
msgstr "Standard-Stauraum"
@@ -1085,7 +1085,7 @@ msgstr "Keine Tags zugeordnet"
msgid "Add Inventory"
msgstr "Inventar hinzufügen"
#: inventory.php:311 inventory.php:423
#: inventory.php:311 inventory.php:426
msgid "Container type"
msgstr "Behälterart"
@@ -1094,19 +1094,23 @@ msgstr "Behälterart"
msgid "%s in %s"
msgstr ""
#: inventory.php:379
#: inventory.php:378
msgid "View Box"
msgstr "Box ansehen"
#: inventory.php:382
msgid "Location ID"
msgstr "Position (x, y, z)"
#: inventory.php:406
#: inventory.php:409
msgid "Edit Inventory"
msgstr "Inventar bearbeiten"
#: inventory.php:469
#: inventory.php:472
msgid "Delete Inventory"
msgstr "Inventar löschen"
#: inventory.php:473
#: inventory.php:476
msgid ""
"Deleting an inventory item is final. There is no way back. Only delete if "
"you are absolute sure."
@@ -1323,10 +1327,6 @@ msgstr "Notiz"
msgid "View Note"
msgstr "Notiz ansehen"
#: note.php:178
msgid "Referenz"
msgstr ""
#: note.php:196
msgid "Edit Note"
msgstr "Notiz bearbeiten"
Binary file not shown.
+2
View File
@@ -75,6 +75,8 @@ Es wird das Einlagerungsdatum erfaßt und das Haltbarkeitsdatum</p>
oder aber <em>frei</em>.
Freier Proviant kann später zugeordnet
werden. Er ist erkennbar am Einkaufswagen-Symbol <i class="bi bi-cart3"></i>.<p>
<p>Proviant wird mit Brutto- und Nettogewicht geführt, hat einen Energiegehalt in kCal
und ggf. einen Preis</p>
<p>TODO/Noch nichtimplementiert: Proviant kann auch in eine Kiste gelegt werden</p>
+1 -1
View File
@@ -175,7 +175,7 @@ $note = $sth->fetch(PDO::FETCH_OBJ);
// edit button
// list where annotaion used
echo '<table class="table">', "\n";
echo '<tr><th>', _('Referenz'),"</th><td>", $note->refid, "</td></tr>\n";
echo '<tr><th>', _('Reference'),"</th><td>", $note->refid, "</td></tr>\n";
echo '<tr><th>', _('Type'),"</th><td>", $note->notetype, "</td></tr>\n";
echo '<tr><th>', _('Annotation'),"</th><td>", nl2br($note->annotation), "</td></tr>\n";
echo "</table>\n";
+16 -2
View File
@@ -61,8 +61,10 @@ switch ($submit = form_get_action()) {
$p[':weight'] = min(gpc_get_float($_POST, 'weight'), 999.99); // limit max value
$p[':weight_net'] = min(gpc_get_float($_POST, 'weight_net'), 999.99);
$p[':unit'] = gpc_get_int($_POST, 'unit');
$p[':calories'] = gpc_get_uint($_POST, 'calories');
$p[':storedate'] = gpc_get_string($_POST, 'storedate');
$p[':shelflife'] = gpc_get_string($_POST, 'shelflife');
$p[':price'] = gpc_get_currency($_POST, 'price');
$p[':sid'] = gpc_get_int($_POST, 'sid');
if ($p[':sid'] <= 0) $p[':sid'] = NULL;
$p[':remarks'] = gpc_get_string($_POST, 'remarks', 150);
@@ -269,7 +271,7 @@ elseif ($action == ACT_VIEW):
// ========== VARIANT: view single record =====================================
$sql = "SELECT provname, provcat, number, target, unit, weight, weight_net,"
. " storedate, shelflife, sid, remarks "
. " calories, storedate, shelflife, price, sid, remarks "
. "FROM provisions "
. "WHERE provid=?";
$sth = $pdo->prepare($sql);
@@ -284,9 +286,11 @@ echo "<tr><th>", _('Target'), "</th><td>", $prov->target, "</td></tr>\n";
echo "<tr><th>", _('Weight'), "</th><td>", format_float($prov->weight, 2, 'kg'), "</td></tr>\n";
echo "<tr><th>", _('Weight net'), "</th><td>", format_float($prov->weight_net, 2, 'kg'), "</td></tr>\n";
echo '<tr><th>', _('Unit'),"</th><td>", $opt_unit[$prov->unit], "</td></tr>\n";
echo "<tr><th>", _('Calories'), "</th><td>", $prov->calories, " kcal</td></tr>\n";
echo '<tr><th>', _('Category'),"</th><td>", $opt_category[$prov->provcat] ?? 'n/a', "</td></tr>\n";
echo '<tr><th>', _('Storedate'),"</th><td>", $prov->storedate, "</td></tr>\n";
echo '<tr><th>', _('Shelflife'),"</th><td>", $prov->shelflife, "</td></tr>\n";
echo '<tr><th>', _('Price'), "</th><td>", format_currency($prov->price), "</td></tr>\n";
echo '<tr><th>', _('Storage'),"</th><td>", ($opt_storage[$prov->sid] ?? ''), "</td></tr>\n";
echo '<tr><th>', _('Remarks'),"</th><td>", nl2br($prov->remarks), "</td></tr>\n";
echo "</table>\n";
@@ -297,7 +301,7 @@ elseif ($action == ACT_EDIT):
// ========== VARIANT: edit single record =====================================
$sql = "SELECT provname, provcat, number, target, unit, weight, weight_net,"
. " storedate, shelflife, sid, remarks "
. " calories, storedate, shelflife, price, sid, remarks "
. "FROM provisions "
. "WHERE provid=?";
$sth = $pdo->prepare($sql);
@@ -330,6 +334,12 @@ echo '<h2>', _('Edit provision'), "</h2>\n";
</div>
<?php
form_create_select('unit', _('Unit'), $opt_unit, $prov->unit);
?>
<div class="mb-3">
<label for="target"><?=_('Calories per Unit (kcal)')?></label>
<input type="number" min="0" max="65000" class="form-control" id="calories" name="calories" value="<?=$prov->calories ?>">
</div>
<?php
form_create_select('provcat', _('Category'), $g_opt_none + $opt_category, $prov->provcat);
?>
<div class="mb-3">
@@ -340,6 +350,10 @@ form_create_select('provcat', _('Category'), $g_opt_none + $opt_category, $prov-
<label for="shelflife"><?=_('Shelflife')?></label>
<input type="date" class="form-control" id="shelflife" name="shelflife" value="<?=$prov->shelflife ?>">
</div>
<div class="mb-3">
<label for="price" class="form-label"><?=sprintf(_('Price, %s'), $g_lconv['currency_symbol']); ?></label>
<input type="text" class="form-control" id="price" name="price" value="<?=format_float($prov->price) ?>">
</div>
<?php
form_create_select('sid', _('Storage'), $g_opt_none + $opt_storage, $prov->sid);
?>
+1 -1
View File
@@ -16,7 +16,7 @@
style="display:inline"><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00001;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:8, 4, 2.00001, 4;stroke-dashoffset:0;stroke-opacity:1"
d="M 42.346999,421.58603 H 1066.347"
id="path399" /><path
id="centerline" /><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 43.434741,421.43502 c 0,0 0.413166,-22.42089 3.894359,-39.92496 8.859879,-44.54909 33.485118,-67.94835 93.87163,-87.80423 71.79884,-23.60841 231.35367,-47.44953 338.07331,-45.17591 58.4656,1.24559 128.71431,3.06837 239.1874,30.59247 125.0131,31.14672 271.98631,88.99119 345.78696,137.98517 0,0 0.7865,1.03131 0.972,1.63871 0.3052,0.99905 0.1801,3.12866 0.1801,3.12866"
id="path1004" /><use

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+138
View File
@@ -0,0 +1,138 @@
<svg
width="1024"
height="325"
viewBox="0 0 1024 325"
version="1.1"
id="svg5"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
id="title134">Colin Archer 40</title><defs
id="defs2" /><g
id="layer1"
style="display:inline"><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 32.902587,163.24723 c 0,0 -6.666293,-22.17724 51.732501,-67.565006 C 187.52515,15.715671 366.50013,5.7999235 433.65337,3.9666602 542.97458,0.98222366 704.13522,4.0275731 830.22893,58.523616 908.62334,92.40465 969.7182,149.13425 978.673,155.26415 c 5.4153,3.70695 4.5761,8.14666 4.5761,8.14666"
id="path951" /><use
x="0"
y="0"
xlink:href="#path951"
id="use1049"
transform="matrix(1,0,0,-1,-0.05781152,326.36779)" /><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 895.985,92.86984 V 232.55335"
id="path1051" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 689.37014,17.637239 V 195.72401"
id="path1051-36" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 540.10735,3.0789488 V 91.21857"
id="path1051-36-7" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 690.02348,237.42732 V 307.8727"
id="path1051-36-7-2" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 416.49715,4.2620683 V 132.78882"
id="path1051-36-5" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 310.10943,14.105407 V 136.19705"
id="path1051-36-3" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 113.52266,76.302173 V 251.83248"
id="path1051-36-56" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 690.08485,105.08884 28.07694,0.60163"
id="path1051-36-7-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 786.68796,42.130597 0.25367,46.194127 -16.3841,16.832756 -12.41255,0.48582"
id="path1051-36-7-1" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 502.13905,196.92602 6.32096,0.0627 31.73427,26.11546 -0.69919,99.59481"
id="path1051-36-7-27" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 417.82431,321.48565 0.004,-123.99946"
id="path1051-36-7-0" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 308.66858,312.05474 0.81755,-70.25663"
id="path1051-36-7-0-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 310.24697,200.53963 0.007,-13.8616"
id="path1051-36-7-0-6-0" /><rect
style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
id="rect625"
width="152.16988"
height="11.532784"
x="866.78735"
y="157.73856"
rx="1.3628241"
ry="1.3045897" /><rect
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="rect640"
width="43.70451"
height="5.1411266"
x="2.7056627"
y="160.75401"
rx="1.3628241"
ry="1.3045897" /><ellipse
style="fill:#ffffff;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="path1248"
cx="657.82275"
cy="163.56593"
rx="12.162697"
ry="7.8855205" /><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:8, 4, 2, 4;stroke-dashoffset:0;stroke-opacity:1"
d="M 3.2714902e-6,163.30965 1024,163.3063"
id="path271" /></g><g
id="layer2"
style="display:inline"
transform="translate(-25.319896,5.562848)"><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 565.67989,236.15227 148.94077,0.58894"
id="path1051-3" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 567.16163,86.445447 148.94077,0.58894"
id="path1051-3-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 811.98823,83.068113 57.1816,30.733537 48.6489,-26.735129"
id="path1051-3-2" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 854.02272,105.74204 876.63281,63.267145"
id="path1051-3-2-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 715.48091,236.62096 39.15964,0.46197 -0.0963,57.26552"
id="path1051-3-2-6-8" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 831.33681,93.198753 854.85906,52.701572"
id="path1051-3-2-1" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 334.88168,257.06299 108.33469,-0.0336"
id="path1051-3-7" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 335.54514,66.093576 106.51923,-0.0336"
id="path1051-3-7-23" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 304.78037,14.051838 0.25341,67.943863 30.47429,-0.462178"
id="path1051-3-7-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 248.70861,289.07127 14.00732,-51.92697 71.96665,5.09935"
id="path1051-3-7-2" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 298.75245,239.75719 -7.2046,59.2492"
id="path1051-3-7-0" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:0.99999899,1.99999797;stroke-opacity:1;stroke-dashoffset:0"
d="m 444.22245,247.11088 63.11081,0.2124 13.52892,-40.80411 0.425,-100.58449 -13.78927,-39.435166 -65.58989,0.129781"
id="path1051-3-7-7" /><rect
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1, 2;stroke-dashoffset:0"
id="rect1606"
width="74.914055"
height="69.12413"
x="570.22064"
y="122.47643"
rx="1.3628241"
ry="1.3045897" /></g><metadata
id="metadata132"><rdf:RDF><cc:Work
rdf:about=""><dc:title>Colin Archer 40</dc:title><dc:creator><cc:Agent><dc:title>Thomas Hooge</dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata></svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

+1 -1
View File
@@ -30,7 +30,7 @@
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.268;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.072, 0.536, 0.268, 0.536;stroke-dashoffset:0;stroke-opacity:1"
d="m 297.50974,51.957452 c -2.54837,0.522337 -297.50950858727,0 -297.50950858727,0"
id="path857" />
id="centerline" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;line-height:98%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

+1 -1
View File
@@ -28,7 +28,7 @@
transform="matrix(1,0,0,-1,-0.02583644,109.41506)" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.529169;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:2.11667, 1.05833, 0.529169, 1.05833;stroke-dashoffset:0;stroke-opacity:1"
d="M 0.21617824,54.888403 270.69991,54.888402"
id="path271" /><path
id="centerline" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.529168;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 141.00077,5.7751708 V 53.289576"
id="path1051-5-20-3-5-3" /><path

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

+289
View File
@@ -0,0 +1,289 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024"
height="305"
viewBox="0 0 1024 305"
version="1.1"
id="svg5"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
id="title134">Gulfstar 41</title><defs
id="defs2" /><g
id="layer1"
style="display:inline"><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:8, 4, 2, 4;stroke-dashoffset:0;stroke-opacity:1"
d="M 3.3e-6,152.00334 1023.9982,151.99999"
id="path271" /><path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 4.541505,152.82638 c 0,0 0.729165,-54.491109 25.243635,-88.018502 C 94.775098,50.967238 140.69217,39.927662 199.55731,31.275058 297.4149,16.890941 397.62677,5.6293124 462.03098,4.859982 568.15897,3.5922467 688.37772,16.318765 809.42487,53.979712 912.75153,86.127351 1008.139,138.58129 1017.0525,144.71119 c 5.3903,3.70695 4.555,8.14666 4.555,8.14666"
id="path951" /><use
x="0"
y="0"
xlink:href="#path951"
id="use1049"
transform="matrix(1,0,0,-1,0.14093269,304.94033)" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 296.62649,18.557857 V 286.46486"
id="path1051-5" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 415.8873,227.21891 v 71.23063"
id="path1051-5-20" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 296.3826,153.91187 75.74474,30.18682 72.23129,0.21079 -0.0479,-57.74576 -27.76098,-0.14802 -0.69918,-119.2298423"
id="path1051-5-0" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 671.07416,20.286177 0.0415,128.557953 56.89431,-0.10936 29.62768,-23.18612 -0.78991,-85.78406"
id="path1051-5-2-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 220.55901,28.151512 v 92.906228 l 17.65895,30.72635 58.41003,0.0579"
id="path1051-7" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 296.53346,226.79659 -75.83471,-11.69391 -0.24255,61.15704"
id="path1051-35" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 914.04719,93.338688 V 212.03375"
id="path1051" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 755.60805,266.13836 -0.2572,-69.86532 -84.93468,-0.33711 -0.018,87.43233"
id="path1051-5-2" /><ellipse
style="display:inline;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0"
id="path1640"
cx="594.63507"
cy="152.02142"
rx="8.7870979"
ry="4.8806257" /></g><g
id="layer2"
style="display:inline"><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:1.99999, 0.999997;stroke-dashoffset:0;stroke-opacity:1"
d="m 756.00672,238.37946 c 0,0 59.16733,-20.02492 85.38293,-30.44142 26.2156,-10.41649 71.66524,-31.86272 71.66524,-31.86272"
id="path1051-3" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 669.46008,195.69652 508.5147,213.65213"
id="path1051-3-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 822.22439,152.3697 758.17324,125.24424"
id="path1051-3-9-62" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 822.09326,152.28781 -66.36093,29.18905 -0.0239,14.78286"
id="path1051-3-9-62-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 508.55542,213.45397 0.0642,86.3025"
id="path1051-3-9-97" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 670.76127,237.09813 508.69631,255.16318"
id="path1051-3-9-75" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 670.66554,254.20123 508.64908,272.20918"
id="path1051-3-9-92" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 661.4627,142.51176 -0.7724,-79.741048 c 0,0 -37.56032,-7.039363 -56.42139,-6.896387 -18.86107,0.142976 -55.78157,7.771263 -55.78157,7.771263 l -0.14541,79.791092"
id="path1051-3-9-2" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 415.442,253.04762 295.11292,242.76125"
id="path1051-3-9-7" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 460.1918,231.34815 -8.69345,-0.0532"
id="path1051-3-9-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 416.39973,52.794624 297.21078,62.947673"
id="path1051-3-9-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 296.55075,62.8079 -76.00147,0.07026"
id="path1051-3-9-6-7" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 271.79139,151.82674 271.63843,63.050532"
id="path1051-3-9-6-7-0" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 271.73415,111.91673 -51.28978,0.0489"
id="path1051-3-9-6-7-0-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 671.34001,43.684548 541.56327,28.616168"
id="path1051-3-9-6-1" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 670.65715,142.55208 -39.25248,0.44377 -1.31417,-55.619742 -48.91307,-0.79758 v 57.313592 l -40.07981,0.0484 L 540.4774,7.3776096"
id="path1051-3-9-6-1-8" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:2, 0.999999;stroke-dashoffset:0;stroke-opacity:1"
d="m 416.30812,74.92521 c 0,0 -82.47253,15.144404 -122.81009,25.9362 -40.33756,10.7918 -119.21527,38.81458 -119.21527,38.81458 l -0.48224,29.55534 c 0,0 80.54391,27.12128 120.73218,37.21131 40.18827,10.09004 121.27862,23.76758 121.27862,23.76758"
id="path1051-3-7" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 219.67776,109.0587 93.855492,151.80189"
id="path1051-3-7-3" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 219.86139,195.03714 94.039117,152.29395"
id="path1051-3-7-3-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 220.0363,213.44097 219.71324,194.9243"
id="path1051-3-7-3-6-6" /><use
x="0"
y="0"
xlink:href="#path1051-6"
id="use457"
transform="matrix(1,0,0,-1,-0.13850787,304.9487)"
style="display:inline" /><rect
style="display:inline;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="rect1255"
width="28.664709"
height="25.4055"
x="444.12122"
y="150.14844" /><rect
style="display:inline;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="rect1255-1"
width="48.177208"
height="62.083755"
x="460.24216"
y="224.74203" /><rect
style="display:inline;fill:none;stroke:#000000;stroke-width:0.999998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="rect1255-1-2"
width="35.377628"
height="52.031517"
x="416.01917"
y="228.25325" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 220.17782,38.804009 c 0,0 -56.08046,11.503545 -98.40722,29.919685 C 79.443836,87.139833 58.501345,112.47149 58.501345,112.47149 l -0.185798,40.1905"
id="path1051-6" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 671.50207,60.229759 84.47664,0.731761"
id="path1051-3-70" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 728.31329,60.955674 0.21143,87.895066"
id="path1051-3-70-9" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 671.31947,114.80366 57.0344,0.0153"
id="path1051-3-70-9-5" /><rect
style="display:inline;fill:none;stroke:#000000;stroke-width:0.999995;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="rect1255-3"
width="28.496277"
height="25.182354"
x="506.32599"
y="47.019566" /><rect
style="display:inline;fill:none;stroke:#000000;stroke-width:0.999995;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="rect1255-3-6"
width="40.192474"
height="30.994183"
x="453.94885"
y="42.663792" /><circle
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0"
id="path1537"
cx="473.59415"
cy="50.722347"
r="4.1049838" /><circle
style="display:inline;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0"
id="path1537-2"
cx="484.03549"
cy="64.60498"
r="4.1049838" /><circle
style="display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0"
id="path1537-2-3"
cx="511.54593"
cy="59.438629"
r="1.4208409" /><circle
style="display:inline;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0"
id="path1537-6"
cx="463.9982"
cy="64.60498"
r="4.1049838" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 670.94118,54.557571 c 0,0 -43.64416,-7.676983 -65.60103,-7.709096 -21.95686,-0.03211 -65.08605,7.813561 -65.08605,7.813561"
id="path1051-3-9-3" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 670.27867,245.85044 508.57702,264.03781"
id="path1051-3-9-28" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 540.61416,41.075906 500.33278,41.27855 V 14.68724 h -51.75124 v 54.26063 l -32.33807,0.248933"
id="path1051-3-9-36" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 541.01887,131.38337 -38.36889,-0.14689 0.35135,-55.159487 -58.43273,0.0059 0.12857,50.081065"
id="path1051-3-9-1" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 444.57231,76.176669 -0.002,-7.190214"
id="path1051-3-9-29" /><rect
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2020"
width="22.906216"
height="18.748451"
x="507.5379"
y="82.955887"
ry="1.3045897"
rx="1.3628241" /><rect
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2020-1"
width="22.906216"
height="18.748451"
x="507.51715"
y="106.56944"
ry="1.3045897"
rx="1.3628241" /><rect
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2020-1-3"
width="12.380301"
height="18.748451"
x="276.67719"
y="118.84782"
ry="1.3045897"
rx="1.3628241" /><rect
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2020-1-3-0"
width="12.380301"
height="18.748451"
x="736.40729"
y="103.76661"
ry="1.3045897"
rx="1.3628241" /><g
id="g1346"
transform="translate(1.6711347,1.1925367)"><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 711.61753,105.88116 2.65465,-0.01"
id="path1051-3-70-9-6" /><rect
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2591-3"
width="8.0729084"
height="10.706293"
x="714.31329"
y="101.32455"
ry="0" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 716.92538,107.04318 2.79647,-0.003"
id="path1051-3-70-9-6-7" /><path
id="rect2588"
style="fill:#ffffff;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;fill-opacity:1"
d="m 686.24942,90.899539 20.82037,0.08492 c 0,0 5.06651,8.402799 4.39879,16.424111 -0.93076,11.18123 -8.47355,16.69152 -14.7092,16.79516 -6.23564,0.1036 -13.73785,-6.42007 -14.55567,-15.91907 -0.83507,-9.699525 4.04571,-17.385082 4.04571,-17.385082 z" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2591"
width="22.45204"
height="2.0430894"
x="685.52374"
y="88.79052" /></g><g
id="g1346-5"
transform="translate(-452.44821,-6.2064984)"
style="display:inline"><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 711.61753,105.88116 2.65465,-0.01"
id="path1051-3-70-9-6-6" /><rect
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2591-3-2"
width="8.0729084"
height="10.706293"
x="714.31329"
y="101.32455"
ry="0" /><path
style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 716.92538,107.04318 2.79647,-0.003"
id="path1051-3-70-9-6-7-9" /><path
id="rect2588-1"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round"
d="m 686.24942,90.899539 20.82037,0.08492 c 0,0 5.06651,8.402799 4.39879,16.424111 -0.93076,11.18123 -8.47355,16.69152 -14.7092,16.79516 -6.23564,0.1036 -13.73785,-6.42007 -14.55567,-15.91907 -0.83507,-9.699525 4.04571,-17.385082 4.04571,-17.385082 z" /><rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect2591-2"
width="22.45204"
height="2.0430894"
x="685.52374"
y="88.79052" /></g></g><metadata
id="metadata132"><rdf:RDF><cc:Work
rdf:about=""><dc:title>Shape template</dc:title><dc:creator><cc:Agent><dc:title>Thomas Hooge</dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata></svg>

After

Width:  |  Height:  |  Size: 20 KiB

+11 -7
View File
@@ -13,7 +13,7 @@ $pagetitle = _('Task');
$id = gpc_get_int($_REQUEST, 'id', 0);
$opt_projects = db_get_opt_proj($user->vid, array(-1 => _('unassigned')));
$opt_projects = db_get_opt_proj($user->vid, [], array(-1 => _('unassigned')));
$opt_priority = db_load_enum('task', 'priority', true, true);
$opt_state = db_load_enum('task', 'taskstate', true, true);
@@ -30,6 +30,7 @@ switch ($submit = form_get_action()) {
case 'filter':
$flt = array();
$flt['proj'] = gpc_get_int($_POST, 'flt_proj');
$flt['prio'] = gpc_get_enum($_POST, 'flt_prio', array_merge(array_keys($opt_priority), ['-2', '-1']));
$flt['txt'] = gpc_get_string($_POST, 'flt_txt');
$allowed = db_load_enum('task', 'taskstate');
@@ -125,7 +126,7 @@ if ($action == ACT_DEFAULT):
page_caption_search($pagetitle);
// load filter from db
$flt = db_get_filter($user->id, 206, ['txt','prio', 'stat']);
$flt = db_get_filter($user->id, 206, ['txt', 'prio', 'stat', 'proj']);
$w = array('(t.vid=:vid OR t.vid IS NULL)');
$p = array(':vid' => $user->vid);
@@ -133,16 +134,18 @@ if (strlen($flt->txt) > 1) {
$w[] = '(taskname LIKE :txt)';
$p[':txt'] = '%'.$flt->txt.'%';
}
if ($flt->proj > 0) {
$w[] = 'projid=:projid';
$p[':projid'] = $flt->proj;
} elseif ($flt->proj == -1) {
$w[] = 'projid IS NULL';
}
if (strlen($flt->prio) > 2 ) {
$w[] = 'priority=:priority';
$p[':priority'] = $flt->prio;
} elseif ($flt->prio == -1) {
$w[] = 'priority IS NULL';
}
/*if (strlen($flt->stat) > 2 ) {
$w[] = 'taskstate=:taskstate';
$p[':taskstate'] = $flt->stat;
} */
$parts = ["taskstate=''", 'taskstate IS NULL'];
foreach ($flt->stat as $f) {
$parts[] = "FIND_IN_SET('$f', taskstate)";
@@ -202,8 +205,9 @@ $res = $sth->fetchAll();
<div class="card-header"><i class="bi bi-funnel me-2"></i>Filter</div>
<div class="card-body d-flex flex-wrap gap-3">
<?php
$opt_proj_filter = db_get_opt_proj($user->vid, ['finished']);
filter_create_select('flt_proj', _('Project'), $g_opt_all + $g_opt_none + $opt_proj_filter, $flt->proj);
filter_create_select('flt_prio', _('Priority'), $g_opt_all + $g_opt_none + $opt_priority, $flt->prio);
//filter_create_select('flt_stat', _('State'), $g_opt_all + $opt_state, $flt->stat);
?>
<div class="d-flex flex-nowrap gap-2">
<label for="flt_txt">Text</label>
+37 -34
View File
@@ -163,26 +163,25 @@
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkListBox" id="lvw_user">
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="tvw_user">
<property name="visible">True</property>
<property name="can-focus">True</property>
<signal name="row-activated" handler="on_tvw_user_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -191,11 +190,12 @@
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton">
<object class="GtkButton" id="but_user_new">
<property name="label" translatable="yes">New</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_but_user_new_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@@ -204,11 +204,12 @@
</packing>
</child>
<child>
<object class="GtkButton">
<object class="GtkButton" id="but_user_del">
<property name="label" translatable="yes">Delete</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_but_user_del_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@@ -220,7 +221,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -262,7 +263,7 @@
</packing>
</child>
<child>
<object class="GtkEntry">
<object class="GtkEntry" id="ent_user_login">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
@@ -272,7 +273,7 @@
</packing>
</child>
<child>
<object class="GtkEntry">
<object class="GtkEntry" id="ent_user_displayname">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
@@ -304,7 +305,7 @@
</packing>
</child>
<child>
<object class="GtkComboBox">
<object class="GtkComboBox" id="cmb_user_role">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
@@ -314,7 +315,7 @@
</packing>
</child>
<child>
<object class="GtkComboBox">
<object class="GtkComboBox" id="cmb_user_lang">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
@@ -335,7 +336,7 @@
</packing>
</child>
<child>
<object class="GtkEntry">
<object class="GtkEntry" id="ent_user_pass">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
@@ -345,11 +346,12 @@
</packing>
</child>
<child>
<object class="GtkButton">
<object class="GtkButton" id="but_user_newpass">
<property name="label" translatable="yes">Create</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_but_user_newpass_clicked" swapped="no"/>
</object>
<packing>
<property name="left-attach">2</property>
@@ -357,7 +359,7 @@
</packing>
</child>
<child>
<object class="GtkCheckButton">
<object class="GtkCheckButton" id="chk_user_locked">
<property name="label" translatable="yes">Locked</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
@@ -370,7 +372,7 @@
</packing>
</child>
<child>
<object class="GtkCheckButton">
<object class="GtkCheckButton" id="chk_user_deleted">
<property name="label" translatable="yes">Deleted</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
@@ -446,11 +448,12 @@
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton">
<object class="GtkButton" id="but_user_save">
<property name="label" translatable="yes">Save</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<signal name="clicked" handler="on_but_user_save_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@@ -480,7 +483,7 @@
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Lists</property>
<property name="label" translatable="yes">User</property>
</object>
<packing>
<property name="position">2</property>
@@ -560,7 +563,7 @@
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">User</property>
<property name="label" translatable="yes">Lists</property>
</object>
<packing>
<property name="position">2</property>
@@ -846,7 +849,7 @@
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView">
<object class="GtkTreeView" id="tvw_equipment">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child internal-child="selection">
@@ -1831,7 +1834,7 @@
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView">
<object class="GtkTreeView" id="tvw_provisions">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child internal-child="selection">
@@ -2040,7 +2043,7 @@
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView">
<object class="GtkTreeView" id="tvw_maintenance">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child internal-child="selection">
@@ -2270,7 +2273,7 @@
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView">
<object class="GtkTreeView" id="tvw_projects">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child internal-child="selection">
@@ -2500,7 +2503,7 @@
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView">
<object class="GtkTreeView" id="tvw_documents">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child internal-child="selection">
+78 -9
View File
@@ -1,13 +1,20 @@
# -*- coding: utf-8 -*-
import MySQLdb.cursors
import bcrypt
import random
import string
class User(object):
@classmethod
def genpass(cls, length=12):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def __init__(self, dbconn, user_id=None):
self.conn = dbconn
self.cur = dbconn.cursor(MySQLdb.cursors.DictCursor)
if user_id:
if user_id is not None:
self.user_id = int(user_id)
self.load()
else:
@@ -15,13 +22,13 @@ class User(object):
self.displayname = None
self.language = 'en'
self.role = 'undef'
self.flags = ''
self.flags = []
def load(self):
def load(self, user_id=0):
sql = (
"SELECT login, displayname, language, role, flags "
"FROM user "
"WHERE user_id=%s")
"WHERE userid=%s")
self.cur.execute(sql, (self.user_id, ))
row = self.cur.fetchone()
if row:
@@ -30,15 +37,32 @@ class User(object):
self.displayname = row['displayname']
self.language = row['language']
self.role = row['role']
self.flags = row['flags']
self.flags = row['flags'].split(',') if row['flags'] else []
else:
self.data = None
def save(self):
"""
Speichern eines Benutzers ist momentan nicht vorgesehen
"""
pass
fields = ('login', 'displayname', 'language', 'role')
sqlfield = []
values = []
for field in fields:
newdata = getattr(self, field)
if self.data[field] != newdata:
sqlfield.append(f"{field}=%s")
values.append(newdata)
self.data[field] = newdata
# flags set handled separately
newflags = ','.join(self.flags) if self.flags else None
if self.data['flags'] != newflags:
sqlfield.append(f"flags=%s")
values.append(newflags)
self.data['flags'] = newflags
if values:
# only save if changed values exist
sql = "UPDATE user SET " + ",".join(sqlfield) + " WHERE userid=%s"
values.append(self.user_id)
self.cur.execute(sql, values)
self.conn.commit()
""" unused at the moment
def mark_login(self):
@@ -52,6 +76,51 @@ class User(object):
self.conn.commit()
"""
def create(self):
"""
Creates a new user with the first free id > 0
The role is the lowest possible one
"""
sql = (
"SELECT MIN(u1.userid+1) AS newid "
"FROM user AS u1 LEFT JOIN user AS u2 ON u2.userid=u1.userid+1 "
"WHERE u1.userid>=0 AND u2.userid IS NULL")
self.cur.execute(sql)
newid = self.cur.fetchone()['newid']
self.user_id = newid
# TODO check if login is unused else create other name
# add function for that feature
self.login = f"user{newid}"
self.displayname = f"User #{newid}"
self.language = 'en'
self.role = 'sailor'
self.flags = None
sql = (
"INSERT INTO user (userid, login, pass, displayname, role, flags, language) "
"VALUES (%s,%s,%s,%s,%s,%s,%s)"
)
cryptpass = bcrypt.hashpw(self.genpass().encode(), bcrypt.gensalt())
self.cur.execute(sql, (newid, self.login, cryptpass, self.displayname, self.role, self.flags, self.language))
# create mandatory settings
sql = "INSERT INTO settings (userid, sno, valint) VALUES (%s, 1, 0)"
self.cur.execute(sql, (newid,))
self.conn.commit()
return newid
def delete(self):
sql = "DELETE FROM settings WHERE userid=%s"
self.cur.execute(sql, (self.user_id,))
sql = "DELETE FROM user WHERE userid=%s"
self.cur.execute(sql, (self.user_id,))
self.conn.commit()
def setpass(self, newpass):
sql = "UPDATE user SET pass=%s WHERE userid=%s"
cryptpass = bcrypt.hashpw(newpass.encode(), bcrypt.gensalt())
print(cryptpass, self.user_id)
self.cur.execute(sql, (cryptpass, self.user_id))
self.conn.commit()
def __repr__(self):
# Unique description
out = "User(%d)" % self.user_id
+88 -6
View File
@@ -30,7 +30,8 @@ gi.require_version('Rsvg', '2.0')
from gi.repository import GLib, Gtk, Rsvg
from yms import database
from yms import gtkutils
from yms import gtkutils, combobox, treeview
from yms import user
cfg = {
'cfgfile': '~/.config/ymsgui.conf',
@@ -69,10 +70,11 @@ class Frontend():
self.vname = "Ghost(0)"
# get list of vessels
db.cur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid")
for row in db.cur.fetchall():
print(row)
print(type(self.combo['vessel']))
vessel = {}
db.dcur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid")
for row in db.dcur.fetchall():
vessel[row['vid']] = row['vesselname'] + " - " + row['model']
combobox.load_from_dict(self.combo['vessel'], vessel, self.vid)
# load shape of vessel if available
handle = Rsvg.Handle()
@@ -84,17 +86,97 @@ class Frontend():
#viewport.height = 240
#self._svg.render_document(ctx, viewport)
# combos
roles = {'captain': 'Captain', 'helmsman': 'Helmsman', 'sailor': 'Sailor'}
combobox.load_from_dict(self.combo['user_role'], roles)
langs = {'de': 'Deutsch', 'en': 'English'}
combobox.load_from_dict(self.combo['user_lang'], langs)
# prepare lists
self.lst_user = Gtk.ListStore(int, str)
db.cur.execute("SELECT userid, login FROM user ORDER BY login")
db.cur.execute("SELECT userid, login FROM user WHERE userid > 0 ORDER BY login")
for row in db.cur.fetchall():
self.lst_user.append(row)
treeview.add_column(self.tree['user'], 'ID', 0)
treeview.add_column(self.tree['user'], 'Login', 1)
self.tree['user'].set_model(self.lst_user)
# current selected objects
self._user = user.User(db.conn, 0)
print(self._user)
def run(self):
self.label['vessel'].set_text(self.vname)
self.window.show_all()
Gtk.main()
def user_display(self)
self.entry['user_login'].set_text(self._user.login)
self.entry['user_displayname'].set_text(self._user.displayname)
combobox.activate_row(self.combo['user_role'], self._user.role)
combobox.activate_row(self.combo['user_lang'], self._user.language)
self.check['user_locked'].set_active('locked' in self._user.flags)
self.check['user_deleted'].set_active('deleted' in self._user.flags)
self.entry['user_pass'].set_text('')
def on_tvw_user_row_activated(self, widget, path, column):
# load selected user
self._user.user_id = treeview.get_value(widget, 0)
self._user.load()
self.user_display()
def on_but_user_new_clicked(self, widget):
# get first free user id and create a new record with name "user<nnn>"
self._user.create()
self.lst_user.append((self._user.user_id, self._user.login))
def on_but_user_del_clicked(self, widget):
print("Delete user")
self._user.delete()
selection = self.tree['user'].get_selection()
model, tree_iter = selection.get_selected()
model.remove(tree_iter)
# TODO Select some other user from new list selection
self._user.user_id = treeview.get_value(self.tree['user'])
self._user.load()
self.user_display()
def on_but_user_newpass_clicked(self, widget):
self.entry['user_pass'].set_text(user.User.genpass())
# self.lst_user.
def on_but_user_save_clicked(self, widget):
newlogin = self.entry['user_login'].get_text()
# check if changed login exists and throw error in that case
if newlogin != self._user.login:
sql = "SELECT COUNT(login) FROM user WHERE login=%s"
db.cur.execute(sql, (newlogin, ))
if db.cur.fetchone()[0] > 0:
# TODO set error indicator
print("Error: Login already exists!")
return
sql = "UPDATE user SET login=%s WHERE userid=%s"
db.cur.execute(sql, (newlogin, self._user.user_id))
treeview.set_value(self.tree['user'], 1, newlogin)
self._user.displayname = self.entry['user_displayname'].get_text()
self._user.role = combobox.get_list_value(self.combo['user_role'])
self._user.language = combobox.get_list_value(self.combo['user_lang'])
self._user.flags = []
if self.check['user_locked'].get_active(): self._user.flags.append('locked')
if self.check['user_deleted'].get_active(): self._user.flags.append('deleted')
self._user.save()
# password field is different. if filled set new password
newpass = self.entry['user_pass'].get_text()
if len(newpass) >= 8:
try:
self._user.setpass(newpass)
finally:
newpass = None
self.entry['user_pass'].set_text('')
else:
# Password to short
pass
def on_but_info_clicked(self, widget):
# Userlist
sql = ("SELECT userid, login, displayname FROM user ORDER BY login")