Compare commits
16 Commits
b130482e21
...
master
Author | SHA1 | Date | |
---|---|---|---|
ff968f62a8 | |||
94b27f9ee4 | |||
2accd04546 | |||
b124d55273 | |||
a980e25e93 | |||
18beebda7a | |||
ef8880b240 | |||
c1cf1847cd | |||
82aed19f85 | |||
f091b318d4 | |||
f4a8439aee | |||
51eaa7a565 | |||
4a108aef06 | |||
684e193049 | |||
50d1e9ccae | |||
2d918eff7e |
@ -410,10 +410,10 @@
|
||||
"images": [],
|
||||
"type": 0,
|
||||
"options": {
|
||||
"q29-0": "switchport mode access switchport port-security",
|
||||
"q29-1": "switchport mode access",
|
||||
"q29-2": "switchport mode access switchport port-security maximum 2 switchport port-security mac-address sticky",
|
||||
"q29-3": "switchport mode access switchport port-security maximum 2 switchport port-security mac-address\n sticky switchport port-security violation protect"
|
||||
"q29-0": "switchport mode access <br> switchport port-security",
|
||||
"q29-1": "switchport mode access <br> switchport port-security <br> switchport port-security maximum 2 <br> switchport port-security mac-address sticky <br> switchport port-security violation restrict",
|
||||
"q29-2": "switchport mode access <br> switchport port-security maximum 2 <br> switchport port-security mac-address sticky",
|
||||
"q29-3": "switchport mode access <br> switchport port-security maximum 2 <br> switchport port-security mac-address sticky <br> switchport port-security violation protect"
|
||||
},
|
||||
"correct": "q29-1"
|
||||
},
|
||||
@ -553,10 +553,10 @@
|
||||
"images": [],
|
||||
"type": 0,
|
||||
"options": {
|
||||
"q39-0": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 ip dhcp excluded-address 192.168.100.254 ip\n dhcp pool LAN POOL-100 network 192.168.100.0 255.255.255.0 ip default gateway 192.168.100.1",
|
||||
"q39-1": "ip dhcp excluded-address 192.168.100.1 192.168.100.9 ip dhcp excluded-address 192.168.101.254 ip dhcp\n pool LAN POOL-100 ip network 192.168.100.0 255.255.254.0 ip default-gateway 192.168.100.1",
|
||||
"q39-2": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 ip dhcp\n excluded-address 192.168.101.254 ip dhcp pool LAN POOL-100 network 192.168.100.0\n 255.255.254.0 default-router 192.168.100.1",
|
||||
"q39-3": "dhcp pool LAN-POOL 100 ip dhcp excluded-address 192.168.100.1 192.168.100.9 ip dhcp excluded-address\n 192.168.100.254 network 192.168.100.0 255.255.254.0 default-router 192.168.101.1"
|
||||
"q39-0": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 <br> ip dhcp excluded-address 192.168.100.254 <br> ip dhcp pool LAN POOL-100 <br> network 192.168.100.0 255.255.255.0 <br> ip default gateway 192.168.100.1",
|
||||
"q39-1": "ip dhcp excluded-address 192.168.100.1 192.168.100.9 <br> ip dhcp excluded-address 192.168.101.254 <br> ip dhcp pool LAN POOL-100 <br> ip network 192.168.100.0 255.255.254.0 <br> ip default-gateway 192.168.100.1",
|
||||
"q39-2": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 <br> ip dhcp excluded-address 192.168.101.254 <br> ip dhcp pool LAN POOL-100 <br> network 192.168.100.0 255.255.254.0 <br> default-router 192.168.100.1",
|
||||
"q39-3": "dhcp pool LAN-POOL 100 <br> ip dhcp excluded-address 192.168.100.1 192.168.100.9 <br> ip dhcp excluded-address 192.168.100.254 <br> network 192.168.100.0 255.255.254.0 <br> default-router 192.168.101.1"
|
||||
},
|
||||
"correct": "q39-2"
|
||||
},
|
||||
@ -862,10 +862,10 @@
|
||||
"images": [],
|
||||
"type": 0,
|
||||
"options": {
|
||||
"q59-0": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat inside source list\n 1 interface serial 0/0/0 overload",
|
||||
"q59-1": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat pool comp 192.168.2.1 192.168.2.8 netmask\n 255.255.255.240 ip nat inside source list 1 pool comp",
|
||||
"q59-2": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat pool comp 192.168.2.1 192.168.2.8 netmask\n 255.255.255.240 ip nat inside source list 1 pool comp overload",
|
||||
"q59-3": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat pool comp 192.168.2.1 192.168.2.8 netmask\n 255.255.255.240 ip nat inside source list 1 pool comp overload ip nat inside source static 10.0.0.5\n 209.165.200.225"
|
||||
"q59-0": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat inside source list 1 interface serial 0/0/0 overload",
|
||||
"q59-1": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat pool comp 192.168.2.1 192.168.2.8 netmask 255.255.255.240 <br> ip nat inside source list 1 pool comp",
|
||||
"q59-2": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat pool comp 192.168.2.1 192.168.2.8 netmask 255.255.255.240 <br> ip nat inside source list 1 pool comp overload",
|
||||
"q59-3": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat pool comp 192.168.2.1 192.168.2.8 netmask 255.255.255.240 <br> ip nat inside source list 1 pool comp overload <br> ip nat inside source static 10.0.0.5 209.165.200.225"
|
||||
},
|
||||
"correct": "q59-0"
|
||||
},
|
||||
@ -1025,10 +1025,10 @@
|
||||
"images": [],
|
||||
"type": 0,
|
||||
"options": {
|
||||
"q74-0": "shutdown no shutdown",
|
||||
"q74-1": "shutdown no switchport port-security",
|
||||
"q74-2": "shutdown no switchport port-security violation shutdown",
|
||||
"q74-3": "shutdown no switchport port-security maximum"
|
||||
"q74-0": "shutdown <br> no shutdown",
|
||||
"q74-1": "shutdown <br> no switchport port-security",
|
||||
"q74-2": "shutdown <br> no switchport port-security violation shutdown",
|
||||
"q74-3": "shutdown <br> no switchport port-security maximum"
|
||||
},
|
||||
"correct": "q74-0"
|
||||
},
|
||||
@ -1344,10 +1344,10 @@
|
||||
"images": [],
|
||||
"type": 0,
|
||||
"options": {
|
||||
"q96-0": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# spanning-tree vlan 1",
|
||||
"q96-1": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# spanning-tree portfast",
|
||||
"q96-2": "Switch(config)# interface gigabitethernet 1/1 ",
|
||||
"q96-3": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# switchport access vlan 1"
|
||||
"q96-0": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# spanning-tree vlan 1",
|
||||
"q96-1": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# spanning-tree portfast",
|
||||
"q96-2": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# switchport mode trunk",
|
||||
"q96-3": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# switchport access vlan 1"
|
||||
},
|
||||
"correct": "q96-2"
|
||||
},
|
2113
ccna3.json
Normal file
2113
ccna3.json
Normal file
File diff suppressed because it is too large
Load Diff
8
ccna4-missing
Normal file
8
ccna4-missing
Normal file
@ -0,0 +1,8 @@
|
||||
'21 | Next is not UL but: undefined',
|
||||
'52 | No correct answers found!',
|
||||
'98 | Next is not UL but: undefined',
|
||||
'99 | Next is not UL but: undefined',
|
||||
'100 | Next is not UL but: undefined',
|
||||
'143 | Next is not UL but: undefined',
|
||||
'144 | Next is not UL but: undefined',
|
||||
'145 | No correct answers found!'
|
2007
ccna4.json
Normal file
2007
ccna4.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,33 +0,0 @@
|
||||
{
|
||||
"title": "CCNA 2 Final Exam",
|
||||
"questions": [{
|
||||
"id": "quest1",
|
||||
"type": 0,
|
||||
"title": "The buffers for packet processing and the running configuration file are temporarily stored in which type of router memory?",
|
||||
"images": [],
|
||||
"options": {
|
||||
"q1-1": "flash",
|
||||
"q1-2": "NVRAM",
|
||||
"q1-3": "RAM",
|
||||
"q1-4": "ROM"
|
||||
},
|
||||
"correct": "q1-3"
|
||||
},
|
||||
{
|
||||
"id": "quest2",
|
||||
"type": 0,
|
||||
"title": "Refer to the exhibit. A company has an internal network of 192.168.10.0/24 for their employee workstations and a DMZ network of 192.168.3.0/24 to host servers. The company uses NAT when inside hosts connect to outside network. A network administrator issues the show ip nat translations command to check the NAT configurations. Which one of source IPv4 addresses is translated by R1 with PAT",
|
||||
"images": [
|
||||
"i258133v1n1_258133.png"
|
||||
],
|
||||
"options": {
|
||||
"q2-1": "10.0.0.31",
|
||||
"q2-2": "192.168.3.5",
|
||||
"q2-3": "192.168.3.33",
|
||||
"q2-4": "192.168.10.35",
|
||||
"q2-5": "172.16.20.5"
|
||||
},
|
||||
"correct": "q2-4"
|
||||
}
|
||||
]
|
||||
}
|
195
data-test.json
Normal file
195
data-test.json
Normal file
@ -0,0 +1,195 @@
|
||||
{
|
||||
"title": "CCNA 2 Final Exam",
|
||||
"questions": [
|
||||
{
|
||||
"id": "asd",
|
||||
"type": 2,
|
||||
"title": "Match the description to the EIGRP packet type. (Not all options are used.)",
|
||||
"images": [],
|
||||
"fields": {
|
||||
"f1": "used to propagate routing information to EIGRP neighbors",
|
||||
"f2": "used to acknowledge the receipt of an EIGTP message that was sent using reliable delivery",
|
||||
"f3": "used for neighbor discovery and to maintain neighbor adjacencies",
|
||||
"f4": "send in response to an EIGRP query",
|
||||
"f5": "used to query routes from neighbors"
|
||||
},
|
||||
"values": {
|
||||
"v1": "hello packets",
|
||||
"v2": "query packets",
|
||||
"v3": "reply packets",
|
||||
"v4": "update packets",
|
||||
"v5": "acknowledgment packets",
|
||||
"v6": "database description packets"
|
||||
},
|
||||
"correct": {
|
||||
"f1": "v4",
|
||||
"f2": "v5",
|
||||
"f3": "v1",
|
||||
"f4": "v3",
|
||||
"f5": "v2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ccna3-q38",
|
||||
"type": 2,
|
||||
"title": "Match each description to its corresponding LSA type. (Not all options are used.)",
|
||||
"images": [],
|
||||
"fields": {
|
||||
"f1": "generated by the DR on a multiaccess segment and flooded within an area",
|
||||
"f2": "generated by ABRs and sent between areas",
|
||||
"f3": "generated by ABRs and sent between areas to advertise the location of an AS",
|
||||
"f4": "generated by all routers and flooded within an area"
|
||||
},
|
||||
"values": {
|
||||
"v1": "type 1",
|
||||
"v2": "type 2",
|
||||
"v3": "type 3",
|
||||
"v4": "type 4",
|
||||
"v5": "type 5"
|
||||
},
|
||||
"correct": {
|
||||
"f1": "v2",
|
||||
"f2": "v3",
|
||||
"f3": "v4",
|
||||
"f4": "v1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ccna3-q135",
|
||||
"type": 2,
|
||||
"title": "Match the order in which the link-state routing process occurs on a router. (Not all options are used.)",
|
||||
"images": [],
|
||||
"fields": {
|
||||
"f1": "step 1",
|
||||
"f2": "step 2",
|
||||
"f3": "step 3",
|
||||
"f4": "step 4",
|
||||
"f5": "step 5"
|
||||
},
|
||||
"values": {
|
||||
"v1": "Each router is responsible for “saying hello” to its neighbors on directly connected networks.",
|
||||
"v2": "Each router builds a Link-State Packet (LSP) containing the state of each directly connected link",
|
||||
"v3": "Each router learns about its own directly connected networks",
|
||||
"v4": "Each router increments the hop count for the destination network",
|
||||
"v5": "Each router floods the LSP to all neighbors, who then store all LSPs received in a database",
|
||||
"v6": "Each router uses the database to construct a complete map of the topology and computes the best"
|
||||
},
|
||||
"correct": {
|
||||
"f1": "v3",
|
||||
"f2": "v1",
|
||||
"f3": "v2",
|
||||
"f4": "v5",
|
||||
"f5": "v6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ccna3-q136",
|
||||
"type": 2,
|
||||
"title": " Match the description to the term. (Not all options are used.)",
|
||||
"images": [],
|
||||
"fields": {
|
||||
"f1": "This is the algorithm used by OSPF",
|
||||
"f2": "This is where the details of the neighboring routers can be found",
|
||||
"f3": "This is where you can find the topology table",
|
||||
"f4": "All the routers are in the backbone area"
|
||||
},
|
||||
"values": {
|
||||
"v1": "adjacency database",
|
||||
"v2": "Shortest Path First",
|
||||
"v3": "single-area OSPF",
|
||||
"v4": "DUAL",
|
||||
"v5": "link-state database",
|
||||
"v6": "multiarea OSPF"
|
||||
},
|
||||
"correct": {
|
||||
"f1": "v2",
|
||||
"f2": "v1",
|
||||
"f3": "v5",
|
||||
"f4": "v3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ccna3-q137",
|
||||
"type": 2,
|
||||
"title": "Match the order of precedence to the process logic that an OSPFv3 network router goes through in choosing a router ID. (Not all options are used.)",
|
||||
"images": [],
|
||||
"fields": {
|
||||
"f1": "priority 1",
|
||||
"f2": "priority 2",
|
||||
"f3": "priority 3",
|
||||
"f4": "priority 4"
|
||||
},
|
||||
"values": {
|
||||
"v1": "The router displays a console message to configure the router ID manually",
|
||||
"v2": "The router uses the highest configured IPv4 address of an active interface",
|
||||
"v3": "The router chooses the highest IPv6 address that is configured on the router",
|
||||
"v4": "The router uses the highest configured IPv4 address of a loopback interface",
|
||||
"v5": "The router uses the explicitly configured router ID if any"
|
||||
},
|
||||
"correct": {
|
||||
"f1": "v5",
|
||||
"f2": "v4",
|
||||
"f3": "v2",
|
||||
"f4": "v1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ccna3-q140",
|
||||
"type": 2,
|
||||
"title": "Match each OSPF router type description with its name. (Not all options are used.)",
|
||||
"images": [],
|
||||
"fields": {
|
||||
"f1": "All the routers of this type have identical LSDBs",
|
||||
"f2": "All the routers of this type mainrain separate LSDBs for each area to which they connect",
|
||||
"f3": "All the routers of this type can import non-OSPF network information to the OSPF network and vice versa using route redistribution"
|
||||
},
|
||||
"values": {
|
||||
"v1": "internal router",
|
||||
"v2": "backbone router",
|
||||
"v3": "autonomus system boudary router (ASBR)",
|
||||
"v4": "area border router (ABR)"
|
||||
},
|
||||
"correct": {
|
||||
"f1": "v1",
|
||||
"f2": "v4",
|
||||
"f3": "v3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"dead": [
|
||||
{
|
||||
"id": "quest1",
|
||||
"type": 1,
|
||||
"title": "The buffers for packet processing and the running configuration file are temporarily stored in which type of router memory?",
|
||||
"images": [],
|
||||
"options": {
|
||||
"q1-1": "flash",
|
||||
"q1-2": "NVRAM",
|
||||
"q1-3": "RAM",
|
||||
"q1-4": "ROM"
|
||||
},
|
||||
"correct": [
|
||||
"q1-3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "quest2",
|
||||
"type": 1,
|
||||
"title": "Refer to the exhibit. A company has an internal network of 192.168.10.0/24 for their employee workstations and a DMZ network of 192.168.3.0/24 to host servers. The company uses NAT when inside hosts connect to outside network. A network administrator issues the show ip nat translations command to check the NAT configurations. Which one of source IPv4 addresses is translated by R1 with PAT",
|
||||
"images": [
|
||||
"i258133v1n1_258133.png"
|
||||
],
|
||||
"options": {
|
||||
"q2-1": "10.0.0.31",
|
||||
"q2-2": "192.168.3.5",
|
||||
"q2-3": "192.168.3.33",
|
||||
"q2-4": "192.168.10.35",
|
||||
"q2-5": "172.16.20.5"
|
||||
},
|
||||
"correct": [
|
||||
"q2-4",
|
||||
"q2-2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4583
package-lock.json
generated
4583
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@ -9,32 +9,27 @@
|
||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@hibas123/theme": "^1.2.6",
|
||||
"@hibas123/utils": "^2.1.1",
|
||||
"@hibas123/theme": "^1.2.14",
|
||||
"@hibas123/utils": "^2.2.3",
|
||||
"feather-icons": "^4.24.1",
|
||||
"fuse.js": "^3.4.6",
|
||||
"navigo": "^7.1.2",
|
||||
"rollup-plugin-json": "^4.0.0",
|
||||
"svelte": "^3.12.1",
|
||||
"ts-node": "^8.3.0",
|
||||
"svelte": "^3.16.0",
|
||||
"svelte-key": "^1.0.0",
|
||||
"ts-node": "^8.5.4",
|
||||
"uuid": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/navigo": "^7.0.1",
|
||||
"@types/node": "^12.7.5",
|
||||
"@types/uuid": "^3.4.5",
|
||||
"node-sass": "^4.12.0",
|
||||
"parcel-bundler": "^1.12.3",
|
||||
"parcel-plugin-svelte": "^3.0.1",
|
||||
"rollup": "^1.21.2",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-svelte": "^5.1.0",
|
||||
"rollup-plugin-terser": "^5.1.1",
|
||||
"rollup-plugin-typescript2": "^0.24.1",
|
||||
"@types/node": "^12.12.14",
|
||||
"@types/uuid": "^3.4.6",
|
||||
"node-sass": "^4.13.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"parcel-plugin-svelte": "^4.0.5",
|
||||
"svelte-preprocess-sass": "^0.2.0",
|
||||
"typescript": "^3.6.3"
|
||||
"typescript": "^3.7.3"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
// import * as rollup from "rollup";
|
||||
import svelteplg from 'rollup-plugin-svelte';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import typescript from "rollup-plugin-typescript2";
|
||||
import rjson from "rollup-plugin-json";
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
terser
|
||||
} from 'rollup-plugin-terser';
|
||||
|
||||
const production = process.argv.indexOf("-d") < 0;
|
||||
console.log(`Runnig in ${production ? "production" : "development"} mode!`);
|
||||
|
||||
let plg = [];
|
||||
|
||||
if (production) {
|
||||
plg.push(terser())
|
||||
}
|
||||
|
||||
if (!fs.existsSync("build"))
|
||||
fs.mkdirSync("build");
|
||||
|
||||
fs.copyFileSync("src/index.html", "build/index.html");
|
||||
|
||||
export default {
|
||||
input: `./src/main.ts`,
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: `build/bundle.js`
|
||||
},
|
||||
watch: {
|
||||
clearScreen: false
|
||||
},
|
||||
plugins: [
|
||||
cssStringPlugin({
|
||||
include: [
|
||||
"@hibas123/theme/out/base.css",
|
||||
"@hibas123/theme/out/light.css",
|
||||
"@hibas123/theme/out/dark.css"
|
||||
]
|
||||
}),
|
||||
// svgSveltePlugin(),
|
||||
resolve({
|
||||
browser: true
|
||||
}),
|
||||
typescript({
|
||||
tsconfig: "./src/tsconfig.json"
|
||||
}),
|
||||
svelteplg({
|
||||
// enable run-time checks when not in production
|
||||
dev: !production,
|
||||
extensions: [".svg", ".svelte"],
|
||||
css: css => {
|
||||
css.write(`build/bundle.css`);
|
||||
}
|
||||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
"node_modules/js-sha256/src/sha256.js": ["sha256"],
|
||||
"node_modules/aes-js/index.js": ["ModeOfOperation"]
|
||||
}
|
||||
}),
|
||||
rjson(),
|
||||
...plg
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
import * as path from "path";
|
||||
|
||||
|
||||
function cssStringPlugin({
|
||||
include
|
||||
}) {
|
||||
return {
|
||||
name: 'css-to-string', // this name will show up in warnings and errors
|
||||
resolveId(source) {
|
||||
if (include.indexOf(source) >= 0)
|
||||
return source;
|
||||
return null; // other ids should be handled as usually
|
||||
},
|
||||
load(id) {
|
||||
if (include.indexOf(id) >= 0) {
|
||||
const p = "./node_modules/" + id;
|
||||
let r = `export default ${JSON.stringify(fs.readFileSync(p).toString("utf-8")).replace("'", "\'")}`;
|
||||
return r;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
};
|
||||
}
|
@ -1,28 +1,40 @@
|
||||
<script>
|
||||
// import Trash from "feather-icons/dist/icons/trash.svg"
|
||||
// import Vaults from "./views/Vaults.svelte";
|
||||
// import Trash from "feather-icons/dist/icons/trash.svg"
|
||||
// import Vaults from "./views/Vaults.svelte";
|
||||
|
||||
import {
|
||||
pageStore
|
||||
} from "./router";
|
||||
import { pageStore } from "./router";
|
||||
|
||||
import {
|
||||
DeviceType,
|
||||
DeviceTypes,
|
||||
ModalStore
|
||||
} from "./stores";
|
||||
import { DeviceType, DeviceTypes, ModalStore } from "./stores";
|
||||
|
||||
$: console.log("Current DeviceType:", DeviceTypes[$DeviceType]);
|
||||
import QuestionManager from "./stores";
|
||||
|
||||
const { version } = QuestionManager;
|
||||
|
||||
$: console.log("Current DeviceType:", DeviceTypes[$DeviceType]);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if $DeviceType === DeviceTypes.MOBILE}
|
||||
<svelte:component this={$pageStore.mobile} params={$pageStore.params} />
|
||||
{:else}
|
||||
<svelte:component this={$pageStore.desktop} params={$pageStore.params} />
|
||||
{/if}
|
||||
<style>
|
||||
.version {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
left: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if $ModalStore}
|
||||
<svelte:component this={$ModalStore.component} modal={$ModalStore}/>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<div class="header" style="padding: .25rem">
|
||||
<span on:click={() => (window.location.hash = '#/')}>CCNA Trainer:</span>
|
||||
<span>{version}</span>
|
||||
</div>
|
||||
{#if $DeviceType === DeviceTypes.MOBILE}
|
||||
<svelte:component this={$pageStore.mobile} params={$pageStore.params} />
|
||||
{:else}
|
||||
<svelte:component this={$pageStore.desktop} params={$pageStore.params} />
|
||||
{/if}
|
||||
|
||||
{#if $ModalStore}
|
||||
<svelte:component this={$ModalStore.component} modal={$ModalStore} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="version">{version}</div>
|
||||
|
@ -43,4 +43,8 @@ const app = new App({
|
||||
target: document.getElementById("content")
|
||||
});
|
||||
|
||||
export default app;
|
||||
export default app;
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register("sw.ts");
|
||||
}
|
9
src/rand.ts
Normal file
9
src/rand.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default function randomize<T>(input: T[]): T[] {
|
||||
let res: T[] = [];
|
||||
input = [...input];
|
||||
while (input.length > 0) {
|
||||
let randomIndex = Math.floor(Math.random() * input.length);
|
||||
res.push(...input.splice(randomIndex, 1));
|
||||
}
|
||||
return res;
|
||||
}
|
@ -19,15 +19,28 @@ export const pageStore = readable<ActiveRoute>(undefined, (set) => {
|
||||
router.resolve();
|
||||
});
|
||||
|
||||
import VaultsMobile from "./views/Overview.svelte";
|
||||
import VaultsDesktop from "./views/Overview.svelte";
|
||||
import Home from "./views/Home.svelte"
|
||||
import Overview from "./views/Overview.svelte";
|
||||
import Browse from "./views/Browse.svelte";
|
||||
|
||||
router.on("/", (params) => {
|
||||
setComponent({
|
||||
desktop: VaultsDesktop as typeof SvelteComponent,
|
||||
mobile: VaultsMobile as typeof SvelteComponent,
|
||||
desktop: Home as typeof SvelteComponent,
|
||||
mobile: Home as typeof SvelteComponent,
|
||||
params: {}
|
||||
})
|
||||
}).on("/test", (params) => {
|
||||
setComponent({
|
||||
desktop: Overview as typeof SvelteComponent,
|
||||
mobile: Overview as typeof SvelteComponent,
|
||||
params
|
||||
})
|
||||
}).on("/browse", (params) => {
|
||||
setComponent({
|
||||
desktop: Browse as typeof SvelteComponent,
|
||||
mobile: Browse as typeof SvelteComponent,
|
||||
params: {}
|
||||
})
|
||||
})
|
||||
|
||||
// .on("/:vaultid", (params) => {
|
||||
|
@ -1,10 +1,36 @@
|
||||
import { writable, readable } from "svelte/store";
|
||||
import Data from "../data.json";
|
||||
import { Question } from "./data";
|
||||
import { Question, Exam, QuestionTypes } from "./data";
|
||||
|
||||
import CCNA2 from "../ccna2.json";
|
||||
import CCNA3 from "../ccna3.json";
|
||||
import CCNA4 from "../ccna4.json";
|
||||
import TestData from "../data-test.json";
|
||||
|
||||
const Tests = new Map<string, Exam>();
|
||||
Tests.set("ccna4", CCNA4);
|
||||
Tests.set("ccna3", CCNA3);
|
||||
Tests.set("ccna2", CCNA2);
|
||||
Tests.set("test", TestData)
|
||||
|
||||
const DEFAULT = "ccna4";
|
||||
|
||||
export let test = new URL(window.location.href).searchParams.get("exam") || DEFAULT;
|
||||
|
||||
const dataVersion = Tests.has(test) ? test : DEFAULT;
|
||||
const Data = Tests.get(dataVersion);
|
||||
const QuestionMap = new Map<string, Question>();
|
||||
|
||||
Data.questions.forEach(q => QuestionMap.set(q.id, q));
|
||||
|
||||
console.log("Running exam:", dataVersion);
|
||||
|
||||
const runsShould = 3;
|
||||
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
class QuestionManager {
|
||||
version: string = dataVersion;
|
||||
|
||||
activeQuestion = writable<{
|
||||
correct: () => void;
|
||||
wrong: () => void;
|
||||
@ -19,6 +45,22 @@ class QuestionManager {
|
||||
level: number
|
||||
}[];
|
||||
|
||||
index: Fuse<any, any>;
|
||||
|
||||
private getAnswerString(question: Question) {
|
||||
switch (question.type) {
|
||||
case QuestionTypes.SelectOne:
|
||||
case QuestionTypes.SelectMultiple:
|
||||
return Object.values(question.options).join(" ; ");
|
||||
case QuestionTypes.TextInput:
|
||||
return question.correct;
|
||||
case QuestionTypes.AssignValues:
|
||||
return [...Object.values(question.fields), ...Object.values(question.values)].join(" ; ");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.availableQuestions = Data.questions.map(question => {
|
||||
return {
|
||||
@ -28,6 +70,30 @@ class QuestionManager {
|
||||
})
|
||||
|
||||
this.getNewActive();
|
||||
|
||||
const searchData = Data.questions
|
||||
.map(e => ({
|
||||
id: e.id,
|
||||
title: e.title,
|
||||
answers: this.getAnswerString(e)
|
||||
}))
|
||||
|
||||
this.index = new Fuse(searchData, {
|
||||
keys: [{
|
||||
name: "title",
|
||||
weight: 0.8
|
||||
}, {
|
||||
name: "answers",
|
||||
weight: 0.2
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
search(term: string) {
|
||||
if (term === "")
|
||||
return Data.questions;
|
||||
const match = (this.index.search(term) as any[]).map(e => QuestionMap.get(e.id));
|
||||
return match;
|
||||
}
|
||||
|
||||
getNewActive() {
|
||||
@ -87,4 +153,6 @@ export const IsDark = readable(Theme.isDark(), set => {
|
||||
return () => Theme.isDarkObservable.unsubscribe(onChange);
|
||||
})
|
||||
|
||||
export { ModalStore } from "./modals";
|
||||
export { ModalStore } from "./modals";
|
||||
|
||||
export const AvailableTests = Array.from(Tests.keys());
|
37
src/sw.ts
Normal file
37
src/sw.ts
Normal file
@ -0,0 +1,37 @@
|
||||
var CACHE = 'cache-and-update';
|
||||
|
||||
self.addEventListener('install', function (evt) {
|
||||
console.log('The service worker is being installed.');
|
||||
evt.waitUntil(precache());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function (evt: FetchEvent) {
|
||||
console.log('The service worker is serving the asset.');
|
||||
evt.respondWith(fromCache(evt.request, update(evt.request)));
|
||||
evt.waitUntil(update(evt.request));
|
||||
});
|
||||
|
||||
function precache() {
|
||||
return caches.open(CACHE).then(function (cache) {
|
||||
return cache.addAll([
|
||||
"./",
|
||||
"./index.html"
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
function fromCache(request, update) {
|
||||
return caches.open(CACHE).then(function (cache) {
|
||||
return cache.match(request).then(function (matching) {
|
||||
return matching || update;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function update(request) {
|
||||
return caches.open(CACHE).then(function (cache) {
|
||||
return fetch(request).then(function (response) {
|
||||
return cache.put(request, response);
|
||||
});
|
||||
});
|
||||
}
|
78
src/views/Browse.svelte
Normal file
78
src/views/Browse.svelte
Normal file
@ -0,0 +1,78 @@
|
||||
<script>
|
||||
import QuestionManager from "../stores";
|
||||
import { QuestionTypes } from "../data";
|
||||
let term = "";
|
||||
$: questions = QuestionManager.search(term);
|
||||
|
||||
let opened = undefined;
|
||||
|
||||
// $:opened = openedId === undefined ? undefined : questions.find(e=>e.id === openedId);
|
||||
|
||||
$: console.log("Opened:", opened)
|
||||
</script>
|
||||
|
||||
<div class="container" style="overflow-x: hidden">
|
||||
<br />
|
||||
<input
|
||||
class="inp"
|
||||
style="width: 100%"
|
||||
type="text"
|
||||
bind:value={term}
|
||||
placeholder="Search..." />
|
||||
|
||||
<ul class="list list-divider list-clickable">
|
||||
{#each questions as question}
|
||||
<li on:click={()=>opened = opened === question.id ? undefined : question.id}>
|
||||
<p>
|
||||
{@html question.title}
|
||||
</p>
|
||||
{#if opened === question.id}
|
||||
{#if question.type === QuestionTypes.SelectOne}
|
||||
<ul>
|
||||
{#each Object.entries(question.options) as [key, value]}
|
||||
<li class={question.correct === key ? "correct" : ""}>
|
||||
{value}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if question.type === QuestionTypes.SelectMultiple}
|
||||
<ul>
|
||||
{#each Object.entries(question.options) as [key, value]}
|
||||
<li class={question.correct.indexOf(key) >= 0 ? "correct" : ""}>
|
||||
{value}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if question.type === QuestionTypes.TextInput}
|
||||
<p>{question.correct}</p>
|
||||
{:else if question.type === QuestionTypes.AssignValues}
|
||||
<ul>
|
||||
{#each Object.entries(question.fields) as [key, value]}
|
||||
<li>
|
||||
{value} ➞ {question.values[question.correct[key]]}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
Unknown type!
|
||||
{/if}
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.correct {
|
||||
color: green
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- {#if opened}
|
||||
<div class="modal">
|
||||
{@html opened.title};
|
||||
<div class="modal-action">
|
||||
<button class="btn" on:click={()=>openedId = undefined}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if} -->
|
20
src/views/Home.svelte
Normal file
20
src/views/Home.svelte
Normal file
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import {AvailableTests, test} from "../stores";
|
||||
|
||||
let selected = test;
|
||||
</script>
|
||||
|
||||
<div class="container" style="overflow-x: hidden">
|
||||
<p>Test your knowlegde</p>
|
||||
<button class="btn btn-primary" on:click={()=>window.location.hash = "#/test"}>Test</button>
|
||||
<p>Browse and search specific questions</p>
|
||||
<button class="btn btn-primary" on:click={()=>window.location.hash = "#/browse"}>Search questions</button>
|
||||
|
||||
<br>
|
||||
<select class="inp" bind:value={selected}>
|
||||
{#each AvailableTests as test}
|
||||
<option>{test}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button class="btn" on:click={()=>window.location.href = "./?exam=" + selected}>switch</button>
|
||||
</div>
|
@ -25,5 +25,5 @@
|
||||
</div>
|
||||
|
||||
<footer class="elv-24">
|
||||
<div style={"width:" + $progress + "%;" }>{$progress}%</div>
|
||||
<div style={"width:" + $progress + "%;" }>{($progress).toFixed(2)}%</div>
|
||||
</footer>
|
@ -8,6 +8,7 @@
|
||||
import SelectMultiple from "./questions/SelectMultiple.svelte";
|
||||
import AssignValues from "./questions/AssignValues.svelte";
|
||||
import TextInput from "./questions/TextInput.svelte";
|
||||
import Identity from 'svelte-key'
|
||||
|
||||
const questions = new Map();
|
||||
questions.set(QuestionTypes.SelectOne, SelectOne);
|
||||
@ -24,18 +25,19 @@
|
||||
let idx = 0;
|
||||
|
||||
function next() {
|
||||
idx++;
|
||||
showResult = false;
|
||||
if (isCorrect) {
|
||||
isCorrect = false;
|
||||
$activeQuestion.correct()
|
||||
} else {
|
||||
$activeQuestion.wrong();
|
||||
}
|
||||
showResult = false;
|
||||
idx++;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if question}
|
||||
<div style={"background:" + (showResult ? (isCorrect ? "rgba(92, 255, 92, 0.30)" : "rgba(255, 92, 92, 0.46)") : "") + ";"}>
|
||||
<div class="margin">
|
||||
<h2>{@html question.title}</h2>
|
||||
</div>
|
||||
@ -45,17 +47,30 @@
|
||||
</div>
|
||||
{/each}
|
||||
<div class="margin">
|
||||
<svelte:component key={idx} this={component} {question} {showResult} bind:isCorrect />
|
||||
<Identity key={idx}>
|
||||
<svelte:component key={idx} this={component} {question} {showResult} bind:isCorrect />
|
||||
</Identity>
|
||||
</div>
|
||||
<div class="margin">
|
||||
<div class="margin fl">
|
||||
{#if !showResult}
|
||||
<button class="btn" on:click={()=>showResult = true}>
|
||||
<button class="btn lbtn" on:click={()=>showResult = true}>
|
||||
Check
|
||||
</button>
|
||||
{:else}
|
||||
<button class="btn" on:click={()=>next()}>
|
||||
<button class="btn lbtn" on:click={()=>next()}>
|
||||
Next
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.fl {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.lbtn {
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
@ -1 +1,81 @@
|
||||
The developer was to lazy to implement this type of question. Sorry :)
|
||||
<script>
|
||||
import {onMount} from "svelte";
|
||||
import randomize from "../../rand.ts";
|
||||
|
||||
export let question;
|
||||
export let showResult = false;
|
||||
|
||||
export let isCorrect = false;
|
||||
|
||||
let fields = [];
|
||||
|
||||
onMount(()=>{
|
||||
fields = randomize(Object.keys(question.fields).map(e => ({
|
||||
key: e,
|
||||
value: question.fields[e]
|
||||
})));
|
||||
})
|
||||
|
||||
$: values = Object.keys(question.values).map(e => ({
|
||||
key: e,
|
||||
value: question.values[e]
|
||||
}))
|
||||
|
||||
let selected = {};
|
||||
// $: isCorrect = selected === question.correct;
|
||||
|
||||
$: console.log(
|
||||
"Selected:",
|
||||
selected,
|
||||
"showResult:",
|
||||
showResult,
|
||||
"isCorrect",
|
||||
isCorrect
|
||||
);
|
||||
|
||||
$: {
|
||||
isCorrect = fields.every(field=>selected[field.key] === question.correct[field.key]);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#each fields as field (field.key)}
|
||||
<div class="input-group">
|
||||
<label>{field.value}</label>
|
||||
<select class="inp" bind:value={selected[field.key]}>
|
||||
<option value={undefined}>-- select --</option>
|
||||
{#each values as value}
|
||||
<option value={value.key}>{value.value}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if showResult}
|
||||
<div class="should">{question.values[question.correct[field.key]]}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
.should {
|
||||
color: var(--primary) !important;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- {#each options as option}
|
||||
<div
|
||||
key={option.key}
|
||||
class:should={showResult && option.key === question.correct }
|
||||
>
|
||||
<label class="input-checkbox" for={option.key}>{@html option.value.replace(/\n/g, "<br>")}
|
||||
<input
|
||||
type="radio"
|
||||
id={option.key}
|
||||
checked={option.key===selected}
|
||||
on:click={(evt)=>showResult ?
|
||||
evt.preventDefault() : selected = option.key}
|
||||
>
|
||||
<span></span>
|
||||
</label>
|
||||
<br/>
|
||||
</div>
|
||||
{/each} -->
|
||||
|
@ -1,38 +1,31 @@
|
||||
<script>
|
||||
import randomize from "../../rand.ts";
|
||||
|
||||
export let question;
|
||||
export let showResult = false;
|
||||
export let isCorrect = false;
|
||||
|
||||
|
||||
$: options = Object.keys(question.options).map(e => ({ key: e, value: question.options[e] }));
|
||||
$: options = randomize(Object.keys(question.options).map(e => ({ key: e, value: question.options[e] })));
|
||||
|
||||
let selected = [];
|
||||
|
||||
$: isCorrect = selected.length === question.correct.length && selected.every(val => question.correct.find(v => v === val));
|
||||
|
||||
$: console.log("Selected:", selected, "showResult:", showResult, "isCorrect", isCorrect);
|
||||
|
||||
|
||||
function toggle(id) {
|
||||
if (selected.indexOf(id) < 0) {
|
||||
selected = [...selected, id];
|
||||
} else {
|
||||
selected = selected.filter(e => e !== id);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<h3>Select {question.correct.length}</h3>
|
||||
{#each options as option}
|
||||
{#each options as option (option.key)}
|
||||
<div
|
||||
key={option.key}
|
||||
class:should={showResult && question.correct.indexOf(option.key) >= 0}
|
||||
>
|
||||
<label class="input-checkbox" for={option.key}>{option.value}
|
||||
<label class="input-checkbox" for={option.key}>{@html option.value.replace(/\n/g, "<br>")}
|
||||
<input
|
||||
type="checkbox"
|
||||
id={option.key}
|
||||
checked={selected.indexOf(option.key) >= 0}
|
||||
on:click={(evt)=>showResult ? evt.preventDefault() : toggle(option.key)}
|
||||
id={option.key}
|
||||
bind:group={selected}
|
||||
on:click={(evt)=>showResult ? evt.preventDefault() : undefined}
|
||||
value={option.key}
|
||||
>
|
||||
<span></span>
|
||||
</label>
|
||||
|
@ -1,11 +1,12 @@
|
||||
<script>
|
||||
import randomize from "../../rand.ts";
|
||||
|
||||
export let question;
|
||||
export let showResult = false;
|
||||
|
||||
export let isCorrect = false;
|
||||
|
||||
|
||||
$: options = Object.keys(question.options).map(e => ({ key: e, value: question.options[e] }));
|
||||
$: options = randomize(Object.keys(question.options).map(e => ({ key: e, value: question.options[e] })));
|
||||
|
||||
let selected = undefined;
|
||||
$: isCorrect = selected === question.correct;
|
||||
@ -19,7 +20,7 @@
|
||||
key={option.key}
|
||||
class:should={showResult && option.key === question.correct }
|
||||
>
|
||||
<label class="input-checkbox" for={option.key}>{option.value}
|
||||
<label class="input-checkbox" for={option.key}>{@html option.value.replace(/\n/g, "<br>")}
|
||||
<input
|
||||
type="radio"
|
||||
id={option.key}
|
||||
|
Reference in New Issue
Block a user