Compare commits

..
10 Commits
Author SHA1 Message Date
altf4 6091da5253 Unit tests in github workflow 2026-03-08 13:43:43 -07:00
altf4 1de8546b8c Dust off the cobwebs
- add uv
 - fix unit tests
2026-03-08 13:16:35 -07:00
Dan Petro 52992277a1 Merge pull request #69 from six2dez/patch-2
Update requirements.txt
2024-02-19 09:17:56 -07:00
six2dez d9a3b39f5a Update requirements.txt
Fixing requirements
2023-04-14 13:11:37 +02:00
six2dez 71bc1e5bff Update requirements.txt 2023-04-14 13:07:54 +02:00
Dan Petro 5b15ce71b1 Merge pull request #68 from six2dez/patch-1
Update requirements.txt
2023-01-05 10:50:11 -07:00
Dan Petro cf71009811 Add link to eyeballer.bishopfox.com to readme 2023-01-05 10:44:13 -07:00
six2dez ba73c9d37a Update requirements.txt
Most distros return the error `ModuleNotFoundError: No module named 'sklearn'` because of missing dependency `scipy`
2022-12-30 23:59:36 +01:00
Dan Petro d1d5182411 Update readme to include new kaggle location of data 2021-06-10 15:30:37 -07:00
Dan Petro b7a4ce2ca3 Minor tweak to verify util script 2021-06-10 13:22:46 -07:00
52 changed files with 5471 additions and 35546 deletions

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
name: Tests
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install dependencies
run: uv sync --group dev
- name: Run tests
run: uv run pytest tests/ -v
+1 -1
View File
@@ -104,7 +104,7 @@ venv.bak/
.mypy_cache/
#screenshot data
images/*.png
*.png
*.jpg
*.tar
+22 -11
View File
@@ -7,6 +7,7 @@ Give those screenshots of yours a quick eyeballing.
Eyeballer is meant for large-scope network penetration tests where you need to find "interesting" targets from a huge set of web-based hosts. Go ahead and use your favorite screenshotting tool like normal (EyeWitness or GoWitness) and then run them through Eyeballer to tell you what's likely to contain vulnerabilities, and what isn't.
Give it a try live at: https://eyeballer.bishopfox.com
### Example Labels
@@ -41,17 +42,22 @@ Parked domains are websites that look real, but aren't valid attack surface. The
## Setup
Download required packages on pip:
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you haven't already:
```
sudo pip3 install -r requirements.txt
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Then install eyeballer and its dependencies:
```
uv sync
```
Or if you want GPU support:
```
sudo pip3 install -r requirements-gpu.txt
uv sync --extra gpu
```
**NOTE**: Setting up a GPU for use with TensorFlow is way beyond the scope of this README. There's hardware compatibility to consider, drivers to install... There's a lot. So you're just going to have to figure this part out on your own if you want a GPU. But at least from a Python package perspective, the above requirements file has you covered.
**NOTE**: Setting up a GPU for use with TensorFlow is way beyond the scope of this README. There's hardware compatibility to consider, drivers to install... There's a lot. So you're just going to have to figure this part out on your own if you want a GPU. But at least from a Python package perspective, the above command has you covered.
**Pretrained Weights**
@@ -59,15 +65,20 @@ For the latest pretrained weights, check out the releases here on GitHub.
**Training Data** You can find our training data here:
https://www.dropbox.com/s/rpylhiv2g0kokts/eyeballer-3.0.zip?dl=1
https://www.kaggle.com/altf42600/pentest-screensots
There's two things you need from the training data:
1. `images/` folder, containing all the screenshots (resized down to 224x224)
2. `labels.csv` that has all the labels
3. `bishop-fox-pretrained-v3.h5` A pretrained weights file you can use right out of the box without training.
Copy all three into the root of the Eyeballer code tree.
Copy both into the root of the Eyeballer code tree.
Additionally, you can find a pretrained weights file you can use right out of the box without training.
1. `bishop-fox-pretrained-vN.h5`
It's here on GitHub, look at the `releases` section for the latest one.
## Predicting Labels
@@ -76,13 +87,13 @@ NOTE: For best results, make sure you screenshot your websites in a native 1.6x
To eyeball some screenshots, just run the "predict" mode:
```
eyeballer.py --weights YOUR_WEIGHTS.h5 predict YOUR_FILE.png
uv run eyeballer.py --weights YOUR_WEIGHTS.h5 predict YOUR_FILE.png
```
Or for a whole directory of files:
```
eyeballer.py --weights YOUR_WEIGHTS.h5 predict PATH_TO/YOUR_FILES/
uv run eyeballer.py --weights YOUR_WEIGHTS.h5 predict PATH_TO/YOUR_FILES/
```
Eyeballer will spit the results back to you in human readable format (a `results.html` file so you can browse it easily) and machine readable format (a `results.csv` file).
@@ -112,7 +123,7 @@ For a detailed explanation on [Precision vs Recall, check out Wikipedia](https:/
## Training
To train a new model, run:
```
eyeballer.py train
uv run eyeballer.py train
```
You'll want a machine with a good GPU for this to run in a reasonable amount of time. Setting that up is outside the scope of this readme, however.
@@ -124,7 +135,7 @@ This will output a new model file (weights.h5 by default).
You just trained a new model, cool! Let's see how well it performs against some images it's never seen before, across a variety of metrics:
```
eyeballer.py --weights YOUR_WEIGHTS.h5 evaluate
uv run eyeballer.py --weights YOUR_WEIGHTS.h5 evaluate
```
The output will describe the model's accuracy in both recall and precision for each of the program's labels. (Including "none of the above" as a pseudo-label)
+5 -2
View File
@@ -11,8 +11,11 @@ class HeatMap():
def __init__(self, screenshot_path, model, threshold=0.5, boxsize=28, step=7):
self.model = model
img = tf.keras.preprocessing.image.load_img(screenshot_path, target_size=(self.model.image_width, self.model.image_height))
img = tf.keras.preprocessing.image.img_to_array(img)
try:
img = tf.keras.preprocessing.image.load_img(screenshot_path, target_size=(self.model.image_width, self.model.image_height))
img = tf.keras.preprocessing.image.img_to_array(img)
except Exception:
img = None
self.screenshot = img
self.boxsize = boxsize
+35
View File
@@ -0,0 +1,35 @@
[project]
name = "eyeballer"
version = "3.0.0"
description = "Give those screenshots of yours a quick eyeballing."
requires-python = ">=3.8"
dependencies = [
"Augmentor",
"click",
"matplotlib",
"numpy",
"pandas",
"pillow",
"scikit-learn",
"tensorflow",
"jinja2",
"progressbar2",
"scipy",
]
[project.optional-dependencies]
gpu = [
"tensorflow[and-cuda]",
]
[project.scripts]
eyeballer = "eyeballer:cli"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[dependency-groups]
dev = [
"pytest>=8.3.5",
]
-10
View File
@@ -1,10 +0,0 @@
Augmentor
click
matplotlib
numpy
pandas
pillow
sklearn
tensorflow-gpu
jinja2
progressbar2
-10
View File
@@ -1,10 +0,0 @@
Augmentor
click
matplotlib
numpy
pandas
pillow
sklearn
tensorflow
jinja2
progressbar2
-142
View File
@@ -1,142 +0,0 @@
import errno
import os
import unittest
import sys
from eyeballer.model import EyeballModel
from eyeballer.visualization import HeatMap
class PredictTest(unittest.TestCase):
def setUp(self):
class DummyFile(object):
def write(self, x): pass
def flush(self): pass
sys.stdout = DummyFile()
weights_file = "tests/models/test_weights.h5"
if not os.path.isfile(weights_file):
print("Error: Symlink the latest weights file to " + weights_file)
raise FileNotFoundError(
errno.ENOENT,
os.strerror(errno.ENOENT),
weights_file)
model_kwargs = {
"weights_file": weights_file,
"print_summary": False,
"seed": None,
"quiet": True
}
self.model = EyeballModel(**model_kwargs)
def test_different_seed_predict(self):
model_kwargs = {
"weights_file": None,
"print_summary": False,
"seed": 12345678,
"quiet": True
}
same_seed_model = EyeballModel(**model_kwargs)
screenshot = "tests/data/404.png"
results_one = self.model.predict(screenshot)
results_two = same_seed_model.predict(screenshot)
self.assertNotEqual(results_one, results_two)
def test_same_seed_predict(self):
model_kwargs = {
"weights_file": None,
"print_summary": False,
"seed": 12345678,
"quiet": True
}
same_seed_model = EyeballModel(**model_kwargs)
screenshot = "tests/data/404.png"
results_one = same_seed_model.predict(screenshot)
results_two = same_seed_model.predict(screenshot)
self.assertEqual(results_one, results_two)
def test_predict_custom404(self):
screenshot = "tests/data/404.png"
results = self.model.predict(screenshot)[0]
self.assertGreater(results["custom404"], 0.5)
def test_predict_not_custom404(self):
screenshot = "tests/data/nothing.png"
results = self.model.predict(screenshot)[0]
self.assertLess(results["custom404"], 0.5)
def test_predict_login(self):
screenshot = "tests/data/login.png"
results = self.model.predict(screenshot)[0]
self.assertGreater(results["login"], 0.5)
def test_predict_not_login(self):
screenshot = "tests/data/nothing.png"
results = self.model.predict(screenshot)[0]
print(screenshot, results)
self.assertLess(results["login"], 0.5)
def test_predict_homepage(self):
screenshot = "tests/data/homepage.png"
results = self.model.predict(screenshot)[0]
self.assertGreater(results["homepage"], 0.5)
def test_predict_not_homepage(self):
screenshot = "tests/data/nothing.png"
results = self.model.predict(screenshot)[0]
self.assertLess(results["homepage"], 0.5)
def test_predict_oldlooking(self):
screenshot = "tests/data/old-looking.png"
results = self.model.predict(screenshot)[0]
self.assertGreater(results["oldlooking"], 0.5)
def test_predict_not_oldlooking(self):
screenshot = "tests/data/nothing.png"
results = self.model.predict(screenshot)[0]
self.assertLess(results["oldlooking"], 0.5)
def test_file_doesnt_exist(self):
screenshot = "tests/data/doesnotexist.png"
try:
self.model.predict(screenshot)[0]
self.fail("FileNotFoundError was expected but not found")
except FileNotFoundError:
pass
def test_folder(self):
screenshots = "tests/data/"
results = self.model.predict(screenshots)
self.assertEqual(len(results), 7)
def test_file_is_empty(self):
"""
We're just testing that it doesn't crash, basically
"""
screenshot = "tests/data/empty.png"
self.model.predict(screenshot)
def test_file_is_invalid(self):
"""
We're just testing that it doesn't crash, basically
"""
screenshot = "tests/data/invalid.png"
self.model.predict(screenshot)
def test_heatmap(self):
screenshot = "tests/data/login.png"
HeatMap(screenshot, self.model, 0.5)
screenshot = "tests/data/404.png"
HeatMap(screenshot, self.model, 0.5)
screenshot = "tests/data/empty.png"
HeatMap(screenshot, self.model, 0.5)
+1 -1
View File
@@ -1 +1 @@
from tests.PredictTest import * # noqa
from tests.test_predict import * # noqa
Binary file not shown.
+141
View File
@@ -0,0 +1,141 @@
import os
import pytest
from eyeballer.model import EyeballModel
from eyeballer.visualization import HeatMap
@pytest.fixture
def model():
weights_file = "tests/models/test_weights.h5"
if not os.path.isfile(weights_file):
pytest.skip("Symlink the latest weights file to " + weights_file)
model_kwargs = {
"weights_file": weights_file,
"print_summary": False,
"seed": None,
"quiet": True
}
return EyeballModel(**model_kwargs)
def test_different_seed_predict(model):
model_kwargs = {
"weights_file": None,
"print_summary": False,
"seed": 12345678,
"quiet": True
}
same_seed_model = EyeballModel(**model_kwargs)
screenshot = "tests/data/404.png"
results_one = model.predict(screenshot)
results_two = same_seed_model.predict(screenshot)
assert results_one != results_two
def test_same_seed_predict():
model_kwargs = {
"weights_file": None,
"print_summary": False,
"seed": 12345678,
"quiet": True
}
same_seed_model = EyeballModel(**model_kwargs)
screenshot = "tests/data/404.png"
results_one = same_seed_model.predict(screenshot)
results_two = same_seed_model.predict(screenshot)
assert results_one == results_two
def test_predict_custom404(model):
screenshot = "tests/data/404.png"
results = model.predict(screenshot)[0]
assert results["custom404"] > 0.5
def test_predict_not_custom404(model):
screenshot = "tests/data/nothing.png"
results = model.predict(screenshot)[0]
assert results["custom404"] < 0.5
def test_predict_login(model):
screenshot = "tests/data/login.png"
results = model.predict(screenshot)[0]
assert results["login"] > 0.5
def test_predict_not_login(model):
screenshot = "tests/data/nothing.png"
results = model.predict(screenshot)[0]
assert results["login"] < 0.5
def test_predict_homepage(model):
screenshot = "tests/data/homepage.png"
results = model.predict(screenshot)[0]
assert results["webapp"] > 0.5
def test_predict_not_homepage(model):
screenshot = "tests/data/nothing.png"
results = model.predict(screenshot)[0]
assert results["webapp"] < 0.5
def test_predict_oldlooking(model):
screenshot = "tests/data/old-looking.png"
results = model.predict(screenshot)[0]
assert results["oldlooking"] > 0.5
def test_predict_not_oldlooking(model):
screenshot = "tests/data/nothing.png"
results = model.predict(screenshot)[0]
assert results["oldlooking"] < 0.5
def test_file_doesnt_exist(model):
screenshot = "tests/data/doesnotexist.png"
with pytest.raises(FileNotFoundError):
model.predict(screenshot)
def test_folder(model):
screenshots = "tests/data/"
results = model.predict(screenshots)
assert len(results) == 7
def test_file_is_empty(model):
"""
We're just testing that it doesn't crash, basically
"""
screenshot = "tests/data/empty.png"
model.predict(screenshot)
def test_file_is_invalid(model):
"""
We're just testing that it doesn't crash, basically
"""
screenshot = "tests/data/invalid.png"
model.predict(screenshot)
def test_heatmap(model):
screenshot = "tests/data/login.png"
HeatMap(screenshot, model, 0.5)
screenshot = "tests/data/404.png"
HeatMap(screenshot, model, 0.5)
screenshot = "tests/data/empty.png"
HeatMap(screenshot, model, 0.5)
+4 -1
View File
@@ -15,11 +15,14 @@ with open("labels.csv", newline="") as csvfile:
for row in csvreader:
label_list.append(row["filename"])
print(len(file_list), "files")
print(len(label_list), "labels")
# Loop through the file
print("Rows in labels.csv, but no image exists:")
for label in label_list:
if label not in file_list:
print(row["filename"])
print(label)
print("Images that exist, but don't have labels:")
i = 0
Generated
+5241
View File
File diff suppressed because it is too large. Load diff
-48
View File
@@ -1,48 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
.vscode/
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
-11
View File
@@ -1,11 +0,0 @@
# Weballer
Web front-end for Eyeballer
### Setup
```
npm install
ng serve
```
-133
View File
@@ -1,133 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"weballer": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/weballer",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"./node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "weballer:build"
},
"configurations": {
"production": {
"browserTarget": "weballer:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "weballer:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "weballer:serve"
},
"configurations": {
"production": {
"devServerTarget": "weballer:serve:production"
}
}
}
}
}
},
"defaultProject": "weballer",
"cli": {
"analytics": false
}
}
-36
View File
@@ -1,36 +0,0 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};
-23
View File
@@ -1,23 +0,0 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('weballer app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
-11
View File
@@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}
-14
View File
@@ -1,14 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
-32
View File
@@ -1,32 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/weballer'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
-33973
View File
File diff suppressed because it is too large. Load diff
-54
View File
@@ -1,54 +0,0 @@
{
"name": "weballer",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.2.6",
"@angular/cdk": "^11.2.5",
"@angular/common": "~11.2.6",
"@angular/compiler": "~11.2.6",
"@angular/core": "~11.2.6",
"@angular/flex-layout": "^11.0.0-beta.33",
"@angular/forms": "~11.2.6",
"@angular/material": "^11.2.5",
"@angular/platform-browser": "~11.2.6",
"@angular/platform-browser-dynamic": "~11.2.6",
"@angular/router": "~11.2.6",
"@fortawesome/fontawesome-free": "^5.15.3",
"@tensorflow/tfjs": "^3.3.0",
"base64-arraybuffer": "^0.2.0",
"ngx-dropzone": "^3.0.0",
"rxjs": "~6.6.6",
"tslib": "^2.0.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.1102.5",
"@angular/cli": "~11.2.5",
"@angular/compiler-cli": "~11.2.6",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"@types/webgl2": "0.0.6",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.2.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.1.5"
}
}
-13
View File
@@ -1,13 +0,0 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { EyeballerComponent } from './components/eyeballer/eyeballer.component';
const routes: Routes = [
{ path: '', component: EyeballerComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
exports: [RouterModule]
})
export class AppRoutingModule { }
-2
View File
@@ -1,2 +0,0 @@
<router-outlet>
<router-outlet>
View File
Whitespace-only changes.
-35
View File
@@ -1,35 +0,0 @@
import { TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'weballer'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('weballer');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('weballer app is running!');
});
});
-10
View File
@@ -1,10 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'weballer';
}
-28
View File
@@ -1,28 +0,0 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from "@angular/flex-layout";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgxDropzoneModule } from 'ngx-dropzone';
import { BaseMaterialModule } from './base-materials';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { EyeballerComponent } from './components/eyeballer/eyeballer.component';
@NgModule({
declarations: [
AppComponent,
EyeballerComponent
],
imports: [
BrowserModule,
AppRoutingModule,
NgxDropzoneModule,
BrowserAnimationsModule,
BaseMaterialModule,
FlexLayoutModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
-58
View File
@@ -1,58 +0,0 @@
/*
Electric Scan
Copyright (C) 2019 Bishop Fox
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { NgModule } from '@angular/core';
import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatOptionModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatExpansionModule } from '@angular/material/expansion';
const modules = [
MatButtonModule, MatCheckboxModule, MatOptionModule, MatSelectModule, MatMenuModule, MatTabsModule,
MatFormFieldModule, MatTooltipModule, MatToolbarModule, MatIconModule, MatProgressSpinnerModule,
MatDialogModule, MatGridListModule, MatCardModule, MatTableModule, MatSortModule, MatInputModule,
MatSnackBarModule, MatSlideToggleModule, MatDividerModule, MatProgressBarModule, MatBadgeModule,
MatSidenavModule, MatExpansionModule
];
@NgModule({
imports: modules,
exports: modules,
})
export class BaseMaterialModule { }
@@ -1,114 +0,0 @@
<mat-toolbar class="app-toolbar" color="primary" fxLayout="row" fxLayoutAlign="start center">
<h2>
<img src="assets/eyeballer_logo.png" width="46" height="60">
</h2>
Eyeballer
<span fxFlex></span>
<span>Bishop Fox R&D</span>
<span fxFlex></span>
<button mat-raised-button (click)="restart()">
<i class="fas fa-fw fa-undo"></i>
Restart
</button>
</mat-toolbar>
<!-- Model Loading Progress -->
<div *ngIf="!tfFilesCompleted">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
<!-- Drop Zone -->
<div *ngIf="tfFilesCompleted && !eyeballing && !eyeballCompleted">
<mat-card>
Drop your screenshots below:
<br><br>
<ngx-dropzone (change)="onSelect($event)">
<ngx-dropzone-label>
Files dropped here are <span style="font-weight: bold">not uploaded anywhere.</span> No data leaves your computer at all on this tool.
</ngx-dropzone-label>
</ngx-dropzone>
</mat-card>
<br />
<mat-card><span style="font-weight: bold">Note:</span> For best results,
ensure your screenshots are taken natively in a <span style="font-weight: bold">16:10 aspect ratio.</span> (ie: 1440x900 or other)
</mat-card>
<mat-card>
Source code available at <a href="https://github.com/BishopFox/eyeballer" target="_blank" rel="noopener noreferrer">GitHub</a>
</mat-card>
</div>
<!-- Progress Bar -->
<div *ngIf="tfFilesCompleted && eyeballing && !eyeballCompleted">
<mat-card>
<h3>Classifying: {{this.eyeballedCount}} of {{this.imageCount}}</h3>
<mat-progress-bar mode="determinate" [value]="eyeballPercent()"></mat-progress-bar>
</mat-card>
<mat-card>
This processing is happening in your browser. <span style="font-weight: bold">There is no backend.</span>
</mat-card>
</div>
<!-- Results -->
<div *ngIf="tfFilesCompleted && !eyeballing && eyeballCompleted">
<div class="results-container">
<h2>Results</h2>
<mat-toolbar>
<mat-toolbar-row style="height:40px;">
<span class="button-spacer"></span>
<button class="label-button" mat-raised-button color="primary" [disabled]="selected.webapp === 0" (click)="selected.webapp = 0; updateSelections()">Must Include</button>
<button class="label-button" mat-raised-button color="basic" [disabled]="selected.webapp === 1" (click)="selected.webapp = 1; updateSelections()">Webapp</button>
<button class="label-button" mat-raised-button color="accent" [disabled]="selected.webapp === 2" (click)="selected.webapp = 2; updateSelections()">Never Include</button>
<label class="label-count">{{ classifications.webapp.length }}</label>
<span class="button-spacer"></span>
</mat-toolbar-row>
<mat-toolbar-row style="height:40px;">
<span class="button-spacer"></span>
<button class="label-button" mat-raised-button color="primary" [disabled]="selected.oldLooking === 0" (click)="selected.oldLooking = 0; updateSelections()">Must Include</button>
<button class="label-button" mat-raised-button color="basic" [disabled]="selected.oldLooking === 1" (click)="selected.oldLooking = 1; updateSelections()">Old Looking</button>
<button class="label-button" mat-raised-button color="accent" [disabled]="selected.oldLooking === 2" (click)="selected.oldLooking = 2; updateSelections()">Never Include</button>
<label class="label-count">{{ classifications.oldLooking.length }}</label>
<span class="button-spacer"></span>
</mat-toolbar-row>
<mat-toolbar-row style="height:40px;">
<span class="button-spacer"></span>
<button class="label-button" mat-raised-button color="primary" mat-raised-button [disabled]="selected.loginPage === 0" (click)="selected.loginPage = 0; updateSelections()">Must Include</button>
<button class="label-button" mat-raised-button color="basic" [disabled]="selected.loginPage === 1" (click)="selected.loginPage = 1; updateSelections()">Login Page</button>
<button class="label-button" mat-raised-button color="accent" [disabled]="selected.loginPage === 2" (click)="selected.loginPage = 2; updateSelections()">Never Include</button>
<label class="label-count">{{ classifications.loginPage.length }}</label>
<span class="button-spacer"></span>
</mat-toolbar-row>
<mat-toolbar-row style="height:40px;">
<span class="button-spacer"></span>
<button class="label-button" mat-raised-button color="primary" mat-raised-button [disabled]="selected.custom404 === 0" (click)="selected.custom404 = 0; updateSelections()">Must Include</button>
<button class="label-button" mat-raised-button color="basic" [disabled]="selected.custom404 === 1" (click)="selected.custom404 = 1; updateSelections()">Custom 404</button>
<button class="label-button" mat-raised-button color="accent" [disabled]="selected.custom404 === 2" (click)="selected.custom404 = 2; updateSelections()">Never Include</button>
<label class="label-count">{{ classifications.custom404.length }}</label>
<span class="button-spacer"></span>
</mat-toolbar-row>
<mat-toolbar-row style="height:40px;">
<span class="button-spacer"></span>
<button class="label-button" mat-raised-button color="primary" mat-raised-button [disabled]="selected.parked === 0" (click)="selected.parked = 0; updateSelections()">Must Include</button>
<button class="label-button" mat-raised-button color="basic" [disabled]="selected.parked === 1" (click)="selected.parked = 1; updateSelections()">Parked Domain</button>
<button class="label-button" mat-raised-button color="accent" [disabled]="selected.parked === 2" (click)="selected.parked = 2; updateSelections()">Never Include</button>
<label class="label-count">{{ classifications.parked.length }}</label>
<span class="button-spacer"></span>
</mat-toolbar-row>
</mat-toolbar>
<mat-card>
<span style="font-weight: bold">Total Selected:</span> {{ selectedScreens.length }}
</mat-card>
<br>
<mat-card>
<button class="export-button" mat-raised-button color="primary" (click)="exportResults()">
Export
</button>
<a href="https://github.com/BishopFox/eyeballer#what-the-labels-mean" target="_blank" rel="noopener noreferrer">What do these labels mean?</a>
</mat-card>
</div>
</div>
@@ -1,128 +0,0 @@
.app-toolbar {
position: fixed;
position: -webkit-sticky; /* For macOS/iOS Safari */
top: 0; /* Sets the sticky toolbar to be on top */
z-index: 1000; /* Ensure that your app's content doesn't overlap the toolbar */
}
.not-completed-container {
display: grid;
justify-items: center;
align-items: center;
grid-template-columns: 1fr 1fr 1fr;
}
.not-completed-message {
grid-column: 2/3;
grid-row: 2/3;
}
.results-container {
display: grid;
justify-items: center;
align-items: center;
grid-template-columns: 1fr;
}
.eyeballed-class-container {
display: grid;
justify-items: center;
align-items: center;
// grid-template-rows: minmax(250px, auto);
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 1em;
margin: 1em;
}
.result-card {
max-width: 100%;
}
.result-card:hover {
background-color: #f5f5f5;
}
.result-card:hover img {
opacity: .5;
}
.result-card > div {
display: none;
position: absolute;
top: 50%;
left: 50%;
width: 70px;
height: 70px;
z-index: 10;
}
.fill-remaining-space {
flex: 1 1 auto;
}
.fill-space {
flex: auto;
}
.img-container {
position: relative;
text-align: center;
color: white;
}
.preview-image {
width: 100%;
}
.webapp-label {
background-color: blue;
color: white;
margin-left: 1px;
margin-right: 1px;
}
.old-looking-label {
background-color: green;
color: white;
margin-left: 1px;
margin-right: 1px;
}
.login-label {
background-color: purple;
color: white;
margin-left: 1px;
margin-right: 1px;
}
.custom-404-label {
background-color: orange;
color: white;
margin-left: 1px;
margin-right: 1px;
}
.parked-label {
background-color: red;
color: white;
margin-left: 1px;
margin-right: 1px;
}
.label-count {
margin-left: 10px;
width: 50px!important;
min-width: unset!important;
}
.label-button{
width: 130px!important;
min-width: unset!important;
}
.button-spacer {
flex: 1 1 auto;
}
.export-button {
margin-right: 10px;
}
@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { EyeballerComponent } from './eyeballer.component';
describe('EyeballerComponent', () => {
let component: EyeballerComponent;
let fixture: ComponentFixture<EyeballerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ EyeballerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EyeballerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,263 +0,0 @@
import { Component, OnInit } from '@angular/core';
import * as base64 from 'base64-arraybuffer';
import * as tf from '@tensorflow/tfjs';
@Component({
selector: 'app-eyeballer',
templateUrl: './eyeballer.component.html',
styleUrls: ['./eyeballer.component.scss']
})
export class EyeballerComponent implements OnInit {
offset = tf.scalar(127.5);
confidence = 0.5;
width = 224;
height = 224;
tfFilesCompleted = false;
tfFiles: File[] = [];
model: any = null;
imageCount = 0;
eyeballing = false;
eyeballCompleted = false;
eyeballedCount = 0;
classifications = {
custom404: [],
loginPage: [],
webapp: [],
oldLooking: [],
parked: [],
};
// What labels are selected?
// This is a tri-state:
// 0 means "always select"
// 1 means "don't care"
// 2 means "never select"
selected = {
custom404: 2,
loginPage: 1,
webapp: 0,
oldLooking: 1,
parked: 2,
}
selectedScreens: string[] = [];
constructor() {}
ngOnInit() {
this.fetchTfFiles();
}
async onSelect(event) {
this.imageCount = event.addedFiles.length;
this.eyeballing = true;
await this.classifyImages(event.addedFiles);
this.eyeballing = false;
this.eyeballCompleted = true;
await this.updateSelections();
}
async classifyImages(files) {
this.model = await tf.loadLayersModel(tf.io.browserFiles(this.tfFiles));
for(const file of files) {
await this.classify(file);
this.eyeballedCount++;
}
}
async classify(file) {
const dataString = await this.dataURI(file);
const img = new Image(this.width, this.height);
img.src = dataString;
img.onerror = () => {
return;
}
img.onload = () => {
tf.tidy(() => {
const tensor = tf.browser.fromPixels(img)
.resizeNearestNeighbor([224, 224])
.toFloat()
.sub(this.offset)
.div(this.offset)
.expandDims();
const resultTensor = <tf.Tensor<tf.Rank>> this.model.predict(tensor);
const predictions = resultTensor.dataSync();
tensor.dispose();
resultTensor.dispose();
if (predictions[0] > this.confidence) {
this.classifications.custom404.push(file.name);
}
if (predictions[1] > this.confidence) {
this.classifications.loginPage.push(file.name);
}
if (predictions[2] > this.confidence) {
this.classifications.webapp.push(file.name);
}
if (predictions[3] > this.confidence) {
this.classifications.oldLooking.push(file.name);
}
if (predictions[4] > this.confidence) {
this.classifications.parked.push(file.name);
}
});
};
}
isWebapp(key) {
if(this.classifications.webapp.includes(key)) {
return "Webapp"
}
return ""
}
isCustom404(key) {
if(this.classifications.custom404.includes(key)) {
return "Custom 404"
}
return ""
}
isLoginPage(key) {
if(this.classifications.loginPage.includes(key)) {
return "Login Page"
}
return ""
}
isOldLooking(key) {
if(this.classifications.oldLooking.includes(key)) {
return "Old Looking"
}
return ""
}
isParked(key) {
if(this.classifications.parked.includes(key)) {
return "Parked Domain"
}
return ""
}
async fetchTfFiles() {
let resp = await fetch('assets/tf/model.json');
const manifest = await resp.json();
const paths: string[] = Array.from(manifest.weightsManifest[0]?.paths);
this.tfFiles = [];
resp = await fetch('assets/tf/model.json');
const blob = await resp.blob();
this.tfFiles.push(new File([blob], 'model.json'));
await Promise.all(paths.map(async (path) => {
const tfFile = await this.fetchTfFile(path);
this.tfFiles.push(tfFile);
}));
this.tfFilesCompleted = true;
}
async fetchTfFile(name: string): Promise<File> {
const base = name.split('/').reverse()[0];
const resp = await fetch(`assets/tf/${base}`);
const blob = await resp.blob();
return new File([blob], base);
}
async updateSelections() {
// First thing: load up all the images into the set
let selectedScreensSet = new Set([]);
for (let entry of this.classifications.webapp) {
selectedScreensSet.add(entry);
}
for (let entry of this.classifications.oldLooking) {
selectedScreensSet.add(entry);
}
for (let entry of this.classifications.loginPage) {
selectedScreensSet.add(entry);
}
for (let entry of this.classifications.custom404) {
selectedScreensSet.add(entry);
}
for (let entry of this.classifications.parked) {
selectedScreensSet.add(entry);
}
let selectionsArray = Array.from(selectedScreensSet);
// Calculate intersections for each "must include" selections
if (this.selected.webapp === 0){
let tempArray = selectionsArray.filter(value => this.classifications.webapp.includes(value));
selectionsArray = tempArray;
}
if (this.selected.oldLooking === 0){
let tempArray = selectionsArray.filter(value => this.classifications.oldLooking.includes(value));
selectionsArray = tempArray;
}
if (this.selected.loginPage === 0){
let tempArray = selectionsArray.filter(value => this.classifications.loginPage.includes(value));
selectionsArray = tempArray;
}
if (this.selected.custom404 === 0){
let tempArray = selectionsArray.filter(value => this.classifications.custom404.includes(value));
selectionsArray = tempArray;
}
if (this.selected.parked === 0){
let tempArray = selectionsArray.filter(value => this.classifications.parked.includes(value));
selectionsArray = tempArray;
}
// Now remove any "Must not include" selection
selectedScreensSet = new Set(selectionsArray);
if (this.selected.webapp === 2){
for (let entry of this.classifications.webapp) {
selectedScreensSet.delete(entry);
}
}
if (this.selected.oldLooking === 2){
for (let entry of this.classifications.oldLooking) {
selectedScreensSet.delete(entry);
}
}
if (this.selected.loginPage === 2){
for (let entry of this.classifications.loginPage) {
selectedScreensSet.delete(entry);
}
}
if (this.selected.custom404 === 2){
for (let entry of this.classifications.custom404) {
selectedScreensSet.delete(entry);
}
}
if (this.selected.parked === 2){
for (let entry of this.classifications.parked) {
selectedScreensSet.delete(entry);
}
}
this.selectedScreens = Array.from(selectedScreensSet);
}
async dataURI(file: File): Promise<string> {
const buf = await file.arrayBuffer();
let ext = file.name.split('.').reverse()[0]?.toLocaleLowerCase();
if (!["jpg", "jpeg", "png", "gif", "bmp"].some(allow => allow === ext)) {
ext = "jpg";
}
return `data:image/${encodeURIComponent(ext)};base64,${base64.encode(buf)}`;
}
eyeballPercent() {
return (this.eyeballedCount / this.imageCount) * 100;
}
restart() {
window.location.reload();
}
async exportResults() {
const str = this.selectedScreens.join("\n");
const blob = new Blob([str], { type: 'text/csv' });
const url= window.URL.createObjectURL(blob);
window.open(url);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
{"format": "layers-model", "generatedBy": "keras v2.2.4-tf", "convertedBy": "TensorFlow.js Converter v3.1.0", "modelTopology": {"keras_version": "2.2.4-tf", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "Model", "config": {"name": "mobilenet_1.00_224", "layers": [{"name": "input_1", "class_name": "InputLayer", "config": {"batch_input_shape": [null, 224, 224, 3], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_1"}, "inbound_nodes": []}, {"name": "conv1_pad", "class_name": "ZeroPadding2D", "config": {"name": "conv1_pad", "trainable": true, "dtype": "float32", "padding": [[0, 1], [0, 1]], "data_format": "channels_last"}, "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"name": "conv1", "class_name": "Conv2D", "config": {"name": "conv1", "trainable": true, "dtype": "float32", "filters": 32, "kernel_size": [3, 3], "strides": [2, 2], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null, "dtype": "float32"}}, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["conv1_pad", 0, 0, {}]]]}, {"name": "conv1_bn", "class_name": "BatchNormalization", "config": {"name": "conv1_bn", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "gamma_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "moving_variance_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["conv1", 0, 0, {}]]]}, {"name": "conv1_relu", "class_name": "ReLU", "config": {"name": "conv1_relu", "trainable": true, "dtype": "float32", "max_value": 6.0, "negative_slope": 0.0, "threshold": 0.0}, "inbound_nodes": [[["conv1_bn", 0, 0, {}]]]}, {"name": "conv_dw_1", "class_name": "DepthwiseConv2D", "config": {"name": "conv_dw_1", "trainable": true, "dtype": "float32", "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": false, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "bias_regularizer": null, "activity_regularizer": null, "bias_constraint": null, "depth_multiplier": 1, "depthwise_initializer": {"class_name": "GlorotUniform", "config": {"seed": null, "dtype": "float32"}}, "depthwise_regularizer": null, "depthwise_constraint": null}, "inbound_nodes": [[["conv1_relu", 0, 0, {}]]]}, {"name": "conv_dw_1_bn", "class_name": "BatchNormalization", "config": {"name": "conv_dw_1_bn", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "gamma_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "moving_mean_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "moving_variance_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null}, "inbound_nodes": [[["conv_dw_1", 0, 0, {}]]]}, {"name": "conv_dw_1_relu", "class_name": "ReLU", "config": {"name": "conv_dw_1_relu", "trainable": true, "dtype": "float32", "max_value": 6.0, "negative_slope": 0.0, "threshold": 0.0}, "inbound_nodes": [[["conv_dw_1_bn", 0, 0, {}]]]}, {"name": "conv_pw_1", "class_name": "Conv2D", "config": {"name": "conv_pw_1", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [1, 1], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "linear", "use_bias": false, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null, "dtype": "float32"}}, "bias_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["conv_dw_1_relu", 0, 0, {}]]]}, {"name": "conv_pw_1_bn", "class_name": "BatchNormalization", "config": {"name": "conv_pw_1_bn", "trainable": true, "dtype": "float32", "axis": [3], "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"class_name": "Zeros", "config": {"dtype": "float32"}}, "gamma_initializer": {"class_name": "Ones", "config": {"dtype": "float32"}}, "moving_mean_initializer": {"class_Line truncated
@@ -1,3 +0,0 @@
export const environment = {
production: true
};
-16
View File
@@ -1,16 +0,0 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

-19
View File
@@ -1,19 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Eyeballer</title>
<base href=".">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>
<h1>
<div style="text-align: center; margin: auto; width: 50%; padding: 50px 0;">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
</h1>
</app-root>
</body>
</html>
-12
View File
@@ -1,12 +0,0 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
-63
View File
@@ -1,63 +0,0 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
-13
View File
@@ -1,13 +0,0 @@
@import '@angular/material/prebuilt-themes/deeppurple-amber.css';
html, body {
margin: 0;
padding: 0;
font-family: Roboto, Arial, sans-serif;
height: 90%;
}
body {
margin-top: 64px;
}
-25
View File
@@ -1,25 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
-15
View File
@@ -1,15 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
-20
View File
@@ -1,20 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
}
}
-18
View File
@@ -1,18 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
-152
View File
@@ -1,152 +0,0 @@
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef": [
true,
"call-signature"
],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": [
"codelyzer"
]
}