commit bf097096c1d2794c121e59b5d4a1a1017380b949 Author: Steve Nyemba Date: Fri Oct 31 17:27:33 2025 -0500 basic qcms site diff --git a/qcms-manifest.json b/qcms-manifest.json new file mode 100644 index 0000000..a2d665e --- /dev/null +++ b/qcms-manifest.json @@ -0,0 +1,84 @@ +{ + "system": { + "version": "0.1.1", + "context": "", + "theme": "hiplab", + "logo": "www/content/_assets/images/logo.png", + "app": { + "debug": true, + "port": 8084, + "threaded": true, + "host": "0.0.0.0" + } + }, + "plugins": { + "themes": [ + "List", + "Get" + ] + }, + "layout": { + "root": "www/content", + "index": "index.html", + "on": { + "load": { + "pane": [ + "www/content/installation.html" + ] + }, + "error": {} + }, + "icons": { + "comment": "use folder names as keys and fontawesome type as values to add icons to menu" + }, + "api": { + "comment": "use keys as uri and function calls as values" + }, + "map": [ + {} + ], + "order": { + "menu": [ + "docs", + "about" + ] + }, + "overwrite": { + "copyright": { + "title": "QCMS - Copyright", + "text": "copyright", + "type": "dialog", + "uri": "www/content/about/copyright.html" + }, + "plugins": { + "title": "Build Plugins", + "text": "Build Plugins" + }, + "themes": { + "text": "Change Themes" + }, + "overview": { + "text": "Why QCMS", + "uri": "www/content/index.html" + }, + "license": { + "type": "dialog", + "text": "License", + "title": "QCMS License - MIT" + } + }, + "header": { + "logo": true, + "title": "QCMS", + "subtitle": "Powered by - Python/Flask" + }, + "footer": [ + { + "text": "Designed & Built by Steve L. Nyemba" + }, + { + "text": "Powered By QCMS" + } + ] + } +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5b55284 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +data-transport@git+https://github.com/lnyemba/data-transport +flask +flask-session +requests +numpy +pandas +mistune +pygments diff --git a/www/content/_assets/files/usecase-diagram.drawio b/www/content/_assets/files/usecase-diagram.drawio new file mode 100644 index 0000000..81eb358 --- /dev/null +++ b/www/content/_assets/files/usecase-diagram.drawio @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/content/_assets/images/logo-1.png b/www/content/_assets/images/logo-1.png new file mode 100644 index 0000000..9c7527c Binary files /dev/null and b/www/content/_assets/images/logo-1.png differ diff --git a/www/content/_assets/images/logo.png b/www/content/_assets/images/logo.png new file mode 100644 index 0000000..d8e5e76 Binary files /dev/null and b/www/content/_assets/images/logo.png differ diff --git a/www/content/_assets/images/usecase.png b/www/content/_assets/images/usecase.png new file mode 100644 index 0000000..179f363 Binary files /dev/null and b/www/content/_assets/images/usecase.png differ diff --git a/www/content/_assets/themes/default/borders.css b/www/content/_assets/themes/default/borders.css new file mode 100644 index 0000000..6f9a192 --- /dev/null +++ b/www/content/_assets/themes/default/borders.css @@ -0,0 +1,38 @@ +.border { + border:1px solid #CAD5E0 ; +} +.border-round { + padding:6px; + border-radius:8px; +} +.border-round-top-left{ + border-top-left-radius: 8px; + padding:6px; +} +.border-round-top-right{ + border-top-right-radius: 8px; + padding:6px; +} +.border-round-bottom-right{ + border-bottom-right-radius: 8px; + padding:6px; +} +.border-round-bottom-left{ + border-bottom-left-radius: 8px; + padding:6px; +} + + +.border-right{ + border-right:1px solid #CAD5E0; + +} + +.border-left{ + border-left:1px solid #CAD5E0; +} + + +.border-bottom { border-bottom:1px solid #CAD5E0} +.border-top { border-top:1px solid #CAD5E0} + diff --git a/www/content/_assets/themes/default/footer.css b/www/content/_assets/themes/default/footer.css new file mode 100644 index 0000000..8086c04 --- /dev/null +++ b/www/content/_assets/themes/default/footer.css @@ -0,0 +1,19 @@ +/** +* This file styles the footer of the page +*/ + +.footer { + text-align:center; + display:grid; + grid-template-columns: repeat(3,1fr); + gap:4px; + padding:8px; + font-size:12px; + color:black; + align-items: center; + align-content: center; + text-transform: capitalize; + /* background-color: rgba(255,255,255,0.8); */ + + grid-column: 1 /span 2; +} diff --git a/www/content/_assets/themes/default/header.css b/www/content/_assets/themes/default/header.css new file mode 100644 index 0000000..a64ce8b --- /dev/null +++ b/www/content/_assets/themes/default/header.css @@ -0,0 +1,3 @@ +.main .header { display:grid; grid-template-columns:64px auto; gap:4px} +.main .header .title {font-size:28px; font-weight:bold} +.main .header img {width:55px; height:55px;} diff --git a/www/content/_assets/themes/default/layout.css b/www/content/_assets/themes/default/layout.css new file mode 100644 index 0000000..0e48ca3 --- /dev/null +++ b/www/content/_assets/themes/default/layout.css @@ -0,0 +1,22 @@ + + +.main { + height:98vh; display:grid; grid-template-columns:70% auto; gap:4px; + grid-template-rows:70px 40px auto 32px; + padding-left:2%; padding-right:2%; + line-height: 1.5; font-size:16px; font-family: sans-serif; font-weight:lighter; +} +.main .content{ + align-self:center; +} +.main .pane {border-left:1px solid #CAD5E0; height:100%; padding:8px;} +.bold {font-weight:bold} +.small-text {font-size:12px; font-weight:lighter; color :#000000;} +.active {cursor:pointer; padding:2px; margin:2px; border-bottom:2px solid transparent} +.active:hover {border-bottom-color: #4682b4;} + +/** +* +*/ +.large-text {font-weight:bold; font-size:28px;} + \ No newline at end of file diff --git a/www/content/_assets/themes/default/menu.css b/www/content/_assets/themes/default/menu.css new file mode 100644 index 0000000..7a14d15 --- /dev/null +++ b/www/content/_assets/themes/default/menu.css @@ -0,0 +1,78 @@ +.menu { + padding:8px; + border:1px solid #CAD5E0 ; + + display:grid; + grid-column: 1 / span 2; + grid-template-columns: 92px repeat(7,auto); + gap:4px; + text-transform: capitalize; + align-items: center; + +} +.menu .icon {padding:4px;} +.menu .icon img {width:30px; height:30px;} +.menu .item { + font-weight:bold; + cursor:pointer; + padding:4px; + text-align: left; + + + + + +} + +.menu .sub-menu { + + display:none; + position:absolute; + + margin-top:2px; + min-width:10%; + z-index:90; + padding:8px; + font-weight:lighter; + text-align:left; + align-items:left; + background-color: rgba(255,255,255,0.8); + +} + +.menu .item:hover .sub-menu{ + + display:block; + height:auto; +} +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} diff --git a/www/content/_assets/themes/default/pane.css b/www/content/_assets/themes/default/pane.css new file mode 100644 index 0000000..e321d4a --- /dev/null +++ b/www/content/_assets/themes/default/pane.css @@ -0,0 +1,2 @@ +.main .pane { height:auto;} +.main .pane iframe{ width:100%; border:0px; height:80%;} diff --git a/www/content/_assets/themes/default/responsive.css b/www/content/_assets/themes/default/responsive.css new file mode 100644 index 0000000..cd91420 --- /dev/null +++ b/www/content/_assets/themes/default/responsive.css @@ -0,0 +1,36 @@ +/* Responsive Design */ +@media (max-width: 1024px) { + .main { + grid-template-columns: 60% 40%; + grid-template-rows: 70px auto 32px; + } +} + +@media (max-width: 768px) { + .main { + grid-template-columns: 100%; + grid-template-rows: auto; + height: auto; + } + + .main .pane { + border-left: none; + border-top: 1px solid #CAD5E0; + padding: 8px; + } +} + +@media (max-width: 480px) { + .main { + padding: 5%; + font-size: 14px; + } + + .large-text { + font-size: 24px; + } + + .small-text { + font-size: 10px; + } +} diff --git a/www/content/_assets/themes/hiplab/borders.css b/www/content/_assets/themes/hiplab/borders.css new file mode 100644 index 0000000..6f9a192 --- /dev/null +++ b/www/content/_assets/themes/hiplab/borders.css @@ -0,0 +1,38 @@ +.border { + border:1px solid #CAD5E0 ; +} +.border-round { + padding:6px; + border-radius:8px; +} +.border-round-top-left{ + border-top-left-radius: 8px; + padding:6px; +} +.border-round-top-right{ + border-top-right-radius: 8px; + padding:6px; +} +.border-round-bottom-right{ + border-bottom-right-radius: 8px; + padding:6px; +} +.border-round-bottom-left{ + border-bottom-left-radius: 8px; + padding:6px; +} + + +.border-right{ + border-right:1px solid #CAD5E0; + +} + +.border-left{ + border-left:1px solid #CAD5E0; +} + + +.border-bottom { border-bottom:1px solid #CAD5E0} +.border-top { border-top:1px solid #CAD5E0} + diff --git a/www/content/_assets/themes/hiplab/footer.css b/www/content/_assets/themes/hiplab/footer.css new file mode 100644 index 0000000..8086c04 --- /dev/null +++ b/www/content/_assets/themes/hiplab/footer.css @@ -0,0 +1,19 @@ +/** +* This file styles the footer of the page +*/ + +.footer { + text-align:center; + display:grid; + grid-template-columns: repeat(3,1fr); + gap:4px; + padding:8px; + font-size:12px; + color:black; + align-items: center; + align-content: center; + text-transform: capitalize; + /* background-color: rgba(255,255,255,0.8); */ + + grid-column: 1 /span 2; +} diff --git a/www/content/_assets/themes/hiplab/header.css b/www/content/_assets/themes/hiplab/header.css new file mode 100644 index 0000000..a64ce8b --- /dev/null +++ b/www/content/_assets/themes/hiplab/header.css @@ -0,0 +1,3 @@ +.main .header { display:grid; grid-template-columns:64px auto; gap:4px} +.main .header .title {font-size:28px; font-weight:bold} +.main .header img {width:55px; height:55px;} diff --git a/www/content/_assets/themes/hiplab/layout.css b/www/content/_assets/themes/hiplab/layout.css new file mode 100644 index 0000000..6615939 --- /dev/null +++ b/www/content/_assets/themes/hiplab/layout.css @@ -0,0 +1,37 @@ +/** +* This is the default window and we will have to hide the pane (side) +*/ +body {line-height: 1.5; font-weight:lighter; font-family:sans-serif;} +.main {height:98vh; display:grid; grid-template-columns:65% 35%; gap:4px; + grid-template-rows:70px 40px auto 32px; + padding-left:2%; padding-right:2%; + } + .main .header { display:grid; grid-template-columns:64px auto; gap:4px; align-items: center;} + .main .header .title {font-size:28px; font-weight:bold} + .main .header img {width:80px; height:80px;} + .main .index{ + align-self:center; + padding:8px; + } + .main .menu {border:1px solid transparent; background-color:#f3f3f3;} + .main .pane {border-left:1px solid #CAD5E0; height:100%; padding:8px; line-height:1.5; font-size: 14px;} + + + .search-box {display:block; grid-template-columns: none;} + + + + +.main .content{ + align-self:center; +} +.bold {font-weight:bold} +.small-text {font-size:12px; font-weight:lighter; color :#000000;} +.active {cursor:pointer; padding:2px; margin:2px; border-bottom:2px solid transparent} +.active:hover {border-bottom-color: #4682b4;} + +/** +* +*/ +.large-text {font-weight:bold; font-size:28px;} + \ No newline at end of file diff --git a/www/content/_assets/themes/hiplab/menu.css b/www/content/_assets/themes/hiplab/menu.css new file mode 100644 index 0000000..bb368d0 --- /dev/null +++ b/www/content/_assets/themes/hiplab/menu.css @@ -0,0 +1,110 @@ +.menu { + padding:8px; + border:1px solid #CAD5E0 ; + + display:grid; + grid-column: 1 / span 2; + grid-template-columns: 92px repeat(7,auto); + gap:4px; + text-transform: capitalize; + align-items: center; + +} +.menu .icon {padding:4px;} +.menu .icon img {width:30px; height:30px;} +.menu .item { + font-weight:bold; + cursor:pointer; + padding:4px; + text-align: left; + + + + + +} + +.menu .sub-menu { + + display:none; + position:absolute; + + margin-top:2px; + min-width:10%; + z-index:90; + padding:8px; + font-weight:lighter; + text-align:left; + align-items:left; + background-color: rgba(255,255,255,0.8); + +} + +.menu .item:hover .sub-menu{ + + display:block; + height:auto; +} + +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} diff --git a/www/content/_assets/themes/hiplab/pane.css b/www/content/_assets/themes/hiplab/pane.css new file mode 100644 index 0000000..f1b63c8 --- /dev/null +++ b/www/content/_assets/themes/hiplab/pane.css @@ -0,0 +1,2 @@ +.main .pane {display:block;} +/* .main .pane div:first-child {margin-top:4px; background-color:#f3f3f3; border-radius: 8px; padding:8px; min-height:150px} */ \ No newline at end of file diff --git a/www/content/_assets/themes/hiplab/responsive.css b/www/content/_assets/themes/hiplab/responsive.css new file mode 100644 index 0000000..cd91420 --- /dev/null +++ b/www/content/_assets/themes/hiplab/responsive.css @@ -0,0 +1,36 @@ +/* Responsive Design */ +@media (max-width: 1024px) { + .main { + grid-template-columns: 60% 40%; + grid-template-rows: 70px auto 32px; + } +} + +@media (max-width: 768px) { + .main { + grid-template-columns: 100%; + grid-template-rows: auto; + height: auto; + } + + .main .pane { + border-left: none; + border-top: 1px solid #CAD5E0; + padding: 8px; + } +} + +@media (max-width: 480px) { + .main { + padding: 5%; + font-size: 14px; + } + + .large-text { + font-size: 24px; + } + + .small-text { + font-size: 10px; + } +} diff --git a/www/content/_assets/themes/magazine/borders.css b/www/content/_assets/themes/magazine/borders.css new file mode 100644 index 0000000..e6c7e66 --- /dev/null +++ b/www/content/_assets/themes/magazine/borders.css @@ -0,0 +1,38 @@ +.border { + border:1px solid #CAD5E0 ; +} +.border-round { + padding:6px; + border-radius:8px; +} +.border-round-top-left{ + border-top-left-radius: 8px; + padding:6px; +} +.border-round-top-right{ + border-top-right-radius: 8px; + padding:6px; +} +.border-round-bottom-right{ + border-bottom-right-radius: 8px; + padding:6px; +} +.border-round-bottom-left{ + border-bottom-left-radius: 8px; + padding:6px; +} + + +.border-right{ + border-right:3px dotted #CAD5E0; + +} + +.border-left{ + border-left:3px dotted gray; +} + + +.border-bottom { border-bottom:1px dotted gray;} +.border-top { border-top:3px dotted gray;} + diff --git a/www/content/_assets/themes/magazine/footer.css b/www/content/_assets/themes/magazine/footer.css new file mode 100644 index 0000000..84ce356 --- /dev/null +++ b/www/content/_assets/themes/magazine/footer.css @@ -0,0 +1,22 @@ +/** +* This file styles the footer of the page +*/ + + + +/* .main .footer { grid-column: 1 / span 2; font-size:13px; font-weight: lighter; padding:8px;} */ +.footer { + text-align:center; + display:grid; + grid-template-columns: repeat(3,1fr); + gap:4px; + padding:8px; + font-size:12px; + color:black; + align-items: center; + align-content: center; + text-transform: capitalize; + /* background-color: rgba(255,255,255,0.8); */ + + grid-column: 1 /span 2; +} diff --git a/www/content/_assets/themes/magazine/header.css b/www/content/_assets/themes/magazine/header.css new file mode 100644 index 0000000..991b9af --- /dev/null +++ b/www/content/_assets/themes/magazine/header.css @@ -0,0 +1,13 @@ +.main .header { + + grid-row:1; + grid-column: 1 / span 2; + display:grid; + grid-template-columns: 50px auto; gap:4px; + line-height: 1; + + +} +.main .header .title {font-size:28px; text-transform: capitalize; font-weight:bold} +.main .header .subtitle {font-size:14px} +.main .header img { width:44px; height:44px;} diff --git a/www/content/_assets/themes/magazine/layout.css b/www/content/_assets/themes/magazine/layout.css new file mode 100644 index 0000000..ad5c277 --- /dev/null +++ b/www/content/_assets/themes/magazine/layout.css @@ -0,0 +1,55 @@ +.main { + margin:10px; + padding:4px; + display:grid; + grid-template-columns: 50% 50% ; gap:4px; + grid-template-rows: 48px 64px auto 32px; + font-family: sans-serif; + font-weight: lighter; + font-size:18px; + line-height: 1.5; + justify-items: normal; + ; + height:96vh; +} +.main .content { + grid-row:3; + grid-column: 1 ; + text-wrap: wrap; + height:100%; + display:grid; + align-content: start; + + + +} + +.main .content #index { + text-align: left; + text-wrap: wrap; +} + +p { + margin-top:22px; +} +table { + width:99%; + border: 1px solid #CAD5E0; +} +table td {padding:4px; margin:4px;} +table thead { + + font-weight:bold; + background-color:#f3f3f3; +} + +.bold {font-weight:bold} +.small-text {font-size:12px; font-weight:lighter; color :#000000;} +.active {cursor:pointer; padding:2px; margin:2px; border-bottom:2px solid transparent} +.active:hover {border-bottom-color: #4682b4;} + +/** +* +*/ +.large-text {font-weight:bold; font-size:28px;} + \ No newline at end of file diff --git a/www/content/_assets/themes/magazine/menu.css b/www/content/_assets/themes/magazine/menu.css new file mode 100644 index 0000000..21cfd84 --- /dev/null +++ b/www/content/_assets/themes/magazine/menu.css @@ -0,0 +1,110 @@ +.menu { + padding:8px; + border-bottom:3px dotted gray ; + + display:grid; + grid-column: 1 / span 2; + grid-template-columns: 92px repeat(7,auto); + gap:4px; + text-transform: capitalize; + align-items: center; + +} +.menu .icon {padding:4px;} +.menu .icon img {width:30px; height:30px;} +.menu .item { + font-weight:bold; + cursor:pointer; + padding:4px; + text-align: left; + + + + + +} + +.menu .sub-menu { + + display:none; + position:absolute; + + margin-top:2px; + min-width:10%; + z-index:90; + padding:8px; + font-weight:lighter; + text-align:left; + align-items:left; + background-color: rgba(255,255,255,0.8); + +} + +.menu .item:hover .sub-menu{ + + display:block; + height:auto; +} + +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} diff --git a/www/content/_assets/themes/magazine/pane.css b/www/content/_assets/themes/magazine/pane.css new file mode 100644 index 0000000..4fb956f --- /dev/null +++ b/www/content/_assets/themes/magazine/pane.css @@ -0,0 +1,14 @@ +.main .pane { + border-left:3px dotted gray; + grid-column: 2; + font-family: sans-serif; + padding-left: 10px; + + +} +.pane iframe { + border:1px solid transparent; + width:99%; + height:100%; + +} \ No newline at end of file diff --git a/www/content/_assets/themes/magazine/responsive.css b/www/content/_assets/themes/magazine/responsive.css new file mode 100644 index 0000000..cd91420 --- /dev/null +++ b/www/content/_assets/themes/magazine/responsive.css @@ -0,0 +1,36 @@ +/* Responsive Design */ +@media (max-width: 1024px) { + .main { + grid-template-columns: 60% 40%; + grid-template-rows: 70px auto 32px; + } +} + +@media (max-width: 768px) { + .main { + grid-template-columns: 100%; + grid-template-rows: auto; + height: auto; + } + + .main .pane { + border-left: none; + border-top: 1px solid #CAD5E0; + padding: 8px; + } +} + +@media (max-width: 480px) { + .main { + padding: 5%; + font-size: 14px; + } + + .large-text { + font-size: 24px; + } + + .small-text { + font-size: 10px; + } +} diff --git a/www/content/_assets/themes/oss/borders.css b/www/content/_assets/themes/oss/borders.css new file mode 100644 index 0000000..6f9a192 --- /dev/null +++ b/www/content/_assets/themes/oss/borders.css @@ -0,0 +1,38 @@ +.border { + border:1px solid #CAD5E0 ; +} +.border-round { + padding:6px; + border-radius:8px; +} +.border-round-top-left{ + border-top-left-radius: 8px; + padding:6px; +} +.border-round-top-right{ + border-top-right-radius: 8px; + padding:6px; +} +.border-round-bottom-right{ + border-bottom-right-radius: 8px; + padding:6px; +} +.border-round-bottom-left{ + border-bottom-left-radius: 8px; + padding:6px; +} + + +.border-right{ + border-right:1px solid #CAD5E0; + +} + +.border-left{ + border-left:1px solid #CAD5E0; +} + + +.border-bottom { border-bottom:1px solid #CAD5E0} +.border-top { border-top:1px solid #CAD5E0} + diff --git a/www/content/_assets/themes/oss/footer.css b/www/content/_assets/themes/oss/footer.css new file mode 100644 index 0000000..e1acfb5 --- /dev/null +++ b/www/content/_assets/themes/oss/footer.css @@ -0,0 +1,21 @@ +/** +* This file styles the footer of the page +*/ + +.footer { + grid-row:5; + margin-top:10px; + text-align:center; + display:grid; + grid-template-columns: repeat(3,1fr); + gap:4px; + padding:8px; + font-size:12px; + color:black; + align-items: center; + align-content: center; + text-transform: capitalize; + /* background-color: rgba(255,255,255,0.8); + + +} diff --git a/www/content/_assets/themes/oss/header.css b/www/content/_assets/themes/oss/header.css new file mode 100644 index 0000000..c7ee323 --- /dev/null +++ b/www/content/_assets/themes/oss/header.css @@ -0,0 +1,7 @@ +.main .header {display:grid; grid-template-columns:64px auto; gap:4px; width:100%; align-items:center;} +.main .header .icon {width:64px; height:64px;} +.main .header .icon img {width:64px; height:64px;} + +.main .header .title { font-size:32px; text-transform: uppercase; font-weight:bold} +.main .header .subtitle {font-style:italic;font-size:14px; color:gray; text-transform: capitalize;} + diff --git a/www/content/_assets/themes/oss/layout.css b/www/content/_assets/themes/oss/layout.css new file mode 100644 index 0000000..b15235a --- /dev/null +++ b/www/content/_assets/themes/oss/layout.css @@ -0,0 +1,50 @@ +/** +* This file implements theme Open Source Software, the expectation is just a pager that presents a tool/software +*/ +body, ul, p, select { + font-size: 16px; + font-weight:lighter; + font-family:sans-serif; + line-height:1.5; + +} +.main { + display:grid; + grid-template-columns: 100%;; + margin:1%; + margin-left:10%; + margin-right:10%; + gap:4px; + height:90%; + width:80%; +} +.main .content{ + + grid-row:4; + align-self:center; + height:100%; + width:100%; +} + +.main .content .banner { + background-repeat: no-repeat; + background-size: cover; + background-position: center; + height:200px; + align-items: center; + align-content: center; + display: grid; + +} + +/* .main .pane {border-left:1px solid #CAD5E0; height:100%; padding:8px;} */ +.bold {font-weight:bold} +.small-text {font-size:12px; font-weight:lighter; color :#000000;} +.active {cursor:pointer; padding:2px; margin:2px; border-bottom:2px solid transparent} +.active:hover {border-bottom-color: #4682b4;} + +/** +* +*/ +.large-text {font-weight:bold; font-size:28px;} + \ No newline at end of file diff --git a/www/content/_assets/themes/oss/menu.css b/www/content/_assets/themes/oss/menu.css new file mode 100644 index 0000000..a34f727 --- /dev/null +++ b/www/content/_assets/themes/oss/menu.css @@ -0,0 +1,82 @@ + +.menu .icon {padding:4px; align-items:center; display:grid;} +.menu .icon img {width:30px; height:30px;} + +.main .menu .sub-menu { + + display:none; + position:absolute; + + margin-top:2px; + min-width:10%; + z-index:90; + padding:8px; + font-weight:lighter; + text-align:left; + align-items:left; + background-color: rgba(255,255,255,0.8); + +} +.main .menu .item { + font-weight:bold; + cursor:pointer; + padding:4px; + text-align: left; +} + +.main .menu .item:hover .sub-menu{ + + display:block; + height:auto; +} + +.main .menu { + padding:8px; + grid-row:2; + width:100%; + display:none; + + /* grid-row:2; grid-column:1 ; */ + grid-template-columns: 92px repeat(7,auto); + gap:4px; + text-transform: capitalize; + border:0; height:48px; padding:0px; + display:grid; grid-template:64px repeat(6,1fr); gap:4px; + /* background-color: #f3f3f3; */ + align-items: center; +} +.main .menu .icon .button { + border:1px solid #CAD5E0; border-radius:8px; padding:2px; + ; +} +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} diff --git a/www/content/_assets/themes/oss/pane.css b/www/content/_assets/themes/oss/pane.css new file mode 100644 index 0000000..3be2b06 --- /dev/null +++ b/www/content/_assets/themes/oss/pane.css @@ -0,0 +1 @@ +.main .pane {display:block} \ No newline at end of file diff --git a/www/content/_assets/themes/oss/responsive.css b/www/content/_assets/themes/oss/responsive.css new file mode 100644 index 0000000..cd91420 --- /dev/null +++ b/www/content/_assets/themes/oss/responsive.css @@ -0,0 +1,36 @@ +/* Responsive Design */ +@media (max-width: 1024px) { + .main { + grid-template-columns: 60% 40%; + grid-template-rows: 70px auto 32px; + } +} + +@media (max-width: 768px) { + .main { + grid-template-columns: 100%; + grid-template-rows: auto; + height: auto; + } + + .main .pane { + border-left: none; + border-top: 1px solid #CAD5E0; + padding: 8px; + } +} + +@media (max-width: 480px) { + .main { + padding: 5%; + font-size: 14px; + } + + .large-text { + font-size: 24px; + } + + .small-text { + font-size: 10px; + } +} diff --git a/www/content/_assets/themes/resume/borders.css b/www/content/_assets/themes/resume/borders.css new file mode 100644 index 0000000..6f9a192 --- /dev/null +++ b/www/content/_assets/themes/resume/borders.css @@ -0,0 +1,38 @@ +.border { + border:1px solid #CAD5E0 ; +} +.border-round { + padding:6px; + border-radius:8px; +} +.border-round-top-left{ + border-top-left-radius: 8px; + padding:6px; +} +.border-round-top-right{ + border-top-right-radius: 8px; + padding:6px; +} +.border-round-bottom-right{ + border-bottom-right-radius: 8px; + padding:6px; +} +.border-round-bottom-left{ + border-bottom-left-radius: 8px; + padding:6px; +} + + +.border-right{ + border-right:1px solid #CAD5E0; + +} + +.border-left{ + border-left:1px solid #CAD5E0; +} + + +.border-bottom { border-bottom:1px solid #CAD5E0} +.border-top { border-top:1px solid #CAD5E0} + diff --git a/www/content/_assets/themes/resume/footer.css b/www/content/_assets/themes/resume/footer.css new file mode 100644 index 0000000..8086c04 --- /dev/null +++ b/www/content/_assets/themes/resume/footer.css @@ -0,0 +1,19 @@ +/** +* This file styles the footer of the page +*/ + +.footer { + text-align:center; + display:grid; + grid-template-columns: repeat(3,1fr); + gap:4px; + padding:8px; + font-size:12px; + color:black; + align-items: center; + align-content: center; + text-transform: capitalize; + /* background-color: rgba(255,255,255,0.8); */ + + grid-column: 1 /span 2; +} diff --git a/www/content/_assets/themes/resume/header.css b/www/content/_assets/themes/resume/header.css new file mode 100644 index 0000000..a64ce8b --- /dev/null +++ b/www/content/_assets/themes/resume/header.css @@ -0,0 +1,3 @@ +.main .header { display:grid; grid-template-columns:64px auto; gap:4px} +.main .header .title {font-size:28px; font-weight:bold} +.main .header img {width:55px; height:55px;} diff --git a/www/content/_assets/themes/resume/layout.css b/www/content/_assets/themes/resume/layout.css new file mode 100644 index 0000000..0e48ca3 --- /dev/null +++ b/www/content/_assets/themes/resume/layout.css @@ -0,0 +1,22 @@ + + +.main { + height:98vh; display:grid; grid-template-columns:70% auto; gap:4px; + grid-template-rows:70px 40px auto 32px; + padding-left:2%; padding-right:2%; + line-height: 1.5; font-size:16px; font-family: sans-serif; font-weight:lighter; +} +.main .content{ + align-self:center; +} +.main .pane {border-left:1px solid #CAD5E0; height:100%; padding:8px;} +.bold {font-weight:bold} +.small-text {font-size:12px; font-weight:lighter; color :#000000;} +.active {cursor:pointer; padding:2px; margin:2px; border-bottom:2px solid transparent} +.active:hover {border-bottom-color: #4682b4;} + +/** +* +*/ +.large-text {font-weight:bold; font-size:28px;} + \ No newline at end of file diff --git a/www/content/_assets/themes/resume/menu.css b/www/content/_assets/themes/resume/menu.css new file mode 100644 index 0000000..3080a4e --- /dev/null +++ b/www/content/_assets/themes/resume/menu.css @@ -0,0 +1,79 @@ +.menu { + padding:8px; + border:1px solid #CAD5E0 ; + + display:grid; + grid-column: 1 / span 2; + grid-template-columns: 92px repeat(7,auto); + gap:4px; + text-transform: capitalize; + align-items: center; + +} +.menu .icon {padding:4px;} +.menu .icon img {width:30px; height:30px;} +.menu .item { + font-weight:bold; + cursor:pointer; + padding:4px; + text-align: left; + + + + + +} + +.menu .sub-menu { + + display:none; + position:absolute; + + margin-top:2px; + min-width:10%; + z-index:90; + padding:8px; + font-weight:lighter; + text-align:left; + align-items:left; + background-color: rgba(255,255,255,0.8); + +} + +.menu .item:hover .sub-menu{ + + display:block; + height:auto; +} + +/** +* TAB SPECIFICATIONS WITH RADIO BUTTONS AND LABELS +*/ +.tab-content table { width:99%; border-radius: 8px; padding:8px; border:1px solid #d3d3d3;} +.tab-content table tr:nth-child(even) {background-color: #f3f3f3;} +/* .tab-content {width:80%;} */ + +.tabs {display:grid; grid-template-columns: repeat(auto-fit,209px); gap:0px; align-content:center; + /* background-color: #f3f3f3; */ + padding-top:4px; + padding-left:4px; + padding-right:4px; +} +.tabs input[type=radio] {display:none; } +.tabs input[type=radio] + label { font-weight:lighter; + border:1px solid transparent; + border-bottom-color: #CAD5E0; + background-color: #f3f3f3; + padding:8px; + padding-right:10px; padding-left:10px; + + cursor:pointer +} +.tabs input[type=radio]:checked +label { + background-color: #ffffff; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + font-weight:bold; + border-color: #CAD5E0; + border-bottom-color: #FFFFFF; +} diff --git a/www/content/_assets/themes/resume/pane.css b/www/content/_assets/themes/resume/pane.css new file mode 100644 index 0000000..e69de29 diff --git a/www/content/_assets/themes/resume/responsive.css b/www/content/_assets/themes/resume/responsive.css new file mode 100644 index 0000000..cd91420 --- /dev/null +++ b/www/content/_assets/themes/resume/responsive.css @@ -0,0 +1,36 @@ +/* Responsive Design */ +@media (max-width: 1024px) { + .main { + grid-template-columns: 60% 40%; + grid-template-rows: 70px auto 32px; + } +} + +@media (max-width: 768px) { + .main { + grid-template-columns: 100%; + grid-template-rows: auto; + height: auto; + } + + .main .pane { + border-left: none; + border-top: 1px solid #CAD5E0; + padding: 8px; + } +} + +@media (max-width: 480px) { + .main { + padding: 5%; + font-size: 14px; + } + + .large-text { + font-size: 24px; + } + + .small-text { + font-size: 10px; + } +} diff --git a/www/content/_plugins/themes.py b/www/content/_plugins/themes.py new file mode 100644 index 0000000..f6fa267 --- /dev/null +++ b/www/content/_plugins/themes.py @@ -0,0 +1,38 @@ +""" + +""" +import os +import cms + +def _getPath(_config): + _layout = _config['layout'] + path = os.sep.join([_layout['location'],_layout['root'],'_assets','themes']) + return path +@cms.Plugin(mimetype='application/json') +def List (**_args) : + """ + This will list available css themes we have on disk + """ + _rq = _args['request'] + _config = _args['config'] + path = _getPath(_config) #os.sep.join([_config['layout']['location'],_config['layout']['root'],'_assets','themes']) + + return os.listdir(path) +def register (): + """ + Registering + """ + pass +def Get (**_args): + _request = _args['request'] + _config = _args['config'] + print (_request.args['theme']) + _theme = 'default' if 'theme' not in _request.args else _request.args['theme'] + path = os.sep.join([_getPath(_config),_theme]) + files = os.listdir(path) + _out = {} + for file in files : + f = open (os.sep.join([path,file])) + _out[file.split('.')[0]] = f.read() + f.close() + return {_theme:_out},"application/json" diff --git a/www/content/about/copyright.html b/www/content/about/copyright.html new file mode 100644 index 0000000..46c5507 --- /dev/null +++ b/www/content/about/copyright.html @@ -0,0 +1,13 @@ + +
+
+ +QCMS is a python/flask based Content Management System +
+
diff --git a/www/content/about/license.md b/www/content/about/license.md new file mode 100644 index 0000000..58b210a --- /dev/null +++ b/www/content/about/license.md @@ -0,0 +1,8 @@ +Copyright 2010 - 2024, Steve L. Nyemba + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/www/content/docs/overview.md b/www/content/docs/overview.md new file mode 100644 index 0000000..e69de29 diff --git a/www/content/docs/plugins.md b/www/content/docs/plugins.md new file mode 100644 index 0000000..9eb8e54 --- /dev/null +++ b/www/content/docs/plugins.md @@ -0,0 +1,37 @@ +### Build plugins +A plugin is a basic python function that takes positional arguments i.e one being request and the other config. We allow the function to have access to the request object (if data were submitted) and to QCMS configuration if any configuration data is needed. + + +
+ # saving this function to www/html/_plugins/myplugins.py + # + import numpy as np + import cms + + @cms.Plugin(mimetype="application/json", methods=["POST"]) + def demo(**_args): + req = _args['request'] + conf= _args['config'] + return {"name":"foo","age":np.random.randint(0,100)} +
+ +### Register Plugin + +Once a plugin is build it needs to be registered, in order to allow the function be made available to calling code (JS/HTML5) + +
+$ qcms plugin-register qcms-manifest.json myplugins.py --import demo +
+ +Calling the registered function as an API. The example uses curl +
+$ curl http://localhost:8000/api/myplugins/demo +
+ +### Limitations + +**QCMS** lowers the barrier to entry and makes the following tradeoffs to deliver an optimal solution: + diff --git a/www/content/docs/themes.md b/www/content/docs/themes.md new file mode 100644 index 0000000..5be554f --- /dev/null +++ b/www/content/docs/themes.md @@ -0,0 +1,49 @@ +Themes consist of basic **css** located in **www/html/_assets/themes**. The css folder names represent the themes. +Each folder should have the following files : +
+
Files (.css extensions)
Description
+
borders
styling of borders (top,bottom,right,left,round)
+
footer
styling the footer
+
header
styling the footer
+
layout
The general layout for a site and how to organize different panes
+
menu
styling the menu
+
pane
styling secondary pane, it's position and visibility depends on the theme
+
+ +### Setup themes + +**QCMS** CLI interface allows to query the backend to determine available themes, in three simple phases described below + +
+ #1. Listing available themes
+ $ qcms list-themes +
+ +
#2. Selecting and applying theme
+$ qcms set-theme magazine
+ +
# Verifying themes set
$ ls www/html/_assets/themes
+ + +### Custom Themes + +**QCMS** supports custom themes or user-defined themes. Build custom theme manually in three simple steps: + +
+# 1. copy default theme to new folder (www/html/_assets/theme/mytheme)
+$ cp -r www/html/_assets/themes/default www/html/_assets/themes/mytheme +
+ +
+# 2. update configuration file to enable configuration
+{ + "system":{ + "theme":"mytheme" + ... + } +}
+ +
+# 3. set the theme and reload the your app (from the app folder)
+$ qcms set-theme magazine +
\ No newline at end of file diff --git a/www/content/features.md b/www/content/features.md new file mode 100644 index 0000000..9a61335 --- /dev/null +++ b/www/content/features.md @@ -0,0 +1,14 @@ +### Build plugins +A plugin is a basic python function that takes positional arguments i.e one being request and the other config. We allow the function to have access to the request object (if data were submitted) and to QCMS configuration if any configuration data is needed. + + +
+ import numpy as np + + def demo(**_args): + req = _args['request'] + conf= _args['config'] + return {"name":"foo","age":np.random.randint(0,100)} +
+ + diff --git a/www/content/index.html b/www/content/index.html new file mode 100644 index 0000000..6816ad3 --- /dev/null +++ b/www/content/index.html @@ -0,0 +1,46 @@ +
+ +

What is QCMS ?

+ Fast lightweight Python/Flask CMS designed for engineers +
Builds a web site from folder structure (local or nextcloud) +
Supports industry standard frameworks +
+ JQuery, Apexcharts, fontawesome +
Numpy, Pandas, flask +
+ Custom functions built & registered as API +
Extensible themes support (mostly css) +
+
+
+
+

+

Why use QCMS

+ +
+ QCMS lowers the barrier of entry to building a site +
It is open-source and available under MIT license +
Removing user administration overhead:smaller footprint & efficiency +
Using basic knowledge of how documents are organized on disk or in the cloud. +
+ Markdown rendering for non-technical users (using an editor). +
HTML5 rendering for more advanced users +
Suitable to deploy multiple sites on an AWS micro/nano instance +
+
+

+
+
+
+
+

Use Cases

+ +
+ QCMS, implements a separation of concerns between web engineer and the content creator with the least amount of friction +
Ideally the roles would look like +
    + The Web Engineers, will setup virtualenv; setup port, install themes, makes sure sure content is accessible. Optionally can make content. +
    The Content Creator, writes up files and uploads images ... +
+
+
\ No newline at end of file diff --git a/www/content/installation.html b/www/content/installation.html new file mode 100644 index 0000000..44d88d8 --- /dev/null +++ b/www/content/installation.html @@ -0,0 +1,120 @@ + + +
+ + + + + + + +
+ +
+ + +
+
+
+
+
+
+ # install using virtualenv
+ $ pip install --upgrade git+https://dev.the-phi.com/git/qcms/cms +
+
+ # explore qcms commands and try them
+ $ qcms --help +
+ +
+ +
+
+
+
#creating a sample project with title and sample title
$ qcms create myproject www/html --title mydemo --subtitle "flask cms demo"
+
#bootup the newly created site
$ qcms bootup ./myproject/qcms-manifest.json
+
Open your browser on port http://localhost:8084
+
+
+
+ +
+

+

+

+ \ No newline at end of file