Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
|
2cea5f44fb | ||
|
37cd4fd17a | ||
|
b5cdfa0a09 | ||
|
0052c00f03 | ||
|
26869a837b | ||
|
3068f5980d |
9 changed files with 1459 additions and 807 deletions
22
Plans.md
22
Plans.md
|
@ -1,8 +1,24 @@
|
|||
# Plans for Webservices App
|
||||
|
||||
High priority:
|
||||
I've got quite a few plans for this app... here's a rough sketch of what I'm planning.
|
||||
|
||||
First release:
|
||||
|
||||
* filtering of all properties like Categories, Analogues, Licenses, Statuses, Hosts, and Affiliations.
|
||||
* modal showing each technology in more detail. DONE
|
||||
* split out all constants (arrays) into separate files. ~DONE
|
||||
* split out functions into svelte components.
|
||||
|
||||
Future versions:
|
||||
|
||||
* upgrade to Svelte 5
|
||||
* a 'Technology' view (overlay), addressable by URL (i.e. going to, say, http://localhost:5173/ServiceName brings up the site with ServiceName shown in 'Technology' view, allowing people to reference a specific technology of interest to others).
|
||||
* split out all constants (arrays) into separate files.
|
||||
* split out functions into svelte modules.
|
||||
* ability to go 'prev' or 'next' in modals within filtered set of technologies.
|
||||
* ability to select a Hosts and view all Technologies with instances on that Host
|
||||
* ability to see the status (via UptimeKuma API) of each instance.
|
||||
|
||||
Possibly, eventually:
|
||||
|
||||
* move data into a database (rather than a fragile hand-crafted JSON file) with integrity checking.
|
||||
* Authentication for administration
|
||||
* CRUD interface for Technologies, Licenses, Hosts, and Affiliations.
|
||||
|
|
1178
package-lock.json
generated
1178
package-lock.json
generated
File diff suppressed because it is too large
Load diff
67
src/app.css
67
src/app.css
|
@ -10,7 +10,7 @@ body {
|
|||
min-height: 100%;
|
||||
padding: 1em;
|
||||
width: 100%;
|
||||
font-size: 1.3em;
|
||||
font-size: 1.15em;
|
||||
border: double 3px #ddd;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
|
@ -28,5 +28,68 @@ a:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
a:visited {
|
||||
color: #437fad;
|
||||
color: #325f81;
|
||||
}
|
||||
.y-accordions,
|
||||
.y-accordion-header-button {
|
||||
background-color: #ddd;
|
||||
}
|
||||
.yaccordion .y-accordians .y-accordion-header-button {
|
||||
background-color: #eee;
|
||||
}
|
||||
/* modal styles */
|
||||
.y-modal .y-modal-container {
|
||||
background-color: #eee;
|
||||
border: solid 3px #3a8ea5;
|
||||
border-radius: 15px;
|
||||
box-shadow: 5px 5px 3px #6a6d6a;
|
||||
width: 90%;
|
||||
margin: 1em auto;
|
||||
max-width: 40em;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-content {
|
||||
width: 100%;
|
||||
border-width: 0;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-header {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
background-color: #999;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-header .y-modal-title {
|
||||
font-size: 1.5em;
|
||||
margin: auto;
|
||||
}
|
||||
.y-modal .y-modal-container .y-modal-body {
|
||||
background-color: #eee;
|
||||
}
|
||||
.y-modal .y-modal-container div.instances {
|
||||
background-color: #ddd;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.y-modal .y-modal-container p.instances,
|
||||
.y-modal .y-modal-container li p {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* end modals */
|
||||
/* popover styles */
|
||||
.y-popover {
|
||||
z-index: 10000;
|
||||
}
|
||||
.y-popover a {
|
||||
font-size: 110%;
|
||||
}
|
||||
.y-popover a:hover {
|
||||
color: #000;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.y-popover a:visited {
|
||||
color: #325f81;
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
<script>
|
||||
// Ref: https://svelte.dev/repl/a5f4d395b15a44d48a6b2239ef705fc4?version=3.35.0
|
||||
// based on suggestions from:
|
||||
// Inclusive Components by Heydon Pickering https://inclusive-components.design/collapsible-sections/
|
||||
export let headerText;
|
||||
|
||||
let expanded = false;
|
||||
</script>
|
||||
|
||||
<div class="collapsible">
|
||||
<h3>
|
||||
<button aria-expanded={expanded} on:click={() => expanded = !expanded}>{headerText}
|
||||
<svg class="indicator" viewBox="0 0 20 20" fill="none" >
|
||||
<path class="vert" d="M10 1V19" stroke="black" stroke-width="2"/>
|
||||
<path d="M1 10L19 10" stroke="black" stroke-width="2"/>
|
||||
</svg>
|
||||
</button>
|
||||
</h3>
|
||||
<!-- <div class='tree' hidden={!expanded}>
|
||||
<svg viewBox="0 0 60 60" fill="#ccc">
|
||||
<path class="open" d="M40 1 V69" stroke="#444" stroke-width="4" />
|
||||
</svg>
|
||||
</div> -->
|
||||
<div class='contents' hidden={!expanded}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.collapsible {
|
||||
margin: 8px 0 4px 0;
|
||||
}
|
||||
.collapsible .collapsible h3 button {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.tree { float: left; }
|
||||
.contents {
|
||||
float: left;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
border-bottom: dashed #aaa 1px;
|
||||
padding-bottom: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
button {
|
||||
background-color: var(--background, #fff);
|
||||
color: var(--gray-darkest, #282828);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border: 1px #aaa solid;
|
||||
margin-left: 0px;
|
||||
padding: 0.7em 0.7em;
|
||||
}
|
||||
button[aria-expanded="true"] { border-bottom: 2px solid var(--gray-light, #eee); }
|
||||
button[aria-expanded="true"] .vert { display: none; }
|
||||
button:focus svg { outline: 1firtpx solid; }
|
||||
button[aria-expanded="true"] rect { fill: currentColor; }
|
||||
svg.indicator {
|
||||
height: 1.0em;
|
||||
width: 1.0em;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
margin-top: 4px;;
|
||||
}
|
||||
.tree:hover svg { outline: 1firtpx solid; }
|
||||
|
||||
</style>
|
|
@ -7,8 +7,9 @@
|
|||
export let context = 'none';
|
||||
export let filterValues;
|
||||
export let histogram; // an array with key = filter value, val = number of technologies
|
||||
export const colours = []; // an array of colour values with
|
||||
export let colours = false; // an array of colour values with
|
||||
//console.log('Filterable contextFilter: ', filterValues);
|
||||
export let colour_type = 'circle';
|
||||
let activeFiltersString = 'None';
|
||||
|
||||
//
|
||||
|
@ -74,6 +75,7 @@
|
|||
isActive = getActive($filterValues);
|
||||
activeFiltersString = joinActive(isActive);
|
||||
filterText = filterVerbage(isActive);
|
||||
//if (colours) { console.log('we have colours! ', colours); }
|
||||
}
|
||||
|
||||
//console.log('histogram: ', histogram);
|
||||
|
@ -92,7 +94,8 @@
|
|||
<span class="filter {context}" class:active={filter.active}
|
||||
on:click="{() => { filter.active = !filter.active; updateFilterValues(filter);}}"
|
||||
on:keypress="{() => {filter.active = !filter.active; updateFilterValues(filter);}}"
|
||||
name="{filter.id}" role="button" tabindex={tabindex + filter.id}>{filter.name}{#if (histogram[filter.name] > 1) } <span class='histogram' title="There are {histogram[filter.name]} webservices in this category"> ({histogram[filter.name]})</span>{/if}</span><wbr/>
|
||||
name="{filter.id}" role="button" tabindex={tabindex + filter.id}>{filter.name}{#if (histogram[filter.name] > 1) }<span class='histogram' title="There are {histogram[filter.name]} webservices in this category"> ({histogram[filter.name]})</span>{/if}{#if
|
||||
(colours && colour_type == 'circle')}<span class='circle' style="background-color: {colours[filter.name].colour}"></span>{/if}</span><wbr/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
@ -149,7 +152,7 @@
|
|||
|
||||
/* instance-specific styles */
|
||||
.filter {
|
||||
font-size: 80%;
|
||||
font-size: 0.8em;
|
||||
margin-right: 0.5em;
|
||||
line-height: 2.5;
|
||||
padding: 6px 8px;
|
||||
|
@ -157,7 +160,7 @@
|
|||
white-space: nowrap;
|
||||
word-break: normal;
|
||||
box-shadow: 3px 3px 3px #6a6d6a;
|
||||
color: #222;
|
||||
color: #888;
|
||||
}
|
||||
.filter:hover { box-shadow: 6px 6px 5px #aaa; color: #000; }
|
||||
.filter:active { box-shadow: 0px 0px 3px #727372; color: #fff;}
|
||||
|
@ -188,4 +191,18 @@
|
|||
.filter .histogram {
|
||||
color: #aaa;
|
||||
}
|
||||
.circle {
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: #555;
|
||||
border: 1px solid #555;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
opacity: .2;
|
||||
}
|
||||
.active .circle {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
<script>
|
||||
// a 'technology' object
|
||||
export let technology;
|
||||
export let licenses;
|
||||
export let affiliate_colours;
|
||||
import { Button, Modal, ModalBody, ModalFooter, Popover } from 'yesvelte';
|
||||
import { Button, ButtonGroup, El, Modal, ModalBody, ModalFooter,
|
||||
ModalHeader, ModalTitle, Popover, PopoverBody, PopoverHeader } from 'yesvelte';
|
||||
|
||||
let showModal = false;
|
||||
export let showModal = false;
|
||||
|
||||
const formatter = new Intl.ListFormat('en', {
|
||||
style: 'long',
|
||||
|
@ -31,137 +33,215 @@
|
|||
tech.hasOwnProperty('instances') &&
|
||||
tech.instances.constructor === Array &&
|
||||
tech.instances.length
|
||||
) return true;
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div on:click={() => (showModal = !showModal)} class="tile technology">
|
||||
<div class="links">
|
||||
<span class="website"><a href="{technology.website}" title="Project site for {technology.name}">P</a></span>
|
||||
{#if technology.repository}<span class="repository"><a
|
||||
href={technology.repository} title="The source code repository for {technology.name}">R</a></span>{/if}
|
||||
{#if technology.wikipedia}<span class="wikipedia"><a
|
||||
href={technology.wikipedia}
|
||||
title="Wikipedia Page for {technology.name}">W</a></span>{/if}
|
||||
</div>
|
||||
<!-- making a dive act like a button: https://kvack.dev/blog/make-that-div-behave-like-a-button -->
|
||||
|
||||
<button on:click={() => (showModal = !showModal)} tabindex=0 class="tile technology" col>
|
||||
<h2>{technology.name}</h2>
|
||||
<div class="links">
|
||||
<span class="website">P</span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader><strong>Project website</strong> for {technology.name}</PopoverHeader>
|
||||
<PopoverBody><a href="{technology.website}">{technology.website}</a></PopoverBody>
|
||||
</Popover>
|
||||
{#if technology.repository}<span class="repository">S</span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader><strong>Source code</strong> repository for {technology.name}</PopoverHeader>
|
||||
<PopoverBody><a href={technology.repository}>{technology.repository}</a></PopoverBody>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if technology.wikipedia}<span class="wikipedia">W</span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader><strong>Wikipedia entry</strong> for {technology.name}</PopoverHeader>
|
||||
<PopoverBody><a href={technology.wikipedia}>{technology.wikipedia}</a></PopoverBody>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="description">{technology.description}{#if technology.extended_description}<span
|
||||
title={technology.extended_description}>i</span>{/if}
|
||||
</p>
|
||||
{#if technology.categories}<p class="categories">{pluraliser('Category',
|
||||
<div class="details">
|
||||
{#if technology.categories}<p class="property categories">{pluraliser('Category',
|
||||
'Categories', technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if}
|
||||
{#if technology.analogues}<p class="analogues">Alternative to <span
|
||||
{#if technology.analogues}<p class="property analogues">Alternative to <span
|
||||
class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if}
|
||||
{#if technology.license}<p class="license"
|
||||
{#if technology.license}<p class="property license"
|
||||
title="The libre license for this project is {technology.license}">License: <span
|
||||
class="value">{technology.license}</span></p>{/if}
|
||||
</div>
|
||||
{#if hasInstances(technology)}
|
||||
<div class="instances">
|
||||
<p>{#each technology.instances as instance}<a
|
||||
href="https://{instance.domain}"
|
||||
title="{technology.name} instance {instance.domain} hosted on '{instance.host}' by {instance.affiliation}"><span class="marker circle"
|
||||
<p>
|
||||
{#each technology.instances as instance}
|
||||
<span class="marker circle"
|
||||
style="background-color: {affiliate_colours[instance.affiliation].colour}"
|
||||
></span></a>{/each}
|
||||
></span>
|
||||
<Popover trigger="hover">
|
||||
<PopoverHeader>{technology.name} Instance</PopoverHeader>
|
||||
<PopoverBody><a href="https://{instance.domain}">https://{instance.domain}</a> hosted on '{instance.host}' by {instance.affiliation}</PopoverBody>
|
||||
</Popover>
|
||||
{/each}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="instances">Nothing here yet...</div>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<Modal title="{technology.name}" bind:showModal>
|
||||
<Modal scrollable bind:show={showModal} autoClose dismissible>
|
||||
<ModalHeader>
|
||||
<ModalTitle>{technology.name}</ModalTitle>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
Modal stuff...
|
||||
<p class="description">{technology.description}{#if technology.extended_description}{technology.extended_description}{/if}
|
||||
</p>
|
||||
<div class="links">
|
||||
<p class="link"><span class="label">Project website</span>: <span class="website"><a href="{technology.website}" title="Project site for {technology.name}">{technology.website}</a></span></p>
|
||||
{#if technology.repository}<p class="link"><span class="label">Source code repository</span>: <span class="repository"><a
|
||||
href={technology.repository} title="The source code repository for {technology.name}">{technology.repository}</a></span></p>{/if}
|
||||
{#if technology.wikipedia}<p class="link"><span class="label">Wikipedia entry</span>: <span class="wikipedia"><a
|
||||
href={technology.wikipedia}
|
||||
title="Wikipedia Page for {technology.name}">{technology.wikipedia}</a></span></p>{/if}
|
||||
</div>
|
||||
<div class="details">
|
||||
{#if technology.categories}<p class="property categories">{pluraliser('Category',
|
||||
'Categories', technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if}
|
||||
{#if technology.analogues}<p class="property analogues">Alternative to <span
|
||||
class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if}
|
||||
{#if technology.license}<p class="property license"
|
||||
title="The libre license for this project is {technology.license}">License: <span
|
||||
class="value"><a href="{licenses[technology.license].url}">{licenses[technology.license].name}</a></span></p>{/if}
|
||||
</div>
|
||||
</ModalBody>
|
||||
{#if hasInstances(technology)}
|
||||
<div class="instances">
|
||||
<p class="instances">Instances of {technology.name}:</p>
|
||||
<ol>
|
||||
{#each technology.instances as instance}
|
||||
<li>
|
||||
<p class="instance"><a href="https://{instance.domain}">{instance.domain}</a> hosted on '{instance.host}' <span class="marker circle" style="background-color: {affiliate_colours[instance.affiliation].colour}"/> by {instance.affiliation}".
|
||||
</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="instances">Nothing here yet...</div>
|
||||
{/if}
|
||||
<!--<ModalFooter>
|
||||
Previous and Next buttons to go here.
|
||||
</ModalFooter>-->
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.tile {
|
||||
display: inline-block;
|
||||
height: 515px;
|
||||
/*min-width: 240px;
|
||||
max-width: 300px;*/
|
||||
width: 290px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 5px 5px 3px #6a6d6a;
|
||||
.tile.technology {
|
||||
|
||||
background-color: #eee;
|
||||
border: solid 3px #3a8ea5;
|
||||
border-radius: 15px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: #eee;
|
||||
margin-top: 8px;
|
||||
margin-right: 18px;
|
||||
box-shadow: 5px 5px 3px #6a6d6a;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
height: 515px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 18px;
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top !important;
|
||||
width: 290px;
|
||||
}
|
||||
.tile .links {
|
||||
.tile.technology .links {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 2px 6px;
|
||||
font-size: 90%;
|
||||
}
|
||||
.tile:hover {
|
||||
box-shadow: 10px 10px 6px #727372;
|
||||
.tile.technology:hover {
|
||||
box-shadow: 6px 6px 5px #727372;
|
||||
}
|
||||
.tile:active {
|
||||
.tile.technology:active {
|
||||
box-shadow: 0 0 0;
|
||||
border-color: #000;
|
||||
}
|
||||
.tile p.description {
|
||||
height: 200px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.tile .links h2 a {
|
||||
.tile.technology .links h2 a {
|
||||
color: #e6c4fc;
|
||||
}
|
||||
.tile .links a:visited {
|
||||
.tile.technology .links a:visited {
|
||||
color: #ced0ff;
|
||||
}
|
||||
.tile .links a:hover,
|
||||
.tile:hover h2 a {
|
||||
.tile.technology .links a:hover,
|
||||
.tile.technology:hover h2 a {
|
||||
color: #fff !important;
|
||||
}
|
||||
.tile h2 {
|
||||
.tile.technology h2 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background-color: #999;
|
||||
text-align: center;
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
}
|
||||
.tile h2 a {
|
||||
.tile.technology h2 a {
|
||||
color: #e6c4fc;
|
||||
}
|
||||
.tile h2 a:visited {
|
||||
.tile.technology h2 a:visited {
|
||||
color: #ced0ff;
|
||||
}
|
||||
.tile h2 a:hover {
|
||||
.tile.technology h2 a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.tile .description {
|
||||
height: 260px;
|
||||
.tile.technology .description {
|
||||
height: 200px;
|
||||
line-height: 1.2em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-top: 0.5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.tile p {
|
||||
padding: 0 1em 0.2em 1em;
|
||||
.tile.technology p {
|
||||
padding: 0 0.7em 0.2em 0.7em;
|
||||
font-size: 80%;
|
||||
color: #555;
|
||||
}
|
||||
.instances {
|
||||
.tile.technology p.property {
|
||||
padding: 0.2em 0.7em;
|
||||
margin: 0;
|
||||
}
|
||||
.tile.technology .details {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.tile.technology .instances {
|
||||
background-color: #ddd;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 3em;
|
||||
height: 2.5em;
|
||||
width: 100% !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tile.technology .instances p {
|
||||
width: 100%;
|
||||
padding-top: 0.7em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.instances .marker {
|
||||
.tile.technology .instances .marker {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.webservices li {
|
||||
list-style-type: none;
|
||||
}
|
||||
.value {
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
|
@ -170,16 +250,14 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
.circle {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: #555;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
.square {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
background-color: #555;
|
||||
display: inline-block;
|
||||
.circle:hover {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import '../app.css';
|
||||
import tabler from 'yesvelte/css/tabler.min.css?url';
|
||||
import daisyui from 'yesvelte/css/daisyui.min.css?url';
|
||||
import '../app.css';
|
||||
|
||||
// you can change theme of your project from here.
|
||||
let theme = 'tabler';
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
// for multiple async fetches: https://youtu.be/EQy-AYhZIlE?si=FwyAPUjbixSUlc9q&t=490
|
||||
export const load = async ({ fetch }) => {
|
||||
const webservicesResult = await fetch('https://static.magnificent.nz/webservices/webservices.json');
|
||||
const webservicesData = await webservicesResult.json();
|
||||
|
||||
//console.log(webservicesData);
|
||||
return {
|
||||
webservices: {
|
||||
technologies: webservicesData.technologies,
|
||||
affiliates: webservicesData.affiliates,
|
||||
hosts: webservicesData.hosts
|
||||
}
|
||||
};
|
||||
}
|
||||
const webservicesResult = await fetch(
|
||||
'https://static.magnificent.nz/webservices/webservices.json'
|
||||
);
|
||||
const webservicesData = await webservicesResult.json();
|
||||
|
||||
//console.log(webservicesData);
|
||||
return {
|
||||
webservices: {
|
||||
technologies: webservicesData.technologies,
|
||||
affiliates: webservicesData.affiliates,
|
||||
hosts: webservicesData.hosts,
|
||||
licenses: webservicesData.licenses
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,130 +8,187 @@
|
|||
|
||||
// the filters
|
||||
import { setFilter, getFilter } from '$lib/components/filter.js';
|
||||
import { Accordions, Accordion, AccordionBody } from 'yesvelte';
|
||||
import { Accordions, Accordion, AccordionBody, El } from 'yesvelte';
|
||||
import Filterable from '$lib/components/Filterable.svelte';
|
||||
// collapsible sections
|
||||
//import CollapsibleSection from '$lib/components/CollapsibleSection.svelte';
|
||||
import TechnologyTile from '$lib/components/TechnologyTile.svelte';
|
||||
// URL-related stuff
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let tabindex = 0; // a counter which facilitates keyboard navigation.
|
||||
let showModal = true; // use to make a technology modal show when rendered
|
||||
|
||||
//
|
||||
// digest and return useful info from the Webservices JSON feed
|
||||
function processData(webservices) {
|
||||
let stats = { 'instances': 0, 'technologies': 0, 'licenses': 0,'weak': 0, 'copyleft': 0 };
|
||||
let all = []; // all services
|
||||
let current = []; // those with active instances
|
||||
let future = []; // those that are planned
|
||||
// properties of technologies
|
||||
let category_list = [];
|
||||
let analogue_list = [];
|
||||
let license_list = [];
|
||||
// properties of instances
|
||||
let status_list = [];
|
||||
let affiliate_list = [];
|
||||
let host_list = [];
|
||||
// return the % a is of b
|
||||
function percent(a, b) {
|
||||
return new Intl.NumberFormat('en', { style: "percent" }).format(a/b);
|
||||
}
|
||||
|
||||
// actual objects
|
||||
let hosts = [];
|
||||
let affiliates = [];
|
||||
//
|
||||
// digest and return useful info from the Webservices JSON feed
|
||||
function processData(webservices) {
|
||||
let stats = { instances: 0, technologies: 0, licenses: 0, weak: 0, copyleft: 0 };
|
||||
let all = []; // all services
|
||||
let current = []; // those with active instances
|
||||
let future = []; // those that are planned
|
||||
// properties of technologies
|
||||
let category_list = [];
|
||||
let analogue_list = [];
|
||||
let license_list = [];
|
||||
// properties of instances
|
||||
let status_list = [];
|
||||
let affiliate_list = [];
|
||||
let host_list = [];
|
||||
|
||||
//
|
||||
// just a trivial reassignment
|
||||
//let hosts = webservices.hosts;
|
||||
//
|
||||
// pull out relevant info in useful chunks.
|
||||
for (let key in webservices.technologies) {
|
||||
let tech = webservices.technologies[key];
|
||||
tech['name'] = key;
|
||||
console.log('tech '+key+' ('+tech.categories.length+'):', tech.categories);
|
||||
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array) {
|
||||
//console.log('tech '+key+' ('+tech.categories.length+'):', tech.categories);
|
||||
tech.categories.forEach(function (category, index) {
|
||||
if (category_list.hasOwnProperty(category)) category_list[category]++;
|
||||
else category_list[category] = 1;
|
||||
});
|
||||
}
|
||||
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array) {
|
||||
tech.analogues.forEach(function (analogue, index) {
|
||||
if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++;
|
||||
else analogue_list[analogue] = 1;
|
||||
});
|
||||
}
|
||||
if (tech.hasOwnProperty('license')) {
|
||||
let license = tech.license;
|
||||
if (license_list.hasOwnProperty(license)) license_list[license]++;
|
||||
else license_list[license] = 1;
|
||||
}
|
||||
if (hasInstances(tech)) {
|
||||
current.push(tech);
|
||||
//console.log(tech.name + ': ' + tech.instances.length + ' instances...');
|
||||
tech.instances.forEach(function (instance, i) {
|
||||
stats.instances++;
|
||||
if (instance.hasOwnProperty('status')) {
|
||||
let tag = instance.status;
|
||||
if (status_list.hasOwnProperty(tag)) status_list[tag]++;
|
||||
else status_list[tag] = 1;
|
||||
}
|
||||
if (instance.hasOwnProperty('affiliation')) {
|
||||
let tag = instance.affiliation;
|
||||
// actual objects
|
||||
let hosts = [];
|
||||
let affiliates = [];
|
||||
let licenses = [];
|
||||
|
||||
if (affiliate_list.hasOwnProperty(tag)) affiliate_list[tag]++;
|
||||
else affiliate_list[tag] = 1;
|
||||
}
|
||||
if (instance.hasOwnProperty('host')) {
|
||||
let tag = instance.host;
|
||||
if (host_list.hasOwnProperty(tag)) host_list[tag]++;
|
||||
else host_list[tag] = 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
future[key] = tech;
|
||||
}
|
||||
all[key] = tech;
|
||||
}
|
||||
for (let key in webservices.hosts) {
|
||||
//console.log('key: ', key);
|
||||
let host = webservices.hosts[key];
|
||||
host['name'] = key;
|
||||
//console.log('host: ', host);
|
||||
if (
|
||||
host.hasOwnProperty('domain') &&
|
||||
!(host.hasOwnProperty('status') && host.status == 'retired')
|
||||
) {
|
||||
hosts[key] = host;
|
||||
}
|
||||
}
|
||||
for (let key in webservices.affiliates) {
|
||||
let affiliate = webservices.affiliates[key];
|
||||
affiliates[key] = affiliate;
|
||||
}
|
||||
//
|
||||
// just a trivial reassignment
|
||||
//let hosts = webservices.hosts;
|
||||
//
|
||||
// pull out relevant info in useful chunks.
|
||||
for (let key in webservices.technologies) {
|
||||
let tech = webservices.technologies[key];
|
||||
console.log('considering ' + countArray(webservices.technologies) + ' technologies at the start.');
|
||||
tech['name'] = key;
|
||||
//console.log('tech '+key+' ('+tech.categories.length+'):', tech.categories);
|
||||
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array) {
|
||||
//console.log('tech '+key+' ('+tech.categories.length+'):', tech.categories);
|
||||
tech.categories.forEach(function (category, index) {
|
||||
if (category_list.hasOwnProperty(category)) category_list[category]++;
|
||||
else category_list[category] = 1;
|
||||
});
|
||||
}
|
||||
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array) {
|
||||
tech.analogues.forEach(function (analogue, index) {
|
||||
if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++;
|
||||
else analogue_list[analogue] = 1;
|
||||
});
|
||||
}
|
||||
if (tech.hasOwnProperty('license')) {
|
||||
let license = tech.license;
|
||||
if (license_list.hasOwnProperty(license)) license_list[license]++;
|
||||
else license_list[license] = 1;
|
||||
}
|
||||
if (hasInstances(tech) || tech.instances == 'all') {
|
||||
if (tech.instances != 'all') {
|
||||
current.push(tech);
|
||||
//console.log(tech.name + ': ' + tech.instances.length + ' instances...');
|
||||
tech.instances.forEach(function (instance, i) {
|
||||
stats.instances++;
|
||||
if (instance.hasOwnProperty('status')) {
|
||||
let tag = instance.status;
|
||||
if (status_list.hasOwnProperty(tag)) status_list[tag]++;
|
||||
else status_list[tag] = 1;
|
||||
}
|
||||
if (instance.hasOwnProperty('affiliation')) {
|
||||
let tag = instance.affiliation;
|
||||
|
||||
if (affiliate_list.hasOwnProperty(tag)) affiliate_list[tag]++;
|
||||
else affiliate_list[tag] = 1;
|
||||
}
|
||||
if (instance.hasOwnProperty('host')) {
|
||||
let tag = instance.host;
|
||||
if (host_list.hasOwnProperty(tag)) host_list[tag]++;
|
||||
else host_list[tag] = 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('tech ' + tech.name + ' applies to *all* instances');
|
||||
}
|
||||
} else {
|
||||
future[key] = tech;
|
||||
}
|
||||
all[key] = tech;
|
||||
}
|
||||
for (let key in webservices.hosts) {
|
||||
//console.log('key: ', key);
|
||||
let host = webservices.hosts[key];
|
||||
host['name'] = key;
|
||||
//console.log('host: ', host);
|
||||
if (
|
||||
host.hasOwnProperty('domain') &&
|
||||
!(host.hasOwnProperty('status') && host.status == 'retired')
|
||||
) {
|
||||
hosts[key] = host;
|
||||
}
|
||||
}
|
||||
for (let key in webservices.affiliates) {
|
||||
let affiliate = webservices.affiliates[key];
|
||||
affiliates[key] = affiliate;
|
||||
}
|
||||
for (let key in webservices.licenses) {
|
||||
let license = webservices.licenses[key];
|
||||
licenses[key] = license;
|
||||
}
|
||||
|
||||
// assign stats based on gathered data...
|
||||
stats.current = current.length;
|
||||
stats.future = future.length;
|
||||
console.log('future technologies: ', future);
|
||||
stats.future = countArray(future);
|
||||
console.log('license_list: ', license_list);
|
||||
stats.licenses = license_list.length;
|
||||
stats.licenses = countArray(license_list);
|
||||
stats.licensed_technologies = numLicensedTechnologies(license_list);
|
||||
stats.strong = numCopyleft(license_list, references.copyleft_list);
|
||||
stats.weak = stats.licensed_technologies - stats.strong;
|
||||
console.log('host_list: ', host_list);
|
||||
console.log('number of hosts: ', countArray(host_list));
|
||||
stats.hosts = countArray(host_list);
|
||||
console.log('stats: ', stats);
|
||||
|
||||
return {
|
||||
stats: stats,
|
||||
all_services: all,
|
||||
active_services: current,
|
||||
candidate_services: future,
|
||||
tech_lists: {
|
||||
category_list: category_list,
|
||||
analogue_list: analogue_list,
|
||||
license_list: license_list
|
||||
},
|
||||
instance_lists: {
|
||||
status_list: status_list,
|
||||
affiliate_list: affiliate_list,
|
||||
host_list: host_list
|
||||
},
|
||||
hosts: hosts,
|
||||
affiliates: affiliates
|
||||
};
|
||||
}
|
||||
return {
|
||||
stats: stats,
|
||||
all_services: all,
|
||||
active_services: current,
|
||||
candidate_services: future,
|
||||
tech_lists: {
|
||||
category_list: category_list,
|
||||
analogue_list: analogue_list,
|
||||
license_list: license_list
|
||||
},
|
||||
instance_lists: {
|
||||
status_list: status_list,
|
||||
affiliate_list: affiliate_list,
|
||||
host_list: host_list
|
||||
},
|
||||
hosts: hosts,
|
||||
affiliates: affiliates,
|
||||
licenses: licenses
|
||||
};
|
||||
}
|
||||
|
||||
// when compound arrays don't have a valid .length key, use this
|
||||
function countArray(arr) {
|
||||
var count = 0;
|
||||
for (var k in arr) {
|
||||
if (arr.hasOwnProperty(k)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// count copyleft licenses, by only checking the copyleft totals
|
||||
function numCopyleft(list, copyleft) {
|
||||
var count = 0;
|
||||
copyleft.forEach(function (license) {
|
||||
//console.log('found ' + list[license] + ' technologies using ' + license);
|
||||
count += list[license];
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
function numLicensedTechnologies(list) {
|
||||
var count = 0;
|
||||
for (var key in list) {
|
||||
//console.log('found ' + list[key] + ' apps for license ' + key);
|
||||
count += list[key];
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// return an object with ob's keys ordered alphabetically, with an id from an object
|
||||
function getSortedFilter(ob) {
|
||||
|
@ -145,23 +202,22 @@
|
|||
// sort the array alphabetically
|
||||
keys.sort();
|
||||
// then create a dictionary of them
|
||||
keys.forEach(function(name) {
|
||||
key_array.push({ "id": i++, "name": name, "active": true });
|
||||
keys.forEach(function (name) {
|
||||
key_array.push({ id: i++, name: name, active: true });
|
||||
});
|
||||
return key_array;
|
||||
}
|
||||
|
||||
// return true if a tech object includes valid instances
|
||||
function hasInstances(tech) {
|
||||
if (
|
||||
tech.hasOwnProperty('instances') &&
|
||||
if (tech.hasOwnProperty('instances') &&
|
||||
tech.instances.constructor === Array &&
|
||||
tech.instances.length
|
||||
) return true;
|
||||
tech.instances.length) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// get intersection: ref https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
|
||||
function getIntersection(a, b) {
|
||||
const set1 = new Set(a);
|
||||
|
@ -172,7 +228,6 @@
|
|||
return intersection;
|
||||
}
|
||||
|
||||
|
||||
// assign colours from a set of differentiated colours to a list of tags...
|
||||
// this one is for 'hosts'
|
||||
//
|
||||
|
@ -187,7 +242,10 @@
|
|||
for (let index in host_list) {
|
||||
console.log(host_list[index]);
|
||||
let hostname = host_list[index].name;
|
||||
if (hosts[hostname].hasOwnProperty('domain') && hosts[hostname].hasOwnProperty('affiliation')) {
|
||||
if (
|
||||
hosts[hostname].hasOwnProperty('domain') &&
|
||||
hosts[hostname].hasOwnProperty('affiliation')
|
||||
) {
|
||||
host_array[hostname] = {
|
||||
colour: colours[i++],
|
||||
domain: hosts[hostname].domain,
|
||||
|
@ -208,8 +266,8 @@
|
|||
//console.log('affiliate_list:', affiliate_list);
|
||||
|
||||
for (let index in affiliate_list) {
|
||||
//console.log('affiliate:', affiliate);
|
||||
let affiliate = affiliate_list[index].name;
|
||||
//console.log('affiliate:', affiliate);
|
||||
if (
|
||||
affiliates[affiliate].hasOwnProperty('name') &&
|
||||
affiliates[affiliate].hasOwnProperty('website')
|
||||
|
@ -229,6 +287,8 @@
|
|||
return technologies.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
// set previous and next on each technology
|
||||
|
||||
// generic function to find the intersection of two arrays
|
||||
// approach ref: https://bobbyhadz.com/blog/javascript-get-intersection-of-two-arrays
|
||||
function inCommon(a, b) {
|
||||
|
@ -243,9 +303,11 @@
|
|||
// converts a filter with id, name, and active fields into an array of names
|
||||
function flattenFilter(filter) {
|
||||
let flat = [];
|
||||
if (filter.constructor === Array) {
|
||||
filter.forEach(function(e) { if (e.active) flat.push(e.name) });
|
||||
}
|
||||
if (filter.constructor === Array) {
|
||||
filter.forEach(function (e) {
|
||||
if (e.active) flat.push(e.name);
|
||||
});
|
||||
}
|
||||
return flat;
|
||||
}
|
||||
|
||||
|
@ -253,15 +315,23 @@
|
|||
function filterTechnologiesByArray(technologies, field, list) {
|
||||
const included = [];
|
||||
technologies.forEach(function (tech) {
|
||||
if (hasInstances(tech)) {
|
||||
let arr = tech[field]
|
||||
const intersection = inCommon(arr, list);
|
||||
if (intersection && intersection.constructor === Array) {
|
||||
//console.log('intersection length: ', intersection.length);
|
||||
if (intersection.length > 0) {
|
||||
included.push(tech);
|
||||
if (hasInstances(tech) || tech.instances == 'all') {
|
||||
if (typeof tech[field] != 'undefined') {
|
||||
let arr = tech[field];
|
||||
//if (field == 'analogues') console.log('tech['+field+'] = ', tech[field]);
|
||||
const intersection = inCommon(arr, list);
|
||||
if (intersection && intersection.constructor === Array) {
|
||||
//console.log('intersection length: ', intersection.length);
|
||||
if (intersection.length > 0) {
|
||||
included.push(tech);
|
||||
}
|
||||
}
|
||||
// this tech doesn't define that field, so don't eliminate it from the list
|
||||
} else {
|
||||
included.push(tech);
|
||||
}
|
||||
} else {
|
||||
console.log('not including tech ', tech.name);
|
||||
}
|
||||
});
|
||||
//console.log('found ' + included.length + ' technologies.');
|
||||
|
@ -272,16 +342,55 @@
|
|||
function filterTechnologiesByValue(technologies, field, list) {
|
||||
const included = [];
|
||||
technologies.forEach(function (tech) {
|
||||
if (hasInstances(tech) || tech.instances == 'all') {
|
||||
if (typeof tech[field] != 'undefined') {
|
||||
let arr = [tech[field]];
|
||||
const intersection = inCommon(arr, list);
|
||||
if (intersection && intersection.constructor === Array) {
|
||||
//console.log('intersection length: ', intersection.length);
|
||||
if (intersection.length > 0) {
|
||||
included.push(tech);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
included.push(tech);
|
||||
}
|
||||
}
|
||||
});
|
||||
//console.log('found ' + included.length + ' technologies.');
|
||||
return included;
|
||||
}
|
||||
|
||||
function filterTechnologiesByInstanceValue(technologies, field, list) {
|
||||
const included = [];
|
||||
technologies.forEach(function (tech) {
|
||||
var to_include = false;
|
||||
if (hasInstances(tech)) {
|
||||
let arr = [tech[field]];
|
||||
const intersection = inCommon(arr, list);
|
||||
if (intersection && intersection.constructor === Array) {
|
||||
//console.log('intersection length: ', intersection.length);
|
||||
if (intersection.length > 0) {
|
||||
included.push(tech);
|
||||
console.log('instances: ', tech.instances);
|
||||
for (let instance in tech.instances) {
|
||||
console.log('instance = ', instance);
|
||||
if (instance.hasOwnProperty(field)) {
|
||||
const intersection = inCommon([instance[field]], list);
|
||||
if (intersection && intersection.constructor === Array) {
|
||||
console.log('intersection length: ', intersection.length);
|
||||
// if any one instance belonging to the tech is in the set, add the tech.
|
||||
if (intersection.length > 0) {
|
||||
console.log('setting to include ',instance.domain);
|
||||
to_include = true;
|
||||
} else {
|
||||
// if an instance doesn't belong in the set, remove it.
|
||||
tech.instances[instance.domain].pop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('instance missing field ', field);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (to_include) {
|
||||
console.log('including ', tech.name);
|
||||
included.push(tech);
|
||||
}
|
||||
});
|
||||
//console.log('found ' + included.length + ' technologies.');
|
||||
return included;
|
||||
|
@ -290,6 +399,7 @@
|
|||
//
|
||||
// process the data received by the web request!
|
||||
const results = processData(webservices);
|
||||
const licenses = results.licenses;
|
||||
|
||||
//const technologies = webservices.technologies;
|
||||
//console.log(technologies);
|
||||
|
@ -323,30 +433,80 @@
|
|||
const affiliates = results.affiliates;
|
||||
//console.log('affiliate_data: ', results.affiliates);
|
||||
const host_colours = hostColours($hostFilter, colours, hosts);
|
||||
//console.log('host_colours:', host_colours);
|
||||
//console.log('host_colours:', host_colours);
|
||||
colours.sort();
|
||||
const affiliate_colours = affiliateColours($affiliateFilter, colours, affiliates);
|
||||
//console.log('affiliate_colours:',affiliate_colours);
|
||||
|
||||
//console.log('licenses:',licenses );
|
||||
|
||||
let filteredTechnologies;
|
||||
let technologies;
|
||||
|
||||
// reactive stuff...
|
||||
$: {
|
||||
console.log('about to filterTechnologiesByCategoryList');
|
||||
console.log(
|
||||
'about to filter technologies by categories - starting with ' +
|
||||
results.active_services.length +
|
||||
' technologies...'
|
||||
);
|
||||
// filter by categories
|
||||
filteredTechnologies = filterTechnologiesByArray(results.active_services, 'categories',
|
||||
flattenFilter($categoryFilter));
|
||||
filteredTechnologies = filterTechnologiesByArray(
|
||||
results.active_services,
|
||||
'categories',
|
||||
flattenFilter($categoryFilter)
|
||||
);
|
||||
console.log(
|
||||
'about to filter technologies by analogues - starting with ' +
|
||||
filteredTechnologies.length +
|
||||
' technologies...'
|
||||
);
|
||||
// filter by analogues
|
||||
filteredTechnologies = filterTechnologiesByArray(filteredTechnologies, 'analogues',
|
||||
flattenFilter($analogueFilter));
|
||||
filteredTechnologies = filterTechnologiesByValue(filteredTechnologies, 'license',
|
||||
flattenFilter($licenseFilter));
|
||||
filteredTechnologies = filterTechnologiesByArray(
|
||||
filteredTechnologies,
|
||||
'analogues',
|
||||
flattenFilter($analogueFilter)
|
||||
);
|
||||
/*console.log(
|
||||
'about to filter technologies by license - starting with ' +
|
||||
filteredTechnologies.length +
|
||||
' technologies...'
|
||||
);*/
|
||||
filteredTechnologies = filterTechnologiesByValue(
|
||||
filteredTechnologies,
|
||||
'license',
|
||||
flattenFilter($licenseFilter)
|
||||
);
|
||||
console.log(
|
||||
'about to filter technologies by instance status - starting with ' +
|
||||
filteredTechnologies.length +
|
||||
' technologies...'
|
||||
);
|
||||
/*filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
filteredTechnologies,
|
||||
'status',
|
||||
flattenFilter($statusFilter)
|
||||
)
|
||||
/*filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
filteredTechnologies,
|
||||
'affiliate',
|
||||
flattenFilter($affiliateFilter)
|
||||
)
|
||||
filteredTechnologies = filterTechnologiesByInstanceValue(
|
||||
filteredTechnologies,
|
||||
'host',
|
||||
flattenFilter($hostFilter)
|
||||
)*/
|
||||
|
||||
//filteredTechnologies = filterTechnologiesByCategoryList(results.all_services, flattenFilter($categoryFilter));
|
||||
console.log(
|
||||
'about to sort technologies alphabetically - starting with ' +
|
||||
filteredTechnologies.length +
|
||||
' technologies...'
|
||||
);
|
||||
const technologies = sortTechnologies(filteredTechnologies);
|
||||
//console.log('finally have ' + technologies.length + ' technologies left to show...');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="webservices">
|
||||
|
@ -355,41 +515,106 @@
|
|||
<div class="summary">
|
||||
<h2>Statistics</h2>
|
||||
<ul>
|
||||
<li><span class="label">Number of individual services: <span class="number">{results.stats.instances}</span></li>
|
||||
<li><span class="label">Number of individual technologies: <span class="number">{results.stats.current}</span></li>
|
||||
<li><span class="label">Number of new technologies to be added: <span class="number">{results.stats.future}</span></li>
|
||||
<li><span class="label">Number of libre licenses used by these technologies: <span class="number">{results.stats.licenses}</span>
|
||||
<ul>
|
||||
<li><span class="label">'weak' corporate-exploitation-friendly open source licenses : <span class="number">{results.stats.weak}</span></li>
|
||||
<li><span class="label">'strong' user-protecting copyleft licenses: <span class="number">{results.stats.copyleft}</span></li>
|
||||
</ul>
|
||||
<li>
|
||||
<span class="label">Current web service instances:
|
||||
<span class="number">{results.stats.instances}</span>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">Active web service technologies:
|
||||
<span class="number">{results.stats.current}</span>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">New technologies to be added:
|
||||
<span class="number">{results.stats.future}</span>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">Licenses used by these
|
||||
<span class="number">{results.stats.licensed_technologies}</span>
|
||||
technologies:
|
||||
<span class="number">{results.stats.licenses}</span>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="label">'strong' copyleft licenses:
|
||||
<span class="number">{results.stats.strong}</span>
|
||||
(<span class="number">{percent(results.stats.strong, results.stats.licensed_technologies)}</span>)
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">'weak' open source licenses :
|
||||
<span class="number">{results.stats.weak}</span>
|
||||
(<span class="number">{percent(results.stats.weak, results.stats.licensed_technologies)}</span>)
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li><span class="label">Total number of services: <span class="number">{results.stats.instances}</span></li>
|
||||
<li>
|
||||
<span class="label">Number of hosts:
|
||||
<span class="number">{results.stats.hosts}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Web Services</h1>
|
||||
<p>This sites exists to provide an 'always-up-to-date', in-depth description of the
|
||||
<p>
|
||||
This sites exists to provide an 'always-up-to-date', in-depth description of the
|
||||
<a href="https://tech.oeru.org/foss-libresoftware-its-about-clarity-and-values"
|
||||
title="What do I mean by 'libre' software?">libre software</a> web services I (<a href="https://davelane.nz" title="Who is this Dave Lane character?">Dave Lane</a>) have set up and maintain.
|
||||
title="What do I mean by 'libre' software?">libre software</a>
|
||||
web services I (<a href="https://davelane.nz"
|
||||
title="Who is this Dave Lane character?">Dave Lane</a>) have set up and maintain.
|
||||
</p>
|
||||
<p>These services are run primarily on commodity Linux Virtual Private Servers, aka 'hosts', which I also maintain. Some systems are also run on real physical hosts either in my home hosting facility or in commercial hosting facilities. These hosts are based in Aotearoa NZ, Singapore, the US, and in Germany. Almost all run an LTS (Long Term Support) version of Ubuntu Linux although a few hosts run Debian Linux.</p>
|
||||
<p>Almost all the hosts are <a href="https://tech.oeru.org/creating-your-own-oer-foundation-style-libre-self-hosting-infrastructure-docker-compose-and-ubuntu">configured in a particular way</a> which allows multiple services to run harmoniously on the same host. I use a set of somewhere between one and twenty-one Docker containers (which I run via Docker Compose) for each service, to keep different services, each of which has different software dependencies, from interfering with one another. Each service is made available to the Internet via a 'reverse proxy' (for which I use Nginx) and user interactions with them are secured (encrypted) with Let's Encrypt SSL certificates (which renew automatically).
|
||||
<p>
|
||||
These services are run primarily on commodity Linux Virtual Private Servers, aka 'hosts',
|
||||
which I also maintain. Some systems are also run on real physical hosts either in my home
|
||||
hosting facility or in commercial hosting facilities. These hosts are based in Aotearoa NZ,
|
||||
Singapore, the US, and in Germany. Almost all run an LTS (Long Term Support) version of Ubuntu
|
||||
Linux although a few hosts run Debian Linux.
|
||||
</p>
|
||||
<p>
|
||||
Almost all the hosts are <a href="https://tech.oeru.org/creating-your-own-oer-foundation-style-libre-self-hosting-infrastructure-docker-compose-and-ubuntu">configured in a particular way</a> which allows multiple services to run harmoniously on the same host. I use a set of somewhere
|
||||
between one and twenty-one Docker containers (which I run via Docker Compose) for each service,
|
||||
to keep different services, each of which has different software dependencies, from interfering
|
||||
with one another. Each service is made available to the Internet via a 'reverse proxy' (for which
|
||||
I use Nginx) and user interactions with them are secured (encrypted) with Let's Encrypt SSL certificates (which renew automatically).
|
||||
</p>
|
||||
<p>
|
||||
To manage the servers and keep them up-to-date, I use SSH to establish secure encrypted
|
||||
connections to them (via key-based authentication). I run updates of the hosts manually, and
|
||||
update individual services when required (urgently if updates are security-related). Some of
|
||||
the Docker containers I run I have built myself, in other cases I use those supplied by the
|
||||
communities developing the service.
|
||||
</p>
|
||||
<p>
|
||||
One huge advantage of the Docker Compose pattern is that I can rapidly move services between
|
||||
different hosts, or even replicate services very rapidly for development or staging purposes.
|
||||
Using them has revolutionised my hosting processes.
|
||||
</p>
|
||||
<p>To manage the servers and keep them up-to-date, I use SSH to establish secure encrypted connections to them (via key-based authentication). I run updates of the hosts manually, and update individual services when required (urgently if updates are security-related). Some of the Docker containers I run I have built myself, in other cases I use those supplied by the communities developing the service.</p>
|
||||
<p>One huge advantage of the Docker Compose pattern is that I can rapidly move services between different hosts, or even replicate services very rapidly for development or staging purposes. Using them has revolutionised my hosting processes.</p>
|
||||
</div>
|
||||
<Accordions>
|
||||
<Accordion title="Filters">
|
||||
<AccordionBody>
|
||||
<p>
|
||||
You can filter the software services listed below by the following filtering dimensions.
|
||||
Click a tag to remove or re-add it or select all or none, or invert your selection if it's
|
||||
more useful.
|
||||
</p>
|
||||
<Accordions>
|
||||
<Accordion title="Software Catergory Filter">
|
||||
<AccordionBody>
|
||||
<div class="filter-group categories">
|
||||
<Filterable filterValues={categoryFilter}
|
||||
histogram={results.tech_lists.category_list} context={'categories'}>
|
||||
<Filterable
|
||||
filterValues={categoryFilter}
|
||||
histogram={results.tech_lists.category_list}
|
||||
context={'categories'}>
|
||||
<h2 slot="title">Categories</h2>
|
||||
<p slot="description">
|
||||
These are general categories of software which help you understand the sort of software you're looking at, as well as identifying functionally similar software for comparison.
|
||||
</p>
|
||||
</Filterable>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
|
@ -397,10 +622,21 @@
|
|||
<Accordion title="Software Analogue Filter">
|
||||
<AccordionBody>
|
||||
<div class="filter-group analogues">
|
||||
<Filterable filterValues={analogueFilter}
|
||||
tabindex={tabindex}
|
||||
histogram={results.tech_lists.analogue_list} context={'analogues'}>
|
||||
<Filterable
|
||||
filterValues={analogueFilter}
|
||||
{tabindex}
|
||||
histogram={results.tech_lists.analogue_list}
|
||||
context={'analogues'}>
|
||||
<h2 slot="title">Analogues</h2>
|
||||
<p slot="description">
|
||||
Because all the software listed here is '<a
|
||||
href="https://tech.oeru.org/foss-libresoftware-its-about-clarity-and-values"
|
||||
>libre</a
|
||||
>', I've included the names of functionally similar but heavily marketed
|
||||
<a href="https://davelane.nz/proprietary">proprietary</a> software that is therefore
|
||||
better known to the general population, also for the purposes of understanding the
|
||||
use of the software shown and to what it can be compared.
|
||||
</p>
|
||||
</Filterable>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
|
@ -408,10 +644,18 @@
|
|||
<Accordion title="License Filter">
|
||||
<AccordionBody>
|
||||
<div class="filter-group licenses">
|
||||
<Filterable filterValues={licenseFilter}
|
||||
tabindex={tabindex}
|
||||
histogram={results.tech_lists.license_list} context={'licenses'}>
|
||||
<Filterable
|
||||
filterValues={licenseFilter}
|
||||
{tabindex}
|
||||
histogram={results.tech_lists.license_list}
|
||||
context={'licenses'}
|
||||
>
|
||||
<h2 slot="title">Open Source & Copyleft Licenses</h2>
|
||||
<p slot="description">
|
||||
These are the various '<a href="https://opensource.org/licenses">open source</a
|
||||
>' and '<a href="https://copyleft.org">copyleft</a>' licenses that apply to the
|
||||
software listed. You can chose
|
||||
</p>
|
||||
</Filterable>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
|
@ -419,10 +663,14 @@
|
|||
<Accordion title="Service Status Filter">
|
||||
<AccordionBody>
|
||||
<div class="filter-group statuses">
|
||||
<Filterable filterValues={statusFilter}
|
||||
tabindex={tabindex}
|
||||
histogram={results.instance_lists.status_list} context={'statuses'}>
|
||||
<Filterable
|
||||
filterValues={statusFilter}
|
||||
{tabindex}
|
||||
histogram={results.instance_lists.status_list}
|
||||
context={'statuses'}
|
||||
>
|
||||
<h2 slot="title">Statuses</h2>
|
||||
<p slot="description"></p>
|
||||
</Filterable>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
|
@ -431,9 +679,10 @@
|
|||
<AccordionBody>
|
||||
<div class="filter-group affilates">
|
||||
<Filterable filterValues={affiliateFilter}
|
||||
tabindex={tabindex}
|
||||
histogram={results.instance_lists.affiliate_list}
|
||||
context={'affiliates'}><h2 slot="title">Affiliates</h2>
|
||||
{tabindex} histogram={results.instance_lists.affiliate_list}
|
||||
context={'affiliates'}
|
||||
colours={affiliate_colours}><h2 slot="title">Affiliates</h2>
|
||||
<p slot="description"></p>
|
||||
</Filterable>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
|
@ -441,10 +690,12 @@
|
|||
<Accordion title="Service Host Filter">
|
||||
<AccordionBody>
|
||||
<div class="filter-group hosts">
|
||||
<Filterable filterValues={hostFilter}
|
||||
tabindex={tabindex}
|
||||
histogram={results.instance_lists.host_list} context={'hosts'}>
|
||||
<h2 slot="title">Hosts</h2>
|
||||
<Filterable filterValues={hostFilter}
|
||||
{tabindex}
|
||||
histogram={results.instance_lists.host_list}
|
||||
context={'hosts'}
|
||||
colours={host_colours}><h2 slot="title">Hosts</h2>
|
||||
<p slot="description"></p>
|
||||
</Filterable>
|
||||
</div>
|
||||
</AccordionBody>
|
||||
|
@ -453,9 +704,17 @@
|
|||
</AccordionBody>
|
||||
</Accordion>
|
||||
</Accordions>
|
||||
<p>Showing {filteredTechnologies.length} technology tiles.</p>
|
||||
<!--<p>Current URL: {$page.url}...</p>-->
|
||||
<div class="tiles">
|
||||
{#each filteredTechnologies as technology}
|
||||
<TechnologyTile technology={technology} affiliate_colours={affiliate_colours} />
|
||||
{#each filteredTechnologies as technology, index}
|
||||
<!--{#if (index != 1) }-->
|
||||
<TechnologyTile {technology} {affiliate_colours} {licenses}/>
|
||||
<!--{:else}
|
||||
<TechnologyTile {technology} {affiliate_colours} {licenses} {showModal}/>
|
||||
{/if}-->
|
||||
{:else}
|
||||
<p class='no-tiles'>No technologies are selected</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -468,7 +727,7 @@
|
|||
position: relative;
|
||||
}
|
||||
.introduction {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.summary-wrapper {
|
||||
content: '';
|
||||
|
@ -479,33 +738,24 @@
|
|||
}
|
||||
.summary {
|
||||
border-radius: 15px;
|
||||
float: right;
|
||||
clear: both;
|
||||
/*display: block;*/
|
||||
background-color: #ccc;
|
||||
/*display: block;*/
|
||||
max-width: 30%;
|
||||
min-width: 30em;
|
||||
border: 2px #aaa solid;
|
||||
margin: 0 15px 20px 6px;
|
||||
padding: 0 20px;
|
||||
box-shadow: 6px 6px 20px #999;
|
||||
float: right;
|
||||
clear: both;
|
||||
/*display: block;*/
|
||||
background-color: #ccc;
|
||||
/*display: block;*/
|
||||
max-width: 30%;
|
||||
min-width: 30em;
|
||||
border: 2px #aaa solid;
|
||||
margin: 0 15px 20px 6px;
|
||||
padding: 10px 20px;
|
||||
box-shadow: 6px 6px 20px #999;
|
||||
}
|
||||
.summary .label { color: #666; }
|
||||
.summary .number { color: #000; }
|
||||
/* .accordion-item {
|
||||
width: 30em;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
.summary .label {
|
||||
color: #666;
|
||||
}
|
||||
.accordion-item h2 {
|
||||
display: inline;
|
||||
.summary .number {
|
||||
color: #000;
|
||||
}
|
||||
.accordion-item svg {
|
||||
width: 12px;
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
}*/
|
||||
@media screen and (max-width: 600px) {
|
||||
.summary {
|
||||
float: none;
|
||||
|
|
Loading…
Reference in a new issue