diff --git a/copilot/controllers.py b/copilot/controllers.py index bd91a24..68202b3 100644 --- a/copilot/controllers.py +++ b/copilot/controllers.py @@ -1,33 +1,13 @@ -from copilot.models.trainer import get_ap_status -from copilot.models.profile import get_profile_status +from flask import flash #stat logging import logging log = logging.getLogger(__name__) -def get_status_items(): - """Get current status items. - icon: the ID of the svg to use. - value: The text to put under the icon - status: [off/on/error] The color of the icon background to use (off=grey, on=green, error=orange) - """ - log.warn("TODO: get_status_items is currently not fully implemented") - profile = get_profile_status() - access_point = get_ap_status() - status_items = [{"icon":"wifi", - "value":access_point['value'], - "status":access_point['status'], - "url":"config"}, - {"icon":"config", - "value":"Configure", - "status":"off", - "url":"config"}, - {"icon":"profile", - "value":profile['value'], - "status":profile['status'], - "url":"profile_current"}, - {"icon":"load", - "value":"Load Profile", - "status":"off", - "url":"profile_load"}] - return status_items +def flash_errors(form): + for field, errors in form.errors.items(): + for error in errors: + flash(u"Error in the %s field - %s" % ( + getattr(form, field).label.text, + error + ), "error") diff --git a/copilot/models/config.py b/copilot/models/config.py index 3be2722..9cb49ca 100644 --- a/copilot/models/config.py +++ b/copilot/models/config.py @@ -1,32 +1,26 @@ import os import string +import importlib from urlparse import urlparse +from copilot.utils.file_sys import get_usb_dirs, get_likely_usb +from ConfigParser import SafeConfigParser +from copilot.utils.plugin import is_plugin, get_plugins #stat logging import logging log = logging.getLogger(__name__) -""" -CP_PACKAGES = { "SERVICE NAME" : { - "name" : "SERVICE NAME", - "config_file": "CONFIG FILE NAME", - "target" : "TARGET NAME", - "actions": ["ACTION 001", "ACTION 002", "ACTION ETC"], - "directory":"DIRECTORY HEADING FROM CP_DIRS VAR"} -""" -#TODO Remove this variable and pull package configs from some sort of config file -CP_PACKAGES = {"dnschef":{"name": "dnschef", - "config_file": "dnschef.conf", - "target" : "dns", - "actions": ["block", "redirect"], - "directory":"main"}, - "create_ap":{"name": "create_ap", - "config_file": "ap.conf", - "directory":"main"}} def get_config_dir(directory): directories = {"main" : "/tmp/copilot/", - "profiles" : "/tmp/copilot/profiles"} + "profiles" : "/tmp/copilot/profiles/", + "temporary" : "/tmp/copilot/tmp/"} + # Adding plugin directories + plugins = get_value_dict("directory") + for p in plugins: + directories[p] = plugins[p][0] + # Adding USB directory + directories["usb"] = get_likely_usb() if directory in directories: log.debug("Directory {0} found and being returned.".format(directory)) return directories[directory] @@ -35,30 +29,112 @@ def get_config_dir(directory): def get_config_file(config): """ return the path to a config file.""" - _copilot_dir = get_config_dir("main") - if config in CP_PACKAGES: - if "config_file" in CP_PACKAGES[config]: - try: - _directory = get_config_dir(CP_PACKAGES[config]["directory"]) - _path = os.path.join(_directory, CP_PACKAGES[config]["config_file"]) - log.debug("Returning config path {0}".format(_path)) - return _path - except ValueError as err: - log.error("Directory found in CP_PACKAGES under the {0} package was invalid.".format(config)) - raise ValueError(err) - else: - raise ValueError("That config file is not valid.") + log.info("getting {0} config file.".format(config)) + directory = get_option("directory", config)[0] + log.debug("found directory {0}".format(directory)) + config_file = get_option("config_file", config)[0] + log.debug("found config file {0}".format(config_file)) + path = os.path.join(directory, config_file) + return path -def get_valid_actions(): - _valid_actions = ["block", "redirect"] - return _valid_actions +def get_valid_actions(package=None): + """ Returns the valid actions for a package, or all packages as a list""" + if not package: + return get_unique_values("actions") + else: + return get_option("actions", package) def get_valid_targets(): - _targets = [] - for item in CP_PACKAGES: - if "target" in CP_PACKAGES[item]: - _targets.append(CP_PACKAGES[item]["target"]) - return _targets + log.info("getting valid targets.") + return get_unique_values("target") + +def get_config_writer(name): + """ + Get a plugins config writer object + """ + log.info("getting a plugins config writer.") + if not is_plugin(name): + raise ValueError("{0} is not a plugin.".format(name)) + config = importlib.import_module('copilot.plugins.{0}.config'.format(name)) + log.debug("{0} contains {1}".format(config.__name__, dir(config))) + writer = config.ConfigWriter() + return writer + +def get_option(option, plugin): + """Get an option from a plugin config file as a list.""" + log.info("getting option {0} from {1}'s config file.".format(option, plugin)) + if not is_plugin(plugin): + raise ValueError("{0} is not a plugin.".format(plugin)) + plugin = PluginConfig(plugin) + if plugin.valid(): + try: + option_found = plugin.data['info'][option] + log.debug("returning {0} option.".format(option_found)) + return option_found + except KeyError as err: + log.warning("Plugin {0} does not have a {1} key.".format(p, option)) + return [] + else: + return [] + +def get_unique_values(option): + """Returns a list of a specific key's value across all plugins config files with no repeats.""" + log.info("getting all unique values for option {0}.".format(option)) + values = [] + val_list = get_value_dict(option) + for plugin in val_list: + # All values are returned as a list + for j in val_list[plugin]: + if j not in values: + values.append(j) + log.debug("unique values found: {0}".format(values)) + return values + +def get_value_list(option): + """Returns a list of (plugin,[value1, value2, value3]) tuples of a specific key's value across all plugins config files.""" + log.info("Getting a list of all values") + plugins = get_plugins() + plist = [] + for p in plugins: + _plugin = PluginConfig(p) + if _plugin.valid(): + try: + plist.append((p, _plugin.data['info'][option])) + except KeyError as err: + log.warning("Plugin {0} does not have a {1} key.".format(p, option)) + log.debug("values found: {0}".format(plist)) + return plist + +def get_value_dict(option): + """Returns a dictionary of {plugin: [value1, value2, value3]} of a specific key's value across all plugins config files.""" + log.info("Getting a dict of all values") + plugins = get_plugins() + pdict = {} + for p in plugins: + _plugin = PluginConfig(p) + if _plugin.valid(): + try: + pdict[p] = _plugin.data['info'][option] + except KeyError as err: + log.warning("Plugin {0} does not have a {1} key.".format(p, option)) + log.debug("values found: {0}".format(pdict)) + return pdict + +def get_target_by_actions(): + log.info("getting targets (e.g. plugins) sorted by actions") + tar_act_dict = {} + tdict = get_value_dict("actions") + for target in tdict: + for action in tdict[target]: + if action not in tar_act_dict: + tar_act_dict[action] = [] + tar_act_dict[action].append(target) + elif target not in tar_act_dict[action]: + tar_act_dict[action].append(target) + else: + log.debug("Found action {0} in target {1}. This should not occur. A plugin is being examed twice or is somehow duplicated.".format(action, target)) + log.debug("target/action pairs found: {0}".format(tar_act_dict)) + return tar_act_dict class Config(object): @@ -69,16 +145,23 @@ def __init__(self): @property def config_type(self): - return self._config_type + try: + return self._config_type + except AttributeError as err: + log.debug("Config type is not yet set, returning empty string.") + return "" @config_type.setter def config_type(self, config_type): try: config_file = get_config_file(config_type) + log.debug("config file {0} found".format(config_file)) except ValueError as err: log.error("An invalid config type was passed. Please check \"get_config_file\" in the models/config.py scripts for the valid types of config files.") raise ValueError(err) + log.debug("setting config type.") self._config_type = config_type + log.debug("setting config file {0}.".format(config_file)) self.config_file = config_file def check_file(self): @@ -120,95 +203,122 @@ def write_header(self, config_file): else: log.debug("No header found.") -class DNSConfig(Config): - def __init__(self): - super(DNSConfig, self).__init__() - self.config_type = "dnschef" - self.header = "[A]\n" - - def add_rule(self, target, action, sub): - _rule = "" - _domain = "" - _address = "" + +class ProfileParser(SafeConfigParser): + def get_list(self,section,option): + value = self.get(section,option) + return list(filter(None, (x.strip() for x in value.splitlines()))) + +class ProfileWriter(ProfileParser): + def set_rule(self, rule): + action = rule[0] + target = rule[1] + sub = rule[2] + rule_list = [] + if not self.has_section(target): + self.add_section(target) + if self.has_option(target, action): + rule_list = self.get_list(target, action) + rule_list.append(sub) + text_rules = "\n\t".join(rule_list) + self.set(target, action, text_rules) + +class ProfileConfig(object): + def __init__(self, path): + self.path = os.path.abspath(path) + self.parser = ProfileParser() + if self.valid(): + self.data = self.build_map() + self.rules = self.get_rules() + + def get_rules(self): + """ + Returns a list of rules. + [["block", "dns", "www.internews.org"],["redirect", "dns", "info.internews"]] + """ + rules = [] + _val_targets = get_valid_targets() + for target in self.data: + if target in _val_targets: + for action in self.data[target]: + if action in get_valid_actions(target): + for sub in self.data[target][action]: + rules.append([action, target, sub]) + log.debug("Found rules: {0}".format(rules)) + return rules + + def valid(self): try: - _domain = self.get_dns(sub) - except ValueError as err: - log.warn("Could not add rule. Invalid Target.") - if action == "block": - log.debug("Found a blocking role. Setting address to localhost (127.0.0.1).") - _address = "127.0.0.1" - elif action == "redirect": - log.debug("Found a redirection role. Setting address to default AP address (192.168.12.1).") - _address = "192.168.12.1" - _rule = "{0} = {1}\n".format(_domain, _address) - self._rules.append(_rule) - - def get_dns(self, sub): - tld = ["ac", "ad", "ae", "aero", "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "arpa", "as", "asia", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "biz", "bj", "bl", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cat", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "com", "coop", "cr", "cs", "cu", "cv", "cw", "cx", "cy", "cz", "dd", "de", "dj", "dk", "dm", "do", "dz", "ec", "edu", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gov", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "info", "int", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jobs", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "local", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mf", "mg", "mh", "mil", "mk", "ml", "mm", "mn", "mo", "mobi", "mp", "mq", "mr", "ms", "mt", "mu", "museum", "mv", "mw", "mx", "my", "mz", "na", "name", "nato", "nc", "ne", "net", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "onion", "org", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "pro", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "su", "sv", "sx", "sy", "sz", "tc", "td", "tel", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tp", "tr", "travel", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "xxx", "ye", "yt", "yu", "za", "zm", "zr", "zw"] - _sub = sub - log.debug("sub target is {0}".format(_sub)) - if _sub == "": - return None - parsed = urlparse(_sub).path - log.debug("parsed url is {0}".format(parsed)) - split_sub = string.split(parsed, ".") - log.debug("split up sub target is {0}".format(split_sub)) - #TODO the below is a monstrosity it needs to be made to actually work - if len(split_sub) > 3: - log.error("The domain {0} has too many parts and cannot be processed. Use a simpler domain with MAX 3 parts.".format(_sub)) - raise ValueError("invalid url") - elif len(split_sub) == 3: - log.debug("The domain {0} has exactly three parts.".format(_sub)) - return parsed - elif len(split_sub) == 1: - log.debug("The domain {0} has exactly one part. Interpreting as a domain name only.".format(_sub)) - return "*.{0}.*".format(split_sub[0]) - elif (len(split_sub) == 2 and split_sub[1] not in tld): - log.debug("The domain {0} has exactly two parts, and the second part is NOT a top level domain I recognize. Interpreting as a host + a domain name.".format(_sub)) - return "{0}.{1}.*".format(split_sub[0], split_sub[1]) - elif split_sub[1] in tld: - log.debug("The domain {0} has exactly two parts, and the second part IS a top level domain I recognize. Interpreting as a domain name with a top level domain.".format(_sub)) - return "*.{0}.{1}".format(split_sub[0], split_sub[1]) - #todo add just TLD. - -class APConfig(Config): - - def __init__(self, ap_name, ap_password, iface_in="eth0", iface_out="wlan0"): - super(APConfig, self).__init__() - self._config_type = "create_ap" - self.iface_in = iface_in - self.iface_out = iface_out - self.header = "{0} {1} ".format(self.iface_out, self.iface_in) - self.ap_name = ap_name - self.ap_password = ap_password - self.add_rule(self.ap_name) - self.add_rule(self.ap_password) - self.config_type = "create_ap" + _data = self.parser.read(self.path) + except: + log.debug("Config file at {0} is not properly configured. Marking as invalid.".format(self.path)) + return False + if _data == []: + log.debug("Config file at {0} is not properly configured or does not exist. Marking as invalid.".format(self.path)) + return False + if not self.parser.has_option("info", "name"): + log.debug("Config file at {0} has no name and therefore cannot be used. Marking as invalid.".format(self.path)) + return False + #TODO Add config file format values for each module + log.info("Config file at {0} is properly formatted. Marking as valid.".format(self.path)) + return True - @property - def ap_password(self): - return self._ap_password - - @ap_password.setter - def ap_password(self, plaintext): - if (8 < len(str(plaintext)) <= 63 and - all(char in string.printable for char in plaintext)): - self._ap_password = plaintext - else: - raise ValueError("Access Point passwords must be between 8 and 63 characters long and use only printable ASCII characters.") + def build_map(self): + _dict = {} + _data = self.parser.read(self.path) + _sections = self.parser.sections() + log.debug("Config file has the following sections {0}.".format(_sections)) + for sect in _sections: + _dict[sect] = {} + _options = self.parser.options(sect) + log.debug("Config file section {0} has the following options {0}.".format(sect, _options)) + for opt in _options: + _dict[sect][opt] = self.parser.get_list(sect, opt) + return _dict - @property - def ap_name(self): - return self._ap_name - @ap_name.setter - def ap_name(self, name): - if 0 < len(str(name)) <= 31: - self._ap_name = name - else: - raise ValueError("Access Point names must be between 1 and 31 characters long.") - def add_rule(self, rule): - log.debug("adding rule {0}".format(rule)) - self._rules.append("{0} ".format(rule)) + +class PluginConfig(object): + def __init__(self, name): + self.path = os.path.abspath(os.path.join("/home/www/copilot/copilot/plugins/", name, "plugin.conf")) + self.parser = ProfileParser() + if self.valid(): + self.data = self.build_map() + + def build_map(self): + _dict = {} + _data = self.parser.readfp(open(self.path)) + _sections = self.parser.sections() + log.debug("Config file has the following sections {0}.".format(_sections)) + for sect in _sections: + _dict[sect] = {} + log.debug("getting options for section {0}".format(sect)) + _options = self.parser.options(sect) + log.debug("It has the following options {0}.".format(_options)) + for opt in _options: + _dict[sect][opt] = self.parser.get_list(sect, opt) + log.debug("Created below plugin data map. \n {0}".format(_dict)) + return _dict + + def valid(self): + try: + _data = self.parser.read(self.path) + except: + log.info("Config file at {0} is not properly configured. Marking as invalid.".format(self.path)) + return False + if _data == []: + log.info("Config file at {0} is not properly configured or does not exist. Marking as invalid.".format(self.path)) + return False + required = ["name", "config_file", "directory"] + desired = ["target", "actions"] + for r in required: + if not self.parser.has_option("info", r): + log.info("Config file at {0} has no {1} and therefore cannot be used. Marking as invalid.".format(self.path, r)) + return False + for r in desired: + if not self.parser.has_option("info", r): + log.info("Config file at {0} has no {1} and will not generate rules.".format(self.path, r)) + log.info("Config file at {0} is properly formatted. Marking as valid.".format(self.path)) + return True diff --git a/copilot/models/profile.py b/copilot/models/profile.py index 9c14b1f..361bae3 100644 --- a/copilot/models/profile.py +++ b/copilot/models/profile.py @@ -5,9 +5,10 @@ import os import uuid import subprocess -from copilot.models.config import get_config_dir, get_config_file, get_valid_targets, get_valid_actions +from copilot.models.config import get_config_dir, get_config_file, get_valid_targets, get_valid_actions, get_config_writer, ProfileConfig, ProfileWriter from copilot.models.trainer import get_trainer -from config import DNSConfig +from copilot.utils.file_sys import get_usb_dirs +from werkzeug import secure_filename #stat logging import logging @@ -19,9 +20,13 @@ def get_profile_status(): current_profile = False try: current_profile = trainer.current + log.debug("current profile found: {0}".format(current_profile)) except: - log.warn("FIX THIS SOON (function get_profile_status)") + log.debug("No current trainer found. Returning broken profile.") + log.warn("FIX THIS SOON: wildcard exception (function get_profile_status)") if current_profile: + if current_profile == "0": + current_profile = "No Active Profile" profile['status'] = "on" profile['value'] = current_profile else: @@ -29,11 +34,27 @@ def get_profile_status(): profile['value'] = "NONE" return profile -class Profile: - def __init__(self, name, description=None, rules={}): +def get_all_profiles(): + _profile_dirs = get_usb_dirs() + _profile_dirs.append(get_config_dir("profiles")) + profiles = [] + for _dir in _profile_dirs: + if os.path.isdir(_dir): + for _prof in os.listdir(_dir): + p_path = os.path.join(_dir, _prof) + if os.path.isfile(p_path): + _test = ProfileConfig(p_path) + if _test.valid(): + profiles.append(_prof) + return profiles + + +class Profile(object): + def __init__(self, name, description="A co-pilot profile", rules={}): self.rules = [] - self._profile_dir = get_config_dir("profiles") + self.profile_dir = "profiles" self.name = name + self.profile_file = os.path.join(self.profile_dir, secure_filename(self.name)) self.description = description if rules: try: @@ -41,129 +62,131 @@ def __init__(self, name, description=None, rules={}): self.add_rule(rule) except ValueError as _err: #TODO add real error correction here raise _err + @property + def profile_dir(self): + return self._profile_dir - def add_rule(self, rule): - log.debug("adding rule {0} {1} {2}".format(rule.action, rule.target, rule.sub_target)) + @profile_dir.setter + def profile_dir(self, plaintext): + try: + _dir = get_config_dir(plaintext) + self._profile_dir = _dir + try: + self.profile_file = os.path.join(self.profile_dir, secure_filename(self.name)) + except AttributeError as ee: + log.debug("cannot set profile_file as {0} is not initialized yet.".format(ee)) + except ValueError: + raise ValueError("\"{0}\" is not a valid co-pilot directory. It cannot be set.".format(plaintext)) + + def add_rule(self, ruleset): + # Rapid prototyping means that I just want the rule, I don't care how it is formatted. + if isinstance(ruleset, dict): + rule = ruleset + elif isinstance(ruleset ,list): + rule = {"action":ruleset[0], "target":ruleset[1], "sub_target":ruleset[2]} + log.debug("adding rule {0} {1} {2}".format(rule['action'], rule['target'], rule['sub_target'])) + config_obj = get_config_writer(rule['target']) try: - _rule = Rule(rule.target, rule.action, rule.sub_target) + config_obj.add_rule([rule['action'], rule['target'], rule['sub_target']]) except ValueError as _err: log.error("Error Encountered in add_rule()") - raise ValueError(_err) #TODO add real error correction here - if _rule.is_valid(): - log.info("Rule is valid") - self.rules.append(_rule) - else: log.info("Rule is NOT valid") + raise ValueError(_err) #TODO add real error correction here + self.rules.append([rule['action'], rule['target'], rule['sub_target']]) def save(self): - log.info("saving profile {0}".format(self.name)) - if not os.path.exists(self._profile_dir): - os.makedirs(self._profile_dir) - profile_file = (self._profile_dir + self.name) - #Empty the file - open(profile_file, 'w').close() - #Save rules to file - for rule in self.rules: - rule.save(profile_file) + log.info("Saving profile {0} to {1}".format(self.name, self.profile_file)) + if not os.path.exists(self.profile_dir): + os.makedirs(self.profile_dir) + with open(self.profile_file, "w+") as config_file: + _prof = ProfileWriter() + _prof.add_section("info") + _prof.set("info", "name", self.name) + _prof.set("info", "description", self.description) + for rule in self.rules: + _prof.set_rule(rule) + _prof.write(config_file) log.info("Profile {0} saved".format(self.name)) def exist(self): - profile_file = (self._profile_dir + self.name) - if os.path.isfile(profile_file): + if os.path.isfile(self.profile_file): log.info(" profile {0} exists".format(self.name)) return True else: log.info(" profile {0} does NOT exists".format(self.name)) + return False + + def refresh(self): + """ + Reload all profile values from its file. + + NOTE: Does not change the profile directory. + """ + log.info("Refreshing profile from file.") + config = ProfileConfig(self.profile_file) + if not config.valid(): + raise ValueError("Config file is not valid. Cannot reload config") + log.debug("Current name: {0}".format(self.name)) + self.name = config.data["info"]["name"][0] + log.debug("Set profile name to {0}".format(self.name)) + log.debug("Current profile file: {0}".format(self.profile_file)) + self.profile_file = os.path.join(self.profile_dir, secure_filename(self.name)) + log.debug("Set profile file to {0}".format(self.profile_file)) + log.debug("Current description: {0}".format(self.description)) + if "description" in config.data["info"]: + self.description = config.data["info"]["description"][0] + log.debug("Set description to {0}".format(self.description)) + log.debug("Current rules: {0}".format(self.rules)) + log.debug("clearing all existing rules") + self.rules = [] + _rules = config.get_rules() + for r in _rules: + self.add_rule(r) + log.debug("Set rules to {0}".format(self.rules)) def load(self): - profile_file = (self._profile_dir + self.name) - with open(profile_file, 'r') as csvfile: - csv_reader = csv.reader(csvfile, delimiter=' ', quotechar='|') - for row in csv_reader: - _rule=Rule(row[0], row[1], row[2]) - self.add_rule(_rule) + """ + Load a profile from a file. + + NOTE: Does not change the profile directory. + """ + log.info("Loading profile.") + log.debug("Using profile at {0}".format(self.profile_file)) + config = ProfileConfig(self.profile_file) + if not config.valid(): + raise ValueError("Config file is not valid. Cannot load config") + self.name = config.data["info"]["name"][0] + log.debug("Set profile name to {0}".format(self.name)) + self.profile_file = os.path.join(self.profile_dir, secure_filename(self.name)) + log.debug("Set profile file to {0}".format(self.profile_file)) + if "description" in config.data["info"]: + self.description = config.data["info"]["description"][0] + log.debug("Set description to {0}".format(self.description)) + _rules = config.get_rules() + for r in _rules: + self.add_rule(r) + log.debug("Set rules to {0}".format(self.rules)) def apply_config(self): - _configs = {} + log.info("Applying profile {0}".format(self.name)) + trainer = get_trainer() #Save as current profile for trainer. + trainer.current = self.name + db.session.commit() + + configs = {} log.info("looking for config files that need to be written.") - _targets = get_valid_targets() for r in self.rules: - if r.target not in _configs: - #TODO This needs to be replaced with some sort of config file that checks the proper config object to instantiate when a specific config type is passed. - if r.target == "dns": - log.debug("Creating a {0} config".format("dnschef")) - _configs["dns"] = DNSConfig() - log.debug("Adding a rule ({0} {1}) to dnschef config.".format(r.action, r.sub_target)) - _configs["dns"].add_rule(r.target, r.action, r.sub_target) + _action = r[0] + _tar = r[1] + _sub = r[2] + if _tar not in configs: + log.debug("Creating a {0} config".format(_tar)) + configs[_tar] = get_config_writer(_tar) + log.debug("Adding a rule ({0} {1}) to {2} config.".format(_tar, _action, _sub)) + configs[_tar].add_rule([_action, _tar, _sub]) else: - if r.target == "dns": - log.debug("Adding a rule ({0} {1}) to dnschef config.".format(r.action, r.sub_target)) - _configs["dns"].add_rule(r.target, r.action, r.sub_target) - for c in _configs: + log.debug("Adding a rule ({0} {1}) to {2} config.".format(_tar, _action, _sub)) + configs[_tar].add_rule([_action, _tar, _sub]) + for c in configs: log.debug("Writing {0} config.".format(c)) - _configs[c].write() - -class Rule: - - def __init__(self, target, action, sub_target=""): - self.valid_actions = get_valid_actions() - self.valid_targets = get_valid_targets() - self.errors = {} - self.target = target - self.action = action - self.sub_target = sub_target - self.uuid = uuid.uuid4() - - def init_validators(self): - """TODO Make these actually validate address'""" - self.valid_sub_targets = { - url : True, - dns : True} - - def save(self, save_file): - with open(save_file, 'a') as csvfile: - csv_writer = csv.writer(csvfile, delimiter=' ', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - csv_writer.writerow([self.target, self.action, self.sub_target]) - - def is_valid(self): - if self.errors: - return False - else: - return True - - @property - def target(self): - return self._target - - @target.setter - def target(self, plaintext): - try: - value_to_set = self.valid_targets.index(string.lower(plaintext)) - self._target = plaintext - except ValueError: - raise ValueError("The target \"{0}\" is invalid.".format(plaintext)) - - @property - def sub_target(self): - return self._sub_target - - @sub_target.setter - def sub_target(self, plaintext): - #If a target is not set then we cannot check the sub-target against it. - if not self._target: - raise ValueError("The sub-target \"{0}\" cannot be set without a valid target.".format(plaintext)) - print("TODO Validate sub-targets.") - self._sub_target = plaintext - - @property - def action(self): - return self._action - - @action.setter - def action(self, plaintext): - try: - value_to_set = self.valid_actions.index(string.lower(plaintext)) - self._action = plaintext - except ValueError: - raise ValueError("The action \"{0}\" is invalid.".format(plaintext)) + configs[c].write() diff --git a/copilot/models/trainer.py b/copilot/models/trainer.py index 27b672e..79776a6 100644 --- a/copilot/models/trainer.py +++ b/copilot/models/trainer.py @@ -3,7 +3,7 @@ from copilot import app from copilot import bcrypt, db from flask.ext.login import UserMixin -from copilot.models.config import get_config_file, APConfig +from copilot.models.config import get_config_file, get_config_writer #stat logging import logging @@ -61,20 +61,24 @@ class Trainer(Base, UserMixin): # Ensure Solo account _solo = db.Column(db.Boolean, default=True, nullable=False) # AP Name -# _ap_name = db.Column(db.String(128), nullable=False) + _ap_name = db.Column(db.String(128), nullable=False) # AP password -# _ap_password = db.Column(db.String(192), nullable=False) + _ap_password = db.Column(db.String(192), nullable=False) # Trainer password _password = db.Column(db.String(192), nullable=False) _current = db.Column(db.String(192), nullable=True) - def __init__(self, trainer_pass, ap_name="copilot", ap_password="copilot"): + def __init__(self, trainer_pass, ap_name="copilot", ap_password="copilot_pass"): log.debug("Creating new trainer object.") log.debug("Trainer AP: {0}".format(ap_name)) self.password = trainer_pass self.solo = True self.current = False - self.ap_config = APConfig(ap_name, ap_password) + self.ap_name = ap_name + self.ap_password = ap_password + self.ap_config = get_config_writer("create_ap") + log.debug(dir(self.ap_config)) + self.ap_config.add_rule(self.ap_name, self.ap_password) @property def solo(self): @@ -84,6 +88,14 @@ def solo(self): def solo(self, val=True): self._solo = True + @property + def ap_name(self): + return self._ap_name + + @ap_name.setter + def ap_name(self, plaintext): + self._ap_name = plaintext + @property def password(self): return self._password @@ -92,6 +104,16 @@ def password(self): def password(self, plaintext): self._password = bcrypt.generate_password_hash(plaintext) + @property + def ap_password(self): + return self._ap_password + + @ap_password.setter + def ap_password(self, plaintext): + # This get's written in plain-text in the create_ap anyway + self._ap_password = plaintext + + @property def current(self): return self._current diff --git a/copilot/plugins/__init__.py b/copilot/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/copilot/plugins/create_ap/__init__.py b/copilot/plugins/create_ap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/copilot/plugins/create_ap/config.py b/copilot/plugins/create_ap/config.py new file mode 100644 index 0000000..c2b99aa --- /dev/null +++ b/copilot/plugins/create_ap/config.py @@ -0,0 +1,49 @@ +from copilot.models.config import Config +from exceptions import ValueError + +#import logging +#log = logging.getLogger(__name__) + +import string + +class ConfigWriter(Config): + + def __init__(self): + super(ConfigWriter, self).__init__() + self.config_type = "create_ap" + self.header = "" + + @property + def ap_password(self): + return self._ap_password + + @ap_password.setter + def ap_password(self, plaintext): + if (8 < len(str(plaintext)) <= 63 and + all(char in string.printable for char in plaintext)): + self._ap_password = plaintext + else: + raise ValueError("Access Point passwords must be between 8 and 63 characters long and use only printable ASCII characters.") + + @property + def ap_name(self): + return self._ap_name + + @ap_name.setter + def ap_name(self, name): + if 0 < len(str(name)) <= 31: + self._ap_name = name + else: + raise ValueError("Access Point names must be between 1 and 31 characters long.") + + def add_rule(self, ap_name="copilot", ap_password="copilot_pass", iface_in="eth0", iface_out="wlan0"): + #log.debug("adding create ap rule {0} {1} {2} {3}".format(iface_out, iface_in, ap_name, ap_password)) + self._rules.append("{0} ".format(iface_out)) + self._rules.append("{0} ".format(iface_in)) + self._rules.append("{0} ".format(ap_name)) + self._rules.append("{0} ".format(ap_password)) + + +def setup(app): + new_writer = ConfigWriter() + app.get_config_writer(new_writer) diff --git a/copilot/plugins/create_ap/install b/copilot/plugins/create_ap/install new file mode 100644 index 0000000..e69de29 diff --git a/copilot/plugins/create_ap/plugin.conf b/copilot/plugins/create_ap/plugin.conf new file mode 100644 index 0000000..e8d566d --- /dev/null +++ b/copilot/plugins/create_ap/plugin.conf @@ -0,0 +1,4 @@ +[info] +name = create_ap +config_file = ap.conf +directory = /tmp/copilot/ \ No newline at end of file diff --git a/copilot/plugins/create_ap/start b/copilot/plugins/create_ap/start new file mode 100644 index 0000000..e69de29 diff --git a/copilot/plugins/dns/__init__.py b/copilot/plugins/dns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/copilot/plugins/dns/config.py b/copilot/plugins/dns/config.py new file mode 100644 index 0000000..4395d39 --- /dev/null +++ b/copilot/plugins/dns/config.py @@ -0,0 +1,69 @@ +from copilot.models.config import Config +import string +from urlparse import urlparse + +#stat logging +import logging +log = logging.getLogger(__name__) + + +class ConfigWriter(Config): + + def __init__(self): + super(ConfigWriter, self).__init__() + log.debug("Initializing dns config writer.") + self.config_type = "dns" + self.header = "[A]\n" + + def add_rule(self, rule): + action, target, sub = rule[0], rule[1], rule[2] + log.debug("Adding rule {0} {1} {2}".format(action, target, sub)) + _rule = "" + _domain = "" + _address = "" + try: + _domain = self.get_dns(sub) + except ValueError as err: + log.warning("Could not add rule. Invalid Target.") + raise ValueError(err) + if action == "block": + log.debug("Found a blocking role. Setting address to localhost (127.0.0.1).") + _address = "127.0.0.1" + elif action == "redirect": + log.debug("Found a redirection role. Setting address to default AP address (192.168.12.1).") + _address = "192.168.12.1" + _rule = "{0} = {1}\n".format(_domain, _address) + self._rules.append(_rule) + + def get_dns(self, sub): + tld = ["ac", "ad", "ae", "aero", "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "arpa", "as", "asia", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "biz", "bj", "bl", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cat", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "com", "coop", "cr", "cs", "cu", "cv", "cw", "cx", "cy", "cz", "dd", "de", "dj", "dk", "dm", "do", "dz", "ec", "edu", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gov", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "info", "int", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jobs", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "local", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mf", "mg", "mh", "mil", "mk", "ml", "mm", "mn", "mo", "mobi", "mp", "mq", "mr", "ms", "mt", "mu", "museum", "mv", "mw", "mx", "my", "mz", "na", "name", "nato", "nc", "ne", "net", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "onion", "org", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "pro", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "su", "sv", "sx", "sy", "sz", "tc", "td", "tel", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tp", "tr", "travel", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "xxx", "ye", "yt", "yu", "za", "zm", "zr", "zw"] + _sub = sub + log.debug("sub target is {0}".format(_sub)) + if _sub == "": + return None + parsed = urlparse(_sub).path + log.debug("parsed url is {0}".format(parsed)) + split_sub = string.split(parsed, ".") + log.debug("split up sub target is {0}".format(split_sub)) + #TODO the below is a monstrosity it needs to be made to actually work + if len(split_sub) > 3: + log.error("The domain {0} has too many parts and cannot be processed. Use a simpler domain with MAX 3 parts.".format(_sub)) + raise ValueError("invalid url") + elif len(split_sub) == 3: + log.debug("The domain {0} has exactly three parts.".format(_sub)) + return parsed + elif len(split_sub) == 1: + log.debug("The domain {0} has exactly one part. Interpreting as a domain name only.".format(_sub)) + return "*.{0}.*".format(split_sub[0]) + elif (len(split_sub) == 2 and split_sub[1] not in tld): + log.debug("The domain {0} has exactly two parts, and the second part is NOT a top level domain I recognize. Interpreting as a host + a domain name.".format(_sub)) + return "{0}.{1}.*".format(split_sub[0], split_sub[1]) + elif split_sub[1] in tld: + log.debug("The domain {0} has exactly two parts, and the second part IS a top level domain I recognize. Interpreting as a domain name with a top level domain.".format(_sub)) + return "*.{0}.{1}".format(split_sub[0], split_sub[1]) + #todo add just TLD. + + +def setup(app): + dns = ConfigWriter() + app.get_config_writer(dns) diff --git a/copilot/plugins/dns/install b/copilot/plugins/dns/install new file mode 100644 index 0000000..e69de29 diff --git a/copilot/plugins/dns/plugin.conf b/copilot/plugins/dns/plugin.conf new file mode 100644 index 0000000..bfc3037 --- /dev/null +++ b/copilot/plugins/dns/plugin.conf @@ -0,0 +1,7 @@ +[info] +name = dnschef +config_file = dnschef.conf +target = dns +actions = block + redirect +directory = /tmp/copilot/ \ No newline at end of file diff --git a/copilot/plugins/dns/start b/copilot/plugins/dns/start new file mode 100644 index 0000000..e69de29 diff --git a/copilot/static/css/core.css b/copilot/static/css/core.css index 49ea35b..c91fd11 100644 --- a/copilot/static/css/core.css +++ b/copilot/static/css/core.css @@ -1,20 +1,161 @@ /* Color Palette -Dark Blue : 002e62 -Light Blue : 005baa -Grey Blue : 96a3c3 -Light Grey : 939192 -Dark Grey : 626264 -Green : 6db33 -Light Green : b3d78d -Orange : fbb033 -Yellow : fbb033 +Primary: +Dark Blue : #002e62 +Mid Blue: #244c78 +Light Blue: #72afb7 + +Secondary: +Dark Orange : #f25d23 +Dark Yellow : #fbb033 + +Base: +Base 01: #1a1a1a +Base 02: #757575 +Base 03: #e0e0e0 +Base 04: #eeeeee +Base 05: #fafafa + */ -body { + +/* Navagation Bar */ + +.navigation { + list-style: none; + background: #002e62; + width: 100%; + height: 100%; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 0; +} + +/* Navigation Menu - List items */ +.nav-item { + /* non-critical appearance styles */ + width: 200px; + border-top: 1px solid #e0e0e0; + border-bottom: 1px solid #002e62; +} + +.nav-item a { + /* non-critical appearance styles */ + display: block; + padding: 1em; + background: linear-gradient(135deg, #002e62 0%,#002e62 100%); + color: #fafafa; + font-size: 1.2em; + text-decoration: none; + transition: color 0.2s, background 0.5s; +} + +.nav-item a:hover { + color: #72afb7; + background: linear-gradient(135deg,#002e62 0%,#244c78 100%); +} + +.site-wrap { + min-width: 100%; + height: 100vh; + padding-top: 60px; + overflow-x: hidden; + background-color: #eeeeee; font-family: "Helvetica"; + position: relative; + top: 0; + bottom: 100%; + left: 0; + z-index: 1; + /*background-image: linear-gradient(135deg, + rgb(254,255,255) 0%, + rgb(221,241,249) 35%, + rgb(160,216,239) 100%); + */ + background-size: 200%; +} +html, body {height:100%;} + +.content-frame { + border-radius: 1em; + padding: 2em 2em 2em 2em; background: #ffffff; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + margin: 5%; +} + + +.nav_bar { + width: 100%; + background: #002e62; + height: 60px; + z-index: 1; +} + +.nav-trigger { + position: absolute; + clip: rect(0, 0, 0, 0); +} + +label[for="nav-trigger"] { + position: fixed; + top: 15px; + left: 15px; + z-index: 2; + width: 30px; + height: 30px; + fill: #ffffff; + cursor: pointer; + background-image: url("data:image/svg+xml;utf8,"); + background-size: contain; +} + +.nav-trigger:checked + label { + left: 215px; +} + +.nav-trigger:checked ~ .site-wrap { + left: 200px; + box-shadow: 0 0 5px 5px rgba(0,0,0,0.5); +} + +.nav-trigger + label, +.site-wrap, +.menu_bar { + transition: left 0.2s; +} + +.site-wrap[left=200px] > .menu_bar { + left: 200px; +} + +.page_title { + line-height: 60px; + color: #ffffff; + font-size: 3em; + margin-right: 1em; +} + +.menu_bar { + position: fixed; + left: inherit; + top: 0; + right: 0; + text-align: right; + text-color: #FFFFFF; + background: #002e62; + height: 60px; + z-index: 1; +} + +body { + overflow-x: hidden; + margin:0; + padding:0; } /* === Text Formatting === */ @@ -32,64 +173,88 @@ h1 { /* Alert Message Lists */ +.alerts { + padding: 1em; + border: 1px solid #e0e0e0; + border-radius: 1em; +} + +.notice { + font-weight:bold; + text-align:center; +} + ul.flashes { list-style: none; padding:0; margin:0; } -li.flashes { +.flashes li { padding-left: 1em; text-indent: -.7em; } -li.flashes.error { +.flashes li.error { border-bottom: 1px solid #f25d23; } -li.flashes.success { - border-bottom: 1px solid #6db33; +.flashes li.success { + border-bottom: 1px solid #72afb7; } -li.flashes:before { +.flashes li:before { content: "• "; } -li.flashes.error:before { +.flashes li.error:before { color: #f25d23; } -li.flashes.success:before { - color: #6db33; +.flashes li.success:before { + color: #72afb7; } /* Form Submission Buttons */ -.form_button { - position: relative; - vertical-align: top; - width: 100%; - height: 80%; - padding: 0; - font-size: 2rem; - color: white; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); - background: #005baa; - border: 0; - border-bottom: 2px solid #002e62; - cursor: pointer; - -webkit-box-shadow: inset 0 -2px #002e62; - box-shadow: inset 0 -2px #002e62; -} - -.form_button:active { - top: 1px; - outline: none; - -webkit-box-shadow: none; - box-shadow: none; +.blues { + background-color:#72afb7 !important; + border-color:#72afb7 !important; +} + +.blues:hover { + background-color:#244c78 !important; + border-color:#244c78 !important; +} + +.submit { + margin-top: 1em; +} + +.input_field { + margin-bottom: .5em; } + +#overlay { + visibility: hidden; + position: absolute; + left: 0px; + top: 0px; + width:100%; + height:100%; + text-align:center; + z-index: 1000; +} + +.paper { + border-radius: 1em; + background: #ffffff; + box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); + padding: 1em; +} + + /* Rules and Profiles */ .rule.error { @@ -99,7 +264,7 @@ li.flashes.success:before { .rule.error:before { content: "• "; - color: #f25d23; + color: #f25d23; } .help { @@ -107,25 +272,159 @@ li.flashes.success:before { } .rule.input:focus + div.help { - display: block; + display: block; } -.status { - text-align: center; - border-bottom:1px solid #939192; - border-top:1px solid #939192; - display: block; - z-index: 99; +.rule { + border-bottom: 1px solid #eeeeee; + padding: 3rem 0; +} + +.mock_rule { + border-bottom: 1px solid #eeeeee; + padding: 3rem 0; +} + +/* Errors */ +.error_bar { + background: #fbb033; + height: 300px; + align-items:center; + display:flex; +} + +.error_container { + margin-right:auto; + margin-left:auto; +} + +.face { + margin: 15px; + height: 200px; + width: 167.046px; + vertical-align: middle; +} + +.big_errors .alerts { + padding-top: 1em; + font-size: 2em; + text-align: center; +} + +.error_msg { + color: #ffffff; + font-size: 5em; +} + +/*Start with emotes invisible*/ +.smile {opacity: 0.0;} +.frown {opacity: 0.0;} +.flame {opacity: 0.0;} +.oface {opacity: 0.0;} + +/* But not everything */ +.glasses_wifi {opacity: 1.0;} +.glasses {opacity: 1.0;} +.face_head {opacity: 1.0;} + +/* Make the emotions become opaque when they are in the right emote class parent */ +.emote_smile /deep/ .smile {opacity: 1.0;} +.emote_frown /deep/ .frown {opacity: 1.0;} +.emote_flame /deep/ .flame {opacity: 1.0;} +.emote_oface /deep/ .oface {opacity: 1.0;} + + +/* Login */ +.welcome_bar { + height: 300px; + position:relative; } -svg.icon.on { - fill: #b3d78d; +.welcome_container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } -svg.icon.off { - fill: #939192; + +/* Load Profiles page */ + +.load_profiles li { + background: url('/static/images/globe_16.png') no-repeat left top; + padding: 0px 0px 30px 63px; + /* reset styles (optional): */ + list-style: none; + margin: 0; + margin-top: 15px; + border-bottom: 1px solid #eeeeee; +} +.load_profiles a { + font-size: 2em; +} + +.load_profiles li.upload_prof { + background: url('/static/images/upload_16.png') no-repeat left top; + padding: 0px 0px 30px 63px; + /* reset styles (optional): */ + list-style: none; + margin: 0; + margin-top: 15px; + border-bottom: 1px solid #eeeeee; + font-size: 2em; +} + +.services li.working { + background: url('/static/images/reload_lblue_16.png') no-repeat left top; + padding: 0px 0px 30px 63px; + /* reset styles (optional): */ + list-style: none; + margin-top: 15px; + margin: 0; + border-bottom: 1px solid #eeeeee; +} + +.services li.broken { + background: url('/static/images/reload_orange_16.png') no-repeat left top; + padding: 0px 0px 30px 63px; + /* reset styles (optional): */ + list-style: none; + margin-top: 15px; + margin: 0; + border-bottom: 1px solid #eeeeee; +} + + +.services a { + font-size: 2em; } -svg.icon.error { - fill: #f25d23; + +.status li { + padding: 0px 0px 30px 63px; + /* reset styles (optional): */ + list-style: none; + margin-top: 15px; + margin: 0; + border-bottom: 1px solid #eeeeee; +} + +.status li.on.wifi { + background: url('/static/images/wifi_on_16.png') no-repeat left top; +} + +.status li.off.wifi { + background: url('/static/images/wifi_off_16.png') no-repeat left top; +} + +.status li.on.profile { + background: url('/static/images/globe_16.png') no-repeat left top; +} + +.status li.off.profile { + background: url('/static/images/globe_off_16.png') no-repeat left top; +} + +.status { + font-size: 2em; } diff --git a/copilot/static/css/large.css b/copilot/static/css/large.css index 356c8a5..2c76aa8 100644 --- a/copilot/static/css/large.css +++ b/copilot/static/css/large.css @@ -1,30 +1,27 @@ -.body { - padding: 3rem 0 2rem; -} -.submit { +/*.submit { padding: 3rem 0 2rem; -} +}*/ .rule { - padding: 1rem 0 1rem; + padding: 1rem 0 1rem; } .rule.error { - padding-left: 1rem; + padding-left: 1rem; } .status .items { - padding-top: 2.5rem; + padding-top: 2.5rem; } svg.icon { - height: 5rem; - width: 5rem; + height: 5rem; + width: 5rem; } .logo { - padding: 3rem 0 3rem; + padding: 3rem 0 3rem; } \ No newline at end of file diff --git a/copilot/static/css/medium.css b/copilot/static/css/medium.css index e69de29..0468075 100644 --- a/copilot/static/css/medium.css +++ b/copilot/static/css/medium.css @@ -0,0 +1,14 @@ +.face { + height: 100px; + width: 83.523px; +} + +.rule { + border-bottom: 1px solid #eeeeee; + padding: 3rem 0; +} + +.mock_rule { + border-bottom: 1px solid #eeeeee; + padding: 3rem 0; +} diff --git a/copilot/static/css/small.css b/copilot/static/css/small.css index e69de29..5cb0bee 100644 --- a/copilot/static/css/small.css +++ b/copilot/static/css/small.css @@ -0,0 +1,20 @@ +.content-frame { + padding: 1em 1em 1em 1em; + margin-top: 5%; +} + +.face { + height: 150px; + width: 125.285px; + font-size: 2em; +} + +.rule { + border-bottom: 1px solid #eeeeee; + padding: 3rem 0; +} + +.mock_rule { + border-bottom: 1px solid #eeeeee; + padding: 3rem 0; +} diff --git a/copilot/static/images/globe.svg b/copilot/static/images/globe.svg new file mode 100644 index 0000000..2a4a8fc --- /dev/null +++ b/copilot/static/images/globe.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/copilot/static/images/globe_16.png b/copilot/static/images/globe_16.png new file mode 100644 index 0000000..ced2e85 Binary files /dev/null and b/copilot/static/images/globe_16.png differ diff --git a/copilot/static/images/globe_24.png b/copilot/static/images/globe_24.png new file mode 100644 index 0000000..fe82f7c Binary files /dev/null and b/copilot/static/images/globe_24.png differ diff --git a/copilot/static/images/globe_48.png b/copilot/static/images/globe_48.png new file mode 100644 index 0000000..4317c7b Binary files /dev/null and b/copilot/static/images/globe_48.png differ diff --git a/copilot/static/images/globe_off_16.png b/copilot/static/images/globe_off_16.png new file mode 100644 index 0000000..268be2e Binary files /dev/null and b/copilot/static/images/globe_off_16.png differ diff --git a/copilot/static/images/reload.svg b/copilot/static/images/reload.svg new file mode 100644 index 0000000..90da7f5 --- /dev/null +++ b/copilot/static/images/reload.svg @@ -0,0 +1,68 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/copilot/static/images/reload_lblue_16.png b/copilot/static/images/reload_lblue_16.png new file mode 100644 index 0000000..8755294 Binary files /dev/null and b/copilot/static/images/reload_lblue_16.png differ diff --git a/copilot/static/images/reload_orange_16.png b/copilot/static/images/reload_orange_16.png new file mode 100644 index 0000000..350df1a Binary files /dev/null and b/copilot/static/images/reload_orange_16.png differ diff --git a/copilot/static/images/upload.svg b/copilot/static/images/upload.svg new file mode 100644 index 0000000..2623f58 --- /dev/null +++ b/copilot/static/images/upload.svg @@ -0,0 +1,54 @@ + +image/svg+xml \ No newline at end of file diff --git a/copilot/static/images/upload_16.png b/copilot/static/images/upload_16.png new file mode 100644 index 0000000..d419267 Binary files /dev/null and b/copilot/static/images/upload_16.png differ diff --git a/copilot/static/images/wifi.svg b/copilot/static/images/wifi.svg new file mode 100644 index 0000000..23ef708 --- /dev/null +++ b/copilot/static/images/wifi.svg @@ -0,0 +1,54 @@ + +image/svg+xml \ No newline at end of file diff --git a/copilot/static/images/wifi_off_16.png b/copilot/static/images/wifi_off_16.png new file mode 100644 index 0000000..2627e43 Binary files /dev/null and b/copilot/static/images/wifi_off_16.png differ diff --git a/copilot/static/images/wifi_on_16.png b/copilot/static/images/wifi_on_16.png new file mode 100644 index 0000000..589e539 Binary files /dev/null and b/copilot/static/images/wifi_on_16.png differ diff --git a/copilot/static/js/profile.js b/copilot/static/js/profile.js index dfe58dd..bc68fb0 100644 --- a/copilot/static/js/profile.js +++ b/copilot/static/js/profile.js @@ -1,7 +1,8 @@ //Rule Defaults -var actionOptions = ["block"]; -var targetOptions = ["dns"]; +// Ignore the ugly one liners. +var targetOptions = document.getElementById("all_targets").content.split(" ").filter(function(el) {return el.length != 0}); +var actionOptions = document.getElementById("all_actions").content.split(" ").filter(function(el) {return el.length != 0}); var subTargetDefault = "internews.org"; var helpText = {} helpText.action = "" @@ -116,6 +117,7 @@ function createRuleData(type, ruleID, options) { } //Set Generic Properties data.id = ruleID + data.onclick = "update_from_".concat(type, "(this)") data.className = "u-full-width {{ type }}" data.name = ruleID return data @@ -140,3 +142,24 @@ function getIdNum() { // return that number return curNum } + + +//update the target based upon an action +function update_from_action(selector) { + var idNum = selector.id.split("-")[1] + // get metadata object data of action target pairs + var raw_targets = document.getElementById('pairs-'.concat(selector.value)).content; + var targets = raw_targets.split(" ").filter(function(el) {return el.length != 0}) + + // get the target selector we will be modifying + var targetObj = document.getElementById('rules-'.concat(idNum, "-", "target")); + // clear all options from it + targetObj.options.length=0 + // repopulate the options + for (i=0; i < targets.length; i++){ + targetObj.options[targetObj.options.length]=new Option(targets[i], targets[i]) + } +} + +function update_from_target(selector) {} +function update_from_sub_target(selector) {} diff --git a/copilot/static/js/visual.js b/copilot/static/js/visual.js new file mode 100644 index 0000000..614c3e1 --- /dev/null +++ b/copilot/static/js/visual.js @@ -0,0 +1,5 @@ +//Cause the overlay to appear +function overlay() { + el = document.getElementById("overlay"); + el.style.visibility = (el.style.visibility == "visible") ? "hidden" : "visible"; +} diff --git a/copilot/templates/alert.html b/copilot/templates/alert.html index 306e381..82c35b5 100644 --- a/copilot/templates/alert.html +++ b/copilot/templates/alert.html @@ -1,6 +1,7 @@ {% macro render_alerts(messages) %} {% if messages %}
+
Co-Pilot reporting back...