776 lines
25 KiB
Svelte
776 lines
25 KiB
Svelte
<script>
|
|
// get the data from the JSON Webservices feed in +page.js
|
|
export let data;
|
|
const { webservices } = data;
|
|
|
|
import { references } from '$lib/references.js';
|
|
import { colours } from '$lib/colours.js';
|
|
|
|
// the filters
|
|
import { setFilter, getFilter } from '$lib/components/filter.js';
|
|
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
|
|
|
|
// return the % a is of b
|
|
function percent(a, b) {
|
|
return new Intl.NumberFormat('en', { style: "percent" }).format(a/b);
|
|
}
|
|
|
|
//
|
|
// 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 = [];
|
|
|
|
// actual objects
|
|
let hosts = [];
|
|
let affiliates = [];
|
|
let licenses = [];
|
|
|
|
//
|
|
// 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;
|
|
console.log('future technologies: ', future);
|
|
stats.future = countArray(future);
|
|
console.log('license_list: ', license_list);
|
|
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,
|
|
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) {
|
|
let keys = [];
|
|
let i = 0;
|
|
let key_array = [];
|
|
// first create a flat array.
|
|
for (let key in ob) {
|
|
keys.push(key);
|
|
}
|
|
// 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 });
|
|
});
|
|
return key_array;
|
|
}
|
|
|
|
// return true if a tech object includes valid instances
|
|
function hasInstances(tech) {
|
|
if (tech.hasOwnProperty('instances') &&
|
|
tech.instances.constructor === Array &&
|
|
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);
|
|
const set2 = new Set(b);
|
|
|
|
const intersection = [...set1].filter((element) => set2.has(element));
|
|
|
|
return intersection;
|
|
}
|
|
|
|
// assign colours from a set of differentiated colours to a list of tags...
|
|
// this one is for 'hosts'
|
|
//
|
|
// host_list is in the form of
|
|
function hostColours(host_list, colours, hosts) {
|
|
let host_array = {};
|
|
let i = 0;
|
|
|
|
//console.log('hosts:', hosts);
|
|
//console.log('host_list:', host_list);
|
|
|
|
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')
|
|
) {
|
|
host_array[hostname] = {
|
|
colour: colours[i++],
|
|
domain: hosts[hostname].domain,
|
|
affiliation: hosts[hostname].affiliation
|
|
};
|
|
}
|
|
}
|
|
return host_array;
|
|
}
|
|
|
|
// assign colours from a set of differentiated colours to a list of tags...
|
|
// this one is for 'affiliates'
|
|
function affiliateColours(affiliate_list, colours, affiliates) {
|
|
let affiliate_result = {};
|
|
let i = 0;
|
|
|
|
//console.log('affiliates:', affiliates);
|
|
//console.log('affiliate_list:', affiliate_list);
|
|
|
|
for (let index in affiliate_list) {
|
|
let affiliate = affiliate_list[index].name;
|
|
//console.log('affiliate:', affiliate);
|
|
if (
|
|
affiliates[affiliate].hasOwnProperty('name') &&
|
|
affiliates[affiliate].hasOwnProperty('website')
|
|
) {
|
|
affiliate_result[affiliate] = {
|
|
colour: colours[i++],
|
|
name: affiliates[affiliate].name,
|
|
website: affiliates[affiliate].website
|
|
};
|
|
}
|
|
}
|
|
return affiliate_result;
|
|
}
|
|
|
|
// sort technologies alphabetically by name
|
|
function sortTechnologies(technologies) {
|
|
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) {
|
|
const set1 = new Set(a);
|
|
const set2 = new Set(b);
|
|
|
|
const intersection = [...set1].filter((element) => set2.has(element));
|
|
//console.log('intersection = ', intersection);
|
|
return intersection;
|
|
}
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
return flat;
|
|
}
|
|
|
|
// filter technologies based on an array of filterables on tech and a list of filters
|
|
function filterTechnologiesByArray(technologies, field, list) {
|
|
const included = [];
|
|
technologies.forEach(function (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.');
|
|
return included;
|
|
}
|
|
|
|
// filter technologies based on a single filterable value on tech and a list of filters
|
|
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)) {
|
|
//console.log('instances: ', tech.instances);
|
|
for (let id in tech.instances) {
|
|
console.log('id = ', id);
|
|
console.log('field = ', field);
|
|
let instance = tech.instances[id];
|
|
let value = instance[field];
|
|
console.log('field value = ', value);
|
|
console.log('list = ', list);
|
|
if (instance.hasOwnProperty(field)) {
|
|
//const intersection = inCommon([instance[field]], list);
|
|
const intersection = inCommon([value], 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) {
|
|
if (instance.hasOwnProperty('domain')) {
|
|
console.log('setting to include ',instance.domain);
|
|
}
|
|
to_include = true;
|
|
} else {
|
|
// if an instance doesn't belong in the set, remove it.
|
|
/*if (instance.hasOwnProperty('domain')) {
|
|
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;
|
|
}
|
|
|
|
//
|
|
// process the data received by the web request!
|
|
const results = processData(webservices);
|
|
const licenses = results.licenses;
|
|
|
|
//const technologies = webservices.technologies;
|
|
//console.log(technologies);
|
|
|
|
// set up filters for each type
|
|
setFilter('categories');
|
|
setFilter('analogues');
|
|
setFilter('licenses');
|
|
setFilter('statuses');
|
|
setFilter('affiliates');
|
|
setFilter('hosts');
|
|
|
|
// define the handle for each writable
|
|
const categoryFilter = getFilter('categories');
|
|
const analogueFilter = getFilter('analogues');
|
|
const licenseFilter = getFilter('licenses');
|
|
const statusFilter = getFilter('statuses');
|
|
const affiliateFilter = getFilter('affiliates');
|
|
const hostFilter = getFilter('hosts');
|
|
|
|
// populate the writable with actual data
|
|
$categoryFilter = getSortedFilter(results.tech_lists.category_list);
|
|
$analogueFilter = getSortedFilter(results.tech_lists.analogue_list);
|
|
$licenseFilter = getSortedFilter(results.tech_lists.license_list);
|
|
$statusFilter = getSortedFilter(results.instance_lists.status_list);
|
|
$affiliateFilter = getSortedFilter(results.instance_lists.affiliate_list);
|
|
$hostFilter = getSortedFilter(results.instance_lists.host_list);
|
|
|
|
const hosts = results.hosts;
|
|
//console.log('host_data: ', results.hosts);
|
|
const affiliates = results.affiliates;
|
|
//console.log('affiliate_data: ', results.affiliates);
|
|
const host_colours = hostColours($hostFilter, colours, hosts);
|
|
//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 filter technologies by categories - starting with ' +
|
|
results.active_services.length +
|
|
' technologies...'
|
|
);
|
|
// filter by categories
|
|
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)
|
|
);
|
|
/*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,
|
|
'affiliation',
|
|
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">
|
|
<div class="introduction">
|
|
<div class="summary-wrapper">
|
|
<div class="summary">
|
|
<h2>Statistics</h2>
|
|
<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">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
|
|
<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.
|
|
</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>
|
|
<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 use 'select all or none' to select none, or invert your selection if it's more useful.</p>
|
|
<p><strong>Tip</strong>: if you want to see all technologies tagged with a specific tag, click that tag to <em>deselect</em> it, and then 'invert selection'. You'll see only the technologies that have the resulting tag.
|
|
</p>
|
|
<Accordions>
|
|
<Accordion title="Software Catergory Filter">
|
|
<AccordionBody>
|
|
<div class="filter-group 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>
|
|
</Accordion>
|
|
<Accordion title="Software Analogue Filter">
|
|
<AccordionBody>
|
|
<div class="filter-group 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>
|
|
</Accordion>
|
|
<Accordion title="License Filter">
|
|
<AccordionBody>
|
|
<div class="filter-group 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>
|
|
</Accordion>
|
|
<Accordion title="Service Status Filter">
|
|
<AccordionBody>
|
|
<div class="filter-group 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>
|
|
</Accordion>
|
|
<Accordion title="Service Affiliates Filter">
|
|
<AccordionBody>
|
|
<div class="filter-group affilates">
|
|
<Filterable filterValues={affiliateFilter}
|
|
{tabindex} histogram={results.instance_lists.affiliate_list}
|
|
context={'affiliates'}
|
|
colours={affiliate_colours}><h2 slot="title">Affiliates</h2>
|
|
<p slot="description"></p>
|
|
</Filterable>
|
|
</div>
|
|
</AccordionBody>
|
|
</Accordion>
|
|
<Accordion title="Service Host Filter">
|
|
<AccordionBody>
|
|
<div class="filter-group hosts">
|
|
<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>
|
|
</Accordion>
|
|
</Accordions>
|
|
</AccordionBody>
|
|
</Accordion>
|
|
</Accordions>
|
|
<p>Showing {filteredTechnologies.length} active technology tiles.</p>
|
|
<!--<p>Current URL: {$page.url}...</p>-->
|
|
<div class="tiles">
|
|
{#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 with active instances are selected.<br/> If you selected a filter tag but no technologies appear, it is because that technology is in a testing or planning stage and does not yet have a publicly visible instance.</p>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.webservices {
|
|
display: grid;
|
|
margin: 0 auto 3em auto;
|
|
padding: 0;
|
|
position: relative;
|
|
}
|
|
.introduction {
|
|
width: 100%;
|
|
}
|
|
.summary-wrapper {
|
|
content: '';
|
|
display: block;
|
|
float: right;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
.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: 10px 20px;
|
|
box-shadow: 6px 6px 20px #999;
|
|
}
|
|
.summary .label {
|
|
color: #666;
|
|
}
|
|
.summary .number {
|
|
color: #000;
|
|
}
|
|
@media screen and (max-width: 600px) {
|
|
.summary {
|
|
float: none;
|
|
width: 90%;
|
|
margin: auto;
|
|
}
|
|
}
|
|
</style>
|