From c1f0fcde3f4ccfe1116cd45eed29f6244dab32bb Mon Sep 17 00:00:00 2001 From: Marco Malavolti <marco.malavolti@garr.it> Date: Sun, 30 Jul 2023 04:18:41 +0200 Subject: [PATCH] Update Docker implementation. Missing CRON --- Dockerfile-dev | 59 +++++++++++++++++------------ eccs_properties.py | 92 ++++++++++++++++++++++++++++++++++++++------- get-sps-metadata.sh | 4 +- setup-eccs-dev.sh | 2 +- start.sh | 4 +- supervisord.conf | 49 ++++++++++++++++++++---- web/index.php | 8 ++-- 7 files changed, 166 insertions(+), 52 deletions(-) diff --git a/Dockerfile-dev b/Dockerfile-dev index a712a79..c881d1a 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -3,31 +3,34 @@ LABEL Authors="Marco Malavolti <marco.malavolti@garr.it>" USER root -ENV ECCS_VERSION=2.1.0 ENV XMLSECTOOL_VERSION=3.0.0 ENV AMAZON_JDK_KEY=https://corretto.aws/downloads/resources/11.0.6.10.1/B04F24E3.pub COPY --from=hairyhenderson/gomplate:v3.11.5 /gomplate /bin/gomplate RUN apt-get update \ - && apt-get install --no-install-recommends -y apt-utils vim git bash-completion \ - ca-certificates curl unzip uwsgi cron gpg gpg-agent libxml2-utils supervisor \ + && apt-get install -y apt-utils net-tools vim bash-completion \ + ca-certificates curl unzip uwsgi uwsgi-plugin-python3 cron gpg gpg-agent libxml2-utils \ python3 python3-pip python3-click python3-flask python3-flask-restful \ - python3-requests python3-selenium python3-urllib3 apache2 \ - libpcre3 libpcre3-dev libapache2-mod-proxy-uwsgi build-essential python3-dev \ + python3-requests python3-selenium python3-urllib3 apache2 supervisor php \ + libpcre3 libapache2-mod-proxy-uwsgi build-essential \ && sed -i -e 's/"syntax on/syntax on/g' /etc/vim/vimrc \ && printf "\nif [ -f /etc/bash_completion ]; then\n . /etc/bash_completion\nfi" >> /etc/profile -COPY supervisord.conf /etc/supervisor/conf.d/ - # Get ECCS WORKDIR /root -RUN curl "https://gitlab.software.geant.org/edugain/eccs/-/archive/v$ECCS_VERSION/eccs-v$ECCS_VERSION.tar.gz" --output eccs-v$ECCS_VERSION.tar.gz \ - && tar xzf eccs-v$ECCS_VERSION.tar.gz && rm eccs-v$ECCS_VERSION.tar.gz \ - && mv eccs-v$ECCS_VERSION eccs -# Get Google Chrome & Google Chrome Driver +RUN mkdir eccs WORKDIR eccs +COPY api.py clean7daysOldFiles.sh cleanAndRunEccs.sh eccs.ini eccs.py eccs_properties.py eccs-sps-md-cron eccs-wsgi.py get-sps-metadata.sh retryFailedChecks.py runEccs.py utils.py . +COPY html/ html/ +COPY input/ input/ +COPY logs/ logs/ +COPY output/ output/ +COPY selenium-logs/ selenium-logs/ +COPY web/ web/ + +# Get Google Chrome & Google Chrome Driver RUN curl "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" --output google-chrome-stable_current_amd64.deb \ && apt install -y ./google-chrome-stable_current_amd64.deb --no-install-recommends \ && curl "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/115.0.5790.110/linux64/chromedriver-linux64.zip" --output chromedriver_linux64.zip \ @@ -42,28 +45,36 @@ RUN wget $AMAZON_JDK_KEY -O /tmp/amazon-corretto.pub \ && gpg --no-default-keyring --keyring /tmp/temp-keyring.gpg --export --output /etc/apt/keyrings/amazon-corretto.gpg \ && rm /tmp/temp-keyring.gpg COPY amazon-corretto.list /etc/apt/sources.list.d/amazon-corretto.list +COPY eccs-sps-md-cron /etc/cron.d/eccs_get_sps_metadata RUN apt-get update && apt-get install -y java-11-amazon-corretto-jdk \ && curl "https://shibboleth.net/downloads/tools/xmlsectool/$XMLSECTOOL_VERSION/xmlsectool-$XMLSECTOOL_VERSION-bin.zip" --output xmlsectool-$XMLSECTOOL_VERSION-bin.zip \ && unzip xmlsectool-$XMLSECTOOL_VERSION-bin.zip \ + && mv xmlsectool-$XMLSECTOOL_VERSION xmlsectool \ + && echo "$XMLSECTOOL_VERSION" > xmlsectool/version.txt \ && rm xmlsectool-$XMLSECTOOL_VERSION-bin.zip \ && curl "https://mdx.idem.garr.it/idem-mdx-service-crt.pem" --output idem-mdx-service-crt.pem -COPY get-sps-metadata.sh get-sps-metadata.sh -COPY eccs_properties.py.template eccs_properties.py -COPY eccs-sps-md-cron /etc/cron.d/eccs_get_sps_metadata - # Install ECCS API -COPY eccs.ini eccs.ini -COPY eccs.service eccs.service -COPY eccs.service /etc/systemd/system/eccs.service - +#COPY eccs.service /etc/systemd/system/multi-user.target.wants/eccs.service +COPY eccs.conf /etc/apache2/conf-available/eccs.conf +RUN ln -s /etc/apache2/conf-available/eccs.conf /etc/apache2/conf-enabled/eccs.conf \ + && ln -s /etc/apache2/mods-available/proxy.conf /etc/apache2/mods-enabled/proxy.conf \ + && ln -s /etc/apache2/mods-available/proxy.load /etc/apache2/mods-enabled/proxy.load \ + && ln -s /etc/apache2/mods-available/proxy_uwsgi.load /etc/apache2/mods-enabled/proxy_uwsgi.load \ + && chmod 755 /root EXPOSE 80 EXPOSE 443 -CMD tail -f /dev/null +#CMD tail -f /dev/null + +#SUPERVISOR +COPY supervisord.conf /etc/supervisor/supervisord.conf +RUN mkdir -p /var/log/supervisor \ + && mkdir -p /var/run/supervisord \ + && chmod -R 0755 /var/log/supervisor + +COPY start.sh /start.sh +RUN chmod +x /start.sh +CMD /start.sh -##COPY start-dev.sh /start.sh -#COPY start.sh /start.sh -#RUN chmod +x /start.sh -#CMD /start.sh diff --git a/eccs_properties.py b/eccs_properties.py index eb63097..c467610 100644 --- a/eccs_properties.py +++ b/eccs_properties.py @@ -1,8 +1,49 @@ import os +import random +import string from datetime import date +import xml.etree.ElementTree as ET -DAY = date.today().isoformat() +def get_real_sps(): + sps_list = [] + + namespaces = { + 'md': 'urn:oasis:names:tc:SAML:2.0:metadata', + } + + sp_1_entityid = "https://sp-demo.idem.garr.it/shibboleth" + sp_2_entityid = "https://attribute-viewer.aai.switch.ch/interfederation-test/shibboleth" + + tree = ET.parse(SPS_MD_PATH) + root = tree.getroot() + + sp_1 = root.find(f"./md:EntityDescriptor[@entityID='{sp_1_entityid}']/md:SPSSODescriptor/md:AssertionConsumerService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']", namespaces) + sp_2 = root.find(f"./md:EntityDescriptor[@entityID='{sp_2_entityid}']/md:SPSSODescriptor/md:AssertionConsumerService[@Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']", namespaces) + + sp_1_http_post_acs = sp_1.get("Location") + sp_2_http_post_acs = sp_2.get("Location") + + # SP 1 + sps_list.append({ + "entityID":f"{sp_1_entityid}", + "http_post_acs_location":f"{sp_1_http_post_acs}" + }) + # SP 2 + sps_list.append({ + "entityID":f"{sp_2_entityid}", + "http_post_acs_location":f"{sp_2_http_post_acs}" + }) + + return sps_list + +def get_fake_sp_name(): + chars = string.ascii_lowercase + return ''.join(random.choice(chars) for x in range(10))+'.org' + +# Miscellaneous +DAY = date.today().isoformat() +CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt" ECCS_DIR = f"{os.environ['HOME']}/eccs" PATHCHROMEDRIVER = f"{ECCS_DIR}/chromedriver" ECCS_PYTHON = f"{ECCS_DIR}/python/bin/python3" @@ -19,11 +60,14 @@ ECCS_OUTPUTDIR = f"{ECCS_DIR}/output" ECCS_RESULTSLOG = f"eccs_{DAY}.log" ECCS_HTMLDIR = f"{ECCS_DIR}/html" +# SPS Metadata +SPS_MD_PATH = f"{ECCS_INPUTDIR}/sps-metadata.xml" + # Selenium ECCS_SELENIUMDEBUG = False ECCS_SELENIUMLOGDIR = f"{ECCS_DIR}/selenium-logs" -ECCS_SELENIUMPAGELOADTIMEOUT = 30 #seconds (remind to change timeout seconds also on web/eccs.js) -ECCS_SELENIUMSCRIPTTIMEOUT = 30 #seconds +ECCS_SELENIUMPAGELOADTIMEOUT = 60 #seconds (remind to change timeout seconds also on web/eccs.js) +ECCS_SELENIUMSCRIPTTIMEOUT = 60 #seconds ECCS_REQUESTSTIMEOUT = 15 #seconds # Logs @@ -36,22 +80,44 @@ ECCS_STDERRIDP = f"{ECCS_LOGSDIR}/stderr_idp_{DAY}.log" ECCS_FAILEDCMDIDP = f"{ECCS_LOGSDIR}/failed-cmd-idp.sh" # Number of processes to run in parallel -ECCS_NUMPROCESSES = 35 +ECCS_NUMPROCESSES = 30 -# The 2 SPs that will be used to test each IdP +# The 3 SPs that will be used to test each IdP ECCS_SPS = [ - "https://sp-demo.idem.garr.it/Shibboleth.sso/Login?entityID=", - "https://attribute-viewer.aai.switch.ch/interfederation-test/Shibboleth.sso/Login?entityID=" + { + "entityID":f"{get_real_sps()[0]['entityID']}", + "http_post_acs_location":f"{get_real_sps()[0]['http_post_acs_location']}", + }, + { + "entityID":f"{get_real_sps()[1]['entityID']}", + "http_post_acs_location":f"{get_real_sps()[1]['http_post_acs_location']}", + }, + { + "entityID":f"https://{get_fake_sp_name()}/shibboleth", + "http_post_acs_location":f"https://{get_fake_sp_name()}/Shibboleth.sso/SAML2/POST", + } ] # ROBOTS.TXT ROBOTS_USER_AGENT = "ECCS/2.0 (+https://technical.edugain.org/eccs)" # PATTERNS -IDPERROR = "error.occurred" -METADATAPATTERN = "Unable.to.locate(\sissuer.in|).metadata(\sfor|)|no.metadata.found|profile.is.not.configured.for.relying.party|Cannot.locate.entity|fail.to.load.unknown.provider|does.not.recognise.the.service|unable.to.load.provider|Nous.n'avons.pas.pu.(charg|charger).le.fournisseur.de service|Metadata.not.found|application.you.have.accessed.is.not.registered.for.use.with.this.service|Message.did.not.meet.security.requirements|Unsupported.Request|Not.Authorized|METADATANOTFOUND|Unknown.login.requester|is.unspecified.or.unsupported|Unknown.service.provider|Richiesta.non.supportata|Metadati.non.trovati|untrusted.provider|Unregistered.Service|Unsupported.request|UNHANDLEDEXCEPTION|Metadata.*.expired|Could.not.find.any.*.metadata.*.for" -PASSWORDPATTERN = '<input[\s]+[^>]*(type=\s*[\'"]password[\'"]|password)[^>]*>' -REFUSEDPATTERN = '(^http)(.*\.png$)|(.*\.css$)|(.*\.js$)|(.*\.gif$)|(.*\.svg$)|(.*\.jpg$)' +JAVASCRIPT = '"x-my-okta-version"' +IDPERROR = "error\s(has\s)?occur(r)?(ed)$|Error\swhen\sprocessing\s(the\s)?authentication\srequest|The\s(server|system)\sencountered\san\s(internal\s)?error|Internal\sServer\sError|403\sForbidden|Service\sUnavailable|InvalidProfileConfiguration|Unexpected\sSystem\sError|404\s(.\s)?not\sfound|OpenAthens:\s404|On\stapahtunut\svirhe|Unhandled\sexception|Bad\sGateway|Page\sNot\sFound|Δεν\sεπιτρέπεται\sη\sπρόσβαση|tempora(ry|rily)\s(unavailable|error)+|License\serror|n'est\spas\sgérée|Invalid\sRequest|Erreur\s!|Please\sreport\sthis\serror\sto|该网站无法访问|proxy\serror|There\sis\sa\sproblem\swith\syour\saccount" +METADATAPATTERN = "Unable\sto\slocate(\sissuer\sin|)\smetadata(\sfor|)|no\smetadata\sfound|profile\sis\snot\sconfigured\sfor\srelying\sparty|Cannot\slocate\sentity|fail\sto\sload\sunknown\sprovider|does\snot\srecognise\sthe\sservice|unable\sto\sload\sprovider|Nous\sn'avons\spas\spu\s(charg|charger)\sle\sfournisseur\sde\sservice|Metadata\snot\sfound|application\s(you\shave\saccessed\s)?is\snot\sregistered\s(for\suse\sthis\sservice)?|Message\sdid\snot\smeet\ssecurity\srequirements|unsupported\s[Rr]equest|METADATANOTFOUND|Unknown\slogin\srequester|is\sunspecified\sor\sunsupported|Unknown\sservice\sprovider|Richiesta\snon\ssupportata|Metadati\snon\strovati|untrusted\sprovider|Unregistered\sService|UNHANDLEDEXCEPTION|Metadata.*.expired|Could\snot\sfind\sany.*.metadata.*.for|不支持的请求|l'application\sn'est\spas\senregistrée|Requisição\snão\ssuportada|トされていないリクエスト|is\snot\sallowed|Authorization\sFailure|Pedido\snão\ssuportado|Nicht\sunterstützte\sAnfrage|Service\sNot\sAuthorized\sfor\sSingle\sSign-On|Your\sbrowser\ssent\sa\srequest\sthat\sthis\sserver\scould\snot\sunderstand|Application\sNot\sAuthorized\sTo\sUse\sCAS" +XPATH_CHECK_PATTERN = '//input[@type="password"]|//input[@type="Password"]|//input[@type="email"]|//input[@type="user"]|//input[@name="name"]|//form[@action="/idp/module.php/multiauth/selectsource.php"]|//input[@type="text"]' +#PASSWORDPATTERN = '<input[\s]+[^>]*(type=\s*[\'"]password[\'"]|password)[^>]*>' +#PASSWORDPATTERN = '<input[\s]+[^>]*((type|name)=\s*[\'"]password|email|user|text|name[\'"]|password|email|user|text|name)[^>]*>|<form[\s]+[^>]*(action)=\s*[\'"]/idp/module.php/multiauth/selectsource.php[\'"][^>]*>' +#PASSWORDPATTERN = '<input[\s]+[^>]*((type|name)=\s*"password|email|user|text|name"|password|email|user|text|name)[^>]*>|<form[\s]+[^>]*(action)=\s*"/idp/module.php/multiauth/selectsource.php"[^>]*>' +#PASSWORDPATTERN = '<input[\s]+[^>]*((type=|name=)["]?(text|password|email)["]?)[\s]+[^>]*>|<form[\s]+[^>]*(action)=\s*"/idp/module.php/multiauth/selectsource.php"[^>]*>' +PASSWORDPATTERN = '<input[\s][^>]*(type=|name=)"?(.*passw(or)?d)"?.*[^>]>$|<form[\s]+[^>]*(action)=\s*"/idp/module.php/multiauth/selectsource.php"[^>]*>' + +### PRODUCTION ECCS +# PASSWORDPATTERN = '<input[\s]+[^>]*((type|name)=\s*"password|email|user|text|name"|password|email|user|text|name)[^>]*>|<form[\s]+[^>]*(action)=\s*"/idp/module.php/multiauth/selectsource.php"[^>]*>' +### END + +#USERNAMEPATTERN = '<input[\s]+[^>]*((type=\s*[\'"](text|email)[\'"]|user)|(name=\s*[\'"](name)[\'"]))[^>]*>' +#REFUSEDPATTERN = '(^http)(.*\.png$)|(.*\.css$)|(.*\.js$)|(.*\.gif$)|(.*\.svg$)|(.*\.jpg$)' # { 'reg_auth':'reason' } FEDS_DISABLED_DICT = { @@ -64,7 +130,7 @@ FEDS_DISABLED_DICT = { IDPS_DISABLED_DICT = { 'https://idp.eie.gr/idp/shibboleth':'Disabled on 2019-04-24 because ECCS cannot check non-standard login page', 'https://edugain-proxy.igtf.net/simplesaml/saml2/idp/metadata.php':'Disabled on 2017-03-17 on request of federation operator', - 'https://gn-vho.grnet.gr/idp/shibboleth':'Disabled on 2019-04-24 because basic authentication is not supported by ECCS check', + #'https://gn-vho.grnet.gr/idp/shibboleth':'Disabled on 2019-04-24 because basic authentication is not supported by ECCS check', 'https://wtc.tu-chemnitz.de/shibboleth':'Disabled on 2019-02-26 because ECCS cannot check non-standard login page', 'https://idp.fraunhofer.de/idp/shibboleth':'Disabled on 2017-11-24 on request of federation operator', 'https://idp.dfn-cert.de/idp/shibboleth':'Disabled on 2018-04-05 on request of federation operator', @@ -72,7 +138,7 @@ IDPS_DISABLED_DICT = { 'https://login.lstonline.ac.uk/idp/pingfederate':'Disabled on 2017-02-08 on request of federation operator', 'https://indiid.net/idp/shibboleth':'Disabled on 2017-10-27 on request of federation operator', 'https://idp.nulc.ac.uk/openathens':'Disabled on 2017-10-27 on request of federation operator', - 'https://lc-idp.lincolncollege.ac.uk/shibboleth':'Disabled on 2015-08-17 because uses HTTP Basic authentication, which cannot be checked reliably', +# 'https://lc-idp.lincolncollege.ac.uk/shibboleth':'Disabled on 2015-08-17 because uses HTTP Basic authentication, which cannot be checked reliably', 'https://idp.wnsc.ac.uk/idp/shibboleth':'Disabled on 2017-10-27 on request of federation operator', # 'https://idp.strodes.ac.uk/shibboleth':'Disabled on 2015-08-17 because uses HTTP Basic authentication, which cannot be checked reliably', 'https://idp.uel.ac.uk/shibboleth':'Disabled on 2017-10-27 on request of federation operator', diff --git a/get-sps-metadata.sh b/get-sps-metadata.sh index 4aa5422..7db93e3 100755 --- a/get-sps-metadata.sh +++ b/get-sps-metadata.sh @@ -14,8 +14,8 @@ if [ ! -f "$sp_md_1" ] || [ ! -f "$sp_md_2" ]; then exit 1 fi -sp_md_1_is_valid=$(bash xmlsectool-3.0.0/xmlsectool.sh --verifySignature --certificate idem-mdx-service-crt.pem --inFile input/sp_md_1.xml | grep "XML document signature verified." | wc -l) -sp_md_2_is_valid=$(bash xmlsectool-3.0.0/xmlsectool.sh --verifySignature --certificate idem-mdx-service-crt.pem --inFile input/sp_md_2.xml | grep "XML document signature verified." | wc -l) +sp_md_1_is_valid=$(bash xmlsectool/xmlsectool.sh --verifySignature --certificate idem-mdx-service-crt.pem --inFile input/sp_md_1.xml | grep "XML document signature verified." | wc -l) +sp_md_2_is_valid=$(bash xmlsectool/xmlsectool.sh --verifySignature --certificate idem-mdx-service-crt.pem --inFile input/sp_md_2.xml | grep "XML document signature verified." | wc -l) # Check the validity of both SP metadata files if [ $sp_md_1_is_valid -eq 0 ] || [ $sp_md_1_is_valid -eq 0 ]; then diff --git a/setup-eccs-dev.sh b/setup-eccs-dev.sh index dc9f153..1175436 100755 --- a/setup-eccs-dev.sh +++ b/setup-eccs-dev.sh @@ -38,7 +38,7 @@ docker compose up -d ECCS_IP=$(docker inspect eccs | grep "IPAddress" | grep -E -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | tail -n 1) -DOMAIN_NAME="technical.edugain.org technical-test.edugain.org" +DOMAIN_NAME="eccs.edugain.org" modify_hosts_file "remove" "$DOMAIN_NAME" modify_hosts_file "add" "$DOMAIN_NAME" "$ECCS_IP" diff --git a/start.sh b/start.sh index 995b731..ee49a1d 100644 --- a/start.sh +++ b/start.sh @@ -1,6 +1,8 @@ #!/bin/bash -# ...other things... +bash /root/eccs/get-sps-metadata.sh + +touch /root/eccs/eccs.ini # Last command exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf diff --git a/supervisord.conf b/supervisord.conf index b7ff44a..288cab2 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -1,17 +1,50 @@ [supervisord] nodaemon=true +pidfile=/var/run/supervisord/supervisord.pid +user=root + +[unix_http_server] +file=/var/run/supervisor.sock +chmod=0777 + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [program:cron] command=cron -f +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true +autorestart=true + +[program:uwsgi] +#command=uwsgi --ini /root/eccs/eccs.ini --die-on-term +command=uwsgi --ini /root/eccs/eccs.ini --die-on-term +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autostart=true +autorestart=true + +[program:apache2] +command=/usr/sbin/apache2ctl -DFOREGROUND +killasgroup=true +stopasgroup=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stdout +stderr_logfile_maxbytes=0 autostart=true autorestart=true -#[program:uwsgi] -#command=uwsgi --ini /path/to/uwsgi_config.ini -#autostart=true -#autorestart=true +#[program:rsyslog] +#command=/usr/sbin/rsyslogd -n +#stdout_logfile=/dev/stdout +#stdout_logfile_maxbytes=0 -#[program:apache2] -#command=apache2ctl -DFOREGROUND -#autostart=true -#autorestart=true diff --git a/web/index.php b/web/index.php index ae3b006..dc7720c 100644 --- a/web/index.php +++ b/web/index.php @@ -15,7 +15,7 @@ $data['firstDate'] = $firstDate; $data['lastDate'] = $lastDate; $data['idp'] = htmlspecialchars($_GET["idp"]); $data['reg_auth'] = htmlspecialchars($_GET["reg_auth"]); -$data['date'] = (htmlspecialchars($_GET["date"]) ? htmlspecialchars($_GET["date"] : $lastDate); +$data['date'] = (htmlspecialchars($_GET["date"]) ? htmlspecialchars($_GET["date"]) : $lastDate); $data['status'] = htmlspecialchars($_GET["status"]); $data['check_result'] = htmlspecialchars($_GET["check_result"]); ?> @@ -24,6 +24,8 @@ $data['check_result'] = htmlspecialchars($_GET["check_result"]); <html> <head> <meta charset=utf-8 /> + <script type="text/javascript" src="https://code.jquery.com/jquery-3.7.0.min.js"></script> + <script type="text/javascript" src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script> <script type="text/javascript" src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css"/> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"/> @@ -69,9 +71,9 @@ $data['check_result'] = htmlspecialchars($_GET["check_result"]); </div> <!-- END clearFix --> <hr> </div> <!-- END status --> - <button id="btn-show-all-children" type="button">Expand All</button> + <!--<button id="btn-show-all-children" type="button">Expand All</button> <button id="btn-hide-all-children" type="button">Collapse All</button> - <hr> + <hr>--> <div class="container"> <div class="loader"></div> <table id="eccstable" class="cell-border" style="width:100%"> -- GitLab