use of org.openhab.binding.shelly.internal.api.ShellyDeviceProfile in project openhab-addons by openhab.
the class ShellyManagerActionPage method generateContent.
@Override
public ShellyMgrResponse generateContent(String path, Map<String, String[]> parameters) throws ShellyApiException {
String action = getUrlParm(parameters, URLPARM_ACTION);
String uid = getUrlParm(parameters, URLPARM_UID);
String update = getUrlParm(parameters, URLPARM_UPDATE);
if (uid.isEmpty() || action.isEmpty()) {
return new ShellyMgrResponse("Invalid URL parameters: " + parameters.toString(), HttpStatus.BAD_REQUEST_400);
}
Map<String, String> properties = new HashMap<>();
properties.put(ATTRIBUTE_METATAG, "");
properties.put(ATTRIBUTE_CSS_HEADER, "");
properties.put(ATTRIBUTE_CSS_FOOTER, "");
String html = loadHTML(HEADER_HTML, properties);
ShellyManagerInterface th = getThingHandler(uid);
if (th != null) {
fillProperties(properties, uid, th);
Map<String, String> actions = getActions(th.getProfile());
String actionUrl = SHELLY_MGR_OVERVIEW_URI;
// Default
String actionButtonLabel = "OK";
String serviceName = getValue(properties, PROPERTY_SERVICE_NAME);
String message = "";
ShellyThingConfiguration config = getThingConfig(th, properties);
ShellyDeviceProfile profile = th.getProfile();
ShellyHttpApi api = th.getApi();
new ShellyHttpApi(uid, config, httpClient);
int refreshTimer = 0;
switch(action) {
case ACTION_RES_STATS:
th.resetStats();
message = getMessageP("action.resstats.confirm", MCINFO);
refreshTimer = 3;
break;
case ACTION_RESTART:
if ("yes".equalsIgnoreCase(update)) {
message = getMessageP("action.restart.info", MCINFO);
actionButtonLabel = "Ok";
new Thread(() -> {
// schedule asynchronous reboot
try {
api.deviceReboot();
} catch (ShellyApiException e) {
// maybe the device restarts before returning the http response
}
// refresh after reboot
setRestarted(th, uid);
}).start();
refreshTimer = profile.isMotion ? 60 : 30;
} else {
message = getMessageS("action.restart.confirm", MCINFO);
actionUrl = buildActionUrl(uid, action);
}
break;
case ACTION_PROTECT:
// Get device settings
if (config.userId.isEmpty() || config.password.isEmpty()) {
message = getMessageP("action.protect.id-missing", MCWARNING);
break;
}
if (!"yes".equalsIgnoreCase(update)) {
ShellySettingsLogin status = api.getLoginSettings();
message = getMessage("action.protect.status", getBool(status.enabled) ? "enabled" : "disabled", status.username) + getMessageP("action.protect.new", MCINFO, config.userId, config.password);
actionUrl = buildActionUrl(uid, action);
} else {
api.setLoginCredentials(config.userId, config.password);
message = getMessageP("action.protect.confirm", MCINFO, config.userId, config.password);
refreshTimer = 3;
}
break;
case ACTION_SETCOIOT_MCAST:
case ACTION_SETCOIOT_PEER:
if ((profile.settings.coiot == null) || (profile.settings.coiot.peer == null)) {
// feature not available
message = getMessage("coiot.mode-not-suppored", MCWARNING, action);
break;
}
String peer = getString(profile.settings.coiot.peer);
boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer);
String newPeer = mcast ? localIp + ":" + ShellyCoapJSonDTO.COIOT_PORT : SHELLY_COIOT_MCAST;
String displayPeer = mcast ? newPeer : "Multicast";
if (profile.isMotion && action.equalsIgnoreCase(ACTION_SETCOIOT_MCAST)) {
// feature not available
message = getMessageP("coiot.multicast-not-supported", "warning", displayPeer);
break;
}
if (!"yes".equalsIgnoreCase(update)) {
message = getMessageP("coiot.current-peer", MCMESSAGE, mcast ? "Multicast" : peer) + getMessageP("coiot.new-peer", MCINFO, displayPeer) + getMessageP(mcast ? "coiot.mode-peer" : "coiot.mode-mcast", MCMESSAGE);
actionUrl = buildActionUrl(uid, action);
} else {
new Thread(() -> {
// schedule asynchronous reboot
try {
api.setCoIoTPeer(newPeer);
api.deviceReboot();
} catch (ShellyApiException e) {
// maybe the device restarts before returning the http response
}
// refresh after reboot
setRestarted(th, uid);
}).start();
// The device needs a restart after changing the peer mode
message = getMessageP("action.restart.info", MCINFO);
refreshTimer = 30;
}
break;
case ACTION_ENCLOUD:
case ACTION_DISCLOUD:
boolean enabled = action.equals(ACTION_ENCLOUD);
api.setCloud(enabled);
message = getMessageP("action.setcloud.config", MCINFO, enabled ? "enabled" : "disabled");
refreshTimer = 20;
break;
case ACTION_RESET:
if (!"yes".equalsIgnoreCase(update)) {
message = getMessageP("action.reset.warning", MCWARNING, serviceName);
actionUrl = buildActionUrl(uid, action);
} else {
new Thread(() -> {
// schedule asynchronous reboot
try {
api.factoryReset();
setRestarted(th, uid);
} catch (ShellyApiException e) {
// maybe the device restarts before returning the http response
}
}).start();
message = getMessageP("action.reset.confirm", MCINFO, serviceName);
refreshTimer = 5;
}
break;
case ACTION_OTACHECK:
try {
ShellyOtaCheckResult result = api.checkForUpdate();
message = getMessage("action.checkupd." + result.status);
} catch (ShellyApiException e) {
// maybe the device restarts before returning the http response
message = getMessageP("action.checkupd.failed", e.toString());
}
refreshTimer = 3;
break;
case ACTION_ENDEBUG:
case ACTION_DISDEBUG:
boolean enable = ACTION_ENDEBUG.equalsIgnoreCase(action);
if (!"yes".equalsIgnoreCase(update)) {
message = getMessage(enable ? "action.debug-enable" : "action.debug-disable");
actionUrl = buildActionUrl(uid, action);
} else {
new Thread(() -> {
// schedule asynchronous reboot
try {
api.setDebug(enable);
} catch (ShellyApiException e) {
// maybe the device restarts before returning the http response
}
}).start();
message = getMessage("action.debug-confirm", enable ? "enabled" : "disabled");
refreshTimer = 3;
}
break;
case ACTION_RESSTA:
if (!"yes".equalsIgnoreCase(update)) {
message = getMessage("action.resetsta-info");
actionUrl = buildActionUrl(uid, action);
} else {
try {
api.resetStaCache();
message = getMessage("action.resetsta-confirm");
} catch (ShellyApiException e) {
message = getMessageP("action.resetsta-failed", e.toString());
}
refreshTimer = 10;
}
break;
case ACTION_ENWIFIREC:
case ACTION_DISWIFIREC:
enable = ACTION_ENWIFIREC.equalsIgnoreCase(action);
if (!"yes".equalsIgnoreCase(update)) {
message = getMessage(enable ? "action.setwifirec-enable" : "action.setwifirec-disable");
actionUrl = buildActionUrl(uid, action);
} else {
try {
api.setWiFiRecovery(enable);
message = getMessage("action.setwifirec-confirm", enable ? "enabled" : "disabled");
} catch (ShellyApiException e) {
message = getMessage("action.setwifirec-failed", e.toString());
}
refreshTimer = 3;
}
break;
case ACTION_ENAPROAMING:
case ACTION_DISAPROAMING:
enable = ACTION_ENAPROAMING.equalsIgnoreCase(action);
if (!"yes".equalsIgnoreCase(update)) {
message = getMessage(enable ? "action.aproaming-enable" : "action.aproaming-disable");
actionUrl = buildActionUrl(uid, action);
} else {
try {
api.setApRoaming(enable);
message = getMessage("action.aproaming-confirm", enable ? "enabled" : "disabled");
} catch (ShellyApiException e) {
message = getMessage("action.aproaming-failed", e.toString());
}
refreshTimer = 3;
}
break;
case ACTION_GETDEB:
case ACTION_GETDEB1:
try {
message = api.getDebugLog(ACTION_GETDEB.equalsIgnoreCase(action) ? "log" : "log1");
message = message.replaceAll("[\r]", "").replaceAll("[\r\n]", "<br>");
} catch (ShellyApiException e) {
message = getMessage("action.getdebug-failed", e.toString());
}
break;
case ACTION_NONE:
break;
default:
logger.warn("{}: {}", LOG_PREFIX, getMessage("action.unknown", action));
}
// get description for command
properties.put(ATTRIBUTE_ACTION, getString(actions.get(action)));
properties.put(ATTRIBUTE_ACTION_BUTTON, actionButtonLabel);
properties.put(ATTRIBUTE_ACTION_URL, actionUrl);
message = fillAttributes(message, properties);
properties.put(ATTRIBUTE_MESSAGE, message);
properties.put(ATTRIBUTE_REFRESH, String.valueOf(refreshTimer));
html += loadHTML(ACTION_HTML, properties);
// trigger background update
th.requestUpdates(1, refreshTimer > 0);
}
properties.clear();
html += loadHTML(FOOTER_HTML, properties);
return new ShellyMgrResponse(html, HttpStatus.OK_200);
}
use of org.openhab.binding.shelly.internal.api.ShellyDeviceProfile in project openhab-addons by openhab.
the class ShellyManagerOverviewPage method getStatusWarnings.
private Map<String, String> getStatusWarnings(ShellyManagerInterface handler) {
Thing thing = handler.getThing();
ThingStatus status = handler.getThing().getStatus();
ShellyDeviceStats stats = handler.getStats();
ShellyDeviceProfile profile = handler.getProfile();
ShellyThingConfiguration config = thing.getConfiguration().as(ShellyThingConfiguration.class);
TreeMap<String, String> result = new TreeMap<>();
if ((status != ThingStatus.ONLINE) && (status != ThingStatus.UNKNOWN)) {
result.put("Thing Status", status.toString());
}
State wifiSignal = handler.getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI);
if ((profile.alwaysOn || (profile.hasBattery && (status == ThingStatus.ONLINE))) && ((wifiSignal != UnDefType.NULL) && (((DecimalType) wifiSignal).intValue() < 2))) {
result.put("Weak WiFi Signal", wifiSignal.toString());
}
if (profile.hasBattery) {
State lowBattery = handler.getChannelValue(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
if ((lowBattery == OnOffType.ON)) {
lowBattery = handler.getChannelValue(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
result.put("Battery Low", lowBattery.toString());
}
}
if (stats.lastAlarm.equalsIgnoreCase(ALARM_TYPE_RESTARTED)) {
result.put("Device Alarm", ALARM_TYPE_RESTARTED + " (" + convertTimestamp(stats.lastAlarmTs) + ")");
}
if (getBool(profile.status.overtemperature)) {
result.put("Device Alarm", ALARM_TYPE_OVERTEMP);
}
if (getBool(profile.status.overload)) {
result.put("Device Alarm", ALARM_TYPE_OVERLOAD);
}
if (getBool(profile.status.loaderror)) {
result.put("Device Alarm", ALARM_TYPE_LOADERR);
}
if (profile.isSensor) {
State sensorError = handler.getChannelValue(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
if (sensorError != UnDefType.NULL) {
if (!sensorError.toString().isEmpty()) {
result.put("Device Alarm", ALARM_TYPE_SENSOR_ERROR);
}
}
}
if (profile.alwaysOn && (status == ThingStatus.ONLINE)) {
if ((config.eventsCoIoT) && (profile.settings.coiot != null)) {
if ((profile.settings.coiot.enabled != null) && !profile.settings.coiot.enabled) {
result.put("CoIoT Status", "COIOT_DISABLED");
} else if (stats.coiotMessages == 0) {
result.put("CoIoT Discovery", "NO_COIOT_DISCOVERY");
} else if (stats.coiotMessages < 2) {
result.put("CoIoT Multicast", "NO_COIOT_MULTICAST");
}
}
}
return result;
}
use of org.openhab.binding.shelly.internal.api.ShellyDeviceProfile in project openhab-addons by openhab.
the class ShellyManagerPage method fillProperties.
protected Map<String, String> fillProperties(Map<String, String> properties, String uid, ShellyManagerInterface th) {
try {
Configuration serviceConfig = configurationAdmin.getConfiguration("binding." + BINDING_ID);
bindingConfig.updateFromProperties(serviceConfig.getProperties());
} catch (IOException e) {
logger.debug("ShellyManager: Unable to get bindingConfig");
}
properties.putAll(th.getThing().getProperties());
Thing thing = th.getThing();
ThingStatus status = thing.getStatus();
properties.put("thingName", getString(thing.getLabel()));
properties.put("thingStatus", status.toString());
ThingStatusDetail detail = thing.getStatusInfo().getStatusDetail();
properties.put("thingStatusDetail", detail.equals(ThingStatusDetail.NONE) ? "" : getString(detail.toString()));
properties.put("thingStatusDescr", getString(thing.getStatusInfo().getDescription()));
properties.put(ATTRIBUTE_UID, uid);
ShellyDeviceProfile profile = th.getProfile();
ShellyThingConfiguration config = thing.getConfiguration().as(ShellyThingConfiguration.class);
ShellyDeviceStats stats = th.getStats();
properties.putAll(stats.asProperties());
for (Map.Entry<String, Object> p : thing.getConfiguration().getProperties().entrySet()) {
String key = p.getKey();
if (p.getValue() != null) {
String value = p.getValue().toString();
properties.put(key, value);
}
}
State state = th.getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_NAME);
if (state != UnDefType.NULL) {
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_NAME);
} else {
// If the Shelly doesn't provide a device name (not configured) we use the service name
String deviceName = getDeviceName(properties);
properties.put(PROPERTY_DEV_NAME, !deviceName.isEmpty() ? deviceName : getString(properties.get(PROPERTY_SERVICE_NAME)));
}
if (config.userId.isEmpty()) {
// Get defauls from Binding Config
properties.put("userId", bindingConfig.defaultUserId);
properties.put("password", bindingConfig.defaultPassword);
}
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_HEARTBEAT);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_WAKEUP);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ALARM);
addAttribute(properties, th, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER);
properties.put(ATTRIBUTE_DEBUG_MODE, getOption(profile.settings.debugEnable));
properties.put(ATTRIBUTE_DISCOVERABLE, String.valueOf(getBool(profile.settings.discoverable)));
properties.put(ATTRIBUTE_WIFI_RECOVERY, String.valueOf(getBool(profile.settings.wifiRecoveryReboot)));
properties.put(ATTRIBUTE_APR_MODE, profile.settings.apRoaming != null ? getOption(profile.settings.apRoaming.enabled) : "n/a");
properties.put(ATTRIBUTE_APR_TRESHOLD, profile.settings.apRoaming != null ? getOption(profile.settings.apRoaming.threshold) : "n/a");
properties.put(ATTRIBUTE_PWD_PROTECT, profile.auth ? "enabled, user=" + getString(profile.settings.login.username) : "disabled");
String tz = getString(profile.settings.timezone);
properties.put(ATTRIBUTE_TIMEZONE, (tz.isEmpty() ? "n/a" : tz) + ", auto-detect: " + getBool(profile.settings.tzautodetect));
properties.put(ATTRIBUTE_ACTIONS_SKIPPED, profile.status.astats != null ? String.valueOf(profile.status.astats.skipped) : "n/a");
properties.put(ATTRIBUTE_MAX_ITEMP, stats.maxInternalTemp > 0 ? stats.maxInternalTemp + " °C" : "n/a");
// Shelly H&T: When external power is connected the battery level is not valid
if (!profile.isHT || (getInteger(profile.settings.externalPower) == 0)) {
addAttribute(properties, th, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
} else {
properties.put(CHANNEL_SENSOR_BAT_LEVEL, "USB");
}
String wiFiSignal = getString(properties.get(CHANNEL_DEVST_RSSI));
if (!wiFiSignal.isEmpty()) {
properties.put("wifiSignalRssi", wiFiSignal + " / " + stats.wifiRssi + " dBm");
properties.put("imgWiFi", "imgWiFi" + wiFiSignal);
}
if (profile.settings.sntp != null) {
properties.put(ATTRIBUTE_SNTP_SERVER, getString(profile.settings.sntp.server) + ", enabled: " + getBool((profile.settings.sntp.enabled)));
}
boolean coiotEnabled = true;
if ((profile.settings.coiot != null) && (profile.settings.coiot.enabled != null)) {
coiotEnabled = profile.settings.coiot.enabled;
}
properties.put(ATTRIBUTE_COIOT_STATUS, !coiotEnabled ? "Disbaled in settings" : "Events are " + (config.eventsCoIoT ? "enabled" : "disabled"));
properties.put(ATTRIBUTE_COIOT_PEER, (profile.settings.coiot != null) && !getString(profile.settings.coiot.peer).isEmpty() ? profile.settings.coiot.peer : "Multicast");
if (profile.status.cloud != null) {
properties.put(ATTRIBUTE_CLOUD_STATUS, getBool(profile.settings.cloud.enabled) ? getBool(profile.status.cloud.connected) ? "connected" : "enabled" : "disabled");
} else {
properties.put(ATTRIBUTE_CLOUD_STATUS, "unknown");
}
if (profile.status.mqtt != null) {
properties.put(ATTRIBUTE_MQTT_STATUS, getBool(profile.settings.mqtt.enable) ? getBool(profile.status.mqtt.connected) ? "connected" : "enabled" : "disabled");
} else {
properties.put(ATTRIBUTE_MQTT_STATUS, "unknown");
}
String statusIcon = "";
ThingStatus ts = th.getThing().getStatus();
switch(ts) {
case UNINITIALIZED:
case REMOVED:
case REMOVING:
statusIcon = ICON_UNINITIALIZED;
break;
case OFFLINE:
ThingStatusDetail sd = th.getThing().getStatusInfo().getStatusDetail();
if (uid.contains(THING_TYPE_SHELLYUNKNOWN_STR) || (sd == ThingStatusDetail.CONFIGURATION_ERROR) || (sd == ThingStatusDetail.HANDLER_CONFIGURATION_PENDING)) {
statusIcon = ICON_CONFIG;
break;
}
default:
statusIcon = ts.toString();
}
properties.put(ATTRIBUTE_STATUS_ICON, statusIcon.toLowerCase());
return properties;
}
use of org.openhab.binding.shelly.internal.api.ShellyDeviceProfile in project openhab-addons by openhab.
the class ShellyManagerOverviewPage method generateContent.
@Override
public ShellyMgrResponse generateContent(String path, Map<String, String[]> parameters) throws ShellyApiException {
String filter = getUrlParm(parameters, URLPARM_FILTER).toLowerCase();
String action = getUrlParm(parameters, URLPARM_ACTION).toLowerCase();
String uidParm = getUrlParm(parameters, URLPARM_UID).toLowerCase();
logger.debug("Generating overview for {} devices", getThingHandlers().size());
String html = "";
Map<String, String> properties = new HashMap<>();
properties.put(ATTRIBUTE_METATAG, "<meta http-equiv=\"refresh\" content=\"60\" />");
properties.put(ATTRIBUTE_CSS_HEADER, loadHTML(OVERVIEW_HEADER, properties));
String deviceHtml = "";
TreeMap<String, ShellyManagerInterface> sortedMap = new TreeMap<>();
for (Map.Entry<String, ShellyManagerInterface> th : getThingHandlers().entrySet()) {
// sort by Device Name
ShellyManagerInterface handler = th.getValue();
sortedMap.put(getDisplayName(handler.getThing().getProperties()), handler);
}
html = loadHTML(HEADER_HTML, properties);
html += loadHTML(OVERVIEW_HTML, properties);
int filteredDevices = 0;
for (Map.Entry<String, ShellyManagerInterface> handler : sortedMap.entrySet()) {
try {
ShellyManagerInterface th = handler.getValue();
ThingStatus status = th.getThing().getStatus();
ShellyDeviceProfile profile = th.getProfile();
// handler.getKey();
String uid = getString(th.getThing().getUID().getAsString());
if (action.equals(ACTION_REFRESH) && (uidParm.isEmpty() || uidParm.equals(uid))) {
// Refresh thing status, this is asynchronosly and takes 0-3sec
th.requestUpdates(1, true);
} else if (action.equals(ACTION_RES_STATS) && (uidParm.isEmpty() || uidParm.equals(uid))) {
th.resetStats();
} else if (action.equals(ACTION_OTACHECK) && (uidParm.isEmpty() || uidParm.equals(uid))) {
th.resetStats();
}
Map<String, String> warnings = getStatusWarnings(th);
if (applyFilter(th, filter) || (filter.equals(FILTER_ATTENTION) && !warnings.isEmpty())) {
filteredDevices++;
properties.clear();
fillProperties(properties, uid, handler.getValue());
String deviceType = getDeviceType(properties);
properties.put(ATTRIBUTE_DISPLAY_NAME, getDisplayName(properties));
properties.put(ATTRIBUTE_DEV_STATUS, fillDeviceStatus(warnings));
if (!warnings.isEmpty() && (status != ThingStatus.UNKNOWN)) {
properties.put(ATTRIBUTE_STATUS_ICON, ICON_ATTENTION);
}
if (!"unknown".equalsIgnoreCase(deviceType) && (status == ThingStatus.ONLINE)) {
properties.put(ATTRIBUTE_FIRMWARE_SEL, fillFirmwareHtml(uid, deviceType, profile.mode));
properties.put(ATTRIBUTE_ACTION_LIST, fillActionHtml(th, uid));
} else {
properties.put(ATTRIBUTE_FIRMWARE_SEL, "");
properties.put(ATTRIBUTE_ACTION_LIST, "");
}
html += loadHTML(OVERVIEW_DEVICE, properties);
}
} catch (ShellyApiException e) {
logger.debug("{}: Exception", LOG_PREFIX, e);
}
}
properties.clear();
properties.put("numberDevices", "<span class=\"footerDevices\">" + "Number of devices: " + filteredDevices + " of " + String.valueOf(getThingHandlers().size()) + " </span>");
properties.put(ATTRIBUTE_CSS_FOOTER, loadHTML(OVERVIEW_FOOTER, properties));
html += deviceHtml + loadHTML(FOOTER_HTML, properties);
return new ShellyMgrResponse(fillAttributes(html, properties), HttpStatus.OK_200);
}
use of org.openhab.binding.shelly.internal.api.ShellyDeviceProfile in project openhab-addons by openhab.
the class ShellyManagerOverviewPage method applyFilter.
private boolean applyFilter(ShellyManagerInterface handler, String filter) {
ThingStatus status = handler.getThing().getStatus();
ShellyDeviceProfile profile = handler.getProfile();
switch(filter) {
case FILTER_ONLINE:
return status == ThingStatus.ONLINE;
case FILTER_INACTIVE:
return status != ThingStatus.ONLINE;
case FILTER_ATTENTION:
return false;
case FILTER_UPDATE:
// return handler.getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE) == OnOffType.ON;
return getBool(profile.status.hasUpdate);
case FILTER_UNPROTECTED:
return !profile.auth;
case "*":
default:
return true;
}
}
Aggregations